tt-help-cli-ycl 1.4.168 → 1.4.171
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/test-subscription-allocator.mjs +220 -0
- package/scripts/test-subscription-auto-alloc-ui.mjs +208 -0
- package/scripts/test-subscription-auto-alloc.mjs +259 -0
- package/scripts/test-subscription-center-e2e.mjs +134 -0
- package/scripts/test-subscription-center-smoke.mjs +154 -0
- package/scripts/test-subscription-center-ui.mjs +111 -0
- package/scripts/vnc-login.sh +68 -0
- package/src/clash/runtime.js +22 -1
- package/src/cli/info.js +6 -1
- package/src/lib/browser/cdp.js +7 -1
- package/src/watch/public/app.js +586 -15
- package/src/watch/public/index.html +70 -3
- package/src/watch/subscription-allocator.js +255 -0
- package/src/watch/subscription-center.js +590 -0
- package/src/watch/watchdog-server.js +210 -0
- package/src/watchdog/agent.js +5 -0
- package/scripts/test-api-interceptor-429.mjs +0 -122
- package/scripts/test-api-interceptor-captcha.mjs +0 -176
- package/scripts/test-claim-fix.mjs +0 -216
- package/scripts/test-clash-discover.mjs +0 -126
- package/scripts/test-clash-hysteresis.mjs +0 -131
- package/scripts/test-clash-multichannel.mjs +0 -120
- package/scripts/test-client-error-monitor.mjs +0 -261
- package/scripts/test-country-fix.mjs +0 -341
- package/scripts/test-daemon-failover.mjs +0 -301
- package/scripts/test-daemon-switch-history.mjs +0 -141
- package/scripts/test-daemon-whitelist.mjs +0 -308
- package/scripts/test-explore-captcha-wait.mjs +0 -170
- package/scripts/test-photo-post.mjs +0 -156
- package/scripts/test-precheck-perf.mjs +0 -270
- package/scripts/test-precise-index.mjs +0 -114
- package/scripts/test-probe-live.mjs +0 -83
- package/scripts/test-probe.mjs +0 -315
- package/scripts/test-product-fill-cdp.mjs +0 -336
- package/scripts/test-product-fill-db.mjs +0 -216
- package/scripts/test-product-fill-http.mjs +0 -117
- package/scripts/test-proxy-switch-restart.mjs +0 -198
- package/scripts/test-refill-order.mjs +0 -218
- package/scripts/test-sample-all-fail.mjs +0 -142
- package/scripts/test-seller-any-country.mjs +0 -167
- package/scripts/test-stats-smoke.mjs +0 -43
- package/scripts/test-upgrade-spawn.mjs +0 -179
- package/scripts/test-webserver-error-classify.mjs +0 -409
- package/scripts/test-wmi-free-upgrade.mjs +0 -98
- package/src/cli/test-real-attach.js +0 -0
- package/src/results/user-videos-bar.lar.lar.moeta.json +0 -37
package/src/watch/public/app.js
CHANGED
|
@@ -710,20 +710,28 @@ const DEFAULT_LOCATION_OPTIONS = [
|
|
|
710
710
|
];
|
|
711
711
|
let locationOptions = [...DEFAULT_LOCATION_OPTIONS];
|
|
712
712
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
if (overlay) return;
|
|
716
|
-
// 优先使用服务端配置的目标国家作为候选,失败则回退默认
|
|
713
|
+
/** 从服务端拉取动态目标国家,刷新 locationOptions(失败回退默认) */
|
|
714
|
+
async function refreshLocationOptions() {
|
|
717
715
|
try {
|
|
718
716
|
const res = await fetch("/api/settings/target-locations");
|
|
719
717
|
const data = await res.json();
|
|
720
718
|
const locs = (data.locations || []).filter(Boolean);
|
|
721
719
|
if (locs.length) {
|
|
722
720
|
locationOptions = [...new Set([...locs, ...DEFAULT_LOCATION_OPTIONS])];
|
|
721
|
+
} else {
|
|
722
|
+
locationOptions = [...DEFAULT_LOCATION_OPTIONS];
|
|
723
723
|
}
|
|
724
724
|
} catch {
|
|
725
725
|
locationOptions = [...DEFAULT_LOCATION_OPTIONS];
|
|
726
726
|
}
|
|
727
|
+
return locationOptions;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
async function openLocationModal(uniqueId, currentLocation) {
|
|
731
|
+
let overlay = document.getElementById("locationModalOverlay");
|
|
732
|
+
if (overlay) return;
|
|
733
|
+
// 优先使用服务端配置的目标国家作为候选,失败则回退默认
|
|
734
|
+
await refreshLocationOptions();
|
|
727
735
|
overlay = document.createElement("div");
|
|
728
736
|
overlay.id = "locationModalOverlay";
|
|
729
737
|
overlay.className = "modal-overlay";
|
|
@@ -1920,7 +1928,7 @@ async function saveLlmConfig() {
|
|
|
1920
1928
|
|
|
1921
1929
|
// ========== 添加关键词弹窗 ==========
|
|
1922
1930
|
|
|
1923
|
-
function openAddTagModal() {
|
|
1931
|
+
async function openAddTagModal() {
|
|
1924
1932
|
let overlay = document.getElementById("addTagModalOverlay");
|
|
1925
1933
|
if (overlay) return;
|
|
1926
1934
|
overlay = document.createElement("div");
|
|
@@ -1948,9 +1956,12 @@ function openAddTagModal() {
|
|
|
1948
1956
|
if (e.target === overlay) closeAddTagModal();
|
|
1949
1957
|
});
|
|
1950
1958
|
|
|
1951
|
-
//
|
|
1959
|
+
// 渲染国家复选框(每次打开都拉取动态目标国家列表,失败回退默认)
|
|
1952
1960
|
const countryGrid = document.getElementById("addTagCountryCheckboxes");
|
|
1953
|
-
countryGrid.innerHTML =
|
|
1961
|
+
countryGrid.innerHTML =
|
|
1962
|
+
'<span style="color:#888;font-size:12px">加载国家列表...</span>';
|
|
1963
|
+
const options = await refreshLocationOptions();
|
|
1964
|
+
countryGrid.innerHTML = options
|
|
1954
1965
|
.map(
|
|
1955
1966
|
(c) =>
|
|
1956
1967
|
`<label class="checkbox-label"><input type="checkbox" value="${c}" checked> ${c}</label>`,
|
|
@@ -4078,6 +4089,7 @@ async function fetchWatchdogData() {
|
|
|
4078
4089
|
const res = await fetch("/api/watchdog/clients");
|
|
4079
4090
|
_wdData = await res.json();
|
|
4080
4091
|
updateWatchdogBadge(_wdData.clients || [], _wdData.clientCommands || {});
|
|
4092
|
+
renderSubscriptionsCenter(_wdData.subscriptions || []);
|
|
4081
4093
|
renderWatchdogDashboard(
|
|
4082
4094
|
_wdData.clients || [],
|
|
4083
4095
|
_wdData.clientCommands || {},
|
|
@@ -4753,20 +4765,193 @@ async function wdSetCountries() {
|
|
|
4753
4765
|
|
|
4754
4766
|
var _wdProxyUserId = "";
|
|
4755
4767
|
|
|
4768
|
+
/** 读取某节点的 proxySettings 配置项 */
|
|
4769
|
+
function wdGetProxyEntry(userId) {
|
|
4770
|
+
return (_wdData && _wdData.proxySettings && _wdData.proxySettings[userId]) || null;
|
|
4771
|
+
}
|
|
4772
|
+
|
|
4773
|
+
/** 节点列表用模式徽标: auto=⚡自动N条(告警标色);manual/无配置返回空串 */
|
|
4774
|
+
function wdProxyModeBadge(userId) {
|
|
4775
|
+
var e = wdGetProxyEntry(userId);
|
|
4776
|
+
if (!e || e.mode !== "auto") return "";
|
|
4777
|
+
var color = e.allocWarn === "pool-empty" ? "#fbbf24" : e.allocWarn === "count-capped" ? "#f87171" : "#4ade80";
|
|
4778
|
+
var tip = e.allocWarn === "pool-empty"
|
|
4779
|
+
? "订阅池可用为空,分配保持现状(补池后自动恢复)"
|
|
4780
|
+
: e.allocWarn === "count-capped"
|
|
4781
|
+
? "数量超上限 8,已截断"
|
|
4782
|
+
: "自动分配(订阅池换血/摊开全自动)";
|
|
4783
|
+
return styled(
|
|
4784
|
+
"span",
|
|
4785
|
+
"display:inline-block;padding:1px 6px;border-radius:8px;font-size:10px;white-space:nowrap;" +
|
|
4786
|
+
"background:" + color + "22;color:" + color + ";border:1px solid " + color + "55;cursor:help",
|
|
4787
|
+
"⚡自动×" + (e.count != null ? e.count : (e.subscriptions || []).length),
|
|
4788
|
+
).replace("<span", '<span title="' + tip + '"');
|
|
4789
|
+
}
|
|
4790
|
+
|
|
4756
4791
|
function wdShowProxyModal(userId) {
|
|
4757
4792
|
_wdProxyUserId = userId;
|
|
4758
4793
|
document.getElementById("wdProxyUserId").textContent = userId;
|
|
4759
|
-
var
|
|
4760
|
-
var
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4794
|
+
var entry = wdGetProxyEntry(userId);
|
|
4795
|
+
var isAuto = !!(entry && entry.mode === "auto");
|
|
4796
|
+
document.getElementById("wdProxyAutoView").style.display = isAuto ? "block" : "none";
|
|
4797
|
+
document.getElementById("wdProxyManualHint").style.display = isAuto ? "none" : "";
|
|
4798
|
+
document.getElementById("wdProxySubsText").style.display = isAuto ? "none" : "";
|
|
4799
|
+
document.getElementById("wdProxySwitchAutoBtn").style.display = isAuto ? "none" : "";
|
|
4800
|
+
document.getElementById("wdProxySwitchManualBtn").style.display = isAuto ? "" : "none";
|
|
4801
|
+
document.getElementById("wdProxySaveManualBtn").style.display = isAuto ? "none" : "";
|
|
4802
|
+
document.getElementById("wdProxySaveAutoBtn").style.display = isAuto ? "" : "none";
|
|
4803
|
+
var badgeEl = document.getElementById("wdProxyModeBadge");
|
|
4804
|
+
badgeEl.innerHTML = isAuto ? wdProxyModeBadge(userId) : styled("span", "color:#666;font-size:10px", "手动模式");
|
|
4805
|
+
if (isAuto) {
|
|
4806
|
+
wdRenderProxyAutoView(userId);
|
|
4807
|
+
} else {
|
|
4808
|
+
var ta = document.getElementById("wdProxySubsText");
|
|
4809
|
+
var configured = [];
|
|
4810
|
+
if (entry) {
|
|
4811
|
+
configured = (entry.subscriptions || []).map(function (s) {
|
|
4812
|
+
return typeof s === "string" ? s : s.url;
|
|
4813
|
+
});
|
|
4814
|
+
}
|
|
4815
|
+
ta.value = configured.join("\n");
|
|
4765
4816
|
}
|
|
4766
|
-
ta.value = configured.join("\n");
|
|
4767
4817
|
document.getElementById("wdProxyModal").style.display = "flex";
|
|
4768
4818
|
}
|
|
4769
4819
|
|
|
4820
|
+
/** 订阅池视图按 URL 索引(自动分配表格展示状态用) */
|
|
4821
|
+
function wdSubViewByUrl(url) {
|
|
4822
|
+
if (!_wdData || !Array.isArray(_wdData.subscriptions)) return null;
|
|
4823
|
+
for (var i = 0; i < _wdData.subscriptions.length; i++) {
|
|
4824
|
+
if (_wdData.subscriptions[i].url === url) return _wdData.subscriptions[i];
|
|
4825
|
+
}
|
|
4826
|
+
return null;
|
|
4827
|
+
}
|
|
4828
|
+
|
|
4829
|
+
/** 自动分配视图: 数量输入 + 告警横幅 + alloc 端口映射只读表 */
|
|
4830
|
+
function wdRenderProxyAutoView(userId) {
|
|
4831
|
+
var entry = wdGetProxyEntry(userId) || {};
|
|
4832
|
+
var alloc = entry.subscriptions || [];
|
|
4833
|
+
document.getElementById("wdProxyCountInput").value = entry.count != null ? entry.count : alloc.length;
|
|
4834
|
+
var warnEl = document.getElementById("wdProxyAllocWarn");
|
|
4835
|
+
if (entry.allocWarn === "pool-empty") {
|
|
4836
|
+
warnEl.innerHTML = styled("div",
|
|
4837
|
+
"padding:6px 10px;margin-bottom:8px;border-radius:6px;font-size:12px;background:#fbbf2422;color:#fbbf24;border:1px solid #fbbf2455",
|
|
4838
|
+
"⚠️ 订阅池可用订阅为空(全部禁用/到期/耗尽),分配保持现状;往池子补可用订阅后下次心跳自动补齐");
|
|
4839
|
+
} else if (entry.allocWarn === "count-capped") {
|
|
4840
|
+
warnEl.innerHTML = styled("div",
|
|
4841
|
+
"padding:6px 10px;margin-bottom:8px;border-radius:6px;font-size:12px;background:#f8717122;color:#f87171;border:1px solid #f8717155",
|
|
4842
|
+
"⚠️ 实例数量超上限 8,已截断(实例端口段 9097-9104 仅 8 个)");
|
|
4843
|
+
} else {
|
|
4844
|
+
warnEl.innerHTML = "";
|
|
4845
|
+
}
|
|
4846
|
+
var rows = "";
|
|
4847
|
+
if (!alloc.length) {
|
|
4848
|
+
rows = styled("div", "color:#666;font-size:12px;padding:6px", "暂无分配(等待下次心跳从订阅池自动补齐)");
|
|
4849
|
+
}
|
|
4850
|
+
for (var i = 0; i < alloc.length; i++) {
|
|
4851
|
+
var it = alloc[i] || {};
|
|
4852
|
+
var url = it.url || "";
|
|
4853
|
+
var sv = wdSubViewByUrl(url);
|
|
4854
|
+
var name = it.name || (sv && sv.name) || "sub-" + (i + 1);
|
|
4855
|
+
var host = url.replace(/^https?:\/\//, "").split("/")[0];
|
|
4856
|
+
var statusHtml;
|
|
4857
|
+
if (!sv) {
|
|
4858
|
+
statusHtml = styled("span", "color:#6b7280;font-size:11px", "未入池(存量保留)");
|
|
4859
|
+
} else {
|
|
4860
|
+
var st = sv.status || {};
|
|
4861
|
+
statusHtml = styled("span",
|
|
4862
|
+
"display:inline-block;padding:1px 6px;border-radius:8px;font-size:10px;white-space:nowrap;" +
|
|
4863
|
+
"background:" + (st.color || "#6b7280") + "22;color:" + (st.color || "#6b7280") + ";border:1px solid " + (st.color || "#6b7280") + "55",
|
|
4864
|
+
escHtml(st.label || "未知"));
|
|
4865
|
+
}
|
|
4866
|
+
rows += styled("div",
|
|
4867
|
+
"display:flex;align-items:center;gap:10px;padding:4px 2px;border-bottom:1px solid #2a2a3a;font-size:12px;flex-wrap:wrap",
|
|
4868
|
+
styled("span", "color:#94a3b8;width:44px", "实例" + (i + 1)) +
|
|
4869
|
+
styled("span", "color:#6b7280;width:150px;font-family:monospace", ":" + (7897 + i) + " / :" + (9097 + i)) +
|
|
4870
|
+
styled("span", "color:#e0e0e0;font-weight:bold", escHtml(name)) +
|
|
4871
|
+
styled("span", "color:#555;font-size:11px", escHtml(host)) +
|
|
4872
|
+
statusHtml);
|
|
4873
|
+
}
|
|
4874
|
+
document.getElementById("wdProxyAllocTable").innerHTML = rows;
|
|
4875
|
+
}
|
|
4876
|
+
|
|
4877
|
+
/** 保存实例数量(auto 模式);count 超限由服务端截断并标红 */
|
|
4878
|
+
async function wdSaveProxyCount() {
|
|
4879
|
+
var userId = _wdProxyUserId;
|
|
4880
|
+
var count = parseInt(document.getElementById("wdProxyCountInput").value, 10);
|
|
4881
|
+
if (!Number.isInteger(count) || count <= 0) {
|
|
4882
|
+
alert("请输入正整数实例数量(1~8)");
|
|
4883
|
+
return;
|
|
4884
|
+
}
|
|
4885
|
+
try {
|
|
4886
|
+
var res = await fetch("/api/watchdog/set-proxy-auto", {
|
|
4887
|
+
method: "POST",
|
|
4888
|
+
headers: { "Content-Type": "application/json" },
|
|
4889
|
+
body: JSON.stringify({ userId: userId, count: count }),
|
|
4890
|
+
});
|
|
4891
|
+
var d = await res.json();
|
|
4892
|
+
if (!res.ok || !d.ok) throw new Error(d.error || "HTTP " + res.status);
|
|
4893
|
+
if (_wdData) {
|
|
4894
|
+
_wdData.proxySettings = _wdData.proxySettings || {};
|
|
4895
|
+
_wdData.proxySettings[userId] = d.proxySettings;
|
|
4896
|
+
}
|
|
4897
|
+
wdShowProxyModal(userId); // 留在弹窗内刷新 alloc 表
|
|
4898
|
+
alert(
|
|
4899
|
+
"实例数量已更新为 " +
|
|
4900
|
+
(d.proxySettings && d.proxySettings.count ? d.proxySettings.count : count) +
|
|
4901
|
+
"\n\n服务端已按「粘性槽位 + 摊开」重算分配,客户端下次心跳(约 15 秒)自动热应用。",
|
|
4902
|
+
);
|
|
4903
|
+
fetchWatchdogData();
|
|
4904
|
+
} catch (e) {
|
|
4905
|
+
alert("保存失败: " + e.message);
|
|
4906
|
+
}
|
|
4907
|
+
}
|
|
4908
|
+
|
|
4909
|
+
/** 手动 → 自动(迁移方案 A): 现有列表原样转 alloc,count=当前条数,切换无感 */
|
|
4910
|
+
async function wdSwitchProxyAuto() {
|
|
4911
|
+
var userId = _wdProxyUserId;
|
|
4912
|
+
if (!confirm("切换为自动分配?\n\n· 当前订阅列表原样保留(切换瞬间节点无感)\n· 之后由服务端订阅池自动换血/跨机器摊开\n· 实例数量 = 当前列表条数,可随时修改")) return;
|
|
4913
|
+
try {
|
|
4914
|
+
var res = await fetch("/api/watchdog/set-proxy-auto", {
|
|
4915
|
+
method: "POST",
|
|
4916
|
+
headers: { "Content-Type": "application/json" },
|
|
4917
|
+
body: JSON.stringify({ userId: userId }),
|
|
4918
|
+
});
|
|
4919
|
+
var d = await res.json();
|
|
4920
|
+
if (!res.ok || !d.ok) throw new Error(d.error || "HTTP " + res.status);
|
|
4921
|
+
if (_wdData) {
|
|
4922
|
+
_wdData.proxySettings = _wdData.proxySettings || {};
|
|
4923
|
+
_wdData.proxySettings[userId] = d.proxySettings;
|
|
4924
|
+
}
|
|
4925
|
+
wdShowProxyModal(userId);
|
|
4926
|
+
fetchWatchdogData();
|
|
4927
|
+
} catch (e) {
|
|
4928
|
+
alert("切换失败: " + e.message);
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
|
|
4932
|
+
/** 自动 → 手动: 当前 alloc 原样转手动列表,分配器不再触碰 */
|
|
4933
|
+
async function wdSwitchProxyManual() {
|
|
4934
|
+
var userId = _wdProxyUserId;
|
|
4935
|
+
if (!confirm("切回手动模式?\n\n当前分配结果原样转为手动列表(客户端不动),之后订阅池变化不再自动换血。")) return;
|
|
4936
|
+
try {
|
|
4937
|
+
var res = await fetch("/api/watchdog/set-proxy-manual", {
|
|
4938
|
+
method: "POST",
|
|
4939
|
+
headers: { "Content-Type": "application/json" },
|
|
4940
|
+
body: JSON.stringify({ userId: userId }),
|
|
4941
|
+
});
|
|
4942
|
+
var d = await res.json();
|
|
4943
|
+
if (!res.ok || !d.ok) throw new Error(d.error || "HTTP " + res.status);
|
|
4944
|
+
if (_wdData) {
|
|
4945
|
+
_wdData.proxySettings = _wdData.proxySettings || {};
|
|
4946
|
+
_wdData.proxySettings[userId] = d.proxySettings;
|
|
4947
|
+
}
|
|
4948
|
+
wdShowProxyModal(userId);
|
|
4949
|
+
fetchWatchdogData();
|
|
4950
|
+
} catch (e) {
|
|
4951
|
+
alert("切换失败: " + e.message);
|
|
4952
|
+
}
|
|
4953
|
+
}
|
|
4954
|
+
|
|
4770
4955
|
function wdCloseProxyModal() {
|
|
4771
4956
|
document.getElementById("wdProxyModal").style.display = "none";
|
|
4772
4957
|
}
|
|
@@ -4806,6 +4991,354 @@ async function wdSaveProxySettings() {
|
|
|
4806
4991
|
}
|
|
4807
4992
|
}
|
|
4808
4993
|
|
|
4994
|
+
// ── 订阅中心(Phase 1: 服务端订阅池 + 到期/流量探测,只读展示节点引用,不改节点行为) ──
|
|
4995
|
+
|
|
4996
|
+
var _wdSubEditId = "";
|
|
4997
|
+
var _wdLastSubsRendered = "";
|
|
4998
|
+
|
|
4999
|
+
/** HTML 转义(订阅名/备注是自由文本,防注入) */
|
|
5000
|
+
function escHtml(s) {
|
|
5001
|
+
return String(s == null ? "" : s)
|
|
5002
|
+
.replace(/&/g, "&")
|
|
5003
|
+
.replace(/</g, "<")
|
|
5004
|
+
.replace(/>/g, ">")
|
|
5005
|
+
.replace(/"/g, """)
|
|
5006
|
+
.replace(/'/g, "'");
|
|
5007
|
+
}
|
|
5008
|
+
|
|
5009
|
+
function wdSubTimeAgo(ts) {
|
|
5010
|
+
if (!ts) return "";
|
|
5011
|
+
var diff = Date.now() - ts;
|
|
5012
|
+
if (diff < 60000) return "刚刚";
|
|
5013
|
+
if (diff < 3600000) return Math.floor(diff / 60000) + "分钟前";
|
|
5014
|
+
if (diff < 86400000) return Math.floor(diff / 3600000) + "小时前";
|
|
5015
|
+
return Math.floor(diff / 86400000) + "天前";
|
|
5016
|
+
}
|
|
5017
|
+
|
|
5018
|
+
function wdSubStatusBadge(st) {
|
|
5019
|
+
var c = st && st.color ? st.color : "#6b7280";
|
|
5020
|
+
return styled(
|
|
5021
|
+
"span",
|
|
5022
|
+
"display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;white-space:nowrap;" +
|
|
5023
|
+
"background:" + c + "22;color:" + c + ";border:1px solid " + c + "55",
|
|
5024
|
+
escHtml((st && st.label) || "未知"),
|
|
5025
|
+
);
|
|
5026
|
+
}
|
|
5027
|
+
|
|
5028
|
+
function wdSubTrafficCell(s) {
|
|
5029
|
+
if (!(s.total > 0))
|
|
5030
|
+
return styled("div", "font-size:11px;color:#666;white-space:nowrap", "用量未知");
|
|
5031
|
+
var pct = Math.min(100, Math.round((s.usedRatio || 0) * 100));
|
|
5032
|
+
var barColor = pct >= 90 ? "#f87171" : pct >= 80 ? "#fbbf24" : "#4ade80";
|
|
5033
|
+
return (
|
|
5034
|
+
styled("div", "width:150px;background:#0a0a12;border-radius:4px;height:8px;overflow:hidden",
|
|
5035
|
+
styled("div", "width:" + pct + "%;height:100%;background:" + barColor, "")) +
|
|
5036
|
+
styled("div", "font-size:10px;color:#888;margin-top:2px;white-space:nowrap",
|
|
5037
|
+
"已用 " + fmtGB(s.used) + " / " + fmtGB(s.total) + "(" + pct + "%)")
|
|
5038
|
+
);
|
|
5039
|
+
}
|
|
5040
|
+
|
|
5041
|
+
function wdSubExpireCell(s) {
|
|
5042
|
+
if (!(s.expireAt > 0))
|
|
5043
|
+
return styled("span", "color:#666;font-size:11px", "长期");
|
|
5044
|
+
var daysLeft = Math.ceil((s.expireAt - Date.now()) / 86400000);
|
|
5045
|
+
var color = daysLeft < 7 ? "#f87171" : daysLeft < 30 ? "#fbbf24" : "#94a3b8";
|
|
5046
|
+
return styled("span", "color:" + color + ";font-size:11px;white-space:nowrap",
|
|
5047
|
+
new Date(s.expireAt).toLocaleDateString() +
|
|
5048
|
+
(daysLeft <= 0 ? "(已到期)" : "(剩" + daysLeft + "天)"));
|
|
5049
|
+
}
|
|
5050
|
+
|
|
5051
|
+
function wdSubRefsCell(s) {
|
|
5052
|
+
if (!s.refs || !s.refs.length)
|
|
5053
|
+
return styled("span", "color:#555;font-size:11px", "未引用");
|
|
5054
|
+
var total = 0;
|
|
5055
|
+
s.refs.forEach(function (r) {
|
|
5056
|
+
total += r.count || 0;
|
|
5057
|
+
});
|
|
5058
|
+
var names = s.refs.map(function (r) {
|
|
5059
|
+
return r.userId + "(" + r.count + "处)";
|
|
5060
|
+
}).join("、");
|
|
5061
|
+
return styled("span",
|
|
5062
|
+
"color:#94a3b8;font-size:11px;cursor:help;white-space:nowrap",
|
|
5063
|
+
total + " 处引用")
|
|
5064
|
+
.replace("<span", '<span title="' + escHtml(names) + '"');
|
|
5065
|
+
}
|
|
5066
|
+
|
|
5067
|
+
function wdSubSourceCell(s) {
|
|
5068
|
+
if (s.source === "probe") {
|
|
5069
|
+
var p = s.lastProbe || {};
|
|
5070
|
+
var txt = "🔍 探测 " + wdSubTimeAgo(s.dataAt);
|
|
5071
|
+
if (p.ok === false) txt += "(" + escHtml(p.error || "失败") + ")";
|
|
5072
|
+
return styled("span", "color:#94a3b8;font-size:11px;white-space:nowrap", txt);
|
|
5073
|
+
}
|
|
5074
|
+
if (s.source === "node") {
|
|
5075
|
+
var r = s.lastNodeReport || {};
|
|
5076
|
+
return styled("span", "color:#94a3b8;font-size:11px;white-space:nowrap",
|
|
5077
|
+
"📡 节点代报 " + wdSubTimeAgo(s.dataAt) + (r.by ? "(" + escHtml(r.by) + ")" : ""));
|
|
5078
|
+
}
|
|
5079
|
+
return styled("span", "color:#555;font-size:11px", "无数据");
|
|
5080
|
+
}
|
|
5081
|
+
|
|
5082
|
+
function wdSubRowBtn(label, fn, color, title) {
|
|
5083
|
+
return tag("button", {
|
|
5084
|
+
onclick: fn,
|
|
5085
|
+
title: title || "",
|
|
5086
|
+
style: "padding:2px 8px;font-size:11px;background:#333;color:" + (color || "#ccc") +
|
|
5087
|
+
";border:1px solid #444;border-radius:3px;cursor:pointer;white-space:nowrap",
|
|
5088
|
+
}, label);
|
|
5089
|
+
}
|
|
5090
|
+
|
|
5091
|
+
// 折叠状态(localStorage 记忆,跨刷新保持;页面每 10s 重渲染,不记就会被合上)
|
|
5092
|
+
var _wdSubsExpanded = false;
|
|
5093
|
+
try {
|
|
5094
|
+
_wdSubsExpanded = localStorage.getItem("wdSubsExpanded") === "1";
|
|
5095
|
+
} catch (e) {}
|
|
5096
|
+
|
|
5097
|
+
function wdSubsToggle() {
|
|
5098
|
+
_wdSubsExpanded = !_wdSubsExpanded;
|
|
5099
|
+
try {
|
|
5100
|
+
localStorage.setItem("wdSubsExpanded", _wdSubsExpanded ? "1" : "0");
|
|
5101
|
+
} catch (e) {}
|
|
5102
|
+
_wdLastSubsRendered = ""; // 强制下一轮重绘
|
|
5103
|
+
renderSubscriptionsCenter((_wdData && _wdData.subscriptions) || []);
|
|
5104
|
+
}
|
|
5105
|
+
|
|
5106
|
+
function renderSubscriptionsCenter(subs) {
|
|
5107
|
+
var el = document.getElementById("wdSubsList");
|
|
5108
|
+
if (!el) return;
|
|
5109
|
+
subs = subs || [];
|
|
5110
|
+
// 汇总统计(折叠态的唯一信息源,始终计算)
|
|
5111
|
+
var summaryEl = document.getElementById("wdSubsSummary");
|
|
5112
|
+
var sectionEl = document.getElementById("wdSubsSection");
|
|
5113
|
+
var arrowEl = document.getElementById("wdSubsArrow");
|
|
5114
|
+
var cnt = { ok: 0, warn: 0, bad: 0, off: 0 };
|
|
5115
|
+
subs.forEach(function (s) {
|
|
5116
|
+
var c = s.status && s.status.code;
|
|
5117
|
+
if (c === "ok") cnt.ok++;
|
|
5118
|
+
else if (c === "expiring" || c === "quota-critical") cnt.warn++;
|
|
5119
|
+
else if (c === "expired" || c === "exhausted") cnt.bad++;
|
|
5120
|
+
else if (c === "disabled") cnt.off++;
|
|
5121
|
+
});
|
|
5122
|
+
if (summaryEl) {
|
|
5123
|
+
summaryEl.textContent =
|
|
5124
|
+
"共 " + subs.length + " 条 · 正常 " + cnt.ok +
|
|
5125
|
+
(cnt.warn ? " · 临期/告急 " + cnt.warn : "") +
|
|
5126
|
+
(cnt.bad ? " · 到期/耗尽 " + cnt.bad : "") +
|
|
5127
|
+
(cnt.off ? " · 禁用 " + cnt.off : "");
|
|
5128
|
+
// 汇总行报警色:到期/耗尽红、临期/告急黄(折叠态的异常可见性全靠它)
|
|
5129
|
+
summaryEl.style.color = cnt.bad > 0 ? "#f87171" : cnt.warn > 0 ? "#fbbf24" : "#666";
|
|
5130
|
+
}
|
|
5131
|
+
if (sectionEl) {
|
|
5132
|
+
sectionEl.style.background = cnt.bad > 0 ? "#241519" : cnt.warn > 0 ? "#221d12" : "#16161f";
|
|
5133
|
+
}
|
|
5134
|
+
if (arrowEl) {
|
|
5135
|
+
arrowEl.textContent = subs.length ? (_wdSubsExpanded ? "▾ 收起" : "▸ 展开") : "";
|
|
5136
|
+
}
|
|
5137
|
+
var html;
|
|
5138
|
+
if (!subs.length) {
|
|
5139
|
+
html = styled("div", "padding:14px;text-align:center;color:#555",
|
|
5140
|
+
"订阅池为空:点右上角「+ 新增订阅」把订阅地址集中录入,服务端每 6 小时自动探测一次到期/流量(不影响任何节点)");
|
|
5141
|
+
} else {
|
|
5142
|
+
// 排序: 异常优先(到期/耗尽 > 临期/告急 > 探测失败 > 正常 > 禁用),同级按名称
|
|
5143
|
+
var rank = { expired: 0, exhausted: 0, expiring: 1, "quota-critical": 1, "probe-failed": 2, unknown: 3, ok: 4, disabled: 5 };
|
|
5144
|
+
var sorted = subs.slice().sort(function (a, b) {
|
|
5145
|
+
var ra = rank[a.status && a.status.code] != null ? rank[a.status.code] : 3;
|
|
5146
|
+
var rb = rank[b.status && b.status.code] != null ? rank[b.status.code] : 3;
|
|
5147
|
+
if (ra !== rb) return ra - rb;
|
|
5148
|
+
return String(a.name).localeCompare(String(b.name));
|
|
5149
|
+
});
|
|
5150
|
+
var rows = "";
|
|
5151
|
+
sorted.forEach(function (s) {
|
|
5152
|
+
var disabled = s.enabled === false;
|
|
5153
|
+
var rowBg = disabled ? "opacity:0.45;" : "";
|
|
5154
|
+
var warnBg = !disabled && s.status && (s.status.code === "expired" || s.status.code === "exhausted")
|
|
5155
|
+
? ";background:#3b1d1d;border-radius:6px"
|
|
5156
|
+
: "";
|
|
5157
|
+
var nameCell =
|
|
5158
|
+
styled("div", "color:#e0e0e0;font-size:12px;font-weight:bold;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:180px",
|
|
5159
|
+
escHtml(s.name)) +
|
|
5160
|
+
(s.note
|
|
5161
|
+
? styled("div", "color:#666;font-size:10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:180px",
|
|
5162
|
+
escHtml(s.note))
|
|
5163
|
+
: "") +
|
|
5164
|
+
(s.resetsCount > 0
|
|
5165
|
+
? styled("div", "color:#888;font-size:10px", "🔄 已检测到重置 " + s.resetsCount + " 次" +
|
|
5166
|
+
(s.lastResetAt ? "(" + wdSubTimeAgo(s.lastResetAt) + ")" : ""))
|
|
5167
|
+
: "");
|
|
5168
|
+
var actions =
|
|
5169
|
+
wdSubRowBtn(disabled ? "▶ 启用" : "⏸ 禁用",
|
|
5170
|
+
"wdSubToggle('" + s.id + "')", disabled ? "#4ade80" : "#fbbf24",
|
|
5171
|
+
disabled ? "重新启用该订阅" : "禁用后不再自动探测(不改节点行为)") +
|
|
5172
|
+
wdSubRowBtn("🔍 检测", "wdSubProbe(this, '" + s.id + "')", "#60a5fa", "立即探测一次(剩余流量/到期时间)") +
|
|
5173
|
+
wdSubRowBtn("✏️ 编辑", "wdSubShowEditModal('" + s.id + "')", "#ccc") +
|
|
5174
|
+
wdSubRowBtn("🗑", "wdSubDelete('" + s.id + "')", "#f87171", "从订阅池删除");
|
|
5175
|
+
rows += styled("div",
|
|
5176
|
+
"display:flex;align-items:center;gap:12px;padding:7px 8px;border-bottom:1px solid #22222f;font-size:12px;" + rowBg + warnBg,
|
|
5177
|
+
wdSubStatusBadge(s.status) +
|
|
5178
|
+
styled("div", "flex:0 0 180px", nameCell) +
|
|
5179
|
+
styled("div", "flex:0 0 160px", wdSubTrafficCell(s)) +
|
|
5180
|
+
styled("div", "flex:0 0 130px", wdSubExpireCell(s)) +
|
|
5181
|
+
styled("div", "flex:0 0 80px;text-align:center", wdSubRefsCell(s)) +
|
|
5182
|
+
styled("div", "flex:1;overflow:hidden", wdSubSourceCell(s)) +
|
|
5183
|
+
styled("div", "display:flex;gap:6px;flex:0 0 auto", actions));
|
|
5184
|
+
});
|
|
5185
|
+
html = rows;
|
|
5186
|
+
}
|
|
5187
|
+
if (!_wdSubsExpanded && subs.length) {
|
|
5188
|
+
// 折叠态:只留汇总条(空池不折叠,保持新增入口可发现)
|
|
5189
|
+
if (el.innerHTML !== "") {
|
|
5190
|
+
el.innerHTML = "";
|
|
5191
|
+
_wdLastSubsRendered = "";
|
|
5192
|
+
}
|
|
5193
|
+
} else if (_wdLastSubsRendered !== html) {
|
|
5194
|
+
_wdLastSubsRendered = html;
|
|
5195
|
+
el.innerHTML = html;
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5198
|
+
|
|
5199
|
+
function wdSubShowAddModal() {
|
|
5200
|
+
_wdSubEditId = "";
|
|
5201
|
+
document.getElementById("wdSubModalTitle").textContent = "+ 新增订阅";
|
|
5202
|
+
document.getElementById("wdSubName").value = "";
|
|
5203
|
+
document.getElementById("wdSubUrl").value = "";
|
|
5204
|
+
document.getElementById("wdSubNote").value = "";
|
|
5205
|
+
document.getElementById("wdSubModal").style.display = "flex";
|
|
5206
|
+
}
|
|
5207
|
+
|
|
5208
|
+
function wdSubShowEditModal(id) {
|
|
5209
|
+
var s = ((_wdData && _wdData.subscriptions) || []).find(function (x) {
|
|
5210
|
+
return x.id === id;
|
|
5211
|
+
});
|
|
5212
|
+
if (!s) {
|
|
5213
|
+
alert("订阅不存在(可能已被删除)");
|
|
5214
|
+
return;
|
|
5215
|
+
}
|
|
5216
|
+
_wdSubEditId = id;
|
|
5217
|
+
document.getElementById("wdSubModalTitle").textContent = "✏️ 编辑订阅 — " + s.name;
|
|
5218
|
+
document.getElementById("wdSubName").value = s.name || "";
|
|
5219
|
+
document.getElementById("wdSubUrl").value = s.url || "";
|
|
5220
|
+
document.getElementById("wdSubNote").value = s.note || "";
|
|
5221
|
+
document.getElementById("wdSubModal").style.display = "flex";
|
|
5222
|
+
}
|
|
5223
|
+
|
|
5224
|
+
function wdSubCloseModal() {
|
|
5225
|
+
document.getElementById("wdSubModal").style.display = "none";
|
|
5226
|
+
}
|
|
5227
|
+
|
|
5228
|
+
async function wdSubSave() {
|
|
5229
|
+
var payload = {
|
|
5230
|
+
name: document.getElementById("wdSubName").value.trim(),
|
|
5231
|
+
url: document.getElementById("wdSubUrl").value.trim(),
|
|
5232
|
+
note: document.getElementById("wdSubNote").value.trim(),
|
|
5233
|
+
};
|
|
5234
|
+
if (!payload.url) {
|
|
5235
|
+
alert("请填写订阅地址");
|
|
5236
|
+
return;
|
|
5237
|
+
}
|
|
5238
|
+
var isEdit = !!_wdSubEditId;
|
|
5239
|
+
try {
|
|
5240
|
+
var res = await fetch(
|
|
5241
|
+
isEdit ? "/api/watchdog/subscriptions/update" : "/api/watchdog/subscriptions",
|
|
5242
|
+
{
|
|
5243
|
+
method: "POST",
|
|
5244
|
+
headers: { "Content-Type": "application/json" },
|
|
5245
|
+
body: JSON.stringify(isEdit ? Object.assign({ id: _wdSubEditId }, payload) : payload),
|
|
5246
|
+
},
|
|
5247
|
+
);
|
|
5248
|
+
var d = await res.json();
|
|
5249
|
+
if (!res.ok || !d.ok) throw new Error(d.error || "HTTP " + res.status);
|
|
5250
|
+
wdSubCloseModal();
|
|
5251
|
+
var justId = isEdit ? _wdSubEditId : d.entry && d.entry.id;
|
|
5252
|
+
_wdSubEditId = "";
|
|
5253
|
+
await fetchWatchdogData();
|
|
5254
|
+
// 新增/改地址后立即探测一次,让用户马上看到到期/流量
|
|
5255
|
+
if (justId) wdSubProbe(null, justId, true);
|
|
5256
|
+
} catch (e) {
|
|
5257
|
+
alert("保存失败: " + e.message);
|
|
5258
|
+
}
|
|
5259
|
+
}
|
|
5260
|
+
|
|
5261
|
+
async function wdSubDelete(id) {
|
|
5262
|
+
var s = ((_wdData && _wdData.subscriptions) || []).find(function (x) {
|
|
5263
|
+
return x.id === id;
|
|
5264
|
+
});
|
|
5265
|
+
var refs = (s && s.refs) || [];
|
|
5266
|
+
var msg = "确定删除订阅「" + ((s && s.name) || id) + "」吗?";
|
|
5267
|
+
if (refs.length) {
|
|
5268
|
+
msg +=
|
|
5269
|
+
"\n\n⚠️ 该订阅仍被 " + refs.length + " 个节点配置引用:\n" +
|
|
5270
|
+
refs.map(function (r) { return "· " + r.userId + "(" + r.count + " 处)"; }).join("\n") +
|
|
5271
|
+
"\n删除后这些节点下次手动改配置时将无法从池中对照(不影响节点现有运行)。";
|
|
5272
|
+
}
|
|
5273
|
+
if (!confirm(msg)) return;
|
|
5274
|
+
try {
|
|
5275
|
+
var res = await fetch("/api/watchdog/subscriptions/delete", {
|
|
5276
|
+
method: "POST",
|
|
5277
|
+
headers: { "Content-Type": "application/json" },
|
|
5278
|
+
body: JSON.stringify({ id: id, force: refs.length > 0 }),
|
|
5279
|
+
});
|
|
5280
|
+
var d = await res.json();
|
|
5281
|
+
if (!res.ok || !d.ok) throw new Error(d.error || "HTTP " + res.status);
|
|
5282
|
+
fetchWatchdogData();
|
|
5283
|
+
} catch (e) {
|
|
5284
|
+
alert("删除失败: " + e.message);
|
|
5285
|
+
}
|
|
5286
|
+
}
|
|
5287
|
+
|
|
5288
|
+
async function wdSubToggle(id) {
|
|
5289
|
+
var s = ((_wdData && _wdData.subscriptions) || []).find(function (x) {
|
|
5290
|
+
return x.id === id;
|
|
5291
|
+
});
|
|
5292
|
+
if (!s) return;
|
|
5293
|
+
try {
|
|
5294
|
+
var res = await fetch("/api/watchdog/subscriptions/update", {
|
|
5295
|
+
method: "POST",
|
|
5296
|
+
headers: { "Content-Type": "application/json" },
|
|
5297
|
+
body: JSON.stringify({ id: id, enabled: s.enabled === false }),
|
|
5298
|
+
});
|
|
5299
|
+
var d = await res.json();
|
|
5300
|
+
if (!res.ok || !d.ok) throw new Error(d.error || "HTTP " + res.status);
|
|
5301
|
+
fetchWatchdogData();
|
|
5302
|
+
} catch (e) {
|
|
5303
|
+
alert("操作失败: " + e.message);
|
|
5304
|
+
}
|
|
5305
|
+
}
|
|
5306
|
+
|
|
5307
|
+
async function wdSubProbe(btn, id, silent) {
|
|
5308
|
+
if (btn) {
|
|
5309
|
+
btn.disabled = true;
|
|
5310
|
+
btn.textContent = "⏳";
|
|
5311
|
+
}
|
|
5312
|
+
try {
|
|
5313
|
+
var res = await fetch("/api/watchdog/subscriptions/probe", {
|
|
5314
|
+
method: "POST",
|
|
5315
|
+
headers: { "Content-Type": "application/json" },
|
|
5316
|
+
body: JSON.stringify({ id: id }),
|
|
5317
|
+
});
|
|
5318
|
+
var d = await res.json();
|
|
5319
|
+
if (!res.ok || !d.ok) throw new Error(d.error || "HTTP " + res.status);
|
|
5320
|
+
var s = d.subscription || {};
|
|
5321
|
+
if (!silent) {
|
|
5322
|
+
var lines = ["「" + (s.name || id) + "」探测完成"];
|
|
5323
|
+
if (s.total > 0) {
|
|
5324
|
+
lines.push("已用 " + fmtGB(s.used) + " / " + fmtGB(s.total) +
|
|
5325
|
+
",剩余 " + fmtGB(s.remain) +
|
|
5326
|
+
(s.usedRatio != null ? "(已用 " + Math.round(s.usedRatio * 100) + "%)" : ""));
|
|
5327
|
+
} else {
|
|
5328
|
+
lines.push("用量未知");
|
|
5329
|
+
}
|
|
5330
|
+
lines.push(s.expireAt > 0
|
|
5331
|
+
? "到期: " + new Date(s.expireAt).toLocaleDateString() +
|
|
5332
|
+
(s.status && s.status.label ? "(" + s.status.label + ")" : "")
|
|
5333
|
+
: "长期有效");
|
|
5334
|
+
alert(lines.join("\n"));
|
|
5335
|
+
}
|
|
5336
|
+
} catch (e) {
|
|
5337
|
+
if (!silent) alert("探测失败: " + e.message);
|
|
5338
|
+
}
|
|
5339
|
+
fetchWatchdogData();
|
|
5340
|
+
}
|
|
5341
|
+
|
|
4809
5342
|
// ── 登录检测端口配置区域(看门狗面板中每个客户端卡片) ──
|
|
4810
5343
|
|
|
4811
5344
|
// userId -> 详情展开状态(跨刷新保留)
|
|
@@ -4852,10 +5385,27 @@ function renderLoginSection(c, userId, loginPorts) {
|
|
|
4852
5385
|
(configured.length ? configured.length + "端口" : "未配置") + " ⚙️",
|
|
4853
5386
|
);
|
|
4854
5387
|
|
|
5388
|
+
// 重检按钮:仅已配置端口的在线客户端显示(手动触发立即检测,结果随心跳自动刷新)
|
|
5389
|
+
var recheckBtn = "";
|
|
5390
|
+
if (configured.length && c.clientId) {
|
|
5391
|
+
recheckBtn = tag(
|
|
5392
|
+
"button",
|
|
5393
|
+
{
|
|
5394
|
+
onclick: "wdRecheckLogin('" + esc(c.clientId) + "')",
|
|
5395
|
+
style:
|
|
5396
|
+
"padding:2px 8px;font-size:11px;background:#333;color:#4ade80;" +
|
|
5397
|
+
"border:1px solid #444;border-radius:3px;cursor:pointer",
|
|
5398
|
+
title: "立即重检一次登录状态(绕过 2h 周期,结果随心跳自动刷新)",
|
|
5399
|
+
},
|
|
5400
|
+
"重检",
|
|
5401
|
+
);
|
|
5402
|
+
}
|
|
5403
|
+
|
|
4855
5404
|
var head =
|
|
4856
5405
|
styled("span", "font-size:14px", "🔑") +
|
|
4857
5406
|
styled("span", "color:#888", "登录检测:") +
|
|
4858
|
-
cfgBtn
|
|
5407
|
+
cfgBtn +
|
|
5408
|
+
recheckBtn;
|
|
4859
5409
|
|
|
4860
5410
|
var wrapStyle =
|
|
4861
5411
|
"margin-top:6px;padding:6px 8px;background:#111;border-radius:6px;" +
|
|
@@ -5038,6 +5588,25 @@ async function wdSetLoginPorts() {
|
|
|
5038
5588
|
}
|
|
5039
5589
|
}
|
|
5040
5590
|
|
|
5591
|
+
// 手动下发登录重检:服务端一次性队列随心跳通知客户端,下一跳立即检测
|
|
5592
|
+
async function wdRecheckLogin(clientId) {
|
|
5593
|
+
try {
|
|
5594
|
+
var res = await fetch("/api/watchdog/recheck-login", {
|
|
5595
|
+
method: "POST",
|
|
5596
|
+
headers: { "Content-Type": "application/json" },
|
|
5597
|
+
body: JSON.stringify({ clientId: clientId }),
|
|
5598
|
+
});
|
|
5599
|
+
var d = await res.json();
|
|
5600
|
+
if (d.ok) {
|
|
5601
|
+
alert("已下发重检,结果随心跳自动刷新");
|
|
5602
|
+
} else {
|
|
5603
|
+
alert("失败: " + (d.error || "未知错误"));
|
|
5604
|
+
}
|
|
5605
|
+
} catch (e) {
|
|
5606
|
+
alert("下发失败: " + e.message);
|
|
5607
|
+
}
|
|
5608
|
+
}
|
|
5609
|
+
|
|
5041
5610
|
// ── 错误自动切节点配置区域(看门狗面板中每个客户端卡片) ──
|
|
5042
5611
|
|
|
5043
5612
|
function renderErrorTriggerSection(c, userId, errorTriggers) {
|
|
@@ -5422,6 +5991,7 @@ function renderSubsOverview(c, userId, builtinChannels, allChannels) {
|
|
|
5422
5991
|
styled("span", "font-size:14px", "🛜") +
|
|
5423
5992
|
styled("span", "color:#e0e0e0;font-weight:bold", "代理订阅") +
|
|
5424
5993
|
styled("span", "color:#94a3b8", count + " 条") +
|
|
5994
|
+
wdProxyModeBadge(userId) +
|
|
5425
5995
|
tag(
|
|
5426
5996
|
"button",
|
|
5427
5997
|
{
|
|
@@ -5707,6 +6277,7 @@ function renderSubscriptions(proxy, userId, withConfig) {
|
|
|
5707
6277
|
"margin-top:4px;padding:5px 8px;background:#111;border-radius:6px;display:flex;align-items:center;gap:8px;font-size:12px",
|
|
5708
6278
|
styled("span", "color:#888", "🛜 订阅配置:") +
|
|
5709
6279
|
styled("span", "color:#e0e0e0", cfgSummary) +
|
|
6280
|
+
wdProxyModeBadge(userId) +
|
|
5710
6281
|
tag(
|
|
5711
6282
|
"button",
|
|
5712
6283
|
{
|