@huaqiu/dsh-tool-plugin-update 0.6.6 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +839 -120
- 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 +137 -6
- package/src/client/index.ts +241 -11
- package/src/client/update-view.ts +474 -41
- 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,174 @@ 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}`
|
|
542
713
|
},
|
|
714
|
+
release: {
|
|
715
|
+
summaryTitle: "版本说明",
|
|
716
|
+
notesLink: "查看发布说明",
|
|
717
|
+
breakingTitle: "破坏性变更",
|
|
718
|
+
changesTitle: "更新内容",
|
|
719
|
+
advanced: "高级:更新源",
|
|
720
|
+
publishedAt: (when) => `发布于 ${when}`
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
/** Compact a byte count for display: "约 12.3 MB" / "约 480 MB". */
|
|
724
|
+
function formatMb(bytes) {
|
|
725
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return "";
|
|
726
|
+
const mb = bytes / 1048576;
|
|
727
|
+
if (mb >= 100) return `${Math.round(mb)} MB`;
|
|
728
|
+
return `${mb.toFixed(1)} MB`;
|
|
729
|
+
}
|
|
730
|
+
const COPY = {
|
|
731
|
+
zh,
|
|
543
732
|
en: {
|
|
544
733
|
entryLabel: "Plugin Updates",
|
|
545
734
|
entryTooltip: "Review available plugin updates",
|
|
@@ -551,9 +740,14 @@ window.__ModuleLoader__.load({
|
|
|
551
740
|
loading: "Reading plugin updates…",
|
|
552
741
|
empty: "No plugin updates available",
|
|
553
742
|
checkFailed: "Failed to check for updates — please retry",
|
|
554
|
-
installAll: (count) => `Install
|
|
555
|
-
installOne: "Install",
|
|
743
|
+
installAll: (count) => `Install whole set (${count})`,
|
|
744
|
+
installOne: "Install set",
|
|
745
|
+
installOneTip: "Installs the whole set of N updates and restarts DSH — not just this plugin",
|
|
556
746
|
installing: "Installing… (DSH will restart)",
|
|
747
|
+
retry: "Retry",
|
|
748
|
+
checkedAt: (when) => `Last checked: ${when}`,
|
|
749
|
+
checkFailedBanner: (message) => `Update check failed: ${message}`,
|
|
750
|
+
staleNote: "Showing the last successful check — it may be stale",
|
|
557
751
|
nothingToInstall: "Nothing to install right now",
|
|
558
752
|
ignore: "Ignore this release",
|
|
559
753
|
unignore: "Show again",
|
|
@@ -587,10 +781,20 @@ window.__ModuleLoader__.load({
|
|
|
587
781
|
unknown: "Version unknown",
|
|
588
782
|
notConfigured: "No update source configured",
|
|
589
783
|
note: "Runtime updates are handled by the EDA installer",
|
|
784
|
+
sizeLabel: "Package size",
|
|
590
785
|
update: "Install runtime update",
|
|
591
786
|
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
|
|
787
|
+
updating: "Downloading and verifying the runtime bundle…",
|
|
788
|
+
durationNote: "Duration depends on download speed and package size; the runtime bundle is large, and HQ Edge restarts after the swap.",
|
|
593
789
|
updateAccepted: "Runtime update accepted — HQ Edge is restarting…",
|
|
790
|
+
size: (bytes) => `~ ${formatMb(bytes)}`,
|
|
791
|
+
lastResult: {
|
|
792
|
+
title: "Last runtime update result",
|
|
793
|
+
succeeded: "Runtime update succeeded",
|
|
794
|
+
failed: "Runtime update failed",
|
|
795
|
+
rolledBack: "Runtime update rolled back (previous version restored)",
|
|
796
|
+
elevation: "A system-wide install may show a UAC elevation prompt; declining it fails the swap and rolls back to the previous version."
|
|
797
|
+
},
|
|
594
798
|
sourceHint: "Where the runtime update manifest is fetched from; saved changes take effect immediately",
|
|
595
799
|
sourceEdit: "Edit",
|
|
596
800
|
sourceSave: "Save",
|
|
@@ -618,7 +822,49 @@ window.__ModuleLoader__.load({
|
|
|
618
822
|
installError: "Install failed",
|
|
619
823
|
installSuccess: "Update installed — restarting DSH…",
|
|
620
824
|
confirmInstall: "Confirm install",
|
|
621
|
-
cancel: "Cancel"
|
|
825
|
+
cancel: "Cancel",
|
|
826
|
+
job: {
|
|
827
|
+
kindPluginSet: "Plugin-set update",
|
|
828
|
+
kindRuntime: "HQ Edge runtime update",
|
|
829
|
+
phase: {
|
|
830
|
+
queued: "Queued",
|
|
831
|
+
resolving: "Resolving version",
|
|
832
|
+
downloading: "Downloading",
|
|
833
|
+
verifying: "Verifying integrity",
|
|
834
|
+
staging: "Staging files",
|
|
835
|
+
activating: "Activating plugin set",
|
|
836
|
+
"awaiting-host": "Waiting for host to swap runtime",
|
|
837
|
+
restarting: "Restarting",
|
|
838
|
+
"verifying-after-restart": "Verifying after restart",
|
|
839
|
+
done: "Done",
|
|
840
|
+
failed: "Failed",
|
|
841
|
+
"rolled-back": "Rolled back",
|
|
842
|
+
abandoned: "Abandoned"
|
|
843
|
+
},
|
|
844
|
+
willRestartDsh: "DSH restarts automatically once this finishes; the update continues in the background.",
|
|
845
|
+
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.",
|
|
846
|
+
closeNote: "Closing this panel does not cancel the update.",
|
|
847
|
+
bytes: (done, total) => `${done} / ${total}`,
|
|
848
|
+
speed: (v) => `${v}/s`,
|
|
849
|
+
eta: (s) => `~${s} left`,
|
|
850
|
+
nextDone: "Finishing — please wait…",
|
|
851
|
+
resultDone: "Update completed",
|
|
852
|
+
resultFailed: "Update failed",
|
|
853
|
+
resultRolledBack: "Update rolled back",
|
|
854
|
+
resultAbandoned: "Update did not complete",
|
|
855
|
+
retry: "Retry (start a new update)",
|
|
856
|
+
recentTitle: "Recent updates",
|
|
857
|
+
stepsTitle: "Per-component progress",
|
|
858
|
+
logHint: (hint) => `Log id: ${hint}`
|
|
859
|
+
},
|
|
860
|
+
release: {
|
|
861
|
+
summaryTitle: "Release notes",
|
|
862
|
+
notesLink: "View release notes",
|
|
863
|
+
breakingTitle: "Breaking changes",
|
|
864
|
+
changesTitle: "What changed",
|
|
865
|
+
advanced: "Advanced: update sources",
|
|
866
|
+
publishedAt: (when) => `Published ${when}`
|
|
867
|
+
}
|
|
622
868
|
}
|
|
623
869
|
};
|
|
624
870
|
//#endregion
|
|
@@ -783,13 +1029,15 @@ window.__ModuleLoader__.load({
|
|
|
783
1029
|
glyph.innerHTML = PLUGIN_ICON(24);
|
|
784
1030
|
const main = el("div", "hqpu-card-main");
|
|
785
1031
|
const titleRow = el("div", "hqpu-title-row");
|
|
786
|
-
const title = el("span", "hqpu-card-title", shortName(item.name));
|
|
1032
|
+
const title = el("span", "hqpu-card-title", typeof item.displayName === "string" && item.displayName.trim().length > 0 ? item.displayName.trim() : shortName(item.name));
|
|
787
1033
|
const tag = el("span", "hqpu-tag");
|
|
788
1034
|
tag.dataset.tone = stateTone(item);
|
|
789
1035
|
tag.textContent = stateLabel(item, copy);
|
|
790
1036
|
titleRow.append(title, tag);
|
|
791
|
-
const
|
|
1037
|
+
const size = formatMb(item.sizeBytes ?? 0);
|
|
1038
|
+
const desc = el("span", "hqpu-card-desc", size.length > 0 ? `${versionLine(item)} · ${size}` : versionLine(item));
|
|
792
1039
|
main.append(titleRow, desc);
|
|
1040
|
+
if (typeof item.description === "string" && item.description.trim().length > 0) main.append(el("span", "hqpu-card-desc", item.description.trim()));
|
|
793
1041
|
head.append(glyph, main);
|
|
794
1042
|
const end = el("div", "hqpu-card-end");
|
|
795
1043
|
if (item.state === "update" || item.state === "install") {
|
|
@@ -797,6 +1045,7 @@ window.__ModuleLoader__.load({
|
|
|
797
1045
|
button.type = "button";
|
|
798
1046
|
button.disabled = installing;
|
|
799
1047
|
button.innerHTML = `${DOWNLOAD_ICON(13)}<span>${copy.installOne}</span>`;
|
|
1048
|
+
button.title = copy.installOneTip;
|
|
800
1049
|
button.addEventListener("click", () => {
|
|
801
1050
|
runInstall(store, copy);
|
|
802
1051
|
});
|
|
@@ -804,9 +1053,23 @@ window.__ModuleLoader__.load({
|
|
|
804
1053
|
}
|
|
805
1054
|
head.append(end);
|
|
806
1055
|
card.append(head);
|
|
1056
|
+
if (Array.isArray(item.changes) && item.changes.length > 0) {
|
|
1057
|
+
const expander = el("div", "hqpu-changes");
|
|
1058
|
+
const toggle = el("button", "hqpu-btn hqpu-btn--ghost hqpu-btn--sm", copy.release.changesTitle);
|
|
1059
|
+
toggle.type = "button";
|
|
1060
|
+
const list = el("ul", "hqpu-changes-list");
|
|
1061
|
+
list.style.display = "none";
|
|
1062
|
+
for (const change of item.changes) if (typeof change === "string" && change.length > 0) list.append(el("li", "", change));
|
|
1063
|
+
toggle.addEventListener("click", () => {
|
|
1064
|
+
list.style.display = list.style.display === "none" ? "" : "none";
|
|
1065
|
+
});
|
|
1066
|
+
expander.append(toggle, list);
|
|
1067
|
+
card.append(expander);
|
|
1068
|
+
}
|
|
807
1069
|
if (item.state === "blocked") {
|
|
808
1070
|
const reason = blockedReason(item, copy);
|
|
809
1071
|
if (reason !== void 0) card.append(el("p", "hqpu-card-reason", reason));
|
|
1072
|
+
for (const entry of [...item.reasons ?? [], ...item.notes ?? []]) if (typeof entry === "string" && entry.length > 0) card.append(el("p", "hqpu-card-reason", entry));
|
|
810
1073
|
}
|
|
811
1074
|
return card;
|
|
812
1075
|
}
|
|
@@ -859,6 +1122,220 @@ window.__ModuleLoader__.load({
|
|
|
859
1122
|
section.setAttribute("aria-label", copy.loading);
|
|
860
1123
|
return section;
|
|
861
1124
|
}
|
|
1125
|
+
/** Format a byte count for the progress line, or null when unknown. */
|
|
1126
|
+
function formatBytes(n) {
|
|
1127
|
+
if (n === void 0 || !Number.isFinite(n) || n < 0) return null;
|
|
1128
|
+
if (n >= 1024 ** 3) return `${(n / 1024 ** 3).toFixed(1)} GB`;
|
|
1129
|
+
if (n >= 1024 ** 2) return `${(n / 1024 ** 2).toFixed(1)} MB`;
|
|
1130
|
+
if (n >= 1024) return `${(n / 1024).toFixed(0)} KB`;
|
|
1131
|
+
return `${n} B`;
|
|
1132
|
+
}
|
|
1133
|
+
/** Format an ETA in seconds to a short human string, or null when unknown. */
|
|
1134
|
+
function formatEta(seconds) {
|
|
1135
|
+
if (seconds === void 0 || !Number.isFinite(seconds) || seconds <= 0) return null;
|
|
1136
|
+
if (seconds < 60) return `${Math.round(seconds)} s`;
|
|
1137
|
+
const minutes = Math.round(seconds / 60);
|
|
1138
|
+
if (minutes < 60) return `${minutes} min`;
|
|
1139
|
+
return `${Math.floor(minutes / 60)} h ${minutes % 60} min`;
|
|
1140
|
+
}
|
|
1141
|
+
function jobKindLabel(job, copy) {
|
|
1142
|
+
return job.kind === "runtime" ? copy.job.kindRuntime : copy.job.kindPluginSet;
|
|
1143
|
+
}
|
|
1144
|
+
/** The phase in human words, falling back to the raw code. */
|
|
1145
|
+
function phaseLabel(job, copy) {
|
|
1146
|
+
return copy.job.phase[job.phase] ?? job.phase;
|
|
1147
|
+
}
|
|
1148
|
+
/** One per-step line for plugin-set installs (and any job that reports steps). */
|
|
1149
|
+
function stepsList(job, copy) {
|
|
1150
|
+
if (!Array.isArray(job.steps) || job.steps.length === 0) return null;
|
|
1151
|
+
const wrap = el("div", "hqpu-job-steps");
|
|
1152
|
+
wrap.append(el("p", "hqpu-group-count", copy.job.stepsTitle));
|
|
1153
|
+
const ol = el("ol", "hqpu-steps");
|
|
1154
|
+
for (const step of job.steps) {
|
|
1155
|
+
const li = el("li", `hqpu-step hqpu-step--${step.state}`);
|
|
1156
|
+
const dot = el("span", "hqpu-dot");
|
|
1157
|
+
dot.dataset.state = step.state === "installed" ? "done" : step.state === "failed" ? "error" : step.state === "running" ? "warning" : "idle";
|
|
1158
|
+
li.append(dot);
|
|
1159
|
+
const text = step.message && step.message.length > 0 ? `${step.name} — ${step.message}` : step.name;
|
|
1160
|
+
li.append(el("span", "", text));
|
|
1161
|
+
ol.append(li);
|
|
1162
|
+
}
|
|
1163
|
+
wrap.append(ol);
|
|
1164
|
+
return wrap;
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* The ACTIVE-JOB view: type + target, previous→target, current phase, a
|
|
1168
|
+
* determinate bar only when a real percentage exists, bytes/speed/ETA when the
|
|
1169
|
+
* server publishes them, and what happens next. Closing the panel never
|
|
1170
|
+
* cancels the job — say so.
|
|
1171
|
+
*/
|
|
1172
|
+
function activeJobView(job, copy) {
|
|
1173
|
+
const section = el("section", "hqpu-group");
|
|
1174
|
+
section.dataset.group = "active-job";
|
|
1175
|
+
const head = el("div", "hqpu-group-head");
|
|
1176
|
+
const tag = el("span", "hqpu-tag");
|
|
1177
|
+
tag.dataset.tone = "info";
|
|
1178
|
+
tag.textContent = jobKindLabel(job, copy);
|
|
1179
|
+
head.append(el("h3", "hqpu-group-title", `${jobKindLabel(job, copy)} → ${job.targetVersion}`), tag);
|
|
1180
|
+
section.append(head);
|
|
1181
|
+
const card = el("div", "hqpu-job");
|
|
1182
|
+
const phaseLine = el("p", "hqpu-status hqpu-status--dot");
|
|
1183
|
+
phaseLine.append(el("span", "hqpu-spinner"));
|
|
1184
|
+
const transition = job.previousVersion !== null && job.previousVersion.length > 0 ? `${job.previousVersion} → ${job.targetVersion} · ${phaseLabel(job, copy)}` : `${job.targetVersion} · ${phaseLabel(job, copy)}`;
|
|
1185
|
+
phaseLine.append(el("span", "", transition));
|
|
1186
|
+
card.append(phaseLine);
|
|
1187
|
+
if (typeof job.message === "string" && job.message.length > 0) card.append(el("p", "hqpu-status", job.message));
|
|
1188
|
+
const pct = job.progress.percent;
|
|
1189
|
+
if (pct !== null) {
|
|
1190
|
+
const bar = el("div", "hqpu-progress");
|
|
1191
|
+
const fill = el("div", "hqpu-progress-fill");
|
|
1192
|
+
fill.style.width = `${Math.min(100, Math.max(0, pct))}%`;
|
|
1193
|
+
bar.append(fill);
|
|
1194
|
+
card.append(bar);
|
|
1195
|
+
}
|
|
1196
|
+
const bits = [];
|
|
1197
|
+
if (pct !== null) bits.push(`${Math.round(pct)}%`);
|
|
1198
|
+
const done = formatBytes(job.progress.bytesDone);
|
|
1199
|
+
const total = formatBytes(job.progress.bytesTotal);
|
|
1200
|
+
if (done !== null && total !== null) bits.push(copy.job.bytes(done, total));
|
|
1201
|
+
const speed = formatBytes(job.progress.bytesPerSecond);
|
|
1202
|
+
if (speed !== null) bits.push(copy.job.speed(speed));
|
|
1203
|
+
const eta = formatEta(job.progress.etaSeconds);
|
|
1204
|
+
if (eta !== null) bits.push(copy.job.eta(eta));
|
|
1205
|
+
if (bits.length > 0) card.append(el("p", "hqpu-status hqpu-job-meta", bits.join(" · ")));
|
|
1206
|
+
const steps = stepsList(job, copy);
|
|
1207
|
+
if (steps !== null) card.append(steps);
|
|
1208
|
+
card.append(el("p", "hqpu-status", job.kind === "runtime" ? copy.job.willSwapRuntime : copy.job.willRestartDsh));
|
|
1209
|
+
card.append(el("p", "hqpu-status", copy.job.closeNote));
|
|
1210
|
+
section.append(card);
|
|
1211
|
+
return section;
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* The RESULT view for a terminal job: success / failed / rolled-back /
|
|
1215
|
+
* abandoned, previous→installed, per-package steps, errorCode + message, a log
|
|
1216
|
+
* correlation hint, and retry guidance (a retry starts a NEW job).
|
|
1217
|
+
*/
|
|
1218
|
+
function resultView(job, copy, onRetry) {
|
|
1219
|
+
const section = el("section", "hqpu-group");
|
|
1220
|
+
section.dataset.group = "job-result";
|
|
1221
|
+
const tone = job.phase === "done" ? "success" : job.phase === "failed" ? "danger" : "warning";
|
|
1222
|
+
const titleText = job.phase === "done" ? copy.job.resultDone : job.phase === "failed" ? copy.job.resultFailed : job.phase === "rolled-back" ? copy.job.resultRolledBack : copy.job.resultAbandoned;
|
|
1223
|
+
const head = el("div", "hqpu-group-head");
|
|
1224
|
+
const tag = el("span", "hqpu-tag");
|
|
1225
|
+
tag.dataset.tone = tone;
|
|
1226
|
+
tag.textContent = titleText;
|
|
1227
|
+
head.append(el("h3", "hqpu-group-title", `${jobKindLabel(job, copy)} ${job.targetVersion}`), tag);
|
|
1228
|
+
section.append(head);
|
|
1229
|
+
const card = el("div", "hqpu-job");
|
|
1230
|
+
const res = job.result;
|
|
1231
|
+
const from = job.previousVersion ?? res?.installedVersion ?? "—";
|
|
1232
|
+
const to = res?.installedVersion ?? job.targetVersion;
|
|
1233
|
+
const finished = res !== void 0 && res.finishedAt.length > 0 ? ` · ${new Date(res.finishedAt).toLocaleString()}` : "";
|
|
1234
|
+
card.append(el("p", "hqpu-status", `${from} → ${to}${finished}`));
|
|
1235
|
+
if (res !== void 0 && res.message !== void 0 && res.message.length > 0) card.append(el("p", "hqpu-status", res.message));
|
|
1236
|
+
if (res !== void 0 && res.errorCode !== void 0 && res.errorCode.length > 0) card.append(el("p", "hqpu-status", res.errorCode));
|
|
1237
|
+
if (typeof job.logHint === "string" && job.logHint.length > 0) card.append(el("p", "hqpu-status hqpu-job-meta", copy.job.logHint(job.logHint)));
|
|
1238
|
+
const steps = stepsList(job, copy);
|
|
1239
|
+
if (steps !== null) card.append(steps);
|
|
1240
|
+
if (job.phase === "failed" || job.phase === "abandoned") {
|
|
1241
|
+
const retry = el("button", "hqpu-btn hqpu-btn--outline hqpu-btn--sm", copy.job.retry);
|
|
1242
|
+
retry.type = "button";
|
|
1243
|
+
retry.addEventListener("click", onRetry);
|
|
1244
|
+
card.append(retry);
|
|
1245
|
+
}
|
|
1246
|
+
section.append(card);
|
|
1247
|
+
return section;
|
|
1248
|
+
}
|
|
1249
|
+
/** A one-line row for older recent jobs (newest already shown as the card). */
|
|
1250
|
+
function recentJobsList(jobs, copy) {
|
|
1251
|
+
const older = jobs.slice(1, 5);
|
|
1252
|
+
if (older.length === 0) return null;
|
|
1253
|
+
const wrap = el("div", "hqpu-group");
|
|
1254
|
+
wrap.dataset.group = "recent-jobs";
|
|
1255
|
+
wrap.append(el("h3", "hqpu-group-count", copy.job.recentTitle));
|
|
1256
|
+
const ul = el("ul", "hqpu-cards");
|
|
1257
|
+
for (const job of older) {
|
|
1258
|
+
const li = el("li", "hqpu-status");
|
|
1259
|
+
const when = job.updatedAt.length > 0 ? ` · ${new Date(job.updatedAt).toLocaleString()}` : "";
|
|
1260
|
+
li.textContent = `${jobKindLabel(job, copy)} ${job.targetVersion} · ${phaseLabel(job, copy)}${when}`;
|
|
1261
|
+
ul.append(li);
|
|
1262
|
+
}
|
|
1263
|
+
wrap.append(ul);
|
|
1264
|
+
return wrap;
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Check-status banner: a FAILED check shows a clear error row + Retry (never
|
|
1268
|
+
* the silent "no updates" empty state); a successful check shows the time;
|
|
1269
|
+
* when a check failed but a prior report exists it is labelled stale.
|
|
1270
|
+
*/
|
|
1271
|
+
function checkBanner(report, copy, onRetry) {
|
|
1272
|
+
const failed = typeof report.lastError === "string" && report.lastError.length > 0;
|
|
1273
|
+
const checked = typeof report.checkedAt === "string" && report.checkedAt.length > 0;
|
|
1274
|
+
if (!failed && !checked) return null;
|
|
1275
|
+
const wrap = el("section", "hqpu-group");
|
|
1276
|
+
wrap.dataset.group = "check-status";
|
|
1277
|
+
if (failed) {
|
|
1278
|
+
const banner = el("div", "hqpu-banner");
|
|
1279
|
+
banner.dataset.tone = "danger";
|
|
1280
|
+
banner.innerHTML = WARNING_ICON(14);
|
|
1281
|
+
banner.append(el("span", "", copy.checkFailedBanner(report.lastError)));
|
|
1282
|
+
wrap.append(banner);
|
|
1283
|
+
if (report.items.length > 0) wrap.append(el("p", "hqpu-status", copy.staleNote));
|
|
1284
|
+
const row = el("div", "hqpu-actions");
|
|
1285
|
+
const retry = el("button", "hqpu-btn hqpu-btn--primary hqpu-btn--sm", copy.retry);
|
|
1286
|
+
retry.type = "button";
|
|
1287
|
+
retry.addEventListener("click", onRetry);
|
|
1288
|
+
row.append(retry);
|
|
1289
|
+
wrap.append(row);
|
|
1290
|
+
return wrap;
|
|
1291
|
+
}
|
|
1292
|
+
wrap.append(el("p", "hqpu-status", copy.checkedAt(new Date(report.checkedAt).toLocaleString())));
|
|
1293
|
+
return wrap;
|
|
1294
|
+
}
|
|
1295
|
+
/** Set-level release metadata (v2 manifest): summary, notes, breaking, link. */
|
|
1296
|
+
function releaseHeader(release, copy) {
|
|
1297
|
+
if (release === null || release === void 0) return null;
|
|
1298
|
+
const notes = Array.isArray(release.notes) ? release.notes.filter((n) => typeof n === "string" && n.length > 0) : [];
|
|
1299
|
+
const breaking = Array.isArray(release.breaking) ? release.breaking.filter((n) => typeof n === "string" && n.length > 0) : [];
|
|
1300
|
+
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;
|
|
1301
|
+
const section = el("section", "hqpu-group");
|
|
1302
|
+
section.dataset.group = "release";
|
|
1303
|
+
section.append(el("h3", "hqpu-group-title", copy.release.summaryTitle));
|
|
1304
|
+
if (typeof release.summary === "string" && release.summary.length > 0) section.append(el("p", "hqpu-status", release.summary));
|
|
1305
|
+
if (typeof release.publishedAt === "string" && release.publishedAt.length > 0) section.append(el("p", "hqpu-status", copy.release.publishedAt(new Date(release.publishedAt).toLocaleString())));
|
|
1306
|
+
if (release.requiresRestart === true) section.append(el("p", "hqpu-status", copy.job.willRestartDsh));
|
|
1307
|
+
if (notes.length > 0) {
|
|
1308
|
+
const ul = el("ul", "hqpu-release-notes");
|
|
1309
|
+
for (const note of notes) ul.append(el("li", "", note));
|
|
1310
|
+
section.append(ul);
|
|
1311
|
+
}
|
|
1312
|
+
if (breaking.length > 0) {
|
|
1313
|
+
const banner = el("div", "hqpu-banner");
|
|
1314
|
+
banner.dataset.tone = "danger";
|
|
1315
|
+
banner.innerHTML = WARNING_ICON(14);
|
|
1316
|
+
const inner = el("span");
|
|
1317
|
+
inner.append(el("strong", "", `${copy.release.breakingTitle}: `));
|
|
1318
|
+
inner.append(document.createTextNode(breaking.join(" · ")));
|
|
1319
|
+
banner.append(inner);
|
|
1320
|
+
section.append(banner);
|
|
1321
|
+
}
|
|
1322
|
+
if (typeof release.notesUrl === "string" && release.notesUrl.length > 0) {
|
|
1323
|
+
const link = el("a", "hqpu-status hqpu-release-link", copy.release.notesLink);
|
|
1324
|
+
link.href = release.notesUrl;
|
|
1325
|
+
link.target = "_blank";
|
|
1326
|
+
link.rel = "noreferrer";
|
|
1327
|
+
section.append(link);
|
|
1328
|
+
}
|
|
1329
|
+
return section;
|
|
1330
|
+
}
|
|
1331
|
+
/** Native disclosure that hides the developer-facing source editors by
|
|
1332
|
+
* default (end users should never need to type a manifest URL). */
|
|
1333
|
+
function advancedBlock(copy, nodes) {
|
|
1334
|
+
const details = el("details", "hqpu-advanced");
|
|
1335
|
+
details.append(el("summary", "", copy.release.advanced));
|
|
1336
|
+
for (const node of nodes) if (node !== null) details.append(node);
|
|
1337
|
+
return details;
|
|
1338
|
+
}
|
|
862
1339
|
/**
|
|
863
1340
|
* Normalize the report's `runtime.source` to the full shape.
|
|
864
1341
|
*
|
|
@@ -1032,13 +1509,18 @@ window.__ModuleLoader__.load({
|
|
|
1032
1509
|
else row(copy.runtime.source, copy.runtime.notConfigured);
|
|
1033
1510
|
} else {
|
|
1034
1511
|
row(copy.runtime.current, runtime.currentVersion ?? copy.runtime.unknown);
|
|
1035
|
-
if (runtime.updateAvailable && latestVersion !== null)
|
|
1512
|
+
if (runtime.updateAvailable && latestVersion !== null) {
|
|
1513
|
+
row(copy.runtime.latest, latestVersion);
|
|
1514
|
+
const latestEntry = runtime.latest;
|
|
1515
|
+
const size = typeof latestEntry?.sizeBytes === "number" ? formatMb(latestEntry.sizeBytes) : "";
|
|
1516
|
+
if (size.length > 0) row(copy.runtime.sizeLabel, size);
|
|
1517
|
+
}
|
|
1036
1518
|
if (report.manifestVersion !== null) row(copy.runtime.set, report.manifestVersion, true);
|
|
1037
1519
|
if (typeof runtime.checkedAt === "string" && runtime.checkedAt.length > 0) row(copy.runtime.checkedAt, new Date(runtime.checkedAt).toLocaleString());
|
|
1038
1520
|
if (source !== null) sourceFactRow(facts, source, copy);
|
|
1039
1521
|
}
|
|
1040
1522
|
section.append(facts);
|
|
1041
|
-
if (source !== null) section.append(sourceEditor("runtime", source, copy, actions));
|
|
1523
|
+
if (source !== null) section.append(advancedBlock(copy, [sourceEditor("runtime", source, copy, actions)]));
|
|
1042
1524
|
if (runtime.updateAvailable && latestVersion !== null) {
|
|
1043
1525
|
const row = el("div", "hqpu-actions hqpu-runtime-actions");
|
|
1044
1526
|
if (actions.accepted) {
|
|
@@ -1070,7 +1552,25 @@ window.__ModuleLoader__.load({
|
|
|
1070
1552
|
row.append(start);
|
|
1071
1553
|
}
|
|
1072
1554
|
section.append(row);
|
|
1555
|
+
section.append(el("p", "hqpu-status hqpu-job-meta", copy.runtime.durationNote));
|
|
1073
1556
|
} else section.append(el("p", "hqpu-status", copy.runtime.note));
|
|
1557
|
+
const lastResult = runtime.lastResult;
|
|
1558
|
+
if (lastResult !== null && lastResult !== void 0) {
|
|
1559
|
+
const state = lastResult.state;
|
|
1560
|
+
const line = el("p", "hqpu-status hqpu-status--dot");
|
|
1561
|
+
line.setAttribute("role", "status");
|
|
1562
|
+
const dot = el("span", "hqpu-dot");
|
|
1563
|
+
dot.dataset.state = state === "succeeded" ? "done" : state === "failed" ? "error" : "warning";
|
|
1564
|
+
const parts = [state === "succeeded" ? copy.runtime.lastResult.succeeded : state === "failed" ? copy.runtime.lastResult.failed : copy.runtime.lastResult.rolledBack];
|
|
1565
|
+
if (lastResult.targetVersion) parts.push(`→ ${lastResult.targetVersion}`);
|
|
1566
|
+
if (lastResult.installedVersion) parts.push(`(${lastResult.installedVersion})`);
|
|
1567
|
+
if (lastResult.at) parts.push(new Date(lastResult.at).toLocaleString());
|
|
1568
|
+
line.append(dot, el("span", "", parts.join(" ")));
|
|
1569
|
+
section.append(line);
|
|
1570
|
+
if (typeof lastResult.message === "string" && lastResult.message.length > 0) section.append(el("p", "hqpu-status", lastResult.message));
|
|
1571
|
+
if (typeof lastResult.errorCode === "string" && lastResult.errorCode.length > 0) section.append(el("p", "hqpu-status hqpu-job-meta", lastResult.errorCode));
|
|
1572
|
+
if (state !== "succeeded") section.append(el("p", "hqpu-status hqpu-job-meta", copy.runtime.lastResult.elevation));
|
|
1573
|
+
}
|
|
1074
1574
|
if (typeof runtime.lastError === "string" && runtime.lastError.length > 0) {
|
|
1075
1575
|
const line = el("p", "hqpu-status hqpu-status--dot");
|
|
1076
1576
|
line.setAttribute("role", "status");
|
|
@@ -1081,14 +1581,18 @@ window.__ModuleLoader__.load({
|
|
|
1081
1581
|
}
|
|
1082
1582
|
return section;
|
|
1083
1583
|
}
|
|
1084
|
-
function renderList(section, report, copy, installing, store) {
|
|
1584
|
+
function renderList(section, report, copy, installing, store, onRetryCheck) {
|
|
1085
1585
|
section.textContent = "";
|
|
1086
1586
|
if (report === null) {
|
|
1087
1587
|
section.append(skeleton(copy));
|
|
1088
1588
|
return;
|
|
1089
1589
|
}
|
|
1590
|
+
const banner = checkBanner(report, copy, onRetryCheck);
|
|
1591
|
+
if (banner !== null) section.append(banner);
|
|
1592
|
+
const release = releaseHeader(report.release, copy);
|
|
1593
|
+
if (release !== null) section.append(release);
|
|
1090
1594
|
if (report.items.length === 0) {
|
|
1091
|
-
section.append(el("div", "hqpu-empty", copy.empty));
|
|
1595
|
+
if (typeof report.lastError !== "string" || report.lastError.length === 0) section.append(el("div", "hqpu-empty", copy.empty));
|
|
1092
1596
|
return;
|
|
1093
1597
|
}
|
|
1094
1598
|
const byState = (state) => report.items.filter((item) => item.state === state);
|
|
@@ -1108,14 +1612,16 @@ window.__ModuleLoader__.load({
|
|
|
1108
1612
|
if (report === null) return;
|
|
1109
1613
|
section.append(runtimeGroup(report, copy, actions));
|
|
1110
1614
|
}
|
|
1111
|
-
/** The plugin-source group, above the plugin groups
|
|
1615
|
+
/** The plugin-source group, above the plugin groups, hidden behind an advanced
|
|
1616
|
+
* disclosure (manifest-URL editing is a developer affordance). No-op without
|
|
1617
|
+
* the field. */
|
|
1112
1618
|
function renderPluginSource(section, report, copy, actions) {
|
|
1113
1619
|
section.textContent = "";
|
|
1114
1620
|
if (report === null) return;
|
|
1115
1621
|
const node = pluginSourceGroup(report, copy, actions);
|
|
1116
|
-
if (node !== null) section.append(node);
|
|
1622
|
+
if (node !== null) section.append(advancedBlock(copy, [node]));
|
|
1117
1623
|
}
|
|
1118
|
-
/** Run the install transaction, then
|
|
1624
|
+
/** Run the install transaction (a new durable job), then let the store poll it. */
|
|
1119
1625
|
async function runInstall(store, copy) {
|
|
1120
1626
|
const report = store.report();
|
|
1121
1627
|
if (report === null) return;
|
|
@@ -1123,15 +1629,18 @@ window.__ModuleLoader__.load({
|
|
|
1123
1629
|
showToast(copy.checkFailed, "error");
|
|
1124
1630
|
return;
|
|
1125
1631
|
}
|
|
1632
|
+
store.setInstalling(true);
|
|
1126
1633
|
try {
|
|
1127
1634
|
const result = await installSet(report.manifestVersion);
|
|
1128
1635
|
if (result.error !== void 0) {
|
|
1636
|
+
store.setInstalling(false);
|
|
1129
1637
|
showToast(`${copy.installError}: ${result.error}`, "error");
|
|
1130
1638
|
return;
|
|
1131
1639
|
}
|
|
1132
1640
|
showToast(copy.installSuccess, "success");
|
|
1133
|
-
|
|
1641
|
+
store.fetchStatus();
|
|
1134
1642
|
} catch (err) {
|
|
1643
|
+
store.setInstalling(false);
|
|
1135
1644
|
showToast(`${copy.installError}: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
1136
1645
|
}
|
|
1137
1646
|
}
|
|
@@ -1194,22 +1703,6 @@ window.__ModuleLoader__.load({
|
|
|
1194
1703
|
bar.append(ignore);
|
|
1195
1704
|
}
|
|
1196
1705
|
}
|
|
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
|
-
});
|
|
1210
|
-
};
|
|
1211
|
-
tick();
|
|
1212
|
-
}
|
|
1213
1706
|
/**
|
|
1214
1707
|
* Open (or focus) the update panel. Re-renders from the store; when the store
|
|
1215
1708
|
* is stale the panel first refreshes the report.
|
|
@@ -1256,11 +1749,12 @@ window.__ModuleLoader__.load({
|
|
|
1256
1749
|
panel.append(head);
|
|
1257
1750
|
const scroll = el("div", "hqpu-scroll");
|
|
1258
1751
|
const column = el("div", "hqpu-column");
|
|
1752
|
+
const jobSection = el("div", "hqpu-stack");
|
|
1259
1753
|
const pluginSourceSection = el("div", "hqpu-stack");
|
|
1260
1754
|
const listSection = el("div", "hqpu-stack");
|
|
1261
1755
|
const runtimeSection = el("div", "hqpu-stack");
|
|
1262
1756
|
const toastSection = el("div", "hqpu-toast-host");
|
|
1263
|
-
column.append(pluginSourceSection, listSection, runtimeSection, toastSection);
|
|
1757
|
+
column.append(jobSection, pluginSourceSection, listSection, runtimeSection, toastSection);
|
|
1264
1758
|
scroll.append(column);
|
|
1265
1759
|
panel.append(scroll);
|
|
1266
1760
|
const footer = el("div", "hqpu-footer");
|
|
@@ -1403,6 +1897,7 @@ window.__ModuleLoader__.load({
|
|
|
1403
1897
|
}
|
|
1404
1898
|
runtimeAccepted = true;
|
|
1405
1899
|
showToast(copy.runtime.updateAccepted, "success");
|
|
1900
|
+
store.fetchStatus();
|
|
1406
1901
|
} catch (err) {
|
|
1407
1902
|
showToast(`${copy.installError}: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
1408
1903
|
} finally {
|
|
@@ -1410,15 +1905,43 @@ window.__ModuleLoader__.load({
|
|
|
1410
1905
|
render();
|
|
1411
1906
|
}
|
|
1412
1907
|
}
|
|
1908
|
+
/** Retry a failed/abandoned job: plugin-set → a fresh install; runtime →
|
|
1909
|
+
* re-arm the runtime confirm flow. Always a NEW job, never re-clicking the
|
|
1910
|
+
* settled job id. */
|
|
1911
|
+
const retryJob = (kind) => {
|
|
1912
|
+
if (kind === "runtime") {
|
|
1913
|
+
runtimeArmed = true;
|
|
1914
|
+
render();
|
|
1915
|
+
} else runInstall(store, copy);
|
|
1916
|
+
};
|
|
1917
|
+
const renderJobSection = () => {
|
|
1918
|
+
jobSection.textContent = "";
|
|
1919
|
+
const active = store.activeJob();
|
|
1920
|
+
if (active !== null && !TERMINAL_PHASES.has(active.phase)) {
|
|
1921
|
+
jobSection.append(activeJobView(active, copy));
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
const recent = store.recentJobs();
|
|
1925
|
+
const latest = recent[0];
|
|
1926
|
+
if (latest !== void 0 && TERMINAL_PHASES.has(latest.phase)) {
|
|
1927
|
+
jobSection.append(resultView(latest, copy, () => retryJob(latest.kind)));
|
|
1928
|
+
const older = recentJobsList(recent, copy);
|
|
1929
|
+
if (older !== null) jobSection.append(older);
|
|
1930
|
+
}
|
|
1931
|
+
};
|
|
1413
1932
|
const render = () => {
|
|
1414
1933
|
const report = store.report();
|
|
1415
|
-
const
|
|
1934
|
+
const jobActive = store.activeJob() !== null && !TERMINAL_PHASES.has(store.activeJob().phase);
|
|
1935
|
+
const installing = store.installing() || jobActive;
|
|
1416
1936
|
const stale = report === null;
|
|
1417
1937
|
refreshBtn.disabled = stale;
|
|
1938
|
+
renderJobSection();
|
|
1418
1939
|
if (stale) {
|
|
1419
1940
|
listSection.textContent = "";
|
|
1420
1941
|
listSection.append(skeleton(copy));
|
|
1421
|
-
} else renderList(listSection, report, copy, installing, store)
|
|
1942
|
+
} else renderList(listSection, report, copy, installing, store, () => {
|
|
1943
|
+
store.check();
|
|
1944
|
+
});
|
|
1422
1945
|
renderPluginSource(pluginSourceSection, report, copy, pluginActions);
|
|
1423
1946
|
renderRuntime(runtimeSection, report, copy, runtimeActions);
|
|
1424
1947
|
renderActions(actionBar, report, copy, store, installing);
|
|
@@ -2202,6 +2725,97 @@ window.__ModuleLoader__.load({
|
|
|
2202
2725
|
padding: 0 24px 20px;
|
|
2203
2726
|
}
|
|
2204
2727
|
|
|
2728
|
+
/* ── Durable job (active / result) ────────────────────────────────────── */
|
|
2729
|
+
.hqpu-job {
|
|
2730
|
+
display: flex;
|
|
2731
|
+
flex-direction: column;
|
|
2732
|
+
gap: 6px;
|
|
2733
|
+
padding: 12px;
|
|
2734
|
+
border: 0.5px solid var(--dsw-alias-border-l2);
|
|
2735
|
+
border-radius: var(--dsw-radius-lg);
|
|
2736
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
2737
|
+
}
|
|
2738
|
+
.hqpu-job-meta {
|
|
2739
|
+
font-variant-numeric: tabular-nums;
|
|
2740
|
+
}
|
|
2741
|
+
.hqpu-progress {
|
|
2742
|
+
height: 6px;
|
|
2743
|
+
border-radius: 999px;
|
|
2744
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
2745
|
+
overflow: hidden;
|
|
2746
|
+
}
|
|
2747
|
+
.hqpu-progress-fill {
|
|
2748
|
+
height: 100%;
|
|
2749
|
+
border-radius: 999px;
|
|
2750
|
+
background: var(--dsw-alias-state-business-primary);
|
|
2751
|
+
transition: width var(--ds-transition-duration, 0.2s) ease;
|
|
2752
|
+
}
|
|
2753
|
+
.hqpu-job-steps {
|
|
2754
|
+
display: flex;
|
|
2755
|
+
flex-direction: column;
|
|
2756
|
+
gap: 4px;
|
|
2757
|
+
}
|
|
2758
|
+
.hqpu-steps {
|
|
2759
|
+
margin: 0;
|
|
2760
|
+
padding: 0;
|
|
2761
|
+
list-style: none;
|
|
2762
|
+
display: flex;
|
|
2763
|
+
flex-direction: column;
|
|
2764
|
+
gap: 4px;
|
|
2765
|
+
}
|
|
2766
|
+
.hqpu-step {
|
|
2767
|
+
display: flex;
|
|
2768
|
+
align-items: center;
|
|
2769
|
+
gap: 6px;
|
|
2770
|
+
font-size: 12.5px;
|
|
2771
|
+
line-height: 18px;
|
|
2772
|
+
color: var(--dsw-alias-label-secondary);
|
|
2773
|
+
}
|
|
2774
|
+
.hqpu-step--skipped,
|
|
2775
|
+
.hqpu-step--pending { opacity: 0.6; }
|
|
2776
|
+
|
|
2777
|
+
/* ── Per-plugin change list + release notes ────────────────────────────── */
|
|
2778
|
+
.hqpu-changes {
|
|
2779
|
+
padding: 0 8px 8px 70px;
|
|
2780
|
+
}
|
|
2781
|
+
.hqpu-changes-list,
|
|
2782
|
+
.hqpu-release-notes {
|
|
2783
|
+
margin: 4px 0 0;
|
|
2784
|
+
padding-left: 18px;
|
|
2785
|
+
font-size: 12.5px;
|
|
2786
|
+
line-height: 19px;
|
|
2787
|
+
color: var(--dsw-alias-label-secondary);
|
|
2788
|
+
}
|
|
2789
|
+
.hqpu-release-link {
|
|
2790
|
+
align-self: flex-start;
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
/* ── Advanced disclosure (source editors) ───────────────────────────────── */
|
|
2794
|
+
.hqpu-advanced > summary {
|
|
2795
|
+
cursor: pointer;
|
|
2796
|
+
font-size: 12.5px;
|
|
2797
|
+
line-height: 20px;
|
|
2798
|
+
color: var(--dsw-alias-label-tertiary);
|
|
2799
|
+
list-style: none;
|
|
2800
|
+
padding: 2px 0;
|
|
2801
|
+
}
|
|
2802
|
+
.hqpu-advanced > summary::-webkit-details-marker { display: none; }
|
|
2803
|
+
.hqpu-advanced > summary::before {
|
|
2804
|
+
content: '▸ ';
|
|
2805
|
+
}
|
|
2806
|
+
.hqpu-advanced[open] > summary::before {
|
|
2807
|
+
content: '▾ ';
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
/* Danger-toned banner (check failure, breaking changes) */
|
|
2811
|
+
.hqpu-banner[data-tone='danger'] {
|
|
2812
|
+
background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent);
|
|
2813
|
+
color: var(--dsw-alias-label-secondary);
|
|
2814
|
+
}
|
|
2815
|
+
.hqpu-banner[data-tone='danger'] > svg {
|
|
2816
|
+
color: var(--dsw-alias-state-error-primary);
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2205
2819
|
.hqpu-visually-hidden {
|
|
2206
2820
|
position: absolute;
|
|
2207
2821
|
width: 1px;
|
|
@@ -2346,33 +2960,114 @@ window.__ModuleLoader__.load({
|
|
|
2346
2960
|
var UpdateStore = class {
|
|
2347
2961
|
current = null;
|
|
2348
2962
|
installingFlag = false;
|
|
2963
|
+
/** The durable job the server currently reports as active (resumed across
|
|
2964
|
+
* panel close and DSH restart); null when idle. */
|
|
2965
|
+
active = null;
|
|
2966
|
+
recent = [];
|
|
2349
2967
|
listeners = /* @__PURE__ */ new Set();
|
|
2350
2968
|
refreshTimer = null;
|
|
2969
|
+
pollTimer = null;
|
|
2970
|
+
pollBackoffMs = 1e3;
|
|
2971
|
+
pollInFlight = false;
|
|
2351
2972
|
report() {
|
|
2352
2973
|
return this.current;
|
|
2353
2974
|
}
|
|
2354
2975
|
installing() {
|
|
2355
2976
|
return this.installingFlag;
|
|
2356
2977
|
}
|
|
2978
|
+
activeJob() {
|
|
2979
|
+
return this.active;
|
|
2980
|
+
}
|
|
2981
|
+
recentJobs() {
|
|
2982
|
+
return [...this.recent];
|
|
2983
|
+
}
|
|
2357
2984
|
subscribe(listener) {
|
|
2358
2985
|
this.listeners.add(listener);
|
|
2359
2986
|
return () => {
|
|
2360
2987
|
this.listeners.delete(listener);
|
|
2361
2988
|
};
|
|
2362
2989
|
}
|
|
2363
|
-
/**
|
|
2364
|
-
*
|
|
2365
|
-
*
|
|
2990
|
+
/**
|
|
2991
|
+
* Badge: counts actionable plugin updates AND an available runtime update AND
|
|
2992
|
+
* (while a job runs) an attention pill. A recent failed/rolled-back job with
|
|
2993
|
+
* nothing actionable left also keeps a dot, so the outcome is never silently
|
|
2994
|
+
* forgotten after the panel closes.
|
|
2995
|
+
*/
|
|
2366
2996
|
badgeCount() {
|
|
2997
|
+
const job = this.active;
|
|
2998
|
+
if (job !== null && !TERMINAL_PHASES.has(job.phase)) return 1;
|
|
2367
2999
|
const report = this.current;
|
|
2368
3000
|
if (report === null) return 0;
|
|
2369
|
-
|
|
3001
|
+
let count = report.items.filter((item) => item.state === "update" || item.state === "install").length;
|
|
3002
|
+
if (report.runtime?.updateAvailable) count += 1;
|
|
3003
|
+
if (count === 0) {
|
|
3004
|
+
const latest = this.recent[0];
|
|
3005
|
+
if (latest !== void 0 && (latest.phase === "failed" || latest.phase === "rolled-back")) count = 1;
|
|
3006
|
+
}
|
|
3007
|
+
return count;
|
|
2370
3008
|
}
|
|
2371
3009
|
emit() {
|
|
2372
3010
|
for (const listener of [...this.listeners]) try {
|
|
2373
3011
|
listener();
|
|
2374
3012
|
} catch {}
|
|
2375
3013
|
}
|
|
3014
|
+
/** Adopt a fresh `/status` snapshot and (re)arm the poll loop. */
|
|
3015
|
+
applyStatus(status) {
|
|
3016
|
+
const wasActive = this.active !== null;
|
|
3017
|
+
this.active = status.activeJob;
|
|
3018
|
+
this.recent = status.recentJobs;
|
|
3019
|
+
if (wasActive && this.active === null) this.installingFlag = false;
|
|
3020
|
+
this.emit();
|
|
3021
|
+
this.schedulePoll();
|
|
3022
|
+
}
|
|
3023
|
+
/**
|
|
3024
|
+
* Fetch the durable job status. Called on mount, on panel open (BEFORE
|
|
3025
|
+
* discovery) and by the poll loop. A failure (older hq-edge with no
|
|
3026
|
+
* `/status`, or a transient HTTP error) is swallowed: the last known job is
|
|
3027
|
+
* kept and the poller backs off and retries.
|
|
3028
|
+
*/
|
|
3029
|
+
async fetchStatus() {
|
|
3030
|
+
try {
|
|
3031
|
+
const status = await fetchJobStatus();
|
|
3032
|
+
this.pollBackoffMs = 1e3;
|
|
3033
|
+
this.applyStatus(status);
|
|
3034
|
+
} catch {
|
|
3035
|
+
this.schedulePoll();
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
/** ~1 s poll while a job is active; stop on terminal; back off on error
|
|
3039
|
+
* without discarding the last known job; never overlap requests. */
|
|
3040
|
+
schedulePoll() {
|
|
3041
|
+
if (this.pollTimer !== null) {
|
|
3042
|
+
window.clearTimeout(this.pollTimer);
|
|
3043
|
+
this.pollTimer = null;
|
|
3044
|
+
}
|
|
3045
|
+
const job = this.active;
|
|
3046
|
+
if (job === null || TERMINAL_PHASES.has(job.phase)) {
|
|
3047
|
+
this.pollBackoffMs = 1e3;
|
|
3048
|
+
return;
|
|
3049
|
+
}
|
|
3050
|
+
this.pollTimer = window.setTimeout(() => {
|
|
3051
|
+
this.pollTick();
|
|
3052
|
+
}, this.pollBackoffMs);
|
|
3053
|
+
}
|
|
3054
|
+
async pollTick() {
|
|
3055
|
+
if (this.pollInFlight) {
|
|
3056
|
+
this.schedulePoll();
|
|
3057
|
+
return;
|
|
3058
|
+
}
|
|
3059
|
+
this.pollInFlight = true;
|
|
3060
|
+
try {
|
|
3061
|
+
const status = await fetchJobStatus();
|
|
3062
|
+
this.pollBackoffMs = 1e3;
|
|
3063
|
+
this.applyStatus(status);
|
|
3064
|
+
} catch {
|
|
3065
|
+
this.pollBackoffMs = Math.min(5e3, Math.max(1e3, this.pollBackoffMs * 2));
|
|
3066
|
+
this.schedulePoll();
|
|
3067
|
+
} finally {
|
|
3068
|
+
this.pollInFlight = false;
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
2376
3071
|
async refresh() {
|
|
2377
3072
|
try {
|
|
2378
3073
|
const report = await fetchReport();
|
|
@@ -2410,6 +3105,10 @@ window.__ModuleLoader__.load({
|
|
|
2410
3105
|
window.clearInterval(this.refreshTimer);
|
|
2411
3106
|
this.refreshTimer = null;
|
|
2412
3107
|
}
|
|
3108
|
+
if (this.pollTimer !== null) {
|
|
3109
|
+
window.clearTimeout(this.pollTimer);
|
|
3110
|
+
this.pollTimer = null;
|
|
3111
|
+
}
|
|
2413
3112
|
this.listeners.clear();
|
|
2414
3113
|
}
|
|
2415
3114
|
};
|
|
@@ -2450,9 +3149,23 @@ window.__ModuleLoader__.load({
|
|
|
2450
3149
|
});
|
|
2451
3150
|
const openView = () => {
|
|
2452
3151
|
ensureDesignTokens();
|
|
2453
|
-
store.
|
|
2454
|
-
|
|
2455
|
-
|
|
3152
|
+
store.fetchStatus();
|
|
3153
|
+
openUpdateView(store, copyFor());
|
|
3154
|
+
store.check();
|
|
3155
|
+
};
|
|
3156
|
+
const statusLabel = () => {
|
|
3157
|
+
const base = entryCopy(locale);
|
|
3158
|
+
const job = store.activeJob();
|
|
3159
|
+
if (job !== null) {
|
|
3160
|
+
const cc = copyFor();
|
|
3161
|
+
const pct = job.progress.percent;
|
|
3162
|
+
const phase = cc.job.phase[job.phase] ?? job.phase;
|
|
3163
|
+
return {
|
|
3164
|
+
label: pct !== null ? `${base.label} · ${Math.round(pct)}%` : `${base.label} · ${phase}`,
|
|
3165
|
+
tooltip: `${base.tooltip} · ${phase}`
|
|
3166
|
+
};
|
|
3167
|
+
}
|
|
3168
|
+
return base;
|
|
2456
3169
|
};
|
|
2457
3170
|
const disposeEntry = mountSidebarEntry({
|
|
2458
3171
|
rowAttribute: UPDATE_ENTRY_ATTRIBUTE,
|
|
@@ -2460,8 +3173,8 @@ window.__ModuleLoader__.load({
|
|
|
2460
3173
|
plugin: PLUGIN_ID,
|
|
2461
3174
|
icon: UPDATE_ICON,
|
|
2462
3175
|
css: ENTRY_CSS,
|
|
2463
|
-
label: () =>
|
|
2464
|
-
tooltip: () =>
|
|
3176
|
+
label: () => statusLabel().label,
|
|
3177
|
+
tooltip: () => statusLabel().tooltip,
|
|
2465
3178
|
refresh: { subscribe: refreshSubscribe },
|
|
2466
3179
|
badge: {
|
|
2467
3180
|
subscribe: (listener) => store.subscribe(listener),
|
|
@@ -2471,8 +3184,14 @@ window.__ModuleLoader__.load({
|
|
|
2471
3184
|
position: "after",
|
|
2472
3185
|
familySelectors: HUAQIU_SIDEBAR_ENTRY_FAMILY
|
|
2473
3186
|
});
|
|
3187
|
+
store.subscribe(() => {
|
|
3188
|
+
for (const fn of [...localeSubscribers]) try {
|
|
3189
|
+
fn();
|
|
3190
|
+
} catch {}
|
|
3191
|
+
});
|
|
2474
3192
|
store.startIdleRefresh();
|
|
2475
3193
|
store.refresh();
|
|
3194
|
+
store.fetchStatus();
|
|
2476
3195
|
const dispose = () => {
|
|
2477
3196
|
try {
|
|
2478
3197
|
disposeEntry();
|