tt-help-cli-ycl 1.4.140 → 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/src/clash/daemon.js +26 -10
- package/src/cli/tag.js +26 -17
- package/src/lib/api-client.js +11 -5
- package/src/lib/client-error-report.js +3 -0
- package/src/watch/data-store.js +3 -0
- package/src/watch/public/app.js +182 -8
- package/src/watch/public/index.html +41 -1
- 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 +1 -0
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,6 +3651,8 @@ 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
3658
|
return tag(
|
|
@@ -3654,10 +3664,10 @@ function renderOfflineCard(
|
|
|
3654
3664
|
},
|
|
3655
3665
|
styled(
|
|
3656
3666
|
"div",
|
|
3657
|
-
"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",
|
|
3658
3668
|
styled(
|
|
3659
3669
|
"div",
|
|
3660
|
-
"",
|
|
3670
|
+
"display:flex;align-items:center;flex-wrap:wrap;min-width:0;flex:1",
|
|
3661
3671
|
tag(
|
|
3662
3672
|
"span",
|
|
3663
3673
|
{
|
|
@@ -3696,7 +3706,7 @@ function renderOfflineCard(
|
|
|
3696
3706
|
) +
|
|
3697
3707
|
styled(
|
|
3698
3708
|
"div",
|
|
3699
|
-
"display:flex;gap:10px;align-items:center",
|
|
3709
|
+
"display:flex;gap:10px;align-items:center;flex-wrap:wrap;flex-shrink:0",
|
|
3700
3710
|
styled("span", "color:#f87171;font-size:13px", "● 离线") +
|
|
3701
3711
|
styled("span", "color:#555;font-size:12px", "—") +
|
|
3702
3712
|
tag(
|
|
@@ -3718,6 +3728,8 @@ function renderOfflineCard(
|
|
|
3718
3728
|
) +
|
|
3719
3729
|
renderCountrySection({}, userId, clashCountries) +
|
|
3720
3730
|
renderErrorTriggerSection({}, userId, errorTriggers) +
|
|
3731
|
+
renderSwitchRestartSection({}, userId, switchRestarts) +
|
|
3732
|
+
renderAutoUpgradeSection({}, userId, autoUpgrades) +
|
|
3721
3733
|
renderLoginSection({}, userId, loginPorts) +
|
|
3722
3734
|
renderCmdRows(cmds || [], userId, [], false),
|
|
3723
3735
|
);
|
|
@@ -3759,6 +3771,8 @@ function renderClientCard(
|
|
|
3759
3771
|
clashCountries,
|
|
3760
3772
|
errorTriggers,
|
|
3761
3773
|
loginPorts,
|
|
3774
|
+
switchRestarts,
|
|
3775
|
+
autoUpgrades,
|
|
3762
3776
|
) {
|
|
3763
3777
|
var os = c.osInfo || {},
|
|
3764
3778
|
cfg = c.config || {},
|
|
@@ -3787,7 +3801,10 @@ function renderClientCard(
|
|
|
3787
3801
|
var onlineBadge = online
|
|
3788
3802
|
? styled("span", "color:#4ade80", "● 在线")
|
|
3789
3803
|
: styled("span", "color:#f87171", "● 离线");
|
|
3790
|
-
|
|
3804
|
+
// 完整日期时间:跨天离线也能看出最后心跳是哪天
|
|
3805
|
+
var lastSeen = c.lastHeartbeat
|
|
3806
|
+
? new Date(c.lastHeartbeat).toLocaleString("zh-CN", { hour12: false })
|
|
3807
|
+
: "?";
|
|
3791
3808
|
|
|
3792
3809
|
// 离线时标题和边框变红
|
|
3793
3810
|
var titleColor = online ? "#60a5fa" : "#f87171";
|
|
@@ -3795,10 +3812,10 @@ function renderClientCard(
|
|
|
3795
3812
|
|
|
3796
3813
|
var header = styled(
|
|
3797
3814
|
"div",
|
|
3798
|
-
"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",
|
|
3799
3816
|
styled(
|
|
3800
3817
|
"div",
|
|
3801
|
-
"",
|
|
3818
|
+
"display:flex;align-items:center;flex-wrap:wrap;min-width:0;flex:1",
|
|
3802
3819
|
tag(
|
|
3803
3820
|
"span",
|
|
3804
3821
|
{
|
|
@@ -3858,10 +3875,16 @@ function renderClientCard(
|
|
|
3858
3875
|
) +
|
|
3859
3876
|
styled(
|
|
3860
3877
|
"div",
|
|
3861
|
-
"display:flex;gap:10px;align-items:center",
|
|
3878
|
+
"display:flex;gap:10px;align-items:center;flex-wrap:wrap;flex-shrink:0",
|
|
3862
3879
|
styled("span", "color:#888;font-size:13px", c.ip) +
|
|
3863
3880
|
onlineBadge +
|
|
3864
|
-
styled(
|
|
3881
|
+
styled(
|
|
3882
|
+
"span",
|
|
3883
|
+
online
|
|
3884
|
+
? "color:#666;font-size:12px"
|
|
3885
|
+
: "color:#fca5a5;font-size:12px",
|
|
3886
|
+
"最后心跳 " + lastSeen,
|
|
3887
|
+
) +
|
|
3865
3888
|
(!online && userId
|
|
3866
3889
|
? tag(
|
|
3867
3890
|
"button",
|
|
@@ -3891,6 +3914,8 @@ function renderClientCard(
|
|
|
3891
3914
|
styled("div", "color:#888;font-size:12px;margin-bottom:6px", infoLine) +
|
|
3892
3915
|
renderCountrySection(c, userId, clashCountries) +
|
|
3893
3916
|
renderErrorTriggerSection(c, userId, errorTriggers) +
|
|
3917
|
+
renderSwitchRestartSection(c, userId, switchRestarts) +
|
|
3918
|
+
renderAutoUpgradeSection(c, userId, autoUpgrades) +
|
|
3894
3919
|
renderLoginSection(c, userId, loginPorts) +
|
|
3895
3920
|
renderProxySection(c, userId) +
|
|
3896
3921
|
renderCmdRows(myCmds, userId, procs, online, c.llmAnalysis, c.clientId),
|
|
@@ -4290,6 +4315,155 @@ function renderErrorTriggerSection(c, userId, errorTriggers) {
|
|
|
4290
4315
|
);
|
|
4291
4316
|
}
|
|
4292
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
|
+
|
|
4293
4467
|
// ── 错误自动切节点配置弹窗 ──
|
|
4294
4468
|
|
|
4295
4469
|
var _wdErrUserId = "";
|
|
@@ -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>
|
package/src/watch/server.js
CHANGED
|
@@ -258,6 +258,46 @@ function getAllErrorTriggers() {
|
|
|
258
258
|
return cfg.errorTriggers || {};
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
// ── 代理切换重启配置管理(per agent,默认不启用:切换后不重启,后端下发开启才重启) ──
|
|
262
|
+
|
|
263
|
+
function getSwitchRestartStored(userId) {
|
|
264
|
+
if (!userId) return null;
|
|
265
|
+
const cfg = loadConfig();
|
|
266
|
+
return (cfg.switchRestarts && cfg.switchRestarts[userId]) || null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function setSwitchRestartStored(userId, config) {
|
|
270
|
+
const cfg = loadConfig();
|
|
271
|
+
if (!cfg.switchRestarts) cfg.switchRestarts = {};
|
|
272
|
+
cfg.switchRestarts[userId] = config;
|
|
273
|
+
saveConfig(cfg);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function getAllSwitchRestarts() {
|
|
277
|
+
const cfg = loadConfig();
|
|
278
|
+
return cfg.switchRestarts || {};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ── 自动升级开关配置管理(per agent,默认开启:未配置或 enabled=true 均开启,后端显式关闭才停用) ──
|
|
282
|
+
|
|
283
|
+
function getAutoUpgradeStored(userId) {
|
|
284
|
+
if (!userId) return null;
|
|
285
|
+
const cfg = loadConfig();
|
|
286
|
+
return (cfg.autoUpgrades && cfg.autoUpgrades[userId]) || null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function setAutoUpgradeStored(userId, config) {
|
|
290
|
+
const cfg = loadConfig();
|
|
291
|
+
if (!cfg.autoUpgrades) cfg.autoUpgrades = {};
|
|
292
|
+
cfg.autoUpgrades[userId] = config;
|
|
293
|
+
saveConfig(cfg);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function getAllAutoUpgrades() {
|
|
297
|
+
const cfg = loadConfig();
|
|
298
|
+
return cfg.autoUpgrades || {};
|
|
299
|
+
}
|
|
300
|
+
|
|
261
301
|
// ── 登录端口检测配置管理(per agent,后端配置要检查的 CDP 端口列表) ──
|
|
262
302
|
|
|
263
303
|
function getLoginPortsStored(userId) {
|
|
@@ -438,6 +478,8 @@ async function handleHeartbeat(params, body, res) {
|
|
|
438
478
|
logRequests: pendingLogs.length > 0 ? pendingLogs : undefined,
|
|
439
479
|
clientConfig: getClientConfigStored(userId),
|
|
440
480
|
errorTrigger: getErrorTriggerStored(userId),
|
|
481
|
+
switchRestart: getSwitchRestartStored(userId),
|
|
482
|
+
autoUpgrade: getAutoUpgradeStored(userId),
|
|
441
483
|
loginPorts: getLoginPortsStored(userId),
|
|
442
484
|
serverTime: Date.now(),
|
|
443
485
|
});
|
|
@@ -452,6 +494,8 @@ async function handleGetClients(params, body, res) {
|
|
|
452
494
|
clashCountries: getAllClientCountries(),
|
|
453
495
|
supportedCountries: SUPPORTED_COUNTRIES,
|
|
454
496
|
errorTriggers: getAllErrorTriggers(),
|
|
497
|
+
switchRestarts: getAllSwitchRestarts(),
|
|
498
|
+
autoUpgrades: getAllAutoUpgrades(),
|
|
455
499
|
loginPorts: getAllLoginPorts(),
|
|
456
500
|
configPath: WATCHDOG_CONFIG_PATH,
|
|
457
501
|
});
|
|
@@ -605,6 +649,8 @@ async function handleDeleteClient(params, body, res) {
|
|
|
605
649
|
if (cfg.clientConfigs) delete cfg.clientConfigs[userId];
|
|
606
650
|
if (cfg.clashCountries) delete cfg.clashCountries[userId];
|
|
607
651
|
if (cfg.errorTriggers) delete cfg.errorTriggers[userId];
|
|
652
|
+
if (cfg.switchRestarts) delete cfg.switchRestarts[userId];
|
|
653
|
+
if (cfg.autoUpgrades) delete cfg.autoUpgrades[userId];
|
|
608
654
|
if (cfg.loginPorts) delete cfg.loginPorts[userId];
|
|
609
655
|
saveConfig(cfg);
|
|
610
656
|
lastSentCountries.delete(userId);
|
|
@@ -725,6 +771,52 @@ async function handleSetErrorTrigger(params, body, res) {
|
|
|
725
771
|
sendJSON(res, 200, { ok: true, config: cleaned });
|
|
726
772
|
}
|
|
727
773
|
|
|
774
|
+
/** 配置某 agent 的代理切换重启(代理切换后强制重启全部命令;默认不启用) */
|
|
775
|
+
async function handleSetSwitchRestart(params, body, res) {
|
|
776
|
+
const { userId, config } = body || {};
|
|
777
|
+
if (!userId) {
|
|
778
|
+
sendJSON(res, 400, { error: "缺少 userId" });
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
if (!config || typeof config !== "object") {
|
|
782
|
+
sendJSON(res, 400, { error: "缺少 config 对象" });
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const cleaned = {
|
|
786
|
+
enabled: !!config.enabled,
|
|
787
|
+
updatedAt: new Date().toISOString(),
|
|
788
|
+
};
|
|
789
|
+
setSwitchRestartStored(userId, cleaned);
|
|
790
|
+
const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false });
|
|
791
|
+
console.error(
|
|
792
|
+
`[watchdog ${ts}] ${userId} 代理切换重启配置已更新: ${JSON.stringify(cleaned)}`,
|
|
793
|
+
);
|
|
794
|
+
sendJSON(res, 200, { ok: true, config: cleaned });
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/** 配置某 agent 的自动升级开关(默认开启;后端显式关闭才停用自动升级检测) */
|
|
798
|
+
async function handleSetAutoUpgrade(params, body, res) {
|
|
799
|
+
const { userId, config } = body || {};
|
|
800
|
+
if (!userId) {
|
|
801
|
+
sendJSON(res, 400, { error: "缺少 userId" });
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
if (!config || typeof config !== "object") {
|
|
805
|
+
sendJSON(res, 400, { error: "缺少 config 对象" });
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
const cleaned = {
|
|
809
|
+
enabled: !!config.enabled,
|
|
810
|
+
updatedAt: new Date().toISOString(),
|
|
811
|
+
};
|
|
812
|
+
setAutoUpgradeStored(userId, cleaned);
|
|
813
|
+
const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false });
|
|
814
|
+
console.error(
|
|
815
|
+
`[watchdog ${ts}] ${userId} 自动升级配置已更新: ${JSON.stringify(cleaned)}`,
|
|
816
|
+
);
|
|
817
|
+
sendJSON(res, 200, { ok: true, config: cleaned });
|
|
818
|
+
}
|
|
819
|
+
|
|
728
820
|
/** 配置某 agent 要检测登录状态的 CDP 端口列表(逗号分隔或数组) */
|
|
729
821
|
async function handleSetLoginPorts(params, body, res) {
|
|
730
822
|
const { userId, ports } = body || {};
|
|
@@ -833,6 +925,14 @@ export async function tryHandleWatchdogRoute(
|
|
|
833
925
|
await handleSetErrorTrigger(params, body, res);
|
|
834
926
|
return true;
|
|
835
927
|
}
|
|
928
|
+
if (method === "POST" && routePath === "/api/watchdog/set-switch-restart") {
|
|
929
|
+
await handleSetSwitchRestart(params, body, res);
|
|
930
|
+
return true;
|
|
931
|
+
}
|
|
932
|
+
if (method === "POST" && routePath === "/api/watchdog/set-auto-upgrade") {
|
|
933
|
+
await handleSetAutoUpgrade(params, body, res);
|
|
934
|
+
return true;
|
|
935
|
+
}
|
|
836
936
|
if (method === "POST" && routePath === "/api/watchdog/login-ports") {
|
|
837
937
|
await handleSetLoginPorts(params, body, res);
|
|
838
938
|
return true;
|