@huaqiu/dsh-tool-plugin-update 0.6.6 → 0.6.8
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/lib/client.js +1852 -397
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +36 -4
- package/package.json +3 -3
- package/src/client/api.ts +281 -1
- package/src/client/copy.ts +230 -6
- package/src/client/index.ts +881 -12
- package/src/client/update-view.ts +848 -242
- package/src/routes.ts +49 -7
package/lib/client.js
CHANGED
|
@@ -375,6 +375,108 @@ window.__ModuleLoader__.load({
|
|
|
375
375
|
}
|
|
376
376
|
return null;
|
|
377
377
|
}
|
|
378
|
+
/** Phases after which a job never runs again — polling stops and a result view
|
|
379
|
+
* replaces the active-job view. */
|
|
380
|
+
const TERMINAL_PHASES = /* @__PURE__ */ new Set([
|
|
381
|
+
"done",
|
|
382
|
+
"failed",
|
|
383
|
+
"rolled-back",
|
|
384
|
+
"abandoned"
|
|
385
|
+
]);
|
|
386
|
+
const PHASE_VALUES = /* @__PURE__ */ new Set([
|
|
387
|
+
"queued",
|
|
388
|
+
"resolving",
|
|
389
|
+
"downloading",
|
|
390
|
+
"verifying",
|
|
391
|
+
"staging",
|
|
392
|
+
"activating",
|
|
393
|
+
"awaiting-host",
|
|
394
|
+
"restarting",
|
|
395
|
+
"verifying-after-restart",
|
|
396
|
+
"done",
|
|
397
|
+
"failed",
|
|
398
|
+
"rolled-back",
|
|
399
|
+
"abandoned"
|
|
400
|
+
]);
|
|
401
|
+
function asFiniteNumber(value) {
|
|
402
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
403
|
+
}
|
|
404
|
+
/** Defensively normalize one job payload from the wire into {@link UpdateJob}.
|
|
405
|
+
* Unknown/older servers may omit fields; a malformed `phase` falls back to
|
|
406
|
+
* `queued` rather than crashing the view. */
|
|
407
|
+
function normalizeJob(raw) {
|
|
408
|
+
if (raw === null || typeof raw !== "object") return null;
|
|
409
|
+
const r = raw;
|
|
410
|
+
if (typeof r.id !== "string" || r.id.length === 0) return null;
|
|
411
|
+
const phase = typeof r.phase === "string" && PHASE_VALUES.has(r.phase) ? r.phase : "queued";
|
|
412
|
+
const progressRaw = r.progress ?? {};
|
|
413
|
+
const percentRaw = asFiniteNumber(progressRaw.percent);
|
|
414
|
+
const progress = { percent: percentRaw === void 0 ? null : Math.min(100, Math.max(0, percentRaw)) };
|
|
415
|
+
const bytesDone = asFiniteNumber(progressRaw.bytesDone);
|
|
416
|
+
const bytesTotal = asFiniteNumber(progressRaw.bytesTotal);
|
|
417
|
+
const bytesPerSecond = asFiniteNumber(progressRaw.bytesPerSecond);
|
|
418
|
+
const etaSeconds = asFiniteNumber(progressRaw.etaSeconds);
|
|
419
|
+
if (bytesDone !== void 0) progress.bytesDone = bytesDone;
|
|
420
|
+
if (bytesTotal !== void 0) progress.bytesTotal = bytesTotal;
|
|
421
|
+
if (bytesPerSecond !== void 0) progress.bytesPerSecond = bytesPerSecond;
|
|
422
|
+
if (etaSeconds !== void 0) progress.etaSeconds = etaSeconds;
|
|
423
|
+
const job = {
|
|
424
|
+
id: r.id,
|
|
425
|
+
kind: r.kind === "runtime" ? "runtime" : "plugin-set",
|
|
426
|
+
targetVersion: typeof r.targetVersion === "string" ? r.targetVersion : "",
|
|
427
|
+
previousVersion: typeof r.previousVersion === "string" ? r.previousVersion : null,
|
|
428
|
+
startedAt: typeof r.startedAt === "string" ? r.startedAt : "",
|
|
429
|
+
updatedAt: typeof r.updatedAt === "string" ? r.updatedAt : "",
|
|
430
|
+
phase,
|
|
431
|
+
progress
|
|
432
|
+
};
|
|
433
|
+
if (typeof r.message === "string" && r.message.length > 0) job.message = r.message;
|
|
434
|
+
if (typeof r.logHint === "string" && r.logHint.length > 0) job.logHint = r.logHint;
|
|
435
|
+
if (Array.isArray(r.steps)) {
|
|
436
|
+
const steps = [];
|
|
437
|
+
for (const entry of r.steps) {
|
|
438
|
+
if (entry === null || typeof entry !== "object") continue;
|
|
439
|
+
const s = entry;
|
|
440
|
+
if (typeof s.name !== "string") continue;
|
|
441
|
+
const state = s.state === "running" || s.state === "installed" || s.state === "skipped" || s.state === "failed" ? s.state : "pending";
|
|
442
|
+
const step = {
|
|
443
|
+
name: s.name,
|
|
444
|
+
state
|
|
445
|
+
};
|
|
446
|
+
const pct = asFiniteNumber(s.percent);
|
|
447
|
+
if (pct !== void 0) step.percent = pct;
|
|
448
|
+
if (typeof s.message === "string" && s.message.length > 0) step.message = s.message;
|
|
449
|
+
steps.push(step);
|
|
450
|
+
}
|
|
451
|
+
if (steps.length > 0) job.steps = steps;
|
|
452
|
+
}
|
|
453
|
+
if (r.result !== null && typeof r.result === "object") {
|
|
454
|
+
const res = r.result;
|
|
455
|
+
const result = {
|
|
456
|
+
ok: res.ok === true,
|
|
457
|
+
installedVersion: typeof res.installedVersion === "string" ? res.installedVersion : null,
|
|
458
|
+
rolledBack: res.rolledBack === true,
|
|
459
|
+
finishedAt: typeof res.finishedAt === "string" ? res.finishedAt : ""
|
|
460
|
+
};
|
|
461
|
+
if (typeof res.errorCode === "string" && res.errorCode.length > 0) result.errorCode = res.errorCode;
|
|
462
|
+
if (typeof res.message === "string" && res.message.length > 0) result.message = res.message;
|
|
463
|
+
job.result = result;
|
|
464
|
+
}
|
|
465
|
+
return job;
|
|
466
|
+
}
|
|
467
|
+
/** Normalize a `GET /status` payload; an empty/absent body degrades to "no
|
|
468
|
+
* active job" rather than throwing. */
|
|
469
|
+
function normalizeJobStatus(raw) {
|
|
470
|
+
if (raw === null || typeof raw !== "object") return {
|
|
471
|
+
activeJob: null,
|
|
472
|
+
recentJobs: []
|
|
473
|
+
};
|
|
474
|
+
const r = raw;
|
|
475
|
+
return {
|
|
476
|
+
activeJob: normalizeJob(r.activeJob),
|
|
477
|
+
recentJobs: Array.isArray(r.recentJobs) ? r.recentJobs.map(normalizeJob).filter((j) => j !== null) : []
|
|
478
|
+
};
|
|
479
|
+
}
|
|
378
480
|
async function request(path, init) {
|
|
379
481
|
const res = await fetch(`${UPDATE_BASE}${path}`, {
|
|
380
482
|
cache: "no-store",
|
|
@@ -459,87 +561,221 @@ window.__ModuleLoader__.load({
|
|
|
459
561
|
body: JSON.stringify({ url })
|
|
460
562
|
});
|
|
461
563
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
564
|
+
/**
|
|
565
|
+
* GET the durable job status: the currently active job (if any) plus a bounded
|
|
566
|
+
* list of recent jobs. This is what the panel reads BEFORE running discovery
|
|
567
|
+
* on open, so an in-flight install/runtime update is resumed instead of
|
|
568
|
+
* forgotten. An older hq-edge without the status route 404s — callers catch
|
|
569
|
+
* that and degrade to "no active job".
|
|
570
|
+
*/
|
|
571
|
+
function fetchJobStatus() {
|
|
572
|
+
return request("/status").then(normalizeJobStatus);
|
|
573
|
+
}
|
|
574
|
+
//#endregion
|
|
575
|
+
//#region src/client/copy.ts
|
|
576
|
+
/**
|
|
577
|
+
* Localized copy for the plugin-update UI (browser half).
|
|
578
|
+
*
|
|
579
|
+
* zh is the fallback, matching the rest of the Huaqiu plugin family
|
|
580
|
+
* (`<html lang>` drives the resolution via the shared sidebar-entry locale
|
|
581
|
+
* helpers). The wording follows the shell's own plugin manager
|
|
582
|
+
* (`ui-plugin-manager/src/client/locales.ts`) so both read as one product.
|
|
583
|
+
*
|
|
584
|
+
* @module
|
|
585
|
+
*/
|
|
586
|
+
const zh = {
|
|
587
|
+
entryLabel: "插件更新",
|
|
588
|
+
entryTooltip: "查看可用的插件更新",
|
|
589
|
+
viewTitle: "插件更新",
|
|
590
|
+
viewIntro: "检查并安装可用的插件更新",
|
|
591
|
+
viewInfo: "在这里查看插件集的更新。标注为「被阻止」的插件因主机或 HQ Edge 版本不兼容而无法安装;已是最新的插件无需操作。",
|
|
592
|
+
refresh: "刷新",
|
|
593
|
+
close: "关闭",
|
|
594
|
+
loading: "正在读取插件更新…",
|
|
595
|
+
empty: "没有可用的插件更新",
|
|
596
|
+
checkFailed: "检查更新失败,请重试",
|
|
597
|
+
installAll: (count) => `安装整套更新(${count} 项)`,
|
|
598
|
+
installOne: "安装整套",
|
|
599
|
+
installOneTip: "将安装整套 N 项更新并自动重启 DSH,而不仅是此插件",
|
|
600
|
+
installing: "正在安装…(DSH 将自动重启)",
|
|
601
|
+
retry: "重试",
|
|
602
|
+
checkedAt: (when) => `上次检查:${when}`,
|
|
603
|
+
checkFailedBanner: (message) => `检查更新失败:${message}`,
|
|
604
|
+
staleNote: "以下为上次成功检查的结果,可能已过期",
|
|
605
|
+
nothingToInstall: "暂无可安装的更新",
|
|
606
|
+
ignore: "忽略此版本",
|
|
607
|
+
unignore: "恢复提醒",
|
|
608
|
+
ignoredOffer: "已忽略当前插件集更新",
|
|
609
|
+
groupCount: (count) => `${count} 个插件`,
|
|
610
|
+
group: {
|
|
611
|
+
actionable: "待处理",
|
|
612
|
+
blocked: "无法安装",
|
|
613
|
+
ignored: "已忽略",
|
|
614
|
+
current: "已是最新"
|
|
615
|
+
},
|
|
616
|
+
state: {
|
|
617
|
+
current: "当前",
|
|
618
|
+
update: "可更新",
|
|
619
|
+
install: "待安装",
|
|
620
|
+
ignored: "已忽略",
|
|
621
|
+
blocked: "被阻止"
|
|
622
|
+
},
|
|
623
|
+
blockedReason: {
|
|
624
|
+
host: "不适用于当前 EDA 主机",
|
|
625
|
+
"hq-edge-version": "当前 HQ Edge 版本不兼容",
|
|
626
|
+
"runtime-unknown": "HQ Edge 版本未知,无法校验"
|
|
627
|
+
},
|
|
628
|
+
runtime: {
|
|
629
|
+
title: "HQ Edge 运行时",
|
|
630
|
+
current: "当前版本",
|
|
631
|
+
latest: "最新版本",
|
|
632
|
+
set: "发布插件集",
|
|
633
|
+
checkedAt: "上次检查",
|
|
634
|
+
source: "更新源",
|
|
635
|
+
unknown: "版本未知",
|
|
636
|
+
notConfigured: "未配置更新源",
|
|
637
|
+
note: "运行时更新由 EDA 安装器处理",
|
|
638
|
+
sizeLabel: "包大小",
|
|
639
|
+
update: "安装运行时更新",
|
|
640
|
+
updateConfirm: (version) => `安装 HQ Edge 运行时 ${version}?HQ Edge 会先关闭并完成替换,然后重新启动。`,
|
|
641
|
+
updating: "正在下载并校验运行时包…",
|
|
642
|
+
durationNote: "耗时取决于下载速度与包体大小;运行时包较大,替换后 HQ Edge 会自动重启。",
|
|
643
|
+
updateAccepted: "运行时更新已受理,HQ Edge 正在重启…",
|
|
644
|
+
size: (bytes) => `约 ${formatMb(bytes)}`,
|
|
645
|
+
lastResult: {
|
|
646
|
+
title: "上次运行时更新结果",
|
|
647
|
+
succeeded: "运行时更新成功",
|
|
648
|
+
failed: "运行时更新失败",
|
|
649
|
+
rolledBack: "运行时更新已回滚(已恢复旧版本)",
|
|
650
|
+
elevation: "系统级安装可能弹出 UAC 提权窗口;拒绝提权会导致替换失败并自动回滚到旧版本。"
|
|
522
651
|
},
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
652
|
+
sourceHint: "运行时更新清单的下载地址,保存后立即生效",
|
|
653
|
+
sourceEdit: "修改",
|
|
654
|
+
sourceSave: "保存",
|
|
655
|
+
sourceReset: "恢复默认",
|
|
656
|
+
saved: "更新源已保存,正在重新检查更新…",
|
|
657
|
+
saveFailed: "保存更新源失败",
|
|
658
|
+
sourceInputLabel: "运行时更新清单地址",
|
|
659
|
+
sourcePlaceholder: "https://gitee.com/huaqiu-electronics/dsh-pcb-eda/raw/main/edge-headless-release-manifest.json"
|
|
660
|
+
},
|
|
661
|
+
pluginSource: {
|
|
662
|
+
title: "插件更新源",
|
|
663
|
+
hint: "插件更新清单的下载地址,保存后立即生效",
|
|
664
|
+
edit: "修改",
|
|
665
|
+
save: "保存",
|
|
666
|
+
reset: "恢复默认",
|
|
667
|
+
saved: "更新源已保存,正在重新检查更新…",
|
|
668
|
+
saveFailed: "保存更新源失败",
|
|
669
|
+
inputLabel: "插件更新清单地址",
|
|
670
|
+
placeholder: "https://gitee.com/huaqiu-electronics/dsh-pcb-eda/raw/main/plugins-release-manifest.json"
|
|
671
|
+
},
|
|
672
|
+
originOverride: "自定义",
|
|
673
|
+
originConfig: "默认配置",
|
|
674
|
+
originUnset: "未配置",
|
|
675
|
+
lastError: "上次检查失败",
|
|
676
|
+
installError: "安装失败",
|
|
677
|
+
installSuccess: "更新已安装,正在重启 DSH…",
|
|
678
|
+
confirmInstall: "确认安装",
|
|
679
|
+
cancel: "取消",
|
|
680
|
+
job: {
|
|
681
|
+
kindPluginSet: "插件集更新",
|
|
682
|
+
kindRuntime: "HQ Edge 运行时更新",
|
|
683
|
+
phase: {
|
|
684
|
+
queued: "排队中",
|
|
685
|
+
resolving: "解析版本",
|
|
686
|
+
downloading: "下载中",
|
|
687
|
+
verifying: "校验完整性",
|
|
688
|
+
staging: "暂存文件",
|
|
689
|
+
activating: "启用插件集",
|
|
690
|
+
"awaiting-host": "等待主机替换运行时",
|
|
691
|
+
restarting: "正在重启",
|
|
692
|
+
"verifying-after-restart": "重启后校验",
|
|
693
|
+
done: "已完成",
|
|
694
|
+
failed: "失败",
|
|
695
|
+
"rolled-back": "已回滚",
|
|
696
|
+
abandoned: "已中断"
|
|
533
697
|
},
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
698
|
+
willRestartDsh: "完成后 DSH 将自动重启,更新本身会在后台继续。",
|
|
699
|
+
willSwapRuntime: "HQ Edge 会下载完整运行时包并退出,由 EDA 主机替换后重启;系统级安装可能弹出 UAC 提权窗口。",
|
|
700
|
+
closeNote: "关闭此面板不会取消更新。",
|
|
701
|
+
bytes: (done, total) => `${done} / ${total}`,
|
|
702
|
+
speed: (v) => `${v}/s`,
|
|
703
|
+
eta: (s) => `剩余约 ${s}`,
|
|
704
|
+
nextDone: "正在完成,请稍候…",
|
|
705
|
+
resultDone: "更新已完成",
|
|
706
|
+
resultFailed: "更新失败",
|
|
707
|
+
resultRolledBack: "更新已回滚",
|
|
708
|
+
resultAbandoned: "更新未完成",
|
|
709
|
+
retry: "重新尝试(发起新的更新)",
|
|
710
|
+
recentTitle: "最近更新",
|
|
711
|
+
stepsTitle: "各组件进度",
|
|
712
|
+
logHint: (hint) => `日志标识:${hint}`
|
|
713
|
+
},
|
|
714
|
+
release: {
|
|
715
|
+
summaryTitle: "版本说明",
|
|
716
|
+
notesLink: "查看发布说明",
|
|
717
|
+
breakingTitle: "破坏性变更",
|
|
718
|
+
changesTitle: "更新内容",
|
|
719
|
+
advanced: "高级:更新源",
|
|
720
|
+
publishedAt: (when) => `发布于 ${when}`
|
|
542
721
|
},
|
|
722
|
+
ui: {
|
|
723
|
+
title: "更新中心",
|
|
724
|
+
gearTooltip: "高级设置",
|
|
725
|
+
settingsTitle: "更新源配置 (高级)",
|
|
726
|
+
originDefault: "默认源",
|
|
727
|
+
originCustom: "自定义源",
|
|
728
|
+
pluginUrlLabel: "插件 Manifest URL",
|
|
729
|
+
runtimeUrlLabel: "运行时 Manifest URL",
|
|
730
|
+
saveAndRefresh: "保存并刷新",
|
|
731
|
+
checkedAgo: (ago) => `上次检查: ${ago}`,
|
|
732
|
+
checkedUnknown: "尚未检查更新",
|
|
733
|
+
agoJust: "刚刚",
|
|
734
|
+
agoMinutes: (n) => `${n} 分钟前`,
|
|
735
|
+
agoHours: (n) => `${n} 小时前`,
|
|
736
|
+
agoDays: (n) => `${n} 天前`,
|
|
737
|
+
jobPluginTitle: (v) => `正在升级全套插件 (v${v})`,
|
|
738
|
+
jobRuntimeTitle: (v) => `正在升级 HQ Edge 运行时 (v${v})`,
|
|
739
|
+
jobSteps: [
|
|
740
|
+
"下载",
|
|
741
|
+
"校验",
|
|
742
|
+
"替换",
|
|
743
|
+
"重启"
|
|
744
|
+
],
|
|
745
|
+
rollbackTitle: "上次 HQ Edge 运行时更新已自动回滚",
|
|
746
|
+
failedTitle: "上次 HQ Edge 运行时更新失败",
|
|
747
|
+
rollbackBody: (oldVer, code) => `系统已安全恢复至旧版本 (v${oldVer})。代码: ${code}`,
|
|
748
|
+
failedBody: (oldVer, code) => `未能完成运行时替换 (旧版本 v${oldVer})。代码: ${code}`,
|
|
749
|
+
dismiss: "忽略",
|
|
750
|
+
successInline: "上次运行时更新成功",
|
|
751
|
+
runtimeEngine: "HQ Edge 核心运行时",
|
|
752
|
+
currentChip: (v) => `v${v} (当前)`,
|
|
753
|
+
newVersion: (v) => `新版本 v${v} 可用`,
|
|
754
|
+
breakingTag: "关键修复",
|
|
755
|
+
sizeLine: (size, summary) => `体积: ${size}${summary ? ` · ${summary}` : ""}`,
|
|
756
|
+
upgradeCore: "升级核心",
|
|
757
|
+
runtimeRestartNote: "升级运行时需自动重启 KiCad 进程服务",
|
|
758
|
+
pendingTitle: "待更新插件",
|
|
759
|
+
allChangelog: "展开全部更新日志",
|
|
760
|
+
changelogBtn: "日志",
|
|
761
|
+
changelogTitle: "更新内容:",
|
|
762
|
+
blockedBadge: "无法安装",
|
|
763
|
+
footerCount: (n) => `准备安装 ${n} 项兼容插件`,
|
|
764
|
+
footerSet: (v) => `版本集: v${v}`,
|
|
765
|
+
installAllSet: (v) => `一键更新全套插件 (v${v})`,
|
|
766
|
+
ignoreBtn: "忽略",
|
|
767
|
+
footnote: "更新将自动应用并重启 DSH 视图,不影响 EDA 图纸数据"
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
/** Compact a byte count for display: "约 12.3 MB" / "约 480 MB". */
|
|
771
|
+
function formatMb(bytes) {
|
|
772
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return "";
|
|
773
|
+
const mb = bytes / 1048576;
|
|
774
|
+
if (mb >= 100) return `${Math.round(mb)} MB`;
|
|
775
|
+
return `${mb.toFixed(1)} MB`;
|
|
776
|
+
}
|
|
777
|
+
const COPY = {
|
|
778
|
+
zh,
|
|
543
779
|
en: {
|
|
544
780
|
entryLabel: "Plugin Updates",
|
|
545
781
|
entryTooltip: "Review available plugin updates",
|
|
@@ -551,9 +787,14 @@ window.__ModuleLoader__.load({
|
|
|
551
787
|
loading: "Reading plugin updates…",
|
|
552
788
|
empty: "No plugin updates available",
|
|
553
789
|
checkFailed: "Failed to check for updates — please retry",
|
|
554
|
-
installAll: (count) => `Install
|
|
555
|
-
installOne: "Install",
|
|
790
|
+
installAll: (count) => `Install whole set (${count})`,
|
|
791
|
+
installOne: "Install set",
|
|
792
|
+
installOneTip: "Installs the whole set of N updates and restarts DSH — not just this plugin",
|
|
556
793
|
installing: "Installing… (DSH will restart)",
|
|
794
|
+
retry: "Retry",
|
|
795
|
+
checkedAt: (when) => `Last checked: ${when}`,
|
|
796
|
+
checkFailedBanner: (message) => `Update check failed: ${message}`,
|
|
797
|
+
staleNote: "Showing the last successful check — it may be stale",
|
|
557
798
|
nothingToInstall: "Nothing to install right now",
|
|
558
799
|
ignore: "Ignore this release",
|
|
559
800
|
unignore: "Show again",
|
|
@@ -587,10 +828,20 @@ window.__ModuleLoader__.load({
|
|
|
587
828
|
unknown: "Version unknown",
|
|
588
829
|
notConfigured: "No update source configured",
|
|
589
830
|
note: "Runtime updates are handled by the EDA installer",
|
|
831
|
+
sizeLabel: "Package size",
|
|
590
832
|
update: "Install runtime update",
|
|
591
833
|
updateConfirm: (version) => `Install HQ Edge runtime ${version}? HQ Edge shuts down to swap the runtime, then restarts.`,
|
|
592
|
-
updating: "Downloading and verifying the runtime bundle
|
|
834
|
+
updating: "Downloading and verifying the runtime bundle…",
|
|
835
|
+
durationNote: "Duration depends on download speed and package size; the runtime bundle is large, and HQ Edge restarts after the swap.",
|
|
593
836
|
updateAccepted: "Runtime update accepted — HQ Edge is restarting…",
|
|
837
|
+
size: (bytes) => `~ ${formatMb(bytes)}`,
|
|
838
|
+
lastResult: {
|
|
839
|
+
title: "Last runtime update result",
|
|
840
|
+
succeeded: "Runtime update succeeded",
|
|
841
|
+
failed: "Runtime update failed",
|
|
842
|
+
rolledBack: "Runtime update rolled back (previous version restored)",
|
|
843
|
+
elevation: "A system-wide install may show a UAC elevation prompt; declining it fails the swap and rolls back to the previous version."
|
|
844
|
+
},
|
|
594
845
|
sourceHint: "Where the runtime update manifest is fetched from; saved changes take effect immediately",
|
|
595
846
|
sourceEdit: "Edit",
|
|
596
847
|
sourceSave: "Save",
|
|
@@ -618,7 +869,96 @@ window.__ModuleLoader__.load({
|
|
|
618
869
|
installError: "Install failed",
|
|
619
870
|
installSuccess: "Update installed — restarting DSH…",
|
|
620
871
|
confirmInstall: "Confirm install",
|
|
621
|
-
cancel: "Cancel"
|
|
872
|
+
cancel: "Cancel",
|
|
873
|
+
job: {
|
|
874
|
+
kindPluginSet: "Plugin-set update",
|
|
875
|
+
kindRuntime: "HQ Edge runtime update",
|
|
876
|
+
phase: {
|
|
877
|
+
queued: "Queued",
|
|
878
|
+
resolving: "Resolving version",
|
|
879
|
+
downloading: "Downloading",
|
|
880
|
+
verifying: "Verifying integrity",
|
|
881
|
+
staging: "Staging files",
|
|
882
|
+
activating: "Activating plugin set",
|
|
883
|
+
"awaiting-host": "Waiting for host to swap runtime",
|
|
884
|
+
restarting: "Restarting",
|
|
885
|
+
"verifying-after-restart": "Verifying after restart",
|
|
886
|
+
done: "Done",
|
|
887
|
+
failed: "Failed",
|
|
888
|
+
"rolled-back": "Rolled back",
|
|
889
|
+
abandoned: "Abandoned"
|
|
890
|
+
},
|
|
891
|
+
willRestartDsh: "DSH restarts automatically once this finishes; the update continues in the background.",
|
|
892
|
+
willSwapRuntime: "HQ Edge downloads the full runtime bundle and exits; the EDA host swaps and relaunches it. A system-wide install may show a UAC prompt.",
|
|
893
|
+
closeNote: "Closing this panel does not cancel the update.",
|
|
894
|
+
bytes: (done, total) => `${done} / ${total}`,
|
|
895
|
+
speed: (v) => `${v}/s`,
|
|
896
|
+
eta: (s) => `~${s} left`,
|
|
897
|
+
nextDone: "Finishing — please wait…",
|
|
898
|
+
resultDone: "Update completed",
|
|
899
|
+
resultFailed: "Update failed",
|
|
900
|
+
resultRolledBack: "Update rolled back",
|
|
901
|
+
resultAbandoned: "Update did not complete",
|
|
902
|
+
retry: "Retry (start a new update)",
|
|
903
|
+
recentTitle: "Recent updates",
|
|
904
|
+
stepsTitle: "Per-component progress",
|
|
905
|
+
logHint: (hint) => `Log id: ${hint}`
|
|
906
|
+
},
|
|
907
|
+
release: {
|
|
908
|
+
summaryTitle: "Release notes",
|
|
909
|
+
notesLink: "View release notes",
|
|
910
|
+
breakingTitle: "Breaking changes",
|
|
911
|
+
changesTitle: "What changed",
|
|
912
|
+
advanced: "Advanced: update sources",
|
|
913
|
+
publishedAt: (when) => `Published ${when}`
|
|
914
|
+
},
|
|
915
|
+
ui: {
|
|
916
|
+
title: "Update Center",
|
|
917
|
+
gearTooltip: "Advanced settings",
|
|
918
|
+
settingsTitle: "Update source (advanced)",
|
|
919
|
+
originDefault: "Default source",
|
|
920
|
+
originCustom: "Custom source",
|
|
921
|
+
pluginUrlLabel: "Plugin manifest URL",
|
|
922
|
+
runtimeUrlLabel: "Runtime manifest URL",
|
|
923
|
+
saveAndRefresh: "Save & refresh",
|
|
924
|
+
checkedAgo: (ago) => `Last checked: ${ago}`,
|
|
925
|
+
checkedUnknown: "No check yet",
|
|
926
|
+
agoJust: "just now",
|
|
927
|
+
agoMinutes: (n) => `${n} min ago`,
|
|
928
|
+
agoHours: (n) => `${n} h ago`,
|
|
929
|
+
agoDays: (n) => `${n} d ago`,
|
|
930
|
+
jobPluginTitle: (v) => `Updating plugin set (v${v})`,
|
|
931
|
+
jobRuntimeTitle: (v) => `Upgrading HQ Edge runtime (v${v})`,
|
|
932
|
+
jobSteps: [
|
|
933
|
+
"Download",
|
|
934
|
+
"Verify",
|
|
935
|
+
"Swap",
|
|
936
|
+
"Restart"
|
|
937
|
+
],
|
|
938
|
+
rollbackTitle: "Last HQ Edge runtime update rolled back automatically",
|
|
939
|
+
failedTitle: "Last HQ Edge runtime update failed",
|
|
940
|
+
rollbackBody: (oldVer, code) => `Safely restored to the previous version (v${oldVer}). Code: ${code}`,
|
|
941
|
+
failedBody: (oldVer, code) => `Runtime swap did not complete (old v${oldVer}). Code: ${code}`,
|
|
942
|
+
dismiss: "Dismiss",
|
|
943
|
+
successInline: "Last runtime update succeeded",
|
|
944
|
+
runtimeEngine: "HQ Core Runtime",
|
|
945
|
+
currentChip: (v) => `v${v} (current)`,
|
|
946
|
+
newVersion: (v) => `New v${v} available`,
|
|
947
|
+
breakingTag: "Critical fix",
|
|
948
|
+
sizeLine: (size, summary) => `Size: ${size}${summary ? ` · ${summary}` : ""}`,
|
|
949
|
+
upgradeCore: "Upgrade core",
|
|
950
|
+
runtimeRestartNote: "Upgrading the runtime restarts the KiCad process service",
|
|
951
|
+
pendingTitle: "Plugins to update",
|
|
952
|
+
allChangelog: "Expand all changelogs",
|
|
953
|
+
changelogBtn: "Log",
|
|
954
|
+
changelogTitle: "What changed:",
|
|
955
|
+
blockedBadge: "Cannot install",
|
|
956
|
+
footerCount: (n) => `Ready to install ${n} compatible plugin${n === 1 ? "" : "s"}`,
|
|
957
|
+
footerSet: (v) => `Set: v${v}`,
|
|
958
|
+
installAllSet: (v) => `Update all plugins (v${v})`,
|
|
959
|
+
ignoreBtn: "Ignore",
|
|
960
|
+
footnote: "Updates apply automatically and restart the DSH view — your EDA schematic data is untouched"
|
|
961
|
+
}
|
|
622
962
|
}
|
|
623
963
|
};
|
|
624
964
|
//#endregion
|
|
@@ -672,18 +1012,6 @@ window.__ModuleLoader__.load({
|
|
|
672
1012
|
]);
|
|
673
1013
|
/** The settled-state glyph (`IconCheckCircleFillRegular`). */
|
|
674
1014
|
const CHECK_CIRCLE_ICON = (size = 16) => fillGlyph(size, ["M28.1936 14.6936L19.8066 23.0806C19.2373 23.65 18.7159 24.1742 18.24 24.5571C17.7389 24.9602 17.1365 25.3359 16.3657 25.458C15.9581 25.5225 15.5428 25.5225 15.1353 25.458C14.3645 25.3359 13.7621 24.9602 13.261 24.5571C12.7851 24.1742 12.2637 23.65 11.6943 23.0806L7.80737 19.1936L10.1936 16.8074L14.0806 20.6943C14.7033 21.317 15.0763 21.6873 15.377 21.9292C15.6523 22.1507 15.7109 22.1325 15.6626 22.1248C15.7208 22.1339 15.7802 22.1339 15.8384 22.1248C15.7901 22.1325 15.8486 22.1507 16.124 21.9292C16.4247 21.6873 16.7977 21.317 17.4204 20.6943L25.8074 12.3074L28.1936 14.6936Z", "M32.8496 18.0005C32.8496 9.79906 26.2019 3.15137 18.0005 3.15137C9.79906 3.15137 3.15137 9.79906 3.15137 18.0005C3.15137 26.2019 9.79906 32.8496 18.0005 32.8496C26.2019 32.8496 32.8496 26.2019 32.8496 18.0005ZM35.7764 18.0005C35.7764 27.8173 27.8173 35.7764 18.0005 35.7764C8.18363 35.7764 0.224609 27.8173 0.224609 18.0005C0.224609 8.18363 8.18363 0.224609 18.0005 0.224609C27.8173 0.224609 35.7764 8.18363 35.7764 18.0005Z"], "0 0 36 36");
|
|
675
|
-
/** The install action (`IconDownloadOutlineRegular`). */
|
|
676
|
-
const DOWNLOAD_ICON = (size = 16) => glyph(size, REGULAR, [
|
|
677
|
-
"M8 1.95317V10.0469",
|
|
678
|
-
"M4.25 6.29688L8 10.0469L11.75 6.29688",
|
|
679
|
-
"M1.5 10.0469V13.158C1.5 13.3937 1.60536 13.6198 1.79289 13.7865C1.98043 13.9532 2.23478 14.0469 2.5 14.0469H13.5C13.7652 14.0469 14.0196 13.9532 14.2071 13.7865C14.3946 13.6198 14.5 13.3937 14.5 13.158V10.0469"
|
|
680
|
-
]);
|
|
681
|
-
/** The head's info affordance (`IconInfoOutlineRegular`, fill-only artwork). */
|
|
682
|
-
const INFO_ICON = (size = 11) => fillGlyph(size, [
|
|
683
|
-
"M12.5757 7.00012C12.5757 3.92085 10.0794 1.42463 7.00012 1.42456C3.9208 1.42456 1.42456 3.9208 1.42456 7.00012C1.42463 10.0794 3.92085 12.5757 7.00012 12.5757C10.0793 12.5756 12.5756 10.0793 12.5757 7.00012ZM13.8002 7.00012C13.8001 10.7559 10.7559 13.8001 7.00012 13.8002C3.2443 13.8002 0.199291 10.7559 0.199219 7.00012C0.199219 3.24426 3.24426 0.199219 7.00012 0.199219C10.7559 0.199291 13.8002 3.2443 13.8002 7.00012Z",
|
|
684
|
-
"M7.6127 3.18921V4.55986H6.38735V3.18921H7.6127Z",
|
|
685
|
-
"M7.6127 5.68921V10.8109H6.38735V5.68921H7.6127Z"
|
|
686
|
-
], "0 0 14 14");
|
|
687
1015
|
//#endregion
|
|
688
1016
|
//#region src/client/update-view.ts
|
|
689
1017
|
/**
|
|
@@ -737,33 +1065,6 @@ window.__ModuleLoader__.load({
|
|
|
737
1065
|
if (Array.isArray(by)) return by.filter((entry) => typeof entry === "string");
|
|
738
1066
|
return typeof by === "string" && by.length > 0 ? [by] : [];
|
|
739
1067
|
}
|
|
740
|
-
/** The tag tone a state reads as, matching the shell's Tag palette. */
|
|
741
|
-
function stateTone(item) {
|
|
742
|
-
switch (item.state) {
|
|
743
|
-
case "update": return "info";
|
|
744
|
-
case "install": return "warning";
|
|
745
|
-
case "blocked": return "danger";
|
|
746
|
-
case "ignored": return "neutral";
|
|
747
|
-
case "current": return "success";
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
function stateLabel(item, copy) {
|
|
751
|
-
switch (item.state) {
|
|
752
|
-
case "current": return copy.state.current;
|
|
753
|
-
case "update": return copy.state.update;
|
|
754
|
-
case "install": return copy.state.install;
|
|
755
|
-
case "ignored": return copy.state.ignored;
|
|
756
|
-
case "blocked": return copy.state.blocked;
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
/** The one-line version transition under a card's title. */
|
|
760
|
-
function versionLine(item) {
|
|
761
|
-
switch (item.state) {
|
|
762
|
-
case "current": return item.currentVersion ?? item.availableVersion;
|
|
763
|
-
case "install": return item.availableVersion;
|
|
764
|
-
default: return item.currentVersion === null ? item.availableVersion : `${item.currentVersion} → ${item.availableVersion}`;
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
1068
|
function blockedReason(item, copy) {
|
|
768
1069
|
const by = blockedByList(item);
|
|
769
1070
|
if (by.includes("host")) return copy.blockedReason.host;
|
|
@@ -771,55 +1072,93 @@ window.__ModuleLoader__.load({
|
|
|
771
1072
|
if (by.includes("runtime-unknown")) return copy.blockedReason["runtime-unknown"];
|
|
772
1073
|
return by.length > 0 ? by.join(", ") : void 0;
|
|
773
1074
|
}
|
|
774
|
-
/**
|
|
775
|
-
function
|
|
776
|
-
const
|
|
1075
|
+
/** Two-letter avatar initials for a card (CJK-safe). */
|
|
1076
|
+
function avatarInitials(item) {
|
|
1077
|
+
const src = typeof item.displayName === "string" && item.displayName.trim().length > 0 ? item.displayName.trim() : shortName(item.name);
|
|
1078
|
+
if (/[⺀-鿿]/.test(src)) return src.slice(0, 2);
|
|
1079
|
+
const words = src.split(/[\s_\-./@]+/).filter(Boolean);
|
|
1080
|
+
if (words.length >= 2) return (words[0][0] + words[1][0]).toUpperCase();
|
|
1081
|
+
return src.replace(/^@/, "").slice(0, 2).toUpperCase();
|
|
1082
|
+
}
|
|
1083
|
+
/** Shared per-card changelog toggle drawer (the per-plugin changes[] list). */
|
|
1084
|
+
function changelogDrawer(item, copy, open) {
|
|
1085
|
+
const drawer = el("div", "hqpu-changelog-drawer");
|
|
1086
|
+
drawer.style.display = open ? "" : "none";
|
|
1087
|
+
drawer.append(el("div", "hqpu-changelog-drawer-title", copy.ui.changelogTitle));
|
|
1088
|
+
const ul = el("ul");
|
|
1089
|
+
for (const change of item.changes ?? []) if (typeof change === "string" && change.length > 0) ul.append(el("li", "", change));
|
|
1090
|
+
drawer.append(ul);
|
|
1091
|
+
return drawer;
|
|
1092
|
+
}
|
|
1093
|
+
/** One package card per the mockup: avatar initials, display name + package
|
|
1094
|
+
* subtitle, old→new version row (new in emerald), a 日志 toggle expanding the
|
|
1095
|
+
* inline changelog. Blocked/incompatible cards are dimmed with a rose
|
|
1096
|
+
* 无法安装 badge and a reason line, and carry NO install button. */
|
|
1097
|
+
function packageCard(item, copy, open, onToggle) {
|
|
1098
|
+
const blocked = item.state === "blocked";
|
|
1099
|
+
const card = el("li", blocked ? "hqpu-card hqpu-card--blocked" : "hqpu-card");
|
|
777
1100
|
card.dataset.pluginName = item.name;
|
|
778
1101
|
card.dataset.state = item.state;
|
|
779
|
-
card.title = `${item.name} · ${versionLine(item)}`;
|
|
780
1102
|
const head = el("div", "hqpu-card-head");
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
1103
|
+
const left = el("div");
|
|
1104
|
+
const avatar = el("span", "hqpu-avatar", avatarInitials(item));
|
|
1105
|
+
avatar.setAttribute("aria-hidden", "true");
|
|
784
1106
|
const main = el("div", "hqpu-card-main");
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
main.append(titleRow, desc);
|
|
793
|
-
head.append(glyph, main);
|
|
794
|
-
const end = el("div", "hqpu-card-end");
|
|
795
|
-
if (item.state === "update" || item.state === "install") {
|
|
796
|
-
const button = el("button", "hqpu-btn hqpu-btn--sm hqpu-btn--primary");
|
|
797
|
-
button.type = "button";
|
|
798
|
-
button.disabled = installing;
|
|
799
|
-
button.innerHTML = `${DOWNLOAD_ICON(13)}<span>${copy.installOne}</span>`;
|
|
800
|
-
button.addEventListener("click", () => {
|
|
801
|
-
runInstall(store, copy);
|
|
802
|
-
});
|
|
803
|
-
end.append(button);
|
|
1107
|
+
const titleText = typeof item.displayName === "string" && item.displayName.trim().length > 0 ? item.displayName.trim() : shortName(item.name);
|
|
1108
|
+
main.append(el("span", "hqpu-card-title", titleText));
|
|
1109
|
+
main.append(el("span", "hqpu-card-sub", shortName(item.name)));
|
|
1110
|
+
const ver = el("div", "hqpu-version-row");
|
|
1111
|
+
if (item.currentVersion !== null && item.currentVersion !== void 0) {
|
|
1112
|
+
ver.append(el("span", "hqpu-ver-old", item.currentVersion));
|
|
1113
|
+
ver.append(el("span", "hqpu-ver-arrow", "→"));
|
|
804
1114
|
}
|
|
805
|
-
|
|
1115
|
+
const size = formatMb(item.sizeBytes ?? 0);
|
|
1116
|
+
ver.append(el("span", "hqpu-ver-new", size.length > 0 ? `${item.availableVersion} (${size})` : item.availableVersion));
|
|
1117
|
+
main.append(ver);
|
|
1118
|
+
left.append(avatar, main);
|
|
1119
|
+
const right = el("div", "hqpu-card-end");
|
|
1120
|
+
if (blocked) right.append(el("span", "hqpu-blocked-badge", copy.ui.blockedBadge));
|
|
1121
|
+
else if (Array.isArray(item.changes) && item.changes.length > 0) {
|
|
1122
|
+
const toggle = el("button", "hqpu-log-toggle");
|
|
1123
|
+
toggle.type = "button";
|
|
1124
|
+
toggle.dataset.open = open ? "true" : "false";
|
|
1125
|
+
toggle.innerHTML = `${copy.ui.changelogBtn}<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6L7.29289 9.29289C7.68342 9.68342 8.31658 9.68342 8.70711 9.29289L12 6"/></svg>`;
|
|
1126
|
+
toggle.addEventListener("click", onToggle);
|
|
1127
|
+
right.append(toggle);
|
|
1128
|
+
}
|
|
1129
|
+
head.append(left, right);
|
|
806
1130
|
card.append(head);
|
|
807
|
-
if (item.
|
|
1131
|
+
if (!blocked && Array.isArray(item.changes) && item.changes.length > 0) card.append(changelogDrawer(item, copy, open));
|
|
1132
|
+
if (blocked) {
|
|
808
1133
|
const reason = blockedReason(item, copy);
|
|
809
|
-
|
|
1134
|
+
const entries = [...reason !== void 0 ? [reason] : [], ...[...item.reasons ?? [], ...item.notes ?? []].filter((e) => typeof e === "string" && e.length > 0)];
|
|
1135
|
+
for (const entry of entries) {
|
|
1136
|
+
const line = el("p", "hqpu-reason-line");
|
|
1137
|
+
line.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 5V8.5M8 11.25h.01M14.5 8a6.5 6.5 0 11-13 0 6.5 6.5 0 0113 0Z"/></svg>`;
|
|
1138
|
+
line.append(document.createTextNode(entry));
|
|
1139
|
+
card.append(line);
|
|
1140
|
+
}
|
|
810
1141
|
}
|
|
811
1142
|
return card;
|
|
812
1143
|
}
|
|
813
|
-
/** A section:
|
|
814
|
-
function group(heading, items, copy,
|
|
1144
|
+
/** A section: head (title + count pill + expand-all toggle) then its cards. */
|
|
1145
|
+
function group(heading, items, copy, changelogOpen, allOpen, onToggle, onToggleAll) {
|
|
815
1146
|
if (items.length === 0) return null;
|
|
816
1147
|
const section = el("section", "hqpu-group");
|
|
817
1148
|
section.dataset.group = heading;
|
|
818
|
-
const head = el("div", "hqpu-
|
|
819
|
-
|
|
820
|
-
|
|
1149
|
+
const head = el("div", "hqpu-section-head");
|
|
1150
|
+
const titleWrap = el("div", "hqpu-section-title");
|
|
1151
|
+
titleWrap.append(el("span", "", heading));
|
|
1152
|
+
titleWrap.append(el("span", "hqpu-count-pill", String(items.length)));
|
|
1153
|
+
head.append(titleWrap);
|
|
1154
|
+
if (items.some((i) => i.state !== "blocked" && Array.isArray(i.changes) && i.changes.length > 0)) {
|
|
1155
|
+
const all = el("button", "hqpu-all-changelog", copy.ui.allChangelog);
|
|
1156
|
+
all.type = "button";
|
|
1157
|
+
all.addEventListener("click", onToggleAll);
|
|
1158
|
+
head.append(all);
|
|
1159
|
+
}
|
|
821
1160
|
const list = el("ul", "hqpu-cards");
|
|
822
|
-
for (const item of items) list.append(packageCard(item, copy,
|
|
1161
|
+
for (const item of items) list.append(packageCard(item, copy, allOpen || changelogOpen.has(item.name), () => onToggle(item.name)));
|
|
823
1162
|
section.append(head, list);
|
|
824
1163
|
return section;
|
|
825
1164
|
}
|
|
@@ -859,6 +1198,234 @@ window.__ModuleLoader__.load({
|
|
|
859
1198
|
section.setAttribute("aria-label", copy.loading);
|
|
860
1199
|
return section;
|
|
861
1200
|
}
|
|
1201
|
+
/** Format a byte count for the progress line, or null when unknown. */
|
|
1202
|
+
function formatBytes(n) {
|
|
1203
|
+
if (n === void 0 || !Number.isFinite(n) || n < 0) return null;
|
|
1204
|
+
if (n >= 1024 ** 3) return `${(n / 1024 ** 3).toFixed(1)} GB`;
|
|
1205
|
+
if (n >= 1024 ** 2) return `${(n / 1024 ** 2).toFixed(1)} MB`;
|
|
1206
|
+
if (n >= 1024) return `${(n / 1024).toFixed(0)} KB`;
|
|
1207
|
+
return `${n} B`;
|
|
1208
|
+
}
|
|
1209
|
+
/** "上次检查: N 分钟前" — relative time from an ISO timestamp. */
|
|
1210
|
+
function relativeAgo(iso, copy) {
|
|
1211
|
+
if (typeof iso !== "string" || iso.length === 0) return null;
|
|
1212
|
+
const then = new Date(iso).getTime();
|
|
1213
|
+
if (!Number.isFinite(then)) return null;
|
|
1214
|
+
const diffMin = Math.round((Date.now() - then) / 6e4);
|
|
1215
|
+
if (diffMin < 1) return copy.ui.agoJust;
|
|
1216
|
+
if (diffMin < 60) return copy.ui.agoMinutes(diffMin);
|
|
1217
|
+
const hours = Math.round(diffMin / 60);
|
|
1218
|
+
if (hours < 24) return copy.ui.agoHours(hours);
|
|
1219
|
+
return copy.ui.agoDays(Math.round(hours / 24));
|
|
1220
|
+
}
|
|
1221
|
+
function jobKindLabel(job, copy) {
|
|
1222
|
+
return job.kind === "runtime" ? copy.job.kindRuntime : copy.job.kindPluginSet;
|
|
1223
|
+
}
|
|
1224
|
+
/** The phase in human words, falling back to the raw code. */
|
|
1225
|
+
function phaseLabel(job, copy) {
|
|
1226
|
+
return copy.job.phase[job.phase] ?? job.phase;
|
|
1227
|
+
}
|
|
1228
|
+
/** One per-step line for plugin-set installs (and any job that reports steps). */
|
|
1229
|
+
function stepsList(job, copy) {
|
|
1230
|
+
if (!Array.isArray(job.steps) || job.steps.length === 0) return null;
|
|
1231
|
+
const wrap = el("div", "hqpu-job-steps");
|
|
1232
|
+
wrap.append(el("p", "hqpu-group-count", copy.job.stepsTitle));
|
|
1233
|
+
const ol = el("ol", "hqpu-steps");
|
|
1234
|
+
for (const step of job.steps) {
|
|
1235
|
+
const li = el("li", `hqpu-step hqpu-step--${step.state}`);
|
|
1236
|
+
const dot = el("span", "hqpu-dot");
|
|
1237
|
+
dot.dataset.state = step.state === "installed" ? "done" : step.state === "failed" ? "error" : step.state === "running" ? "warning" : "idle";
|
|
1238
|
+
li.append(dot);
|
|
1239
|
+
const text = step.message && step.message.length > 0 ? `${step.name} — ${step.message}` : step.name;
|
|
1240
|
+
li.append(el("span", "", text));
|
|
1241
|
+
ol.append(li);
|
|
1242
|
+
}
|
|
1243
|
+
wrap.append(ol);
|
|
1244
|
+
return wrap;
|
|
1245
|
+
}
|
|
1246
|
+
/** Map a job phase onto the 4-step indicator index (下载/校验/替换/重启). */
|
|
1247
|
+
function stepIndex(phase) {
|
|
1248
|
+
switch (phase) {
|
|
1249
|
+
case "downloading": return 0;
|
|
1250
|
+
case "verifying":
|
|
1251
|
+
case "resolving": return 1;
|
|
1252
|
+
case "staging":
|
|
1253
|
+
case "activating":
|
|
1254
|
+
case "awaiting-host": return 2;
|
|
1255
|
+
case "restarting":
|
|
1256
|
+
case "verifying-after-restart": return 3;
|
|
1257
|
+
case "queued": return -1;
|
|
1258
|
+
default: return -1;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* The ACTIVE-JOB banner per the mockup: pulsing brand dot + job title with the
|
|
1263
|
+
* target version, mono percentage, a gradient bar only when `percent != null`,
|
|
1264
|
+
* phase + speed line ("3.1 MB/s (286MB / 450MB)"), and a 4-step indicator row
|
|
1265
|
+
* (done=emerald, active=pulsing brand, upcoming=muted).
|
|
1266
|
+
*/
|
|
1267
|
+
function activeJobView(job, copy) {
|
|
1268
|
+
const section = el("section", "hqpu-group");
|
|
1269
|
+
section.dataset.group = "active-job";
|
|
1270
|
+
const card = el("div", "hqpu-active");
|
|
1271
|
+
const top = el("div", "hqpu-active-top");
|
|
1272
|
+
const titleWrap = el("div", "hqpu-active-title");
|
|
1273
|
+
const pulse = el("span", "hqpu-pulse-dot");
|
|
1274
|
+
pulse.append(el("span", "hqpu-pulse-ring"));
|
|
1275
|
+
const title = job.kind === "runtime" ? copy.ui.jobRuntimeTitle(job.targetVersion) : copy.ui.jobPluginTitle(job.targetVersion);
|
|
1276
|
+
titleWrap.append(pulse, el("span", "", title));
|
|
1277
|
+
const pct = job.progress.percent;
|
|
1278
|
+
if (pct !== null) top.append(el("span", "hqpu-active-percent", `${Math.round(pct)}%`));
|
|
1279
|
+
card.append(top);
|
|
1280
|
+
if (pct !== null) {
|
|
1281
|
+
const bar = el("div", "hqpu-progress");
|
|
1282
|
+
const fill = el("div", "hqpu-progress-fill");
|
|
1283
|
+
fill.style.width = `${Math.min(100, Math.max(0, pct))}%`;
|
|
1284
|
+
bar.append(fill);
|
|
1285
|
+
card.append(bar);
|
|
1286
|
+
}
|
|
1287
|
+
const meta = el("div", "hqpu-active-meta");
|
|
1288
|
+
const phaseText = typeof job.message === "string" && job.message.length > 0 ? job.message : phaseLabel(job, copy);
|
|
1289
|
+
meta.append(el("span", "", phaseText));
|
|
1290
|
+
const speed = formatBytes(job.progress.bytesPerSecond);
|
|
1291
|
+
const done = formatBytes(job.progress.bytesDone);
|
|
1292
|
+
const total = formatBytes(job.progress.bytesTotal);
|
|
1293
|
+
const telemetry = speed !== null && done !== null && total !== null ? `${speed}/s (${done} / ${total})` : speed !== null ? `${speed}/s` : done !== null && total !== null ? copy.job.bytes(done, total) : "";
|
|
1294
|
+
if (telemetry.length > 0) meta.append(el("span", "", telemetry));
|
|
1295
|
+
card.append(meta);
|
|
1296
|
+
const transition = el("div", "hqpu-active-meta");
|
|
1297
|
+
transition.append(el("span", "", job.previousVersion !== null && job.previousVersion.length > 0 ? `${job.previousVersion} → ${job.targetVersion}` : job.targetVersion));
|
|
1298
|
+
transition.append(el("span", "", job.kind === "runtime" ? copy.job.willSwapRuntime : copy.job.willRestartDsh));
|
|
1299
|
+
card.append(transition);
|
|
1300
|
+
card.append(el("p", "hqpu-job-meta", copy.job.closeNote));
|
|
1301
|
+
const current = stepIndex(job.phase);
|
|
1302
|
+
const stepsRow = el("div", "hqpu-job-steps4");
|
|
1303
|
+
copy.ui.jobSteps.forEach((label, index) => {
|
|
1304
|
+
const cls = current === -1 ? "hqpu-job-step--todo" : index < current ? "hqpu-job-step--done" : index === current ? "hqpu-job-step--active" : "hqpu-job-step--todo";
|
|
1305
|
+
stepsRow.append(el("div", cls, label));
|
|
1306
|
+
});
|
|
1307
|
+
card.append(stepsRow);
|
|
1308
|
+
section.append(card);
|
|
1309
|
+
return section;
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* The RESULT view for a terminal job: success / failed / rolled-back /
|
|
1313
|
+
* abandoned, previous→installed, per-package steps, errorCode + message, a log
|
|
1314
|
+
* correlation hint, and retry guidance (a retry starts a NEW job).
|
|
1315
|
+
*/
|
|
1316
|
+
function resultView(job, copy, onRetry) {
|
|
1317
|
+
const section = el("section", "hqpu-group");
|
|
1318
|
+
section.dataset.group = "job-result";
|
|
1319
|
+
const tone = job.phase === "done" ? "success" : job.phase === "failed" ? "danger" : "warning";
|
|
1320
|
+
const titleText = job.phase === "done" ? copy.job.resultDone : job.phase === "failed" ? copy.job.resultFailed : job.phase === "rolled-back" ? copy.job.resultRolledBack : copy.job.resultAbandoned;
|
|
1321
|
+
const head = el("div", "hqpu-group-head");
|
|
1322
|
+
const tag = el("span", "hqpu-tag");
|
|
1323
|
+
tag.dataset.tone = tone;
|
|
1324
|
+
tag.textContent = titleText;
|
|
1325
|
+
head.append(el("h3", "hqpu-group-title", `${jobKindLabel(job, copy)} ${job.targetVersion}`), tag);
|
|
1326
|
+
section.append(head);
|
|
1327
|
+
const card = el("div", "hqpu-job");
|
|
1328
|
+
const res = job.result;
|
|
1329
|
+
const from = job.previousVersion ?? res?.installedVersion ?? "—";
|
|
1330
|
+
const to = res?.installedVersion ?? job.targetVersion;
|
|
1331
|
+
const finished = res !== void 0 && res.finishedAt.length > 0 ? ` · ${new Date(res.finishedAt).toLocaleString()}` : "";
|
|
1332
|
+
card.append(el("p", "hqpu-status", `${from} → ${to}${finished}`));
|
|
1333
|
+
if (res !== void 0 && res.message !== void 0 && res.message.length > 0) card.append(el("p", "hqpu-status", res.message));
|
|
1334
|
+
if (res !== void 0 && res.errorCode !== void 0 && res.errorCode.length > 0) card.append(el("p", "hqpu-status", res.errorCode));
|
|
1335
|
+
if (typeof job.logHint === "string" && job.logHint.length > 0) card.append(el("p", "hqpu-status hqpu-job-meta", copy.job.logHint(job.logHint)));
|
|
1336
|
+
const steps = stepsList(job, copy);
|
|
1337
|
+
if (steps !== null) card.append(steps);
|
|
1338
|
+
if (job.phase === "failed" || job.phase === "abandoned") {
|
|
1339
|
+
const retry = el("button", "hqpu-btn hqpu-btn--outline hqpu-btn--sm", copy.job.retry);
|
|
1340
|
+
retry.type = "button";
|
|
1341
|
+
retry.addEventListener("click", onRetry);
|
|
1342
|
+
card.append(retry);
|
|
1343
|
+
}
|
|
1344
|
+
section.append(card);
|
|
1345
|
+
return section;
|
|
1346
|
+
}
|
|
1347
|
+
/** A one-line row for older recent jobs (newest already shown as the card). */
|
|
1348
|
+
function recentJobsList(jobs, copy) {
|
|
1349
|
+
const older = jobs.slice(1, 5);
|
|
1350
|
+
if (older.length === 0) return null;
|
|
1351
|
+
const wrap = el("div", "hqpu-group");
|
|
1352
|
+
wrap.dataset.group = "recent-jobs";
|
|
1353
|
+
wrap.append(el("h3", "hqpu-group-count", copy.job.recentTitle));
|
|
1354
|
+
const ul = el("ul", "hqpu-cards");
|
|
1355
|
+
for (const job of older) {
|
|
1356
|
+
const li = el("li", "hqpu-status");
|
|
1357
|
+
const when = job.updatedAt.length > 0 ? ` · ${new Date(job.updatedAt).toLocaleString()}` : "";
|
|
1358
|
+
li.textContent = `${jobKindLabel(job, copy)} ${job.targetVersion} · ${phaseLabel(job, copy)}${when}`;
|
|
1359
|
+
ul.append(li);
|
|
1360
|
+
}
|
|
1361
|
+
wrap.append(ul);
|
|
1362
|
+
return wrap;
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Check-status banner: a FAILED check shows a clear error row + Retry (never
|
|
1366
|
+
* the silent "no updates" empty state); a successful check shows the time;
|
|
1367
|
+
* when a check failed but a prior report exists it is labelled stale.
|
|
1368
|
+
*/
|
|
1369
|
+
function checkBanner(report, copy, onRetry) {
|
|
1370
|
+
const failed = typeof report.lastError === "string" && report.lastError.length > 0;
|
|
1371
|
+
const checked = typeof report.checkedAt === "string" && report.checkedAt.length > 0;
|
|
1372
|
+
if (!failed && !checked) return null;
|
|
1373
|
+
const wrap = el("section", "hqpu-group");
|
|
1374
|
+
wrap.dataset.group = "check-status";
|
|
1375
|
+
if (failed) {
|
|
1376
|
+
const banner = el("div", "hqpu-banner");
|
|
1377
|
+
banner.dataset.tone = "danger";
|
|
1378
|
+
banner.innerHTML = WARNING_ICON(14);
|
|
1379
|
+
banner.append(el("span", "", copy.checkFailedBanner(report.lastError)));
|
|
1380
|
+
wrap.append(banner);
|
|
1381
|
+
if (report.items.length > 0) wrap.append(el("p", "hqpu-status", copy.staleNote));
|
|
1382
|
+
const row = el("div", "hqpu-actions");
|
|
1383
|
+
const retry = el("button", "hqpu-btn hqpu-btn--primary hqpu-btn--sm", copy.retry);
|
|
1384
|
+
retry.type = "button";
|
|
1385
|
+
retry.addEventListener("click", onRetry);
|
|
1386
|
+
row.append(retry);
|
|
1387
|
+
wrap.append(row);
|
|
1388
|
+
return wrap;
|
|
1389
|
+
}
|
|
1390
|
+
wrap.append(el("p", "hqpu-status", copy.checkedAt(new Date(report.checkedAt).toLocaleString())));
|
|
1391
|
+
return wrap;
|
|
1392
|
+
}
|
|
1393
|
+
/** Set-level release metadata (v2 manifest): summary, notes, breaking, link. */
|
|
1394
|
+
function releaseHeader(release, copy) {
|
|
1395
|
+
if (release === null || release === void 0) return null;
|
|
1396
|
+
const notes = Array.isArray(release.notes) ? release.notes.filter((n) => typeof n === "string" && n.length > 0) : [];
|
|
1397
|
+
const breaking = Array.isArray(release.breaking) ? release.breaking.filter((n) => typeof n === "string" && n.length > 0) : [];
|
|
1398
|
+
if (!(typeof release.summary === "string" && release.summary.length > 0 || notes.length > 0 || breaking.length > 0 || typeof release.notesUrl === "string" || typeof release.publishedAt === "string")) return null;
|
|
1399
|
+
const section = el("section", "hqpu-group");
|
|
1400
|
+
section.dataset.group = "release";
|
|
1401
|
+
section.append(el("h3", "hqpu-group-title", copy.release.summaryTitle));
|
|
1402
|
+
if (typeof release.summary === "string" && release.summary.length > 0) section.append(el("p", "hqpu-status", release.summary));
|
|
1403
|
+
if (typeof release.publishedAt === "string" && release.publishedAt.length > 0) section.append(el("p", "hqpu-status", copy.release.publishedAt(new Date(release.publishedAt).toLocaleString())));
|
|
1404
|
+
if (release.requiresRestart === true) section.append(el("p", "hqpu-status", copy.job.willRestartDsh));
|
|
1405
|
+
if (notes.length > 0) {
|
|
1406
|
+
const ul = el("ul", "hqpu-release-notes");
|
|
1407
|
+
for (const note of notes) ul.append(el("li", "", note));
|
|
1408
|
+
section.append(ul);
|
|
1409
|
+
}
|
|
1410
|
+
if (breaking.length > 0) {
|
|
1411
|
+
const banner = el("div", "hqpu-banner");
|
|
1412
|
+
banner.dataset.tone = "danger";
|
|
1413
|
+
banner.innerHTML = WARNING_ICON(14);
|
|
1414
|
+
const inner = el("span");
|
|
1415
|
+
inner.append(el("strong", "", `${copy.release.breakingTitle}: `));
|
|
1416
|
+
inner.append(document.createTextNode(breaking.join(" · ")));
|
|
1417
|
+
banner.append(inner);
|
|
1418
|
+
section.append(banner);
|
|
1419
|
+
}
|
|
1420
|
+
if (typeof release.notesUrl === "string" && release.notesUrl.length > 0) {
|
|
1421
|
+
const link = el("a", "hqpu-status hqpu-release-link", copy.release.notesLink);
|
|
1422
|
+
link.href = release.notesUrl;
|
|
1423
|
+
link.target = "_blank";
|
|
1424
|
+
link.rel = "noreferrer";
|
|
1425
|
+
section.append(link);
|
|
1426
|
+
}
|
|
1427
|
+
return section;
|
|
1428
|
+
}
|
|
862
1429
|
/**
|
|
863
1430
|
* Normalize the report's `runtime.source` to the full shape.
|
|
864
1431
|
*
|
|
@@ -877,29 +1444,6 @@ window.__ModuleLoader__.load({
|
|
|
877
1444
|
override: candidate.override === true
|
|
878
1445
|
};
|
|
879
1446
|
}
|
|
880
|
-
/** The tag naming where the URL in effect came from. */
|
|
881
|
-
function originLabel(origin, copy) {
|
|
882
|
-
switch (origin) {
|
|
883
|
-
case "override": return copy.originOverride;
|
|
884
|
-
case "config": return copy.originConfig;
|
|
885
|
-
default: return copy.originUnset;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
/** The 更新源 fact row: the URL in effect plus an origin tag. */
|
|
889
|
-
function sourceFactRow(facts, source, copy) {
|
|
890
|
-
const wrap = el("div");
|
|
891
|
-
wrap.append(el("dt", "", copy.runtime.source));
|
|
892
|
-
const dd = el("dd");
|
|
893
|
-
const line = el("div", "hqpu-source-line");
|
|
894
|
-
const code = el("code", "hqpu-source-url", source.url.length > 0 ? source.url : copy.runtime.notConfigured);
|
|
895
|
-
const tag = el("span", "hqpu-tag");
|
|
896
|
-
tag.dataset.tone = source.override ? "info" : source.origin === "config" ? "success" : "neutral";
|
|
897
|
-
tag.textContent = originLabel(source.origin, copy);
|
|
898
|
-
line.append(code, tag);
|
|
899
|
-
dd.append(line);
|
|
900
|
-
wrap.append(dd);
|
|
901
|
-
facts.append(wrap);
|
|
902
|
-
}
|
|
903
1447
|
/** Pick the labels for one kind out of the copy table. */
|
|
904
1448
|
function sourceLabels(copy, kind) {
|
|
905
1449
|
return kind === "runtime" ? {
|
|
@@ -932,6 +1476,9 @@ window.__ModuleLoader__.load({
|
|
|
932
1476
|
const labels = sourceLabels(copy, kind);
|
|
933
1477
|
const block = el("div", `hqpu-source-editor hqpu-source-editor--${kind}`);
|
|
934
1478
|
if (!actions.sourceEditing) {
|
|
1479
|
+
const urlLine = el("p", "hqpu-source-current-url", source.url);
|
|
1480
|
+
urlLine.style.cssText = "margin:2px 0 6px;font-size:9px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:#a1a1aa;word-break:break-all;";
|
|
1481
|
+
block.append(urlLine);
|
|
935
1482
|
block.append(el("p", "hqpu-status hqpu-source-hint", labels.hint));
|
|
936
1483
|
const edit = el("button", "hqpu-btn hqpu-btn--outline hqpu-btn--sm", labels.edit);
|
|
937
1484
|
edit.type = "button";
|
|
@@ -986,109 +1533,119 @@ window.__ModuleLoader__.load({
|
|
|
986
1533
|
return block;
|
|
987
1534
|
}
|
|
988
1535
|
/**
|
|
989
|
-
* The
|
|
990
|
-
*
|
|
991
|
-
*
|
|
992
|
-
*
|
|
1536
|
+
* The host's own report of the last runtime swap. Rolled-back / failed renders
|
|
1537
|
+
* as an AMBER alert card (title, old→new body, errorCode chip, 忽略 dismiss);
|
|
1538
|
+
* succeeded renders as a subtle green inline line — not a loud banner. The
|
|
1539
|
+
* plugin has NO log-open affordance, so "查看诊断日志" is omitted (never faked).
|
|
993
1540
|
*/
|
|
994
|
-
function
|
|
995
|
-
const
|
|
996
|
-
if (
|
|
1541
|
+
function runtimeResultAlert(report, copy, dismissed, onDismiss) {
|
|
1542
|
+
const lastResult = report.runtime.lastResult;
|
|
1543
|
+
if (lastResult === null || lastResult === void 0) return null;
|
|
1544
|
+
if (lastResult.state === "succeeded") {
|
|
1545
|
+
const line = el("p", "hqpu-alert-green");
|
|
1546
|
+
line.dataset.role = "runtime-success";
|
|
1547
|
+
line.textContent = copy.ui.successInline;
|
|
1548
|
+
return line;
|
|
1549
|
+
}
|
|
1550
|
+
if (dismissed) return null;
|
|
997
1551
|
const section = el("section", "hqpu-group");
|
|
998
|
-
section.dataset.group = "
|
|
999
|
-
const
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
const
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1552
|
+
section.dataset.group = "runtime-alert";
|
|
1553
|
+
const card = el("div", "hqpu-alert hqpu-alert--amber");
|
|
1554
|
+
card.innerHTML = WARNING_ICON(14);
|
|
1555
|
+
const body = el("div");
|
|
1556
|
+
const title = el("div", "hqpu-alert-title");
|
|
1557
|
+
title.textContent = lastResult.state === "failed" ? copy.ui.failedTitle : copy.ui.rollbackTitle;
|
|
1558
|
+
body.append(title);
|
|
1559
|
+
const oldVer = lastResult.installedVersion ?? lastResult.targetVersion ?? "—";
|
|
1560
|
+
const code = typeof lastResult.errorCode === "string" && lastResult.errorCode.length > 0 ? lastResult.errorCode : "—";
|
|
1561
|
+
lastResult.state === "failed" ? copy.ui.failedBody(oldVer, code) : copy.ui.rollbackBody(oldVer, code);
|
|
1562
|
+
const bodyP = el("p", "hqpu-alert-body");
|
|
1563
|
+
const textOnly = lastResult.state === "failed" ? copy.ui.failedBody(oldVer, "") : copy.ui.rollbackBody(oldVer, "");
|
|
1564
|
+
bodyP.append(document.createTextNode(textOnly.replace(/\s*$/, "")));
|
|
1565
|
+
if (typeof lastResult.errorCode === "string" && lastResult.errorCode.length > 0) {
|
|
1566
|
+
bodyP.append(document.createTextNode(" "));
|
|
1567
|
+
const chip = el("code", "", lastResult.errorCode);
|
|
1568
|
+
bodyP.append(chip);
|
|
1569
|
+
}
|
|
1570
|
+
if (typeof lastResult.message === "string" && lastResult.message.length > 0) bodyP.append(el("br"), document.createTextNode(lastResult.message));
|
|
1571
|
+
body.append(bodyP);
|
|
1572
|
+
const actions = el("div", "hqpu-alert-actions");
|
|
1573
|
+
const dismiss = el("button", "hqpu-alert-dismiss", copy.ui.dismiss);
|
|
1574
|
+
dismiss.type = "button";
|
|
1575
|
+
dismiss.addEventListener("click", onDismiss);
|
|
1576
|
+
actions.append(dismiss);
|
|
1577
|
+
body.append(actions);
|
|
1578
|
+
card.append(body);
|
|
1579
|
+
section.append(card);
|
|
1006
1580
|
return section;
|
|
1007
1581
|
}
|
|
1008
|
-
/** The
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
const head = el("div", "hqpu-group-head");
|
|
1013
|
-
head.append(el("h3", "hqpu-group-title", copy.runtime.title));
|
|
1014
|
-
section.append(head);
|
|
1015
|
-
const facts = el("dl", "hqpu-facts");
|
|
1582
|
+
/** The HQ Edge runtime engine card per the mockup: current chip, "新版本 vY
|
|
1583
|
+
* 可用" with a 关键修复 tag when breaking/notes exist, size+summary line, a
|
|
1584
|
+
* 升级核心 button (existing arm→confirm flow), and the amber restart note. */
|
|
1585
|
+
function runtimeEngineCard(report, copy, actions) {
|
|
1016
1586
|
const runtime = report.runtime;
|
|
1017
1587
|
const latestVersion = runtimeLatestVersion(runtime);
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
facts.append(wrap);
|
|
1028
|
-
};
|
|
1029
|
-
const source = normalizeSource(runtime.source);
|
|
1030
|
-
if (runtime.configured === false) {
|
|
1031
|
-
if (source !== null) sourceFactRow(facts, source, copy);
|
|
1032
|
-
else row(copy.runtime.source, copy.runtime.notConfigured);
|
|
1033
|
-
} else {
|
|
1034
|
-
row(copy.runtime.current, runtime.currentVersion ?? copy.runtime.unknown);
|
|
1035
|
-
if (runtime.updateAvailable && latestVersion !== null) row(copy.runtime.latest, latestVersion);
|
|
1036
|
-
if (report.manifestVersion !== null) row(copy.runtime.set, report.manifestVersion, true);
|
|
1037
|
-
if (typeof runtime.checkedAt === "string" && runtime.checkedAt.length > 0) row(copy.runtime.checkedAt, new Date(runtime.checkedAt).toLocaleString());
|
|
1038
|
-
if (source !== null) sourceFactRow(facts, source, copy);
|
|
1039
|
-
}
|
|
1040
|
-
section.append(facts);
|
|
1041
|
-
if (source !== null) section.append(sourceEditor("runtime", source, copy, actions));
|
|
1588
|
+
const card = el("section", "hqpu-engine");
|
|
1589
|
+
card.dataset.group = "runtime";
|
|
1590
|
+
const head = el("div", "hqpu-engine-head");
|
|
1591
|
+
const titleWrap = el("div", "hqpu-engine-title");
|
|
1592
|
+
titleWrap.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.33333 2L4.66667 8H8L6.66667 14L11.33333 8H8L9.33333 2Z"/></svg>`;
|
|
1593
|
+
titleWrap.append(el("span", "", copy.ui.runtimeEngine));
|
|
1594
|
+
head.append(titleWrap);
|
|
1595
|
+
if (typeof runtime.currentVersion === "string" && runtime.currentVersion.length > 0) head.append(el("span", "hqpu-chip", copy.ui.currentChip(runtime.currentVersion)));
|
|
1596
|
+
card.append(head);
|
|
1042
1597
|
if (runtime.updateAvailable && latestVersion !== null) {
|
|
1043
|
-
const row = el("div", "hqpu-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1598
|
+
const row = el("div", "hqpu-engine-update-row");
|
|
1599
|
+
const left = el("div");
|
|
1600
|
+
const newLine = el("div", "hqpu-engine-new-line");
|
|
1601
|
+
newLine.append(el("span", "", copy.ui.newVersion(latestVersion)));
|
|
1602
|
+
const latestEntry = runtime.latest;
|
|
1603
|
+
if (Array.isArray(latestEntry?.breaking) && (latestEntry?.breaking?.length ?? 0) > 0 || Array.isArray(latestEntry?.notes) && (latestEntry?.notes?.length ?? 0) > 0) newLine.append(el("span", "hqpu-tag--brand", copy.ui.breakingTag));
|
|
1604
|
+
left.append(newLine);
|
|
1605
|
+
const size = typeof latestEntry?.sizeBytes === "number" ? formatMb(latestEntry.sizeBytes) : "";
|
|
1606
|
+
const summary = typeof latestEntry?.summary === "string" ? latestEntry.summary : "";
|
|
1607
|
+
if (size.length > 0 || summary.length > 0) left.append(el("p", "hqpu-engine-size", copy.ui.sizeLine(size, summary)));
|
|
1608
|
+
row.append(left);
|
|
1609
|
+
if (actions.accepted) row.append(el("span", "hqpu-status", copy.runtime.updateAccepted));
|
|
1610
|
+
else if (actions.busy) row.append(el("span", "hqpu-status", copy.runtime.updating));
|
|
1611
|
+
else if (actions.armed) {
|
|
1612
|
+
const confirmWrap = el("div");
|
|
1613
|
+
confirmWrap.append(el("p", "hqpu-status", copy.runtime.updateConfirm(latestVersion)));
|
|
1614
|
+
const yes = el("button", "hqpu-btn hqpu-btn--engine", copy.confirmInstall);
|
|
1057
1615
|
yes.type = "button";
|
|
1058
1616
|
yes.dataset.runtimeConfirm = "";
|
|
1059
1617
|
yes.addEventListener("click", actions.onConfirm);
|
|
1060
|
-
const no = el("button", "hqpu-btn hqpu-btn--
|
|
1618
|
+
const no = el("button", "hqpu-btn hqpu-btn--engine", copy.cancel);
|
|
1061
1619
|
no.type = "button";
|
|
1062
1620
|
no.addEventListener("click", actions.onCancel);
|
|
1063
|
-
|
|
1621
|
+
confirmWrap.append(yes, no);
|
|
1622
|
+
row.append(confirmWrap);
|
|
1064
1623
|
} else {
|
|
1065
|
-
const start = el("button", "hqpu-btn hqpu-btn--
|
|
1624
|
+
const start = el("button", "hqpu-btn hqpu-btn--engine", copy.ui.upgradeCore);
|
|
1066
1625
|
start.type = "button";
|
|
1067
1626
|
start.dataset.runtimeUpdate = "";
|
|
1068
|
-
start.innerHTML = `${DOWNLOAD_ICON(13)}<span>${copy.runtime.update}</span>`;
|
|
1069
1627
|
start.addEventListener("click", actions.onArm);
|
|
1070
1628
|
row.append(start);
|
|
1071
1629
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
dot.dataset.state = "error";
|
|
1079
|
-
line.append(dot, el("span", "", `${copy.lastError}: ${runtime.lastError}`));
|
|
1080
|
-
section.append(line);
|
|
1081
|
-
}
|
|
1082
|
-
return section;
|
|
1630
|
+
card.append(row);
|
|
1631
|
+
const note = el("p", "hqpu-engine-note");
|
|
1632
|
+
note.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2.5L14 13H2L8 2.5Z"/></svg><span>${copy.ui.runtimeRestartNote}</span>`;
|
|
1633
|
+
card.append(note);
|
|
1634
|
+
} else if (typeof runtime.lastError === "string" && runtime.lastError.length > 0) card.append(el("p", "hqpu-status", `${copy.lastError}: ${runtime.lastError}`));
|
|
1635
|
+
return card;
|
|
1083
1636
|
}
|
|
1084
|
-
function renderList(section, report, copy,
|
|
1637
|
+
function renderList(section, report, copy, changelogOpen, allOpen, onToggleCard, onToggleAll, onRetryCheck) {
|
|
1085
1638
|
section.textContent = "";
|
|
1086
1639
|
if (report === null) {
|
|
1087
1640
|
section.append(skeleton(copy));
|
|
1088
1641
|
return;
|
|
1089
1642
|
}
|
|
1643
|
+
const banner = checkBanner(report, copy, onRetryCheck);
|
|
1644
|
+
if (banner !== null) section.append(banner);
|
|
1645
|
+
const release = releaseHeader(report.release, copy);
|
|
1646
|
+
if (release !== null) section.append(release);
|
|
1090
1647
|
if (report.items.length === 0) {
|
|
1091
|
-
section.append(el("div", "hqpu-empty", copy.empty));
|
|
1648
|
+
if (typeof report.lastError !== "string" || report.lastError.length === 0) section.append(el("div", "hqpu-empty", copy.empty));
|
|
1092
1649
|
return;
|
|
1093
1650
|
}
|
|
1094
1651
|
const byState = (state) => report.items.filter((item) => item.state === state);
|
|
@@ -1099,23 +1656,19 @@ window.__ModuleLoader__.load({
|
|
|
1099
1656
|
[copy.group.current, byState("current")]
|
|
1100
1657
|
];
|
|
1101
1658
|
for (const [heading, items] of order) {
|
|
1102
|
-
const node = group(heading, items, copy,
|
|
1659
|
+
const node = group(heading, items, copy, changelogOpen, allOpen, onToggleCard, onToggleAll);
|
|
1103
1660
|
if (node !== null) section.append(node);
|
|
1104
1661
|
}
|
|
1105
1662
|
}
|
|
1106
|
-
function renderRuntime(section, report, copy, actions) {
|
|
1107
|
-
section.textContent = "";
|
|
1108
|
-
if (report === null) return;
|
|
1109
|
-
section.append(runtimeGroup(report, copy, actions));
|
|
1110
|
-
}
|
|
1111
|
-
/** The plugin-source group, above the plugin groups. No-op without the field. */
|
|
1112
|
-
function renderPluginSource(section, report, copy, actions) {
|
|
1663
|
+
function renderRuntime(section, report, copy, actions, dismissedAlert, onDismissAlert) {
|
|
1113
1664
|
section.textContent = "";
|
|
1114
1665
|
if (report === null) return;
|
|
1115
|
-
const
|
|
1116
|
-
if (
|
|
1666
|
+
const alert = runtimeResultAlert(report, copy, dismissedAlert, onDismissAlert);
|
|
1667
|
+
if (alert !== null) section.append(alert);
|
|
1668
|
+
const engine = runtimeEngineCard(report, copy, actions);
|
|
1669
|
+
if (engine !== null) section.append(engine);
|
|
1117
1670
|
}
|
|
1118
|
-
/** Run the install transaction, then
|
|
1671
|
+
/** Run the install transaction (a new durable job), then let the store poll it. */
|
|
1119
1672
|
async function runInstall(store, copy) {
|
|
1120
1673
|
const report = store.report();
|
|
1121
1674
|
if (report === null) return;
|
|
@@ -1123,15 +1676,18 @@ window.__ModuleLoader__.load({
|
|
|
1123
1676
|
showToast(copy.checkFailed, "error");
|
|
1124
1677
|
return;
|
|
1125
1678
|
}
|
|
1679
|
+
store.setInstalling(true);
|
|
1126
1680
|
try {
|
|
1127
1681
|
const result = await installSet(report.manifestVersion);
|
|
1128
1682
|
if (result.error !== void 0) {
|
|
1683
|
+
store.setInstalling(false);
|
|
1129
1684
|
showToast(`${copy.installError}: ${result.error}`, "error");
|
|
1130
1685
|
return;
|
|
1131
1686
|
}
|
|
1132
1687
|
showToast(copy.installSuccess, "success");
|
|
1133
|
-
|
|
1688
|
+
store.fetchStatus();
|
|
1134
1689
|
} catch (err) {
|
|
1690
|
+
store.setInstalling(false);
|
|
1135
1691
|
showToast(`${copy.installError}: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
1136
1692
|
}
|
|
1137
1693
|
}
|
|
@@ -1154,61 +1710,65 @@ window.__ModuleLoader__.load({
|
|
|
1154
1710
|
toastTimer = null;
|
|
1155
1711
|
}, TOAST_HOLD_MS);
|
|
1156
1712
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1713
|
+
/**
|
|
1714
|
+
* The collapsible "更新源配置 (高级)" panel (gear-toggled). It hosts the
|
|
1715
|
+
* EXISTING plugin-source and runtime-source editors (POST /plugin-source,
|
|
1716
|
+
* POST /runtime-source, save→check→refresh already wired). No new endpoints:
|
|
1717
|
+
* if a manifest source has no data, that field is omitted rather than faked.
|
|
1718
|
+
*/
|
|
1719
|
+
function renderSettingsPanel(panel, report, copy, open, pluginActions, runtimeActions) {
|
|
1720
|
+
panel.textContent = "";
|
|
1721
|
+
panel.style.display = open ? "" : "none";
|
|
1722
|
+
if (!open || report === null) return;
|
|
1723
|
+
const row = el("div", "hqpu-settings-row");
|
|
1724
|
+
row.append(el("div", "hqpu-settings-title", copy.ui.settingsTitle));
|
|
1725
|
+
const pluginSource = normalizeSource(report.pluginSource);
|
|
1726
|
+
const runtimeSource = normalizeSource(report.runtime.source);
|
|
1727
|
+
const overridden = (pluginSource?.override ?? false) || (runtimeSource?.override ?? false);
|
|
1728
|
+
const badge = el("span", "hqpu-origin-badge", overridden ? copy.ui.originCustom : copy.ui.originDefault);
|
|
1729
|
+
badge.dataset.origin = overridden ? "override" : "config";
|
|
1730
|
+
row.append(badge);
|
|
1731
|
+
panel.append(row);
|
|
1732
|
+
if (pluginSource !== null) {
|
|
1733
|
+
panel.append(el("label", "", copy.ui.pluginUrlLabel));
|
|
1734
|
+
panel.append(sourceEditor("plugin", pluginSource, copy, pluginActions));
|
|
1735
|
+
}
|
|
1736
|
+
if (runtimeSource !== null) {
|
|
1737
|
+
panel.append(el("label", "", copy.ui.runtimeUrlLabel));
|
|
1738
|
+
panel.append(sourceEditor("runtime", runtimeSource, copy, runtimeActions));
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
/**
|
|
1742
|
+
* The sticky footer: left meta ("准备安装 N 项兼容插件" / "版本集: vX"), a
|
|
1743
|
+
* full-width primary "一键更新全套插件 (vX)" (install = one durable job),
|
|
1744
|
+
* a secondary 忽略 wired to the existing ignore/unignore affordance, and the
|
|
1745
|
+
* footnote. The count EXCLUDES blocked / ignored / already-current entries.
|
|
1746
|
+
*/
|
|
1747
|
+
function renderFooter(footer, installBtn, ignoreBtn, countEl, setEl, report, copy, installing, store) {
|
|
1748
|
+
if (report === null) {
|
|
1749
|
+
countEl.textContent = "";
|
|
1750
|
+
setEl.textContent = "";
|
|
1751
|
+
installBtn.disabled = true;
|
|
1752
|
+
ignoreBtn.style.display = "none";
|
|
1165
1753
|
return;
|
|
1166
1754
|
}
|
|
1167
1755
|
const actionable = report.items.filter((item) => item.state === "update" || item.state === "install").length;
|
|
1168
1756
|
const hasSet = report.manifestVersion !== null;
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
unignore.type = "button";
|
|
1184
|
-
unignore.addEventListener("click", () => {
|
|
1185
|
-
unignoreOffer(report.manifestVersion).then(() => store.refresh()).catch(() => void 0);
|
|
1186
|
-
});
|
|
1187
|
-
bar.append(unignore);
|
|
1188
|
-
} else if (actionable > 0) {
|
|
1189
|
-
const ignore = el("button", "hqpu-btn hqpu-btn--ghost hqpu-btn--sm", copy.ignore);
|
|
1190
|
-
ignore.type = "button";
|
|
1191
|
-
ignore.addEventListener("click", () => {
|
|
1192
|
-
ignoreOffer(report.manifestVersion).then(() => store.refresh()).catch(() => void 0);
|
|
1193
|
-
});
|
|
1194
|
-
bar.append(ignore);
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
/** After an install, poll the backend until the update report settles
|
|
1198
|
-
* (installing flag clears or DSH restarts under us — the next load re-reads
|
|
1199
|
-
* the fresh state). */
|
|
1200
|
-
function pollUntilSettled(store) {
|
|
1201
|
-
let attempts = 0;
|
|
1202
|
-
const tick = () => {
|
|
1203
|
-
store.refresh().then((report) => {
|
|
1204
|
-
if (store.installing()) return;
|
|
1205
|
-
if (report !== null && report.ignoredSet === false && attempts >= 2) return;
|
|
1206
|
-
}).catch(() => void 0).finally(() => {
|
|
1207
|
-
attempts += 1;
|
|
1208
|
-
if (attempts < 10) window.setTimeout(tick, 2e3);
|
|
1209
|
-
});
|
|
1757
|
+
const setVersion = report.manifestVersion ?? "";
|
|
1758
|
+
countEl.textContent = copy.ui.footerCount(actionable);
|
|
1759
|
+
setEl.textContent = hasSet ? copy.ui.footerSet(setVersion) : "";
|
|
1760
|
+
installBtn.textContent = hasSet ? copy.ui.installAllSet(setVersion) : copy.ui.installAllSet("—");
|
|
1761
|
+
installBtn.disabled = installing || actionable === 0 || !hasSet || report.ignoredSet;
|
|
1762
|
+
installBtn.dataset.busy = installing ? "true" : "false";
|
|
1763
|
+
installBtn.onclick = () => {
|
|
1764
|
+
runInstall(store, copy);
|
|
1765
|
+
};
|
|
1766
|
+
ignoreBtn.style.display = hasSet ? "" : "none";
|
|
1767
|
+
ignoreBtn.textContent = report.ignoredSet ? copy.unignore : copy.ui.ignoreBtn;
|
|
1768
|
+
ignoreBtn.onclick = () => {
|
|
1769
|
+
const version = report.manifestVersion;
|
|
1770
|
+
(report.ignoredSet ? unignoreOffer(version) : ignoreOffer(version)).then(() => store.refresh()).catch(() => void 0);
|
|
1210
1771
|
};
|
|
1211
|
-
tick();
|
|
1212
1772
|
}
|
|
1213
1773
|
/**
|
|
1214
1774
|
* Open (or focus) the update panel. Re-renders from the store; when the store
|
|
@@ -1233,39 +1793,53 @@ window.__ModuleLoader__.load({
|
|
|
1233
1793
|
panel.tabIndex = -1;
|
|
1234
1794
|
const head = el("div", "hqpu-head");
|
|
1235
1795
|
const headMain = el("div", "hqpu-head-main");
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
info.innerHTML = INFO_ICON(11);
|
|
1244
|
-
intro.append(info);
|
|
1245
|
-
headMain.append(intro);
|
|
1796
|
+
const brandIcon = el("span", "hqpu-brand-icon");
|
|
1797
|
+
brandIcon.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5.6 2.6a6 6 0 015.6 1.6M12.4 13.4a6 6 0 01-5.6-1.6M10.6 2V5.4H7.2M5.4 14V10.6H8.8"/></svg>`;
|
|
1798
|
+
const titleBlock = el("div", "hqpu-title-block");
|
|
1799
|
+
titleBlock.append(el("h1", "hqpu-title", copy.ui.title));
|
|
1800
|
+
const sub = el("p", "hqpu-sub");
|
|
1801
|
+
titleBlock.append(sub);
|
|
1802
|
+
headMain.append(brandIcon, titleBlock);
|
|
1246
1803
|
const toolbar = el("div", "hqpu-toolbar");
|
|
1247
1804
|
const refreshBtn = iconButton("hqpu-icon-btn", REFRESH_ICON(16), copy.refresh, () => {
|
|
1248
|
-
store.
|
|
1805
|
+
store.check();
|
|
1249
1806
|
});
|
|
1250
1807
|
toolbar.append(refreshBtn);
|
|
1808
|
+
const gearBtn = iconButton("hqpu-icon-btn", `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="2.2"/><path d="M13.5 9.2v-2.4l-1.8-.6a4.4 4.4 0 00-.5-1.2l.9-1.7-1.7-1.7-1.7.9a4.4 4.4 0 00-1.2-.5L7.2 2.5H4.8l-.6 1.8a4.4 4.4 0 00-1.2.5l-1.7-.9-1.7 1.7.9 1.7a4.4 4.4 0 00-.5 1.2L2.5 7.2v2.4l1.8.6a4.4 4.4 0 00.5 1.2l-.9 1.7 1.7 1.7 1.7-.9a4.4 4.4 0 001.2.5l.6 1.8h2.4l.6-1.8a4.4 4.4 0 001.2-.5l1.7.9 1.7-1.7-.9-1.7a4.4 4.4 0 00.5-1.2l1.8-.6z"/></svg>`, copy.ui.gearTooltip, () => {
|
|
1809
|
+
settingsOpen = !settingsOpen;
|
|
1810
|
+
render();
|
|
1811
|
+
});
|
|
1812
|
+
toolbar.append(gearBtn);
|
|
1251
1813
|
const closeBtn = iconButton("hqpu-icon-btn", CLOSE_ICON(16), copy.close, () => {
|
|
1252
1814
|
onClose();
|
|
1253
1815
|
});
|
|
1254
1816
|
toolbar.append(closeBtn);
|
|
1255
1817
|
head.append(headMain, toolbar);
|
|
1256
1818
|
panel.append(head);
|
|
1819
|
+
const settingsPanel = el("div", "hqpu-settings");
|
|
1820
|
+
panel.append(settingsPanel);
|
|
1257
1821
|
const scroll = el("div", "hqpu-scroll");
|
|
1258
1822
|
const column = el("div", "hqpu-column");
|
|
1259
|
-
const
|
|
1823
|
+
const jobSection = el("div", "hqpu-stack");
|
|
1260
1824
|
const listSection = el("div", "hqpu-stack");
|
|
1261
1825
|
const runtimeSection = el("div", "hqpu-stack");
|
|
1262
1826
|
const toastSection = el("div", "hqpu-toast-host");
|
|
1263
|
-
column.append(
|
|
1827
|
+
column.append(jobSection, listSection, runtimeSection, toastSection);
|
|
1264
1828
|
scroll.append(column);
|
|
1265
1829
|
panel.append(scroll);
|
|
1266
1830
|
const footer = el("div", "hqpu-footer");
|
|
1267
|
-
const
|
|
1268
|
-
|
|
1831
|
+
const footMeta = el("div", "hqpu-foot-meta");
|
|
1832
|
+
const footCount = el("span", "");
|
|
1833
|
+
const footSet = el("span", "hqpu-foot-set");
|
|
1834
|
+
footMeta.append(footCount, footSet);
|
|
1835
|
+
const footActions = el("div", "hqpu-foot-actions");
|
|
1836
|
+
const installAllBtn = el("button", "hqpu-btn hqpu-btn--block");
|
|
1837
|
+
installAllBtn.type = "button";
|
|
1838
|
+
const ignoreBtn = el("button", "hqpu-btn--ignore", copy.ui.ignoreBtn);
|
|
1839
|
+
ignoreBtn.type = "button";
|
|
1840
|
+
footActions.append(installAllBtn, ignoreBtn);
|
|
1841
|
+
const footnote = el("p", "hqpu-footnote", copy.ui.footnote);
|
|
1842
|
+
footer.append(footMeta, footActions, footnote);
|
|
1269
1843
|
panel.append(footer);
|
|
1270
1844
|
overlay.append(scrim, panel);
|
|
1271
1845
|
document.body.appendChild(overlay);
|
|
@@ -1275,6 +1849,10 @@ window.__ModuleLoader__.load({
|
|
|
1275
1849
|
let runtimeArmed = false;
|
|
1276
1850
|
let runtimeBusy = false;
|
|
1277
1851
|
let runtimeAccepted = false;
|
|
1852
|
+
const changelogOpen = /* @__PURE__ */ new Set();
|
|
1853
|
+
let allOpen = false;
|
|
1854
|
+
let settingsOpen = false;
|
|
1855
|
+
let dismissedAlert = false;
|
|
1278
1856
|
const runtimeSourceState = {
|
|
1279
1857
|
editing: false,
|
|
1280
1858
|
saving: false,
|
|
@@ -1403,6 +1981,7 @@ window.__ModuleLoader__.load({
|
|
|
1403
1981
|
}
|
|
1404
1982
|
runtimeAccepted = true;
|
|
1405
1983
|
showToast(copy.runtime.updateAccepted, "success");
|
|
1984
|
+
store.fetchStatus();
|
|
1406
1985
|
} catch (err) {
|
|
1407
1986
|
showToast(`${copy.installError}: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
1408
1987
|
} finally {
|
|
@@ -1410,18 +1989,59 @@ window.__ModuleLoader__.load({
|
|
|
1410
1989
|
render();
|
|
1411
1990
|
}
|
|
1412
1991
|
}
|
|
1992
|
+
/** Retry a failed/abandoned job: plugin-set → a fresh install; runtime →
|
|
1993
|
+
* re-arm the runtime confirm flow. Always a NEW job, never re-clicking the
|
|
1994
|
+
* settled job id. */
|
|
1995
|
+
const retryJob = (kind) => {
|
|
1996
|
+
if (kind === "runtime") {
|
|
1997
|
+
runtimeArmed = true;
|
|
1998
|
+
render();
|
|
1999
|
+
} else runInstall(store, copy);
|
|
2000
|
+
};
|
|
2001
|
+
const renderJobSection = () => {
|
|
2002
|
+
jobSection.textContent = "";
|
|
2003
|
+
const active = store.activeJob();
|
|
2004
|
+
if (active !== null && !TERMINAL_PHASES.has(active.phase)) {
|
|
2005
|
+
jobSection.append(activeJobView(active, copy));
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
const recent = store.recentJobs();
|
|
2009
|
+
const latest = recent[0];
|
|
2010
|
+
if (latest !== void 0 && TERMINAL_PHASES.has(latest.phase)) {
|
|
2011
|
+
jobSection.append(resultView(latest, copy, () => retryJob(latest.kind)));
|
|
2012
|
+
const older = recentJobsList(recent, copy);
|
|
2013
|
+
if (older !== null) jobSection.append(older);
|
|
2014
|
+
}
|
|
2015
|
+
};
|
|
1413
2016
|
const render = () => {
|
|
1414
2017
|
const report = store.report();
|
|
1415
|
-
const
|
|
2018
|
+
const jobActive = store.activeJob() !== null && !TERMINAL_PHASES.has(store.activeJob().phase);
|
|
2019
|
+
const installing = store.installing() || jobActive;
|
|
1416
2020
|
const stale = report === null;
|
|
1417
2021
|
refreshBtn.disabled = stale;
|
|
2022
|
+
refreshBtn.title = copy.refresh;
|
|
2023
|
+
const ago = relativeAgo(report?.checkedAt, copy);
|
|
2024
|
+
sub.textContent = ago !== null ? copy.ui.checkedAgo(ago) : copy.ui.checkedUnknown;
|
|
2025
|
+
renderSettingsPanel(settingsPanel, report, copy, settingsOpen, pluginActions, runtimeSourceActions);
|
|
2026
|
+
renderJobSection();
|
|
1418
2027
|
if (stale) {
|
|
1419
2028
|
listSection.textContent = "";
|
|
1420
2029
|
listSection.append(skeleton(copy));
|
|
1421
|
-
} else renderList(listSection, report, copy,
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
2030
|
+
} else renderList(listSection, report, copy, changelogOpen, allOpen, (name) => {
|
|
2031
|
+
if (changelogOpen.has(name)) changelogOpen.delete(name);
|
|
2032
|
+
else changelogOpen.add(name);
|
|
2033
|
+
render();
|
|
2034
|
+
}, () => {
|
|
2035
|
+
allOpen = !allOpen;
|
|
2036
|
+
render();
|
|
2037
|
+
}, () => {
|
|
2038
|
+
store.check();
|
|
2039
|
+
});
|
|
2040
|
+
renderRuntime(runtimeSection, report, copy, runtimeActions, dismissedAlert, () => {
|
|
2041
|
+
dismissedAlert = true;
|
|
2042
|
+
render();
|
|
2043
|
+
});
|
|
2044
|
+
renderFooter(footer, installAllBtn, ignoreBtn, footCount, footSet, report, copy, installing, store);
|
|
1425
2045
|
};
|
|
1426
2046
|
const unsubscribe = store.subscribe(render);
|
|
1427
2047
|
disposers.push(unsubscribe);
|
|
@@ -2202,6 +2822,97 @@ window.__ModuleLoader__.load({
|
|
|
2202
2822
|
padding: 0 24px 20px;
|
|
2203
2823
|
}
|
|
2204
2824
|
|
|
2825
|
+
/* ── Durable job (active / result) ────────────────────────────────────── */
|
|
2826
|
+
.hqpu-job {
|
|
2827
|
+
display: flex;
|
|
2828
|
+
flex-direction: column;
|
|
2829
|
+
gap: 6px;
|
|
2830
|
+
padding: 12px;
|
|
2831
|
+
border: 0.5px solid var(--dsw-alias-border-l2);
|
|
2832
|
+
border-radius: var(--dsw-radius-lg);
|
|
2833
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
2834
|
+
}
|
|
2835
|
+
.hqpu-job-meta {
|
|
2836
|
+
font-variant-numeric: tabular-nums;
|
|
2837
|
+
}
|
|
2838
|
+
.hqpu-progress {
|
|
2839
|
+
height: 6px;
|
|
2840
|
+
border-radius: 999px;
|
|
2841
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
2842
|
+
overflow: hidden;
|
|
2843
|
+
}
|
|
2844
|
+
.hqpu-progress-fill {
|
|
2845
|
+
height: 100%;
|
|
2846
|
+
border-radius: 999px;
|
|
2847
|
+
background: var(--dsw-alias-state-business-primary);
|
|
2848
|
+
transition: width var(--ds-transition-duration, 0.2s) ease;
|
|
2849
|
+
}
|
|
2850
|
+
.hqpu-job-steps {
|
|
2851
|
+
display: flex;
|
|
2852
|
+
flex-direction: column;
|
|
2853
|
+
gap: 4px;
|
|
2854
|
+
}
|
|
2855
|
+
.hqpu-steps {
|
|
2856
|
+
margin: 0;
|
|
2857
|
+
padding: 0;
|
|
2858
|
+
list-style: none;
|
|
2859
|
+
display: flex;
|
|
2860
|
+
flex-direction: column;
|
|
2861
|
+
gap: 4px;
|
|
2862
|
+
}
|
|
2863
|
+
.hqpu-step {
|
|
2864
|
+
display: flex;
|
|
2865
|
+
align-items: center;
|
|
2866
|
+
gap: 6px;
|
|
2867
|
+
font-size: 12.5px;
|
|
2868
|
+
line-height: 18px;
|
|
2869
|
+
color: var(--dsw-alias-label-secondary);
|
|
2870
|
+
}
|
|
2871
|
+
.hqpu-step--skipped,
|
|
2872
|
+
.hqpu-step--pending { opacity: 0.6; }
|
|
2873
|
+
|
|
2874
|
+
/* ── Per-plugin change list + release notes ────────────────────────────── */
|
|
2875
|
+
.hqpu-changes {
|
|
2876
|
+
padding: 0 8px 8px 70px;
|
|
2877
|
+
}
|
|
2878
|
+
.hqpu-changes-list,
|
|
2879
|
+
.hqpu-release-notes {
|
|
2880
|
+
margin: 4px 0 0;
|
|
2881
|
+
padding-left: 18px;
|
|
2882
|
+
font-size: 12.5px;
|
|
2883
|
+
line-height: 19px;
|
|
2884
|
+
color: var(--dsw-alias-label-secondary);
|
|
2885
|
+
}
|
|
2886
|
+
.hqpu-release-link {
|
|
2887
|
+
align-self: flex-start;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
/* ── Advanced disclosure (source editors) ───────────────────────────────── */
|
|
2891
|
+
.hqpu-advanced > summary {
|
|
2892
|
+
cursor: pointer;
|
|
2893
|
+
font-size: 12.5px;
|
|
2894
|
+
line-height: 20px;
|
|
2895
|
+
color: var(--dsw-alias-label-tertiary);
|
|
2896
|
+
list-style: none;
|
|
2897
|
+
padding: 2px 0;
|
|
2898
|
+
}
|
|
2899
|
+
.hqpu-advanced > summary::-webkit-details-marker { display: none; }
|
|
2900
|
+
.hqpu-advanced > summary::before {
|
|
2901
|
+
content: '▸ ';
|
|
2902
|
+
}
|
|
2903
|
+
.hqpu-advanced[open] > summary::before {
|
|
2904
|
+
content: '▾ ';
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
/* Danger-toned banner (check failure, breaking changes) */
|
|
2908
|
+
.hqpu-banner[data-tone='danger'] {
|
|
2909
|
+
background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent);
|
|
2910
|
+
color: var(--dsw-alias-label-secondary);
|
|
2911
|
+
}
|
|
2912
|
+
.hqpu-banner[data-tone='danger'] > svg {
|
|
2913
|
+
color: var(--dsw-alias-state-error-primary);
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2205
2916
|
.hqpu-visually-hidden {
|
|
2206
2917
|
position: absolute;
|
|
2207
2918
|
width: 1px;
|
|
@@ -2220,58 +2931,697 @@ window.__ModuleLoader__.load({
|
|
|
2220
2931
|
.hqpu-panel,
|
|
2221
2932
|
.hqpu-skeleton-fill,
|
|
2222
2933
|
.hqpu-spinner,
|
|
2223
|
-
.hqpu-toast
|
|
2934
|
+
.hqpu-toast,
|
|
2935
|
+
.hqpu-pulse-ring,
|
|
2936
|
+
.hqpu-job-step--active {
|
|
2224
2937
|
animation: none;
|
|
2225
2938
|
}
|
|
2226
2939
|
}
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2940
|
+
|
|
2941
|
+
/* ════════════════════════════════════════════════════════════════════════
|
|
2942
|
+
* Refactored dock (interactive_refactored_ui_mockup.html) — a narrow dark-zinc
|
|
2943
|
+
* update-center column. These rules cascade over the shell-token base: the
|
|
2944
|
+
* panel becomes ~340px wide, the zinc palette hard-coded per mockup, with a
|
|
2945
|
+
* sticky header / scroll body / sticky footer. No Tailwind, no dependencies.
|
|
2946
|
+
* ════════════════════════════════════════════════════════════════════════ */
|
|
2947
|
+
.hqpu-panel,
|
|
2948
|
+
.hqpu-panel * {
|
|
2949
|
+
--hq-bg: #18181b;
|
|
2950
|
+
--hq-card: #27272a;
|
|
2951
|
+
--hq-card-soft: #1c1c1f;
|
|
2952
|
+
--hq-border: #3f3f46;
|
|
2953
|
+
--hq-hover: #323236;
|
|
2954
|
+
--hq-text: #f4f4f5;
|
|
2955
|
+
--hq-muted: #a1a1aa;
|
|
2956
|
+
--hq-faint: #71717a;
|
|
2957
|
+
--hq-brand: #dc2626;
|
|
2958
|
+
--hq-brand-soft: #ef4444;
|
|
2959
|
+
--hq-emerald: #34d399;
|
|
2960
|
+
--hq-amber: #f59e0b;
|
|
2961
|
+
}
|
|
2962
|
+
.hqpu-panel {
|
|
2963
|
+
width: min(360px, calc(100vw - 32px));
|
|
2964
|
+
max-height: min(86vh, 720px);
|
|
2965
|
+
background: var(--hq-bg);
|
|
2966
|
+
color: var(--hq-text);
|
|
2967
|
+
border: 0.5px solid var(--hq-border);
|
|
2968
|
+
border-radius: 12px;
|
|
2969
|
+
gap: 0;
|
|
2970
|
+
overflow: hidden;
|
|
2971
|
+
}
|
|
2972
|
+
.hqpu-scrim {
|
|
2973
|
+
background: rgba(0, 0, 0, 0.55);
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2976
|
+
/* Sticky header */
|
|
2977
|
+
.hqpu-head {
|
|
2978
|
+
flex: none;
|
|
2979
|
+
display: flex;
|
|
2980
|
+
align-items: center;
|
|
2981
|
+
justify-content: space-between;
|
|
2982
|
+
gap: 8px;
|
|
2983
|
+
padding: 10px 12px;
|
|
2984
|
+
border-bottom: 0.5px solid var(--hq-border);
|
|
2985
|
+
background: color-mix(in srgb, var(--hq-bg) 95%, transparent);
|
|
2986
|
+
position: sticky;
|
|
2987
|
+
top: 0;
|
|
2988
|
+
z-index: 2;
|
|
2989
|
+
}
|
|
2990
|
+
.hqpu-head-main {
|
|
2991
|
+
display: flex;
|
|
2992
|
+
align-items: center;
|
|
2993
|
+
gap: 8px;
|
|
2994
|
+
min-width: 0;
|
|
2995
|
+
margin: 0;
|
|
2996
|
+
}
|
|
2997
|
+
.hqpu-brand-icon {
|
|
2998
|
+
display: flex;
|
|
2999
|
+
align-items: center;
|
|
3000
|
+
justify-content: center;
|
|
3001
|
+
flex: none;
|
|
3002
|
+
width: 24px;
|
|
3003
|
+
height: 24px;
|
|
3004
|
+
border-radius: 6px;
|
|
3005
|
+
background: color-mix(in srgb, var(--hq-brand) 20%, transparent);
|
|
3006
|
+
color: var(--hq-brand-soft);
|
|
3007
|
+
}
|
|
3008
|
+
.hqpu-brand-icon svg { width: 14px; height: 14px; display: block; }
|
|
3009
|
+
.hqpu-title-block { min-width: 0; }
|
|
3010
|
+
.hqpu-title {
|
|
3011
|
+
margin: 0;
|
|
3012
|
+
font-size: 13px;
|
|
3013
|
+
font-weight: 600;
|
|
3014
|
+
line-height: 16px;
|
|
3015
|
+
color: var(--hq-text);
|
|
3016
|
+
}
|
|
3017
|
+
.hqpu-sub {
|
|
3018
|
+
margin: 2px 0 0;
|
|
3019
|
+
font-size: 10px;
|
|
3020
|
+
line-height: 14px;
|
|
3021
|
+
color: var(--hq-muted);
|
|
3022
|
+
white-space: nowrap;
|
|
3023
|
+
overflow: hidden;
|
|
3024
|
+
text-overflow: ellipsis;
|
|
3025
|
+
}
|
|
3026
|
+
.hqpu-toolbar { gap: 2px; }
|
|
3027
|
+
.hqpu-icon-btn {
|
|
3028
|
+
width: 26px;
|
|
3029
|
+
height: 26px;
|
|
3030
|
+
border-radius: 6px;
|
|
3031
|
+
color: var(--hq-muted);
|
|
3032
|
+
}
|
|
3033
|
+
.hqpu-icon-btn:hover:not(:disabled) {
|
|
3034
|
+
background: var(--hq-hover);
|
|
3035
|
+
color: var(--hq-text);
|
|
3036
|
+
}
|
|
3037
|
+
.hqpu-icon-btn svg { width: 14px; height: 14px; }
|
|
3038
|
+
|
|
3039
|
+
/* Collapsible settings panel */
|
|
3040
|
+
.hqpu-settings {
|
|
3041
|
+
flex: none;
|
|
3042
|
+
padding: 10px 12px;
|
|
3043
|
+
border-bottom: 0.5px solid var(--hq-border);
|
|
3044
|
+
background: #131316;
|
|
3045
|
+
}
|
|
3046
|
+
.hqpu-settings-row {
|
|
3047
|
+
display: flex;
|
|
3048
|
+
align-items: center;
|
|
3049
|
+
justify-content: space-between;
|
|
3050
|
+
gap: 8px;
|
|
3051
|
+
margin-bottom: 8px;
|
|
3052
|
+
}
|
|
3053
|
+
.hqpu-settings-title {
|
|
3054
|
+
font-size: 11px;
|
|
3055
|
+
font-weight: 500;
|
|
3056
|
+
color: #d4d4d8;
|
|
3057
|
+
}
|
|
3058
|
+
.hqpu-origin-badge {
|
|
3059
|
+
font-size: 9px;
|
|
3060
|
+
padding: 1px 6px;
|
|
3061
|
+
border-radius: 4px;
|
|
3062
|
+
border: 0.5px solid;
|
|
3063
|
+
}
|
|
3064
|
+
.hqpu-origin-badge[data-origin='config'] {
|
|
3065
|
+
background: rgba(6, 78, 59, 0.4);
|
|
3066
|
+
color: var(--hq-emerald);
|
|
3067
|
+
border-color: rgba(6, 95, 70, 0.6);
|
|
3068
|
+
}
|
|
3069
|
+
.hqpu-origin-badge[data-origin='override'] {
|
|
3070
|
+
background: rgba(120, 53, 15, 0.4);
|
|
3071
|
+
color: var(--hq-amber);
|
|
3072
|
+
border-color: rgba(180, 83, 9, 0.5);
|
|
3073
|
+
}
|
|
3074
|
+
.hqpu-settings label {
|
|
3075
|
+
display: block;
|
|
3076
|
+
font-size: 10px;
|
|
3077
|
+
color: var(--hq-muted);
|
|
3078
|
+
margin: 6px 0 3px;
|
|
3079
|
+
}
|
|
3080
|
+
.hqpu-source-input {
|
|
3081
|
+
width: 100%;
|
|
3082
|
+
height: 26px;
|
|
3083
|
+
font-size: 10px;
|
|
3084
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
3085
|
+
background: var(--hq-bg);
|
|
3086
|
+
border: 0.5px solid var(--hq-border);
|
|
3087
|
+
border-radius: 5px;
|
|
3088
|
+
color: #d4d4d8;
|
|
3089
|
+
padding: 0 8px;
|
|
3090
|
+
box-sizing: border-box;
|
|
3091
|
+
}
|
|
3092
|
+
.hqpu-source-input:focus-visible {
|
|
3093
|
+
outline: none;
|
|
3094
|
+
border-color: var(--hq-brand-soft);
|
|
3095
|
+
}
|
|
3096
|
+
.hqpu-settings-actions {
|
|
3097
|
+
display: flex;
|
|
3098
|
+
justify-content: flex-end;
|
|
3099
|
+
gap: 6px;
|
|
3100
|
+
margin-top: 8px;
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
/* Scroll body */
|
|
3104
|
+
.hqpu-scroll {
|
|
3105
|
+
flex: 1;
|
|
3106
|
+
padding: 12px;
|
|
3107
|
+
background: var(--hq-bg);
|
|
3108
|
+
--dsh-scrollbar-thumb: #3f3f46;
|
|
3109
|
+
--dsh-scrollbar-thumb-hover: #52525b;
|
|
3110
|
+
}
|
|
3111
|
+
.hqpu-column {
|
|
3112
|
+
max-width: none;
|
|
3113
|
+
margin: 0;
|
|
3114
|
+
gap: 10px;
|
|
3115
|
+
}
|
|
3116
|
+
.hqpu-stack { gap: 10px; }
|
|
3117
|
+
|
|
3118
|
+
/* ── Active-job banner ─────────────────────────────────────────────────── */
|
|
3119
|
+
.hqpu-active {
|
|
3120
|
+
position: relative;
|
|
3121
|
+
padding: 12px;
|
|
3122
|
+
border-radius: 8px;
|
|
3123
|
+
border: 0.5px solid color-mix(in srgb, var(--hq-brand-soft) 30%, transparent);
|
|
3124
|
+
background: #18181b;
|
|
3125
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
3126
|
+
}
|
|
3127
|
+
.hqpu-active-top {
|
|
3128
|
+
display: flex;
|
|
3129
|
+
align-items: center;
|
|
3130
|
+
justify-content: space-between;
|
|
3131
|
+
gap: 8px;
|
|
3132
|
+
}
|
|
3133
|
+
.hqpu-active-title {
|
|
3134
|
+
display: flex;
|
|
3135
|
+
align-items: center;
|
|
3136
|
+
gap: 6px;
|
|
3137
|
+
min-width: 0;
|
|
3138
|
+
font-size: 11px;
|
|
3139
|
+
font-weight: 600;
|
|
3140
|
+
color: #e4e4e7;
|
|
3141
|
+
}
|
|
3142
|
+
.hqpu-pulse-dot {
|
|
3143
|
+
position: relative;
|
|
3144
|
+
flex: none;
|
|
3145
|
+
width: 8px;
|
|
3146
|
+
height: 8px;
|
|
3147
|
+
}
|
|
3148
|
+
.hqpu-pulse-dot::before {
|
|
3149
|
+
content: '';
|
|
3150
|
+
position: absolute;
|
|
3151
|
+
inset: 0;
|
|
3152
|
+
border-radius: 50%;
|
|
3153
|
+
background: var(--hq-brand-soft);
|
|
3154
|
+
}
|
|
3155
|
+
.hqpu-pulse-ring {
|
|
3156
|
+
position: absolute;
|
|
3157
|
+
inset: 0;
|
|
3158
|
+
border-radius: 50%;
|
|
3159
|
+
background: var(--hq-brand-soft);
|
|
3160
|
+
opacity: 0.6;
|
|
3161
|
+
animation: hqpu-ping 1.4s cubic-bezier(0, 0, 0.2, 1) infinite;
|
|
3162
|
+
}
|
|
3163
|
+
@keyframes hqpu-ping {
|
|
3164
|
+
75%, 100% { transform: scale(2.2); opacity: 0; }
|
|
3165
|
+
}
|
|
3166
|
+
.hqpu-active-percent {
|
|
3167
|
+
flex: none;
|
|
3168
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
3169
|
+
font-size: 11px;
|
|
3170
|
+
font-weight: 500;
|
|
3171
|
+
color: var(--hq-brand-soft);
|
|
3172
|
+
}
|
|
3173
|
+
.hqpu-progress {
|
|
3174
|
+
height: 6px;
|
|
3175
|
+
margin-top: 10px;
|
|
3176
|
+
border-radius: 999px;
|
|
3177
|
+
background: #27272a;
|
|
3178
|
+
overflow: hidden;
|
|
3179
|
+
}
|
|
3180
|
+
.hqpu-progress-fill {
|
|
3181
|
+
background: linear-gradient(to right, var(--hq-brand), var(--hq-brand-soft));
|
|
3182
|
+
transition: width 0.3s ease;
|
|
3183
|
+
}
|
|
3184
|
+
.hqpu-active-meta {
|
|
3185
|
+
display: flex;
|
|
3186
|
+
justify-content: space-between;
|
|
3187
|
+
gap: 8px;
|
|
3188
|
+
margin-top: 4px;
|
|
3189
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
3190
|
+
font-size: 10px;
|
|
3191
|
+
color: var(--hq-muted);
|
|
3192
|
+
}
|
|
3193
|
+
/* 4-step indicator row */
|
|
3194
|
+
.hqpu-job-steps4 {
|
|
3195
|
+
display: grid;
|
|
3196
|
+
grid-template-columns: repeat(4, 1fr);
|
|
3197
|
+
gap: 4px;
|
|
3198
|
+
margin-top: 10px;
|
|
3199
|
+
padding-top: 8px;
|
|
3200
|
+
border-top: 0.5px solid #27272a;
|
|
3201
|
+
text-align: center;
|
|
3202
|
+
font-size: 9px;
|
|
3203
|
+
font-weight: 500;
|
|
3204
|
+
}
|
|
3205
|
+
.hqpu-job-step--done { color: var(--hq-emerald); }
|
|
3206
|
+
.hqpu-job-step--active {
|
|
3207
|
+
color: var(--hq-brand-soft);
|
|
3208
|
+
animation: hqpu-step-pulse 1.2s ease-in-out infinite;
|
|
3209
|
+
}
|
|
3210
|
+
.hqpu-job-step--todo { color: var(--hq-muted); }
|
|
3211
|
+
@keyframes hqpu-step-pulse {
|
|
3212
|
+
0%, 100% { opacity: 1; }
|
|
3213
|
+
50% { opacity: 0.45; }
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
/* ── Result / rollback alert (amber) vs green success ──────────────────── */
|
|
3217
|
+
.hqpu-alert {
|
|
3218
|
+
display: flex;
|
|
3219
|
+
align-items: flex-start;
|
|
3220
|
+
gap: 8px;
|
|
3221
|
+
padding: 10px;
|
|
3222
|
+
border-radius: 8px;
|
|
3223
|
+
}
|
|
3224
|
+
.hqpu-alert--amber {
|
|
3225
|
+
background: rgba(69, 26, 3, 0.35);
|
|
3226
|
+
border: 0.5px solid rgba(180, 83, 9, 0.4);
|
|
3227
|
+
}
|
|
3228
|
+
.hqpu-alert--amber svg { color: var(--hq-amber); flex: none; margin-top: 1px; }
|
|
3229
|
+
.hqpu-alert-green {
|
|
3230
|
+
background: rgba(6, 78, 59, 0.3);
|
|
3231
|
+
border: 0.5px solid rgba(6, 95, 70, 0.4);
|
|
3232
|
+
color: #a7f3d0;
|
|
3233
|
+
font-size: 11px;
|
|
3234
|
+
}
|
|
3235
|
+
.hqpu-alert-title {
|
|
3236
|
+
font-size: 11px;
|
|
3237
|
+
font-weight: 500;
|
|
3238
|
+
color: #fcd34d;
|
|
3239
|
+
}
|
|
3240
|
+
.hqpu-alert-body {
|
|
3241
|
+
margin: 2px 0 0;
|
|
3242
|
+
font-size: 10px;
|
|
3243
|
+
line-height: 15px;
|
|
3244
|
+
color: rgba(252, 211, 77, 0.8);
|
|
3245
|
+
}
|
|
3246
|
+
.hqpu-alert-body code {
|
|
3247
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
3248
|
+
background: rgba(120, 53, 15, 0.5);
|
|
3249
|
+
padding: 1px 4px;
|
|
3250
|
+
border-radius: 3px;
|
|
3251
|
+
}
|
|
3252
|
+
.hqpu-alert-actions {
|
|
3253
|
+
display: flex;
|
|
3254
|
+
gap: 8px;
|
|
3255
|
+
margin-top: 6px;
|
|
3256
|
+
}
|
|
3257
|
+
.hqpu-alert-dismiss {
|
|
3258
|
+
background: none;
|
|
3259
|
+
border: 0;
|
|
3260
|
+
padding: 0;
|
|
3261
|
+
font-size: 10px;
|
|
3262
|
+
color: #fbbf24;
|
|
3263
|
+
cursor: pointer;
|
|
3264
|
+
text-decoration: underline;
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3267
|
+
/* ── Runtime engine card ────────────────────────────────────────────────── */
|
|
3268
|
+
.hqpu-engine {
|
|
3269
|
+
padding: 12px;
|
|
3270
|
+
border-radius: 8px;
|
|
3271
|
+
border: 0.5px solid var(--hq-border);
|
|
3272
|
+
background: var(--hq-card);
|
|
3273
|
+
}
|
|
3274
|
+
.hqpu-engine-head {
|
|
3275
|
+
display: flex;
|
|
3276
|
+
align-items: center;
|
|
3277
|
+
justify-content: space-between;
|
|
3278
|
+
gap: 8px;
|
|
3279
|
+
}
|
|
3280
|
+
.hqpu-engine-title {
|
|
3281
|
+
display: flex;
|
|
3282
|
+
align-items: center;
|
|
3283
|
+
gap: 6px;
|
|
3284
|
+
font-size: 12px;
|
|
3285
|
+
font-weight: 600;
|
|
3286
|
+
color: #e4e4e7;
|
|
3287
|
+
}
|
|
3288
|
+
.hqpu-engine-title svg { width: 14px; height: 14px; color: #a1a1aa; }
|
|
3289
|
+
.hqpu-chip {
|
|
3290
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
3291
|
+
font-size: 9px;
|
|
3292
|
+
padding: 2px 6px;
|
|
3293
|
+
border-radius: 4px;
|
|
3294
|
+
background: #27272a;
|
|
3295
|
+
border: 0.5px solid #52525b;
|
|
3296
|
+
color: #d4d4d8;
|
|
3297
|
+
}
|
|
3298
|
+
.hqpu-engine-update-row {
|
|
3299
|
+
display: flex;
|
|
3300
|
+
align-items: center;
|
|
3301
|
+
justify-content: space-between;
|
|
3302
|
+
gap: 8px;
|
|
3303
|
+
margin-top: 10px;
|
|
3304
|
+
padding: 8px;
|
|
3305
|
+
border-radius: 6px;
|
|
3306
|
+
border: 0.5px solid rgba(63, 63, 70, 0.7);
|
|
3307
|
+
background: rgba(24, 24, 27, 0.6);
|
|
3308
|
+
}
|
|
3309
|
+
.hqpu-engine-new-line {
|
|
3310
|
+
display: flex;
|
|
3311
|
+
align-items: center;
|
|
3312
|
+
gap: 4px;
|
|
3313
|
+
font-size: 11px;
|
|
3314
|
+
font-weight: 500;
|
|
3315
|
+
color: #d4d4d8;
|
|
3316
|
+
}
|
|
3317
|
+
.hqpu-tag--brand {
|
|
3318
|
+
font-size: 9px;
|
|
3319
|
+
padding: 1px 4px;
|
|
3320
|
+
border-radius: 3px;
|
|
3321
|
+
color: var(--hq-brand-soft);
|
|
3322
|
+
background: rgba(69, 10, 10, 0.6);
|
|
3323
|
+
border: 0.5px solid rgba(153, 27, 27, 0.5);
|
|
3324
|
+
}
|
|
3325
|
+
.hqpu-engine-size {
|
|
3326
|
+
margin: 3px 0 0;
|
|
3327
|
+
font-size: 10px;
|
|
3328
|
+
color: var(--hq-muted);
|
|
3329
|
+
overflow: hidden;
|
|
3330
|
+
text-overflow: ellipsis;
|
|
3331
|
+
white-space: nowrap;
|
|
3332
|
+
}
|
|
3333
|
+
.hqpu-btn--engine {
|
|
3334
|
+
flex: none;
|
|
3335
|
+
height: auto;
|
|
3336
|
+
padding: 6px 10px;
|
|
3337
|
+
font-size: 10px;
|
|
3338
|
+
background: #27272a;
|
|
3339
|
+
border: 0.5px solid #52525b;
|
|
3340
|
+
color: #e4e4e7;
|
|
3341
|
+
border-radius: 5px;
|
|
3342
|
+
}
|
|
3343
|
+
.hqpu-btn--engine:hover:not(:disabled) { background: #3f3f46; }
|
|
3344
|
+
.hqpu-engine-note {
|
|
3345
|
+
display: flex;
|
|
3346
|
+
align-items: center;
|
|
3347
|
+
gap: 4px;
|
|
3348
|
+
margin: 8px 0 0;
|
|
3349
|
+
font-size: 9px;
|
|
3350
|
+
color: var(--hq-muted);
|
|
3351
|
+
}
|
|
3352
|
+
.hqpu-engine-note svg { width: 10px; height: 10px; color: var(--hq-amber); flex: none; }
|
|
3353
|
+
|
|
3354
|
+
/* ── Plugin cards ───────────────────────────────────────────────────────── */
|
|
3355
|
+
.hqpu-section-head {
|
|
3356
|
+
display: flex;
|
|
3357
|
+
align-items: center;
|
|
3358
|
+
justify-content: space-between;
|
|
3359
|
+
gap: 8px;
|
|
3360
|
+
padding: 0 2px;
|
|
3361
|
+
}
|
|
3362
|
+
.hqpu-section-title {
|
|
3363
|
+
display: flex;
|
|
3364
|
+
align-items: center;
|
|
3365
|
+
gap: 6px;
|
|
3366
|
+
font-size: 12px;
|
|
3367
|
+
font-weight: 600;
|
|
3368
|
+
color: #e4e4e7;
|
|
3369
|
+
}
|
|
3370
|
+
.hqpu-count-pill {
|
|
3371
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
3372
|
+
font-size: 10px;
|
|
3373
|
+
font-weight: 700;
|
|
3374
|
+
padding: 1px 6px;
|
|
3375
|
+
border-radius: 999px;
|
|
3376
|
+
background: color-mix(in srgb, var(--hq-brand) 20%, transparent);
|
|
3377
|
+
color: var(--hq-brand-soft);
|
|
3378
|
+
}
|
|
3379
|
+
.hqpu-all-changelog {
|
|
3380
|
+
background: none;
|
|
3381
|
+
border: 0;
|
|
3382
|
+
padding: 0;
|
|
3383
|
+
font-size: 10px;
|
|
3384
|
+
color: var(--hq-muted);
|
|
3385
|
+
cursor: pointer;
|
|
3386
|
+
}
|
|
3387
|
+
.hqpu-all-changelog:hover { color: var(--hq-text); }
|
|
3388
|
+
|
|
3389
|
+
.hqpu-cards { gap: 8px; }
|
|
3390
|
+
.hqpu-card {
|
|
3391
|
+
margin: 0;
|
|
3392
|
+
padding: 10px;
|
|
3393
|
+
border-radius: 8px;
|
|
3394
|
+
border: 0.5px solid var(--hq-border);
|
|
3395
|
+
background: var(--hq-card);
|
|
3396
|
+
transition: border-color 0.15s ease;
|
|
3397
|
+
}
|
|
3398
|
+
.hqpu-card:hover { border-color: #52525b; }
|
|
3399
|
+
.hqpu-card-head {
|
|
3400
|
+
display: flex;
|
|
3401
|
+
align-items: flex-start;
|
|
3402
|
+
justify-content: space-between;
|
|
3403
|
+
gap: 8px;
|
|
3404
|
+
padding: 0;
|
|
3405
|
+
}
|
|
3406
|
+
.hqpu-card-head > * { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
3407
|
+
.hqpu-avatar {
|
|
3408
|
+
display: flex;
|
|
3409
|
+
align-items: center;
|
|
3410
|
+
justify-content: center;
|
|
3411
|
+
flex: none;
|
|
3412
|
+
width: 28px;
|
|
3413
|
+
height: 28px;
|
|
3414
|
+
border-radius: 6px;
|
|
3415
|
+
border: 0.5px solid #52525b;
|
|
3416
|
+
background: #27272a;
|
|
3417
|
+
color: #d4d4d8;
|
|
3418
|
+
font-size: 10px;
|
|
3419
|
+
font-weight: 700;
|
|
3420
|
+
}
|
|
3421
|
+
.hqpu-card-title {
|
|
3422
|
+
font-size: 12px;
|
|
3423
|
+
font-weight: 600;
|
|
3424
|
+
color: #e4e4e7;
|
|
3425
|
+
}
|
|
3426
|
+
.hqpu-card-sub {
|
|
3427
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
3428
|
+
font-size: 9px;
|
|
3429
|
+
color: var(--hq-faint);
|
|
3430
|
+
}
|
|
3431
|
+
.hqpu-version-row {
|
|
3432
|
+
display: flex;
|
|
3433
|
+
align-items: center;
|
|
3434
|
+
gap: 4px;
|
|
3435
|
+
margin-top: 2px;
|
|
3436
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
3437
|
+
font-size: 10px;
|
|
3438
|
+
}
|
|
3439
|
+
.hqpu-ver-old { color: var(--hq-muted); }
|
|
3440
|
+
.hqpu-ver-arrow { color: #52525b; }
|
|
3441
|
+
.hqpu-ver-new { color: var(--hq-emerald); font-weight: 700; }
|
|
3442
|
+
.hqpu-log-toggle {
|
|
3443
|
+
display: inline-flex;
|
|
3444
|
+
align-items: center;
|
|
3445
|
+
gap: 2px;
|
|
3446
|
+
background: none;
|
|
3447
|
+
border: 0;
|
|
3448
|
+
padding: 4px;
|
|
3449
|
+
font-size: 10px;
|
|
3450
|
+
color: var(--hq-muted);
|
|
3451
|
+
cursor: pointer;
|
|
3452
|
+
}
|
|
3453
|
+
.hqpu-log-toggle:hover { color: var(--hq-text); }
|
|
3454
|
+
.hqpu-log-toggle svg { width: 12px; height: 12px; transition: transform 0.15s ease; }
|
|
3455
|
+
.hqpu-log-toggle[data-open='true'] svg { transform: rotate(180deg); }
|
|
3456
|
+
.hqpu-changelog-drawer {
|
|
3457
|
+
margin-top: 8px;
|
|
3458
|
+
padding: 8px;
|
|
3459
|
+
border-radius: 6px;
|
|
3460
|
+
border: 0.5px solid #27272a;
|
|
3461
|
+
background: rgba(24, 24, 27, 0.8);
|
|
3462
|
+
font-size: 10px;
|
|
3463
|
+
}
|
|
3464
|
+
.hqpu-changelog-drawer-title {
|
|
3465
|
+
font-size: 9px;
|
|
3466
|
+
font-weight: 500;
|
|
3467
|
+
text-transform: uppercase;
|
|
3468
|
+
letter-spacing: 0.05em;
|
|
3469
|
+
color: #a1a1aa;
|
|
3470
|
+
}
|
|
3471
|
+
.hqpu-changelog-drawer ul {
|
|
3472
|
+
margin: 4px 0 0;
|
|
3473
|
+
padding-left: 16px;
|
|
3474
|
+
color: #d4d4d8;
|
|
3475
|
+
line-height: 16px;
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
/* Blocked / incompatible card: dimmed, rose badge, no install button */
|
|
3479
|
+
.hqpu-card--blocked {
|
|
3480
|
+
opacity: 0.8;
|
|
3481
|
+
background: rgba(39, 39, 42, 0.6);
|
|
3482
|
+
border-color: #27272a;
|
|
3483
|
+
}
|
|
3484
|
+
.hqpu-card--blocked .hqpu-avatar {
|
|
3485
|
+
background: rgba(39, 39, 42, 0.5);
|
|
3486
|
+
border-color: #27272a;
|
|
3487
|
+
color: #71717a;
|
|
3488
|
+
}
|
|
3489
|
+
.hqpu-card--blocked .hqpu-card-title { color: #a1a1aa; }
|
|
3490
|
+
.hqpu-blocked-badge {
|
|
3491
|
+
font-size: 9px;
|
|
3492
|
+
padding: 2px 6px;
|
|
3493
|
+
border-radius: 4px;
|
|
3494
|
+
color: #fb7185;
|
|
3495
|
+
background: rgba(76, 5, 25, 0.5);
|
|
3496
|
+
border: 0.5px solid rgba(136, 19, 55, 0.5);
|
|
3497
|
+
}
|
|
3498
|
+
.hqpu-reason-line {
|
|
3499
|
+
display: flex;
|
|
3500
|
+
align-items: flex-start;
|
|
3501
|
+
gap: 4px;
|
|
3502
|
+
margin: 8px 0 0;
|
|
3503
|
+
padding: 6px;
|
|
3504
|
+
border-radius: 5px;
|
|
3505
|
+
border: 0.5px solid rgba(136, 19, 55, 0.2);
|
|
3506
|
+
background: rgba(76, 5, 25, 0.2);
|
|
3507
|
+
font-size: 10px;
|
|
3508
|
+
color: rgba(251, 113, 133, 0.9);
|
|
3509
|
+
}
|
|
3510
|
+
.hqpu-reason-line svg { width: 12px; height: 12px; flex: none; margin-top: 1px; }
|
|
3511
|
+
|
|
3512
|
+
/* Sticky footer */
|
|
3513
|
+
.hqpu-footer {
|
|
3514
|
+
flex: none;
|
|
3515
|
+
display: flex;
|
|
3516
|
+
flex-direction: column;
|
|
3517
|
+
align-items: stretch;
|
|
3518
|
+
gap: 8px;
|
|
3519
|
+
padding: 10px 12px 12px;
|
|
3520
|
+
border-top: 0.5px solid var(--hq-border);
|
|
3521
|
+
background: color-mix(in srgb, var(--hq-bg) 95%, transparent);
|
|
3522
|
+
}
|
|
3523
|
+
.hqpu-foot-meta {
|
|
3524
|
+
display: flex;
|
|
3525
|
+
align-items: center;
|
|
3526
|
+
justify-content: space-between;
|
|
3527
|
+
gap: 8px;
|
|
3528
|
+
font-size: 10px;
|
|
3529
|
+
color: var(--hq-muted);
|
|
3530
|
+
}
|
|
3531
|
+
.hqpu-foot-set {
|
|
3532
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
3533
|
+
}
|
|
3534
|
+
.hqpu-foot-actions {
|
|
3535
|
+
display: flex;
|
|
3536
|
+
gap: 8px;
|
|
3537
|
+
}
|
|
3538
|
+
.hqpu-btn--block {
|
|
3539
|
+
flex: 1;
|
|
3540
|
+
height: 32px;
|
|
3541
|
+
background: var(--hq-brand);
|
|
3542
|
+
color: #fff;
|
|
3543
|
+
font-size: 11px;
|
|
3544
|
+
font-weight: 500;
|
|
3545
|
+
border-radius: 6px;
|
|
3546
|
+
box-shadow: 0 4px 12px rgba(127, 29, 29, 0.4);
|
|
3547
|
+
}
|
|
3548
|
+
.hqpu-btn--block:hover:not(:disabled) { background: var(--hq-brand-soft); }
|
|
3549
|
+
.hqpu-btn--block:disabled { opacity: 0.5; box-shadow: none; }
|
|
3550
|
+
.hqpu-btn--ignore {
|
|
3551
|
+
height: 32px;
|
|
3552
|
+
padding: 0 10px;
|
|
3553
|
+
background: var(--hq-card);
|
|
3554
|
+
border: 0.5px solid var(--hq-border);
|
|
3555
|
+
color: var(--hq-muted);
|
|
3556
|
+
font-size: 10px;
|
|
3557
|
+
border-radius: 6px;
|
|
3558
|
+
}
|
|
3559
|
+
.hqpu-btn--ignore:hover:not(:disabled) { background: var(--hq-hover); color: var(--hq-text); }
|
|
3560
|
+
.hqpu-footnote {
|
|
3561
|
+
margin: 0;
|
|
3562
|
+
font-size: 9px;
|
|
3563
|
+
text-align: center;
|
|
3564
|
+
color: var(--hq-muted);
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3567
|
+
/* Shared bits on the new palette */
|
|
3568
|
+
.hqpu-status { color: var(--hq-muted); font-size: 11px; }
|
|
3569
|
+
.hqpu-empty { color: var(--hq-muted); font-size: 11px; padding: 16px 0; }
|
|
3570
|
+
.hqpu-toast { background: var(--hq-card); border: 0.5px solid var(--hq-border); color: var(--hq-text); font-size: 12px; }
|
|
3571
|
+
.hqpu-skeleton-fill { background: #3f3f46; }
|
|
3572
|
+
.hqpu-job { background: var(--hq-card); border-color: var(--hq-border); }
|
|
3573
|
+
.hqpu-job-meta { color: var(--hq-muted); }
|
|
3574
|
+
.hqpu-release-notes { color: var(--hq-muted); font-size: 11px; }
|
|
3575
|
+
.hqpu-advanced > summary { color: var(--hq-muted); }
|
|
3576
|
+
.hqpu-recent-jobs li { color: var(--hq-muted); font-size: 10px; }
|
|
3577
|
+
`;
|
|
3578
|
+
const STYLE_TAG_ID = "data-plugin-css=\"huaqiu-plugin-update\"";
|
|
3579
|
+
/**
|
|
3580
|
+
* Design tokens the bundled DSH shell may not publish.
|
|
3581
|
+
*
|
|
3582
|
+
* hq-edge ships an older `ui-theme` build. A live probe of the running shell
|
|
3583
|
+
* (both themes) shows 13 of the tokens this stylesheet reads are absent:
|
|
3584
|
+
* `--dsw-radius-{xs,sm,md,lg,xl,panel}`, `--dsw-focus-ring-width`,
|
|
3585
|
+
* `--dsw-focus-ring-color`, `--dsw-alias-toast-label`,
|
|
3586
|
+
* `--dsw-alias-state-idle-primary`, `--dsw-font-mono`,
|
|
3587
|
+
* `--dsh-frame-overlay-top` and `--dsh-frame-chrome-top`.
|
|
3588
|
+
*
|
|
3589
|
+
* An unresolved `var()` is not an error — it silently degrades the property to
|
|
3590
|
+
* its initial value. That is how this panel once rendered with
|
|
3591
|
+
* `border-radius: 0px` and a 3px `auto` focus ring while every colour was
|
|
3592
|
+
* already correct.
|
|
3593
|
+
*
|
|
3594
|
+
* The last three (`--dsw-font-mono`, `--dsh-frame-*`) are deliberately NOT
|
|
3595
|
+
* backfilled: the shell's own primitives rely on the inline `var(…, fallback)`
|
|
3596
|
+
* form for those (`Modal.module.css`, `PluginManagerPage.module.css`), so this
|
|
3597
|
+
* sheet does too. Everything else is backfilled below with the canonical value
|
|
3598
|
+
* from `ui-theme/src/styles/{base,focus,design-platform}.css`.
|
|
3599
|
+
*
|
|
3600
|
+
* Values are feature-detected at runtime and scoped to this plugin's own roots
|
|
3601
|
+
* rather than `:root`, so a newer shell always wins and filling a gap can never
|
|
3602
|
+
* restyle the host.
|
|
3603
|
+
*/
|
|
3604
|
+
/** The two roots this plugin styles: the view overlay (built in
|
|
3605
|
+
* update-view.ts) and the sidebar row ({@link ENTRY_CSS}.entry). */
|
|
3606
|
+
const STYLE_ROOTS = ".hqpu-overlay, .hqpu-sidebar-entry";
|
|
3607
|
+
/** The shell's dark-theme carrier — audited live as `body[data-ds-dark-theme]`. */
|
|
3608
|
+
const DARK_THEME_SELECTOR = "body[data-ds-dark-theme]";
|
|
3609
|
+
/** Same roots, one rule per theme, for tokens whose official value flips. */
|
|
3610
|
+
const DARK_STYLE_ROOTS = STYLE_ROOTS.split(",").map((root) => `${DARK_THEME_SELECTOR} ${root.trim()}`).join(",");
|
|
3611
|
+
const DESIGN_TOKEN_FALLBACKS = [
|
|
3612
|
+
{
|
|
3613
|
+
token: "--dsw-radius-xs",
|
|
3614
|
+
value: "4px"
|
|
3615
|
+
},
|
|
3616
|
+
{
|
|
3617
|
+
token: "--dsw-radius-sm",
|
|
3618
|
+
value: "8px"
|
|
3619
|
+
},
|
|
3620
|
+
{
|
|
3621
|
+
token: "--dsw-radius-md",
|
|
3622
|
+
value: "12px"
|
|
3623
|
+
},
|
|
3624
|
+
{
|
|
2275
3625
|
token: "--dsw-radius-lg",
|
|
2276
3626
|
value: "16px"
|
|
2277
3627
|
},
|
|
@@ -2346,33 +3696,114 @@ window.__ModuleLoader__.load({
|
|
|
2346
3696
|
var UpdateStore = class {
|
|
2347
3697
|
current = null;
|
|
2348
3698
|
installingFlag = false;
|
|
3699
|
+
/** The durable job the server currently reports as active (resumed across
|
|
3700
|
+
* panel close and DSH restart); null when idle. */
|
|
3701
|
+
active = null;
|
|
3702
|
+
recent = [];
|
|
2349
3703
|
listeners = /* @__PURE__ */ new Set();
|
|
2350
3704
|
refreshTimer = null;
|
|
3705
|
+
pollTimer = null;
|
|
3706
|
+
pollBackoffMs = 1e3;
|
|
3707
|
+
pollInFlight = false;
|
|
2351
3708
|
report() {
|
|
2352
3709
|
return this.current;
|
|
2353
3710
|
}
|
|
2354
3711
|
installing() {
|
|
2355
3712
|
return this.installingFlag;
|
|
2356
3713
|
}
|
|
3714
|
+
activeJob() {
|
|
3715
|
+
return this.active;
|
|
3716
|
+
}
|
|
3717
|
+
recentJobs() {
|
|
3718
|
+
return [...this.recent];
|
|
3719
|
+
}
|
|
2357
3720
|
subscribe(listener) {
|
|
2358
3721
|
this.listeners.add(listener);
|
|
2359
3722
|
return () => {
|
|
2360
3723
|
this.listeners.delete(listener);
|
|
2361
3724
|
};
|
|
2362
3725
|
}
|
|
2363
|
-
/**
|
|
2364
|
-
*
|
|
2365
|
-
*
|
|
3726
|
+
/**
|
|
3727
|
+
* Badge: counts actionable plugin updates AND an available runtime update AND
|
|
3728
|
+
* (while a job runs) an attention pill. A recent failed/rolled-back job with
|
|
3729
|
+
* nothing actionable left also keeps a dot, so the outcome is never silently
|
|
3730
|
+
* forgotten after the panel closes.
|
|
3731
|
+
*/
|
|
2366
3732
|
badgeCount() {
|
|
3733
|
+
const job = this.active;
|
|
3734
|
+
if (job !== null && !TERMINAL_PHASES.has(job.phase)) return 1;
|
|
2367
3735
|
const report = this.current;
|
|
2368
3736
|
if (report === null) return 0;
|
|
2369
|
-
|
|
3737
|
+
let count = report.items.filter((item) => item.state === "update" || item.state === "install").length;
|
|
3738
|
+
if (report.runtime?.updateAvailable) count += 1;
|
|
3739
|
+
if (count === 0) {
|
|
3740
|
+
const latest = this.recent[0];
|
|
3741
|
+
if (latest !== void 0 && (latest.phase === "failed" || latest.phase === "rolled-back")) count = 1;
|
|
3742
|
+
}
|
|
3743
|
+
return count;
|
|
2370
3744
|
}
|
|
2371
3745
|
emit() {
|
|
2372
3746
|
for (const listener of [...this.listeners]) try {
|
|
2373
3747
|
listener();
|
|
2374
3748
|
} catch {}
|
|
2375
3749
|
}
|
|
3750
|
+
/** Adopt a fresh `/status` snapshot and (re)arm the poll loop. */
|
|
3751
|
+
applyStatus(status) {
|
|
3752
|
+
const wasActive = this.active !== null;
|
|
3753
|
+
this.active = status.activeJob;
|
|
3754
|
+
this.recent = status.recentJobs;
|
|
3755
|
+
if (wasActive && this.active === null) this.installingFlag = false;
|
|
3756
|
+
this.emit();
|
|
3757
|
+
this.schedulePoll();
|
|
3758
|
+
}
|
|
3759
|
+
/**
|
|
3760
|
+
* Fetch the durable job status. Called on mount, on panel open (BEFORE
|
|
3761
|
+
* discovery) and by the poll loop. A failure (older hq-edge with no
|
|
3762
|
+
* `/status`, or a transient HTTP error) is swallowed: the last known job is
|
|
3763
|
+
* kept and the poller backs off and retries.
|
|
3764
|
+
*/
|
|
3765
|
+
async fetchStatus() {
|
|
3766
|
+
try {
|
|
3767
|
+
const status = await fetchJobStatus();
|
|
3768
|
+
this.pollBackoffMs = 1e3;
|
|
3769
|
+
this.applyStatus(status);
|
|
3770
|
+
} catch {
|
|
3771
|
+
this.schedulePoll();
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
/** ~1 s poll while a job is active; stop on terminal; back off on error
|
|
3775
|
+
* without discarding the last known job; never overlap requests. */
|
|
3776
|
+
schedulePoll() {
|
|
3777
|
+
if (this.pollTimer !== null) {
|
|
3778
|
+
window.clearTimeout(this.pollTimer);
|
|
3779
|
+
this.pollTimer = null;
|
|
3780
|
+
}
|
|
3781
|
+
const job = this.active;
|
|
3782
|
+
if (job === null || TERMINAL_PHASES.has(job.phase)) {
|
|
3783
|
+
this.pollBackoffMs = 1e3;
|
|
3784
|
+
return;
|
|
3785
|
+
}
|
|
3786
|
+
this.pollTimer = window.setTimeout(() => {
|
|
3787
|
+
this.pollTick();
|
|
3788
|
+
}, this.pollBackoffMs);
|
|
3789
|
+
}
|
|
3790
|
+
async pollTick() {
|
|
3791
|
+
if (this.pollInFlight) {
|
|
3792
|
+
this.schedulePoll();
|
|
3793
|
+
return;
|
|
3794
|
+
}
|
|
3795
|
+
this.pollInFlight = true;
|
|
3796
|
+
try {
|
|
3797
|
+
const status = await fetchJobStatus();
|
|
3798
|
+
this.pollBackoffMs = 1e3;
|
|
3799
|
+
this.applyStatus(status);
|
|
3800
|
+
} catch {
|
|
3801
|
+
this.pollBackoffMs = Math.min(5e3, Math.max(1e3, this.pollBackoffMs * 2));
|
|
3802
|
+
this.schedulePoll();
|
|
3803
|
+
} finally {
|
|
3804
|
+
this.pollInFlight = false;
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
2376
3807
|
async refresh() {
|
|
2377
3808
|
try {
|
|
2378
3809
|
const report = await fetchReport();
|
|
@@ -2410,6 +3841,10 @@ window.__ModuleLoader__.load({
|
|
|
2410
3841
|
window.clearInterval(this.refreshTimer);
|
|
2411
3842
|
this.refreshTimer = null;
|
|
2412
3843
|
}
|
|
3844
|
+
if (this.pollTimer !== null) {
|
|
3845
|
+
window.clearTimeout(this.pollTimer);
|
|
3846
|
+
this.pollTimer = null;
|
|
3847
|
+
}
|
|
2413
3848
|
this.listeners.clear();
|
|
2414
3849
|
}
|
|
2415
3850
|
};
|
|
@@ -2450,9 +3885,23 @@ window.__ModuleLoader__.load({
|
|
|
2450
3885
|
});
|
|
2451
3886
|
const openView = () => {
|
|
2452
3887
|
ensureDesignTokens();
|
|
2453
|
-
store.
|
|
2454
|
-
|
|
2455
|
-
|
|
3888
|
+
store.fetchStatus();
|
|
3889
|
+
openUpdateView(store, copyFor());
|
|
3890
|
+
store.check();
|
|
3891
|
+
};
|
|
3892
|
+
const statusLabel = () => {
|
|
3893
|
+
const base = entryCopy(locale);
|
|
3894
|
+
const job = store.activeJob();
|
|
3895
|
+
if (job !== null) {
|
|
3896
|
+
const cc = copyFor();
|
|
3897
|
+
const pct = job.progress.percent;
|
|
3898
|
+
const phase = cc.job.phase[job.phase] ?? job.phase;
|
|
3899
|
+
return {
|
|
3900
|
+
label: pct !== null ? `${base.label} · ${Math.round(pct)}%` : `${base.label} · ${phase}`,
|
|
3901
|
+
tooltip: `${base.tooltip} · ${phase}`
|
|
3902
|
+
};
|
|
3903
|
+
}
|
|
3904
|
+
return base;
|
|
2456
3905
|
};
|
|
2457
3906
|
const disposeEntry = mountSidebarEntry({
|
|
2458
3907
|
rowAttribute: UPDATE_ENTRY_ATTRIBUTE,
|
|
@@ -2460,8 +3909,8 @@ window.__ModuleLoader__.load({
|
|
|
2460
3909
|
plugin: PLUGIN_ID,
|
|
2461
3910
|
icon: UPDATE_ICON,
|
|
2462
3911
|
css: ENTRY_CSS,
|
|
2463
|
-
label: () =>
|
|
2464
|
-
tooltip: () =>
|
|
3912
|
+
label: () => statusLabel().label,
|
|
3913
|
+
tooltip: () => statusLabel().tooltip,
|
|
2465
3914
|
refresh: { subscribe: refreshSubscribe },
|
|
2466
3915
|
badge: {
|
|
2467
3916
|
subscribe: (listener) => store.subscribe(listener),
|
|
@@ -2471,8 +3920,14 @@ window.__ModuleLoader__.load({
|
|
|
2471
3920
|
position: "after",
|
|
2472
3921
|
familySelectors: HUAQIU_SIDEBAR_ENTRY_FAMILY
|
|
2473
3922
|
});
|
|
3923
|
+
store.subscribe(() => {
|
|
3924
|
+
for (const fn of [...localeSubscribers]) try {
|
|
3925
|
+
fn();
|
|
3926
|
+
} catch {}
|
|
3927
|
+
});
|
|
2474
3928
|
store.startIdleRefresh();
|
|
2475
3929
|
store.refresh();
|
|
3930
|
+
store.fetchStatus();
|
|
2476
3931
|
const dispose = () => {
|
|
2477
3932
|
try {
|
|
2478
3933
|
disposeEntry();
|