fix: repair clash subscription toggle and separate clash path settings

This commit is contained in:
root 2026-04-26 00:43:09 +08:00
parent 67bf6c1a9a
commit d6de00cd00
6 changed files with 107 additions and 17 deletions

View file

@ -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.

View file

@ -19,14 +19,15 @@
"codemirror/lint/lint.css": "codemirror/lint/lint.5902b061.css", "codemirror/lint/lint.css": "codemirror/lint/lint.5902b061.css",
"codemirror/lint/lint.js": "codemirror/lint/lint.8d16a6b4.js", "codemirror/lint/lint.js": "codemirror/lint/lint.8d16a6b4.js",
"codemirror/xq.min.css": "codemirror/xq.min.18b37ea8.css", "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", "css/custom.min.css": "css/custom.min.7a7540cc.css",
"js/axios-init.js": "js/axios-init.5d82a152.js", "js/axios-init.js": "js/axios-init.5d82a152.js",
"js/model/dbinbound.js": "js/model/dbinbound.2232a7d6.js", "js/model/dbinbound.js": "js/model/dbinbound.2232a7d6.js",
"js/model/inbound.js": "js/model/inbound.afa36664.js", "js/model/inbound.js": "js/model/inbound.afa36664.js",
"js/model/outbound.js": "js/model/outbound.5b13ad17.js", "js/model/outbound.js": "js/model/outbound.5b13ad17.js",
"js/model/reality_targets.js": "js/model/reality_targets.6ca38c21.js", "js/model/reality_targets.js": "js/model/reality_targets.6ca38c21.js",
"js/model/setting.js": "js/model/setting.3c69ea22.js", "js/model/setting.js": "js/model/setting.a37c1aec.js",
"js/subscription.js": "js/subscription.d3d8665a.js", "js/subscription.js": "js/subscription.84ade419.js",
"js/util/index.js": "js/util/index.7ced4edf.js", "js/util/index.js": "js/util/index.7ced4edf.js",
"js/websocket.js": "js/websocket.938f634e.js", "js/websocket.js": "js/websocket.938f634e.js",
"moment/moment-jalali.min.js": "moment/moment-jalali.min.9d3db244.js", "moment/moment-jalali.min.js": "moment/moment-jalali.min.9d3db244.js",

View file

@ -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<i.inlineList&&!r&&","==t)return e.next(),"meta";if(0<i.inlinePairs&&!r&&","==t)return i.keyCol=0,i.pair=!1,i.pairStart=!1,e.next(),"meta";if(i.pairStart){if(e.match(/^\s*(\||\>)\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<i.inlinePairs&&e.match(/^\s*-?[0-9\.\,]+\s?(?=(,|}))/))return"number";if(e.match(n))return"keyword"}return!i.pair&&e.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\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")});

View file

@ -52,6 +52,10 @@ class AllSetting {
this.subJsonNoises = ""; this.subJsonNoises = "";
this.subJsonMux = ""; this.subJsonMux = "";
this.subJsonRules = ""; this.subJsonRules = "";
this.subClashEnable = false;
this.subClashPath = "/clash/";
this.subClashURI = "";
this.subClashTemplate = "";
this.timeLocation = "Local"; this.timeLocation = "Local";

View file

@ -9,6 +9,7 @@
sId: el.getAttribute('data-sid') || '', sId: el.getAttribute('data-sid') || '',
subUrl: el.getAttribute('data-sub-url') || '', subUrl: el.getAttribute('data-sub-url') || '',
subJsonUrl: el.getAttribute('data-subjson-url') || '', subJsonUrl: el.getAttribute('data-subjson-url') || '',
subClashUrl: el.getAttribute('data-subclash-url') || '',
download: el.getAttribute('data-download') || '', download: el.getAttribute('data-download') || '',
upload: el.getAttribute('data-upload') || '', upload: el.getAttribute('data-upload') || '',
used: el.getAttribute('data-used') || '', used: el.getAttribute('data-used') || '',
@ -99,6 +100,8 @@
const tpl = document.getElementById('subscription-data'); const tpl = document.getElementById('subscription-data');
const sj = tpl ? tpl.getAttribute('data-subjson-url') : ''; const sj = tpl ? tpl.getAttribute('data-subjson-url') : '';
if (sj) this.app.subJsonUrl = sj; if (sj) this.app.subJsonUrl = sj;
const sc = tpl ? tpl.getAttribute('data-subclash-url') : '';
if (sc) this.app.subClashUrl = sc;
drawQR(this.app.subUrl); drawQR(this.app.subUrl);
try { try {
const elJson = document.getElementById('qrcode-subjson'); const elJson = document.getElementById('qrcode-subjson');
@ -106,6 +109,12 @@
new QRious({ element: elJson, value: this.app.subJsonUrl, size: 220 }); new QRious({ element: elJson, value: this.app.subJsonUrl, size: 220 });
} }
} catch (e) { /* ignore */ } } 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; }; this._onResize = () => { this.viewportWidth = window.innerWidth; };
window.addEventListener('resize', this._onResize); window.addEventListener('resize', this._onResize);
}, },
@ -145,6 +154,10 @@
}, },
happUrl() { happUrl() {
return `happ://add/${this.app.subUrl}`; 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: { methods: {

View file

@ -171,20 +171,23 @@ var settingGroups = map[string]map[string]string{
"lang": "tgLang", "lang": "tgLang",
}, },
"subscriptionNetwork": { "subscriptionNetwork": {
"enable": "subEnable", "enable": "subEnable",
"jsonEnable": "subJsonEnable", "jsonEnable": "subJsonEnable",
"listen": "subListen", "clashEnable": "subClashEnable",
"port": "subPort", "listen": "subListen",
"path": "subPath", "port": "subPort",
"domain": "subDomain", "path": "subPath",
"certFile": "subCertFile", "jsonPath": "subJsonPath",
"keyFile": "subKeyFile", "clashPath": "subClashPath",
"updates": "subUpdates", "domain": "subDomain",
"encrypt": "subEncrypt", "certFile": "subCertFile",
"showInfo": "subShowInfo", "keyFile": "subKeyFile",
"uri": "subURI", "updates": "subUpdates",
"jsonPath": "subJsonPath", "encrypt": "subEncrypt",
"jsonURI": "subJsonURI", "showInfo": "subShowInfo",
"uri": "subURI",
"jsonURI": "subJsonURI",
"clashURI": "subClashURI",
}, },
"subscriptionBranding": { "subscriptionBranding": {
"title": "subTitle", "title": "subTitle",
@ -271,6 +274,7 @@ var legacySettingGroups = map[string]map[string]string{
"sub": { "sub": {
"enable": "subEnable", "enable": "subEnable",
"jsonEnable": "subJsonEnable", "jsonEnable": "subJsonEnable",
"clashEnable": "subClashEnable",
"title": "subTitle", "title": "subTitle",
"supportUrl": "subSupportUrl", "supportUrl": "subSupportUrl",
"profileUrl": "subProfileUrl", "profileUrl": "subProfileUrl",
@ -280,6 +284,8 @@ var legacySettingGroups = map[string]map[string]string{
"listen": "subListen", "listen": "subListen",
"port": "subPort", "port": "subPort",
"path": "subPath", "path": "subPath",
"jsonPath": "subJsonPath",
"clashPath": "subClashPath",
"domain": "subDomain", "domain": "subDomain",
"certFile": "subCertFile", "certFile": "subCertFile",
"keyFile": "subKeyFile", "keyFile": "subKeyFile",
@ -287,8 +293,8 @@ var legacySettingGroups = map[string]map[string]string{
"encrypt": "subEncrypt", "encrypt": "subEncrypt",
"showInfo": "subShowInfo", "showInfo": "subShowInfo",
"uri": "subURI", "uri": "subURI",
"jsonPath": "subJsonPath",
"jsonURI": "subJsonURI", "jsonURI": "subJsonURI",
"clashURI": "subClashURI",
"jsonFragment": "subJsonFragment", "jsonFragment": "subJsonFragment",
"jsonNoises": "subJsonNoises", "jsonNoises": "subJsonNoises",
"jsonMux": "subJsonMux", "jsonMux": "subJsonMux",