diff --git a/docs/Tasktracking/2026-04-26-fix-clash-subscription-toggle-and-path.md b/docs/Tasktracking/2026-04-26-fix-clash-subscription-toggle-and-path.md new file mode 100644 index 00000000..d9bf6967 --- /dev/null +++ b/docs/Tasktracking/2026-04-26-fix-clash-subscription-toggle-and-path.md @@ -0,0 +1,65 @@ +Task Record + +Date: 2026-04-26 +Related Module: web settings / subscription assets / setting service +Change Type: Fix + +Background + +The Clash subscription toggle in `/panel/settings` did not behave correctly in practice. +The page was loading an outdated fingerprinted frontend model from `web/public` that did not include Clash subscription fields, which caused inconsistent binding and update behavior for `subClashEnable` and related properties. +In addition, Clash subscription keys were not mapped in the settings grouping metadata, making their nested config representation inconsistent. + +Changes + +Regenerated fingerprinted frontend assets via `go run ./cmd/genassets` so the settings page now loads an updated `AllSetting` model containing: +- `subClashEnable` +- `subClashPath` +- `subClashURI` + +Updated settings group mappings in `web/service/setting.go`: +- Added Clash keys to `subscriptionNetwork`: + - `clashEnable -> subClashEnable` + - `clashPath -> subClashPath` + - `clashURI -> subClashURI` +- Added corresponding Clash keys to legacy `sub` mapping for compatibility. + +Impact + +Affected modules or files. +- `web/service/setting.go` +- `web/public/assets-manifest.json` +- `web/public/assets/js/model/setting.*.js` (fingerprinted replacement) +- `web/public/assets/js/subscription.*.js` (fingerprinted replacement) +- `web/public/assets/codemirror/yaml.*.js` (fingerprinted generated asset) + +Whether APIs, database, config, build, or compatibility are affected. +- API schema unchanged. +- Database unchanged. +- Settings file structure compatibility improved for Clash keys in nested/legacy mappings. +- Frontend static asset fingerprints updated. + +Whether upstream or downstream callers are affected. +- Panel settings frontend now correctly tracks and submits Clash subscription toggle/path fields. +- Subscription page uses refreshed asset bundle. + +Verification + +List validation commands or checks performed. +- `go run ./cmd/genassets` +- `go test -race ./web/service/...` + +State the result. +- Asset generation succeeded. +- Related service tests passed. + +If not verified, explain why. +- No live runtime verification against a deployed panel/subscription server was performed in this local environment. + +Risks And Follow-Up + +Remaining risks. +- Existing browser caches may keep old fingerprint mappings until refresh; after deploy/restart, hard refresh may still be needed in some clients. + +Recommended follow-up work. +- Verify in a deployed environment that toggling `Clash Subscription` and editing `subClashPath` immediately reflects expected behavior after restart/reload cycle. diff --git a/web/public/assets-manifest.json b/web/public/assets-manifest.json index 040e48f6..34fbca8c 100644 --- a/web/public/assets-manifest.json +++ b/web/public/assets-manifest.json @@ -19,14 +19,15 @@ "codemirror/lint/lint.css": "codemirror/lint/lint.5902b061.css", "codemirror/lint/lint.js": "codemirror/lint/lint.8d16a6b4.js", "codemirror/xq.min.css": "codemirror/xq.min.18b37ea8.css", + "codemirror/yaml.js": "codemirror/yaml.7f87153b.js", "css/custom.min.css": "css/custom.min.7a7540cc.css", "js/axios-init.js": "js/axios-init.5d82a152.js", "js/model/dbinbound.js": "js/model/dbinbound.2232a7d6.js", "js/model/inbound.js": "js/model/inbound.afa36664.js", "js/model/outbound.js": "js/model/outbound.5b13ad17.js", "js/model/reality_targets.js": "js/model/reality_targets.6ca38c21.js", - "js/model/setting.js": "js/model/setting.3c69ea22.js", - "js/subscription.js": "js/subscription.d3d8665a.js", + "js/model/setting.js": "js/model/setting.a37c1aec.js", + "js/subscription.js": "js/subscription.84ade419.js", "js/util/index.js": "js/util/index.7ced4edf.js", "js/websocket.js": "js/websocket.938f634e.js", "moment/moment-jalali.min.js": "moment/moment-jalali.min.9d3db244.js", diff --git a/web/public/assets/codemirror/yaml.7f87153b.js b/web/public/assets/codemirror/yaml.7f87153b.js new file mode 100644 index 00000000..f0bf4bac --- /dev/null +++ b/web/public/assets/codemirror/yaml.7f87153b.js @@ -0,0 +1 @@ +!function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){"use strict";e.defineMode("yaml",function(){var n=new RegExp("\\b(("+["true","false","on","off","yes","no"].join(")|(")+"))$","i");return{token:function(e,i){var t=e.peek(),r=i.escaped;if(i.escaped=!1,"#"==t&&(0==e.pos||/\s/.test(e.string.charAt(e.pos-1))))return e.skipToEnd(),"comment";if(e.match(/^('([^']|\\.)*'?|"([^"]|\\.)*"?)/))return"string";if(i.literal&&e.indentation()>i.keyCol)return e.skipToEnd(),"string";if(i.literal&&(i.literal=!1),e.sol()){if(i.keyCol=0,i.pair=!1,i.pairStart=!1,e.match("---"))return"def";if(e.match("..."))return"def";if(e.match(/\s*-\s+/))return"meta"}if(e.match(/^(\{|\}|\[|\])/))return"{"==t?i.inlinePairs++:"}"==t?i.inlinePairs--:"["==t?i.inlineList++:i.inlineList--,"meta";if(0)\s*/))return i.literal=!0,"meta";if(e.match(/^\s*(\&|\*)[a-z0-9\._-]+\b/i))return"variable-2";if(0==i.inlinePairs&&e.match(/^\s*-?[0-9\.\,]+\s?$/))return"number";if(0'"%@`][^\s'":]|[^,\[\]{}#&*!|>'"%@`])[^#]*?(?=\s*:($|\s))/)?(i.pair=!0,i.keyCol=e.indentation(),"atom"):i.pair&&e.match(/^:\s*/)?(i.pairStart=!0,"meta"):(i.pairStart=!1,i.escaped="\\"==t,e.next(),null)},startState:function(){return{pair:!1,pairStart:!1,keyCol:0,inlinePairs:0,inlineList:0,literal:!1,escaped:!1}},lineComment:"#",fold:"indent"}}),e.defineMIME("text/x-yaml","yaml"),e.defineMIME("text/yaml","yaml")}); \ No newline at end of file diff --git a/web/public/assets/js/model/setting.3c69ea22.js b/web/public/assets/js/model/setting.a37c1aec.js similarity index 95% rename from web/public/assets/js/model/setting.3c69ea22.js rename to web/public/assets/js/model/setting.a37c1aec.js index 8814bf43..a3c15dca 100644 --- a/web/public/assets/js/model/setting.3c69ea22.js +++ b/web/public/assets/js/model/setting.a37c1aec.js @@ -52,6 +52,10 @@ class AllSetting { this.subJsonNoises = ""; this.subJsonMux = ""; this.subJsonRules = ""; + this.subClashEnable = false; + this.subClashPath = "/clash/"; + this.subClashURI = ""; + this.subClashTemplate = ""; this.timeLocation = "Local"; diff --git a/web/public/assets/js/subscription.d3d8665a.js b/web/public/assets/js/subscription.84ade419.js similarity index 90% rename from web/public/assets/js/subscription.d3d8665a.js rename to web/public/assets/js/subscription.84ade419.js index 228dcfa0..9cc496df 100644 --- a/web/public/assets/js/subscription.d3d8665a.js +++ b/web/public/assets/js/subscription.84ade419.js @@ -9,6 +9,7 @@ sId: el.getAttribute('data-sid') || '', subUrl: el.getAttribute('data-sub-url') || '', subJsonUrl: el.getAttribute('data-subjson-url') || '', + subClashUrl: el.getAttribute('data-subclash-url') || '', download: el.getAttribute('data-download') || '', upload: el.getAttribute('data-upload') || '', used: el.getAttribute('data-used') || '', @@ -99,6 +100,8 @@ const tpl = document.getElementById('subscription-data'); const sj = tpl ? tpl.getAttribute('data-subjson-url') : ''; if (sj) this.app.subJsonUrl = sj; + const sc = tpl ? tpl.getAttribute('data-subclash-url') : ''; + if (sc) this.app.subClashUrl = sc; drawQR(this.app.subUrl); try { const elJson = document.getElementById('qrcode-subjson'); @@ -106,6 +109,12 @@ new QRious({ element: elJson, value: this.app.subJsonUrl, size: 220 }); } } catch (e) { /* ignore */ } + try { + const elClash = document.getElementById('qrcode-subclash'); + if (elClash && this.app.subClashUrl) { + new QRious({ element: elClash, value: this.app.subClashUrl, size: 220 }); + } + } catch (e) { /* ignore */ } this._onResize = () => { this.viewportWidth = window.innerWidth; }; window.addEventListener('resize', this._onResize); }, @@ -145,6 +154,10 @@ }, happUrl() { return `happ://add/${this.app.subUrl}`; + }, + clashvergeUrl() { + const url = this.app.subClashUrl || this.app.subUrl; + return `clash-verge://install-config?url=${encodeURIComponent(url)}&name=${encodeURIComponent(this.app.sId)}`; } }, methods: { diff --git a/web/service/setting.go b/web/service/setting.go index fab7ffdb..39d1ba57 100644 --- a/web/service/setting.go +++ b/web/service/setting.go @@ -171,20 +171,23 @@ var settingGroups = map[string]map[string]string{ "lang": "tgLang", }, "subscriptionNetwork": { - "enable": "subEnable", - "jsonEnable": "subJsonEnable", - "listen": "subListen", - "port": "subPort", - "path": "subPath", - "domain": "subDomain", - "certFile": "subCertFile", - "keyFile": "subKeyFile", - "updates": "subUpdates", - "encrypt": "subEncrypt", - "showInfo": "subShowInfo", - "uri": "subURI", - "jsonPath": "subJsonPath", - "jsonURI": "subJsonURI", + "enable": "subEnable", + "jsonEnable": "subJsonEnable", + "clashEnable": "subClashEnable", + "listen": "subListen", + "port": "subPort", + "path": "subPath", + "jsonPath": "subJsonPath", + "clashPath": "subClashPath", + "domain": "subDomain", + "certFile": "subCertFile", + "keyFile": "subKeyFile", + "updates": "subUpdates", + "encrypt": "subEncrypt", + "showInfo": "subShowInfo", + "uri": "subURI", + "jsonURI": "subJsonURI", + "clashURI": "subClashURI", }, "subscriptionBranding": { "title": "subTitle", @@ -271,6 +274,7 @@ var legacySettingGroups = map[string]map[string]string{ "sub": { "enable": "subEnable", "jsonEnable": "subJsonEnable", + "clashEnable": "subClashEnable", "title": "subTitle", "supportUrl": "subSupportUrl", "profileUrl": "subProfileUrl", @@ -280,6 +284,8 @@ var legacySettingGroups = map[string]map[string]string{ "listen": "subListen", "port": "subPort", "path": "subPath", + "jsonPath": "subJsonPath", + "clashPath": "subClashPath", "domain": "subDomain", "certFile": "subCertFile", "keyFile": "subKeyFile", @@ -287,8 +293,8 @@ var legacySettingGroups = map[string]map[string]string{ "encrypt": "subEncrypt", "showInfo": "subShowInfo", "uri": "subURI", - "jsonPath": "subJsonPath", "jsonURI": "subJsonURI", + "clashURI": "subClashURI", "jsonFragment": "subJsonFragment", "jsonNoises": "subJsonNoises", "jsonMux": "subJsonMux",