dsh-console-sentinel 0.3.0 → 1.0.0
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/CHANGELOG.md +438 -1
- package/README.md +104 -108
- package/README.zh.md +96 -102
- package/docs/screenshots/01-panel-overview.png +0 -0
- package/docs/screenshots/02-confirm-dialog.png +0 -0
- package/docs/screenshots/03-preview-result.png +0 -0
- package/docs/screenshots/04-detail-view.png +0 -0
- package/docs/screenshots/05-console-tab.png +0 -0
- package/docs/screenshots/06-whitelist-tab.png +0 -0
- package/lib/client.js +733 -91
- package/lib/desktop.js +397 -9
- package/lib/index.js +305 -7
- package/lib/probe.js +2 -1
- package/lib/whitelist.js +12 -6
- package/package.json +90 -82
- package/README-es.md +0 -29
- package/README-hi.md +0 -29
- package/README-pt.md +0 -29
package/lib/client.js
CHANGED
|
@@ -163,7 +163,13 @@ window.__ModuleLoader__.load({
|
|
|
163
163
|
busy: false,
|
|
164
164
|
message: "",
|
|
165
165
|
tone: "muted",
|
|
166
|
-
desktop: null
|
|
166
|
+
desktop: null,
|
|
167
|
+
taskbar: null,
|
|
168
|
+
taskbarLoading: false,
|
|
169
|
+
taskbarQueued: false,
|
|
170
|
+
taskbarView: "icon", // "icon" (compact icon grid, DEFAULT) | "detail" (grouped cards)
|
|
171
|
+
autoTimer: null,
|
|
172
|
+
visHooked: false
|
|
167
173
|
};
|
|
168
174
|
|
|
169
175
|
function el(id) {
|
|
@@ -240,6 +246,378 @@ window.__ModuleLoader__.load({
|
|
|
240
246
|
renderWindows();
|
|
241
247
|
renderWhitelist();
|
|
242
248
|
renderDesktopResult();
|
|
249
|
+
renderTaskbar();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// One refresh for everything: status/counts (via /status) AND the taskbar
|
|
253
|
+
// window list (via /taskbar-windows). Replaces the old separate "刷新" +
|
|
254
|
+
// "刷新任务栏" pair that confused users.
|
|
255
|
+
function refreshAll() {
|
|
256
|
+
setStatus("正在刷新…", "muted");
|
|
257
|
+
loadStatus();
|
|
258
|
+
loadTaskbar({ silent: true });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ---- compact tab switching (任务栏 / 控制台 / 白名单) --------------------
|
|
262
|
+
var TAB = { active: "taskbar" };
|
|
263
|
+
function setActiveTab(name) {
|
|
264
|
+
TAB.active = name;
|
|
265
|
+
var panels = { taskbar: "dcs-panel-taskbar", console: "dcs-panel-console", whitelist: "dcs-panel-whitelist" };
|
|
266
|
+
for (var k in panels) {
|
|
267
|
+
if (!panels.hasOwnProperty(k)) continue;
|
|
268
|
+
var p = el(panels[k]);
|
|
269
|
+
if (p) p.style.display = (k === name) ? "block" : "none";
|
|
270
|
+
}
|
|
271
|
+
var tabs = { taskbar: "dcs-tab-taskbar", console: "dcs-tab-console", whitelist: "dcs-tab-whitelist" };
|
|
272
|
+
for (var j in tabs) {
|
|
273
|
+
if (!tabs.hasOwnProperty(j)) continue;
|
|
274
|
+
var b = el(tabs[j]);
|
|
275
|
+
if (b) {
|
|
276
|
+
var on = (j === name);
|
|
277
|
+
b.style.color = on ? TOKEN.accent : TOKEN.textDim;
|
|
278
|
+
b.style.borderBottom = "2px solid " + (on ? TOKEN.accent : "transparent");
|
|
279
|
+
b.style.fontWeight = on ? 600 : 500;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ---- taskbar window whitelist (icon + checkbox list) --------------------
|
|
285
|
+
|
|
286
|
+
// Queue-safe loader: if a load is in flight, remember that another is needed
|
|
287
|
+
// and run it as soon as the current one finishes (a plain early `return`
|
|
288
|
+
// used to silently DROP the refresh after closing windows → list looked stale).
|
|
289
|
+
function loadTaskbar(opts) {
|
|
290
|
+
opts = opts || {};
|
|
291
|
+
if (cache.taskbarLoading) { cache.taskbarQueued = true; return; }
|
|
292
|
+
cache.taskbarLoading = true;
|
|
293
|
+
if (!opts.silent) {
|
|
294
|
+
var box0 = el("dcs-taskbar");
|
|
295
|
+
if (box0 && !(cache.taskbar && cache.taskbar.windows && cache.taskbar.windows.length)) {
|
|
296
|
+
clear(box0); box0.appendChild(mk("div", S.muted, "正在读取任务栏窗口…"));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
api("/taskbar-windows").then(function (body) {
|
|
300
|
+
cache.taskbar = body && body.ok ? body : { ok: false, reason: (body && body.reason) || "读取失败", windows: [] };
|
|
301
|
+
}).catch(function (error) {
|
|
302
|
+
cache.taskbar = { ok: false, reason: (error && error.message ? error.message : String(error)), windows: [] };
|
|
303
|
+
}).then(function () {
|
|
304
|
+
cache.taskbarLoading = false;
|
|
305
|
+
renderTaskbar();
|
|
306
|
+
if (cache.taskbarQueued) { cache.taskbarQueued = false; loadTaskbar({ silent: true }); }
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Optimistically drop windows from the cached taskbar list so the UI updates
|
|
311
|
+
// the instant a close action returns, before the real refresh reconciles.
|
|
312
|
+
function pruneTaskbar(pred) {
|
|
313
|
+
if (!cache.taskbar || !cache.taskbar.windows) return;
|
|
314
|
+
var before = cache.taskbar.windows.length;
|
|
315
|
+
cache.taskbar.windows = cache.taskbar.windows.filter(function (w) { return !pred(w); });
|
|
316
|
+
if (cache.taskbar.windows.length !== before) renderTaskbar();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// One call after any mutating action: optimistic prune + refresh counters +
|
|
320
|
+
// refresh the taskbar list (queue-safe). Fixes "关了却不更新".
|
|
321
|
+
function afterMutation(opt) {
|
|
322
|
+
opt = opt || {};
|
|
323
|
+
var rmHwnds = (opt.removeHwnds || []).map(Number);
|
|
324
|
+
var rmProc = String(opt.removeProcess || "").toLowerCase();
|
|
325
|
+
if (rmHwnds.length || rmProc) {
|
|
326
|
+
pruneTaskbar(function (w) {
|
|
327
|
+
if (rmHwnds.length && rmHwnds.indexOf(Number(w.hwnd)) >= 0) return true;
|
|
328
|
+
if (rmProc && String(w.process || "").toLowerCase().replace(/\.exe$/, "") === rmProc.replace(/\.exe$/, "")) return true;
|
|
329
|
+
return false;
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
loadStatus(true);
|
|
333
|
+
loadTaskbar({ silent: true });
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function iconImg(icon, label) {
|
|
337
|
+
if (icon && typeof icon === "string" && icon.indexOf("data:image") === 0) {
|
|
338
|
+
var img = document.createElement("img");
|
|
339
|
+
img.src = icon;
|
|
340
|
+
img.alt = label || "";
|
|
341
|
+
img.style.width = "18px";
|
|
342
|
+
img.style.height = "18px";
|
|
343
|
+
img.style.flex = "0 0 auto";
|
|
344
|
+
img.style.objectFit = "contain";
|
|
345
|
+
img.style.borderRadius = "3px";
|
|
346
|
+
return img;
|
|
347
|
+
}
|
|
348
|
+
// Fallback: a small neutral placeholder block keeps rows aligned.
|
|
349
|
+
var ph = mk("span", {
|
|
350
|
+
display: "inline-block", width: "18px", height: "18px", flex: "0 0 auto",
|
|
351
|
+
borderRadius: "3px", background: TOKEN.bgCardSoft,
|
|
352
|
+
border: "1px solid " + TOKEN.borderSoft
|
|
353
|
+
});
|
|
354
|
+
return ph;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function _badge(text, color, dashed) {
|
|
358
|
+
return mk("span", {
|
|
359
|
+
fontSize: "10.5px", fontWeight: "600", color: color,
|
|
360
|
+
border: (dashed ? "1px dashed " : "1px solid ") + color,
|
|
361
|
+
borderRadius: "6px", padding: "0 5px", whiteSpace: "nowrap", flex: "0 0 auto"
|
|
362
|
+
}, text);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// System-level protection: host + compute jobs. These can NEVER be closed
|
|
366
|
+
// from the panel (closing them kills the harness or an in-progress Fluent run).
|
|
367
|
+
function isSystemLocked(proc, hostLocked) {
|
|
368
|
+
if (hostLocked) return true;
|
|
369
|
+
return /^dsh|electron|deepseek|mpi|fluent|ansys|^cx\d|solver|cae|launcher/i.test(String(proc || ""));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function toggleTaskbarView() {
|
|
373
|
+
cache.taskbarView = cache.taskbarView === "icon" ? "detail" : "icon";
|
|
374
|
+
renderTaskbar();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function renderTaskbar() {
|
|
378
|
+
var box = el("dcs-taskbar");
|
|
379
|
+
if (!box) return;
|
|
380
|
+
clear(box);
|
|
381
|
+
var vb = el("dcs-taskbar-view");
|
|
382
|
+
if (vb) vb.textContent = cache.taskbarView === "icon" ? "详细视图" : "仅图标视图"; var data = cache.taskbar;
|
|
383
|
+
if (!data) { box.appendChild(mk("div", S.muted, "读取中…")); return; }
|
|
384
|
+
if (!data.ok) {
|
|
385
|
+
box.appendChild(mk("div", { fontSize: "12px", color: TOKEN.danger, padding: "4px 0" },
|
|
386
|
+
"读取任务栏失败:" + (data.reason || "未知")));
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
var list = data.windows || [];
|
|
390
|
+
if (!list.length) { box.appendChild(mk("div", S.muted, "任务栏上没有可列出的窗口。")); return; }
|
|
391
|
+
|
|
392
|
+
// ---- Icon-only mode: a compact grid, one icon per window, row by row ----
|
|
393
|
+
if (cache.taskbarView === "icon") {
|
|
394
|
+
var grid = mk("div", { display: "flex", flexWrap: "wrap", gap: "6px", alignContent: "flex-start" });
|
|
395
|
+
for (var k = 0; k < list.length; k++) {
|
|
396
|
+
(function (w) {
|
|
397
|
+
var sysLocked = isSystemLocked(w.process, w.hostLocked);
|
|
398
|
+
var closable = !w.whitelisted && !sysLocked;
|
|
399
|
+
var cell = mk("div", {
|
|
400
|
+
display: "flex", alignItems: "center", justifyContent: "center",
|
|
401
|
+
width: "30px", height: "30px", borderRadius: "8px",
|
|
402
|
+
border: "1px solid " + (closable ? TOKEN.borderSoft : TOKEN.borderSoft),
|
|
403
|
+
background: TOKEN.bgCardSoft,
|
|
404
|
+
opacity: closable ? "1" : "0.45",
|
|
405
|
+
cursor: closable ? "pointer" : "default", flex: "0 0 auto"
|
|
406
|
+
});
|
|
407
|
+
var ic = iconImg(w.icon, w.process);
|
|
408
|
+
ic.style.width = "20px"; ic.style.height = "20px";
|
|
409
|
+
cell.appendChild(ic);
|
|
410
|
+
cell.title = (w.process || "?") + (w.title ? (" — " + w.title) : "") +
|
|
411
|
+
(closable ? "(点击关闭此窗口)" : (w.whitelisted ? "(白名单,不可关闭)" : "(系统保护,不可关闭)"));
|
|
412
|
+
if (closable) {
|
|
413
|
+
cell.addEventListener("click", function (ev) {
|
|
414
|
+
try { ev.preventDefault(); } catch (e) {}
|
|
415
|
+
doWindow(w.hwnd, "close", Boolean(w.elevated), w.process);
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
grid.appendChild(cell);
|
|
419
|
+
})(list[k]);
|
|
420
|
+
}
|
|
421
|
+
box.appendChild(grid);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Group by software (process). Same process → same pid / protection / elevation.
|
|
426
|
+
var order = [];
|
|
427
|
+
var groups = {};
|
|
428
|
+
for (var i = 0; i < list.length; i++) {
|
|
429
|
+
var w = list[i];
|
|
430
|
+
var key = String(w.process || "?").toLowerCase().replace(/\.exe$/, "");
|
|
431
|
+
if (!groups[key]) { groups[key] = { key: key, meta: w, windows: [] }; order.push(key); }
|
|
432
|
+
groups[key].windows.push(w);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
for (var gi = 0; gi < order.length; gi++) {
|
|
436
|
+
(function () {
|
|
437
|
+
var g = groups[order[gi]];
|
|
438
|
+
var m = g.meta;
|
|
439
|
+
var proc = m.process || "?";
|
|
440
|
+
var pid = m.pid;
|
|
441
|
+
var wl = Boolean(m.whitelisted);
|
|
442
|
+
var sysLocked = isSystemLocked(proc, Boolean(m.hostLocked));
|
|
443
|
+
var elevated = Boolean(m.elevated);
|
|
444
|
+
var closable = !wl && !sysLocked; // only these can be closed
|
|
445
|
+
var n = g.windows.length;
|
|
446
|
+
|
|
447
|
+
var card = mk("div", {
|
|
448
|
+
border: "1px solid " + TOKEN.borderSoft, borderRadius: "8px",
|
|
449
|
+
background: TOKEN.bgCardSoft, padding: "7px 9px", marginBottom: "7px"
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// ---- group header ----
|
|
453
|
+
var head = mk("div", { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" });
|
|
454
|
+
|
|
455
|
+
var cb = document.createElement("input");
|
|
456
|
+
cb.type = "checkbox";
|
|
457
|
+
cb.checked = wl;
|
|
458
|
+
cb.disabled = sysLocked;
|
|
459
|
+
cb.title = sysLocked ? "系统保护程序,不可加入白名单" : "勾选=加入白名单(该软件永不被关闭)";
|
|
460
|
+
cb.style.flex = "0 0 auto";
|
|
461
|
+
cb.style.cursor = sysLocked ? "default" : "pointer";
|
|
462
|
+
(function (el2, procName, locked) {
|
|
463
|
+
el2.addEventListener("change", function () {
|
|
464
|
+
if (locked) return;
|
|
465
|
+
setTaskbarWhitelist(procName, "", el2.checked);
|
|
466
|
+
});
|
|
467
|
+
})(cb, proc, sysLocked);
|
|
468
|
+
head.appendChild(cb);
|
|
469
|
+
head.appendChild(iconImg(m.icon, proc));
|
|
470
|
+
|
|
471
|
+
var nameWrap = mk("div", { display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap", minWidth: "0" });
|
|
472
|
+
nameWrap.appendChild(mk("span", { fontSize: "13px", fontWeight: 600, color: TOKEN.text },
|
|
473
|
+
proc + (pid ? " (pid " + pid + ")" : "")));
|
|
474
|
+
if (n > 1) nameWrap.appendChild(mk("span", Object.assign({}, S.muted, { fontSize: "11px" }), "· " + n + " 个窗口"));
|
|
475
|
+
if (wl) nameWrap.appendChild(_badge("白名单", TOKEN.success, false));
|
|
476
|
+
if (sysLocked) nameWrap.appendChild(_badge("系统保护", TOKEN.danger, false));
|
|
477
|
+
if (elevated && !sysLocked) nameWrap.appendChild(_badge("管理员", TOKEN.textDim, true));
|
|
478
|
+
head.appendChild(nameWrap);
|
|
479
|
+
|
|
480
|
+
var acts = mk("div", { display: "flex", gap: "6px", alignItems: "center", marginLeft: "auto", flex: "0 0 auto" });
|
|
481
|
+
if (closable) {
|
|
482
|
+
(function (procName, cnt) {
|
|
483
|
+
acts.appendChild(button("关掉该软件" + (cnt > 1 ? ("(" + cnt + ")") : ""), S.btn, function () { closeSoftware(procName, cnt); }));
|
|
484
|
+
})(proc, n);
|
|
485
|
+
}
|
|
486
|
+
head.appendChild(acts);
|
|
487
|
+
card.appendChild(head);
|
|
488
|
+
|
|
489
|
+
// ---- per-window rows, each with its own 关闭 button ----
|
|
490
|
+
var winsBox = mk("div", { marginTop: "5px", paddingLeft: "22px", display: "flex", flexDirection: "column", gap: "2px" });
|
|
491
|
+
for (var wi = 0; wi < g.windows.length; wi++) {
|
|
492
|
+
(function (winRow) {
|
|
493
|
+
var line = mk("div", { display: "flex", alignItems: "center", gap: "6px" });
|
|
494
|
+
line.appendChild(mk("span", { color: TOKEN.textDim, flex: "0 0 auto", fontSize: "12px" }, "•"));
|
|
495
|
+
line.appendChild(mk("span", {
|
|
496
|
+
fontSize: "11.5px", color: TOKEN.textDim, minWidth: "0", flex: "1 1 auto",
|
|
497
|
+
overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap"
|
|
498
|
+
}, winRow.title ? String(winRow.title) : "(无标题)"));
|
|
499
|
+
// 关闭此窗口: disabled (ineffective) when whitelisted or system-protected.
|
|
500
|
+
var closeBtn = mk("button", {
|
|
501
|
+
appearance: "none", border: "1px solid " + (closable ? TOKEN.border : TOKEN.borderSoft),
|
|
502
|
+
background: "transparent", color: closable ? TOKEN.text : TOKEN.textDim,
|
|
503
|
+
borderRadius: "6px", padding: "1px 8px", fontSize: "11px",
|
|
504
|
+
flex: "0 0 auto", cursor: closable ? "pointer" : "not-allowed",
|
|
505
|
+
opacity: closable ? "1" : "0.45"
|
|
506
|
+
}, "关闭");
|
|
507
|
+
closeBtn.type = "button";
|
|
508
|
+
if (closable) {
|
|
509
|
+
closeBtn.title = elevated ? "关闭此窗口(管理员窗口会弹一次 UAC)" : "关闭此窗口(优雅关闭,等同 Alt+F4)";
|
|
510
|
+
closeBtn.addEventListener("click", function (ev) {
|
|
511
|
+
try { ev.preventDefault(); } catch (e) {}
|
|
512
|
+
doWindow(winRow.hwnd, "close", elevated, proc);
|
|
513
|
+
});
|
|
514
|
+
} else {
|
|
515
|
+
closeBtn.title = wl ? "白名单程序,关闭无效" : "系统保护程序,关闭无效";
|
|
516
|
+
}
|
|
517
|
+
line.appendChild(closeBtn);
|
|
518
|
+
winsBox.appendChild(line);
|
|
519
|
+
})(g.windows[wi]);
|
|
520
|
+
}
|
|
521
|
+
card.appendChild(winsBox);
|
|
522
|
+
box.appendChild(card);
|
|
523
|
+
})();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// 取消 / 恢复 内置保护(写入持久 whitelist.unprotect 列表)
|
|
528
|
+
function setTaskbarProtection(proc, lift) {
|
|
529
|
+
var name = String(proc || "").trim();
|
|
530
|
+
if (!name) return;
|
|
531
|
+
var msg = lift ? ("确认取消 " + name + " 的保护?取消后它会被「一键关闭其他窗口」关闭。") : ("恢复 " + name + " 的保护?");
|
|
532
|
+
if (lift) { var yes = true; try { yes = window.confirm(msg); } catch (e) {} if (!yes) return; }
|
|
533
|
+
cache.busy = true;
|
|
534
|
+
setStatus(lift ? ("正在取消 " + name + " 的保护…") : ("正在恢复 " + name + " 的保护…"), "muted");
|
|
535
|
+
api("/whitelist", { method: "POST", body: JSON.stringify({ action: lift ? "add" : "remove", kind: "unprotect", items: [name] }) })
|
|
536
|
+
.then(function (body) {
|
|
537
|
+
cache.busy = false;
|
|
538
|
+
if (cache.status) cache.status.whitelist = { processes: body.processes || [], titles: body.titles || [], unprotect: body.unprotect || [] };
|
|
539
|
+
setStatus(lift ? (name + " 的保护已取消") : (name + " 的保护已恢复"), "ok");
|
|
540
|
+
loadTaskbar();
|
|
541
|
+
})
|
|
542
|
+
.catch(function (error) {
|
|
543
|
+
cache.busy = false;
|
|
544
|
+
setStatus("操作失败:" + (error && error.message ? error.message : error), "error");
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
function unprotectProcess(proc) { setTaskbarProtection(proc, true); }
|
|
548
|
+
function reprotectProcess(proc) { setTaskbarProtection(proc, false); }
|
|
549
|
+
|
|
550
|
+
// 强制关闭(管理员): elevated taskkill. One UAC prompt; answering No changes
|
|
551
|
+
// nothing. Used for windows whose owner runs elevated — an unelevated
|
|
552
|
+
// WM_CLOSE is silently dropped by Windows UIPI, so the normal 关闭 never works.
|
|
553
|
+
function forceCloseElevated(pid, procName) {
|
|
554
|
+
var name = String(procName || "").trim();
|
|
555
|
+
if (!pid) return;
|
|
556
|
+
var yes = true;
|
|
557
|
+
try {
|
|
558
|
+
yes = window.confirm(
|
|
559
|
+
"将以管理员权限强制结束 " + (name || ("PID " + pid)) + "(PID " + pid + ")。\n\n" +
|
|
560
|
+
"接下来会弹出 Windows UAC 授权框,点「是」才会执行。\n" +
|
|
561
|
+
"强制结束会丢弃该程序未保存的内容。确定继续?");
|
|
562
|
+
} catch (e) { yes = true; }
|
|
563
|
+
if (!yes) return;
|
|
564
|
+
cache.busy = true;
|
|
565
|
+
setStatus("正在请求管理员权限…(请在弹出的 UAC 窗口点「是」)", "muted");
|
|
566
|
+
api("/force-close", {
|
|
567
|
+
method: "POST",
|
|
568
|
+
body: JSON.stringify({ pids: [pid], allowedProcesses: name ? [name] : [] })
|
|
569
|
+
})
|
|
570
|
+
.then(function (body) {
|
|
571
|
+
cache.busy = false;
|
|
572
|
+
if (body && body.ok) {
|
|
573
|
+
setStatus("已强制结束 " + (name || ("PID " + pid)) + "(" + (body.killedCount || 0) + " 个)", "ok");
|
|
574
|
+
} else {
|
|
575
|
+
var why = (body && body.reason) || "未知";
|
|
576
|
+
if (why === "uac-denied-or-cancelled" || why.indexOf("elevated-helper-failed") === 0 || why === "no-marker-output") {
|
|
577
|
+
setStatus("未执行:UAC 被取消或被系统策略阻止(" + why + ")", "error");
|
|
578
|
+
} else if (why === "all-rejected") {
|
|
579
|
+
setStatus("未执行:目标进程被安全规则拒绝", "error");
|
|
580
|
+
} else {
|
|
581
|
+
setStatus("强制关闭失败:" + why, "error");
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
loadTaskbar();
|
|
585
|
+
})
|
|
586
|
+
.catch(function (error) {
|
|
587
|
+
cache.busy = false;
|
|
588
|
+
setStatus("强制关闭失败:" + (error && error.message ? error.message : error), "error");
|
|
589
|
+
loadTaskbar();
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function setTaskbarWhitelist(proc, title, checked) {
|
|
594
|
+
var name = String(proc || "").trim();
|
|
595
|
+
if (!name) return;
|
|
596
|
+
cache.busy = true;
|
|
597
|
+
setStatus(checked ? ("正在把 " + name + " 加入白名单…") : ("正在把 " + name + " 移出白名单…"), "muted");
|
|
598
|
+
api("/whitelist", { method: "POST", body: JSON.stringify({ action: checked ? "add" : "remove", kind: "process", items: [name] }) })
|
|
599
|
+
.then(function (body) {
|
|
600
|
+
cache.busy = false;
|
|
601
|
+
if (cache.status) cache.status.whitelist = { processes: body.processes || [], titles: body.titles || [] };
|
|
602
|
+
setStatus(checked ? (name + " 已加入白名单") : (name + " 已移出白名单"), "ok");
|
|
603
|
+
// Reflect new state on every row for this process without a full reload.
|
|
604
|
+
if (cache.taskbar && cache.taskbar.windows) {
|
|
605
|
+
for (var i = 0; i < cache.taskbar.windows.length; i++) {
|
|
606
|
+
var row = cache.taskbar.windows[i];
|
|
607
|
+
if (String(row.process || "").toLowerCase() === name.toLowerCase()) {
|
|
608
|
+
row.whitelisted = checked;
|
|
609
|
+
row.whitelistedBy = checked ? "process" : "";
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
renderTaskbar();
|
|
614
|
+
renderWhitelist();
|
|
615
|
+
})
|
|
616
|
+
.catch(function (error) {
|
|
617
|
+
cache.busy = false;
|
|
618
|
+
setStatus("白名单更新失败:" + (error && error.message ? error.message : error), "error");
|
|
619
|
+
renderTaskbar();
|
|
620
|
+
});
|
|
243
621
|
}
|
|
244
622
|
|
|
245
623
|
function renderHeader() {
|
|
@@ -251,8 +629,8 @@ window.__ModuleLoader__.load({
|
|
|
251
629
|
pill.textContent = win ? "Windows · 运行中" : ("非 Windows(" + st.platform + ")· 不生效");
|
|
252
630
|
pill.style.color = win ? TOKEN.success : TOKEN.danger;
|
|
253
631
|
var cfg = st.config || {};
|
|
254
|
-
setText("dcs-cfg", "
|
|
255
|
-
"s ·
|
|
632
|
+
setText("dcs-cfg", "扫描 " + cfg.scanIntervalSec + "s · 空闲 " + cfg.idleThresholdSec +
|
|
633
|
+
"s · 空壳 " + (cfg.emptyConsoleAction || "hide"));
|
|
256
634
|
}
|
|
257
635
|
|
|
258
636
|
function renderCounts() {
|
|
@@ -262,22 +640,25 @@ window.__ModuleLoader__.load({
|
|
|
262
640
|
clear(box);
|
|
263
641
|
var counts = st.counts || {};
|
|
264
642
|
var items = [
|
|
265
|
-
["
|
|
266
|
-
["空壳
|
|
643
|
+
["控制台", counts.total || 0],
|
|
644
|
+
["空壳", counts.empty || 0],
|
|
267
645
|
["可见空壳", counts.visibleEmpty || 0],
|
|
268
646
|
["已隐藏", counts.hidden || 0]
|
|
269
647
|
];
|
|
270
648
|
for (var i = 0; i < items.length; i++) {
|
|
649
|
+
// Compact inline pill (auto width, does NOT stretch across the row).
|
|
271
650
|
var cell = mk("div", {
|
|
272
|
-
flex: "
|
|
273
|
-
|
|
651
|
+
flex: "0 0 auto",
|
|
652
|
+
display: "inline-flex",
|
|
653
|
+
alignItems: "baseline",
|
|
654
|
+
gap: "4px",
|
|
274
655
|
background: TOKEN.bgCardSoft,
|
|
275
656
|
border: "1px solid " + TOKEN.borderSoft,
|
|
276
|
-
borderRadius: "
|
|
277
|
-
padding: "
|
|
657
|
+
borderRadius: "999px",
|
|
658
|
+
padding: "2px 9px"
|
|
278
659
|
});
|
|
279
|
-
cell.appendChild(mk("
|
|
280
|
-
cell.appendChild(mk("
|
|
660
|
+
cell.appendChild(mk("span", { fontSize: "13px", fontWeight: "600", color: TOKEN.text }, items[i][1]));
|
|
661
|
+
cell.appendChild(mk("span", Object.assign({}, S.muted, { fontSize: "11px" }), items[i][0]));
|
|
281
662
|
box.appendChild(cell);
|
|
282
663
|
}
|
|
283
664
|
}
|
|
@@ -386,34 +767,68 @@ window.__ModuleLoader__.load({
|
|
|
386
767
|
|
|
387
768
|
function renderDesktopResult() {
|
|
388
769
|
var box = el("dcs-desktop-result");
|
|
770
|
+
// Enable「执行清理」only after a preview (dryRun) with plans exists.
|
|
771
|
+
var execBtn = el("dcs-exec-cleanup");
|
|
772
|
+
var d = cache.desktop;
|
|
773
|
+
var canExec = Boolean(d && d.dryRun && (d.plannedCount || 0) > 0);
|
|
774
|
+
if (execBtn) {
|
|
775
|
+
execBtn.disabled = !canExec;
|
|
776
|
+
execBtn.style.opacity = canExec ? "1" : "0.5";
|
|
777
|
+
execBtn.style.cursor = canExec ? "pointer" : "not-allowed";
|
|
778
|
+
execBtn.title = canExec ? "执行清理上面预演的窗口" : "请先点「预演」核对清单";
|
|
779
|
+
}
|
|
389
780
|
if (!box) return;
|
|
390
781
|
clear(box);
|
|
391
|
-
|
|
392
|
-
if (!d) { box.appendChild(mk("div", S.muted, "点「预演」列出将被关闭的窗口;点「执行」真正关闭(保护项与白名单自动跳过)。")); return; }
|
|
782
|
+
if (!d) return; // no preview yet → render nothing (stay compact)
|
|
393
783
|
var head = (d.dryRun ? "预演" : "已执行") + ":可关闭 " + (d.plannedCount || 0) + " 个" +
|
|
394
784
|
(d.dryRun ? "" : (",已关闭 " + (d.closedCount || 0) + " 个")) +
|
|
395
|
-
(d.
|
|
785
|
+
(d.elevatedCount ? (";其中管理员窗口 " + d.elevatedCount + " 个(执行时弹一次 UAC)") : "") +
|
|
786
|
+
(d.kept && d.kept.length ? (";保护跳过 " + d.kept.length + " 个") : "") +
|
|
787
|
+
(d.elevatedUacCancelled ? ";管理员窗口的 UAC 被取消" : "");
|
|
396
788
|
box.appendChild(mk("div", { fontSize: "12px", fontWeight: "600", color: d.dryRun ? TOKEN.text : TOKEN.success }, head));
|
|
397
789
|
var ul = mk("div", { marginTop: "6px" });
|
|
398
790
|
var planned = d.planned || [];
|
|
791
|
+
var detailed = d.plannedDetailed || [];
|
|
399
792
|
if (!planned.length) ul.appendChild(mk("div", S.muted, "没有可关闭的窗口。"));
|
|
400
793
|
for (var i = 0; i < planned.length && i < 40; i++) {
|
|
401
|
-
|
|
794
|
+
var det = detailed[i];
|
|
795
|
+
if (det) {
|
|
796
|
+
var row = mk("div", { display: "flex", alignItems: "center", gap: "8px", padding: "2px 0" });
|
|
797
|
+
row.appendChild(iconImg(det.icon, det.process));
|
|
798
|
+
row.appendChild(mk("span", Object.assign({}, S.mono, { color: TOKEN.textDim, wordBreak: "break-all" }), planned[i]));
|
|
799
|
+
ul.appendChild(row);
|
|
800
|
+
} else {
|
|
801
|
+
ul.appendChild(mk("div", Object.assign({}, S.mono, { color: TOKEN.textDim, padding: "1px 0" }), planned[i]));
|
|
802
|
+
}
|
|
402
803
|
}
|
|
403
804
|
box.appendChild(ul);
|
|
404
805
|
}
|
|
405
806
|
|
|
406
807
|
// ---- actions -------------------------------------------------------------
|
|
407
808
|
|
|
408
|
-
function doWindow(hwnd, action) {
|
|
809
|
+
function doWindow(hwnd, action, elevated, process) {
|
|
409
810
|
if (!hwnd) return;
|
|
410
811
|
cache.busy = true;
|
|
411
|
-
setStatus("正在 " + action + " …", "muted");
|
|
412
|
-
api("/window", { method: "POST", body: JSON.stringify({ hwnd: hwnd, action: action }) })
|
|
812
|
+
setStatus(action === "close" && elevated ? "正在请求管理员权限关闭该窗口(请点 UAC「是」)…" : ("正在 " + action + " …"), "muted");
|
|
813
|
+
api("/window", { method: "POST", body: JSON.stringify({ hwnd: hwnd, action: action, elevated: Boolean(elevated), process: process || "" }) })
|
|
413
814
|
.then(function (body) {
|
|
414
815
|
cache.busy = false;
|
|
415
|
-
|
|
416
|
-
|
|
816
|
+
var refused = body && body.reason === "host-protected";
|
|
817
|
+
if (refused) {
|
|
818
|
+
setStatus("已拒绝:这是 DSH 宿主相关窗口,不能 " + action, "error");
|
|
819
|
+
} else if (body && body.ok === false) {
|
|
820
|
+
var r = body.reason || "未知";
|
|
821
|
+
if (r === "uac-denied-or-cancelled") setStatus("未关闭:UAC 被取消", "error");
|
|
822
|
+
else setStatus(action + " 未成功:" + r, "error");
|
|
823
|
+
} else {
|
|
824
|
+
setStatus(action + " 完成" + (body && body.elevated ? "(管理员提权)" : ""), "ok");
|
|
825
|
+
}
|
|
826
|
+
if (body && body.status) { cache.status = body.status; renderWindows(); renderHeader(); renderCounts(); renderWhitelist(); }
|
|
827
|
+
if (!refused && action === "close") {
|
|
828
|
+
afterMutation({ removeHwnds: [hwnd] });
|
|
829
|
+
} else if (!refused) {
|
|
830
|
+
afterMutation({});
|
|
831
|
+
}
|
|
417
832
|
})
|
|
418
833
|
.catch(function (error) {
|
|
419
834
|
cache.busy = false;
|
|
@@ -428,7 +843,9 @@ window.__ModuleLoader__.load({
|
|
|
428
843
|
.then(function (body) {
|
|
429
844
|
cache.busy = false;
|
|
430
845
|
setStatus("已隐藏 " + (body.hidden || 0) + " 个空壳", "ok");
|
|
431
|
-
|
|
846
|
+
if (body && body.status) cache.status = body.status;
|
|
847
|
+
renderWindows(); renderHeader(); renderCounts();
|
|
848
|
+
afterMutation({});
|
|
432
849
|
})
|
|
433
850
|
.catch(function (error) {
|
|
434
851
|
cache.busy = false;
|
|
@@ -436,20 +853,91 @@ window.__ModuleLoader__.load({
|
|
|
436
853
|
});
|
|
437
854
|
}
|
|
438
855
|
|
|
856
|
+
// 按软件一键关闭:优雅关闭该软件的全部窗口(等同对每个窗口按 Alt+F4)。
|
|
857
|
+
function closeSoftware(processName, count) {
|
|
858
|
+
var name = String(processName || "").trim();
|
|
859
|
+
if (!name) return;
|
|
860
|
+
var yes = true;
|
|
861
|
+
try {
|
|
862
|
+
yes = window.confirm(
|
|
863
|
+
"关闭「" + name + "」的全部窗口" + (count > 1 ? ("(共 " + count + " 个)") : "") + "?\n\n" +
|
|
864
|
+
"这是优雅关闭(等同对每个窗口按 Alt+F4),不是强杀。\n" +
|
|
865
|
+
"若该软件是管理员程序,会弹一次 UAC,请点「是」。");
|
|
866
|
+
} catch (e) { yes = true; }
|
|
867
|
+
if (!yes) return;
|
|
868
|
+
cache.busy = true;
|
|
869
|
+
setStatus("正在关闭 " + name + " 的全部窗口…", "muted");
|
|
870
|
+
api("/close-process", { method: "POST", body: JSON.stringify({ process: name }) })
|
|
871
|
+
.then(function (body) {
|
|
872
|
+
cache.busy = false;
|
|
873
|
+
if (body && body.ok) {
|
|
874
|
+
var m = "已关闭 " + name + " " + (body.closedCount || 0) + "/" + (body.total || 0) + " 个窗口";
|
|
875
|
+
if (body.elevated) m += "(含管理员窗口 " + body.elevated + " 个)";
|
|
876
|
+
if (body.uacCancelled) m += " · UAC 被取消,管理员窗口未关";
|
|
877
|
+
setStatus(m, "ok");
|
|
878
|
+
} else {
|
|
879
|
+
setStatus("关闭失败:" + ((body && body.reason) || "未知"), "error");
|
|
880
|
+
}
|
|
881
|
+
if (body && body.status) cache.status = body.status;
|
|
882
|
+
renderWindows(); renderHeader(); renderCounts();
|
|
883
|
+
if (body && body.ok !== false && !body.uacCancelled) {
|
|
884
|
+
afterMutation({ removeProcess: name });
|
|
885
|
+
} else {
|
|
886
|
+
afterMutation({});
|
|
887
|
+
}
|
|
888
|
+
})
|
|
889
|
+
.catch(function (error) {
|
|
890
|
+
cache.busy = false;
|
|
891
|
+
setStatus("关闭失败:" + (error && error.message ? error.message : error), "error");
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// 关闭空壳窗口:真正结束空壳控制台的进程(不是隐藏),释放资源。
|
|
896
|
+
function closeEmpty() {
|
|
897
|
+
var yes = true;
|
|
898
|
+
try { yes = window.confirm("关闭所有空壳控制台窗口?\n只关闭「裸解释器空壳」(cmd / powershell / pwsh / python 且没有正在运行的任务);\n宿主程序、Fluent/MPI 等一律不动。确定的请按「确定」。"); } catch (e) { yes = true; }
|
|
899
|
+
if (!yes) return;
|
|
900
|
+
cache.busy = true;
|
|
901
|
+
setStatus("正在关闭空壳窗口…", "muted");
|
|
902
|
+
api("/close-empty", { method: "POST", body: "{}" })
|
|
903
|
+
.then(function (body) {
|
|
904
|
+
cache.busy = false;
|
|
905
|
+
var msg = "已关闭 " + (body.closed || 0) + " 个空壳窗口";
|
|
906
|
+
if (body.skipped) msg += "(跳过 " + body.skipped + " 个受保护/非裸解释器)";
|
|
907
|
+
setStatus(msg, "ok");
|
|
908
|
+
if (body && body.status) cache.status = body.status;
|
|
909
|
+
renderWindows(); renderHeader(); renderCounts();
|
|
910
|
+
afterMutation({});
|
|
911
|
+
})
|
|
912
|
+
.catch(function (error) {
|
|
913
|
+
cache.busy = false;
|
|
914
|
+
setStatus("关闭空壳失败:" + (error && error.message ? error.message : error), "error");
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
|
|
439
918
|
function desktopCleanup(dryRun) {
|
|
440
919
|
if (!dryRun) {
|
|
441
920
|
var yes = true;
|
|
442
|
-
try { yes = window.confirm("
|
|
921
|
+
try { yes = window.confirm("将清理任务栏窗口(关闭其他程序的窗口)。受保护的进程和白名单会自动跳过,前台窗口不会被关。\n\n执行前建议先「预演」核对清单。确定执行?"); } catch (e) {}
|
|
443
922
|
if (!yes) return;
|
|
444
923
|
}
|
|
445
924
|
cache.busy = true;
|
|
446
|
-
setStatus(dryRun ? "正在预演…" : "
|
|
925
|
+
setStatus(dryRun ? "正在预演…" : "正在清理任务栏窗口…", "muted");
|
|
447
926
|
api("/desktop-cleanup", { method: "POST", body: JSON.stringify({ dryRun: Boolean(dryRun) }) })
|
|
448
927
|
.then(function (body) {
|
|
449
928
|
cache.busy = false;
|
|
450
929
|
cache.desktop = body;
|
|
451
|
-
setStatus(dryRun ? "预演完成" : ("已关闭 " + (body.closedCount || 0) + " 个窗口"), "ok");
|
|
452
930
|
renderDesktopResult();
|
|
931
|
+
if (dryRun) {
|
|
932
|
+
setStatus("预演完成:可关闭 " + (body.plannedCount || 0) + " 个,查看清单后点「执行清理」", "ok");
|
|
933
|
+
} else {
|
|
934
|
+
var mm = "已清理 " + (body.closedCount || 0) + " 个窗口";
|
|
935
|
+
if (body.elevatedUacCancelled) mm += "(管理员窗口的 UAC 被取消)";
|
|
936
|
+
setStatus(mm, "ok");
|
|
937
|
+
if (body && body.status) cache.status = body.status;
|
|
938
|
+
renderWindows(); renderHeader(); renderCounts();
|
|
939
|
+
afterMutation({});
|
|
940
|
+
}
|
|
453
941
|
})
|
|
454
942
|
.catch(function (error) {
|
|
455
943
|
cache.busy = false;
|
|
@@ -490,6 +978,63 @@ window.__ModuleLoader__.load({
|
|
|
490
978
|
whitelistAction("reset", "process", []);
|
|
491
979
|
}
|
|
492
980
|
|
|
981
|
+
// ---- Auto refresh while the section is VISIBLE --------------------------
|
|
982
|
+
// 🔴 只在面板真正可见时定时刷新;关闭设置 / 切走 / 隐藏后立刻停表。
|
|
983
|
+
// 判定三重条件,任一不满足即视为不可见(保守,宁可少刷不可空转):
|
|
984
|
+
// ① document.visibilityState === 'visible'(窗口最小化/切到别的应用时停)
|
|
985
|
+
// ② 我们的容器在文档里(document.contains)——设置页关掉后容器被卸载
|
|
986
|
+
// ③ 容器 offsetParent 非空 或 有非零尺寸(display:none / 被隐藏时停)
|
|
987
|
+
// 另监听 visibilitychange 与设置页消失,即时停表(不只等定时器)。
|
|
988
|
+
var AUTO_MS = 3000;
|
|
989
|
+
function panelAlive() {
|
|
990
|
+
try {
|
|
991
|
+
if (typeof document === "undefined") return false;
|
|
992
|
+
if (document.visibilityState && document.visibilityState !== "visible") return false;
|
|
993
|
+
var box = el("dcs-taskbar") || el("dcs-counts");
|
|
994
|
+
if (!box) return false;
|
|
995
|
+
if (document.contains && !document.contains(box)) return false;
|
|
996
|
+
// offsetParent 为空通常意味着某级祖先 display:none(关闭/隐藏)
|
|
997
|
+
if ("offsetParent" in box && box.offsetParent === null) {
|
|
998
|
+
var r = box.getBoundingClientRect ? box.getBoundingClientRect() : null;
|
|
999
|
+
if (!r || (r.width === 0 && r.height === 0)) return false;
|
|
1000
|
+
}
|
|
1001
|
+
return true;
|
|
1002
|
+
} catch (e) { return false; }
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function startAutoRefresh() {
|
|
1006
|
+
if (cache.autoTimer) return;
|
|
1007
|
+
cache.autoTimer = setInterval(function () {
|
|
1008
|
+
if (!panelAlive()) { stopAutoRefresh(); return; }
|
|
1009
|
+
if (cache.busy || cache.taskbarLoading) return; // 正在操作/加载时跳过,避免打断
|
|
1010
|
+
loadTaskbar({ silent: true });
|
|
1011
|
+
loadStatus(true);
|
|
1012
|
+
}, AUTO_MS);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
function stopAutoRefresh() {
|
|
1016
|
+
if (cache.autoTimer) { try { clearInterval(cache.autoTimer); } catch (e) {} cache.autoTimer = null; }
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// 挂一次性全局监听:窗口可见性变化 / 页面卸载 → 停表;重新可见 → 视情况恢复。
|
|
1020
|
+
function installVisibilityHooks() {
|
|
1021
|
+
if (cache.visHooked) return;
|
|
1022
|
+
cache.visHooked = true;
|
|
1023
|
+
try {
|
|
1024
|
+
document.addEventListener("visibilitychange", function () {
|
|
1025
|
+
if (document.visibilityState === "visible") {
|
|
1026
|
+
if (panelAlive()) { loadTaskbar({ silent: true }); loadStatus(true); startAutoRefresh(); }
|
|
1027
|
+
} else {
|
|
1028
|
+
stopAutoRefresh();
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
} catch (e) {}
|
|
1032
|
+
try {
|
|
1033
|
+
window.addEventListener("beforeunload", function () { stopAutoRefresh(); });
|
|
1034
|
+
window.addEventListener("pagehide", function () { stopAutoRefresh(); });
|
|
1035
|
+
} catch (e) {}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
493
1038
|
// ---- React component -----------------------------------------------------
|
|
494
1039
|
|
|
495
1040
|
function SettingsSection() {
|
|
@@ -500,94 +1045,172 @@ window.__ModuleLoader__.load({
|
|
|
500
1045
|
setTimeout(function () {
|
|
501
1046
|
if (cache.status) { renderAll(); }
|
|
502
1047
|
else { loadStatus(); }
|
|
1048
|
+
loadTaskbar({ silent: true });
|
|
1049
|
+
installVisibilityHooks();
|
|
1050
|
+
startAutoRefresh();
|
|
503
1051
|
}, 0);
|
|
504
1052
|
} catch (e) {}
|
|
505
1053
|
|
|
1054
|
+
var TABS = [
|
|
1055
|
+
{ key: "taskbar", label: "任务栏" },
|
|
1056
|
+
{ key: "console", label: "控制台" },
|
|
1057
|
+
{ key: "whitelist", label: "白名单" }
|
|
1058
|
+
];
|
|
1059
|
+
var activeTab = TAB.active || "taskbar";
|
|
1060
|
+
var tabBtn = function (t) {
|
|
1061
|
+
var on = activeTab === t.key;
|
|
1062
|
+
return React.createElement("button", {
|
|
1063
|
+
id: "dcs-tab-" + t.key,
|
|
1064
|
+
type: "button",
|
|
1065
|
+
onClick: function () { setActiveTab(t.key); },
|
|
1066
|
+
style: {
|
|
1067
|
+
background: "transparent", border: "none",
|
|
1068
|
+
borderBottom: "2px solid " + (on ? TOKEN.accent : "transparent"),
|
|
1069
|
+
color: on ? TOKEN.accent : TOKEN.textDim,
|
|
1070
|
+
fontWeight: on ? 600 : 500,
|
|
1071
|
+
fontSize: "12.5px", padding: "6px 10px", cursor: "pointer", lineHeight: 1.3
|
|
1072
|
+
}
|
|
1073
|
+
}, t.label);
|
|
1074
|
+
};
|
|
1075
|
+
var panel = function (t, children) {
|
|
1076
|
+
return React.createElement("div", {
|
|
1077
|
+
id: "dcs-panel-" + t.key,
|
|
1078
|
+
style: { display: (activeTab === t.key) ? "block" : "none" }
|
|
1079
|
+
}, children);
|
|
1080
|
+
};
|
|
1081
|
+
var hint = function (text) {
|
|
1082
|
+
return React.createElement("div", {
|
|
1083
|
+
style: Object.assign({}, S.muted, { fontSize: "11px", marginBottom: "6px", lineHeight: 1.45 })
|
|
1084
|
+
}, text);
|
|
1085
|
+
};
|
|
1086
|
+
var scrollBox = { maxHeight: "270px", overflowY: "auto", paddingRight: "2px" };
|
|
1087
|
+
|
|
506
1088
|
return React.createElement(
|
|
507
1089
|
"div",
|
|
508
|
-
{ style: { display: "flex", flexDirection: "column", gap: "
|
|
509
|
-
|
|
510
|
-
// Header
|
|
511
|
-
React.createElement("div", { style: S.card },
|
|
512
|
-
React.createElement("div", { style:
|
|
513
|
-
React.createElement("div",
|
|
514
|
-
React.createElement("
|
|
515
|
-
React.createElement("
|
|
1090
|
+
{ style: { display: "flex", flexDirection: "column", gap: "8px" } },
|
|
1091
|
+
|
|
1092
|
+
// Header (compact: title + platform pill + refresh, then cfg + status)
|
|
1093
|
+
React.createElement("div", { style: Object.assign({}, S.card, { padding: "9px 12px", marginBottom: "2px" }) },
|
|
1094
|
+
React.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "10px" } },
|
|
1095
|
+
React.createElement("div", { style: { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" } },
|
|
1096
|
+
React.createElement("span", { style: S.title }, "控制台哨兵"),
|
|
1097
|
+
React.createElement("span", { id: "dcs-platform", style: { fontSize: "11.5px", fontWeight: 600 } }, "读取中…")
|
|
516
1098
|
),
|
|
517
|
-
React.createElement("
|
|
518
|
-
React.createElement("span", { id: "dcs-platform", style: { fontSize: "12px", fontWeight: 600 } }, "读取中…"),
|
|
519
|
-
React.createElement("button", {
|
|
520
|
-
type: "button", style: S.btnPrimary,
|
|
521
|
-
onClick: function () { loadStatus(); }
|
|
522
|
-
}, "刷新")
|
|
523
|
-
)
|
|
1099
|
+
React.createElement("button", { type: "button", style: S.btnPrimary, onClick: function () { refreshAll(); } }, "刷新")
|
|
524
1100
|
),
|
|
525
|
-
React.createElement("div", { id: "dcs-
|
|
1101
|
+
React.createElement("div", { id: "dcs-cfg", style: Object.assign({}, S.muted, { marginTop: "4px", fontSize: "11.5px" }) }, "读取中…"),
|
|
1102
|
+
React.createElement("div", { id: "dcs-status", style: { fontSize: "11.5px", color: TOKEN.textDim, minHeight: "14px", marginTop: "2px" } }, "")
|
|
526
1103
|
),
|
|
527
1104
|
|
|
528
|
-
// Counts
|
|
529
|
-
React.createElement("div", { style:
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
1105
|
+
// Counts — slim inline strip (no card chrome)
|
|
1106
|
+
React.createElement("div", { id: "dcs-counts", style: { display: "flex", gap: "6px", flexWrap: "wrap" } }),
|
|
1107
|
+
|
|
1108
|
+
// 操作栏 —— 全部动作压成一行(说明走 title 悬浮提示,不占版面)
|
|
1109
|
+
React.createElement("div", { style: { display: "flex", gap: "6px", flexWrap: "wrap", alignItems: "center" } },
|
|
1110
|
+
React.createElement("button", {
|
|
1111
|
+
type: "button", style: S.btn,
|
|
1112
|
+
title: "把「空壳」控制台窗口隐藏起来(进程不杀,可恢复)",
|
|
1113
|
+
onClick: function () { hideEmpty(); }
|
|
1114
|
+
}, "隐藏可见空壳"),
|
|
1115
|
+
React.createElement("button", {
|
|
1116
|
+
type: "button", style: S.btnDanger,
|
|
1117
|
+
title: "真正结束「空壳」控制台进程(只关 cmd/powershell/pwsh/python 且无任务在跑;宿主与 Fluent 不动)",
|
|
1118
|
+
onClick: function () { closeEmpty(); }
|
|
1119
|
+
}, "关闭空壳窗口"),
|
|
1120
|
+
React.createElement("span", { style: { width: "1px", height: "18px", background: TOKEN.borderSoft, flex: "0 0 auto" } }),
|
|
1121
|
+
React.createElement("button", {
|
|
1122
|
+
type: "button", style: S.btn,
|
|
1123
|
+
title: "预演:列出将被关闭的窗口(保护项与白名单自动跳过)",
|
|
1124
|
+
onClick: function () { desktopCleanup(true); }
|
|
1125
|
+
}, "预演清理"),
|
|
1126
|
+
React.createElement("button", {
|
|
1127
|
+
id: "dcs-exec-cleanup", type: "button", style: S.btnPrimary,
|
|
1128
|
+
title: "执行清理:关闭其他程序的窗口(需先预演;管理员窗口会弹一次 UAC)",
|
|
1129
|
+
onClick: function () { desktopCleanup(false); }
|
|
1130
|
+
}, "执行清理")
|
|
535
1131
|
),
|
|
536
1132
|
|
|
537
|
-
//
|
|
538
|
-
React.createElement("div", { style:
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
1133
|
+
// 预演/执行结果(有内容才显示,平时只是一行小字,不占高度)
|
|
1134
|
+
React.createElement("div", { id: "dcs-desktop-result", style: { maxHeight: "160px", overflowY: "auto", paddingRight: "2px" } }),
|
|
1135
|
+
|
|
1136
|
+
// Tabs
|
|
1137
|
+
React.createElement("div", { style: { display: "flex", gap: "2px", borderBottom: "1px solid " + TOKEN.borderSoft } },
|
|
1138
|
+
tabBtn(TABS[0]), tabBtn(TABS[1]), tabBtn(TABS[2])
|
|
1139
|
+
),
|
|
1140
|
+
|
|
1141
|
+
// Panel 1: 任务栏(图标网格 / 详细分组 · 每窗口可单独关闭 / 关掉整个软件 / 白名单)
|
|
1142
|
+
panel(TABS[0],
|
|
1143
|
+
React.createElement("div", null,
|
|
1144
|
+
React.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "8px", marginBottom: "6px" } },
|
|
1145
|
+
React.createElement("span", {
|
|
1146
|
+
style: Object.assign({}, S.muted, { fontSize: "11px" }),
|
|
1147
|
+
title: "图标=点一下关闭该窗口;详细视图=按软件分组,可勾选加入白名单、可关掉整个软件。管理员窗口会弹一次 UAC;「系统保护」程序(宿主/Fluent/MPI)不可关闭。"
|
|
1148
|
+
}, cache.taskbarView === "icon" ? "点击图标关闭对应窗口(悬停看详情)" : "勾选=加入白名单;每窗口后可单独关闭"),
|
|
1149
|
+
React.createElement("button", { id: "dcs-taskbar-view", type: "button", style: S.btn, onClick: function () { toggleTaskbarView(); } }, "详细视图")
|
|
543
1150
|
),
|
|
544
|
-
React.createElement("div", {
|
|
545
|
-
|
|
546
|
-
React.createElement("button", { type: "button", style: S.btnPrimary, onClick: function () { desktopCleanup(false); } }, "一键关闭其他窗口")
|
|
547
|
-
)
|
|
548
|
-
),
|
|
549
|
-
React.createElement("div", { id: "dcs-desktop-result" })
|
|
1151
|
+
React.createElement("div", { id: "dcs-taskbar", style: scrollBox })
|
|
1152
|
+
)
|
|
550
1153
|
),
|
|
551
1154
|
|
|
552
|
-
//
|
|
553
|
-
|
|
554
|
-
React.createElement("div",
|
|
555
|
-
|
|
556
|
-
React.createElement("div", { style:
|
|
557
|
-
)
|
|
558
|
-
React.createElement("div", { id: "dcs-windows" })
|
|
1155
|
+
// Panel 2: 控制台窗口
|
|
1156
|
+
panel(TABS[1],
|
|
1157
|
+
React.createElement("div", null,
|
|
1158
|
+
hint("空壳 = 解释器无业务子进程(只剩 conhost)。点上方「隐藏可见空壳 / 关闭空壳窗口」一键处理。"),
|
|
1159
|
+
React.createElement("div", { id: "dcs-windows", style: { maxHeight: "260px", overflowY: "auto", paddingRight: "2px" } })
|
|
1160
|
+
)
|
|
559
1161
|
),
|
|
560
1162
|
|
|
561
|
-
//
|
|
562
|
-
|
|
563
|
-
React.createElement("div",
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
React.createElement("div", { style: S.muted }, "这些进程/窗口标题永远不被隐藏或关闭。")
|
|
567
|
-
),
|
|
568
|
-
React.createElement("button", { type: "button", style: S.btnDanger, onClick: function () { whitelistReset(); } }, "清空运行时白名单")
|
|
569
|
-
),
|
|
570
|
-
React.createElement("div", { style: { marginTop: "6px" } },
|
|
571
|
-
React.createElement("div", { style: Object.assign({}, S.muted, { marginBottom: "4px" }) }, "进程名"),
|
|
1163
|
+
// Panel 3: 白名单
|
|
1164
|
+
panel(TABS[2],
|
|
1165
|
+
React.createElement("div", null,
|
|
1166
|
+
hint("这些进程 / 标题永远不被隐藏或关闭。"),
|
|
1167
|
+
React.createElement("div", { style: Object.assign({}, S.muted, { marginBottom: "3px", fontSize: "11px" }) }, "进程名"),
|
|
572
1168
|
React.createElement("div", { id: "dcs-wl-proc", style: { marginBottom: "4px" } }),
|
|
573
|
-
React.createElement("div", { style: { display: "flex", gap: "
|
|
1169
|
+
React.createElement("div", { style: { display: "flex", gap: "6px", alignItems: "center", marginTop: "3px" } },
|
|
574
1170
|
React.createElement("input", { id: "dcs-wl-proc-input", type: "text", placeholder: "如 chrome / code / excel", style: S.input }),
|
|
575
|
-
React.createElement("button", { type: "button", style: S.btn, onClick: function () { whitelistAdd("process", "dcs-wl-proc-input"); } }, "
|
|
576
|
-
)
|
|
577
|
-
|
|
578
|
-
React.createElement("div", { style: { marginTop: "12px" } },
|
|
579
|
-
React.createElement("div", { style: Object.assign({}, S.muted, { marginBottom: "4px" }) }, "窗口标题包含"),
|
|
1171
|
+
React.createElement("button", { type: "button", style: S.btn, onClick: function () { whitelistAdd("process", "dcs-wl-proc-input"); } }, "加入")
|
|
1172
|
+
),
|
|
1173
|
+
React.createElement("div", { style: Object.assign({}, S.muted, { marginTop: "10px", marginBottom: "3px", fontSize: "11px" }) }, "窗口标题包含"),
|
|
580
1174
|
React.createElement("div", { id: "dcs-wl-title", style: { marginBottom: "4px" } }),
|
|
581
|
-
React.createElement("div", { style: { display: "flex", gap: "
|
|
1175
|
+
React.createElement("div", { style: { display: "flex", gap: "6px", alignItems: "center", marginTop: "3px" } },
|
|
582
1176
|
React.createElement("input", { id: "dcs-wl-title-input", type: "text", placeholder: "如 Visual Studio Code / 记事本", style: S.input }),
|
|
583
|
-
React.createElement("button", { type: "button", style: S.btn, onClick: function () { whitelistAdd("title", "dcs-wl-title-input"); } }, "
|
|
584
|
-
)
|
|
585
|
-
|
|
586
|
-
|
|
1177
|
+
React.createElement("button", { type: "button", style: S.btn, onClick: function () { whitelistAdd("title", "dcs-wl-title-input"); } }, "加入")
|
|
1178
|
+
),
|
|
1179
|
+
React.createElement("div", { style: { display: "flex", justifyContent: "flex-end", marginTop: "10px" } },
|
|
1180
|
+
React.createElement("button", { type: "button", style: S.btnDanger, onClick: function () { whitelistReset(); } }, "清空运行时白名单")
|
|
1181
|
+
),
|
|
1182
|
+
React.createElement("div", { id: "dcs-wl-path", style: Object.assign({}, S.muted, S.mono, { marginTop: "8px", fontSize: "10.5px", wordBreak: "break-all" }) })
|
|
1183
|
+
)
|
|
587
1184
|
)
|
|
588
1185
|
);
|
|
589
1186
|
}
|
|
590
1187
|
|
|
1188
|
+
// 分区导航图标(与插件内容相关:终端窗口 + 盾牌「哨兵」)。
|
|
1189
|
+
// 🔴 宿主设置页对未注册 icon 的分区会 fallback 到通用的齿轮图标
|
|
1190
|
+
// (navIcon(row.id) 的默认分支) —— 视觉上像"没有图标/缺省图标"。
|
|
1191
|
+
// 必须注册 settings.section.icon 槽并用 only 限定到本分区 id。
|
|
1192
|
+
function SettingsIcon(props) {
|
|
1193
|
+
var size = (props && props.size) || 16;
|
|
1194
|
+
var active = Boolean(props && props.active);
|
|
1195
|
+
var color = active ? TOKEN.accent : "currentColor";
|
|
1196
|
+
return React.createElement(
|
|
1197
|
+
"svg",
|
|
1198
|
+
{
|
|
1199
|
+
width: size, height: size, viewBox: "0 0 16 16",
|
|
1200
|
+
fill: "none", stroke: color, strokeWidth: 1.4,
|
|
1201
|
+
strokeLinecap: "round", strokeLinejoin: "round",
|
|
1202
|
+
"aria-hidden": "true", focusable: "false"
|
|
1203
|
+
},
|
|
1204
|
+
// 终端窗口外框
|
|
1205
|
+
React.createElement("rect", { x: "1.6", y: "2.6", width: "12.8", height: "10.8", rx: "1.9" }),
|
|
1206
|
+
// 标题栏分隔线
|
|
1207
|
+
React.createElement("path", { d: "M1.6 5.4h12.8" }),
|
|
1208
|
+
// 提示符 >_ (控制台的标志性元素)
|
|
1209
|
+
React.createElement("path", { d: "M4.3 8.1l1.7 1.5-1.7 1.5" }),
|
|
1210
|
+
React.createElement("path", { d: "M7.9 11.1h3.4" })
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
|
|
591
1214
|
function registerSettings(ctx) {
|
|
592
1215
|
if (!ctx || !ctx.slots || !React) return false;
|
|
593
1216
|
try {
|
|
@@ -603,11 +1226,30 @@ window.__ModuleLoader__.load({
|
|
|
603
1226
|
SettingsSection
|
|
604
1227
|
);
|
|
605
1228
|
});
|
|
606
|
-
return true;
|
|
607
1229
|
} catch (e) {
|
|
608
|
-
try { console.warn("[dsh-console-sentinel] settings registration failed", e); } catch (e2) {}
|
|
609
|
-
|
|
1230
|
+
try { console.warn("[dsh-console-sentinel] settings.section registration failed", e); } catch (e2) {}
|
|
1231
|
+
}
|
|
1232
|
+
// 导航图标:独立注册,失败不影响分区注册本身。
|
|
1233
|
+
// 🔴 宿主契约(dsh-cordis-client-runner / slots.ts:65):
|
|
1234
|
+
// 「settings.section.icon」的 registration id **必须等于目标分区的 id**,
|
|
1235
|
+
// 不匹配的 id 永不渲染(instead the shell keeps its DEFAULT GLYPH = 齿轮)。
|
|
1236
|
+
// 所以这里必须写 "dsh-console-sentinel",不能加 -icon 后缀。
|
|
1237
|
+
try {
|
|
1238
|
+
ctx.slots.inject("settings.section.icon", function () {
|
|
1239
|
+
return ctx.slots.register(
|
|
1240
|
+
{
|
|
1241
|
+
name: "settings.section.icon",
|
|
1242
|
+
id: "dsh-console-sentinel",
|
|
1243
|
+
order: 28,
|
|
1244
|
+
inject: function () { return {}; }
|
|
1245
|
+
},
|
|
1246
|
+
SettingsIcon
|
|
1247
|
+
);
|
|
1248
|
+
});
|
|
1249
|
+
} catch (e) {
|
|
1250
|
+
try { console.warn("[dsh-console-sentinel] settings icon registration failed", e); } catch (e2) {}
|
|
610
1251
|
}
|
|
1252
|
+
return true;
|
|
611
1253
|
}
|
|
612
1254
|
|
|
613
1255
|
// MUST be a plain array (see the header comment).
|