@1e0zj/dsh-plugin-mall 0.4.6 → 0.4.7
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/client.js +255 -111
- package/src/index.js +42 -3
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -28,6 +28,7 @@ window.__ModuleLoader__.load({
|
|
|
28
28
|
".mkt_btnDanger:hover:not(:disabled){border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}",
|
|
29
29
|
".mkt_btnSm{padding:3px 10px;font-size:12px}",
|
|
30
30
|
".mkt_error{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}",
|
|
31
|
+
".mkt_notice{border-left:3px solid var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-state-error-primary);padding:7px 10px;font-size:12px;line-height:18px;overflow-wrap:anywhere}",
|
|
31
32
|
".mkt_list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;align-items:stretch;min-width:0}",
|
|
32
33
|
"@media (max-width:820px){.mkt_list{grid-template-columns:minmax(0,1fr)}}",
|
|
33
34
|
".mkt_listHead{grid-column:1/-1;font-size:12px;color:var(--dsw-alias-label-tertiary)}",
|
|
@@ -43,8 +44,9 @@ window.__ModuleLoader__.load({
|
|
|
43
44
|
".mkt_panelTitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);margin:0}",
|
|
44
45
|
".mkt_panelRow{display:flex;align-items:center;gap:8px}",
|
|
45
46
|
".mkt_panelRow .mkt_link{margin-left:auto}",
|
|
46
|
-
".mkt_pre{font-family:Consolas,Monaco,monospace;font-size:11.5px;line-height:16px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-markdown-code-block);border-radius:6px;padding:8px;max-height:
|
|
47
|
+
".mkt_pre{box-sizing:border-box;width:100%;max-width:100%;min-width:0;font-family:Consolas,Monaco,monospace;font-size:11.5px;line-height:16px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-markdown-code-block);border-radius:6px;padding:8px;max-height:280px;overflow:auto;white-space:pre;word-break:normal}",
|
|
47
48
|
".mkt_ok{color:var(--dsw-alias-state-success-primary)}",
|
|
49
|
+
".mkt_warn{color:var(--dsw-alias-state-warn-label)}",
|
|
48
50
|
".mkt_badge{display:inline-block;border-radius:999px;padding:1px 8px;font-size:11px;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary)}",
|
|
49
51
|
".mkt_badgeOk{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}",
|
|
50
52
|
".mkt_badgeWarn{border-color:var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-state-warn-label)}",
|
|
@@ -57,13 +59,31 @@ window.__ModuleLoader__.load({
|
|
|
57
59
|
".mkt_depList{display:flex;flex-direction:column;gap:6px;margin-top:8px}",
|
|
58
60
|
".mkt_depRow{display:flex;justify-content:space-between;align-items:center;gap:8px}",
|
|
59
61
|
".mkt_depActions{display:flex;align-items:center;gap:6px;flex-wrap:wrap}",
|
|
60
|
-
".
|
|
61
|
-
".
|
|
62
|
+
".mkt_decision{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-state-warn-secondary);border-radius:6px;overflow:hidden;margin-top:4px}",
|
|
63
|
+
".mkt_decisionError{border-color:var(--dsw-alias-state-error-primary)}",
|
|
64
|
+
".mkt_decisionHead{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;padding:8px 10px;background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}",
|
|
65
|
+
".mkt_decisionError .mkt_decisionHead{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-state-error-primary)}",
|
|
66
|
+
".mkt_decisionTitle{font-size:13px;font-weight:600}",
|
|
67
|
+
".mkt_decisionBody{display:flex;flex-direction:column;gap:8px;min-width:0;padding:10px;max-height:300px;overflow:auto}",
|
|
68
|
+
".mkt_decisionActions{display:flex;justify-content:flex-end;align-items:center;gap:8px;padding:8px 10px;border-top:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3)}",
|
|
62
69
|
".mkt_approvePkg{display:flex;flex-direction:column;gap:4px}",
|
|
63
70
|
".mkt_approveName{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);margin-right:8px;overflow-wrap:anywhere}",
|
|
64
71
|
".mkt_approveCmd{max-height:none;margin:0}",
|
|
65
72
|
".mkt_jobDone{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:2px}",
|
|
66
|
-
".mkt_logBlock{display:flex;flex-direction:column;gap:4px;align-items:
|
|
73
|
+
".mkt_logBlock{display:flex;flex-direction:column;gap:4px;align-items:stretch;min-width:0;max-width:100%}",
|
|
74
|
+
".mkt_linkButton{align-self:flex-start;border:0;background:none;padding:0;color:var(--dsw-alias-state-business-primary);font:inherit;font-size:12px;cursor:pointer}",
|
|
75
|
+
".mkt_job{display:flex;flex-direction:column;gap:6px;min-width:0;max-width:100%;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l2)}",
|
|
76
|
+
".mkt_panelRow + .mkt_job{padding-top:0;border-top:0}",
|
|
77
|
+
".mkt_jobHead{display:flex;align-items:center;gap:8px;min-width:0}",
|
|
78
|
+
".mkt_jobIdentity{display:flex;align-items:center;gap:7px;min-width:0;font-size:12px;color:var(--dsw-alias-label-tertiary)}",
|
|
79
|
+
".mkt_jobHead .mkt_btn{margin-left:auto;flex:none}",
|
|
80
|
+
".mkt_stateDot{width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-label-caption);flex:none}",
|
|
81
|
+
".mkt_stateRunning{background:var(--dsw-alias-state-business-primary);animation:mktPulse 1.6s ease-in-out infinite}",
|
|
82
|
+
".mkt_stateDone{background:var(--dsw-alias-state-success-primary)}",
|
|
83
|
+
".mkt_stateWarn{background:var(--dsw-alias-state-warn-primary)}",
|
|
84
|
+
".mkt_stateError{background:var(--dsw-alias-state-error-primary)}",
|
|
85
|
+
"@keyframes mktPulse{0%,100%{opacity:.45}50%{opacity:1}}",
|
|
86
|
+
"@media (prefers-reduced-motion:reduce){.mkt_stateRunning{animation:none}.mkt_switch,.mkt_switchKnob{transition:none}}",
|
|
67
87
|
".mkt_depOff{opacity:.5;text-decoration:line-through}",
|
|
68
88
|
".mkt_switch{position:relative;width:34px;height:18px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);cursor:pointer;padding:0;transition:background .15s,border-color .15s}",
|
|
69
89
|
".mkt_switch:disabled{opacity:.55;cursor:default}",
|
|
@@ -71,11 +91,16 @@ window.__ModuleLoader__.load({
|
|
|
71
91
|
".mkt_switchKnob{position:absolute;top:1px;left:1px;width:14px;height:14px;border-radius:50%;background:var(--dsw-alias-label-primary-foreground);transition:transform .15s;box-shadow:var(--dsw-shadow-lv1)}",
|
|
72
92
|
".mkt_switchOn .mkt_switchKnob{transform:translateX(16px)}",
|
|
73
93
|
".mkt_issueList{list-style:none;display:flex;flex-direction:column;gap:8px;margin:0;padding:0}",
|
|
74
|
-
".mkt_issue{border:1px solid var(--dsw-alias-border-l2);border-radius:
|
|
94
|
+
".mkt_issue{border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:8px 10px;background:var(--dsw-alias-bg-layer-3)}",
|
|
75
95
|
".mkt_issueBlock{border-color:var(--dsw-alias-state-error-primary)}",
|
|
76
96
|
".mkt_issueWarn{border-color:var(--dsw-alias-state-warn-primary)}",
|
|
77
97
|
".mkt_issueTitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}",
|
|
78
98
|
".mkt_issueDetail{font-size:12px;color:var(--dsw-alias-label-secondary);margin-top:4px;line-height:18px;overflow-wrap:anywhere}",
|
|
99
|
+
".mkt_modalMask{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:20px;background:var(--dsw-alias-bg-mask-3)}",
|
|
100
|
+
".mkt_modal{width:min(420px,100%);max-height:min(520px,calc(100vh - 40px));display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-3);box-shadow:var(--dsw-shadow-lv3)}",
|
|
101
|
+
".mkt_modalHead{padding:14px 16px 8px;font-size:15px;font-weight:600;color:var(--dsw-alias-label-primary)}",
|
|
102
|
+
".mkt_modalBody{padding:0 16px 14px;font-size:12.5px;line-height:19px;color:var(--dsw-alias-label-secondary);overflow:auto}",
|
|
103
|
+
".mkt_modalActions{display:flex;justify-content:flex-end;gap:8px;padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2)}",
|
|
79
104
|
].join("\n");
|
|
80
105
|
var tagId = "@1e0zj/dsh-plugin-mall/market-tab.css";
|
|
81
106
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
@@ -102,6 +127,10 @@ window.__ModuleLoader__.load({
|
|
|
102
127
|
return String(e && e.message ? e.message : e);
|
|
103
128
|
}
|
|
104
129
|
|
|
130
|
+
function isJobActive(status) {
|
|
131
|
+
return status === "running" || status === "stopping";
|
|
132
|
+
}
|
|
133
|
+
|
|
105
134
|
function kindLabel(kind) {
|
|
106
135
|
if (kind === "bundle") return "宿主插件层";
|
|
107
136
|
if (kind === "client") return "浏览器UI插件";
|
|
@@ -159,7 +188,7 @@ window.__ModuleLoader__.load({
|
|
|
159
188
|
var current = jobsRef.current;
|
|
160
189
|
var running = Object.keys(current).filter(function (id) {
|
|
161
190
|
var job = current[id];
|
|
162
|
-
return job && job.status
|
|
191
|
+
return job && isJobActive(job.status);
|
|
163
192
|
});
|
|
164
193
|
if (running.length === 0) return;
|
|
165
194
|
running.forEach(function (id) {
|
|
@@ -211,21 +240,23 @@ window.__ModuleLoader__.load({
|
|
|
211
240
|
delete next[carryFromId];
|
|
212
241
|
}
|
|
213
242
|
for (var key in next) {
|
|
214
|
-
if (key !== id && next[key] && next[key].spec === spec && next[key].status
|
|
243
|
+
if (key !== id && next[key] && next[key].spec === spec && !isJobActive(next[key].status)) {
|
|
215
244
|
delete next[key];
|
|
216
245
|
}
|
|
217
246
|
}
|
|
218
247
|
next[id] = { status: "running", spec: spec, output: carried };
|
|
219
248
|
commit(next);
|
|
220
249
|
}, [commit]);
|
|
221
|
-
var clear = useCallback(function () {
|
|
222
|
-
commit({});
|
|
223
|
-
}, [commit]);
|
|
224
250
|
var drop = useCallback(function (id) {
|
|
225
251
|
var next = Object.assign({}, jobsRef.current);
|
|
226
252
|
delete next[id];
|
|
227
253
|
commit(next);
|
|
228
254
|
}, [commit]);
|
|
255
|
+
var setStatus = useCallback(function (id, status) {
|
|
256
|
+
var current = jobsRef.current[id];
|
|
257
|
+
if (!current) return;
|
|
258
|
+
commit(Object.assign({}, jobsRef.current, { [id]: Object.assign({}, current, { status: status }) }));
|
|
259
|
+
}, [commit]);
|
|
229
260
|
// 恢复后端任务记录(tracker.list 的形状):安装事务改写
|
|
230
261
|
// cordis.patch.yml 会让 dsh 重放装配树、整个市场 UI 重挂载,React
|
|
231
262
|
// state 全丢——任务面板、完成提醒、重启按钮一起消失(真实事故:
|
|
@@ -269,7 +300,7 @@ window.__ModuleLoader__.load({
|
|
|
269
300
|
for (var key in next) {
|
|
270
301
|
if (serverIds[key]) continue;
|
|
271
302
|
var stale = next[key];
|
|
272
|
-
if (stale.status
|
|
303
|
+
if (isJobActive(stale.status)) {
|
|
273
304
|
next[key] = Object.assign({}, stale, {
|
|
274
305
|
status: "killed",
|
|
275
306
|
detail: "宿主进程已重启,该任务的记录随之丢失",
|
|
@@ -282,7 +313,7 @@ window.__ModuleLoader__.load({
|
|
|
282
313
|
}
|
|
283
314
|
commit(next);
|
|
284
315
|
}, [commit]);
|
|
285
|
-
return { jobs: jobs, track: track,
|
|
316
|
+
return { jobs: jobs, track: track, drop: drop, setStatus: setStatus, restore: restore };
|
|
286
317
|
}
|
|
287
318
|
|
|
288
319
|
// ── plugin verification badge ───────────────────────────────────────────
|
|
@@ -319,7 +350,10 @@ window.__ModuleLoader__.load({
|
|
|
319
350
|
var installLabel = props.alreadyInstalled === true ? "已装" : "安装";
|
|
320
351
|
var installDisabled = installing || props.alreadyInstalled === true;
|
|
321
352
|
if (!installing && job) {
|
|
322
|
-
if (job.status
|
|
353
|
+
if (isJobActive(job.status)) {
|
|
354
|
+
installLabel = job.status === "stopping" ? "停止中…" : "安装中…";
|
|
355
|
+
installDisabled = true;
|
|
356
|
+
}
|
|
323
357
|
else if (job.status === "completed") { installLabel = "已装 · 重启生效"; installDisabled = true; }
|
|
324
358
|
else if (job.status === "failed") { installLabel = "安装失败 · 见任务日志"; }
|
|
325
359
|
else { installLabel = "已取消 · 重试"; }
|
|
@@ -346,11 +380,62 @@ window.__ModuleLoader__.load({
|
|
|
346
380
|
);
|
|
347
381
|
}
|
|
348
382
|
|
|
383
|
+
function DecisionPanel(props) {
|
|
384
|
+
var tone = props.tone === "error" ? " mkt_decisionError" : "";
|
|
385
|
+
return h("div", { className: "mkt_decision" + tone },
|
|
386
|
+
h("div", { className: "mkt_decisionHead" },
|
|
387
|
+
h("span", { className: "mkt_decisionTitle" }, props.title),
|
|
388
|
+
props.meta ? h("span", { className: "mkt_meta" }, props.meta) : null),
|
|
389
|
+
h("div", { className: "mkt_decisionBody" }, props.children),
|
|
390
|
+
h("div", { className: "mkt_decisionActions" }, props.actions));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function ErrorNotice(props) {
|
|
394
|
+
if (!props.message) return null;
|
|
395
|
+
return h("div", { className: "mkt_notice", role: "alert" }, props.message);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function RestartDialog(props) {
|
|
399
|
+
useEffect(function () {
|
|
400
|
+
function onKeyDown(event) {
|
|
401
|
+
if (event.key === "Escape" && props.busy !== true) props.onCancel();
|
|
402
|
+
}
|
|
403
|
+
document.addEventListener("keydown", onKeyDown);
|
|
404
|
+
return function () { document.removeEventListener("keydown", onKeyDown); };
|
|
405
|
+
}, [props.busy, props.onCancel]);
|
|
406
|
+
return h("div", {
|
|
407
|
+
className: "mkt_modalMask",
|
|
408
|
+
onMouseDown: function (event) {
|
|
409
|
+
if (event.target === event.currentTarget && props.busy !== true) props.onCancel();
|
|
410
|
+
},
|
|
411
|
+
}, h("div", { className: "mkt_modal", role: "dialog", "aria-modal": "true", "aria-labelledby": "mkt-restart-title" },
|
|
412
|
+
h("div", { id: "mkt-restart-title", className: "mkt_modalHead" }, "重启 dsh"),
|
|
413
|
+
h("div", { className: "mkt_modalBody" }, props.activeCount > 0
|
|
414
|
+
? "当前有 " + props.activeCount + " 个任务仍在进行。重启会中断这些任务,未完成的改动将由事务恢复逻辑处理。"
|
|
415
|
+
: "重启后,已经完成的插件安装、更新或卸载才会生效。"),
|
|
416
|
+
h("div", { className: "mkt_modalActions" },
|
|
417
|
+
h("button", { className: "mkt_btn", disabled: props.busy === true, autoFocus: true, onClick: props.onCancel }, "取消"),
|
|
418
|
+
h("button", { className: "mkt_btn mkt_btnPrimary", disabled: props.busy === true, onClick: props.onConfirm }, props.busy ? "重启中…" : "确认重启"))));
|
|
419
|
+
}
|
|
420
|
+
|
|
349
421
|
// ── install-script approval ─────────────────────────────────────────────
|
|
350
422
|
function ApprovalRequest(props) {
|
|
351
423
|
var pkgs = props.needsApproval || [];
|
|
352
|
-
|
|
353
|
-
h("
|
|
424
|
+
var actions = [
|
|
425
|
+
h("button", { key: "cancel", className: "mkt_btn mkt_btnSm", disabled: props.busy === true, onClick: props.onDismiss }, "取消"),
|
|
426
|
+
h("button", {
|
|
427
|
+
key: "allow",
|
|
428
|
+
className: "mkt_btn mkt_btnPrimary mkt_btnSm",
|
|
429
|
+
disabled: props.busy === true,
|
|
430
|
+
onClick: function () { props.onApprove(pkgs.map(function (p) { return p.name; })); },
|
|
431
|
+
}, props.busy ? "继续中…" : "允许并继续"),
|
|
432
|
+
];
|
|
433
|
+
return h(DecisionPanel, {
|
|
434
|
+
title: "安装期代码需要确认",
|
|
435
|
+
meta: clip(props.spec || "", 50),
|
|
436
|
+
actions: actions,
|
|
437
|
+
},
|
|
438
|
+
props.detail ? h("div", { className: "mkt_desc" }, props.detail) : null,
|
|
354
439
|
pkgs.map(function (p, index) {
|
|
355
440
|
var facts = [];
|
|
356
441
|
if (typeof p.weeklyDownloads === "number") facts.push("周下载 " + p.weeklyDownloads.toLocaleString());
|
|
@@ -370,16 +455,7 @@ window.__ModuleLoader__.load({
|
|
|
370
455
|
h("div", { className: "mkt_meta" }, facts.join(" · "))
|
|
371
456
|
);
|
|
372
457
|
}),
|
|
373
|
-
h("div", { className: "mkt_meta" }, "这些命令会以你的权限在你的机器上运行,早于任何插件代码加载。")
|
|
374
|
-
h("div", { className: "mkt_row" },
|
|
375
|
-
h("button", {
|
|
376
|
-
className: "mkt_btn mkt_btnPrimary mkt_btnSm",
|
|
377
|
-
disabled: props.busy === true,
|
|
378
|
-
onClick: function () { props.onApprove(pkgs.map(function (p) { return p.name; })); },
|
|
379
|
-
}, props.busy ? "继续中…" : "允许并继续"),
|
|
380
|
-
h("button", { className: "mkt_btn mkt_btnSm", onClick: props.onDismiss }, "取消")
|
|
381
|
-
)
|
|
382
|
-
);
|
|
458
|
+
h("div", { className: "mkt_meta" }, "这些命令会以你的权限在你的机器上运行,早于任何插件代码加载。"));
|
|
383
459
|
}
|
|
384
460
|
|
|
385
461
|
// ── preflight result card ────────────────────────────────────────────────
|
|
@@ -391,11 +467,23 @@ window.__ModuleLoader__.load({
|
|
|
391
467
|
var blocked = report.verdict === "blocked";
|
|
392
468
|
var title = blocked ? "安装被阻止" : "安装存在风险";
|
|
393
469
|
var issues = report.issues || [];
|
|
394
|
-
|
|
395
|
-
h("
|
|
396
|
-
|
|
397
|
-
h("
|
|
398
|
-
|
|
470
|
+
var actions = blocked
|
|
471
|
+
? h("button", { className: "mkt_btn mkt_btnSm", onClick: props.onClose }, "关闭")
|
|
472
|
+
: [
|
|
473
|
+
h("button", { key: "cancel", className: "mkt_btn mkt_btnSm", disabled: props.busy === true, onClick: props.onClose }, "取消"),
|
|
474
|
+
h("button", {
|
|
475
|
+
key: "confirm",
|
|
476
|
+
className: "mkt_btn mkt_btnPrimary mkt_btnSm",
|
|
477
|
+
disabled: props.busy === true,
|
|
478
|
+
onClick: props.onConfirm,
|
|
479
|
+
}, props.busy ? "安装中…" : "了解风险并继续"),
|
|
480
|
+
];
|
|
481
|
+
return h(DecisionPanel, {
|
|
482
|
+
tone: blocked ? "error" : "warning",
|
|
483
|
+
title: title,
|
|
484
|
+
meta: clip(props.spec, 50),
|
|
485
|
+
actions: actions,
|
|
486
|
+
},
|
|
399
487
|
report.summary ? h("div", { className: "mkt_desc" }, report.summary) : null,
|
|
400
488
|
issues.length > 0 ? h("ul", { className: "mkt_issueList" },
|
|
401
489
|
issues.map(function (issue) {
|
|
@@ -406,21 +494,7 @@ window.__ModuleLoader__.load({
|
|
|
406
494
|
h("div", { className: "mkt_issueTitle" }, issue.title),
|
|
407
495
|
issue.detail ? h("div", { className: "mkt_issueDetail" }, issue.detail) : null);
|
|
408
496
|
})
|
|
409
|
-
) : null
|
|
410
|
-
h("div", { className: "mkt_row" },
|
|
411
|
-
blocked
|
|
412
|
-
? h("button", { className: "mkt_btn mkt_btnSm", onClick: props.onClose }, "关闭")
|
|
413
|
-
: [
|
|
414
|
-
h("button", { key: "cancel", className: "mkt_btn mkt_btnSm", onClick: props.onClose }, "取消"),
|
|
415
|
-
h("button", {
|
|
416
|
-
key: "confirm",
|
|
417
|
-
className: "mkt_btn mkt_btnPrimary mkt_btnSm",
|
|
418
|
-
disabled: props.busy === true,
|
|
419
|
-
onClick: props.onConfirm,
|
|
420
|
-
}, props.busy ? "安装中…" : "我已了解风险,继续安装"),
|
|
421
|
-
]
|
|
422
|
-
)
|
|
423
|
-
);
|
|
497
|
+
) : null);
|
|
424
498
|
}
|
|
425
499
|
|
|
426
500
|
var MARKET_PACKAGE = "@1e0zj/dsh-plugin-mall";
|
|
@@ -435,64 +509,99 @@ window.__ModuleLoader__.load({
|
|
|
435
509
|
if (status === "completed") return "完成";
|
|
436
510
|
if (status === "failed") return "失败";
|
|
437
511
|
if (status === "killed") return "已取消";
|
|
512
|
+
if (status === "stopping") return "停止中";
|
|
438
513
|
return "进行中";
|
|
439
514
|
}
|
|
440
515
|
|
|
441
516
|
function jobStatusClass(status) {
|
|
442
517
|
if (status === "completed") return "mkt_ok";
|
|
443
|
-
if (status === "failed"
|
|
518
|
+
if (status === "failed") return "mkt_error";
|
|
519
|
+
if (status === "killed" || status === "stopping") return "mkt_warn";
|
|
444
520
|
return "";
|
|
445
521
|
}
|
|
446
522
|
|
|
523
|
+
function jobStateDotClass(status, awaitingDecision) {
|
|
524
|
+
if (awaitingDecision || status === "killed" || status === "stopping") return "mkt_stateWarn";
|
|
525
|
+
if (status === "completed") return "mkt_stateDone";
|
|
526
|
+
if (status === "failed") return "mkt_stateError";
|
|
527
|
+
return "mkt_stateRunning";
|
|
528
|
+
}
|
|
529
|
+
|
|
447
530
|
// 任务日志:跑的时候只露尾部几行(够看进度,不霸屏),落定后默认折叠
|
|
448
531
|
// ——成功的日志没人回头读,失败的才需要,点开即可。
|
|
449
|
-
var LOG_TAIL_LINES =
|
|
532
|
+
var LOG_TAIL_LINES = 12;
|
|
450
533
|
function JobLog(props) {
|
|
451
534
|
var _open = useState(false);
|
|
452
535
|
var open = _open[0];
|
|
453
536
|
var setOpen = _open[1];
|
|
454
537
|
var text = String(props.output || "").trimEnd();
|
|
538
|
+
var failed = props.status === "failed";
|
|
539
|
+
useEffect(function () {
|
|
540
|
+
if (failed) setOpen(true);
|
|
541
|
+
}, [failed]);
|
|
455
542
|
if (text.length === 0) return null;
|
|
456
543
|
var lines = text.split("\n");
|
|
457
|
-
var
|
|
458
|
-
var
|
|
544
|
+
var settled = !isJobActive(props.status);
|
|
545
|
+
var collapsed = settled && !open;
|
|
546
|
+
var shown = settled || lines.length <= LOG_TAIL_LINES ? lines : lines.slice(-LOG_TAIL_LINES);
|
|
547
|
+
var rendered = shown.join("\n");
|
|
548
|
+
if (rendered.length > 64000) rendered = "…前部日志已截断…\n" + rendered.slice(-64000);
|
|
459
549
|
// 展开/收起始终在日志块上方:放在下方的话,点开后按钮会被推到长长的
|
|
460
550
|
// 日志末尾,想收起还得先滚回去。
|
|
461
551
|
return h("div", { className: "mkt_logBlock" },
|
|
462
|
-
|
|
463
|
-
? h("
|
|
464
|
-
|
|
552
|
+
settled
|
|
553
|
+
? h("button", {
|
|
554
|
+
type: "button",
|
|
555
|
+
className: "mkt_linkButton",
|
|
556
|
+
"aria-expanded": open ? "true" : "false",
|
|
465
557
|
onClick: function () { setOpen(!open); },
|
|
466
558
|
}, open ? "收起日志" : "查看日志(" + lines.length + " 行)")
|
|
467
559
|
: lines.length > LOG_TAIL_LINES
|
|
468
560
|
? h("div", { className: "mkt_meta" }, "只显示最后 " + LOG_TAIL_LINES + " 行")
|
|
469
561
|
: null,
|
|
470
|
-
collapsed ? null : h("pre", { className: "mkt_pre" },
|
|
562
|
+
collapsed ? null : h("pre", { className: "mkt_pre", "aria-label": "任务日志" }, rendered)
|
|
471
563
|
);
|
|
472
564
|
}
|
|
473
565
|
|
|
474
566
|
// ── jobs panel ──────────────────────────────────────────────────────────
|
|
475
567
|
function JobsPanel(props) {
|
|
476
568
|
var ids = Object.keys(props.jobs);
|
|
477
|
-
if (ids.length === 0) return null;
|
|
569
|
+
if (ids.length === 0 && !props.error) return null;
|
|
570
|
+
var hasSettled = ids.some(function (id) {
|
|
571
|
+
var job = props.jobs[id];
|
|
572
|
+
var hasDecision = (props.preflight && props.preflight.jobId === id)
|
|
573
|
+
|| (job.needsApproval && job.needsApproval.length > 0);
|
|
574
|
+
return !isJobActive(job.status) && !hasDecision;
|
|
575
|
+
});
|
|
478
576
|
return h("div", { className: "mkt_card" },
|
|
479
577
|
h("div", { className: "mkt_panelRow" },
|
|
480
578
|
h("p", { className: "mkt_panelTitle" }, "任务"),
|
|
481
|
-
props.onClear ? h("span", { className: "mkt_link", onClick: props.onClear }, "
|
|
579
|
+
props.onClear && hasSettled ? h("span", { className: "mkt_link", onClick: props.onClear }, "清空已结束") : null
|
|
482
580
|
),
|
|
581
|
+
h(ErrorNotice, { message: props.error }),
|
|
483
582
|
ids.map(function (id) {
|
|
484
583
|
var job = props.jobs[id];
|
|
485
584
|
var done = job.status === "completed" || job.status === "failed" || job.status === "killed";
|
|
486
|
-
|
|
585
|
+
var preflightDecision = props.preflight && props.preflight.jobId === id ? props.preflight : null;
|
|
586
|
+
var awaitingDecision = preflightDecision !== null || (job.needsApproval && job.needsApproval.length > 0);
|
|
587
|
+
return h("div", { key: id, className: "mkt_job" },
|
|
487
588
|
// 不再显示 market-N 这种内部 id——对用户没有意义,反而让人以为
|
|
488
589
|
// 自己点了两次。只说清「在做什么 · 到哪一步了」。
|
|
489
|
-
//
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
590
|
+
// 状态词和状态点表达结论,任务类型与目标保持次要色。
|
|
591
|
+
h("div", { className: "mkt_jobHead" },
|
|
592
|
+
h("div", { className: "mkt_jobIdentity" },
|
|
593
|
+
h("span", { className: "mkt_stateDot " + jobStateDotClass(job.status, awaitingDecision), "aria-hidden": "true" }),
|
|
594
|
+
h("span", null, jobKindLabel(job.kind) + clip(job.spec || "", 44) + " · "),
|
|
595
|
+
h("span", { className: awaitingDecision ? "mkt_warn" : jobStatusClass(job.status) }, awaitingDecision ? "待确认" : jobStatusLabel(job.status))),
|
|
596
|
+
isJobActive(job.status) ? h("button", {
|
|
597
|
+
className: "mkt_btn mkt_btnSm",
|
|
598
|
+
disabled: job.status === "stopping",
|
|
599
|
+
onClick: function () { props.onCancel(id); },
|
|
600
|
+
}, job.status === "stopping" ? "停止中…" : "停止") : null),
|
|
494
601
|
job.needsApproval && job.needsApproval.length > 0
|
|
495
602
|
? h(ApprovalRequest, {
|
|
603
|
+
spec: job.spec,
|
|
604
|
+
detail: job.detail,
|
|
496
605
|
needsApproval: job.needsApproval,
|
|
497
606
|
busy: props.approving === job.spec,
|
|
498
607
|
onApprove: function (names) {
|
|
@@ -505,7 +614,15 @@ window.__ModuleLoader__.load({
|
|
|
505
614
|
},
|
|
506
615
|
onDismiss: function () { props.onDismiss(id); },
|
|
507
616
|
})
|
|
508
|
-
:
|
|
617
|
+
: preflightDecision
|
|
618
|
+
? h(PreflightCard, {
|
|
619
|
+
spec: preflightDecision.spec,
|
|
620
|
+
report: preflightDecision.report,
|
|
621
|
+
busy: props.preflightBusy === true,
|
|
622
|
+
onConfirm: props.onConfirmPreflight,
|
|
623
|
+
onClose: props.onClosePreflight,
|
|
624
|
+
})
|
|
625
|
+
: job.detail ? h("div", { className: job.status === "failed" ? "mkt_error" : "mkt_desc" }, job.detail) : null,
|
|
509
626
|
// 预检完成不提示重启——它没有改动任何东西,接下来才是安装。
|
|
510
627
|
// 也不再重复一个绿色「完成」:状态行已经写了「· 完成」。
|
|
511
628
|
done && job.status === "completed" && job.kind !== "dsh-plugin-preflight"
|
|
@@ -520,10 +637,8 @@ window.__ModuleLoader__.load({
|
|
|
520
637
|
disabled: props.restarting === true,
|
|
521
638
|
onClick: props.onRestart,
|
|
522
639
|
}, props.restarting ? "重启中…" : "重启 dsh 生效"))
|
|
523
|
-
:
|
|
524
|
-
|
|
525
|
-
: null,
|
|
526
|
-
h(JobLog, { output: job.output, done: done })
|
|
640
|
+
: null,
|
|
641
|
+
h(JobLog, { output: job.output, status: job.status })
|
|
527
642
|
);
|
|
528
643
|
})
|
|
529
644
|
);
|
|
@@ -548,6 +663,7 @@ window.__ModuleLoader__.load({
|
|
|
548
663
|
h("span", { className: "mkt_badge" }, count + " 个"),
|
|
549
664
|
h("span", { className: "mkt_meta mkt_installedToggle" }, open ? "收起" : "展开")
|
|
550
665
|
),
|
|
666
|
+
h(ErrorNotice, { message: props.error }),
|
|
551
667
|
installed.error
|
|
552
668
|
? h("div", { className: "mkt_error" }, installed.error)
|
|
553
669
|
: !open ? null
|
|
@@ -604,6 +720,9 @@ window.__ModuleLoader__.load({
|
|
|
604
720
|
var _error = useState(null);
|
|
605
721
|
var error = _error[0];
|
|
606
722
|
var setError = _error[1];
|
|
723
|
+
var reportError = useCallback(function (scope, value) {
|
|
724
|
+
setError({ scope: scope, message: errorText(value) });
|
|
725
|
+
}, []);
|
|
607
726
|
var _installed = useState(null);
|
|
608
727
|
var installed = _installed[0];
|
|
609
728
|
var setInstalled = _installed[1];
|
|
@@ -645,6 +764,9 @@ window.__ModuleLoader__.load({
|
|
|
645
764
|
var _restarting = useState(false);
|
|
646
765
|
var restarting = _restarting[0];
|
|
647
766
|
var setRestarting = _restarting[1];
|
|
767
|
+
var _restartConfirm = useState(false);
|
|
768
|
+
var restartConfirm = _restartConfirm[0];
|
|
769
|
+
var setRestartConfirm = _restartConfirm[1];
|
|
648
770
|
// 本次宿主进程的启动时间(jobs 端点带回):完成时间早于它的任务,
|
|
649
771
|
// 其「重启 dsh 生效」按钮已经兑现,改显示「重启已生效」。
|
|
650
772
|
var _hostStartedAt = useState(0);
|
|
@@ -714,11 +836,11 @@ window.__ModuleLoader__.load({
|
|
|
714
836
|
setCompat({});
|
|
715
837
|
verifyPage(value.items);
|
|
716
838
|
}).catch(function (e) {
|
|
717
|
-
|
|
839
|
+
reportError("search", e);
|
|
718
840
|
}).finally(function () {
|
|
719
841
|
setLoading(false);
|
|
720
842
|
});
|
|
721
|
-
}, [call, query, sort, verifyPage]);
|
|
843
|
+
}, [call, query, reportError, sort, verifyPage]);
|
|
722
844
|
|
|
723
845
|
var canLoadMore = results !== null && results.items.length < results.total && !reachedLimit;
|
|
724
846
|
var loadMoreLock = useRef(false);
|
|
@@ -744,13 +866,13 @@ window.__ModuleLoader__.load({
|
|
|
744
866
|
return { total: value.total, items: merged };
|
|
745
867
|
});
|
|
746
868
|
}).catch(function (e) {
|
|
747
|
-
|
|
869
|
+
reportError("search", e);
|
|
748
870
|
setRetryAt(Date.now() + 60000);
|
|
749
871
|
}).finally(function () {
|
|
750
872
|
loadMoreLock.current = false;
|
|
751
873
|
setLoadingMore(false);
|
|
752
874
|
});
|
|
753
|
-
}, [call, canLoadMore, loading, loadingMore, page, query, sort, retryAt, verifyPage]);
|
|
875
|
+
}, [call, canLoadMore, loading, loadingMore, page, query, reportError, sort, retryAt, verifyPage]);
|
|
754
876
|
|
|
755
877
|
useEffect(function () {
|
|
756
878
|
var node = sentinelRef.current;
|
|
@@ -775,7 +897,7 @@ window.__ModuleLoader__.load({
|
|
|
775
897
|
return prev && !prev.error ? Object.assign({}, prev, { entries: value.entries }) : prev;
|
|
776
898
|
});
|
|
777
899
|
}).catch(function (e) {
|
|
778
|
-
|
|
900
|
+
reportError("installed", e);
|
|
779
901
|
}).finally(function () {
|
|
780
902
|
setToggling(function (prev) {
|
|
781
903
|
var next = Object.assign({}, prev);
|
|
@@ -783,7 +905,7 @@ window.__ModuleLoader__.load({
|
|
|
783
905
|
return next;
|
|
784
906
|
});
|
|
785
907
|
});
|
|
786
|
-
}, [call]);
|
|
908
|
+
}, [call, reportError]);
|
|
787
909
|
|
|
788
910
|
var refreshInstalled = useCallback(function () {
|
|
789
911
|
call("installed", {}).then(function (value) {
|
|
@@ -821,8 +943,8 @@ window.__ModuleLoader__.load({
|
|
|
821
943
|
var polling = useJobPolling(call, onJobSettled, onApprovalToken, onPreflightSettled);
|
|
822
944
|
var track = polling.track;
|
|
823
945
|
var jobs = polling.jobs;
|
|
824
|
-
var clearJobs = polling.clear;
|
|
825
946
|
var dropJob = polling.drop;
|
|
947
|
+
var setJobStatus = polling.setStatus;
|
|
826
948
|
|
|
827
949
|
useEffect(function () {
|
|
828
950
|
doSearch();
|
|
@@ -848,7 +970,7 @@ window.__ModuleLoader__.load({
|
|
|
848
970
|
track(value.jobId, spec, carryFromId);
|
|
849
971
|
}).catch(function (e) {
|
|
850
972
|
delete approvalTokensRef.current[spec];
|
|
851
|
-
|
|
973
|
+
reportError("tasks", e);
|
|
852
974
|
}).finally(function () {
|
|
853
975
|
setInstalling(function (prev) {
|
|
854
976
|
var next = Object.assign({}, prev);
|
|
@@ -856,7 +978,7 @@ window.__ModuleLoader__.load({
|
|
|
856
978
|
return next;
|
|
857
979
|
});
|
|
858
980
|
});
|
|
859
|
-
}, [call, track]);
|
|
981
|
+
}, [call, reportError, track]);
|
|
860
982
|
|
|
861
983
|
var doApprove = useCallback(function (spec, names, token, carryFromId) {
|
|
862
984
|
var extra = { allowBuildScripts: names };
|
|
@@ -872,7 +994,7 @@ window.__ModuleLoader__.load({
|
|
|
872
994
|
useEffect(function () {
|
|
873
995
|
preflightHandlerRef.current = function (spec, report, jobId) {
|
|
874
996
|
if (!report || !report.verdict) {
|
|
875
|
-
|
|
997
|
+
reportError("tasks", "预检没有返回结论,请重试");
|
|
876
998
|
return;
|
|
877
999
|
}
|
|
878
1000
|
if (report.verdict === "safe") {
|
|
@@ -882,7 +1004,7 @@ window.__ModuleLoader__.load({
|
|
|
882
1004
|
setPreflight({ spec: spec, report: report, jobId: jobId });
|
|
883
1005
|
}
|
|
884
1006
|
};
|
|
885
|
-
}, [doRawInstall]);
|
|
1007
|
+
}, [doRawInstall, reportError]);
|
|
886
1008
|
|
|
887
1009
|
// 点安装 = 立刻起一个预检 job,面板马上有东西看、日志实时流。
|
|
888
1010
|
// 结论由轮询经 onPreflightSettled 交回上面的 handler,这里不等待。
|
|
@@ -892,18 +1014,15 @@ window.__ModuleLoader__.load({
|
|
|
892
1014
|
call("preflight", { spec: spec }).then(function (value) {
|
|
893
1015
|
track(value.jobId, value.spec || spec);
|
|
894
1016
|
}).catch(function (e) {
|
|
895
|
-
|
|
1017
|
+
reportError("tasks", e);
|
|
896
1018
|
});
|
|
897
|
-
}, [call, track]);
|
|
1019
|
+
}, [call, reportError, track]);
|
|
898
1020
|
|
|
899
1021
|
var doInstall = useCallback(function (repo) {
|
|
900
1022
|
preflightAndInstall("github:" + repo);
|
|
901
1023
|
}, [preflightAndInstall]);
|
|
902
1024
|
|
|
903
1025
|
var doRestart = useCallback(function () {
|
|
904
|
-
if (typeof window !== "undefined" && typeof window.confirm === "function") {
|
|
905
|
-
if (window.confirm("重启 dsh?正在进行的任务会中断。") !== true) return;
|
|
906
|
-
}
|
|
907
1026
|
clearRestartPing();
|
|
908
1027
|
setRestarting(true);
|
|
909
1028
|
setError(null);
|
|
@@ -921,7 +1040,7 @@ window.__ModuleLoader__.load({
|
|
|
921
1040
|
if (tries > 40) {
|
|
922
1041
|
clearRestartPing();
|
|
923
1042
|
setRestarting(false);
|
|
924
|
-
|
|
1043
|
+
reportError("restart", "2 分钟内未检测到 dsh 重启完成,请手动检查 dsh 状态后刷新页面。");
|
|
925
1044
|
return;
|
|
926
1045
|
}
|
|
927
1046
|
// Prefer identity change over wall-clock ordering: NTP may move the
|
|
@@ -944,9 +1063,9 @@ window.__ModuleLoader__.load({
|
|
|
944
1063
|
if (restartControlRef.current.mounted !== true) return;
|
|
945
1064
|
clearRestartPing();
|
|
946
1065
|
setRestarting(false);
|
|
947
|
-
|
|
1066
|
+
reportError("restart", e);
|
|
948
1067
|
});
|
|
949
|
-
}, [call, clearRestartPing, hostStartedAt]);
|
|
1068
|
+
}, [call, clearRestartPing, hostStartedAt, reportError]);
|
|
950
1069
|
|
|
951
1070
|
var doUninstall = useCallback(function (name) {
|
|
952
1071
|
delete approvalTokensRef.current[name];
|
|
@@ -958,7 +1077,7 @@ window.__ModuleLoader__.load({
|
|
|
958
1077
|
call("uninstall", { package: name }).then(function (value) {
|
|
959
1078
|
track(value.jobId, name);
|
|
960
1079
|
}).catch(function (e) {
|
|
961
|
-
|
|
1080
|
+
reportError("installed", e);
|
|
962
1081
|
}).finally(function () {
|
|
963
1082
|
setRemoving(function (prev) {
|
|
964
1083
|
var next = Object.assign({}, prev);
|
|
@@ -966,7 +1085,7 @@ window.__ModuleLoader__.load({
|
|
|
966
1085
|
return next;
|
|
967
1086
|
});
|
|
968
1087
|
});
|
|
969
|
-
}, [call, track]);
|
|
1088
|
+
}, [call, reportError, track]);
|
|
970
1089
|
|
|
971
1090
|
useEffect(function () {
|
|
972
1091
|
refreshInstalled();
|
|
@@ -978,6 +1097,9 @@ window.__ModuleLoader__.load({
|
|
|
978
1097
|
return v !== undefined && (v.kind === "bundle" || v.kind === "client")
|
|
979
1098
|
&& !(v.hostDeps !== undefined && v.hostDeps.length > 0);
|
|
980
1099
|
});
|
|
1100
|
+
var activeJobCount = Object.keys(jobs).filter(function (id) {
|
|
1101
|
+
return jobs[id] && isJobActive(jobs[id].status);
|
|
1102
|
+
}).length;
|
|
981
1103
|
resultsRef.current = results;
|
|
982
1104
|
var verifyPending = results !== null && results.items.some(function (it) { return verified[it.fullName] === undefined; });
|
|
983
1105
|
|
|
@@ -1000,12 +1122,12 @@ window.__ModuleLoader__.load({
|
|
|
1000
1122
|
h("option", { value: "forks" }, "按 fork")
|
|
1001
1123
|
),
|
|
1002
1124
|
h("button", { className: "mkt_btn mkt_btnPrimary", disabled: loading, onClick: doSearch }, loading ? "搜索中…" : "搜索"),
|
|
1003
|
-
h("button", { className: "mkt_btn mkt_btnDanger", disabled: restarting, onClick:
|
|
1125
|
+
h("button", { className: "mkt_btn mkt_btnDanger", disabled: restarting, onClick: function () { setRestartConfirm(true); } }, restarting ? "重启中…" : "重启 dsh"),
|
|
1004
1126
|
h("label", { className: "mkt_check" },
|
|
1005
1127
|
h("input", { type: "checkbox", checked: verifiedOnly, onChange: function (e) { setVerifiedOnly(e.target.checked); } }),
|
|
1006
1128
|
"只看已验证插件")
|
|
1007
1129
|
),
|
|
1008
|
-
|
|
1130
|
+
h(ErrorNotice, { message: error && (error.scope === "search" || error.scope === "restart") ? error.message : null }),
|
|
1009
1131
|
h(InstalledPanel, {
|
|
1010
1132
|
installed: installed,
|
|
1011
1133
|
removing: removing,
|
|
@@ -1015,19 +1137,50 @@ window.__ModuleLoader__.load({
|
|
|
1015
1137
|
onToggle: doToggle,
|
|
1016
1138
|
onUninstall: doUninstall,
|
|
1017
1139
|
onInstallSpec: preflightAndInstall,
|
|
1140
|
+
error: error && error.scope === "installed" ? error.message : null,
|
|
1018
1141
|
}),
|
|
1019
1142
|
h(JobsPanel, {
|
|
1020
1143
|
jobs: jobs,
|
|
1144
|
+
error: error && error.scope === "tasks" ? error.message : null,
|
|
1145
|
+
preflight: preflight,
|
|
1146
|
+
preflightBusy: preflight ? installing[preflight.spec] === true : false,
|
|
1021
1147
|
onClear: function () {
|
|
1022
1148
|
Object.keys(jobs).forEach(function (id) {
|
|
1023
1149
|
var job = jobs[id];
|
|
1150
|
+
var hasDecision = job && ((preflight && preflight.jobId === id)
|
|
1151
|
+
|| (job.needsApproval && job.needsApproval.length > 0));
|
|
1152
|
+
if (!job || isJobActive(job.status) || hasDecision) return;
|
|
1024
1153
|
var token = job && job.approvalToken ? job.approvalToken : (job && job.spec ? approvalTokensRef.current[job.spec] : undefined);
|
|
1025
1154
|
call("jobDismiss", { jobId: id, token: token }).catch(function () {});
|
|
1155
|
+
if (job.spec) delete approvalTokensRef.current[job.spec];
|
|
1156
|
+
dropJob(id);
|
|
1157
|
+
});
|
|
1158
|
+
},
|
|
1159
|
+
onCancel: function (id) {
|
|
1160
|
+
var job = jobs[id];
|
|
1161
|
+
if (!job || job.status !== "running") return;
|
|
1162
|
+
setJobStatus(id, "stopping");
|
|
1163
|
+
call("jobCancel", { jobId: id }).catch(function (e) {
|
|
1164
|
+
setJobStatus(id, "running");
|
|
1165
|
+
reportError("tasks", e);
|
|
1026
1166
|
});
|
|
1027
|
-
approvalTokensRef.current = {};
|
|
1028
|
-
clearJobs();
|
|
1029
1167
|
},
|
|
1030
1168
|
onApprove: doApprove,
|
|
1169
|
+
onConfirmPreflight: function () {
|
|
1170
|
+
if (!preflight) return;
|
|
1171
|
+
var spec = preflight.spec;
|
|
1172
|
+
var carry = preflight.jobId;
|
|
1173
|
+
var report = preflight.report;
|
|
1174
|
+
setPreflight(null);
|
|
1175
|
+
// consentDigest 来自预检 job 的 extras,装的时候与当前报告比对:
|
|
1176
|
+
// 用户点「继续」到安装真正开跑之间候选包或 profile 变了,
|
|
1177
|
+
// 布尔同意不得沿用,要重新看新的警告。
|
|
1178
|
+
doRawInstall(spec, { acceptWarnings: true, acceptedReportDigest: report.consentDigest }, carry);
|
|
1179
|
+
},
|
|
1180
|
+
onClosePreflight: function () {
|
|
1181
|
+
if (preflight && preflight.spec) delete approvalTokensRef.current[preflight.spec];
|
|
1182
|
+
setPreflight(null);
|
|
1183
|
+
},
|
|
1031
1184
|
onDismiss: function (id) {
|
|
1032
1185
|
var job = jobs[id];
|
|
1033
1186
|
var token = job && job.approvalToken ? job.approvalToken : (job && job.spec ? approvalTokensRef.current[job.spec] : undefined);
|
|
@@ -1036,29 +1189,11 @@ window.__ModuleLoader__.load({
|
|
|
1036
1189
|
dropJob(id);
|
|
1037
1190
|
},
|
|
1038
1191
|
onDrop: dropJob,
|
|
1039
|
-
onRestart:
|
|
1192
|
+
onRestart: function () { setRestartConfirm(true); },
|
|
1040
1193
|
restarting: restarting,
|
|
1041
1194
|
hostStartedAt: hostStartedAt,
|
|
1042
1195
|
approving: Object.keys(installing).filter(function (s) { return installing[s]; })[0],
|
|
1043
1196
|
}),
|
|
1044
|
-
preflight ? h(PreflightCard, {
|
|
1045
|
-
spec: preflight.spec,
|
|
1046
|
-
report: preflight.report,
|
|
1047
|
-
busy: installing[preflight.spec] === true,
|
|
1048
|
-
onConfirm: function () {
|
|
1049
|
-
var spec = preflight.spec;
|
|
1050
|
-
var carry = preflight.jobId;
|
|
1051
|
-
setPreflight(null);
|
|
1052
|
-
// consentDigest 来自预检 job 的 extras,装的时候与当前报告比对:
|
|
1053
|
-
// 用户点「继续」到安装真正开跑之间候选包或 profile 变了,
|
|
1054
|
-
// 布尔同意不得沿用,要重新看新的警告。
|
|
1055
|
-
doRawInstall(spec, { acceptWarnings: true, acceptedReportDigest: preflight.report.consentDigest }, carry);
|
|
1056
|
-
},
|
|
1057
|
-
onClose: function () {
|
|
1058
|
-
if (preflight && preflight.spec) delete approvalTokensRef.current[preflight.spec];
|
|
1059
|
-
setPreflight(null);
|
|
1060
|
-
},
|
|
1061
|
-
}) : null,
|
|
1062
1197
|
h("div", { className: "mkt_list" },
|
|
1063
1198
|
results == null
|
|
1064
1199
|
? h("div", { className: "mkt_meta mkt_listHead" }, loading ? "正在加载最热插件…" : "—")
|
|
@@ -1095,7 +1230,16 @@ window.__ModuleLoader__.load({
|
|
|
1095
1230
|
? h("div", { className: "mkt_loadMore", ref: sentinelRef }, loadingMore ? "加载中…" : Date.now() < retryAt ? "GitHub 限流中,稍后再下滑加载" : "下滑加载更多")
|
|
1096
1231
|
: h("div", { className: "mkt_loadMore" }, reachedLimit ? "已达 GitHub 搜索上限(前 1000 个结果)" : "已显示全部 " + results.items.length + " 个")
|
|
1097
1232
|
)
|
|
1098
|
-
)
|
|
1233
|
+
),
|
|
1234
|
+
restartConfirm ? h(RestartDialog, {
|
|
1235
|
+
activeCount: activeJobCount,
|
|
1236
|
+
busy: restarting,
|
|
1237
|
+
onCancel: function () { setRestartConfirm(false); },
|
|
1238
|
+
onConfirm: function () {
|
|
1239
|
+
setRestartConfirm(false);
|
|
1240
|
+
doRestart();
|
|
1241
|
+
},
|
|
1242
|
+
}) : null
|
|
1099
1243
|
);
|
|
1100
1244
|
}
|
|
1101
1245
|
|
package/src/index.js
CHANGED
|
@@ -1084,14 +1084,13 @@ export function createJobTracker({ producerFactory } = {}) {
|
|
|
1084
1084
|
const prune = () => {
|
|
1085
1085
|
const now = Date.now();
|
|
1086
1086
|
for (const [id, record] of records) {
|
|
1087
|
-
|
|
1088
|
-
if (terminal && record.finishedAt !== undefined && now - record.finishedAt > 3600000) records.delete(id);
|
|
1087
|
+
if (record.finishedAt !== undefined && now - record.finishedAt > 3600000) records.delete(id);
|
|
1089
1088
|
}
|
|
1090
1089
|
if (records.size > 20) {
|
|
1091
1090
|
const ordered = [...records.entries()].sort((a, b) => a[1].startedAt - b[1].startedAt);
|
|
1092
1091
|
for (const [id, record] of ordered) {
|
|
1093
1092
|
if (records.size <= 20) break;
|
|
1094
|
-
if (record.
|
|
1093
|
+
if (record.finishedAt !== undefined) records.delete(id);
|
|
1095
1094
|
}
|
|
1096
1095
|
}
|
|
1097
1096
|
};
|
|
@@ -1314,6 +1313,9 @@ export function createJobTracker({ producerFactory } = {}) {
|
|
|
1314
1313
|
}
|
|
1315
1314
|
}
|
|
1316
1315
|
if (typeof record.producer?.cancel === "function") {
|
|
1316
|
+
if (record.finishedAt === undefined && record.status === "running") {
|
|
1317
|
+
record.status = "stopping";
|
|
1318
|
+
}
|
|
1317
1319
|
record.producer.cancel();
|
|
1318
1320
|
}
|
|
1319
1321
|
if (record.approvalToken) {
|
|
@@ -1330,6 +1332,9 @@ export function createJobTracker({ producerFactory } = {}) {
|
|
|
1330
1332
|
if (record.surface === "browser" && (record.session === "" || record.session !== session)) {
|
|
1331
1333
|
return false;
|
|
1332
1334
|
}
|
|
1335
|
+
// Dismiss hides settled history. Live work must go through cancel and
|
|
1336
|
+
// remain observable until its producer reports a terminal outcome.
|
|
1337
|
+
if (record.finishedAt === undefined) return false;
|
|
1333
1338
|
// Marked, not deleted: `list` (panel restore after a remount) skips these,
|
|
1334
1339
|
// so a cleared panel stays cleared across remounts.
|
|
1335
1340
|
record.dismissed = true;
|
|
@@ -3012,6 +3017,40 @@ export async function runSelfTests() {
|
|
|
3012
3017
|
const snapAfterDismiss = sessionTracker.get(sessionJobId, "session-alpha").snapshot;
|
|
3013
3018
|
check("dismiss 后 job snapshot 中 approvalToken 为 undefined", snapAfterDismiss.approvalToken === undefined);
|
|
3014
3019
|
|
|
3020
|
+
// 运行中任务不能靠 dismiss 从观察面消失;cancel 先进入 stopping,等
|
|
3021
|
+
// producer 真正结算后才成为可清理的历史。
|
|
3022
|
+
{
|
|
3023
|
+
let settleLive;
|
|
3024
|
+
let cancelCalls = 0;
|
|
3025
|
+
const liveProducer = {
|
|
3026
|
+
cancel: () => { cancelCalls++; },
|
|
3027
|
+
done: new Promise((resolvePromise) => { settleLive = resolvePromise; }),
|
|
3028
|
+
readOutput: () => "",
|
|
3029
|
+
};
|
|
3030
|
+
const liveTracker = createJobTracker({ producerFactory: () => liveProducer });
|
|
3031
|
+
const liveJobId = liveTracker.start({
|
|
3032
|
+
profile: "web",
|
|
3033
|
+
spec: "live-pkg",
|
|
3034
|
+
surface: "browser",
|
|
3035
|
+
session: "session-alpha",
|
|
3036
|
+
});
|
|
3037
|
+
check("运行中 job 拒绝 dismiss", liveTracker.dismiss(liveJobId, "session-alpha") === false);
|
|
3038
|
+
check("dismiss 被拒后运行中 job 仍可恢复",
|
|
3039
|
+
liveTracker.list("session-alpha").some((entry) => entry.id === liveJobId));
|
|
3040
|
+
check("cancel 请求把可杀 job 标为 stopping",
|
|
3041
|
+
liveTracker.cancel(liveJobId, "session-alpha") === "requested"
|
|
3042
|
+
&& liveTracker.get(liveJobId, "session-alpha").snapshot.status === "stopping"
|
|
3043
|
+
&& cancelCalls === 1);
|
|
3044
|
+
check("stopping job 仍拒绝 dismiss", liveTracker.dismiss(liveJobId, "session-alpha") === false);
|
|
3045
|
+
settleLive({ status: "killed", detail: "cancelled" });
|
|
3046
|
+
await new Promise((resolvePromise) => setImmediate(resolvePromise));
|
|
3047
|
+
check("producer 结算后 job 进入 killed",
|
|
3048
|
+
liveTracker.get(liveJobId, "session-alpha").snapshot.status === "killed");
|
|
3049
|
+
check("终态 job 可 dismiss 并不再恢复",
|
|
3050
|
+
liveTracker.dismiss(liveJobId, "session-alpha") === true
|
|
3051
|
+
&& liveTracker.list("session-alpha").every((entry) => entry.id !== liveJobId));
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3015
3054
|
// ── 5b. list(重挂载恢复):日志累积、dismissed 过滤、session 隔离 ────────
|
|
3016
3055
|
// 安装事务改写 cordis.patch.yml 会让 dsh 重放装配树、市场 UI 整体重挂载,
|
|
3017
3056
|
// 前端 state 全丢。任务记录在后端活着——list 就是恢复通道:drain 过的
|