tt-help-cli-ycl 1.4.140 → 1.4.144
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/sync-raw-jobs.mjs +13 -7
- package/scripts/test-api-interceptor-429.mjs +122 -0
- 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/comments.js +5 -5
- package/src/cli/precheck.js +2 -2
- package/src/cli/refresh-mini.js +1 -1
- package/src/cli/tag.js +32 -41
- package/src/lib/api-client.js +11 -5
- package/src/lib/api-interceptor.js +26 -8
- package/src/lib/app-config.js +119 -0
- package/src/lib/args.js +3 -1
- package/src/lib/client-error-report.js +3 -0
- package/src/lib/constants.js +3 -1
- package/src/lib/target-locations.js +1 -0
- package/src/scraper/core.js +2 -2
- package/src/scraper/explore-core.js +2 -2
- package/src/watch/data-store.js +9 -8
- package/src/watch/public/app.js +362 -34
- package/src/watch/public/index.html +62 -2
- package/src/watch/public/style.css +16 -0
- package/src/watch/server.js +85 -40
- 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
|
@@ -683,7 +683,7 @@ function closeAddModal() {
|
|
|
683
683
|
|
|
684
684
|
// ========== 国家修改模态框 ==========
|
|
685
685
|
|
|
686
|
-
const
|
|
686
|
+
const DEFAULT_LOCATION_OPTIONS = [
|
|
687
687
|
"AT",
|
|
688
688
|
"BE",
|
|
689
689
|
"CZ",
|
|
@@ -697,19 +697,35 @@ const TARGET_LOCATIONS = [
|
|
|
697
697
|
"NL",
|
|
698
698
|
"PL",
|
|
699
699
|
"PT",
|
|
700
|
+
"TH",
|
|
701
|
+
"US",
|
|
700
702
|
];
|
|
703
|
+
let locationOptions = [...DEFAULT_LOCATION_OPTIONS];
|
|
701
704
|
|
|
702
|
-
function openLocationModal(uniqueId, currentLocation) {
|
|
705
|
+
async function openLocationModal(uniqueId, currentLocation) {
|
|
703
706
|
let overlay = document.getElementById("locationModalOverlay");
|
|
704
707
|
if (overlay) return;
|
|
708
|
+
// 优先使用服务端配置的目标国家作为候选,失败则回退默认
|
|
709
|
+
try {
|
|
710
|
+
const res = await fetch("/api/settings/target-locations");
|
|
711
|
+
const data = await res.json();
|
|
712
|
+
const locs = (data.locations || []).filter(Boolean);
|
|
713
|
+
if (locs.length) {
|
|
714
|
+
locationOptions = [...new Set([...locs, ...DEFAULT_LOCATION_OPTIONS])];
|
|
715
|
+
}
|
|
716
|
+
} catch {
|
|
717
|
+
locationOptions = [...DEFAULT_LOCATION_OPTIONS];
|
|
718
|
+
}
|
|
705
719
|
overlay = document.createElement("div");
|
|
706
720
|
overlay.id = "locationModalOverlay";
|
|
707
721
|
overlay.className = "modal-overlay";
|
|
708
722
|
const safeId = escapeJsString(uniqueId);
|
|
709
|
-
const options =
|
|
710
|
-
(
|
|
711
|
-
|
|
712
|
-
|
|
723
|
+
const options = locationOptions
|
|
724
|
+
.map(
|
|
725
|
+
(loc) =>
|
|
726
|
+
`<button class="loc-option ${loc === currentLocation ? "active" : ""}" onclick="selectLocation('${safeId}','${loc}')">${loc}</button>`,
|
|
727
|
+
)
|
|
728
|
+
.join("");
|
|
713
729
|
overlay.innerHTML = `
|
|
714
730
|
<div class="modal" style="max-width:420px">
|
|
715
731
|
<h3>修改用户国家</h3>
|
|
@@ -1392,6 +1408,157 @@ function showSettingsPage() {
|
|
|
1392
1408
|
hideAllPages();
|
|
1393
1409
|
document.getElementById("settingsPage").classList.add("active");
|
|
1394
1410
|
fetchLlmConfig();
|
|
1411
|
+
loadTargetLocations();
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// ========== 目标国家配置(通用 app-config,落盘 ~/.tt-help-config.json) ==========
|
|
1415
|
+
|
|
1416
|
+
const AVAILABLE_COUNTRIES = [
|
|
1417
|
+
"AT",
|
|
1418
|
+
"BE",
|
|
1419
|
+
"CZ",
|
|
1420
|
+
"DE",
|
|
1421
|
+
"ES",
|
|
1422
|
+
"FR",
|
|
1423
|
+
"GR",
|
|
1424
|
+
"HU",
|
|
1425
|
+
"IE",
|
|
1426
|
+
"IT",
|
|
1427
|
+
"NL",
|
|
1428
|
+
"PL",
|
|
1429
|
+
"PT",
|
|
1430
|
+
"TH",
|
|
1431
|
+
"US",
|
|
1432
|
+
"GB",
|
|
1433
|
+
"SE",
|
|
1434
|
+
"NO",
|
|
1435
|
+
"FI",
|
|
1436
|
+
"DK",
|
|
1437
|
+
"RO",
|
|
1438
|
+
"BG",
|
|
1439
|
+
"CH",
|
|
1440
|
+
"SK",
|
|
1441
|
+
"SI",
|
|
1442
|
+
"HR",
|
|
1443
|
+
"RS",
|
|
1444
|
+
"UA",
|
|
1445
|
+
"TR",
|
|
1446
|
+
"MX",
|
|
1447
|
+
"BR",
|
|
1448
|
+
"AR",
|
|
1449
|
+
"CL",
|
|
1450
|
+
"CO",
|
|
1451
|
+
];
|
|
1452
|
+
|
|
1453
|
+
function normalizeCountryInput(str) {
|
|
1454
|
+
return [
|
|
1455
|
+
...new Set(
|
|
1456
|
+
String(str || "")
|
|
1457
|
+
.split(",")
|
|
1458
|
+
.map((s) => s.trim().toUpperCase())
|
|
1459
|
+
.filter(Boolean),
|
|
1460
|
+
),
|
|
1461
|
+
];
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
async function loadTargetLocations() {
|
|
1465
|
+
const input = document.getElementById("targetLocationsInput");
|
|
1466
|
+
const status = document.getElementById("targetLocationsStatus");
|
|
1467
|
+
if (!input) return;
|
|
1468
|
+
try {
|
|
1469
|
+
const res = await fetch("/api/settings/target-locations");
|
|
1470
|
+
const data = await res.json();
|
|
1471
|
+
const locations = data.locations || [];
|
|
1472
|
+
input.value = locations.join(",");
|
|
1473
|
+
renderTargetCountryGrid(locations);
|
|
1474
|
+
if (status) {
|
|
1475
|
+
status.textContent = `当前 ${locations.length} 个国家: ${locations.join(", ")}`;
|
|
1476
|
+
status.className = "settings-status ok";
|
|
1477
|
+
}
|
|
1478
|
+
} catch (e) {
|
|
1479
|
+
console.error("loadTargetLocations error:", e);
|
|
1480
|
+
if (status) {
|
|
1481
|
+
status.textContent = "读取目标国家失败";
|
|
1482
|
+
status.className = "settings-status warn";
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
function renderTargetCountryGrid(selected) {
|
|
1488
|
+
const grid = document.getElementById("targetCountryGrid");
|
|
1489
|
+
if (!grid) return;
|
|
1490
|
+
const selectedSet = new Set(selected || []);
|
|
1491
|
+
grid.innerHTML = AVAILABLE_COUNTRIES.map(
|
|
1492
|
+
(loc) =>
|
|
1493
|
+
`<button type="button" class="loc-option ${selectedSet.has(loc) ? "active" : ""}" onclick="toggleTargetCountry('${loc}')">${loc}</button>`,
|
|
1494
|
+
).join("");
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
function toggleTargetCountry(loc) {
|
|
1498
|
+
const input = document.getElementById("targetLocationsInput");
|
|
1499
|
+
if (!input) return;
|
|
1500
|
+
let current = normalizeCountryInput(input.value);
|
|
1501
|
+
if (current.includes(loc)) {
|
|
1502
|
+
current = current.filter((l) => l !== loc);
|
|
1503
|
+
} else {
|
|
1504
|
+
current.push(loc);
|
|
1505
|
+
}
|
|
1506
|
+
input.value = current.join(",");
|
|
1507
|
+
renderTargetCountryGrid(current);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
async function saveTargetLocations() {
|
|
1511
|
+
const input = document.getElementById("targetLocationsInput");
|
|
1512
|
+
const status = document.getElementById("targetLocationsStatus");
|
|
1513
|
+
if (!input) return;
|
|
1514
|
+
const locations = normalizeCountryInput(input.value);
|
|
1515
|
+
status.textContent = "保存中...";
|
|
1516
|
+
status.className = "settings-status loading";
|
|
1517
|
+
try {
|
|
1518
|
+
const res = await fetch("/api/settings/target-locations", {
|
|
1519
|
+
method: "POST",
|
|
1520
|
+
headers: { "Content-Type": "application/json" },
|
|
1521
|
+
body: JSON.stringify({ locations }),
|
|
1522
|
+
});
|
|
1523
|
+
const data = await res.json();
|
|
1524
|
+
input.value = (data.locations || []).join(",");
|
|
1525
|
+
renderTargetCountryGrid(data.locations || []);
|
|
1526
|
+
status.textContent = `已保存 ${(data.locations || []).length} 个国家`;
|
|
1527
|
+
status.className = "settings-status ok";
|
|
1528
|
+
} catch (e) {
|
|
1529
|
+
console.error("saveTargetLocations error:", e);
|
|
1530
|
+
status.textContent = "保存失败";
|
|
1531
|
+
status.className = "settings-status warn";
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
async function resetTargetLocations() {
|
|
1536
|
+
const status = document.getElementById("targetLocationsStatus");
|
|
1537
|
+
if (status) {
|
|
1538
|
+
status.textContent = "保存中...";
|
|
1539
|
+
status.className = "settings-status loading";
|
|
1540
|
+
}
|
|
1541
|
+
try {
|
|
1542
|
+
const res = await fetch("/api/settings/target-locations", {
|
|
1543
|
+
method: "POST",
|
|
1544
|
+
headers: { "Content-Type": "application/json" },
|
|
1545
|
+
body: JSON.stringify({ locations: [] }),
|
|
1546
|
+
});
|
|
1547
|
+
const data = await res.json();
|
|
1548
|
+
const input = document.getElementById("targetLocationsInput");
|
|
1549
|
+
if (input) input.value = (data.locations || []).join(",");
|
|
1550
|
+
renderTargetCountryGrid(data.locations || []);
|
|
1551
|
+
if (status) {
|
|
1552
|
+
status.textContent = `已恢复默认 ${(data.locations || []).length} 个国家`;
|
|
1553
|
+
status.className = "settings-status ok";
|
|
1554
|
+
}
|
|
1555
|
+
} catch (e) {
|
|
1556
|
+
console.error("resetTargetLocations error:", e);
|
|
1557
|
+
if (status) {
|
|
1558
|
+
status.textContent = "恢复默认失败";
|
|
1559
|
+
status.className = "settings-status warn";
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1395
1562
|
}
|
|
1396
1563
|
|
|
1397
1564
|
let _llmProvidersCache = null;
|
|
@@ -1596,27 +1763,14 @@ function openAddTagModal() {
|
|
|
1596
1763
|
if (e.target === overlay) closeAddTagModal();
|
|
1597
1764
|
});
|
|
1598
1765
|
|
|
1599
|
-
//
|
|
1766
|
+
// 渲染国家复选框(使用动态目标国家列表,含 TH/US,失败回退默认)
|
|
1600
1767
|
const countryGrid = document.getElementById("addTagCountryCheckboxes");
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
"
|
|
1607
|
-
"FR",
|
|
1608
|
-
"GR",
|
|
1609
|
-
"HU",
|
|
1610
|
-
"IE",
|
|
1611
|
-
"IT",
|
|
1612
|
-
"NL",
|
|
1613
|
-
"PL",
|
|
1614
|
-
"PT",
|
|
1615
|
-
];
|
|
1616
|
-
countryGrid.innerHTML = TARGET_LOCATIONS.map(
|
|
1617
|
-
(c) =>
|
|
1618
|
-
`<label class="checkbox-label"><input type="checkbox" value="${c}" checked> ${c}</label>`,
|
|
1619
|
-
).join("");
|
|
1768
|
+
countryGrid.innerHTML = locationOptions
|
|
1769
|
+
.map(
|
|
1770
|
+
(c) =>
|
|
1771
|
+
`<label class="checkbox-label"><input type="checkbox" value="${c}" checked> ${c}</label>`,
|
|
1772
|
+
)
|
|
1773
|
+
.join("");
|
|
1620
1774
|
|
|
1621
1775
|
const ta = document.getElementById("addTagModalInput");
|
|
1622
1776
|
ta.focus();
|
|
@@ -3450,6 +3604,8 @@ async function fetchWatchdogData() {
|
|
|
3450
3604
|
_wdData.clashCountries || {},
|
|
3451
3605
|
_wdData.errorTriggers || {},
|
|
3452
3606
|
_wdData.loginPorts || {},
|
|
3607
|
+
_wdData.switchRestarts || {},
|
|
3608
|
+
_wdData.autoUpgrades || {},
|
|
3453
3609
|
);
|
|
3454
3610
|
} catch (e) {
|
|
3455
3611
|
document.getElementById("wdClientsList").textContent =
|
|
@@ -3503,6 +3659,8 @@ function renderWatchdogDashboard(
|
|
|
3503
3659
|
clashCountries,
|
|
3504
3660
|
errorTriggers,
|
|
3505
3661
|
loginPorts,
|
|
3662
|
+
switchRestarts,
|
|
3663
|
+
autoUpgrades,
|
|
3506
3664
|
) {
|
|
3507
3665
|
// 顶栏统计始终更新(textContent 不闪烁)
|
|
3508
3666
|
var onlineCount = clients.filter(function (c) {
|
|
@@ -3567,6 +3725,8 @@ function renderWatchdogDashboard(
|
|
|
3567
3725
|
clashCountries,
|
|
3568
3726
|
errorTriggers,
|
|
3569
3727
|
loginPorts,
|
|
3728
|
+
switchRestarts,
|
|
3729
|
+
autoUpgrades,
|
|
3570
3730
|
),
|
|
3571
3731
|
);
|
|
3572
3732
|
});
|
|
@@ -3592,6 +3752,8 @@ function renderWatchdogDashboard(
|
|
|
3592
3752
|
clashCountries,
|
|
3593
3753
|
errorTriggers,
|
|
3594
3754
|
loginPorts,
|
|
3755
|
+
switchRestarts,
|
|
3756
|
+
autoUpgrades,
|
|
3595
3757
|
),
|
|
3596
3758
|
);
|
|
3597
3759
|
});
|
|
@@ -3643,6 +3805,8 @@ function renderOfflineCard(
|
|
|
3643
3805
|
clashCountries,
|
|
3644
3806
|
errorTriggers,
|
|
3645
3807
|
loginPorts,
|
|
3808
|
+
switchRestarts,
|
|
3809
|
+
autoUpgrades,
|
|
3646
3810
|
) {
|
|
3647
3811
|
var cmdCount = cmds ? cmds.length : 0;
|
|
3648
3812
|
return tag(
|
|
@@ -3654,10 +3818,10 @@ function renderOfflineCard(
|
|
|
3654
3818
|
},
|
|
3655
3819
|
styled(
|
|
3656
3820
|
"div",
|
|
3657
|
-
"display:flex;justify-content:space-between;align-items:center;margin-bottom:8px",
|
|
3821
|
+
"display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;flex-wrap:wrap;gap:8px",
|
|
3658
3822
|
styled(
|
|
3659
3823
|
"div",
|
|
3660
|
-
"",
|
|
3824
|
+
"display:flex;align-items:center;flex-wrap:wrap;min-width:0;flex:1",
|
|
3661
3825
|
tag(
|
|
3662
3826
|
"span",
|
|
3663
3827
|
{
|
|
@@ -3696,7 +3860,7 @@ function renderOfflineCard(
|
|
|
3696
3860
|
) +
|
|
3697
3861
|
styled(
|
|
3698
3862
|
"div",
|
|
3699
|
-
"display:flex;gap:10px;align-items:center",
|
|
3863
|
+
"display:flex;gap:10px;align-items:center;flex-wrap:wrap;flex-shrink:0",
|
|
3700
3864
|
styled("span", "color:#f87171;font-size:13px", "● 离线") +
|
|
3701
3865
|
styled("span", "color:#555;font-size:12px", "—") +
|
|
3702
3866
|
tag(
|
|
@@ -3718,6 +3882,8 @@ function renderOfflineCard(
|
|
|
3718
3882
|
) +
|
|
3719
3883
|
renderCountrySection({}, userId, clashCountries) +
|
|
3720
3884
|
renderErrorTriggerSection({}, userId, errorTriggers) +
|
|
3885
|
+
renderSwitchRestartSection({}, userId, switchRestarts) +
|
|
3886
|
+
renderAutoUpgradeSection({}, userId, autoUpgrades) +
|
|
3721
3887
|
renderLoginSection({}, userId, loginPorts) +
|
|
3722
3888
|
renderCmdRows(cmds || [], userId, [], false),
|
|
3723
3889
|
);
|
|
@@ -3759,6 +3925,8 @@ function renderClientCard(
|
|
|
3759
3925
|
clashCountries,
|
|
3760
3926
|
errorTriggers,
|
|
3761
3927
|
loginPorts,
|
|
3928
|
+
switchRestarts,
|
|
3929
|
+
autoUpgrades,
|
|
3762
3930
|
) {
|
|
3763
3931
|
var os = c.osInfo || {},
|
|
3764
3932
|
cfg = c.config || {},
|
|
@@ -3787,7 +3955,10 @@ function renderClientCard(
|
|
|
3787
3955
|
var onlineBadge = online
|
|
3788
3956
|
? styled("span", "color:#4ade80", "● 在线")
|
|
3789
3957
|
: styled("span", "color:#f87171", "● 离线");
|
|
3790
|
-
|
|
3958
|
+
// 完整日期时间:跨天离线也能看出最后心跳是哪天
|
|
3959
|
+
var lastSeen = c.lastHeartbeat
|
|
3960
|
+
? new Date(c.lastHeartbeat).toLocaleString("zh-CN", { hour12: false })
|
|
3961
|
+
: "?";
|
|
3791
3962
|
|
|
3792
3963
|
// 离线时标题和边框变红
|
|
3793
3964
|
var titleColor = online ? "#60a5fa" : "#f87171";
|
|
@@ -3795,10 +3966,10 @@ function renderClientCard(
|
|
|
3795
3966
|
|
|
3796
3967
|
var header = styled(
|
|
3797
3968
|
"div",
|
|
3798
|
-
"display:flex;justify-content:space-between;align-items:center;margin-bottom:8px",
|
|
3969
|
+
"display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;flex-wrap:wrap;gap:8px",
|
|
3799
3970
|
styled(
|
|
3800
3971
|
"div",
|
|
3801
|
-
"",
|
|
3972
|
+
"display:flex;align-items:center;flex-wrap:wrap;min-width:0;flex:1",
|
|
3802
3973
|
tag(
|
|
3803
3974
|
"span",
|
|
3804
3975
|
{
|
|
@@ -3858,10 +4029,16 @@ function renderClientCard(
|
|
|
3858
4029
|
) +
|
|
3859
4030
|
styled(
|
|
3860
4031
|
"div",
|
|
3861
|
-
"display:flex;gap:10px;align-items:center",
|
|
4032
|
+
"display:flex;gap:10px;align-items:center;flex-wrap:wrap;flex-shrink:0",
|
|
3862
4033
|
styled("span", "color:#888;font-size:13px", c.ip) +
|
|
3863
4034
|
onlineBadge +
|
|
3864
|
-
styled(
|
|
4035
|
+
styled(
|
|
4036
|
+
"span",
|
|
4037
|
+
online
|
|
4038
|
+
? "color:#666;font-size:12px"
|
|
4039
|
+
: "color:#fca5a5;font-size:12px",
|
|
4040
|
+
"最后心跳 " + lastSeen,
|
|
4041
|
+
) +
|
|
3865
4042
|
(!online && userId
|
|
3866
4043
|
? tag(
|
|
3867
4044
|
"button",
|
|
@@ -3891,6 +4068,8 @@ function renderClientCard(
|
|
|
3891
4068
|
styled("div", "color:#888;font-size:12px;margin-bottom:6px", infoLine) +
|
|
3892
4069
|
renderCountrySection(c, userId, clashCountries) +
|
|
3893
4070
|
renderErrorTriggerSection(c, userId, errorTriggers) +
|
|
4071
|
+
renderSwitchRestartSection(c, userId, switchRestarts) +
|
|
4072
|
+
renderAutoUpgradeSection(c, userId, autoUpgrades) +
|
|
3894
4073
|
renderLoginSection(c, userId, loginPorts) +
|
|
3895
4074
|
renderProxySection(c, userId) +
|
|
3896
4075
|
renderCmdRows(myCmds, userId, procs, online, c.llmAnalysis, c.clientId),
|
|
@@ -4290,6 +4469,155 @@ function renderErrorTriggerSection(c, userId, errorTriggers) {
|
|
|
4290
4469
|
);
|
|
4291
4470
|
}
|
|
4292
4471
|
|
|
4472
|
+
// ── 代理切换重启配置区域(看门狗面板中每个客户端卡片) ──
|
|
4473
|
+
|
|
4474
|
+
function renderSwitchRestartSection(c, userId, switchRestarts) {
|
|
4475
|
+
if (!userId) return "";
|
|
4476
|
+
var entry = (switchRestarts && switchRestarts[userId]) || null;
|
|
4477
|
+
var enabled = entry ? !!entry.enabled : false;
|
|
4478
|
+
var label = entry ? (enabled ? "已启用" : "已禁用") : "未配置(默认禁用)";
|
|
4479
|
+
var btnColor = enabled ? "#4ade80" : "#888";
|
|
4480
|
+
return styled(
|
|
4481
|
+
"div",
|
|
4482
|
+
"margin-top:6px;padding:6px 8px;background:#111;border-radius:6px;" +
|
|
4483
|
+
"display:flex;align-items:center;gap:8px;font-size:12px",
|
|
4484
|
+
styled("span", "font-size:14px", "🔁") +
|
|
4485
|
+
styled("span", "color:#888", "切换重启:") +
|
|
4486
|
+
tag(
|
|
4487
|
+
"button",
|
|
4488
|
+
{
|
|
4489
|
+
onclick: "wdShowSwitchRestartModal('" + esc(userId) + "')",
|
|
4490
|
+
style:
|
|
4491
|
+
"padding:2px 8px;font-size:11px;background:#333;color:" +
|
|
4492
|
+
btnColor +
|
|
4493
|
+
";border:1px solid #444;border-radius:3px;cursor:pointer",
|
|
4494
|
+
title: "代理切换后强制重启全部命令(默认关闭)",
|
|
4495
|
+
},
|
|
4496
|
+
(enabled ? "开 " : "关 ") + "⚙️",
|
|
4497
|
+
) +
|
|
4498
|
+
styled("span", "color:#666;margin-left:auto", label),
|
|
4499
|
+
);
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
// ── 代理切换重启配置弹窗 ──
|
|
4503
|
+
|
|
4504
|
+
var _wdSwitchUserId = "";
|
|
4505
|
+
|
|
4506
|
+
function wdShowSwitchRestartModal(userId) {
|
|
4507
|
+
_wdSwitchUserId = userId;
|
|
4508
|
+
document.getElementById("wdSwitchUserId").textContent = userId;
|
|
4509
|
+
var entry =
|
|
4510
|
+
_wdData && _wdData.switchRestarts && _wdData.switchRestarts[userId]
|
|
4511
|
+
? _wdData.switchRestarts[userId]
|
|
4512
|
+
: null;
|
|
4513
|
+
document.getElementById("wdSwitchEnabled").checked = entry
|
|
4514
|
+
? !!entry.enabled
|
|
4515
|
+
: false;
|
|
4516
|
+
document.getElementById("wdSwitchRestartModal").style.display = "flex";
|
|
4517
|
+
}
|
|
4518
|
+
|
|
4519
|
+
async function wdSetSwitchRestart() {
|
|
4520
|
+
var config = {
|
|
4521
|
+
enabled: document.getElementById("wdSwitchEnabled").checked,
|
|
4522
|
+
};
|
|
4523
|
+
try {
|
|
4524
|
+
var res = await fetch("/api/watchdog/set-switch-restart", {
|
|
4525
|
+
method: "POST",
|
|
4526
|
+
headers: { "Content-Type": "application/json" },
|
|
4527
|
+
body: JSON.stringify({ userId: _wdSwitchUserId, config: config }),
|
|
4528
|
+
});
|
|
4529
|
+
var d = await res.json();
|
|
4530
|
+
document.getElementById("wdSwitchRestartModal").style.display = "none";
|
|
4531
|
+
if (d.ok) {
|
|
4532
|
+
alert(
|
|
4533
|
+
config.enabled
|
|
4534
|
+
? "已开启代理切换重启\n\n代理节点切换后,客户端将强制重启全部命令。\n客户端将在下次心跳时生效(约15秒内)。"
|
|
4535
|
+
: "已关闭代理切换重启\n\n客户端将在下次心跳时生效(约15秒内)。",
|
|
4536
|
+
);
|
|
4537
|
+
} else {
|
|
4538
|
+
alert("失败: " + (d.error || "未知错误"));
|
|
4539
|
+
}
|
|
4540
|
+
fetchWatchdogData();
|
|
4541
|
+
} catch (e) {
|
|
4542
|
+
alert("保存失败: " + e.message);
|
|
4543
|
+
}
|
|
4544
|
+
}
|
|
4545
|
+
|
|
4546
|
+
// ── 自动升级开关(默认开启,后端可下发关闭) ──
|
|
4547
|
+
|
|
4548
|
+
function renderAutoUpgradeSection(c, userId, autoUpgrades) {
|
|
4549
|
+
if (!userId) return "";
|
|
4550
|
+
var entry = (autoUpgrades && autoUpgrades[userId]) || null;
|
|
4551
|
+
var enabled = entry ? !!entry.enabled : true; // 未配置默认开启
|
|
4552
|
+
var label = enabled ? "已开启" : "已关闭";
|
|
4553
|
+
var btnColor = enabled ? "#4ade80" : "#f87171";
|
|
4554
|
+
return styled(
|
|
4555
|
+
"div",
|
|
4556
|
+
"margin-top:6px;padding:6px 8px;background:#111;border-radius:6px;" +
|
|
4557
|
+
"display:flex;align-items:center;gap:8px;font-size:12px",
|
|
4558
|
+
styled("span", "font-size:14px", "🚀") +
|
|
4559
|
+
styled("span", "color:#888", "自动升级:") +
|
|
4560
|
+
tag(
|
|
4561
|
+
"button",
|
|
4562
|
+
{
|
|
4563
|
+
onclick: "wdShowAutoUpgradeModal('" + esc(userId) + "')",
|
|
4564
|
+
style:
|
|
4565
|
+
"padding:2px 8px;font-size:11px;background:#333;color:" +
|
|
4566
|
+
btnColor +
|
|
4567
|
+
";border:1px solid #444;border-radius:3px;cursor:pointer",
|
|
4568
|
+
title: "看门狗客户端检测到新版本时自动升级(默认开启)",
|
|
4569
|
+
},
|
|
4570
|
+
(enabled ? "开 " : "关 ") + "⚙️",
|
|
4571
|
+
) +
|
|
4572
|
+
styled("span", "color:#666;margin-left:auto", label),
|
|
4573
|
+
);
|
|
4574
|
+
}
|
|
4575
|
+
|
|
4576
|
+
// ── 自动升级开关弹窗 ──
|
|
4577
|
+
|
|
4578
|
+
var _wdAutoUpgradeUserId = "";
|
|
4579
|
+
|
|
4580
|
+
function wdShowAutoUpgradeModal(userId) {
|
|
4581
|
+
_wdAutoUpgradeUserId = userId;
|
|
4582
|
+
document.getElementById("wdAutoUpgradeUserId").textContent = userId;
|
|
4583
|
+
var entry =
|
|
4584
|
+
_wdData && _wdData.autoUpgrades && _wdData.autoUpgrades[userId]
|
|
4585
|
+
? _wdData.autoUpgrades[userId]
|
|
4586
|
+
: null;
|
|
4587
|
+
// 未配置 = 默认开启
|
|
4588
|
+
document.getElementById("wdAutoUpgradeEnabled").checked = entry
|
|
4589
|
+
? !!entry.enabled
|
|
4590
|
+
: true;
|
|
4591
|
+
document.getElementById("wdAutoUpgradeModal").style.display = "flex";
|
|
4592
|
+
}
|
|
4593
|
+
|
|
4594
|
+
async function wdSetAutoUpgrade() {
|
|
4595
|
+
var config = {
|
|
4596
|
+
enabled: document.getElementById("wdAutoUpgradeEnabled").checked,
|
|
4597
|
+
};
|
|
4598
|
+
try {
|
|
4599
|
+
var res = await fetch("/api/watchdog/set-auto-upgrade", {
|
|
4600
|
+
method: "POST",
|
|
4601
|
+
headers: { "Content-Type": "application/json" },
|
|
4602
|
+
body: JSON.stringify({ userId: _wdAutoUpgradeUserId, config: config }),
|
|
4603
|
+
});
|
|
4604
|
+
var d = await res.json();
|
|
4605
|
+
document.getElementById("wdAutoUpgradeModal").style.display = "none";
|
|
4606
|
+
if (d.ok) {
|
|
4607
|
+
alert(
|
|
4608
|
+
config.enabled
|
|
4609
|
+
? "已开启自动升级\n\n客户端检测到新版本时会自动升级。\n客户端将在下次心跳时生效(约15秒内)。"
|
|
4610
|
+
: "已关闭自动升级\n\n该客户端将不再自动升级新版本(仍可手动升级)。\n客户端将在下次心跳时生效(约15秒内)。",
|
|
4611
|
+
);
|
|
4612
|
+
} else {
|
|
4613
|
+
alert("失败: " + (d.error || "未知错误"));
|
|
4614
|
+
}
|
|
4615
|
+
fetchWatchdogData();
|
|
4616
|
+
} catch (e) {
|
|
4617
|
+
alert("保存失败: " + e.message);
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
|
|
4293
4621
|
// ── 错误自动切节点配置弹窗 ──
|
|
4294
4622
|
|
|
4295
4623
|
var _wdErrUserId = "";
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
<input type="checkbox" id="fireworkToggle" checked onchange="toggleFireworks()">
|
|
21
21
|
<span>🎆</span>
|
|
22
22
|
</label>
|
|
23
|
-
<a class="script-link" onclick="navigateToSettings()" style="cursor:pointer">⚙️
|
|
23
|
+
<a class="script-link" onclick="navigateToSettings()" style="cursor:pointer">⚙️ 配置</a>
|
|
24
24
|
<a class="script-link" onclick="navigateToWatchdog()" style="cursor:pointer">🐕 看门狗</a>
|
|
25
25
|
<span class="status" id="lastUpdate">--</span>
|
|
26
26
|
</div>
|
|
@@ -488,6 +488,26 @@
|
|
|
488
488
|
</div>
|
|
489
489
|
</div>
|
|
490
490
|
</div>
|
|
491
|
+
<div class="settings-card">
|
|
492
|
+
<h3>🎯 目标国家</h3>
|
|
493
|
+
<p class="settings-hint">设置采集的目标国家列表,保存后写入通用配置文件(~/.tt-help-config.json)。当前仅保存配置,后续接入采集流程后生效。</p>
|
|
494
|
+
<div class="settings-form">
|
|
495
|
+
<div class="settings-field">
|
|
496
|
+
<label>目标国家(英文国家代码,逗号分隔,如 CZ,GR,TH)</label>
|
|
497
|
+
<input type="text" id="targetLocationsInput" placeholder="CZ,GR,HU,PT,ES,PL,NL,BE,DE,FR,IT,IE,AT,TH,US">
|
|
498
|
+
<span class="settings-note">可手动输入,或点击下方国家快速选择。</span>
|
|
499
|
+
</div>
|
|
500
|
+
<div class="settings-field">
|
|
501
|
+
<label>快速选择</label>
|
|
502
|
+
<div class="loc-grid" id="targetCountryGrid"></div>
|
|
503
|
+
</div>
|
|
504
|
+
<div class="settings-status" id="targetLocationsStatus"></div>
|
|
505
|
+
<div class="btn-row">
|
|
506
|
+
<button class="btn-submit" onclick="saveTargetLocations()">保存目标国家</button>
|
|
507
|
+
<button class="btn-cancel" onclick="resetTargetLocations()">恢复默认</button>
|
|
508
|
+
</div>
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
491
511
|
</div>
|
|
492
512
|
<div id="watchdogPage">
|
|
493
513
|
<div class="stats" style="margin-bottom:16px">
|
|
@@ -667,6 +687,46 @@
|
|
|
667
687
|
</div>
|
|
668
688
|
</div>
|
|
669
689
|
|
|
690
|
+
<!-- 代理切换重启配置弹窗 -->
|
|
691
|
+
<div id="wdSwitchRestartModal"
|
|
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="wdSwitchUserId"></span></div>
|
|
695
|
+
<div style="color:#888;font-size:12px;margin-bottom:12px">代理节点发生切换后,客户端强制重启全部被监控命令(错峰停止 + 错峰启动)。<br><b
|
|
696
|
+
style="color:#f87171">默认关闭</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="wdSwitchEnabled" 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('wdSwitchRestartModal').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="wdSetSwitchRestart()"
|
|
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
|
+
|
|
710
|
+
<!-- 自动升级开关弹窗 -->
|
|
711
|
+
<div id="wdAutoUpgradeModal"
|
|
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">
|
|
713
|
+
<div style="background:#1e1e2e;border:1px solid #333;border-radius:12px;padding:24px;width:90%;max-width:480px">
|
|
714
|
+
<div style="font-size:16px;color:#4ade80;margin-bottom:4px">🚀 自动升级 — <span id="wdAutoUpgradeUserId"></span></div>
|
|
715
|
+
<div style="color:#888;font-size:12px;margin-bottom:12px">客户端看门狗定期检测新版本并自动升级。<br><b
|
|
716
|
+
style="color:#4ade80">默认开启</b>,关闭后该客户端不再自动升级(仍可手动升级)。</div>
|
|
717
|
+
<label
|
|
718
|
+
style="display:flex;align-items:center;gap:8px;margin-bottom:12px;color:#e0e0e0;font-size:13px;cursor:pointer">
|
|
719
|
+
<input type="checkbox" id="wdAutoUpgradeEnabled" style="width:16px;height:16px"> 启用(检测到新版本自动升级)
|
|
720
|
+
</label>
|
|
721
|
+
<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:16px">
|
|
722
|
+
<button onclick="document.getElementById('wdAutoUpgradeModal').style.display='none'"
|
|
723
|
+
style="padding:8px 16px;background:#333;color:#ccc;border:none;border-radius:6px;cursor:pointer;font-size:12px">取消</button>
|
|
724
|
+
<button onclick="wdSetAutoUpgrade()"
|
|
725
|
+
style="padding:8px 16px;background:#4ade80;color:#000;border:none;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600">保存并下发</button>
|
|
726
|
+
</div>
|
|
727
|
+
</div>
|
|
728
|
+
</div>
|
|
729
|
+
|
|
670
730
|
<!-- 登录检测端口配置弹窗 -->
|
|
671
731
|
<div id="wdLoginPortsModal"
|
|
672
732
|
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 +764,7 @@
|
|
|
704
764
|
</div>
|
|
705
765
|
</div>
|
|
706
766
|
|
|
707
|
-
<script src="app.js?v=
|
|
767
|
+
<script src="app.js?v=20260828c"></script>
|
|
708
768
|
</body>
|
|
709
769
|
|
|
710
770
|
</html>
|
|
@@ -1834,6 +1834,22 @@ td.user-id:hover {
|
|
|
1834
1834
|
background: #e61944;
|
|
1835
1835
|
}
|
|
1836
1836
|
|
|
1837
|
+
.settings-form .btn-cancel {
|
|
1838
|
+
padding: 10px 20px;
|
|
1839
|
+
border: 1px solid #333;
|
|
1840
|
+
border-radius: 8px;
|
|
1841
|
+
background: #2a2a3a;
|
|
1842
|
+
color: #ccc;
|
|
1843
|
+
font-size: 14px;
|
|
1844
|
+
font-weight: 600;
|
|
1845
|
+
cursor: pointer;
|
|
1846
|
+
transition: background 0.2s;
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
.settings-form .btn-cancel:hover {
|
|
1850
|
+
background: #333;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1837
1853
|
/* ── 看门狗页面 ── */
|
|
1838
1854
|
|
|
1839
1855
|
#watchdogPage {
|