tt-help-cli-ycl 1.4.56 → 1.4.57
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/src/cli/watchdog.js +48 -0
- package/src/lib/args.js +38 -0
- package/src/lib/constants.js +10 -0
- package/src/main.js +3 -0
- package/src/npm-main.js +3 -0
- package/src/watch/public/app.js +454 -0
- package/src/watch/public/index.html +21 -0
- package/src/watch/public/style.css +43 -0
- package/src/watch/server.js +22 -0
- package/src/watch/watchdog-server.js +239 -0
- package/src/watchdog/agent.js +219 -0
- package/src/watchdog/process-manager.js +332 -0
- package/src/watchdog/reporter.js +69 -0
- package/src/watchdog/upgrader.js +119 -0
package/package.json
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 看门狗 CLI 命令入口
|
|
3
|
+
*
|
|
4
|
+
* 用法: tt-help watchdog [选项]
|
|
5
|
+
*
|
|
6
|
+
* 选项:
|
|
7
|
+
* --server <URL> 服务端地址(默认使用 ~/.tt-help.json 中的 server)
|
|
8
|
+
* --interval <秒> 心跳间隔,默认 15 秒
|
|
9
|
+
* --client-id <ID> 客户端标识(默认本机 hostname)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import os from "os";
|
|
13
|
+
import { WatchdogAgent } from "../watchdog/agent.js";
|
|
14
|
+
import { getClientConfig } from "../watchdog/reporter.js";
|
|
15
|
+
|
|
16
|
+
export async function handleWatchdog(options) {
|
|
17
|
+
const { watchdogServer, watchdogInterval, watchdogClientId } = options;
|
|
18
|
+
|
|
19
|
+
// 服务端地址:--server 参数 > ~/.tt-help.json server > 默认值
|
|
20
|
+
const clientConfig = getClientConfig();
|
|
21
|
+
const serverUrl =
|
|
22
|
+
watchdogServer || clientConfig.server || "http://127.0.0.1:3001";
|
|
23
|
+
|
|
24
|
+
// 客户端 ID
|
|
25
|
+
const clientId = watchdogClientId || os.hostname() || `node-${process.pid}`;
|
|
26
|
+
|
|
27
|
+
// 心跳间隔
|
|
28
|
+
const intervalSec = Math.max(5, parseInt(watchdogInterval) || 15);
|
|
29
|
+
|
|
30
|
+
if (!serverUrl) {
|
|
31
|
+
console.error("错误: 未指定服务端地址。");
|
|
32
|
+
console.error("用法: tt-help watchdog --server http://127.0.0.1:3001");
|
|
33
|
+
console.error("或在 ~/.tt-help.json 中配置 server 字段");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.error("═".repeat(50));
|
|
38
|
+
console.error(" TikTok 看门狗");
|
|
39
|
+
console.error("═".repeat(50));
|
|
40
|
+
|
|
41
|
+
const agent = new WatchdogAgent({
|
|
42
|
+
serverUrl,
|
|
43
|
+
clientId,
|
|
44
|
+
intervalMs: intervalSec * 1000,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await agent.start();
|
|
48
|
+
}
|
package/src/lib/args.js
CHANGED
|
@@ -621,6 +621,40 @@ function parseSearchArgs(args) {
|
|
|
621
621
|
};
|
|
622
622
|
}
|
|
623
623
|
|
|
624
|
+
function parseWatchdogArgs(args) {
|
|
625
|
+
let watchdogServer = null;
|
|
626
|
+
let watchdogInterval = 15;
|
|
627
|
+
let watchdogClientId = null;
|
|
628
|
+
|
|
629
|
+
for (let i = 0; i < args.length; i++) {
|
|
630
|
+
const arg = args[i];
|
|
631
|
+
if (arg === "--server" || arg === "-s") {
|
|
632
|
+
watchdogServer = args[++i];
|
|
633
|
+
} else if (arg === "--interval" || arg === "-i") {
|
|
634
|
+
watchdogInterval = parseInt(args[++i]) || 15;
|
|
635
|
+
} else if (arg === "--client-id") {
|
|
636
|
+
watchdogClientId = args[++i];
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return {
|
|
641
|
+
subcommand: "watchdog",
|
|
642
|
+
watchdogServer,
|
|
643
|
+
watchdogInterval,
|
|
644
|
+
watchdogClientId,
|
|
645
|
+
urls: [],
|
|
646
|
+
outputFormat: "json",
|
|
647
|
+
exploreCount: 0,
|
|
648
|
+
showConfig: false,
|
|
649
|
+
showHelp: false,
|
|
650
|
+
customProxy: null,
|
|
651
|
+
configAction: null,
|
|
652
|
+
configValue: null,
|
|
653
|
+
pipeMode: false,
|
|
654
|
+
filterStr: null,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
624
658
|
function parseAttachArgs(args) {
|
|
625
659
|
let parallel = 1;
|
|
626
660
|
let interval = 10;
|
|
@@ -1324,6 +1358,10 @@ export function parseArgs() {
|
|
|
1324
1358
|
return parseRawSyncArgs(args.slice(1));
|
|
1325
1359
|
}
|
|
1326
1360
|
|
|
1361
|
+
if (args.length > 0 && args[0] === "watchdog") {
|
|
1362
|
+
return parseWatchdogArgs(args.slice(1));
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1327
1365
|
if (args.length > 0 && args[0] === "precheck") {
|
|
1328
1366
|
return parsePrecheckArgs(args.slice(1));
|
|
1329
1367
|
}
|
package/src/lib/constants.js
CHANGED
|
@@ -375,6 +375,16 @@ const HELP_TEXT = [
|
|
|
375
375
|
" tt-help raw-sync --db data/result.db --archive data/result-archive.db --once",
|
|
376
376
|
" tt-help raw-sync --db data/result.db --archive data/result-archive.db --llm --threshold 300",
|
|
377
377
|
"",
|
|
378
|
+
" tt-help watchdog [选项]",
|
|
379
|
+
" 看门狗客户端:向服务端上报心跳+系统信息,拉取命令列表并启动/监控子进程",
|
|
380
|
+
" 选项:",
|
|
381
|
+
" --server <URL>, -s 服务端地址(默认使用 ~/.tt-help.json 中的 server)",
|
|
382
|
+
" --interval <秒>, -i 心跳间隔,默认 15 秒(最小 5 秒)",
|
|
383
|
+
" --client-id <ID> 客户端标识(默认本机 hostname)",
|
|
384
|
+
" 示例: tt-help watchdog",
|
|
385
|
+
" tt-help watchdog --server http://117.71.53.99:17301",
|
|
386
|
+
" tt-help watchdog -i 30 --client-id my-server-01",
|
|
387
|
+
"",
|
|
378
388
|
" config [show|set|unset|reset]",
|
|
379
389
|
" config 查看当前配置",
|
|
380
390
|
" config set <key> <value> 设置配置(key: proxy, server, browser, userId, maxFollowing, maxFollowers, maxVideos, maxComments)",
|
package/src/main.js
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from "./cli/tag.js";
|
|
23
23
|
import { handleRawSync } from "./cli/raw-sync.js";
|
|
24
24
|
import { handleSearch } from "./cli/search.js";
|
|
25
|
+
import { handleWatchdog } from "./cli/watchdog.js";
|
|
25
26
|
|
|
26
27
|
async function main() {
|
|
27
28
|
const parsed = parseArgs();
|
|
@@ -65,6 +66,8 @@ async function main() {
|
|
|
65
66
|
return handleTagRefresh(parsed);
|
|
66
67
|
case "search":
|
|
67
68
|
return handleSearch(parsed);
|
|
69
|
+
case "watchdog":
|
|
70
|
+
return handleWatchdog(parsed);
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
const {
|
package/src/npm-main.js
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from "./cli/tag.js";
|
|
20
20
|
import { handleRawSync } from "./cli/raw-sync.js";
|
|
21
21
|
import { handleSearch } from "./cli/search.js";
|
|
22
|
+
import { handleWatchdog } from "./cli/watchdog.js";
|
|
22
23
|
|
|
23
24
|
function exitUnsupportedCommand(command) {
|
|
24
25
|
console.error(
|
|
@@ -67,6 +68,8 @@ async function main() {
|
|
|
67
68
|
return handleRawSync(parsed);
|
|
68
69
|
case "search":
|
|
69
70
|
return handleSearch(parsed);
|
|
71
|
+
case "watchdog":
|
|
72
|
+
return handleWatchdog(parsed);
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
const {
|
package/src/watch/public/app.js
CHANGED
|
@@ -1270,6 +1270,8 @@ function handleRoute() {
|
|
|
1270
1270
|
showTagsPage();
|
|
1271
1271
|
} else if (hash === "#settings") {
|
|
1272
1272
|
showSettingsPage();
|
|
1273
|
+
} else if (hash === "#watchdog") {
|
|
1274
|
+
showWatchdogPage();
|
|
1273
1275
|
} else {
|
|
1274
1276
|
showMainPage();
|
|
1275
1277
|
}
|
|
@@ -1366,6 +1368,7 @@ function hideAllPages() {
|
|
|
1366
1368
|
document.getElementById("targetPage").classList.remove("active");
|
|
1367
1369
|
document.getElementById("tagsPage").classList.remove("active");
|
|
1368
1370
|
document.getElementById("settingsPage").classList.remove("active");
|
|
1371
|
+
document.getElementById("watchdogPage").classList.remove("active");
|
|
1369
1372
|
}
|
|
1370
1373
|
|
|
1371
1374
|
function showSettingsPage() {
|
|
@@ -3388,3 +3391,454 @@ setInterval(fetchStats, 10000);
|
|
|
3388
3391
|
setTimeout(() => setInterval(fetchUsers, 10000), 2500);
|
|
3389
3392
|
setTimeout(() => setInterval(fetchClientErrors, 10000), 5000);
|
|
3390
3393
|
setTimeout(() => setInterval(refreshUserUpdateIfActive, 10000), 7500);
|
|
3394
|
+
|
|
3395
|
+
// ========== 看门狗页面 ==========
|
|
3396
|
+
|
|
3397
|
+
function navigateToWatchdog() {
|
|
3398
|
+
window.location.hash = "#watchdog";
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
let _wdData = null;
|
|
3402
|
+
|
|
3403
|
+
function showWatchdogPage() {
|
|
3404
|
+
hideAllPages();
|
|
3405
|
+
document.getElementById("watchdogPage").classList.add("active");
|
|
3406
|
+
fetchWatchdogData();
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
async function fetchWatchdogData() {
|
|
3410
|
+
try {
|
|
3411
|
+
const res = await fetch("/api/watchdog/clients");
|
|
3412
|
+
_wdData = await res.json();
|
|
3413
|
+
renderWatchdogDashboard(
|
|
3414
|
+
_wdData.clients || [],
|
|
3415
|
+
_wdData.clientCommands || {},
|
|
3416
|
+
);
|
|
3417
|
+
} catch (e) {
|
|
3418
|
+
document.getElementById("wdClientsList").textContent =
|
|
3419
|
+
"加载失败: " + e.message;
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
// ── 渲染辅助 ──
|
|
3424
|
+
|
|
3425
|
+
function tag(tagName, attrs, content) {
|
|
3426
|
+
var a = "";
|
|
3427
|
+
for (var k in attrs) a += " " + k + '="' + attrs[k] + '"';
|
|
3428
|
+
return "<" + tagName + a + ">" + (content || "") + "</" + tagName + ">";
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
function styled(el, style, content) {
|
|
3432
|
+
return tag(el, { style: style }, content);
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
function fmtThreshold(cmd) {
|
|
3436
|
+
if (typeof cmd === "string") return "";
|
|
3437
|
+
var maxUp = cmd.maxUptimeSeconds || 0,
|
|
3438
|
+
maxMem = cmd.maxMemoryMB || 0;
|
|
3439
|
+
if (!maxUp && !maxMem) return "";
|
|
3440
|
+
var parts = [];
|
|
3441
|
+
if (maxUp > 0)
|
|
3442
|
+
parts.push(
|
|
3443
|
+
"最长 " + (maxUp >= 3600 ? Math.round(maxUp / 3600) + "h" : maxUp + "s"),
|
|
3444
|
+
);
|
|
3445
|
+
if (maxMem > 0) parts.push("最大 " + maxMem + "MB");
|
|
3446
|
+
return styled(
|
|
3447
|
+
"span",
|
|
3448
|
+
"color:#f59e0b;font-size:9px;margin-left:4px",
|
|
3449
|
+
"[" + parts.join("/") + "]",
|
|
3450
|
+
);
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
function cmdObj(cmd) {
|
|
3454
|
+
if (typeof cmd === "string")
|
|
3455
|
+
return { cmd: cmd, enabled: true, maxUptimeSeconds: 0, maxMemoryMB: 0 };
|
|
3456
|
+
return {
|
|
3457
|
+
cmd: cmd.cmd || "",
|
|
3458
|
+
enabled: cmd.enabled !== false,
|
|
3459
|
+
maxUptimeSeconds: cmd.maxUptimeSeconds || 0,
|
|
3460
|
+
maxMemoryMB: cmd.maxMemoryMB || 0,
|
|
3461
|
+
};
|
|
3462
|
+
}
|
|
3463
|
+
|
|
3464
|
+
function esc(s) {
|
|
3465
|
+
return String(s).replace(/'/g, "\\'");
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3468
|
+
// ── 渲染主函数 ──
|
|
3469
|
+
|
|
3470
|
+
function renderWatchdogDashboard(clients, clientCommands) {
|
|
3471
|
+
var onlineCount = clients.filter(function (c) {
|
|
3472
|
+
return c.online;
|
|
3473
|
+
}).length;
|
|
3474
|
+
var procCount = clients.reduce(function (s, c) {
|
|
3475
|
+
return s + (c.processes ? c.processes.length : 0);
|
|
3476
|
+
}, 0);
|
|
3477
|
+
var cmdCount = 0;
|
|
3478
|
+
for (var k in clientCommands) cmdCount += clientCommands[k].length;
|
|
3479
|
+
|
|
3480
|
+
document.getElementById("wdOnlineCount").textContent = onlineCount;
|
|
3481
|
+
document.getElementById("wdProcessCount").textContent = procCount;
|
|
3482
|
+
document.getElementById("wdCmdCount").textContent = cmdCount;
|
|
3483
|
+
|
|
3484
|
+
var el = document.getElementById("wdClientsList");
|
|
3485
|
+
|
|
3486
|
+
if (clients.length === 0) {
|
|
3487
|
+
el.innerHTML = styled(
|
|
3488
|
+
"div",
|
|
3489
|
+
"color:#666;padding:30px;text-align:center",
|
|
3490
|
+
"暂无客户端连接,等待看门狗上线..." +
|
|
3491
|
+
tag(
|
|
3492
|
+
"button",
|
|
3493
|
+
{
|
|
3494
|
+
onclick: "fetchWatchdogData()",
|
|
3495
|
+
style:
|
|
3496
|
+
"margin-top:12px;padding:6px 14px;background:#60a5fa;color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:12px",
|
|
3497
|
+
},
|
|
3498
|
+
"🔄 刷新",
|
|
3499
|
+
),
|
|
3500
|
+
);
|
|
3501
|
+
return;
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
var cards = clients
|
|
3505
|
+
.map(function (c) {
|
|
3506
|
+
return renderClientCard(c, clientCommands);
|
|
3507
|
+
})
|
|
3508
|
+
.join("");
|
|
3509
|
+
|
|
3510
|
+
el.innerHTML =
|
|
3511
|
+
styled(
|
|
3512
|
+
"button",
|
|
3513
|
+
{
|
|
3514
|
+
onclick: "fetchWatchdogData()",
|
|
3515
|
+
style:
|
|
3516
|
+
"padding:6px 14px;background:#60a5fa;color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:12px;margin-bottom:8px",
|
|
3517
|
+
},
|
|
3518
|
+
"🔄 刷新",
|
|
3519
|
+
) +
|
|
3520
|
+
styled(
|
|
3521
|
+
"div",
|
|
3522
|
+
{
|
|
3523
|
+
style:
|
|
3524
|
+
"display:grid;grid-template-columns:repeat(auto-fill,minmax(440px,1fr));gap:16px",
|
|
3525
|
+
},
|
|
3526
|
+
cards,
|
|
3527
|
+
);
|
|
3528
|
+
}
|
|
3529
|
+
|
|
3530
|
+
function renderClientCard(c, clientCommands) {
|
|
3531
|
+
var os = c.osInfo || {},
|
|
3532
|
+
cfg = c.config || {},
|
|
3533
|
+
procs = c.processes || [];
|
|
3534
|
+
var userId = cfg.userId || "";
|
|
3535
|
+
var myCmds = userId && clientCommands[userId] ? clientCommands[userId] : [];
|
|
3536
|
+
|
|
3537
|
+
var osStr = [
|
|
3538
|
+
os.platform,
|
|
3539
|
+
os.release,
|
|
3540
|
+
os.arch,
|
|
3541
|
+
os.cpus + "核",
|
|
3542
|
+
(os.totalMemoryMB / 1024).toFixed(1) + "GB",
|
|
3543
|
+
]
|
|
3544
|
+
.filter(Boolean)
|
|
3545
|
+
.join(" | ");
|
|
3546
|
+
|
|
3547
|
+
// 信息行
|
|
3548
|
+
var infoLine =
|
|
3549
|
+
"用户编号: " +
|
|
3550
|
+
styled("span", "color:#f59e0b", userId || "未设置") +
|
|
3551
|
+
" | tt-help v" +
|
|
3552
|
+
(c.ttHelpVersion || "?") +
|
|
3553
|
+
" | Node " +
|
|
3554
|
+
(os.nodeVersion || "?") +
|
|
3555
|
+
" | 运行 " +
|
|
3556
|
+
(os.uptime != null ? Math.round(os.uptime / 3600) + "h" : "?");
|
|
3557
|
+
|
|
3558
|
+
// 在线状态
|
|
3559
|
+
var onlineBadge = c.online
|
|
3560
|
+
? styled("span", "color:#4ade80", "● 在线")
|
|
3561
|
+
: styled("span", "color:#f87171", "● 离线");
|
|
3562
|
+
var lastSeen = new Date(c.lastHeartbeat).toLocaleTimeString("zh-CN");
|
|
3563
|
+
|
|
3564
|
+
// 头部
|
|
3565
|
+
var header = styled(
|
|
3566
|
+
"div",
|
|
3567
|
+
"display:flex;justify-content:space-between;align-items:center;margin-bottom:8px",
|
|
3568
|
+
styled(
|
|
3569
|
+
"div",
|
|
3570
|
+
"",
|
|
3571
|
+
styled("strong", "color:#60a5fa;font-size:14px", c.hostname) +
|
|
3572
|
+
styled("span", "color:#666;font-size:11px;margin-left:8px", c.clientId),
|
|
3573
|
+
) +
|
|
3574
|
+
styled(
|
|
3575
|
+
"div",
|
|
3576
|
+
"display:flex;gap:10px;align-items:center",
|
|
3577
|
+
styled("span", "color:#888;font-size:11px", c.ip) +
|
|
3578
|
+
onlineBadge +
|
|
3579
|
+
styled("span", "color:#666;font-size:10px", lastSeen),
|
|
3580
|
+
),
|
|
3581
|
+
);
|
|
3582
|
+
|
|
3583
|
+
// 进程表格
|
|
3584
|
+
var procTable = "";
|
|
3585
|
+
if (procs.length > 0) {
|
|
3586
|
+
var rows = procs
|
|
3587
|
+
.map(function (p) {
|
|
3588
|
+
var sc = p.status === "running" ? "#4ade80" : "#f87171";
|
|
3589
|
+
return tag(
|
|
3590
|
+
"tr",
|
|
3591
|
+
{},
|
|
3592
|
+
tag(
|
|
3593
|
+
"td",
|
|
3594
|
+
{
|
|
3595
|
+
style:
|
|
3596
|
+
"padding:3px;font-family:monospace;font-size:11px;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap",
|
|
3597
|
+
title: p.name || p.cmdId,
|
|
3598
|
+
},
|
|
3599
|
+
p.name || p.cmdId,
|
|
3600
|
+
) +
|
|
3601
|
+
tag(
|
|
3602
|
+
"td",
|
|
3603
|
+
{ style: "padding:3px;color:#aaa;font-size:10px" },
|
|
3604
|
+
p.pid || "-",
|
|
3605
|
+
) +
|
|
3606
|
+
tag(
|
|
3607
|
+
"td",
|
|
3608
|
+
{ style: "padding:3px;font-size:11px" },
|
|
3609
|
+
p.memoryMB != null ? p.memoryMB + "MB" : "-",
|
|
3610
|
+
) +
|
|
3611
|
+
tag(
|
|
3612
|
+
"td",
|
|
3613
|
+
{ style: "padding:3px;color:#aaa;font-size:10px" },
|
|
3614
|
+
p.uptimeDisplay || p.uptimeSeconds + "s",
|
|
3615
|
+
) +
|
|
3616
|
+
tag(
|
|
3617
|
+
"td",
|
|
3618
|
+
{ style: "padding:3px;color:" + sc + ";font-size:10px" },
|
|
3619
|
+
p.status,
|
|
3620
|
+
) +
|
|
3621
|
+
tag(
|
|
3622
|
+
"td",
|
|
3623
|
+
{ style: "padding:3px;color:#f59e0b;font-size:10px" },
|
|
3624
|
+
p.restartCount > 0 ? String(p.restartCount) : "0",
|
|
3625
|
+
),
|
|
3626
|
+
);
|
|
3627
|
+
})
|
|
3628
|
+
.join("");
|
|
3629
|
+
procTable = styled(
|
|
3630
|
+
"div",
|
|
3631
|
+
"margin-top:10px",
|
|
3632
|
+
tag(
|
|
3633
|
+
"table",
|
|
3634
|
+
{ style: "width:100%;font-size:11px;border-collapse:collapse" },
|
|
3635
|
+
tag(
|
|
3636
|
+
"thead",
|
|
3637
|
+
{},
|
|
3638
|
+
tag(
|
|
3639
|
+
"tr",
|
|
3640
|
+
{ style: "color:#888;font-size:10px" },
|
|
3641
|
+
tag("th", { style: "text-align:left;padding:3px" }, "命令") +
|
|
3642
|
+
tag("th", { style: "text-align:left;padding:3px" }, "PID") +
|
|
3643
|
+
tag("th", { style: "text-align:left;padding:3px" }, "内存") +
|
|
3644
|
+
tag("th", { style: "text-align:left;padding:3px" }, "运行") +
|
|
3645
|
+
tag("th", { style: "text-align:left;padding:3px" }, "状态") +
|
|
3646
|
+
tag("th", { style: "text-align:left;padding:3px" }, "重启"),
|
|
3647
|
+
),
|
|
3648
|
+
) + tag("tbody", {}, rows),
|
|
3649
|
+
),
|
|
3650
|
+
);
|
|
3651
|
+
}
|
|
3652
|
+
|
|
3653
|
+
return styled(
|
|
3654
|
+
"div",
|
|
3655
|
+
"background:#1e1e2e;border:1px solid #333;border-radius:12px;padding:16px",
|
|
3656
|
+
header +
|
|
3657
|
+
styled("div", "color:#888;font-size:10px", osStr) +
|
|
3658
|
+
styled("div", "color:#888;font-size:10px", infoLine) +
|
|
3659
|
+
renderCmdList(myCmds, userId) +
|
|
3660
|
+
(procs.length > 0
|
|
3661
|
+
? procTable
|
|
3662
|
+
: styled(
|
|
3663
|
+
"div",
|
|
3664
|
+
"color:#555;font-size:11px;margin-top:6px",
|
|
3665
|
+
"无运行进程",
|
|
3666
|
+
)),
|
|
3667
|
+
);
|
|
3668
|
+
}
|
|
3669
|
+
|
|
3670
|
+
function renderCmdList(myCmds, userId) {
|
|
3671
|
+
var html = styled(
|
|
3672
|
+
"div",
|
|
3673
|
+
"margin-top:10px;border-top:1px solid #2a2a3a;padding-top:10px",
|
|
3674
|
+
styled(
|
|
3675
|
+
"div",
|
|
3676
|
+
"display:flex;justify-content:space-between;align-items:center;margin-bottom:6px",
|
|
3677
|
+
styled(
|
|
3678
|
+
"span",
|
|
3679
|
+
"font-size:11px;color:#888",
|
|
3680
|
+
"📋 命令列表 (" + myCmds.length + "条)",
|
|
3681
|
+
) +
|
|
3682
|
+
tag(
|
|
3683
|
+
"button",
|
|
3684
|
+
{
|
|
3685
|
+
onclick: "wdPromptAddCmd('" + esc(userId) + "')",
|
|
3686
|
+
style:
|
|
3687
|
+
"padding:4px 10px;background:#4ade80;color:#000;border:none;border-radius:4px;cursor:pointer;font-size:11px;font-weight:600",
|
|
3688
|
+
},
|
|
3689
|
+
"+ 添加命令",
|
|
3690
|
+
),
|
|
3691
|
+
),
|
|
3692
|
+
);
|
|
3693
|
+
|
|
3694
|
+
myCmds.forEach(function (cmd, idx) {
|
|
3695
|
+
var c = cmdObj(cmd);
|
|
3696
|
+
var color = c.enabled ? "#a78bfa" : "#555";
|
|
3697
|
+
var badge = c.enabled
|
|
3698
|
+
? styled("span", "color:#4ade80;font-size:9px", "▶")
|
|
3699
|
+
: styled("span", "color:#f87171;font-size:9px", "⏸");
|
|
3700
|
+
var codeStyle =
|
|
3701
|
+
"color:" +
|
|
3702
|
+
color +
|
|
3703
|
+
";font-size:10px;word-break:break-all;flex:1;margin:0 6px;" +
|
|
3704
|
+
(c.enabled ? "" : "text-decoration:line-through");
|
|
3705
|
+
|
|
3706
|
+
html += styled(
|
|
3707
|
+
"div",
|
|
3708
|
+
"display:flex;justify-content:space-between;align-items:center;padding:4px 0;font-size:11px;border-top:1px solid #222",
|
|
3709
|
+
badge +
|
|
3710
|
+
tag(
|
|
3711
|
+
"code",
|
|
3712
|
+
{ style: codeStyle, title: c.cmd },
|
|
3713
|
+
c.cmd + fmtThreshold(cmd),
|
|
3714
|
+
) +
|
|
3715
|
+
tag(
|
|
3716
|
+
"button",
|
|
3717
|
+
{
|
|
3718
|
+
onclick: "wdToggleClientCmd('" + esc(userId) + "'," + idx + ")",
|
|
3719
|
+
style:
|
|
3720
|
+
"padding:2px 6px;font-size:9px;background:" +
|
|
3721
|
+
(c.enabled ? "#f59e0b" : "#4ade80") +
|
|
3722
|
+
";color:#000;border:none;border-radius:3px;cursor:pointer;flex-shrink:0;margin-right:3px",
|
|
3723
|
+
title: c.enabled ? "停止" : "启动",
|
|
3724
|
+
},
|
|
3725
|
+
c.enabled ? "⏹" : "▶",
|
|
3726
|
+
) +
|
|
3727
|
+
tag(
|
|
3728
|
+
"button",
|
|
3729
|
+
{
|
|
3730
|
+
onclick: "wdPromptEditCmd('" + esc(userId) + "'," + idx + ")",
|
|
3731
|
+
style:
|
|
3732
|
+
"padding:2px 6px;font-size:9px;background:#60a5fa;color:#fff;border:none;border-radius:3px;cursor:pointer;flex-shrink:0;margin-right:3px",
|
|
3733
|
+
title: "编辑",
|
|
3734
|
+
},
|
|
3735
|
+
"✎",
|
|
3736
|
+
) +
|
|
3737
|
+
tag(
|
|
3738
|
+
"button",
|
|
3739
|
+
{
|
|
3740
|
+
onclick: "wdRemoveClientCmd('" + esc(userId) + "'," + idx + ")",
|
|
3741
|
+
style:
|
|
3742
|
+
"padding:2px 6px;font-size:9px;background:#991b1b;color:#fff;border:none;border-radius:3px;cursor:pointer;flex-shrink:0",
|
|
3743
|
+
title: "删除",
|
|
3744
|
+
},
|
|
3745
|
+
"✕",
|
|
3746
|
+
),
|
|
3747
|
+
);
|
|
3748
|
+
});
|
|
3749
|
+
|
|
3750
|
+
return html + "</div>";
|
|
3751
|
+
}
|
|
3752
|
+
|
|
3753
|
+
// ── 命令管理操作 ──
|
|
3754
|
+
|
|
3755
|
+
function wdPromptAddCmd(userId) {
|
|
3756
|
+
var cmd = prompt(
|
|
3757
|
+
"输入完整命令行:\n\n例如: tt-help explore --location DE --server http://...",
|
|
3758
|
+
);
|
|
3759
|
+
if (!cmd || !cmd.trim()) return;
|
|
3760
|
+
if (!_wdData) return;
|
|
3761
|
+
var allCmds = _wdData.clientCommands || {};
|
|
3762
|
+
var myCmds = (allCmds[userId] || []).slice();
|
|
3763
|
+
myCmds.push({
|
|
3764
|
+
cmd: cmd.trim(),
|
|
3765
|
+
enabled: true,
|
|
3766
|
+
maxUptimeSeconds: 0,
|
|
3767
|
+
maxMemoryMB: 0,
|
|
3768
|
+
});
|
|
3769
|
+
wdSaveClientCmds(userId, myCmds);
|
|
3770
|
+
}
|
|
3771
|
+
|
|
3772
|
+
function wdPromptEditCmd(userId, idx) {
|
|
3773
|
+
if (!_wdData) return;
|
|
3774
|
+
var allCmds = _wdData.clientCommands || {};
|
|
3775
|
+
var myCmds = (allCmds[userId] || []).slice();
|
|
3776
|
+
if (idx >= myCmds.length) return;
|
|
3777
|
+
var c = cmdObj(myCmds[idx]);
|
|
3778
|
+
|
|
3779
|
+
var newText = prompt("编辑命令行(留空不变,0=不限制):", c.cmd);
|
|
3780
|
+
if (newText === null) return;
|
|
3781
|
+
var newMaxUp =
|
|
3782
|
+
parseInt(
|
|
3783
|
+
prompt("最大运行时长(秒,0=不限制):", c.maxUptimeSeconds || "0"),
|
|
3784
|
+
) || 0;
|
|
3785
|
+
var newMaxMem =
|
|
3786
|
+
parseInt(prompt("最大内存(MB,0=不限制):", c.maxMemoryMB || "0")) || 0;
|
|
3787
|
+
|
|
3788
|
+
if (typeof myCmds[idx] === "string") {
|
|
3789
|
+
myCmds[idx] = {
|
|
3790
|
+
cmd: (newText || c.cmd).trim(),
|
|
3791
|
+
enabled: true,
|
|
3792
|
+
maxUptimeSeconds: newMaxUp,
|
|
3793
|
+
maxMemoryMB: newMaxMem,
|
|
3794
|
+
};
|
|
3795
|
+
} else {
|
|
3796
|
+
if (newText && newText.trim()) myCmds[idx].cmd = newText.trim();
|
|
3797
|
+
myCmds[idx].maxUptimeSeconds = newMaxUp;
|
|
3798
|
+
myCmds[idx].maxMemoryMB = newMaxMem;
|
|
3799
|
+
}
|
|
3800
|
+
wdSaveClientCmds(userId, myCmds);
|
|
3801
|
+
}
|
|
3802
|
+
|
|
3803
|
+
function wdRemoveClientCmd(userId, idx) {
|
|
3804
|
+
if (!_wdData) return;
|
|
3805
|
+
var allCmds = _wdData.clientCommands || {};
|
|
3806
|
+
var myCmds = (allCmds[userId] || []).slice();
|
|
3807
|
+
if (idx >= myCmds.length) return;
|
|
3808
|
+
var txt = cmdObj(myCmds[idx]).cmd;
|
|
3809
|
+
if (!confirm("确定删除命令?\n\n" + txt)) return;
|
|
3810
|
+
myCmds.splice(idx, 1);
|
|
3811
|
+
wdSaveClientCmds(userId, myCmds);
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
function wdToggleClientCmd(userId, idx) {
|
|
3815
|
+
if (!_wdData) return;
|
|
3816
|
+
var allCmds = _wdData.clientCommands || {};
|
|
3817
|
+
var myCmds = (allCmds[userId] || []).slice();
|
|
3818
|
+
if (idx >= myCmds.length) return;
|
|
3819
|
+
var cmd = myCmds[idx];
|
|
3820
|
+
if (typeof cmd === "string") {
|
|
3821
|
+
myCmds[idx] = {
|
|
3822
|
+
cmd: cmd,
|
|
3823
|
+
enabled: false,
|
|
3824
|
+
maxUptimeSeconds: 0,
|
|
3825
|
+
maxMemoryMB: 0,
|
|
3826
|
+
};
|
|
3827
|
+
} else {
|
|
3828
|
+
cmd.enabled = !cmd.enabled;
|
|
3829
|
+
}
|
|
3830
|
+
wdSaveClientCmds(userId, myCmds);
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
async function wdSaveClientCmds(userId, commands) {
|
|
3834
|
+
try {
|
|
3835
|
+
await fetch("/api/watchdog/client-commands", {
|
|
3836
|
+
method: "POST",
|
|
3837
|
+
headers: { "Content-Type": "application/json" },
|
|
3838
|
+
body: JSON.stringify({ userId: userId, commands: commands }),
|
|
3839
|
+
});
|
|
3840
|
+
fetchWatchdogData();
|
|
3841
|
+
} catch (e) {
|
|
3842
|
+
alert("保存失败: " + e.message);
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
<span>🎆</span>
|
|
22
22
|
</label>
|
|
23
23
|
<a class="script-link" onclick="navigateToSettings()" style="cursor:pointer">⚙️ LLM</a>
|
|
24
|
+
<a class="script-link" onclick="navigateToWatchdog()" style="cursor:pointer">🐕 看门狗</a>
|
|
24
25
|
<span class="status" id="lastUpdate">--</span>
|
|
25
26
|
</div>
|
|
26
27
|
</div>
|
|
@@ -488,6 +489,26 @@
|
|
|
488
489
|
</div>
|
|
489
490
|
</div>
|
|
490
491
|
</div>
|
|
492
|
+
<div id="watchdogPage">
|
|
493
|
+
<div class="stats" style="margin-bottom:16px">
|
|
494
|
+
<div class="stat-card clickable" onclick="navigateToMain()" style="background:rgba(96,165,250,0.1)">
|
|
495
|
+
<div class="label">← 返回主页面</div>
|
|
496
|
+
</div>
|
|
497
|
+
<div class="stat-card">
|
|
498
|
+
<div class="label">在线客户端</div>
|
|
499
|
+
<div class="value total" id="wdOnlineCount">0</div>
|
|
500
|
+
</div>
|
|
501
|
+
<div class="stat-card">
|
|
502
|
+
<div class="label">运行进程</div>
|
|
503
|
+
<div class="value pending" id="wdProcessCount">0</div>
|
|
504
|
+
</div>
|
|
505
|
+
<div class="stat-card">
|
|
506
|
+
<div class="label">命令池</div>
|
|
507
|
+
<div class="value slate" id="wdCmdCount">0</div>
|
|
508
|
+
</div>
|
|
509
|
+
</div>
|
|
510
|
+
<div id="wdClientsList" style="color:#666;font-size:12px">加载中...</div>
|
|
511
|
+
</div>
|
|
491
512
|
<script src="app.js"></script>
|
|
492
513
|
</body>
|
|
493
514
|
|