tt-help-cli-ycl 1.4.153 → 1.4.155

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.
@@ -4159,6 +4159,7 @@ function cmdObj(cmd) {
4159
4159
  enabled: true,
4160
4160
  maxUptimeSeconds: 0,
4161
4161
  maxMemoryMB: 0,
4162
+ resumeAt: 0,
4162
4163
  };
4163
4164
  return {
4164
4165
  id: cmd.id || "",
@@ -4167,6 +4168,7 @@ function cmdObj(cmd) {
4167
4168
  enabled: cmd.enabled !== false,
4168
4169
  maxUptimeSeconds: cmd.maxUptimeSeconds || 0,
4169
4170
  maxMemoryMB: cmd.maxMemoryMB || 0,
4171
+ resumeAt: cmd.resumeAt || 0,
4170
4172
  };
4171
4173
  }
4172
4174
 
@@ -4686,14 +4688,36 @@ function wdShowCountryModal(userId) {
4686
4688
  cc +
4687
4689
  '" ' +
4688
4690
  checked +
4689
- "/> " +
4691
+ ' onchange="wdSyncAllCountriesCb()"/> ' +
4690
4692
  cc +
4691
4693
  "</label>";
4692
4694
  }
4693
4695
  document.getElementById("wdCountryList").innerHTML = html;
4696
+ wdSyncAllCountriesCb();
4694
4697
  document.getElementById("wdCountryModal").style.display = "flex";
4695
4698
  }
4696
4699
 
4700
+ // 全选/全不选所有国家
4701
+ function wdToggleAllCountries(cb) {
4702
+ var cbs = document.querySelectorAll(".wd-country-cb");
4703
+ for (var i = 0; i < cbs.length; i++) cbs[i].checked = cb.checked;
4704
+ }
4705
+
4706
+ // 根据国家勾选情况同步“全选”框状态(全部勾中→勾上,否则取消)
4707
+ function wdSyncAllCountriesCb() {
4708
+ var allCb = document.getElementById("wdCountryAllCb");
4709
+ if (!allCb) return;
4710
+ var cbs = document.querySelectorAll(".wd-country-cb");
4711
+ var all = cbs.length > 0;
4712
+ for (var i = 0; i < cbs.length; i++) {
4713
+ if (!cbs[i].checked) {
4714
+ all = false;
4715
+ break;
4716
+ }
4717
+ }
4718
+ allCb.checked = all;
4719
+ }
4720
+
4697
4721
  async function wdSetCountries() {
4698
4722
  var cbs = document.querySelectorAll(".wd-country-cb:checked");
4699
4723
  var countries = [];
@@ -5462,6 +5486,27 @@ function renderCmdRows(myCmds, userId, procs, isOnline, llmAnalysis, clientId) {
5462
5486
  var label =
5463
5487
  c.name || (c.cmd.length > 40 ? c.cmd.substring(0, 37) + "..." : c.cmd);
5464
5488
  var cmdTitle = c.name ? c.name + "\n" + c.cmd : c.cmd;
5489
+ // 禁用中的定时恢复徽标(点击可改时间)
5490
+ var resumeBadge = "";
5491
+ if (!c.enabled && c.resumeAt) {
5492
+ var rd = new Date(c.resumeAt);
5493
+ var rhh = ("0" + rd.getHours()).slice(-2);
5494
+ var rmm = ("0" + rd.getMinutes()).slice(-2);
5495
+ var rtxt =
5496
+ c.resumeAt > Date.now()
5497
+ ? "⏰ " + rhh + ":" + rmm + " 自动启动"
5498
+ : "⏰ 待心跳恢复";
5499
+ resumeBadge = tag(
5500
+ "span",
5501
+ {
5502
+ onclick: "wdShowCmdOffModal('" + esc(userId) + "'," + idx + ")",
5503
+ style:
5504
+ "color:#fbbf24;font-size:10px;margin-left:6px;cursor:pointer;white-space:nowrap",
5505
+ title: "点击修改自动启动时间",
5506
+ },
5507
+ rtxt,
5508
+ );
5509
+ }
5465
5510
  var pid = p ? p.pid : "-";
5466
5511
  var mem = p && p.memoryMB != null ? p.memoryMB + "MB" : "-";
5467
5512
  var uptime = p ? p.uptimeDisplay || p.uptimeSeconds + "s" : "-";
@@ -5493,7 +5538,8 @@ function renderCmdRows(myCmds, userId, procs, isOnline, llmAnalysis, clientId) {
5493
5538
  "color:#555;font-size:10px;margin-left:4px;font-family:monospace",
5494
5539
  c.cmd.substring(0, 30) + (c.cmd.length > 30 ? "..." : ""),
5495
5540
  )
5496
- : ""),
5541
+ : "") +
5542
+ resumeBadge,
5497
5543
  ) +
5498
5544
  tag(
5499
5545
  "span",
@@ -5809,7 +5855,7 @@ function wdModalSave() {
5809
5855
  var myCmds = (allCmds[_wdModalUserId] || []).slice();
5810
5856
 
5811
5857
  if (_wdModalEditIdx >= 0) {
5812
- // 编辑模式:保留已有 ID
5858
+ // 编辑模式:保留已有 ID 与定时恢复状态
5813
5859
  if (_wdModalEditIdx < myCmds.length) {
5814
5860
  var oldObj = cmdObj(myCmds[_wdModalEditIdx]);
5815
5861
  myCmds[_wdModalEditIdx] = {
@@ -5819,6 +5865,7 @@ function wdModalSave() {
5819
5865
  enabled: oldObj.enabled,
5820
5866
  maxUptimeSeconds: maxUp,
5821
5867
  maxMemoryMB: maxMem,
5868
+ resumeAt: oldObj.resumeAt,
5822
5869
  };
5823
5870
  }
5824
5871
  } else {
@@ -5913,17 +5960,13 @@ function wdToggleClientCmd(userId, idx) {
5913
5960
  var myCmds = (allCmds[userId] || []).slice();
5914
5961
  if (idx >= myCmds.length) return;
5915
5962
  var cmd = myCmds[idx];
5916
- if (typeof cmd === "string") {
5917
- myCmds[idx] = {
5918
- id: String(Date.now()),
5919
- cmd: cmd,
5920
- enabled: false,
5921
- maxUptimeSeconds: 0,
5922
- maxMemoryMB: 0,
5923
- };
5924
- } else {
5925
- cmd.enabled = !cmd.enabled;
5963
+ // 启用中(含老数据字符串命令)→ 弹关闭定时弹窗;已禁用 → 立即开启
5964
+ if (typeof cmd === "string" || cmdObj(cmd).enabled) {
5965
+ wdShowCmdOffModal(userId, idx);
5966
+ return;
5926
5967
  }
5968
+ cmd.enabled = true;
5969
+ cmd.resumeAt = 0;
5927
5970
  // 立即更新按钮为 loading 状态
5928
5971
  var btn = event.target;
5929
5972
  if (btn && btn.tagName === "BUTTON") {
@@ -5934,6 +5977,47 @@ function wdToggleClientCmd(userId, idx) {
5934
5977
  wdSaveClientCmds(userId, myCmds);
5935
5978
  }
5936
5979
 
5980
+ // ── 关闭命令定时弹窗 ──
5981
+
5982
+ var _wdOffUserId = "";
5983
+ var _wdOffIdx = -1;
5984
+
5985
+ function wdShowCmdOffModal(userId, idx) {
5986
+ if (!_wdData) return;
5987
+ var myCmds = (_wdData.clientCommands || {})[userId] || [];
5988
+ if (idx >= myCmds.length) return;
5989
+ var c = cmdObj(myCmds[idx]);
5990
+ _wdOffUserId = userId;
5991
+ _wdOffIdx = idx;
5992
+ document.getElementById("wdCmdOffName").textContent = c.name || c.cmd;
5993
+ document.getElementById("wdCmdOffMinutes").value = "";
5994
+ document.getElementById("wdCmdOffModal").style.display = "flex";
5995
+ }
5996
+
5997
+ function wdCmdOffCancel() {
5998
+ document.getElementById("wdCmdOffModal").style.display = "none";
5999
+ }
6000
+
6001
+ // minutes: 数字分钟;0=永久关闭;undefined=读自定义输入框。
6002
+ // 只把 resumeDelayMinutes 传给服务端,恢复时间由服务端按服务器时钟换算。
6003
+ function wdCmdOffConfirm(minutes) {
6004
+ var mins =
6005
+ minutes !== undefined
6006
+ ? Number(minutes) || 0
6007
+ : parseInt(document.getElementById("wdCmdOffMinutes").value, 10) || 0;
6008
+ document.getElementById("wdCmdOffModal").style.display = "none";
6009
+ if (!_wdData) return;
6010
+ var allCmds = _wdData.clientCommands || {};
6011
+ var myCmds = (allCmds[_wdOffUserId] || []).slice();
6012
+ if (_wdOffIdx >= myCmds.length) return;
6013
+ var c = cmdObj(myCmds[_wdOffIdx]);
6014
+ c.enabled = false;
6015
+ c.resumeAt = 0;
6016
+ c.resumeDelayMinutes = mins;
6017
+ myCmds[_wdOffIdx] = c;
6018
+ wdSaveClientCmds(_wdOffUserId, myCmds);
6019
+ }
6020
+
5937
6021
  var _wdSaving = false;
5938
6022
 
5939
6023
  function wdShowSaving() {
@@ -690,6 +690,37 @@
690
690
  </div>
691
691
  </div>
692
692
 
693
+ <!-- 命令关闭定时弹窗 -->
694
+ <div id="wdCmdOffModal"
695
+ 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">
696
+ <div style="background:#1e1e2e;border:1px solid #333;border-radius:12px;padding:24px;width:90%;max-width:420px">
697
+ <div style="font-size:16px;color:#f59e0b;margin-bottom:4px">⏸ 关闭命令</div>
698
+ <div style="color:#888;font-size:12px;margin-bottom:12px"><span id="wdCmdOffName"
699
+ style="color:#e0e0e0;font-family:monospace"></span><br>关闭后经过所选时间自动重新启动(按服务器时钟计时,到点随心跳生效):</div>
700
+ <div style="display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap">
701
+ <button onclick="wdCmdOffConfirm(30)"
702
+ style="padding:8px 14px;background:#333;color:#e0e0e0;border:none;border-radius:6px;cursor:pointer;font-size:12px">30分钟</button>
703
+ <button onclick="wdCmdOffConfirm(60)"
704
+ style="padding:8px 14px;background:#333;color:#e0e0e0;border:none;border-radius:6px;cursor:pointer;font-size:12px">1小时</button>
705
+ <button onclick="wdCmdOffConfirm(120)"
706
+ style="padding:8px 14px;background:#333;color:#e0e0e0;border:none;border-radius:6px;cursor:pointer;font-size:12px">2小时</button>
707
+ <button onclick="wdCmdOffConfirm(0)"
708
+ style="padding:8px 14px;background:#7f1d1d;color:#fca5a5;border:none;border-radius:6px;cursor:pointer;font-size:12px">永久关闭</button>
709
+ </div>
710
+ <div style="display:flex;gap:8px;align-items:center;margin-bottom:16px">
711
+ <label style="color:#888;font-size:12px;white-space:nowrap">自定义(分钟)</label>
712
+ <input id="wdCmdOffMinutes" type="number" min="1"
713
+ style="width:90px;padding:6px;background:#111;border:1px solid #333;color:#e0e0e0;border-radius:6px;font-size:12px;box-sizing:border-box">
714
+ <button onclick="wdCmdOffConfirm()"
715
+ style="padding:8px 14px;background:#f59e0b;color:#000;border:none;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600">确定</button>
716
+ </div>
717
+ <div style="display:flex;justify-content:flex-end">
718
+ <button onclick="wdCmdOffCancel()"
719
+ style="padding:8px 16px;background:#333;color:#ccc;border:none;border-radius:6px;cursor:pointer;font-size:12px">取消</button>
720
+ </div>
721
+ </div>
722
+ </div>
723
+
693
724
  <!-- 客户端配置编辑弹窗 -->
694
725
  <div id="wdConfigModal"
695
726
  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">
@@ -757,6 +788,12 @@
757
788
  <div style="font-size:16px;color:#fbbf24;margin-bottom:4px">🌍 节点国家配置 — <span id="wdCountryUserId"></span></div>
758
789
  <div style="color:#888;font-size:12px;margin-bottom:12px">选择该客户端可用的代理节点国家(可多选)。<br>未配置时默认使用 <b
759
790
  style="color:#fbbf24">US</b>。修改后客户端将在下次心跳时生效。</div>
791
+ <div style="margin-bottom:8px">
792
+ <label style="display:inline-flex;align-items:center;gap:6px;color:#e0e0e0;font-size:12px;cursor:pointer">
793
+ <input type="checkbox" id="wdCountryAllCb" onchange="wdToggleAllCountries(this)"
794
+ style="width:14px;height:14px"> 全选
795
+ </label>
796
+ </div>
760
797
  <div id="wdCountryList"
761
798
  style="padding:10px;background:#16161e;border:1px solid #333;border-radius:8px;min-height:120px">加载中...</div>
762
799
  <div style="display:flex;justify-content:flex-end;gap:8px;margin-top:16px">
@@ -882,7 +919,7 @@
882
919
  </div>
883
920
  </div>
884
921
 
885
- <script src="app.js?v=20260828c"></script>
922
+ <script src="app.js?v=20260921a"></script>
886
923
  </body>
887
924
 
888
925
  </html>
@@ -195,10 +195,23 @@ function setClientCommands(userId, commands) {
195
195
  enabled: true,
196
196
  maxUptimeSeconds: 0,
197
197
  maxMemoryMB: 0,
198
+ resumeAt: 0,
198
199
  };
199
200
  }
200
201
  const obj = { ...c, cmd: (c.cmd || "").replace(/^[▶\n\r\s]+/, "").trim() };
201
202
  if (!obj.id) obj.id = String(Date.now() + nextId++);
203
+ // 关闭延迟启动:resumeDelayMinutes 是前端显式信号(本次关闭操作指定的分钟数)。
204
+ // 恢复时间一律按服务器时钟换算,避免浏览器时钟偏差导致提前/延后恢复。
205
+ if (obj.resumeDelayMinutes !== undefined) {
206
+ const mins = Number(obj.resumeDelayMinutes) || 0;
207
+ obj.resumeAt =
208
+ obj.enabled === false && mins > 0 ? Date.now() + mins * 60000 : 0;
209
+ delete obj.resumeDelayMinutes;
210
+ } else if (obj.enabled !== false) {
211
+ obj.resumeAt = 0; // 启用的命令不允许残留恢复时间
212
+ } else {
213
+ obj.resumeAt = obj.resumeAt || 0; // 未改动的禁用命令保留原定时
214
+ }
202
215
  return obj;
203
216
  });
204
217
  const cfg = loadConfig();
@@ -223,6 +236,30 @@ function getAllClientCommandsRaw() {
223
236
  return cfg.clientCommands || {};
224
237
  }
225
238
 
239
+ // 命令到点自动恢复:禁用且 resumeAt 已过 → 置回启用并写盘。
240
+ // 心跳前惰性触发(~15s 粒度足够);客户端零改动,恢复后随本次心跳正常下发。
241
+ function autoResumeCommands(userId) {
242
+ if (!userId) return;
243
+ const cfg = loadConfig();
244
+ const cmds = cfg.clientCommands && cfg.clientCommands[userId];
245
+ if (!cmds || !cmds.length) return;
246
+ const now = Date.now();
247
+ let resumed = [];
248
+ for (const c of cmds) {
249
+ if (c && c.enabled === false && c.resumeAt && c.resumeAt <= now) {
250
+ c.enabled = true;
251
+ c.resumeAt = 0;
252
+ resumed.push(c.id || c.cmd);
253
+ }
254
+ }
255
+ if (resumed.length) {
256
+ saveConfig(cfg);
257
+ console.error(
258
+ `[watchdog ${new Date().toLocaleTimeString("zh-CN", { hour12: false })}] ${userId} 命令到点自动恢复: ${resumed.join(", ")}`,
259
+ );
260
+ }
261
+ }
262
+
226
263
  // ── 客户端配置管理(用于远程下发 ~/.tt-help.json 配置) ──
227
264
 
228
265
  function getClientConfigStored(userId) {
@@ -435,8 +472,9 @@ async function handleHeartbeat(params, body, res) {
435
472
  loginReport: body.loginReport || null,
436
473
  });
437
474
 
438
- // 按 userId 返回该客户端的专属命令列表
475
+ // 按 userId 返回该客户端的专属命令列表(先处理到点自动恢复)
439
476
  const userId = (body.config && body.config.userId) || "";
477
+ autoResumeCommands(userId);
440
478
  const commands = getClientCommands(userId);
441
479
 
442
480
  // 幂等下发节点国家配置(默认 US)