tt-help-cli-ycl 1.4.139 → 1.4.142
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-client-error-monitor.mjs +176 -0
- package/scripts/test-daemon-failover.mjs +13 -0
- package/scripts/test-proxy-switch-restart.mjs +198 -0
- package/scripts/test-webserver-error-classify.mjs +379 -0
- package/src/clash/checker.js +14 -2
- package/src/clash/daemon.js +33 -11
- package/src/cli/comments.js +5 -4
- package/src/cli/tag.js +26 -17
- package/src/lib/api-client.js +11 -5
- package/src/lib/args.js +4 -4
- package/src/lib/client-error-report.js +3 -0
- package/src/lib/constants.js +1 -1
- package/src/lib/parse-ssr.mjs +13 -1
- package/src/lib/port.js +72 -0
- package/src/lib/tiktok-scraper.mjs +62 -4
- package/src/watch/data-store.js +3 -0
- package/src/watch/public/app.js +249 -31
- package/src/watch/public/index.html +41 -1
- package/src/watch/public/style.css +18 -0
- package/src/watch/server.js +1 -0
- package/src/watch/watchdog-server.js +100 -0
- package/src/watchdog/agent.js +157 -1
- package/src/watchdog/client-error-monitor.js +131 -0
- package/src/watchdog/process-manager.js +49 -0
- package/src/webserver/server.mjs +263 -79
package/src/watch/public/app.js
CHANGED
|
@@ -3450,6 +3450,8 @@ async function fetchWatchdogData() {
|
|
|
3450
3450
|
_wdData.clashCountries || {},
|
|
3451
3451
|
_wdData.errorTriggers || {},
|
|
3452
3452
|
_wdData.loginPorts || {},
|
|
3453
|
+
_wdData.switchRestarts || {},
|
|
3454
|
+
_wdData.autoUpgrades || {},
|
|
3453
3455
|
);
|
|
3454
3456
|
} catch (e) {
|
|
3455
3457
|
document.getElementById("wdClientsList").textContent =
|
|
@@ -3503,6 +3505,8 @@ function renderWatchdogDashboard(
|
|
|
3503
3505
|
clashCountries,
|
|
3504
3506
|
errorTriggers,
|
|
3505
3507
|
loginPorts,
|
|
3508
|
+
switchRestarts,
|
|
3509
|
+
autoUpgrades,
|
|
3506
3510
|
) {
|
|
3507
3511
|
// 顶栏统计始终更新(textContent 不闪烁)
|
|
3508
3512
|
var onlineCount = clients.filter(function (c) {
|
|
@@ -3567,6 +3571,8 @@ function renderWatchdogDashboard(
|
|
|
3567
3571
|
clashCountries,
|
|
3568
3572
|
errorTriggers,
|
|
3569
3573
|
loginPorts,
|
|
3574
|
+
switchRestarts,
|
|
3575
|
+
autoUpgrades,
|
|
3570
3576
|
),
|
|
3571
3577
|
);
|
|
3572
3578
|
});
|
|
@@ -3592,6 +3598,8 @@ function renderWatchdogDashboard(
|
|
|
3592
3598
|
clashCountries,
|
|
3593
3599
|
errorTriggers,
|
|
3594
3600
|
loginPorts,
|
|
3601
|
+
switchRestarts,
|
|
3602
|
+
autoUpgrades,
|
|
3595
3603
|
),
|
|
3596
3604
|
);
|
|
3597
3605
|
});
|
|
@@ -3643,17 +3651,23 @@ function renderOfflineCard(
|
|
|
3643
3651
|
clashCountries,
|
|
3644
3652
|
errorTriggers,
|
|
3645
3653
|
loginPorts,
|
|
3654
|
+
switchRestarts,
|
|
3655
|
+
autoUpgrades,
|
|
3646
3656
|
) {
|
|
3647
3657
|
var cmdCount = cmds ? cmds.length : 0;
|
|
3648
|
-
return
|
|
3658
|
+
return tag(
|
|
3649
3659
|
"div",
|
|
3650
|
-
|
|
3660
|
+
{
|
|
3661
|
+
class: "wd-client-card",
|
|
3662
|
+
style:
|
|
3663
|
+
"background:#1e1e2e;border:2px solid #f87171;border-radius:12px;padding:16px;opacity:0.9",
|
|
3664
|
+
},
|
|
3651
3665
|
styled(
|
|
3652
3666
|
"div",
|
|
3653
|
-
"display:flex;justify-content:space-between;align-items:center;margin-bottom:8px",
|
|
3667
|
+
"display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;flex-wrap:wrap;gap:8px",
|
|
3654
3668
|
styled(
|
|
3655
3669
|
"div",
|
|
3656
|
-
"",
|
|
3670
|
+
"display:flex;align-items:center;flex-wrap:wrap;min-width:0;flex:1",
|
|
3657
3671
|
tag(
|
|
3658
3672
|
"span",
|
|
3659
3673
|
{
|
|
@@ -3692,7 +3706,7 @@ function renderOfflineCard(
|
|
|
3692
3706
|
) +
|
|
3693
3707
|
styled(
|
|
3694
3708
|
"div",
|
|
3695
|
-
"display:flex;gap:10px;align-items:center",
|
|
3709
|
+
"display:flex;gap:10px;align-items:center;flex-wrap:wrap;flex-shrink:0",
|
|
3696
3710
|
styled("span", "color:#f87171;font-size:13px", "● 离线") +
|
|
3697
3711
|
styled("span", "color:#555;font-size:12px", "—") +
|
|
3698
3712
|
tag(
|
|
@@ -3714,6 +3728,8 @@ function renderOfflineCard(
|
|
|
3714
3728
|
) +
|
|
3715
3729
|
renderCountrySection({}, userId, clashCountries) +
|
|
3716
3730
|
renderErrorTriggerSection({}, userId, errorTriggers) +
|
|
3731
|
+
renderSwitchRestartSection({}, userId, switchRestarts) +
|
|
3732
|
+
renderAutoUpgradeSection({}, userId, autoUpgrades) +
|
|
3717
3733
|
renderLoginSection({}, userId, loginPorts) +
|
|
3718
3734
|
renderCmdRows(cmds || [], userId, [], false),
|
|
3719
3735
|
);
|
|
@@ -3755,6 +3771,8 @@ function renderClientCard(
|
|
|
3755
3771
|
clashCountries,
|
|
3756
3772
|
errorTriggers,
|
|
3757
3773
|
loginPorts,
|
|
3774
|
+
switchRestarts,
|
|
3775
|
+
autoUpgrades,
|
|
3758
3776
|
) {
|
|
3759
3777
|
var os = c.osInfo || {},
|
|
3760
3778
|
cfg = c.config || {},
|
|
@@ -3783,7 +3801,10 @@ function renderClientCard(
|
|
|
3783
3801
|
var onlineBadge = online
|
|
3784
3802
|
? styled("span", "color:#4ade80", "● 在线")
|
|
3785
3803
|
: styled("span", "color:#f87171", "● 离线");
|
|
3786
|
-
|
|
3804
|
+
// 完整日期时间:跨天离线也能看出最后心跳是哪天
|
|
3805
|
+
var lastSeen = c.lastHeartbeat
|
|
3806
|
+
? new Date(c.lastHeartbeat).toLocaleString("zh-CN", { hour12: false })
|
|
3807
|
+
: "?";
|
|
3787
3808
|
|
|
3788
3809
|
// 离线时标题和边框变红
|
|
3789
3810
|
var titleColor = online ? "#60a5fa" : "#f87171";
|
|
@@ -3791,10 +3812,10 @@ function renderClientCard(
|
|
|
3791
3812
|
|
|
3792
3813
|
var header = styled(
|
|
3793
3814
|
"div",
|
|
3794
|
-
"display:flex;justify-content:space-between;align-items:center;margin-bottom:8px",
|
|
3815
|
+
"display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;flex-wrap:wrap;gap:8px",
|
|
3795
3816
|
styled(
|
|
3796
3817
|
"div",
|
|
3797
|
-
"",
|
|
3818
|
+
"display:flex;align-items:center;flex-wrap:wrap;min-width:0;flex:1",
|
|
3798
3819
|
tag(
|
|
3799
3820
|
"span",
|
|
3800
3821
|
{
|
|
@@ -3854,10 +3875,16 @@ function renderClientCard(
|
|
|
3854
3875
|
) +
|
|
3855
3876
|
styled(
|
|
3856
3877
|
"div",
|
|
3857
|
-
"display:flex;gap:10px;align-items:center",
|
|
3878
|
+
"display:flex;gap:10px;align-items:center;flex-wrap:wrap;flex-shrink:0",
|
|
3858
3879
|
styled("span", "color:#888;font-size:13px", c.ip) +
|
|
3859
3880
|
onlineBadge +
|
|
3860
|
-
styled(
|
|
3881
|
+
styled(
|
|
3882
|
+
"span",
|
|
3883
|
+
online
|
|
3884
|
+
? "color:#666;font-size:12px"
|
|
3885
|
+
: "color:#fca5a5;font-size:12px",
|
|
3886
|
+
"最后心跳 " + lastSeen,
|
|
3887
|
+
) +
|
|
3861
3888
|
(!online && userId
|
|
3862
3889
|
? tag(
|
|
3863
3890
|
"button",
|
|
@@ -3873,16 +3900,22 @@ function renderClientCard(
|
|
|
3873
3900
|
),
|
|
3874
3901
|
);
|
|
3875
3902
|
|
|
3876
|
-
return
|
|
3903
|
+
return tag(
|
|
3877
3904
|
"div",
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3905
|
+
{
|
|
3906
|
+
class: "wd-client-card",
|
|
3907
|
+
style:
|
|
3908
|
+
"background:#1e1e2e;border:2px solid " +
|
|
3909
|
+
borderColor +
|
|
3910
|
+
";border-radius:12px;padding:16px",
|
|
3911
|
+
},
|
|
3881
3912
|
header +
|
|
3882
3913
|
styled("div", "color:#888;font-size:12px", osStr) +
|
|
3883
3914
|
styled("div", "color:#888;font-size:12px;margin-bottom:6px", infoLine) +
|
|
3884
3915
|
renderCountrySection(c, userId, clashCountries) +
|
|
3885
3916
|
renderErrorTriggerSection(c, userId, errorTriggers) +
|
|
3917
|
+
renderSwitchRestartSection(c, userId, switchRestarts) +
|
|
3918
|
+
renderAutoUpgradeSection(c, userId, autoUpgrades) +
|
|
3886
3919
|
renderLoginSection(c, userId, loginPorts) +
|
|
3887
3920
|
renderProxySection(c, userId) +
|
|
3888
3921
|
renderCmdRows(myCmds, userId, procs, online, c.llmAnalysis, c.clientId),
|
|
@@ -4282,6 +4315,155 @@ function renderErrorTriggerSection(c, userId, errorTriggers) {
|
|
|
4282
4315
|
);
|
|
4283
4316
|
}
|
|
4284
4317
|
|
|
4318
|
+
// ── 代理切换重启配置区域(看门狗面板中每个客户端卡片) ──
|
|
4319
|
+
|
|
4320
|
+
function renderSwitchRestartSection(c, userId, switchRestarts) {
|
|
4321
|
+
if (!userId) return "";
|
|
4322
|
+
var entry = (switchRestarts && switchRestarts[userId]) || null;
|
|
4323
|
+
var enabled = entry ? !!entry.enabled : false;
|
|
4324
|
+
var label = entry ? (enabled ? "已启用" : "已禁用") : "未配置(默认禁用)";
|
|
4325
|
+
var btnColor = enabled ? "#4ade80" : "#888";
|
|
4326
|
+
return styled(
|
|
4327
|
+
"div",
|
|
4328
|
+
"margin-top:6px;padding:6px 8px;background:#111;border-radius:6px;" +
|
|
4329
|
+
"display:flex;align-items:center;gap:8px;font-size:12px",
|
|
4330
|
+
styled("span", "font-size:14px", "🔁") +
|
|
4331
|
+
styled("span", "color:#888", "切换重启:") +
|
|
4332
|
+
tag(
|
|
4333
|
+
"button",
|
|
4334
|
+
{
|
|
4335
|
+
onclick: "wdShowSwitchRestartModal('" + esc(userId) + "')",
|
|
4336
|
+
style:
|
|
4337
|
+
"padding:2px 8px;font-size:11px;background:#333;color:" +
|
|
4338
|
+
btnColor +
|
|
4339
|
+
";border:1px solid #444;border-radius:3px;cursor:pointer",
|
|
4340
|
+
title: "代理切换后强制重启全部命令(默认关闭)",
|
|
4341
|
+
},
|
|
4342
|
+
(enabled ? "开 " : "关 ") + "⚙️",
|
|
4343
|
+
) +
|
|
4344
|
+
styled("span", "color:#666;margin-left:auto", label),
|
|
4345
|
+
);
|
|
4346
|
+
}
|
|
4347
|
+
|
|
4348
|
+
// ── 代理切换重启配置弹窗 ──
|
|
4349
|
+
|
|
4350
|
+
var _wdSwitchUserId = "";
|
|
4351
|
+
|
|
4352
|
+
function wdShowSwitchRestartModal(userId) {
|
|
4353
|
+
_wdSwitchUserId = userId;
|
|
4354
|
+
document.getElementById("wdSwitchUserId").textContent = userId;
|
|
4355
|
+
var entry =
|
|
4356
|
+
_wdData && _wdData.switchRestarts && _wdData.switchRestarts[userId]
|
|
4357
|
+
? _wdData.switchRestarts[userId]
|
|
4358
|
+
: null;
|
|
4359
|
+
document.getElementById("wdSwitchEnabled").checked = entry
|
|
4360
|
+
? !!entry.enabled
|
|
4361
|
+
: false;
|
|
4362
|
+
document.getElementById("wdSwitchRestartModal").style.display = "flex";
|
|
4363
|
+
}
|
|
4364
|
+
|
|
4365
|
+
async function wdSetSwitchRestart() {
|
|
4366
|
+
var config = {
|
|
4367
|
+
enabled: document.getElementById("wdSwitchEnabled").checked,
|
|
4368
|
+
};
|
|
4369
|
+
try {
|
|
4370
|
+
var res = await fetch("/api/watchdog/set-switch-restart", {
|
|
4371
|
+
method: "POST",
|
|
4372
|
+
headers: { "Content-Type": "application/json" },
|
|
4373
|
+
body: JSON.stringify({ userId: _wdSwitchUserId, config: config }),
|
|
4374
|
+
});
|
|
4375
|
+
var d = await res.json();
|
|
4376
|
+
document.getElementById("wdSwitchRestartModal").style.display = "none";
|
|
4377
|
+
if (d.ok) {
|
|
4378
|
+
alert(
|
|
4379
|
+
config.enabled
|
|
4380
|
+
? "已开启代理切换重启\n\n代理节点切换后,客户端将强制重启全部命令。\n客户端将在下次心跳时生效(约15秒内)。"
|
|
4381
|
+
: "已关闭代理切换重启\n\n客户端将在下次心跳时生效(约15秒内)。",
|
|
4382
|
+
);
|
|
4383
|
+
} else {
|
|
4384
|
+
alert("失败: " + (d.error || "未知错误"));
|
|
4385
|
+
}
|
|
4386
|
+
fetchWatchdogData();
|
|
4387
|
+
} catch (e) {
|
|
4388
|
+
alert("保存失败: " + e.message);
|
|
4389
|
+
}
|
|
4390
|
+
}
|
|
4391
|
+
|
|
4392
|
+
// ── 自动升级开关(默认开启,后端可下发关闭) ──
|
|
4393
|
+
|
|
4394
|
+
function renderAutoUpgradeSection(c, userId, autoUpgrades) {
|
|
4395
|
+
if (!userId) return "";
|
|
4396
|
+
var entry = (autoUpgrades && autoUpgrades[userId]) || null;
|
|
4397
|
+
var enabled = entry ? !!entry.enabled : true; // 未配置默认开启
|
|
4398
|
+
var label = enabled ? "已开启" : "已关闭";
|
|
4399
|
+
var btnColor = enabled ? "#4ade80" : "#f87171";
|
|
4400
|
+
return styled(
|
|
4401
|
+
"div",
|
|
4402
|
+
"margin-top:6px;padding:6px 8px;background:#111;border-radius:6px;" +
|
|
4403
|
+
"display:flex;align-items:center;gap:8px;font-size:12px",
|
|
4404
|
+
styled("span", "font-size:14px", "🚀") +
|
|
4405
|
+
styled("span", "color:#888", "自动升级:") +
|
|
4406
|
+
tag(
|
|
4407
|
+
"button",
|
|
4408
|
+
{
|
|
4409
|
+
onclick: "wdShowAutoUpgradeModal('" + esc(userId) + "')",
|
|
4410
|
+
style:
|
|
4411
|
+
"padding:2px 8px;font-size:11px;background:#333;color:" +
|
|
4412
|
+
btnColor +
|
|
4413
|
+
";border:1px solid #444;border-radius:3px;cursor:pointer",
|
|
4414
|
+
title: "看门狗客户端检测到新版本时自动升级(默认开启)",
|
|
4415
|
+
},
|
|
4416
|
+
(enabled ? "开 " : "关 ") + "⚙️",
|
|
4417
|
+
) +
|
|
4418
|
+
styled("span", "color:#666;margin-left:auto", label),
|
|
4419
|
+
);
|
|
4420
|
+
}
|
|
4421
|
+
|
|
4422
|
+
// ── 自动升级开关弹窗 ──
|
|
4423
|
+
|
|
4424
|
+
var _wdAutoUpgradeUserId = "";
|
|
4425
|
+
|
|
4426
|
+
function wdShowAutoUpgradeModal(userId) {
|
|
4427
|
+
_wdAutoUpgradeUserId = userId;
|
|
4428
|
+
document.getElementById("wdAutoUpgradeUserId").textContent = userId;
|
|
4429
|
+
var entry =
|
|
4430
|
+
_wdData && _wdData.autoUpgrades && _wdData.autoUpgrades[userId]
|
|
4431
|
+
? _wdData.autoUpgrades[userId]
|
|
4432
|
+
: null;
|
|
4433
|
+
// 未配置 = 默认开启
|
|
4434
|
+
document.getElementById("wdAutoUpgradeEnabled").checked = entry
|
|
4435
|
+
? !!entry.enabled
|
|
4436
|
+
: true;
|
|
4437
|
+
document.getElementById("wdAutoUpgradeModal").style.display = "flex";
|
|
4438
|
+
}
|
|
4439
|
+
|
|
4440
|
+
async function wdSetAutoUpgrade() {
|
|
4441
|
+
var config = {
|
|
4442
|
+
enabled: document.getElementById("wdAutoUpgradeEnabled").checked,
|
|
4443
|
+
};
|
|
4444
|
+
try {
|
|
4445
|
+
var res = await fetch("/api/watchdog/set-auto-upgrade", {
|
|
4446
|
+
method: "POST",
|
|
4447
|
+
headers: { "Content-Type": "application/json" },
|
|
4448
|
+
body: JSON.stringify({ userId: _wdAutoUpgradeUserId, config: config }),
|
|
4449
|
+
});
|
|
4450
|
+
var d = await res.json();
|
|
4451
|
+
document.getElementById("wdAutoUpgradeModal").style.display = "none";
|
|
4452
|
+
if (d.ok) {
|
|
4453
|
+
alert(
|
|
4454
|
+
config.enabled
|
|
4455
|
+
? "已开启自动升级\n\n客户端检测到新版本时会自动升级。\n客户端将在下次心跳时生效(约15秒内)。"
|
|
4456
|
+
: "已关闭自动升级\n\n该客户端将不再自动升级新版本(仍可手动升级)。\n客户端将在下次心跳时生效(约15秒内)。",
|
|
4457
|
+
);
|
|
4458
|
+
} else {
|
|
4459
|
+
alert("失败: " + (d.error || "未知错误"));
|
|
4460
|
+
}
|
|
4461
|
+
fetchWatchdogData();
|
|
4462
|
+
} catch (e) {
|
|
4463
|
+
alert("保存失败: " + e.message);
|
|
4464
|
+
}
|
|
4465
|
+
}
|
|
4466
|
+
|
|
4285
4467
|
// ── 错误自动切节点配置弹窗 ──
|
|
4286
4468
|
|
|
4287
4469
|
var _wdErrUserId = "";
|
|
@@ -4353,10 +4535,11 @@ function renderProxySection(c, userId) {
|
|
|
4353
4535
|
var h = c.clashHealth;
|
|
4354
4536
|
if (!h || !h.node) return "";
|
|
4355
4537
|
var nodes = (h.meta && h.meta.healthyNodes) || [];
|
|
4538
|
+
var delays = (h.meta && h.meta.healthyNodeDelays) || {};
|
|
4356
4539
|
var hasNodes = nodes.length > 0;
|
|
4357
4540
|
return (
|
|
4358
4541
|
renderProxyLine(h, nodes, userId, 0, hasNodes) +
|
|
4359
|
-
renderNodeList(nodes, h.node, userId, 0)
|
|
4542
|
+
renderNodeList(nodes, h.node, userId, 0, delays)
|
|
4360
4543
|
);
|
|
4361
4544
|
}
|
|
4362
4545
|
|
|
@@ -4364,10 +4547,11 @@ function renderProxySection(c, userId) {
|
|
|
4364
4547
|
function renderProxyChannel(ch, userId) {
|
|
4365
4548
|
if (!ch || !ch.node) return "";
|
|
4366
4549
|
var nodes = (ch.meta && ch.meta.healthyNodes) || [];
|
|
4550
|
+
var delays = (ch.meta && ch.meta.healthyNodeDelays) || {};
|
|
4367
4551
|
var hasNodes = nodes.length > 0;
|
|
4368
4552
|
return (
|
|
4369
4553
|
renderProxyLine(ch, nodes, userId, ch.port || 0, hasNodes) +
|
|
4370
|
-
renderNodeList(nodes, ch.node, userId, ch.port || 0)
|
|
4554
|
+
renderNodeList(nodes, ch.node, userId, ch.port || 0, delays)
|
|
4371
4555
|
);
|
|
4372
4556
|
}
|
|
4373
4557
|
|
|
@@ -4417,31 +4601,49 @@ function renderProxyLine(h, nodes, userId, port, hasNodes) {
|
|
|
4417
4601
|
}
|
|
4418
4602
|
|
|
4419
4603
|
/** 节点列表面板(默认隐藏) */
|
|
4420
|
-
function renderNodeList(nodes, currentNode, userId, port) {
|
|
4604
|
+
function renderNodeList(nodes, currentNode, userId, port, delays) {
|
|
4421
4605
|
if (!nodes || !nodes.length) return "";
|
|
4606
|
+
delays = delays || {};
|
|
4422
4607
|
|
|
4423
4608
|
var items = "";
|
|
4424
4609
|
for (var i = 0; i < nodes.length; i++) {
|
|
4425
|
-
items += renderNodeItem(
|
|
4610
|
+
items += renderNodeItem(
|
|
4611
|
+
nodes[i],
|
|
4612
|
+
nodes[i] === currentNode,
|
|
4613
|
+
userId,
|
|
4614
|
+
port,
|
|
4615
|
+
delays[nodes[i]],
|
|
4616
|
+
);
|
|
4426
4617
|
}
|
|
4427
4618
|
|
|
4428
4619
|
var id = "wdNodes-" + wdEscId(userId) + "-" + port;
|
|
4620
|
+
var expanded = _wdExpandedNodes[wdEscId(userId) + "-" + (port || 0)];
|
|
4429
4621
|
return (
|
|
4430
4622
|
'<div id="' +
|
|
4431
4623
|
id +
|
|
4432
4624
|
'" style="margin-top:4px;border:1px solid #2a2a3a;' +
|
|
4433
|
-
|
|
4625
|
+
"border-radius:6px;overflow:hidden;display:" +
|
|
4626
|
+
(expanded ? "block" : "none") +
|
|
4627
|
+
'">' +
|
|
4434
4628
|
items +
|
|
4435
4629
|
"</div>"
|
|
4436
4630
|
);
|
|
4437
4631
|
}
|
|
4438
4632
|
|
|
4439
4633
|
/** 单个节点行 */
|
|
4440
|
-
function renderNodeItem(name, isActive, userId, port) {
|
|
4634
|
+
function renderNodeItem(name, isActive, userId, port, delay) {
|
|
4441
4635
|
var color = isActive ? "#4ade80" : "#888";
|
|
4442
4636
|
var bg = isActive ? "background:#1a3a2a;" : "";
|
|
4443
4637
|
var dot = isActive ? "●" : "○";
|
|
4444
4638
|
var textColor = isActive ? "#4ade80" : "#aaa";
|
|
4639
|
+
var delayTag =
|
|
4640
|
+
typeof delay === "number" && delay > 0
|
|
4641
|
+
? '<span style="color:' +
|
|
4642
|
+
(isActive ? "#6ee7a0" : "#777") +
|
|
4643
|
+
';font-size:10px">' +
|
|
4644
|
+
Math.round(delay) +
|
|
4645
|
+
"ms</span>"
|
|
4646
|
+
: "";
|
|
4445
4647
|
var action = isActive
|
|
4446
4648
|
? '<span style="margin-left:auto;color:#555;font-size:10px">当前</span>'
|
|
4447
4649
|
: "<button onclick=\"wdSwitchNode('" +
|
|
@@ -4455,7 +4657,7 @@ function renderNodeItem(name, isActive, userId, port) {
|
|
|
4455
4657
|
'color:#fff;border:none;border-radius:2px;cursor:pointer">切换</button>';
|
|
4456
4658
|
|
|
4457
4659
|
return (
|
|
4458
|
-
'<div style="padding:3px 8px;display:flex;align-items:center;gap:6px;font-size:11px;' +
|
|
4660
|
+
'<div class="wd-hover-row" style="padding:3px 8px;display:flex;align-items:center;gap:6px;font-size:11px;' +
|
|
4459
4661
|
bg +
|
|
4460
4662
|
'">' +
|
|
4461
4663
|
'<span style="color:' +
|
|
@@ -4468,6 +4670,7 @@ function renderNodeItem(name, isActive, userId, port) {
|
|
|
4468
4670
|
'">' +
|
|
4469
4671
|
name +
|
|
4470
4672
|
"</span>" +
|
|
4673
|
+
delayTag +
|
|
4471
4674
|
action +
|
|
4472
4675
|
"</div>"
|
|
4473
4676
|
);
|
|
@@ -4475,16 +4678,24 @@ function renderNodeItem(name, isActive, userId, port) {
|
|
|
4475
4678
|
|
|
4476
4679
|
// ── 交互函数 ──
|
|
4477
4680
|
|
|
4681
|
+
// 节点列表面板展开状态(key: userId-port → true),自动刷新重渲染时保持展开
|
|
4682
|
+
var _wdExpandedNodes = {};
|
|
4683
|
+
|
|
4478
4684
|
function wdEscId(s) {
|
|
4479
4685
|
return (s || "").replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4480
4686
|
}
|
|
4481
4687
|
|
|
4482
4688
|
function wdToggleNodes(userId, port) {
|
|
4483
|
-
var
|
|
4484
|
-
|
|
4485
|
-
);
|
|
4689
|
+
var key = wdEscId(userId) + "-" + (port || 0);
|
|
4690
|
+
var el = document.getElementById("wdNodes-" + key);
|
|
4486
4691
|
if (!el) return;
|
|
4487
|
-
|
|
4692
|
+
var nowHidden = el.style.display === "none";
|
|
4693
|
+
el.style.display = nowHidden ? "block" : "none";
|
|
4694
|
+
if (nowHidden) {
|
|
4695
|
+
_wdExpandedNodes[key] = true;
|
|
4696
|
+
} else {
|
|
4697
|
+
delete _wdExpandedNodes[key];
|
|
4698
|
+
}
|
|
4488
4699
|
}
|
|
4489
4700
|
|
|
4490
4701
|
function wdSwitchNode(userId, node, port) {
|
|
@@ -4578,9 +4789,13 @@ function renderCmdRows(myCmds, userId, procs, isOnline, llmAnalysis, clientId) {
|
|
|
4578
4789
|
var restart = p && p.restartCount > 0 ? String(p.restartCount) : "0";
|
|
4579
4790
|
|
|
4580
4791
|
var prefix = "wd-" + esc(userId) + "-" + idx;
|
|
4581
|
-
html +=
|
|
4792
|
+
html += tag(
|
|
4582
4793
|
"div",
|
|
4583
|
-
|
|
4794
|
+
{
|
|
4795
|
+
class: "wd-hover-row",
|
|
4796
|
+
style:
|
|
4797
|
+
"display:grid;grid-template-columns:1fr 50px 55px 55px 50px 80px 40px;gap:4px;padding:4px 0;align-items:center;font-size:12px;border-top:1px solid #1a1a2a",
|
|
4798
|
+
},
|
|
4584
4799
|
styled(
|
|
4585
4800
|
"span",
|
|
4586
4801
|
"color:" +
|
|
@@ -4710,9 +4925,13 @@ function renderCmdRows(myCmds, userId, procs, isOnline, llmAnalysis, clientId) {
|
|
|
4710
4925
|
var sc = p.status === "running" ? "#4ade80" : "#f87171";
|
|
4711
4926
|
var oid = p.pid || p.cmdId || "?";
|
|
4712
4927
|
var opre = "wd-" + esc(userId) + "-orphan-" + esc(String(oid));
|
|
4713
|
-
html +=
|
|
4928
|
+
html += tag(
|
|
4714
4929
|
"div",
|
|
4715
|
-
|
|
4930
|
+
{
|
|
4931
|
+
class: "wd-hover-row",
|
|
4932
|
+
style:
|
|
4933
|
+
"display:grid;grid-template-columns:1fr 50px 55px 55px 50px 80px 40px;gap:4px;padding:4px 0;align-items:center;font-size:12px;border-top:1px solid #1a1a2a;opacity:0.5",
|
|
4934
|
+
},
|
|
4716
4935
|
styled(
|
|
4717
4936
|
"span",
|
|
4718
4937
|
"color:#666;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:monospace",
|
|
@@ -4795,8 +5014,7 @@ function wdShowLogsModal(clientId, cmdId, title) {
|
|
|
4795
5014
|
_wdLogs.deadline = Date.now() + 90000;
|
|
4796
5015
|
_wdLogs.gotFirst = false;
|
|
4797
5016
|
document.getElementById("wdLogsTitle").textContent = "📄 " + title;
|
|
4798
|
-
document.getElementById("wdLogsStatus").textContent =
|
|
4799
|
-
"⏳ 已请求客户端回传…";
|
|
5017
|
+
document.getElementById("wdLogsStatus").textContent = "⏳ 已请求客户端回传…";
|
|
4800
5018
|
document.getElementById("wdLogsContent").textContent =
|
|
4801
5019
|
"等待客户端心跳回传日志…\n(心跳周期 ≤15s,最长等待 90s;拿到后会实时刷新)";
|
|
4802
5020
|
document.getElementById("wdLogsModal").style.display = "flex";
|
|
@@ -667,6 +667,46 @@
|
|
|
667
667
|
</div>
|
|
668
668
|
</div>
|
|
669
669
|
|
|
670
|
+
<!-- 代理切换重启配置弹窗 -->
|
|
671
|
+
<div id="wdSwitchRestartModal"
|
|
672
|
+
style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.6);z-index:9999;align-items:center;justify-content:center">
|
|
673
|
+
<div style="background:#1e1e2e;border:1px solid #333;border-radius:12px;padding:24px;width:90%;max-width:480px">
|
|
674
|
+
<div style="font-size:16px;color:#4ade80;margin-bottom:4px">🔁 代理切换重启 — <span id="wdSwitchUserId"></span></div>
|
|
675
|
+
<div style="color:#888;font-size:12px;margin-bottom:12px">代理节点发生切换后,客户端强制重启全部被监控命令(错峰停止 + 错峰启动)。<br><b
|
|
676
|
+
style="color:#f87171">默认关闭</b>,需手动开启。</div>
|
|
677
|
+
<label
|
|
678
|
+
style="display:flex;align-items:center;gap:8px;margin-bottom:12px;color:#e0e0e0;font-size:13px;cursor:pointer">
|
|
679
|
+
<input type="checkbox" id="wdSwitchEnabled" style="width:16px;height:16px"> 启用(切换后强制重启全部命令)
|
|
680
|
+
</label>
|
|
681
|
+
<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:16px">
|
|
682
|
+
<button onclick="document.getElementById('wdSwitchRestartModal').style.display='none'"
|
|
683
|
+
style="padding:8px 16px;background:#333;color:#ccc;border:none;border-radius:6px;cursor:pointer;font-size:12px">取消</button>
|
|
684
|
+
<button onclick="wdSetSwitchRestart()"
|
|
685
|
+
style="padding:8px 16px;background:#4ade80;color:#000;border:none;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600">保存并下发</button>
|
|
686
|
+
</div>
|
|
687
|
+
</div>
|
|
688
|
+
</div>
|
|
689
|
+
|
|
690
|
+
<!-- 自动升级开关弹窗 -->
|
|
691
|
+
<div id="wdAutoUpgradeModal"
|
|
692
|
+
style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.6);z-index:9999;align-items:center;justify-content:center">
|
|
693
|
+
<div style="background:#1e1e2e;border:1px solid #333;border-radius:12px;padding:24px;width:90%;max-width:480px">
|
|
694
|
+
<div style="font-size:16px;color:#4ade80;margin-bottom:4px">🚀 自动升级 — <span id="wdAutoUpgradeUserId"></span></div>
|
|
695
|
+
<div style="color:#888;font-size:12px;margin-bottom:12px">客户端看门狗定期检测新版本并自动升级。<br><b
|
|
696
|
+
style="color:#4ade80">默认开启</b>,关闭后该客户端不再自动升级(仍可手动升级)。</div>
|
|
697
|
+
<label
|
|
698
|
+
style="display:flex;align-items:center;gap:8px;margin-bottom:12px;color:#e0e0e0;font-size:13px;cursor:pointer">
|
|
699
|
+
<input type="checkbox" id="wdAutoUpgradeEnabled" style="width:16px;height:16px"> 启用(检测到新版本自动升级)
|
|
700
|
+
</label>
|
|
701
|
+
<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:16px">
|
|
702
|
+
<button onclick="document.getElementById('wdAutoUpgradeModal').style.display='none'"
|
|
703
|
+
style="padding:8px 16px;background:#333;color:#ccc;border:none;border-radius:6px;cursor:pointer;font-size:12px">取消</button>
|
|
704
|
+
<button onclick="wdSetAutoUpgrade()"
|
|
705
|
+
style="padding:8px 16px;background:#4ade80;color:#000;border:none;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600">保存并下发</button>
|
|
706
|
+
</div>
|
|
707
|
+
</div>
|
|
708
|
+
</div>
|
|
709
|
+
|
|
670
710
|
<!-- 登录检测端口配置弹窗 -->
|
|
671
711
|
<div id="wdLoginPortsModal"
|
|
672
712
|
style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.6);z-index:9999;align-items:center;justify-content:center">
|
|
@@ -704,7 +744,7 @@
|
|
|
704
744
|
</div>
|
|
705
745
|
</div>
|
|
706
746
|
|
|
707
|
-
<script src="app.js?v=
|
|
747
|
+
<script src="app.js?v=20260828c"></script>
|
|
708
748
|
</body>
|
|
709
749
|
|
|
710
750
|
</html>
|
|
@@ -118,6 +118,24 @@ body {
|
|
|
118
118
|
margin-top: 2px;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
/* 看门狗面板:列表行 hover 高亮(内联背景用 !important 覆盖,移开后自动恢复) */
|
|
122
|
+
.wd-hover-row {
|
|
123
|
+
transition: background 0.08s ease;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.wd-hover-row:hover {
|
|
127
|
+
background: rgba(96, 165, 250, 0.12) !important;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* 看门狗面板:客户端卡片 hover 微亮(不改边框,保留离线红色警示) */
|
|
131
|
+
.wd-client-card {
|
|
132
|
+
transition: background 0.15s ease;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.wd-client-card:hover {
|
|
136
|
+
background: #232338 !important;
|
|
137
|
+
}
|
|
138
|
+
|
|
121
139
|
.stat-card.clickable {
|
|
122
140
|
cursor: pointer;
|
|
123
141
|
}
|
package/src/watch/server.js
CHANGED