dsh-config-manager 0.1.47 → 0.1.49
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/README.md +15 -0
- package/README.zh-CN.md +15 -0
- package/lib/client.d.ts +70 -3
- package/lib/client.js +792 -268
- package/lib/client.js.map +1 -1
- package/lib/core/restore.d.ts +7 -0
- package/lib/core/restore.js +4 -0
- package/lib/core/restore.js.map +1 -1
- package/lib/core/run-registry.d.ts +11 -4
- package/lib/core/run-registry.js +14 -7
- package/lib/core/run-registry.js.map +1 -1
- package/lib/index.d.ts +7 -1
- package/lib/index.js +131 -19
- package/lib/index.js.map +1 -1
- package/lib/ui/backup-schedule.d.ts +49 -0
- package/lib/ui/backup-schedule.js +42 -0
- package/lib/ui/backup-schedule.js.map +1 -0
- package/package.json +1 -1
- package/src/client/api.ts +35 -1
- package/src/client/common/ConfirmDialog.tsx +4 -2
- package/src/client/common/ErrorBanner.tsx +8 -4
- package/src/client/common/ReportView.tsx +10 -3
- package/src/client/common/ui.tsx +9 -2
- package/src/client/config-manager.module.css +22 -0
- package/src/client/export/ExportView.tsx +15 -5
- package/src/client/import/ImportWizardView.tsx +75 -14
- package/src/client/locales.ts +46 -0
- package/src/client/market/MarketPanel.tsx +9 -4
- package/src/client/market/MyConfigsView.tsx +8 -8
- package/src/client/run-store.test.ts +112 -1
- package/src/client/run-store.ts +78 -4
- package/src/client/snapshots/SnapshotsPanel.tsx +261 -7
- package/src/client/sync/SyncHistoryView.tsx +16 -2
- package/src/core/restore.ts +7 -0
- package/src/core/run-registry.test.ts +40 -0
- package/src/core/run-registry.ts +16 -9
- package/src/index.ts +129 -18
- package/src/ui/backup-schedule.test.ts +53 -0
- package/src/ui/backup-schedule.ts +83 -0
package/lib/client.js
CHANGED
|
@@ -539,6 +539,8 @@ const CONFIG_MANAGER_API = {
|
|
|
539
539
|
runs: "/api/dsh-config-manager/runs",
|
|
540
540
|
snapshots: "/api/dsh-config-manager/snapshots",
|
|
541
541
|
restore: "/api/dsh-config-manager/restore",
|
|
542
|
+
backupSchedule: "/api/dsh-config-manager/backup-schedule",
|
|
543
|
+
backupScheduleRun: "/api/dsh-config-manager/backup-schedule/run",
|
|
542
544
|
starPrompt: "/api/dsh-config-manager/star-prompt"
|
|
543
545
|
};
|
|
544
546
|
/** 导出请求超时(ms):与 Host 半 ROUTE_TIMEOUT_MS 对齐,防止宿主卡死时 UI 无限等待 */
|
|
@@ -784,6 +786,26 @@ var ConfigManagerApi = class {
|
|
|
784
786
|
})
|
|
785
787
|
}), this.t);
|
|
786
788
|
}
|
|
789
|
+
/** 读取定时备份配置(enabled / interval / 上次运行状态;无敏感字段)。 */
|
|
790
|
+
async backupSchedule() {
|
|
791
|
+
return (await readJson$3(await fetch(CONFIG_MANAGER_API.backupSchedule), this.t)).schedule;
|
|
792
|
+
}
|
|
793
|
+
/** 保存定时备份设置(enabled + interval);Host 校验后原子写 + 重排调度器。 */
|
|
794
|
+
async saveBackupSchedule(draft) {
|
|
795
|
+
return (await readJson$3(await fetch(CONFIG_MANAGER_API.backupSchedule, {
|
|
796
|
+
method: "PUT",
|
|
797
|
+
headers: { "content-type": "application/json" },
|
|
798
|
+
body: JSON.stringify(draft)
|
|
799
|
+
}), this.t)).schedule;
|
|
800
|
+
}
|
|
801
|
+
/** 立即执行一次全量备份(复用调度器 runOnce,防重;返回执行结果 + 最新配置)。 */
|
|
802
|
+
async runBackupNow() {
|
|
803
|
+
return readJson$3(await fetch(CONFIG_MANAGER_API.backupScheduleRun, {
|
|
804
|
+
method: "POST",
|
|
805
|
+
headers: { "content-type": "application/json" },
|
|
806
|
+
body: JSON.stringify({})
|
|
807
|
+
}), this.t);
|
|
808
|
+
}
|
|
787
809
|
};
|
|
788
810
|
//#endregion
|
|
789
811
|
//#region src/ui/conflict-view.ts
|
|
@@ -2300,9 +2322,11 @@ function defaultSnapshotsState() {
|
|
|
2300
2322
|
return {
|
|
2301
2323
|
selectedId: null,
|
|
2302
2324
|
plan: null,
|
|
2325
|
+
running: false,
|
|
2303
2326
|
report: null,
|
|
2304
2327
|
actionError: null,
|
|
2305
|
-
error: null
|
|
2328
|
+
error: null,
|
|
2329
|
+
backupDraft: null
|
|
2306
2330
|
};
|
|
2307
2331
|
}
|
|
2308
2332
|
function defaultState() {
|
|
@@ -2379,9 +2403,11 @@ function toSnapshotsStoreSlice(s) {
|
|
|
2379
2403
|
return {
|
|
2380
2404
|
selectedId: s.selectedId,
|
|
2381
2405
|
plan: s.plan,
|
|
2406
|
+
running: s.running,
|
|
2382
2407
|
report: s.report,
|
|
2383
2408
|
actionError: s.actionError,
|
|
2384
|
-
error: s.error
|
|
2409
|
+
error: s.error,
|
|
2410
|
+
backupDraft: s.backupDraft
|
|
2385
2411
|
};
|
|
2386
2412
|
}
|
|
2387
2413
|
/**
|
|
@@ -2416,7 +2442,10 @@ function toPersistedState(state) {
|
|
|
2416
2442
|
}
|
|
2417
2443
|
},
|
|
2418
2444
|
market: state.market,
|
|
2419
|
-
snapshots:
|
|
2445
|
+
snapshots: {
|
|
2446
|
+
...state.snapshots,
|
|
2447
|
+
running: false
|
|
2448
|
+
}
|
|
2420
2449
|
};
|
|
2421
2450
|
}
|
|
2422
2451
|
/** 解析 + 轻量校验持久化状态;损坏/版本不符返回 null(调用方回退默认并清键)。 */
|
|
@@ -2701,7 +2730,8 @@ var RunStore = class {
|
|
|
2701
2730
|
},
|
|
2702
2731
|
snapshots: {
|
|
2703
2732
|
...defaultSnapshotsState(),
|
|
2704
|
-
...parsed.snapshots
|
|
2733
|
+
...parsed.snapshots,
|
|
2734
|
+
running: false
|
|
2705
2735
|
}
|
|
2706
2736
|
};
|
|
2707
2737
|
if (this.state.import.containerEncrypted && this.state.import.phase !== "preview" && this.state.import.phase !== "decrypt-archive") this.state.import.phase = "decrypt-archive";
|
|
@@ -2799,6 +2829,18 @@ var RunStore = class {
|
|
|
2799
2829
|
error: "上次导入任务已结束或超过保留期,结果无法恢复,请重新导入"
|
|
2800
2830
|
} });
|
|
2801
2831
|
}
|
|
2832
|
+
const restoreRun = active.find((r) => r.kind === "restore");
|
|
2833
|
+
if (restoreRun !== void 0) {
|
|
2834
|
+
resumed = true;
|
|
2835
|
+
this.patch({ snapshots: {
|
|
2836
|
+
running: true,
|
|
2837
|
+
actionError: null
|
|
2838
|
+
} });
|
|
2839
|
+
this.pollRun(restoreRun.runId, "restore");
|
|
2840
|
+
} else if (this.state.snapshots.running) this.patch({ snapshots: {
|
|
2841
|
+
running: false,
|
|
2842
|
+
actionError: "上次恢复任务已结束但结果无法恢复,请重新执行"
|
|
2843
|
+
} });
|
|
2802
2844
|
return resumed;
|
|
2803
2845
|
}
|
|
2804
2846
|
/** 停止全部轮询(视图卸载时调用;服务端执行不受影响,重开面板再 resume)。 */
|
|
@@ -2886,7 +2928,11 @@ var RunStore = class {
|
|
|
2886
2928
|
patchProgress(kind, state) {
|
|
2887
2929
|
const event = mapRunProgress(kind, state);
|
|
2888
2930
|
if (kind === "export") this.patch({ export: { progress: event } });
|
|
2889
|
-
else this.patch({
|
|
2931
|
+
else if (kind === "restore") this.patch({ snapshots: { running: true } });
|
|
2932
|
+
else this.patch({ import: {
|
|
2933
|
+
progress: event,
|
|
2934
|
+
runId: state.runId
|
|
2935
|
+
} });
|
|
2890
2936
|
}
|
|
2891
2937
|
/** run 完成/失败:把 RunState.result 回填 store(并镜像回控制器)。 */
|
|
2892
2938
|
applySettled(kind, state) {
|
|
@@ -2914,6 +2960,19 @@ var RunStore = class {
|
|
|
2914
2960
|
} });
|
|
2915
2961
|
return;
|
|
2916
2962
|
}
|
|
2963
|
+
if (kind === "restore") {
|
|
2964
|
+
const result = state.result;
|
|
2965
|
+
if (state.status === "done" && result !== void 0 && typeof result === "object") this.patch({ snapshots: {
|
|
2966
|
+
running: false,
|
|
2967
|
+
report: result,
|
|
2968
|
+
actionError: null
|
|
2969
|
+
} });
|
|
2970
|
+
else this.patch({ snapshots: {
|
|
2971
|
+
running: false,
|
|
2972
|
+
actionError: state.error ?? "恢复失败(结果不可用)"
|
|
2973
|
+
} });
|
|
2974
|
+
return;
|
|
2975
|
+
}
|
|
2917
2976
|
const result = state.result;
|
|
2918
2977
|
if (state.status === "done" && result !== void 0 && typeof result === "object") {
|
|
2919
2978
|
const wizard = this.importWizardInst;
|
|
@@ -2959,6 +3018,10 @@ var RunStore = class {
|
|
|
2959
3018
|
runId: null,
|
|
2960
3019
|
error: "任务已结束或超过保留期,进度不可恢复"
|
|
2961
3020
|
} });
|
|
3021
|
+
else if (kind === "restore") this.patch({ snapshots: {
|
|
3022
|
+
running: false,
|
|
3023
|
+
actionError: "恢复任务已结束或超过保留期,进度不可恢复"
|
|
3024
|
+
} });
|
|
2962
3025
|
else {
|
|
2963
3026
|
this.importWizardInst?.reset();
|
|
2964
3027
|
this.patch({ import: {
|
|
@@ -2979,7 +3042,7 @@ var RunStore = class {
|
|
|
2979
3042
|
const runStore = new RunStore();
|
|
2980
3043
|
//#endregion
|
|
2981
3044
|
//#region \0config-manager-css:/home/runner/work/dsh-config-manager/dsh-config-manager/src/client/config-manager.module.css.js
|
|
2982
|
-
const css = "._367UGW_section{height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:10px;font-size:13px;display:flex}._367UGW_sectionHeader{flex:none;align-items:center;gap:10px;display:flex}._367UGW_viewTabs{border-bottom:1px solid var(--dsw-alias-border-l1);flex:1;gap:2px;display:flex}._367UGW_viewTab{color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-bottom:2px solid #0000;border-radius:6px 6px 0 0;padding:7px 14px;font-size:13px}._367UGW_viewTab:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}._367UGW_viewTab[data-active]{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-business-primary);font-weight:600}._367UGW_sectionBody{flex-direction:column;flex:1;min-height:0;display:flex;overflow-y:auto}._367UGW_viewBody{flex-direction:column;gap:12px;padding:4px 2px 16px;display:flex}._367UGW_sectionTitleBlock{flex-direction:column;gap:2px;display:flex}._367UGW_sectionTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:15px;font-weight:700}._367UGW_sectionSubtitle{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}._367UGW_primaryButton{color:var(--dsw-alias-label-primary-foreground);background:var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;align-items:center;gap:6px;padding:6px 14px;font-size:13px;font-weight:600;display:inline-flex}._367UGW_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-info-hover)}._367UGW_primaryButton:disabled{opacity:.5;cursor:default}._367UGW_ghostButton{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;align-items:center;gap:6px;padding:5px 12px;font-size:12px;display:inline-flex}._367UGW_ghostButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._367UGW_ghostButton:disabled{opacity:.45;cursor:default}._367UGW_dangerButton{color:var(--dsw-alias-state-error-primary);border:1px solid var(--dsw-alias-state-error-primary);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;padding:5px 12px;font-size:12px}._367UGW_dangerButton:hover:not(:disabled){background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-label-primary-foreground)}._367UGW_dangerButton:disabled{opacity:.45;cursor:default}._367UGW_badge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:11px;line-height:1.6;display:inline-block}._367UGW_badgeInfo{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}._367UGW_badgeOk{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._367UGW_badgeWarn{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._367UGW_badgeError{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._367UGW_banner{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;border-radius:8px;padding:8px 12px;font-size:12.5px;line-height:1.5}._367UGW_banner[data-kind=ok]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._367UGW_banner[data-kind=error]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._367UGW_banner[data-kind=info]{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}._367UGW_banner[data-kind=warn]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._367UGW_card{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex-direction:column;flex:none;gap:10px;padding:12px;display:flex}._367UGW_wizardCard{gap:14px}._367UGW_wizardCard ._367UGW_field{gap:8px}._367UGW_field{flex-direction:column;gap:5px;display:flex}._367UGW_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}._367UGW_input{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;outline:none;padding:7px 10px;font-family:inherit;font-size:13px}._367UGW_input:focus{border-color:var(--dsw-alias-state-business-primary)}._367UGW_input::placeholder{color:var(--dsw-alias-label-tertiary)}._367UGW_input:disabled{opacity:.55}._367UGW_select{-webkit-appearance:none;appearance:none;color:var(--dsw-alias-label-primary);background-color:var(--dsw-specific-input-major);background-image:linear-gradient(45deg, transparent 50%, var(--dsw-alias-label-secondary) 50%), linear-gradient(135deg, var(--dsw-alias-label-secondary) 50%, transparent 50%);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;background-position:calc(100% - 17px) calc(50% - 1px),calc(100% - 12px) calc(50% - 1px);background-repeat:no-repeat;background-size:5px 5px,5px 5px;border-radius:8px;outline:none;max-width:100%;padding:7px 30px 7px 10px;font-family:inherit;font-size:13px}._367UGW_select:hover:not(:disabled){border-color:var(--dsw-alias-border-l1)}._367UGW_select:focus{border-color:var(--dsw-alias-state-business-primary)}._367UGW_select:disabled{opacity:.55;cursor:default}._367UGW_select option{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}._367UGW_hint{color:var(--dsw-alias-label-tertiary);font-size:11.5px;line-height:1.5}._367UGW_formError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}._367UGW_checkboxRow{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:flex-start;gap:8px;font-size:13px;display:flex}._367UGW_checkboxRow input{flex:none;margin-top:2px}._367UGW_radioLabel{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:center;gap:6px;font-size:13px;display:inline-flex}._367UGW_secretFields{grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px 12px;padding-top:4px;display:grid}._367UGW_hiddenFile{display:none}._367UGW_modeTabs{border-bottom:1px solid var(--dsw-alias-border-l1);flex:none;gap:2px;display:flex}._367UGW_modeTab{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-bottom:2px solid #0000;padding:6px 12px;font-size:12.5px}._367UGW_modeTab:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}._367UGW_modeTab[data-active]{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-business-primary);font-weight:600}._367UGW_modeHint{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5}._367UGW_groupList{flex-direction:column;gap:10px;display:flex}._367UGW_groupCard{gap:8px}._367UGW_groupHeader{flex-wrap:wrap;align-items:baseline;gap:8px;display:flex}._367UGW_groupLabel{color:var(--dsw-alias-label-primary);font-size:12.5px;font-weight:700}._367UGW_groupNote{color:var(--dsw-alias-label-tertiary);font-size:11.5px}._367UGW_groupItems{flex-direction:column;gap:6px;display:flex}._367UGW_categoryItem{flex-wrap:wrap;align-items:center;gap:8px;display:inline-flex}._367UGW_categoryName{font-weight:500}._367UGW_categoryDesc{color:var(--dsw-alias-label-tertiary);font-size:11.5px}._367UGW_optionsCard{gap:8px}._367UGW_optionsHeader{color:var(--dsw-alias-label-primary);letter-spacing:.02em;margin-bottom:2px;font-size:12px;font-weight:600}._367UGW_warnList{margin:4px 0 0;padding-left:18px}._367UGW_actionRow{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._367UGW_progressBlock{flex-direction:column;flex:none;gap:6px;display:flex}._367UGW_progressMeta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:12px;font-size:12px;display:flex}._367UGW_progressLabel{font-weight:600}._367UGW_progressDetail{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;max-width:320px;overflow:hidden}._367UGW_progressBadge{font-variant-numeric:tabular-nums;white-space:nowrap;border-radius:999px;align-items:center;gap:2px;padding:1px 8px;font-size:11px;line-height:18px;display:inline-flex}._367UGW_progressBadgeSection{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 14%, transparent);color:var(--dsw-alias-state-business-primary)}._367UGW_progressBadgeCount{background:color-mix(in srgb, var(--dsw-alias-state-info-primary) 14%, transparent);color:var(--dsw-alias-state-info-primary)}._367UGW_progressPercent{font-variant-numeric:tabular-nums;margin-left:auto}._367UGW_progressTrack{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;height:8px;overflow:hidden}._367UGW_progressBar{background:var(--dsw-alias-state-business-primary);border-radius:999px;height:100%;transition:width .12s linear}._367UGW_progressBarDone{background:var(--dsw-alias-state-success-primary)}._367UGW_progressIndeterminate{width:40%;animation:1s ease-in-out infinite _367UGW_dshCmIndeterminate}._367UGW_logPanel{flex-direction:column;flex:none;gap:6px;display:flex}._367UGW_logHeader{color:var(--dsw-alias-label-secondary);font-size:12.5px;font-weight:600}._367UGW_logScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;max-height:200px;padding:2px 10px 6px;overflow-y:auto}._367UGW_logLine{white-space:pre-wrap;word-break:break-all;color:var(--dsw-alias-label-secondary);padding:1px 0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;line-height:1.6}._367UGW_logEmpty{color:var(--dsw-alias-label-tertiary);padding:6px 0;font-size:11.5px}@keyframes _367UGW_dshCmIndeterminate{0%{margin-left:-40%}to{margin-left:100%}}._367UGW_reportView{flex-direction:column;gap:10px;display:flex}._367UGW_statRow{flex-wrap:wrap;align-items:center;gap:6px;display:flex}._367UGW_reportText{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;max-height:260px;color:var(--dsw-alias-label-primary);border-radius:8px;margin:0;padding:10px 12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;line-height:1.6;overflow:auto}._367UGW_reportFooter{justify-content:flex-end;gap:8px;display:flex}._367UGW_rollbackBox{border:1px solid var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-state-warn-primary);border-radius:8px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._367UGW_rollbackBox ._367UGW_reportText{border-color:var(--dsw-alias-border-l2)}._367UGW_errorBanner{border:1px solid var(--dsw-alias-state-error-primary);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border-radius:8px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._367UGW_errorTitle{color:var(--dsw-alias-state-error-primary);font-size:13px;font-weight:700}._367UGW_errorReason{white-space:pre-wrap;word-break:break-all;color:var(--dsw-alias-label-secondary);margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px}._367UGW_errorAction{color:var(--dsw-alias-label-secondary);font-size:12.5px}._367UGW_errorActionLabel{color:var(--dsw-alias-state-business-primary)}._367UGW_errorItem{color:var(--dsw-alias-label-tertiary);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px}._367UGW_errorFooter{justify-content:flex-end;display:flex}._367UGW_errorList{flex-direction:column;gap:4px;display:flex}._367UGW_errorLine{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;color:var(--dsw-alias-state-error-primary);border-radius:6px;margin:0;padding:6px 10px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}._367UGW_conflictList{flex-direction:column;gap:10px;display:flex}._367UGW_conflictItem{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._367UGW_conflictHead{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._367UGW_conflictId{color:var(--dsw-alias-label-primary);font-size:12.5px;font-weight:600}._367UGW_severityError{color:var(--dsw-alias-state-error-primary);border:1px solid var(--dsw-alias-state-error-primary);border-radius:999px;padding:0 6px;font-size:10.5px}._367UGW_conflictDetail{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;color:var(--dsw-alias-label-secondary);border-radius:6px;margin:0;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}._367UGW_conflictOptions{flex-wrap:wrap;gap:14px;display:flex}._367UGW_pathMappingList{flex-direction:column;gap:8px;display:flex}._367UGW_pathRow{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:10px;padding:10px 12px;display:grid}._367UGW_pathOld,._367UGW_pathNew{flex-direction:column;gap:4px;min-width:0;display:flex}._367UGW_pathValue{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;overflow:hidden}._367UGW_pathIssueKind{color:var(--dsw-alias-state-warn-primary);font-size:10.5px}._367UGW_secretsList{flex-direction:column;gap:10px;display:flex}._367UGW_empty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:22px 12px;font-size:12.5px}._367UGW_spinnerWrap{align-items:center;gap:6px;display:inline-flex}._367UGW_spinner{border:2px solid var(--dsw-alias-state-business-primary);vertical-align:-1px;border-top-color:#0000;border-radius:50%;flex:none;width:11px;height:11px;animation:.8s linear infinite _367UGW_dshCmSpin;display:inline-block}._367UGW_spinnerLabel{color:var(--dsw-alias-label-secondary);font-size:12px}@keyframes _367UGW_dshCmSpin{to{transform:rotate(360deg)}}._367UGW_snapshotList{flex-direction:column;gap:2px;display:flex}._367UGW_snapshotRowHeader,._367UGW_snapshotRow{grid-template-columns:minmax(120px,1.2fr) minmax(140px,1.6fr) 90px 56px 56px;align-items:center;gap:8px;padding:6px 10px;font-size:12px;display:grid}._367UGW_snapshotRowHeader{color:var(--dsw-alias-label-tertiary);border-bottom:1px solid var(--dsw-alias-border-l1);font-size:11px}._367UGW_snapshotRow{text-align:left;width:100%;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px}._367UGW_snapshotRow:hover{background:var(--dsw-alias-interactive-bg-hover)}._367UGW_snapshotRow[data-active]{border-color:var(--dsw-alias-state-business-primary);background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 10%, transparent)}._367UGW_warnText{color:var(--dsw-alias-state-warn-primary)}._367UGW_reportList{color:var(--dsw-alias-label-primary);flex-direction:column;gap:3px;margin:8px 0 4px;padding-left:18px;font-size:12px;display:flex}._367UGW_kindTag{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);border-radius:4px;padding:1px 6px;font-size:10.5px;display:inline-block}._367UGW_planScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;height:280px;padding:2px 10px 8px;overflow-y:auto}._367UGW_planScroll ._367UGW_reportList{margin-top:6px}._367UGW_reportScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;max-height:220px;padding:2px 10px 6px;overflow-y:auto}._367UGW_reportScroll ._367UGW_reportList{margin-top:4px}._367UGW_confirmScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;max-height:280px;padding:2px 10px 6px;overflow-y:auto}._367UGW_confirmScroll ._367UGW_reportList{margin-top:4px;margin-bottom:0}._367UGW_pullScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;height:280px;padding:2px 10px 6px;overflow-y:auto}._367UGW_pullScroll ._367UGW_reportList{margin-top:4px;margin-bottom:0}._367UGW_rowActions{gap:8px;margin-top:10px;display:flex}._367UGW_diffScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;max-height:240px;color:var(--dsw-alias-label-primary);border-radius:8px;margin:0;padding:8px 10px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;line-height:1.6;overflow:auto}._367UGW_aboutLinkRow{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._367UGW_aboutAuthor{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:6px;padding:4px 8px;font-size:13px;text-decoration:none;display:inline-flex}._367UGW_aboutAuthor:hover{background:var(--dsw-alias-interactive-bg-hover)}._367UGW_dialogMask{z-index:100;background:color-mix(in srgb, var(--dsw-alias-bg-base) 55%, transparent);justify-content:center;align-items:center;padding:20px;display:flex;position:fixed;inset:0}._367UGW_dialogCard{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:min(420px,100%);max-height:80vh;box-shadow:0 8px 24px color-mix(in srgb, var(--dsw-alias-bg-base) 40%, transparent);border-radius:10px;flex-direction:column;gap:10px;padding:14px;display:flex}._367UGW_dialogHeader{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600}._367UGW_dialogBody{overscroll-behavior:contain;max-height:240px;color:var(--dsw-alias-label-primary);white-space:pre-wrap;flex-direction:column;gap:10px;font-size:13px;line-height:1.6;display:flex;overflow-y:auto}._367UGW_dialogWide{width:min(560px,100%)}._367UGW_dialogBodyScroll{overscroll-behavior:contain;max-height:70vh;color:var(--dsw-alias-label-primary);white-space:normal;flex-direction:column;gap:10px;font-size:13px;line-height:1.6;display:flex;overflow-y:auto}._367UGW_dialogHeaderRow{justify-content:space-between;align-items:center;gap:10px;display:flex}._367UGW_dialogClose{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;padding:2px 8px;font-size:16px;line-height:1}._367UGW_dialogClose:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-hover)}";
|
|
3045
|
+
const css = "._367UGW_section{height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:10px;font-size:13px;display:flex}._367UGW_sectionHeader{flex:none;align-items:center;gap:10px;display:flex}._367UGW_viewTabs{border-bottom:1px solid var(--dsw-alias-border-l1);flex:1;gap:2px;display:flex}._367UGW_viewTab{color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-bottom:2px solid #0000;border-radius:6px 6px 0 0;padding:7px 14px;font-size:13px}._367UGW_viewTab:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}._367UGW_viewTab[data-active]{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-business-primary);font-weight:600}._367UGW_sectionBody{flex-direction:column;flex:1;min-height:0;display:flex;overflow-y:auto}._367UGW_viewBody{flex-direction:column;gap:12px;padding:4px 2px 16px;display:flex}._367UGW_sectionTitleBlock{flex-direction:column;gap:2px;display:flex}._367UGW_sectionTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:15px;font-weight:700}._367UGW_sectionSubtitle{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}._367UGW_primaryButton{color:var(--dsw-alias-label-primary-foreground);background:var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;align-items:center;gap:6px;padding:6px 14px;font-size:13px;font-weight:600;display:inline-flex}._367UGW_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-info-hover)}._367UGW_primaryButton:disabled{opacity:.5;cursor:default}._367UGW_ghostButton{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;align-items:center;gap:6px;padding:5px 12px;font-size:12px;display:inline-flex}._367UGW_ghostButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._367UGW_ghostButton:disabled{opacity:.45;cursor:default}._367UGW_dangerButton{color:var(--dsw-alias-state-error-primary);border:1px solid var(--dsw-alias-state-error-primary);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;padding:5px 12px;font-size:12px}._367UGW_dangerButton:hover:not(:disabled){background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-label-primary-foreground)}._367UGW_dangerButton:disabled{opacity:.45;cursor:default}._367UGW_badge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:11px;line-height:1.6;display:inline-block}._367UGW_badgeInfo{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}._367UGW_badgeOk{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._367UGW_badgeWarn{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._367UGW_badgeError{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._367UGW_banner{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;border-radius:8px;padding:8px 12px;font-size:12.5px;line-height:1.5}._367UGW_banner[data-kind=ok]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._367UGW_banner[data-kind=error]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._367UGW_banner[data-kind=info]{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}._367UGW_banner[data-kind=warn]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._367UGW_card{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex-direction:column;flex:none;gap:10px;padding:12px;display:flex}._367UGW_wizardCard{gap:14px}._367UGW_wizardCard ._367UGW_field{gap:8px}._367UGW_field{flex-direction:column;gap:5px;display:flex}._367UGW_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}._367UGW_input{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;outline:none;padding:7px 10px;font-family:inherit;font-size:13px}._367UGW_input:focus{border-color:var(--dsw-alias-state-business-primary)}._367UGW_input::placeholder{color:var(--dsw-alias-label-tertiary)}._367UGW_input:disabled{opacity:.55}._367UGW_select{-webkit-appearance:none;appearance:none;color:var(--dsw-alias-label-primary);background-color:var(--dsw-specific-input-major);background-image:linear-gradient(45deg, transparent 50%, var(--dsw-alias-label-secondary) 50%), linear-gradient(135deg, var(--dsw-alias-label-secondary) 50%, transparent 50%);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;background-position:calc(100% - 17px) calc(50% - 1px),calc(100% - 12px) calc(50% - 1px);background-repeat:no-repeat;background-size:5px 5px,5px 5px;border-radius:8px;outline:none;max-width:100%;padding:7px 30px 7px 10px;font-family:inherit;font-size:13px}._367UGW_select:hover:not(:disabled){border-color:var(--dsw-alias-border-l1)}._367UGW_select:focus{border-color:var(--dsw-alias-state-business-primary)}._367UGW_select:disabled{opacity:.55;cursor:default}._367UGW_select option{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}._367UGW_hint{color:var(--dsw-alias-label-tertiary);font-size:11.5px;line-height:1.5}._367UGW_formError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}._367UGW_checkboxRow{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:flex-start;gap:8px;font-size:13px;display:flex}._367UGW_checkboxRow input{flex:none;margin-top:2px}._367UGW_radioLabel{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:center;gap:6px;font-size:13px;display:inline-flex}._367UGW_secretFields{grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px 12px;padding-top:4px;display:grid}._367UGW_hiddenFile{display:none}._367UGW_modeTabs{border-bottom:1px solid var(--dsw-alias-border-l1);flex:none;gap:2px;display:flex}._367UGW_modeTab{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-bottom:2px solid #0000;padding:6px 12px;font-size:12.5px}._367UGW_modeTab:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}._367UGW_modeTab[data-active]{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-business-primary);font-weight:600}._367UGW_modeHint{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5}._367UGW_groupList{flex-direction:column;gap:10px;display:flex}._367UGW_groupCard{gap:8px}._367UGW_groupHeader{flex-wrap:wrap;align-items:baseline;gap:8px;display:flex}._367UGW_groupLabel{color:var(--dsw-alias-label-primary);font-size:12.5px;font-weight:700}._367UGW_groupNote{color:var(--dsw-alias-label-tertiary);font-size:11.5px}._367UGW_groupItems{flex-direction:column;gap:6px;display:flex}._367UGW_categoryItem{flex-wrap:wrap;align-items:center;gap:8px;display:inline-flex}._367UGW_categoryName{font-weight:500}._367UGW_categoryDesc{color:var(--dsw-alias-label-tertiary);font-size:11.5px}._367UGW_optionsCard{gap:8px}._367UGW_optionsHeader{color:var(--dsw-alias-label-primary);letter-spacing:.02em;margin-bottom:2px;font-size:12px;font-weight:600}._367UGW_warnList{margin:4px 0 0;padding-left:18px}._367UGW_actionRow{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._367UGW_progressBlock{flex-direction:column;flex:none;gap:6px;display:flex}._367UGW_progressMeta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:12px;font-size:12px;display:flex}._367UGW_progressLabel{font-weight:600}._367UGW_progressDetail{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;max-width:320px;overflow:hidden}._367UGW_progressBadge{font-variant-numeric:tabular-nums;white-space:nowrap;border-radius:999px;align-items:center;gap:2px;padding:1px 8px;font-size:11px;line-height:18px;display:inline-flex}._367UGW_progressBadgeSection{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 14%, transparent);color:var(--dsw-alias-state-business-primary)}._367UGW_progressBadgeCount{background:color-mix(in srgb, var(--dsw-alias-state-info-primary) 14%, transparent);color:var(--dsw-alias-state-info-primary)}._367UGW_progressPercent{font-variant-numeric:tabular-nums;margin-left:auto}._367UGW_progressTrack{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;height:8px;overflow:hidden}._367UGW_progressBar{background:var(--dsw-alias-state-business-primary);border-radius:999px;height:100%;transition:width .12s linear}._367UGW_progressBarDone{background:var(--dsw-alias-state-success-primary)}._367UGW_progressIndeterminate{width:40%;animation:1s ease-in-out infinite _367UGW_dshCmIndeterminate}._367UGW_logPanel{flex-direction:column;flex:none;gap:6px;display:flex}._367UGW_logHeader{color:var(--dsw-alias-label-secondary);align-items:center;gap:8px;font-size:12.5px;font-weight:600;display:flex}._367UGW_logJumpButton{font-family:var(--dsw-font-family);color:var(--dsw-alias-label-secondary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;background:0 0;border-radius:999px;padding:3px 8px;font-size:11px;line-height:1}._367UGW_logJumpButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._367UGW_logScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;max-height:200px;padding:2px 10px 6px;overflow-y:auto}._367UGW_logLine{white-space:pre-wrap;word-break:break-all;color:var(--dsw-alias-label-secondary);padding:1px 0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;line-height:1.6}._367UGW_logEmpty{color:var(--dsw-alias-label-tertiary);padding:6px 0;font-size:11.5px}@keyframes _367UGW_dshCmIndeterminate{0%{margin-left:-40%}to{margin-left:100%}}._367UGW_reportView{flex-direction:column;gap:10px;display:flex}._367UGW_statRow{flex-wrap:wrap;align-items:center;gap:6px;display:flex}._367UGW_reportText{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;max-height:260px;color:var(--dsw-alias-label-primary);border-radius:8px;margin:0;padding:10px 12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;line-height:1.6;overflow:auto}._367UGW_reportFooter{justify-content:flex-end;gap:8px;display:flex}._367UGW_rollbackBox{border:1px solid var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-state-warn-primary);border-radius:8px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._367UGW_rollbackBox ._367UGW_reportText{border-color:var(--dsw-alias-border-l2)}._367UGW_errorBanner{border:1px solid var(--dsw-alias-state-error-primary);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border-radius:8px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._367UGW_errorTitle{color:var(--dsw-alias-state-error-primary);font-size:13px;font-weight:700}._367UGW_errorReason{white-space:pre-wrap;word-break:break-all;color:var(--dsw-alias-label-secondary);margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px}._367UGW_errorAction{color:var(--dsw-alias-label-secondary);font-size:12.5px}._367UGW_errorActionLabel{color:var(--dsw-alias-state-business-primary)}._367UGW_errorItem{color:var(--dsw-alias-label-tertiary);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px}._367UGW_errorFooter{justify-content:flex-end;display:flex}._367UGW_errorList{flex-direction:column;gap:4px;display:flex}._367UGW_errorLine{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;color:var(--dsw-alias-state-error-primary);border-radius:6px;margin:0;padding:6px 10px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}._367UGW_conflictList{flex-direction:column;gap:10px;display:flex}._367UGW_conflictItem{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._367UGW_conflictHead{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._367UGW_conflictId{color:var(--dsw-alias-label-primary);font-size:12.5px;font-weight:600}._367UGW_severityError{color:var(--dsw-alias-state-error-primary);border:1px solid var(--dsw-alias-state-error-primary);border-radius:999px;padding:0 6px;font-size:10.5px}._367UGW_conflictDetail{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;color:var(--dsw-alias-label-secondary);border-radius:6px;margin:0;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}._367UGW_conflictOptions{flex-wrap:wrap;gap:14px;display:flex}._367UGW_pathMappingList{flex-direction:column;gap:8px;display:flex}._367UGW_pathRow{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:10px;padding:10px 12px;display:grid}._367UGW_pathOld,._367UGW_pathNew{flex-direction:column;gap:4px;min-width:0;display:flex}._367UGW_pathValue{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;overflow:hidden}._367UGW_pathIssueKind{color:var(--dsw-alias-state-warn-primary);font-size:10.5px}._367UGW_secretsList{flex-direction:column;gap:10px;display:flex}._367UGW_empty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:22px 12px;font-size:12.5px}._367UGW_spinnerWrap{align-items:center;gap:6px;display:inline-flex}._367UGW_spinner{border:2px solid var(--dsw-alias-state-business-primary);vertical-align:-1px;border-top-color:#0000;border-radius:50%;flex:none;width:11px;height:11px;animation:.8s linear infinite _367UGW_dshCmSpin;display:inline-block}._367UGW_spinnerLabel{color:var(--dsw-alias-label-secondary);font-size:12px}@keyframes _367UGW_dshCmSpin{to{transform:rotate(360deg)}}._367UGW_snapshotList{flex-direction:column;gap:2px;display:flex}._367UGW_snapshotRowHeader,._367UGW_snapshotRow{grid-template-columns:minmax(120px,1.2fr) minmax(140px,1.6fr) 90px 56px 56px;align-items:center;gap:8px;padding:6px 10px;font-size:12px;display:grid}._367UGW_snapshotRowHeader{color:var(--dsw-alias-label-tertiary);border-bottom:1px solid var(--dsw-alias-border-l1);font-size:11px}._367UGW_snapshotRow{text-align:left;width:100%;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px}._367UGW_snapshotRow:hover{background:var(--dsw-alias-interactive-bg-hover)}._367UGW_snapshotRow[data-active]{border-color:var(--dsw-alias-state-business-primary);background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 10%, transparent)}._367UGW_warnText{color:var(--dsw-alias-state-warn-primary)}._367UGW_reportList{color:var(--dsw-alias-label-primary);flex-direction:column;gap:3px;margin:8px 0 4px;padding-left:18px;font-size:12px;display:flex}._367UGW_kindTag{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);border-radius:4px;padding:1px 6px;font-size:10.5px;display:inline-block}._367UGW_planScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;height:280px;padding:2px 10px 8px;overflow-y:auto}._367UGW_planScroll ._367UGW_reportList{margin-top:6px}._367UGW_reportScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;max-height:220px;padding:2px 10px 6px;overflow-y:auto}._367UGW_reportScroll ._367UGW_reportList{margin-top:4px}._367UGW_confirmScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;max-height:280px;padding:2px 10px 6px;overflow-y:auto}._367UGW_confirmScroll ._367UGW_reportList{margin-top:4px;margin-bottom:0}._367UGW_pullScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;height:280px;padding:2px 10px 6px;overflow-y:auto}._367UGW_pullScroll ._367UGW_reportList{margin-top:4px;margin-bottom:0}._367UGW_rowActions{gap:8px;margin-top:10px;display:flex}._367UGW_diffScroll{overscroll-behavior:contain;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;max-height:240px;color:var(--dsw-alias-label-primary);border-radius:8px;margin:0;padding:8px 10px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;line-height:1.6;overflow:auto}._367UGW_aboutLinkRow{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._367UGW_aboutAuthor{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:6px;padding:4px 8px;font-size:13px;text-decoration:none;display:inline-flex}._367UGW_aboutAuthor:hover{background:var(--dsw-alias-interactive-bg-hover)}._367UGW_dialogMask{z-index:100;background:color-mix(in srgb, var(--dsw-alias-bg-base) 55%, transparent);justify-content:center;align-items:center;padding:20px;display:flex;position:fixed;inset:0}._367UGW_dialogCard{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:min(420px,100%);max-height:80vh;box-shadow:0 8px 24px color-mix(in srgb, var(--dsw-alias-bg-base) 40%, transparent);border-radius:10px;flex-direction:column;gap:10px;padding:14px;display:flex}._367UGW_dialogHeader{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600}._367UGW_dialogBody{overscroll-behavior:contain;max-height:240px;color:var(--dsw-alias-label-primary);white-space:pre-wrap;flex-direction:column;gap:10px;font-size:13px;line-height:1.6;display:flex;overflow-y:auto}._367UGW_dialogWide{width:min(560px,100%)}._367UGW_dialogBodyScroll{overscroll-behavior:contain;max-height:70vh;color:var(--dsw-alias-label-primary);white-space:normal;flex-direction:column;gap:10px;font-size:13px;line-height:1.6;display:flex;overflow-y:auto}._367UGW_dialogHeaderRow{justify-content:space-between;align-items:center;gap:10px;display:flex}._367UGW_dialogClose{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;padding:2px 8px;font-size:16px;line-height:1}._367UGW_dialogClose:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-hover)}";
|
|
2983
3046
|
const tagId = "dsh-config-manager/config-manager.module.css";
|
|
2984
3047
|
if (typeof document !== "undefined" && document.querySelector("style[data-tag=\"dsh-config-manager/config-manager.module.css\"]") === null) {
|
|
2985
3048
|
const tag = document.createElement("style");
|
|
@@ -2989,125 +3052,126 @@ if (typeof document !== "undefined" && document.querySelector("style[data-tag=\"
|
|
|
2989
3052
|
document.head.appendChild(tag);
|
|
2990
3053
|
}
|
|
2991
3054
|
var config_manager_module_css_default = {
|
|
2992
|
-
"
|
|
2993
|
-
"
|
|
2994
|
-
"
|
|
3055
|
+
"progressLabel": "_367UGW_progressLabel",
|
|
3056
|
+
"sectionHeader": "_367UGW_sectionHeader",
|
|
3057
|
+
"dialogMask": "_367UGW_dialogMask",
|
|
3058
|
+
"secretFields": "_367UGW_secretFields",
|
|
3059
|
+
"badgeInfo": "_367UGW_badgeInfo",
|
|
3060
|
+
"groupList": "_367UGW_groupList",
|
|
3061
|
+
"progressMeta": "_367UGW_progressMeta",
|
|
3062
|
+
"snapshotRowHeader": "_367UGW_snapshotRowHeader",
|
|
3063
|
+
"banner": "_367UGW_banner",
|
|
3064
|
+
"reportList": "_367UGW_reportList",
|
|
3065
|
+
"optionsCard": "_367UGW_optionsCard",
|
|
2995
3066
|
"reportView": "_367UGW_reportView",
|
|
2996
|
-
"badgeError": "_367UGW_badgeError",
|
|
2997
|
-
"fieldLabel": "_367UGW_fieldLabel",
|
|
2998
3067
|
"reportScroll": "_367UGW_reportScroll",
|
|
2999
|
-
"
|
|
3000
|
-
"
|
|
3001
|
-
"
|
|
3002
|
-
"progressTrack": "_367UGW_progressTrack",
|
|
3003
|
-
"progressIndeterminate": "_367UGW_progressIndeterminate",
|
|
3068
|
+
"errorItem": "_367UGW_errorItem",
|
|
3069
|
+
"errorFooter": "_367UGW_errorFooter",
|
|
3070
|
+
"errorTitle": "_367UGW_errorTitle",
|
|
3004
3071
|
"rowActions": "_367UGW_rowActions",
|
|
3005
|
-
"
|
|
3006
|
-
"
|
|
3007
|
-
"
|
|
3008
|
-
"
|
|
3009
|
-
"
|
|
3010
|
-
"
|
|
3011
|
-
"
|
|
3012
|
-
"
|
|
3013
|
-
"pathNew": "_367UGW_pathNew",
|
|
3014
|
-
"logLine": "_367UGW_logLine",
|
|
3015
|
-
"pathMappingList": "_367UGW_pathMappingList",
|
|
3016
|
-
"progressPercent": "_367UGW_progressPercent",
|
|
3072
|
+
"actionRow": "_367UGW_actionRow",
|
|
3073
|
+
"spinnerWrap": "_367UGW_spinnerWrap",
|
|
3074
|
+
"badgeWarn": "_367UGW_badgeWarn",
|
|
3075
|
+
"errorList": "_367UGW_errorList",
|
|
3076
|
+
"groupCard": "_367UGW_groupCard",
|
|
3077
|
+
"progressIndeterminate": "_367UGW_progressIndeterminate",
|
|
3078
|
+
"groupNote": "_367UGW_groupNote",
|
|
3079
|
+
"pathOld": "_367UGW_pathOld",
|
|
3017
3080
|
"hint": "_367UGW_hint",
|
|
3018
|
-
"
|
|
3081
|
+
"modeHint": "_367UGW_modeHint",
|
|
3082
|
+
"pathMappingList": "_367UGW_pathMappingList",
|
|
3083
|
+
"progressBar": "_367UGW_progressBar",
|
|
3019
3084
|
"reportFooter": "_367UGW_reportFooter",
|
|
3020
|
-
"
|
|
3021
|
-
"
|
|
3022
|
-
"
|
|
3023
|
-
"
|
|
3024
|
-
"
|
|
3025
|
-
"
|
|
3026
|
-
"
|
|
3027
|
-
"diffScroll": "_367UGW_diffScroll",
|
|
3028
|
-
"categoryDesc": "_367UGW_categoryDesc",
|
|
3029
|
-
"kindTag": "_367UGW_kindTag",
|
|
3030
|
-
"spinnerLabel": "_367UGW_spinnerLabel",
|
|
3031
|
-
"warnList": "_367UGW_warnList",
|
|
3032
|
-
"progressBadgeCount": "_367UGW_progressBadgeCount",
|
|
3085
|
+
"spinner": "_367UGW_spinner",
|
|
3086
|
+
"dshCmSpin": "_367UGW_dshCmSpin",
|
|
3087
|
+
"secretsList": "_367UGW_secretsList",
|
|
3088
|
+
"logLine": "_367UGW_logLine",
|
|
3089
|
+
"conflictHead": "_367UGW_conflictHead",
|
|
3090
|
+
"ghostButton": "_367UGW_ghostButton",
|
|
3091
|
+
"sectionTitle": "_367UGW_sectionTitle",
|
|
3033
3092
|
"empty": "_367UGW_empty",
|
|
3034
|
-
"
|
|
3035
|
-
"
|
|
3036
|
-
"errorItem": "_367UGW_errorItem",
|
|
3093
|
+
"dialogWide": "_367UGW_dialogWide",
|
|
3094
|
+
"logPanel": "_367UGW_logPanel",
|
|
3037
3095
|
"dialogHeader": "_367UGW_dialogHeader",
|
|
3038
|
-
"optionsHeader": "_367UGW_optionsHeader",
|
|
3039
|
-
"conflictDetail": "_367UGW_conflictDetail",
|
|
3040
|
-
"reportList": "_367UGW_reportList",
|
|
3041
|
-
"errorTitle": "_367UGW_errorTitle",
|
|
3042
|
-
"progressBlock": "_367UGW_progressBlock",
|
|
3043
|
-
"pathRow": "_367UGW_pathRow",
|
|
3044
|
-
"hiddenFile": "_367UGW_hiddenFile",
|
|
3045
|
-
"errorLine": "_367UGW_errorLine",
|
|
3046
|
-
"groupNote": "_367UGW_groupNote",
|
|
3047
|
-
"formError": "_367UGW_formError",
|
|
3048
|
-
"snapshotRowHeader": "_367UGW_snapshotRowHeader",
|
|
3049
|
-
"progressLabel": "_367UGW_progressLabel",
|
|
3050
|
-
"aboutAuthor": "_367UGW_aboutAuthor",
|
|
3051
|
-
"ghostButton": "_367UGW_ghostButton",
|
|
3052
3096
|
"badge": "_367UGW_badge",
|
|
3097
|
+
"pathRow": "_367UGW_pathRow",
|
|
3098
|
+
"aboutLinkRow": "_367UGW_aboutLinkRow",
|
|
3099
|
+
"snapshotList": "_367UGW_snapshotList",
|
|
3100
|
+
"modeTab": "_367UGW_modeTab",
|
|
3101
|
+
"logScroll": "_367UGW_logScroll",
|
|
3053
3102
|
"groupHeader": "_367UGW_groupHeader",
|
|
3103
|
+
"progressBadge": "_367UGW_progressBadge",
|
|
3104
|
+
"modeTabs": "_367UGW_modeTabs",
|
|
3105
|
+
"dialogBody": "_367UGW_dialogBody",
|
|
3106
|
+
"statRow": "_367UGW_statRow",
|
|
3107
|
+
"field": "_367UGW_field",
|
|
3108
|
+
"dialogBodyScroll": "_367UGW_dialogBodyScroll",
|
|
3109
|
+
"radioLabel": "_367UGW_radioLabel",
|
|
3110
|
+
"progressBadgeSection": "_367UGW_progressBadgeSection",
|
|
3111
|
+
"progressPercent": "_367UGW_progressPercent",
|
|
3112
|
+
"dangerButton": "_367UGW_dangerButton",
|
|
3054
3113
|
"viewTab": "_367UGW_viewTab",
|
|
3055
|
-
"
|
|
3056
|
-
"badgeWarn": "_367UGW_badgeWarn",
|
|
3114
|
+
"categoryItem": "_367UGW_categoryItem",
|
|
3057
3115
|
"conflictId": "_367UGW_conflictId",
|
|
3058
|
-
"
|
|
3116
|
+
"groupItems": "_367UGW_groupItems",
|
|
3117
|
+
"viewBody": "_367UGW_viewBody",
|
|
3118
|
+
"dialogClose": "_367UGW_dialogClose",
|
|
3119
|
+
"dialogCard": "_367UGW_dialogCard",
|
|
3120
|
+
"snapshotRow": "_367UGW_snapshotRow",
|
|
3121
|
+
"conflictOptions": "_367UGW_conflictOptions",
|
|
3059
3122
|
"dshCmIndeterminate": "_367UGW_dshCmIndeterminate",
|
|
3060
|
-
"
|
|
3123
|
+
"optionsHeader": "_367UGW_optionsHeader",
|
|
3124
|
+
"fieldLabel": "_367UGW_fieldLabel",
|
|
3125
|
+
"formError": "_367UGW_formError",
|
|
3126
|
+
"logEmpty": "_367UGW_logEmpty",
|
|
3127
|
+
"errorBanner": "_367UGW_errorBanner",
|
|
3128
|
+
"conflictDetail": "_367UGW_conflictDetail",
|
|
3061
3129
|
"conflictList": "_367UGW_conflictList",
|
|
3062
|
-
"groupList": "_367UGW_groupList",
|
|
3063
|
-
"sectionTitle": "_367UGW_sectionTitle",
|
|
3064
|
-
"progressBarDone": "_367UGW_progressBarDone",
|
|
3065
|
-
"snapshotRow": "_367UGW_snapshotRow",
|
|
3066
|
-
"progressBadgeSection": "_367UGW_progressBadgeSection",
|
|
3067
|
-
"logPanel": "_367UGW_logPanel",
|
|
3068
|
-
"wizardCard": "_367UGW_wizardCard",
|
|
3069
|
-
"dialogHeaderRow": "_367UGW_dialogHeaderRow",
|
|
3070
|
-
"spinner": "_367UGW_spinner",
|
|
3071
|
-
"groupCard": "_367UGW_groupCard",
|
|
3072
|
-
"conflictHead": "_367UGW_conflictHead",
|
|
3073
|
-
"categoryName": "_367UGW_categoryName",
|
|
3074
3130
|
"pathValue": "_367UGW_pathValue",
|
|
3075
|
-
"dialogCard": "_367UGW_dialogCard",
|
|
3076
|
-
"logEmpty": "_367UGW_logEmpty",
|
|
3077
|
-
"actionRow": "_367UGW_actionRow",
|
|
3078
|
-
"snapshotList": "_367UGW_snapshotList",
|
|
3079
|
-
"dialogWide": "_367UGW_dialogWide",
|
|
3080
3131
|
"errorReason": "_367UGW_errorReason",
|
|
3081
|
-
"secretFields": "_367UGW_secretFields",
|
|
3082
|
-
"secretsList": "_367UGW_secretsList",
|
|
3083
3132
|
"badgeOk": "_367UGW_badgeOk",
|
|
3084
|
-
"
|
|
3085
|
-
"
|
|
3086
|
-
"
|
|
3133
|
+
"wizardCard": "_367UGW_wizardCard",
|
|
3134
|
+
"groupLabel": "_367UGW_groupLabel",
|
|
3135
|
+
"reportText": "_367UGW_reportText",
|
|
3136
|
+
"sectionSubtitle": "_367UGW_sectionSubtitle",
|
|
3137
|
+
"dialogHeaderRow": "_367UGW_dialogHeaderRow",
|
|
3138
|
+
"card": "_367UGW_card",
|
|
3139
|
+
"viewTabs": "_367UGW_viewTabs",
|
|
3140
|
+
"categoryName": "_367UGW_categoryName",
|
|
3141
|
+
"kindTag": "_367UGW_kindTag",
|
|
3142
|
+
"errorAction": "_367UGW_errorAction",
|
|
3143
|
+
"badgeError": "_367UGW_badgeError",
|
|
3144
|
+
"progressBarDone": "_367UGW_progressBarDone",
|
|
3145
|
+
"sectionBody": "_367UGW_sectionBody",
|
|
3146
|
+
"progressTrack": "_367UGW_progressTrack",
|
|
3147
|
+
"logJumpButton": "_367UGW_logJumpButton",
|
|
3148
|
+
"categoryDesc": "_367UGW_categoryDesc",
|
|
3149
|
+
"checkboxRow": "_367UGW_checkboxRow",
|
|
3150
|
+
"progressDetail": "_367UGW_progressDetail",
|
|
3151
|
+
"primaryButton": "_367UGW_primaryButton",
|
|
3152
|
+
"errorActionLabel": "_367UGW_errorActionLabel",
|
|
3087
3153
|
"input": "_367UGW_input",
|
|
3154
|
+
"warnText": "_367UGW_warnText",
|
|
3155
|
+
"logHeader": "_367UGW_logHeader",
|
|
3156
|
+
"progressBlock": "_367UGW_progressBlock",
|
|
3157
|
+
"confirmScroll": "_367UGW_confirmScroll",
|
|
3158
|
+
"sectionTitleBlock": "_367UGW_sectionTitleBlock",
|
|
3159
|
+
"aboutAuthor": "_367UGW_aboutAuthor",
|
|
3160
|
+
"section": "_367UGW_section",
|
|
3161
|
+
"errorLine": "_367UGW_errorLine",
|
|
3162
|
+
"hiddenFile": "_367UGW_hiddenFile",
|
|
3163
|
+
"warnList": "_367UGW_warnList",
|
|
3164
|
+
"rollbackBox": "_367UGW_rollbackBox",
|
|
3165
|
+
"pullScroll": "_367UGW_pullScroll",
|
|
3166
|
+
"diffScroll": "_367UGW_diffScroll",
|
|
3088
3167
|
"pathIssueKind": "_367UGW_pathIssueKind",
|
|
3089
|
-
"
|
|
3090
|
-
"viewBody": "_367UGW_viewBody",
|
|
3091
|
-
"modeTab": "_367UGW_modeTab",
|
|
3092
|
-
"badgeInfo": "_367UGW_badgeInfo",
|
|
3093
|
-
"errorList": "_367UGW_errorList",
|
|
3094
|
-
"dshCmSpin": "_367UGW_dshCmSpin",
|
|
3095
|
-
"viewTabs": "_367UGW_viewTabs",
|
|
3168
|
+
"spinnerLabel": "_367UGW_spinnerLabel",
|
|
3096
3169
|
"conflictItem": "_367UGW_conflictItem",
|
|
3097
|
-
"
|
|
3098
|
-
"
|
|
3099
|
-
"
|
|
3100
|
-
"
|
|
3101
|
-
"
|
|
3102
|
-
"errorFooter": "_367UGW_errorFooter",
|
|
3103
|
-
"banner": "_367UGW_banner",
|
|
3104
|
-
"field": "_367UGW_field",
|
|
3105
|
-
"logScroll": "_367UGW_logScroll",
|
|
3106
|
-
"dangerButton": "_367UGW_dangerButton",
|
|
3107
|
-
"primaryButton": "_367UGW_primaryButton",
|
|
3108
|
-
"radioLabel": "_367UGW_radioLabel",
|
|
3109
|
-
"sectionBody": "_367UGW_sectionBody",
|
|
3110
|
-
"section": "_367UGW_section"
|
|
3170
|
+
"severityError": "_367UGW_severityError",
|
|
3171
|
+
"pathNew": "_367UGW_pathNew",
|
|
3172
|
+
"select": "_367UGW_select",
|
|
3173
|
+
"planScroll": "_367UGW_planScroll",
|
|
3174
|
+
"progressBadgeCount": "_367UGW_progressBadgeCount"
|
|
3111
3175
|
};
|
|
3112
3176
|
//#endregion
|
|
3113
3177
|
//#region src/client/common/ui.tsx
|
|
@@ -3115,15 +3179,19 @@ var config_manager_module_css_default = {
|
|
|
3115
3179
|
* 统一按钮(primary=主操作 / ghost=次操作 / danger=危险操作)。
|
|
3116
3180
|
* 带 href 时渲染同款按钮类的外链 <a>(target=_blank + rel=noreferrer),
|
|
3117
3181
|
* 外观与普通按钮一致,不破坏既有 <button> 调用。
|
|
3182
|
+
* loading=true 时自动禁用(防重复点击)并标注 aria-busy(无障碍);
|
|
3183
|
+
* 视觉 loading(Spinner)由调用方按既有模式放在 children 中。
|
|
3118
3184
|
*/
|
|
3119
|
-
function Button({ variant = "ghost", disabled, onClick, children, title, className, href, newTab = true }) {
|
|
3185
|
+
function Button({ variant = "ghost", disabled, loading = false, onClick, children, title, className, href, newTab = true }) {
|
|
3120
3186
|
const cls = variant === "primary" ? config_manager_module_css_default.primaryButton : variant === "danger" ? config_manager_module_css_default.dangerButton : config_manager_module_css_default.ghostButton;
|
|
3187
|
+
const effectiveDisabled = disabled === true || loading;
|
|
3121
3188
|
if (href !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
3122
3189
|
className: className !== void 0 ? `${cls} ${className}` : cls,
|
|
3123
3190
|
href,
|
|
3124
3191
|
target: newTab ? "_blank" : void 0,
|
|
3125
3192
|
rel: newTab ? "noreferrer" : void 0,
|
|
3126
3193
|
title,
|
|
3194
|
+
"aria-busy": loading || void 0,
|
|
3127
3195
|
onClick,
|
|
3128
3196
|
style: { textDecoration: "none" },
|
|
3129
3197
|
children
|
|
@@ -3131,8 +3199,9 @@ function Button({ variant = "ghost", disabled, onClick, children, title, classNa
|
|
|
3131
3199
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3132
3200
|
type: "button",
|
|
3133
3201
|
className: className !== void 0 ? `${cls} ${className}` : cls,
|
|
3134
|
-
disabled,
|
|
3202
|
+
disabled: effectiveDisabled,
|
|
3135
3203
|
title,
|
|
3204
|
+
"aria-busy": loading || void 0,
|
|
3136
3205
|
onClick,
|
|
3137
3206
|
children
|
|
3138
3207
|
});
|
|
@@ -3236,8 +3305,9 @@ function Checkbox({ checked, onChange, label, disabled }) {
|
|
|
3236
3305
|
/**
|
|
3237
3306
|
* 错误横幅:toActionableError 解析为标题 + 原因 + 建议动作,
|
|
3238
3307
|
* 文本在渲染前再经 redact() 兜底(双保险),Reason 以等宽块展示。
|
|
3308
|
+
* 重试按钮:进行中(retrying)时显示 Spinner 并禁用(防重复点击)。
|
|
3239
3309
|
*/
|
|
3240
|
-
function ErrorBanner({ error, onRetry, retrying }) {
|
|
3310
|
+
function ErrorBanner({ error, onRetry, retrying, t = zhUiT }) {
|
|
3241
3311
|
const [actionable] = (0, react.useState)(() => toActionableError(error));
|
|
3242
3312
|
const reason = redact(actionable.reason);
|
|
3243
3313
|
const item = actionable.item !== void 0 ? redact(actionable.item) : void 0;
|
|
@@ -3274,7 +3344,8 @@ function ErrorBanner({ error, onRetry, retrying }) {
|
|
|
3274
3344
|
variant: "primary",
|
|
3275
3345
|
onClick: onRetry,
|
|
3276
3346
|
disabled: retrying === true,
|
|
3277
|
-
|
|
3347
|
+
loading: retrying === true,
|
|
3348
|
+
children: retrying === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, {}) : t("commonRetry")
|
|
3278
3349
|
})
|
|
3279
3350
|
})
|
|
3280
3351
|
]
|
|
@@ -3475,7 +3546,7 @@ function ExportSummary({ report }) {
|
|
|
3475
3546
|
* 文本详情 = report.ts 渲染器的输出(已脱敏),展示前再过 redact() 双保险;
|
|
3476
3547
|
* 以 <pre> 等宽块呈现保持对齐。
|
|
3477
3548
|
*/
|
|
3478
|
-
function ReportView({ kind, exportReport, importResult, onAction, onDownload }) {
|
|
3549
|
+
function ReportView({ kind, exportReport, importResult, onAction, onDownload, downloadBusy = false, t = zhUiT }) {
|
|
3479
3550
|
const actions = kind === "import" && importResult !== void 0 ? suggestedActions(importResult) : [];
|
|
3480
3551
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3481
3552
|
className: config_manager_module_css_default.reportView,
|
|
@@ -3490,7 +3561,8 @@ function ReportView({ kind, exportReport, importResult, onAction, onDownload })
|
|
|
3490
3561
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
3491
3562
|
variant: "primary",
|
|
3492
3563
|
onClick: onDownload,
|
|
3493
|
-
|
|
3564
|
+
loading: downloadBusy,
|
|
3565
|
+
children: downloadBusy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, {}) : t("export.download")
|
|
3494
3566
|
})
|
|
3495
3567
|
})
|
|
3496
3568
|
] }), kind === "import" && importResult !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
@@ -3554,6 +3626,10 @@ function ExportView({ api, t }) {
|
|
|
3554
3626
|
const result = exp.result;
|
|
3555
3627
|
const error = exp.error;
|
|
3556
3628
|
const downloaded = exp.downloaded;
|
|
3629
|
+
/** 下载进行中(瞬态 UI:下载通常数秒,切 tab 后由 api 层继续,切回显示完成态) */
|
|
3630
|
+
const [downloading, setDownloading] = (0, react.useState)(false);
|
|
3631
|
+
/** 下载防重入 ref(导出完成自动下载 + 用户手动下载共享;避免双击并发下载同一文件) */
|
|
3632
|
+
const downloadingRef = (0, react.useRef)(false);
|
|
3557
3633
|
const setMode = (next) => {
|
|
3558
3634
|
runStore.patch({ export: { mode: next } });
|
|
3559
3635
|
};
|
|
@@ -3614,15 +3690,21 @@ function ExportView({ api, t }) {
|
|
|
3614
3690
|
runStore.patch({ export: { running: false } });
|
|
3615
3691
|
}
|
|
3616
3692
|
};
|
|
3617
|
-
/** 把导出的 ZIP
|
|
3693
|
+
/** 把导出的 ZIP 下载到浏览器「下载」目录(默认静默下载,不弹另存为对话框)。
|
|
3694
|
+
* 防重入:downloadingRef 作锁,进行中忽略重复点击/自动触发;失败后恢复可重试。 */
|
|
3618
3695
|
const download = async (zipPath) => {
|
|
3619
|
-
if (zipPath === "") return;
|
|
3696
|
+
if (zipPath === "" || downloadingRef.current) return;
|
|
3697
|
+
downloadingRef.current = true;
|
|
3698
|
+
setDownloading(true);
|
|
3620
3699
|
try {
|
|
3621
3700
|
runStore.patch({ export: { error: null } });
|
|
3622
3701
|
await api.download(zipPath);
|
|
3623
3702
|
runStore.patch({ export: { downloaded: true } });
|
|
3624
3703
|
} catch (err) {
|
|
3625
3704
|
runStore.patch({ export: { error: err instanceof Error ? err.message : String(err) } });
|
|
3705
|
+
} finally {
|
|
3706
|
+
downloadingRef.current = false;
|
|
3707
|
+
setDownloading(false);
|
|
3626
3708
|
}
|
|
3627
3709
|
};
|
|
3628
3710
|
const customWarnings = mode === "custom" ? flow.validateSelection(selection).warnings : [];
|
|
@@ -3814,14 +3896,17 @@ function ExportView({ api, t }) {
|
|
|
3814
3896
|
onRetry: () => {
|
|
3815
3897
|
runExport();
|
|
3816
3898
|
},
|
|
3817
|
-
retrying: running
|
|
3899
|
+
retrying: running,
|
|
3900
|
+
t: api.t
|
|
3818
3901
|
}),
|
|
3819
3902
|
result !== null && !running && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReportView, {
|
|
3820
3903
|
kind: "export",
|
|
3821
3904
|
exportReport: result.report,
|
|
3822
3905
|
onDownload: () => {
|
|
3823
3906
|
download(result.zipPath);
|
|
3824
|
-
}
|
|
3907
|
+
},
|
|
3908
|
+
downloadBusy: downloading,
|
|
3909
|
+
t: api.t
|
|
3825
3910
|
}), downloaded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
|
|
3826
3911
|
kind: "ok",
|
|
3827
3912
|
children: t("export.saved", { name: result.report.file.name })
|
|
@@ -4166,22 +4251,62 @@ function SecretsForm({ missing, t, onChange }) {
|
|
|
4166
4251
|
* (逐计划项操作 `▶/✓/⚠/✗/–` + 子进程命令行 `$ dsh plugin …`)。
|
|
4167
4252
|
* - 数据来自 Host RunRegistry(经 /progress 轮询回传),行文本仅非敏感内容,
|
|
4168
4253
|
* 渲染前再过 redact() 兜底(安全不变量:UI 展示文本先脱敏);
|
|
4169
|
-
* - 限高内滚(logScroll
|
|
4254
|
+
* - 限高内滚(logScroll);**智能自动滚动**:仅当用户贴近底部时跟随最新行;
|
|
4255
|
+
* 用户向上滚动查看历史时不强制拉回,改显示「↓ 新输出」提示,点击再滚到底部;
|
|
4256
|
+
* - memo 自定义比较:lines 数组为同一引用被 append(RunState.log push 不换引用),
|
|
4257
|
+
* 按引用浅比较无法感知新行 —— 比较长度 + t 引用,避免整页轮询反复重渲染整个列表。
|
|
4170
4258
|
*/
|
|
4171
|
-
function
|
|
4259
|
+
function ImportLogPanelBase({ lines, t }) {
|
|
4172
4260
|
const scrollRef = (0, react.useRef)(null);
|
|
4261
|
+
/** 是否贴底(用户上滚置 false;滚动回底部自动恢复) */
|
|
4262
|
+
const stickRef = (0, react.useRef)(true);
|
|
4263
|
+
/** 用户上滚后是否有新行到达(显示「↓ 新输出」;点击跳到底部清除) */
|
|
4264
|
+
const [hasNewOutput, setHasNewOutput] = (0, react.useState)(false);
|
|
4265
|
+
/**
|
|
4266
|
+
* 上次渲染的数组引用(新输出 = 引用变化)。依赖 appendLog 的**不可变写入**:
|
|
4267
|
+
* 每次追加都生成新数组(run-registry.ts)——行数封顶后长度恒定,但引用必变,
|
|
4268
|
+
* 以引用判断才能感知截断后的新行(长度比较在 500 行封顶时失效)。
|
|
4269
|
+
*/
|
|
4270
|
+
const prevLinesRef = (0, react.useRef)(lines);
|
|
4173
4271
|
(0, react.useEffect)(() => {
|
|
4272
|
+
const el = scrollRef.current;
|
|
4273
|
+
if (el === null) return;
|
|
4274
|
+
const hasNew = lines !== prevLinesRef.current;
|
|
4275
|
+
prevLinesRef.current = lines;
|
|
4276
|
+
if (stickRef.current) {
|
|
4277
|
+
el.scrollTop = el.scrollHeight;
|
|
4278
|
+
setHasNewOutput(false);
|
|
4279
|
+
} else if (hasNew) setHasNewOutput(true);
|
|
4280
|
+
}, [lines]);
|
|
4281
|
+
/** 滚动中更新贴底状态(上滚 → 停止跟随;滚回底部 → 恢复跟随并清除提示) */
|
|
4282
|
+
const onScroll = () => {
|
|
4283
|
+
const el = scrollRef.current;
|
|
4284
|
+
if (el === null) return;
|
|
4285
|
+
const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 48;
|
|
4286
|
+
stickRef.current = nearBottom;
|
|
4287
|
+
if (nearBottom) setHasNewOutput(false);
|
|
4288
|
+
};
|
|
4289
|
+
/** 「↓ 新输出」:跳到底部 + 恢复跟随 */
|
|
4290
|
+
const jumpToBottom = () => {
|
|
4174
4291
|
const el = scrollRef.current;
|
|
4175
4292
|
if (el !== null) el.scrollTop = el.scrollHeight;
|
|
4176
|
-
|
|
4293
|
+
stickRef.current = true;
|
|
4294
|
+
setHasNewOutput(false);
|
|
4295
|
+
};
|
|
4177
4296
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4178
4297
|
className: config_manager_module_css_default.logPanel,
|
|
4179
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
4298
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4180
4299
|
className: config_manager_module_css_default.logHeader,
|
|
4181
|
-
children: t("import.log.title")
|
|
4300
|
+
children: [t("import.log.title"), hasNewOutput && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4301
|
+
type: "button",
|
|
4302
|
+
className: config_manager_module_css_default.logJumpButton,
|
|
4303
|
+
onClick: jumpToBottom,
|
|
4304
|
+
children: ["↓ ", t("import.log.newOutput")]
|
|
4305
|
+
})]
|
|
4182
4306
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4183
4307
|
className: config_manager_module_css_default.logScroll,
|
|
4184
4308
|
ref: scrollRef,
|
|
4309
|
+
onScroll,
|
|
4185
4310
|
children: lines.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4186
4311
|
className: config_manager_module_css_default.logEmpty,
|
|
4187
4312
|
children: t("import.log.empty")
|
|
@@ -4192,6 +4317,10 @@ function ImportLogPanel({ lines, t }) {
|
|
|
4192
4317
|
})]
|
|
4193
4318
|
});
|
|
4194
4319
|
}
|
|
4320
|
+
/** memo:lines 数组经 appendLog **不可变追加**(每次 append 换新引用,run-registry.ts)——
|
|
4321
|
+
* 自定义比较以「数组引用 + t 引用」为准:引用未变 = 无新输出,跳过整个列表重渲染;
|
|
4322
|
+
* 引用已变 = 有新行(含 500 行封顶后长度不变的情况),必须重渲染。 */
|
|
4323
|
+
const ImportLogPanel = (0, react.memo)(ImportLogPanelBase, (prev, next) => prev.lines === next.lines && prev.t === next.t);
|
|
4195
4324
|
/**
|
|
4196
4325
|
* 导入向导主视图。
|
|
4197
4326
|
*/
|
|
@@ -4345,10 +4474,12 @@ function ImportWizardView({ api, t }) {
|
|
|
4345
4474
|
* 解密密码交给向导——整个导入只输入这一次密码,没有第二个密码校验页面。 */
|
|
4346
4475
|
const onUnlockArchive = async () => {
|
|
4347
4476
|
if (imp.zipPath === null) return;
|
|
4477
|
+
const generation = pickGeneration.current;
|
|
4348
4478
|
setUnlocking(true);
|
|
4349
4479
|
setArchiveUnlockError(null);
|
|
4350
4480
|
try {
|
|
4351
4481
|
const { refs } = await wizard.unlockArchive(imp.zipPath, archivePassword);
|
|
4482
|
+
if (generation !== pickGeneration.current) return;
|
|
4352
4483
|
wizard.setDecryptPassword(archivePassword);
|
|
4353
4484
|
runStore.patch({ import: {
|
|
4354
4485
|
archiveUnlocked: true,
|
|
@@ -4357,9 +4488,11 @@ function ImportWizardView({ api, t }) {
|
|
|
4357
4488
|
} });
|
|
4358
4489
|
runStore.syncWizard();
|
|
4359
4490
|
await wizard.selectZip(imp.zipPath);
|
|
4491
|
+
if (generation !== pickGeneration.current) return;
|
|
4360
4492
|
runStore.syncWizard();
|
|
4361
4493
|
runStore.patch({ import: { phase: "preview" } });
|
|
4362
4494
|
} catch (err) {
|
|
4495
|
+
if (generation !== pickGeneration.current) return;
|
|
4363
4496
|
setArchiveUnlockError(err instanceof Error ? err.message : String(err));
|
|
4364
4497
|
runStore.patch({ import: { error: err instanceof Error ? err.message : String(err) } });
|
|
4365
4498
|
runStore.syncWizard();
|
|
@@ -4475,7 +4608,8 @@ function ImportWizardView({ api, t }) {
|
|
|
4475
4608
|
}),
|
|
4476
4609
|
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBanner, {
|
|
4477
4610
|
error,
|
|
4478
|
-
onRetry: resetWizard
|
|
4611
|
+
onRetry: resetWizard,
|
|
4612
|
+
t: api.t
|
|
4479
4613
|
})
|
|
4480
4614
|
]
|
|
4481
4615
|
});
|
|
@@ -4489,7 +4623,8 @@ function ImportWizardView({ api, t }) {
|
|
|
4489
4623
|
}),
|
|
4490
4624
|
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBanner, {
|
|
4491
4625
|
error,
|
|
4492
|
-
onRetry: resetWizard
|
|
4626
|
+
onRetry: resetWizard,
|
|
4627
|
+
t: api.t
|
|
4493
4628
|
}),
|
|
4494
4629
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorList, { errors: imp.errors })
|
|
4495
4630
|
]
|
|
@@ -4544,7 +4679,8 @@ function ImportWizardView({ api, t }) {
|
|
|
4544
4679
|
error,
|
|
4545
4680
|
onRetry: () => {
|
|
4546
4681
|
goPreview();
|
|
4547
|
-
}
|
|
4682
|
+
},
|
|
4683
|
+
t: api.t
|
|
4548
4684
|
}),
|
|
4549
4685
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4550
4686
|
className: config_manager_module_css_default.actionRow,
|
|
@@ -4622,7 +4758,8 @@ function ImportWizardView({ api, t }) {
|
|
|
4622
4758
|
error,
|
|
4623
4759
|
onRetry: () => {
|
|
4624
4760
|
goPreview();
|
|
4625
|
-
}
|
|
4761
|
+
},
|
|
4762
|
+
t: api.t
|
|
4626
4763
|
}),
|
|
4627
4764
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4628
4765
|
className: config_manager_module_css_default.actionRow,
|
|
@@ -4661,7 +4798,10 @@ function ImportWizardView({ api, t }) {
|
|
|
4661
4798
|
setArchiveUnlockError(null);
|
|
4662
4799
|
}
|
|
4663
4800
|
}),
|
|
4664
|
-
archiveUnlockError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBanner, {
|
|
4801
|
+
archiveUnlockError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBanner, {
|
|
4802
|
+
error: archiveUnlockError,
|
|
4803
|
+
t: api.t
|
|
4804
|
+
}),
|
|
4665
4805
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4666
4806
|
className: config_manager_module_css_default.actionRow,
|
|
4667
4807
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
@@ -4820,7 +4960,8 @@ function ImportWizardView({ api, t }) {
|
|
|
4820
4960
|
error,
|
|
4821
4961
|
onRetry: () => {
|
|
4822
4962
|
execute();
|
|
4823
|
-
}
|
|
4963
|
+
},
|
|
4964
|
+
t: api.t
|
|
4824
4965
|
})]
|
|
4825
4966
|
});
|
|
4826
4967
|
if (step === "importing") {
|
|
@@ -4856,7 +4997,10 @@ function ImportWizardView({ api, t }) {
|
|
|
4856
4997
|
className: config_manager_module_css_default.hint,
|
|
4857
4998
|
children: t("import.importing")
|
|
4858
4999
|
}),
|
|
4859
|
-
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBanner, {
|
|
5000
|
+
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBanner, {
|
|
5001
|
+
error,
|
|
5002
|
+
t: api.t
|
|
5003
|
+
}),
|
|
4860
5004
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorList, { errors: imp.errors })
|
|
4861
5005
|
]
|
|
4862
5006
|
});
|
|
@@ -4891,16 +5035,186 @@ function ImportWizardView({ api, t }) {
|
|
|
4891
5035
|
kind: "warn",
|
|
4892
5036
|
children: t("report.needsRestart")
|
|
4893
5037
|
}),
|
|
4894
|
-
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBanner, {
|
|
5038
|
+
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBanner, {
|
|
5039
|
+
error,
|
|
5040
|
+
t: api.t
|
|
5041
|
+
})
|
|
4895
5042
|
]
|
|
4896
5043
|
});
|
|
4897
5044
|
}
|
|
4898
5045
|
return null;
|
|
4899
5046
|
}
|
|
4900
5047
|
//#endregion
|
|
4901
|
-
//#region src/client/
|
|
5048
|
+
//#region src/client/common/ConfirmDialog.tsx
|
|
4902
5049
|
/**
|
|
4903
|
-
*
|
|
5050
|
+
* ConfirmDialog —— 确认弹窗(危险/重要操作的二次确认,DESIGN.md §8.11 / §14)。
|
|
5051
|
+
*
|
|
5052
|
+
* 背景(2026-08-20):用户要求「删除按钮需要弹窗弹出二次确认」,替代原先的
|
|
5053
|
+
* 「行内两步确认」(已从 DESIGN.md §14 移除,见 t8 设计)。本项目此前无任何
|
|
5054
|
+
* modal / fixed / z-index 元素,本组件是首个浮动层 —— DESIGN.md §15 Anti-pattern #7
|
|
5055
|
+
* 已登记豁免(z-index 100;浮动层必须先设计进 DESIGN.md 的流程满足)。
|
|
5056
|
+
*
|
|
5057
|
+
* 交互约定:
|
|
5058
|
+
* - 受控组件:open=false 时不渲染;open=true 渲染遮罩 + 居中卡片;
|
|
5059
|
+
* - 关闭三途径:遮罩点击(仅 e.target === currentTarget)、Esc 键、取消按钮;
|
|
5060
|
+
* (可选 backdropClose:遮罩点击 / Esc 走此回调而非 onCancel —— 用于「不再提示」类
|
|
5061
|
+
* 弹窗,用户点遮罩只是暂时关闭、不算表态;缺省时三途径一致走 onCancel);
|
|
5062
|
+
* - busy=true(或 onConfirm 返回 Promise 的自管 busy)时禁用一切关闭途径与确认按钮,
|
|
5063
|
+
* 防重复提交;
|
|
5064
|
+
* - 初始焦点在取消按钮(危险确认不默认落破坏性按钮);关闭后还原焦点到触发按钮
|
|
5065
|
+
* (open 前的 document.activeElement);
|
|
5066
|
+
* - 不做完整 focus trap(两按钮场景风险可接受,DESIGN.md §8.11 已注明)。
|
|
5067
|
+
*
|
|
5068
|
+
* 安全:message 由调用方传入(渲染前已 redact 兜底,与全站一致);本组件不触碰任何凭据。
|
|
5069
|
+
* 样式:全部走 --dsw-* token(遮罩 color-mix 半透明、卡片 bg-layer-2 + border-l1),
|
|
5070
|
+
* 仅新增 dialogMask/dialogCard/dialogHeader/dialogBody 四个类(config-manager.module.css)。
|
|
5071
|
+
*/
|
|
5072
|
+
/**
|
|
5073
|
+
* 确认弹窗:遮罩 + 居中卡片 + 标题/正文/按钮区。
|
|
5074
|
+
* 自管 busy:onConfirm 返回 Promise 时置 busy 直到 resolve(reject 仍关闭 busy,错误由调用方处理)。
|
|
5075
|
+
*/
|
|
5076
|
+
function ConfirmDialog({ open, title, message, confirmLabel, cancelLabel, danger, busy: busyProp, onConfirm, onCancel, backdropClose, children }) {
|
|
5077
|
+
const [selfBusy, setSelfBusy] = (0, react.useState)(false);
|
|
5078
|
+
const busy = busyProp === true || selfBusy;
|
|
5079
|
+
/** open 前的活动元素(关闭后还原焦点) */
|
|
5080
|
+
const prevFocus = (0, react.useRef)(null);
|
|
5081
|
+
/** 取消按钮 ref(初始焦点) */
|
|
5082
|
+
const cancelRef = (0, react.useRef)(null);
|
|
5083
|
+
/** 遮罩/Esc 关闭回调(缺省 = 取消按钮同一回调) */
|
|
5084
|
+
const handleBackdropClose = backdropClose ?? onCancel;
|
|
5085
|
+
(0, react.useEffect)(() => {
|
|
5086
|
+
if (open) {
|
|
5087
|
+
prevFocus.current = document.activeElement;
|
|
5088
|
+
requestAnimationFrame(() => {
|
|
5089
|
+
cancelRef.current?.focus();
|
|
5090
|
+
});
|
|
5091
|
+
return;
|
|
5092
|
+
}
|
|
5093
|
+
if (prevFocus.current !== null && prevFocus.current instanceof HTMLElement) {
|
|
5094
|
+
prevFocus.current.focus();
|
|
5095
|
+
prevFocus.current = null;
|
|
5096
|
+
}
|
|
5097
|
+
}, [open]);
|
|
5098
|
+
(0, react.useEffect)(() => {
|
|
5099
|
+
if (!open || busy) return;
|
|
5100
|
+
const close = backdropClose ?? onCancel;
|
|
5101
|
+
const onKey = (e) => {
|
|
5102
|
+
if (e.key === "Escape") close();
|
|
5103
|
+
};
|
|
5104
|
+
window.addEventListener("keydown", onKey);
|
|
5105
|
+
return () => {
|
|
5106
|
+
window.removeEventListener("keydown", onKey);
|
|
5107
|
+
};
|
|
5108
|
+
}, [
|
|
5109
|
+
open,
|
|
5110
|
+
busy,
|
|
5111
|
+
backdropClose,
|
|
5112
|
+
onCancel
|
|
5113
|
+
]);
|
|
5114
|
+
if (!open) return null;
|
|
5115
|
+
const handleConfirm = () => {
|
|
5116
|
+
if (busy) return;
|
|
5117
|
+
const result = onConfirm();
|
|
5118
|
+
if (result instanceof Promise) {
|
|
5119
|
+
setSelfBusy(true);
|
|
5120
|
+
result.finally(() => {
|
|
5121
|
+
setSelfBusy(false);
|
|
5122
|
+
});
|
|
5123
|
+
}
|
|
5124
|
+
};
|
|
5125
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5126
|
+
className: config_manager_module_css_default.dialogMask,
|
|
5127
|
+
onMouseDown: (e) => {
|
|
5128
|
+
if (e.target === e.currentTarget && !busy) handleBackdropClose();
|
|
5129
|
+
},
|
|
5130
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5131
|
+
className: config_manager_module_css_default.dialogCard,
|
|
5132
|
+
role: "dialog",
|
|
5133
|
+
"aria-modal": "true",
|
|
5134
|
+
"aria-label": title,
|
|
5135
|
+
children: [
|
|
5136
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5137
|
+
className: config_manager_module_css_default.dialogHeader,
|
|
5138
|
+
children: title
|
|
5139
|
+
}),
|
|
5140
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5141
|
+
className: config_manager_module_css_default.dialogBody,
|
|
5142
|
+
children: [message !== void 0 && message !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: message }), children]
|
|
5143
|
+
}),
|
|
5144
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5145
|
+
className: config_manager_module_css_default.actionRow,
|
|
5146
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
5147
|
+
variant: danger === true ? "danger" : "primary",
|
|
5148
|
+
disabled: busy,
|
|
5149
|
+
loading: busy,
|
|
5150
|
+
onClick: () => {
|
|
5151
|
+
handleConfirm();
|
|
5152
|
+
},
|
|
5153
|
+
children: busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, {}) : confirmLabel ?? ""
|
|
5154
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5155
|
+
ref: cancelRef,
|
|
5156
|
+
type: "button",
|
|
5157
|
+
className: config_manager_module_css_default.ghostButton,
|
|
5158
|
+
disabled: busy,
|
|
5159
|
+
"aria-busy": busy || void 0,
|
|
5160
|
+
onClick: onCancel,
|
|
5161
|
+
children: cancelLabel ?? ""
|
|
5162
|
+
})]
|
|
5163
|
+
})
|
|
5164
|
+
]
|
|
5165
|
+
})
|
|
5166
|
+
});
|
|
5167
|
+
}
|
|
5168
|
+
//#endregion
|
|
5169
|
+
//#region src/ui/backup-schedule.ts
|
|
5170
|
+
/** 可选的间隔档位(UI 展示顺序 = 由短到长)。 */
|
|
5171
|
+
const BACKUP_INTERVAL_OPTIONS = [
|
|
5172
|
+
"6h",
|
|
5173
|
+
"12h",
|
|
5174
|
+
"24h",
|
|
5175
|
+
"7d"
|
|
5176
|
+
];
|
|
5177
|
+
/** 校验设置输入(host 侧保存路由与组件提交共用;接受 unknown 防御畸形/空 body)。 */
|
|
5178
|
+
function validateBackupScheduleDraft(draft) {
|
|
5179
|
+
if (draft === null || typeof draft !== "object" || Array.isArray(draft)) return {
|
|
5180
|
+
ok: false,
|
|
5181
|
+
error: "body must be an object with enabled (boolean) and interval"
|
|
5182
|
+
};
|
|
5183
|
+
const d = draft;
|
|
5184
|
+
if (typeof d["enabled"] !== "boolean") return {
|
|
5185
|
+
ok: false,
|
|
5186
|
+
error: "enabled must be a boolean"
|
|
5187
|
+
};
|
|
5188
|
+
if (!BACKUP_INTERVAL_OPTIONS.includes(d["interval"])) return {
|
|
5189
|
+
ok: false,
|
|
5190
|
+
error: "interval must be one of 6h/12h/24h/7d"
|
|
5191
|
+
};
|
|
5192
|
+
return {
|
|
5193
|
+
ok: true,
|
|
5194
|
+
value: {
|
|
5195
|
+
enabled: d["enabled"],
|
|
5196
|
+
interval: d["interval"]
|
|
5197
|
+
}
|
|
5198
|
+
};
|
|
5199
|
+
}
|
|
5200
|
+
/** 上次运行状态 → Badge kind(success→ok / skipped→info / failed→error / 未知→info)。 */
|
|
5201
|
+
function backupRunBadgeKind(status) {
|
|
5202
|
+
switch (status) {
|
|
5203
|
+
case "success": return "ok";
|
|
5204
|
+
case "failed": return "error";
|
|
5205
|
+
case "skipped": return "info";
|
|
5206
|
+
default: return "info";
|
|
5207
|
+
}
|
|
5208
|
+
}
|
|
5209
|
+
/** 判断草稿相对已保存配置是否有未保存修改(决定「保存设置」按钮可用性)。 */
|
|
5210
|
+
function backupDraftDirty(draft, saved) {
|
|
5211
|
+
if (saved === null) return true;
|
|
5212
|
+
return draft.enabled !== saved.enabled || draft.interval !== saved.interval;
|
|
5213
|
+
}
|
|
5214
|
+
//#endregion
|
|
5215
|
+
//#region src/client/snapshots/SnapshotsPanel.tsx
|
|
5216
|
+
/**
|
|
5217
|
+
* 快照恢复面板(M4):列出快照 → 选择 → dry-run 计划预览 → 确认执行 → 诚实报告。
|
|
4904
5218
|
*
|
|
4905
5219
|
* 数据流:api.snapshots() 加载列表;选择快照后 api.restoreSnapshot(id, true) 拿
|
|
4906
5220
|
* 恢复计划(零写入预览);确认后 api.restoreSnapshot(id, false) 执行并展示报告
|
|
@@ -4908,6 +5222,12 @@ function ImportWizardView({ api, t }) {
|
|
|
4908
5222
|
* 状态组件内自持(useState),同时经 toSnapshotsStoreSlice() 镜像进模块级 runStore:
|
|
4909
5223
|
* 模块级单例保证「切 tab 不丢」,sessionStorage 白名单保证「刷新恢复」
|
|
4910
5224
|
* (选中快照 / dry-run 计划 / 执行报告;快照列表本身可随时重载,不持久化)。
|
|
5225
|
+
*
|
|
5226
|
+
* 视图底部附「定时全量备份」设置卡(BackupScheduleCard,m-backup-schedule):
|
|
5227
|
+
* 开关 + 间隔档位(6h/12h/24h/7d)保存经 PUT /backup-schedule(host 校验 + 重排
|
|
5228
|
+
* 调度器);「立即备份」经 POST /backup-schedule/run 复用 BackupScheduler.runOnce
|
|
5229
|
+
* (同一时刻防重)。草稿镜像 runStore.snapshots.backupDraft(未保存修改切 tab /
|
|
5230
|
+
* 刷新保留)。安全:定时备份恒不含 secret、不加密;本卡无敏感字段。
|
|
4911
5231
|
*/
|
|
4912
5232
|
const initial$3 = {
|
|
4913
5233
|
status: "loading",
|
|
@@ -4952,12 +5272,15 @@ function actionKindLabel(kind) {
|
|
|
4952
5272
|
/**
|
|
4953
5273
|
* 从 runStore 恢复上次的快照面板状态(切 tab 回 / 刷新后挂载)。
|
|
4954
5274
|
* 无敏感字段;plan/report 为纯数据,可安全序列化恢复。
|
|
5275
|
+
* running 来自 store 镜像(刷新后经 runStore.resume() 以宿主 /runs 为权威重新置位——
|
|
5276
|
+
* 持久化白名单恒为 false,绝不把浏览器陈旧状态当成恢复执行中的依据)。
|
|
4955
5277
|
*/
|
|
4956
5278
|
function initFromStore$2() {
|
|
4957
5279
|
const s = runStore.getSnapshot().snapshots;
|
|
4958
5280
|
return {
|
|
4959
5281
|
...initial$3,
|
|
4960
5282
|
selectedId: s.selectedId,
|
|
5283
|
+
running: s.running,
|
|
4961
5284
|
plan: s.plan,
|
|
4962
5285
|
report: s.report,
|
|
4963
5286
|
actionError: s.actionError,
|
|
@@ -4970,15 +5293,27 @@ function SnapshotsPanel({ api, t }) {
|
|
|
4970
5293
|
const stateRef = (0, react.useRef)(state);
|
|
4971
5294
|
/** 挂载守卫:卸载后不再 setState(store 镜像仍执行,异步结果照常落库) */
|
|
4972
5295
|
const mountedRef = (0, react.useRef)(true);
|
|
5296
|
+
/** dry-run 计划请求代数:快速切换快照时作废在途旧请求(防晚到响应覆盖新选择) */
|
|
5297
|
+
const planGeneration = (0, react.useRef)(0);
|
|
5298
|
+
/** 执行恢复的二次确认弹窗开关(危险操作,DESIGN.md §8.11 场景) */
|
|
5299
|
+
const [confirmOpen, setConfirmOpen] = (0, react.useState)(false);
|
|
4973
5300
|
/**
|
|
4974
5301
|
* 统一提交入口:更新 stateRef → 挂载时 setState → **总是**镜像进 runStore。
|
|
4975
5302
|
* 关键:镜像不依赖 effect flush —— 异步操作(dry-run 计划/执行恢复)完成回调
|
|
4976
5303
|
* 在组件已卸载(切走 tab)时也能把结果(plan/report)写进 store,切回恢复。
|
|
5304
|
+
* backupDraft 由 BackupScheduleCard 独立管理,此处从 store 读回原值避免覆盖。
|
|
4977
5305
|
*/
|
|
4978
5306
|
const commit = (next) => {
|
|
4979
5307
|
stateRef.current = next;
|
|
4980
5308
|
if (mountedRef.current) setState(next);
|
|
4981
|
-
runStore.
|
|
5309
|
+
const backupDraft = runStore.getSnapshot().snapshots.backupDraft;
|
|
5310
|
+
runStore.patch({ snapshots: {
|
|
5311
|
+
...toSnapshotsStoreSlice({
|
|
5312
|
+
...next,
|
|
5313
|
+
backupDraft
|
|
5314
|
+
}),
|
|
5315
|
+
backupDraft
|
|
5316
|
+
} });
|
|
4982
5317
|
};
|
|
4983
5318
|
const patch = (p) => commit({
|
|
4984
5319
|
...stateRef.current,
|
|
@@ -4987,7 +5322,14 @@ function SnapshotsPanel({ api, t }) {
|
|
|
4987
5322
|
/** 卸载时置挂载守卫 + 最后镜像一次(防止「最后一次改动后立即切 tab」时丢状态)。 */
|
|
4988
5323
|
(0, react.useEffect)(() => () => {
|
|
4989
5324
|
mountedRef.current = false;
|
|
4990
|
-
runStore.
|
|
5325
|
+
const backupDraft = runStore.getSnapshot().snapshots.backupDraft;
|
|
5326
|
+
runStore.patch({ snapshots: {
|
|
5327
|
+
...toSnapshotsStoreSlice({
|
|
5328
|
+
...stateRef.current,
|
|
5329
|
+
backupDraft
|
|
5330
|
+
}),
|
|
5331
|
+
backupDraft
|
|
5332
|
+
} });
|
|
4991
5333
|
}, []);
|
|
4992
5334
|
const load = () => {
|
|
4993
5335
|
patch({
|
|
@@ -5008,6 +5350,8 @@ function SnapshotsPanel({ api, t }) {
|
|
|
5008
5350
|
};
|
|
5009
5351
|
(0, react.useEffect)(load, [api]);
|
|
5010
5352
|
const select = (id) => {
|
|
5353
|
+
const generation = planGeneration.current + 1;
|
|
5354
|
+
planGeneration.current = generation;
|
|
5011
5355
|
patch({
|
|
5012
5356
|
selectedId: id,
|
|
5013
5357
|
plan: null,
|
|
@@ -5016,11 +5360,13 @@ function SnapshotsPanel({ api, t }) {
|
|
|
5016
5360
|
planning: true
|
|
5017
5361
|
});
|
|
5018
5362
|
api.restoreSnapshot(id, true).then((res) => {
|
|
5363
|
+
if (generation !== planGeneration.current) return;
|
|
5019
5364
|
patch({
|
|
5020
5365
|
planning: false,
|
|
5021
5366
|
plan: res.plan ?? null
|
|
5022
5367
|
});
|
|
5023
5368
|
}, (err) => {
|
|
5369
|
+
if (generation !== planGeneration.current) return;
|
|
5024
5370
|
patch({
|
|
5025
5371
|
planning: false,
|
|
5026
5372
|
actionError: err instanceof Error ? err.message : String(err)
|
|
@@ -5034,6 +5380,8 @@ function SnapshotsPanel({ api, t }) {
|
|
|
5034
5380
|
report: null,
|
|
5035
5381
|
actionError: null
|
|
5036
5382
|
});
|
|
5383
|
+
setConfirmOpen(false);
|
|
5384
|
+
runStore.watchRunning("restore", 500);
|
|
5037
5385
|
api.restoreSnapshot(state.selectedId, false).then((res) => {
|
|
5038
5386
|
patch({
|
|
5039
5387
|
running: false,
|
|
@@ -5044,6 +5392,8 @@ function SnapshotsPanel({ api, t }) {
|
|
|
5044
5392
|
running: false,
|
|
5045
5393
|
actionError: err instanceof Error ? err.message : String(err)
|
|
5046
5394
|
});
|
|
5395
|
+
}).finally(() => {
|
|
5396
|
+
runStore.stopRunWatch("restore");
|
|
5047
5397
|
});
|
|
5048
5398
|
};
|
|
5049
5399
|
const summary = () => {
|
|
@@ -5111,6 +5461,7 @@ function SnapshotsPanel({ api, t }) {
|
|
|
5111
5461
|
"aria-selected": selected,
|
|
5112
5462
|
"data-active": selected ? "" : void 0,
|
|
5113
5463
|
className: config_manager_module_css_default.snapshotRow,
|
|
5464
|
+
disabled: state.planning || state.running,
|
|
5114
5465
|
onClick: () => {
|
|
5115
5466
|
select(meta.id);
|
|
5116
5467
|
},
|
|
@@ -5166,8 +5517,9 @@ function SnapshotsPanel({ api, t }) {
|
|
|
5166
5517
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
5167
5518
|
variant: "danger",
|
|
5168
5519
|
disabled: state.running || state.plan.actions.every((a) => a.kind === "skip"),
|
|
5169
|
-
onClick:
|
|
5170
|
-
|
|
5520
|
+
onClick: () => {
|
|
5521
|
+
setConfirmOpen(true);
|
|
5522
|
+
},
|
|
5171
5523
|
children: state.running ? t("snapshots.executing") : t("snapshots.execute")
|
|
5172
5524
|
})
|
|
5173
5525
|
}),
|
|
@@ -5184,7 +5536,238 @@ function SnapshotsPanel({ api, t }) {
|
|
|
5184
5536
|
reportLine(t("snapshots.failed"), state.report.failed.map((f) => `${f.item}: ${f.reason}`), true),
|
|
5185
5537
|
reportLine(t("snapshots.skipped"), state.report.skipped, false)
|
|
5186
5538
|
] })
|
|
5187
|
-
] })
|
|
5539
|
+
] }),
|
|
5540
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BackupScheduleCard, {
|
|
5541
|
+
api,
|
|
5542
|
+
t
|
|
5543
|
+
}),
|
|
5544
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
|
|
5545
|
+
open: confirmOpen,
|
|
5546
|
+
title: t("snapshots.confirmTitle"),
|
|
5547
|
+
message: t("snapshots.confirmRestore"),
|
|
5548
|
+
confirmLabel: t("snapshots.execute"),
|
|
5549
|
+
cancelLabel: t("common.cancel"),
|
|
5550
|
+
danger: true,
|
|
5551
|
+
busy: state.running,
|
|
5552
|
+
onConfirm: execute,
|
|
5553
|
+
onCancel: () => {
|
|
5554
|
+
setConfirmOpen(false);
|
|
5555
|
+
}
|
|
5556
|
+
})
|
|
5557
|
+
]
|
|
5558
|
+
});
|
|
5559
|
+
}
|
|
5560
|
+
/** 间隔档位 → 字典键(t 的类型是字面量联合,switch 保持类型安全)。 */
|
|
5561
|
+
function intervalLabel$1(t, interval) {
|
|
5562
|
+
switch (interval) {
|
|
5563
|
+
case "6h": return t("backupSchedule.interval.6h");
|
|
5564
|
+
case "12h": return t("backupSchedule.interval.12h");
|
|
5565
|
+
case "24h": return t("backupSchedule.interval.24h");
|
|
5566
|
+
case "7d": return t("backupSchedule.interval.7d");
|
|
5567
|
+
}
|
|
5568
|
+
}
|
|
5569
|
+
/** 上次运行状态 → 字典键。 */
|
|
5570
|
+
function runStatusLabel(t, status) {
|
|
5571
|
+
switch (status) {
|
|
5572
|
+
case "success": return t("backupSchedule.status.success");
|
|
5573
|
+
case "skipped": return t("backupSchedule.status.skipped");
|
|
5574
|
+
case "failed": return t("backupSchedule.status.failed");
|
|
5575
|
+
default: return "—";
|
|
5576
|
+
}
|
|
5577
|
+
}
|
|
5578
|
+
function formatRunTime(iso) {
|
|
5579
|
+
if (iso === void 0 || iso === "") return "";
|
|
5580
|
+
const d = new Date(iso);
|
|
5581
|
+
return Number.isNaN(d.getTime()) ? iso : d.toLocaleString();
|
|
5582
|
+
}
|
|
5583
|
+
/**
|
|
5584
|
+
* 定时全量备份设置卡:总开关 + 间隔档位 + 上次运行状态 + 保存 / 立即备份。
|
|
5585
|
+
* 状态自持;草稿镜像 runStore.snapshots.backupDraft(未保存修改切 tab/刷新保留),
|
|
5586
|
+
* 保存成功清草稿(宿主配置为权威)。
|
|
5587
|
+
*/
|
|
5588
|
+
function BackupScheduleCard({ api, t }) {
|
|
5589
|
+
const [status, setStatus] = (0, react.useState)("loading");
|
|
5590
|
+
const [error, setError] = (0, react.useState)(null);
|
|
5591
|
+
const [draft, setDraft] = (0, react.useState)({
|
|
5592
|
+
enabled: false,
|
|
5593
|
+
interval: "24h"
|
|
5594
|
+
});
|
|
5595
|
+
const [saved, setSaved] = (0, react.useState)(null);
|
|
5596
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
5597
|
+
const [running, setRunning] = (0, react.useState)(false);
|
|
5598
|
+
const [lastRun, setLastRun] = (0, react.useState)(void 0);
|
|
5599
|
+
const [lastRunDetail, setLastRunDetail] = (0, react.useState)(null);
|
|
5600
|
+
const [flash, setFlash] = (0, react.useState)(null);
|
|
5601
|
+
const [actionError, setActionError] = (0, react.useState)(null);
|
|
5602
|
+
/** 挂载守卫:切 tab 卸载后异步回调只更新 store(草稿),不再 setState(React 18 无告警但浪费) */
|
|
5603
|
+
const mountedRef = (0, react.useRef)(true);
|
|
5604
|
+
(0, react.useEffect)(() => () => {
|
|
5605
|
+
mountedRef.current = false;
|
|
5606
|
+
}, []);
|
|
5607
|
+
const load = () => {
|
|
5608
|
+
setStatus("loading");
|
|
5609
|
+
setError(null);
|
|
5610
|
+
api.backupSchedule().then((schedule) => {
|
|
5611
|
+
if (!mountedRef.current) return;
|
|
5612
|
+
setSaved(schedule);
|
|
5613
|
+
setDraft(runStore.getSnapshot().snapshots.backupDraft ?? {
|
|
5614
|
+
enabled: schedule.enabled,
|
|
5615
|
+
interval: schedule.interval
|
|
5616
|
+
});
|
|
5617
|
+
setLastRun(schedule.lastRunStatus);
|
|
5618
|
+
setLastRunDetail(formatRunTime(schedule.lastRunAt));
|
|
5619
|
+
setStatus("ready");
|
|
5620
|
+
}, (err) => {
|
|
5621
|
+
if (!mountedRef.current) return;
|
|
5622
|
+
setStatus("error");
|
|
5623
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
5624
|
+
});
|
|
5625
|
+
};
|
|
5626
|
+
(0, react.useEffect)(load, [api]);
|
|
5627
|
+
const updateDraft = (next) => {
|
|
5628
|
+
setDraft(next);
|
|
5629
|
+
runStore.patch({ snapshots: { backupDraft: next } });
|
|
5630
|
+
};
|
|
5631
|
+
const save = () => {
|
|
5632
|
+
if (saving || running) return;
|
|
5633
|
+
const parsed = validateBackupScheduleDraft(draft);
|
|
5634
|
+
if (!parsed.ok) {
|
|
5635
|
+
setActionError(parsed.error);
|
|
5636
|
+
return;
|
|
5637
|
+
}
|
|
5638
|
+
setSaving(true);
|
|
5639
|
+
setFlash(null);
|
|
5640
|
+
setActionError(null);
|
|
5641
|
+
api.saveBackupSchedule(parsed.value).then((schedule) => {
|
|
5642
|
+
runStore.patch({ snapshots: { backupDraft: null } });
|
|
5643
|
+
if (!mountedRef.current) return;
|
|
5644
|
+
setSaved(schedule);
|
|
5645
|
+
setDraft({
|
|
5646
|
+
enabled: schedule.enabled,
|
|
5647
|
+
interval: schedule.interval
|
|
5648
|
+
});
|
|
5649
|
+
setLastRun(schedule.lastRunStatus);
|
|
5650
|
+
setLastRunDetail(formatRunTime(schedule.lastRunAt));
|
|
5651
|
+
setSaving(false);
|
|
5652
|
+
setFlash(t("backupSchedule.saved"));
|
|
5653
|
+
}, (err) => {
|
|
5654
|
+
if (!mountedRef.current) return;
|
|
5655
|
+
setSaving(false);
|
|
5656
|
+
setActionError(err instanceof Error ? err.message : String(err));
|
|
5657
|
+
});
|
|
5658
|
+
};
|
|
5659
|
+
const runNow = () => {
|
|
5660
|
+
if (running || saving) return;
|
|
5661
|
+
setRunning(true);
|
|
5662
|
+
setFlash(null);
|
|
5663
|
+
setActionError(null);
|
|
5664
|
+
api.runBackupNow().then((res) => {
|
|
5665
|
+
if (!mountedRef.current) return;
|
|
5666
|
+
setSaved(res.schedule);
|
|
5667
|
+
setLastRun(res.run.status);
|
|
5668
|
+
setLastRunDetail(res.run.zip !== void 0 && res.run.zip !== "" ? res.run.zip : res.run.skipReason !== void 0 ? res.run.skipReason : formatRunTime(res.schedule.lastRunAt));
|
|
5669
|
+
setRunning(false);
|
|
5670
|
+
}, (err) => {
|
|
5671
|
+
if (!mountedRef.current) return;
|
|
5672
|
+
setRunning(false);
|
|
5673
|
+
setActionError(err instanceof Error ? err.message : String(err));
|
|
5674
|
+
});
|
|
5675
|
+
};
|
|
5676
|
+
const dirty = backupDraftDirty(draft, saved);
|
|
5677
|
+
const busy = saving || running;
|
|
5678
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, {
|
|
5679
|
+
className: config_manager_module_css_default.card,
|
|
5680
|
+
children: [
|
|
5681
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5682
|
+
className: config_manager_module_css_default.groupLabel,
|
|
5683
|
+
children: t("backupSchedule.title")
|
|
5684
|
+
}),
|
|
5685
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5686
|
+
className: config_manager_module_css_default.hint,
|
|
5687
|
+
children: t("backupSchedule.hint")
|
|
5688
|
+
}),
|
|
5689
|
+
status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("backupSchedule.loading") }),
|
|
5690
|
+
status === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Banner, {
|
|
5691
|
+
kind: "error",
|
|
5692
|
+
children: [t("backupSchedule.error"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
5693
|
+
variant: "primary",
|
|
5694
|
+
onClick: load,
|
|
5695
|
+
children: t("common.retry")
|
|
5696
|
+
})]
|
|
5697
|
+
}),
|
|
5698
|
+
status === "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
5699
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
5700
|
+
label: t("backupSchedule.enabled"),
|
|
5701
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Checkbox, {
|
|
5702
|
+
checked: draft.enabled,
|
|
5703
|
+
onChange: (checked) => {
|
|
5704
|
+
updateDraft({
|
|
5705
|
+
...draft,
|
|
5706
|
+
enabled: checked
|
|
5707
|
+
});
|
|
5708
|
+
},
|
|
5709
|
+
label: t("backupSchedule.enabledHint"),
|
|
5710
|
+
disabled: busy
|
|
5711
|
+
})
|
|
5712
|
+
}),
|
|
5713
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
5714
|
+
label: t("backupSchedule.interval"),
|
|
5715
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
5716
|
+
className: config_manager_module_css_default.select,
|
|
5717
|
+
value: draft.interval,
|
|
5718
|
+
disabled: busy,
|
|
5719
|
+
onChange: (event) => {
|
|
5720
|
+
updateDraft({
|
|
5721
|
+
...draft,
|
|
5722
|
+
interval: event.target.value
|
|
5723
|
+
});
|
|
5724
|
+
},
|
|
5725
|
+
children: BACKUP_INTERVAL_OPTIONS.map((interval) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
5726
|
+
value: interval,
|
|
5727
|
+
children: intervalLabel$1(t, interval)
|
|
5728
|
+
}, interval))
|
|
5729
|
+
})
|
|
5730
|
+
}),
|
|
5731
|
+
lastRun !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5732
|
+
className: config_manager_module_css_default.statRow,
|
|
5733
|
+
children: [
|
|
5734
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5735
|
+
className: config_manager_module_css_default.hint,
|
|
5736
|
+
children: t("backupSchedule.lastRun")
|
|
5737
|
+
}),
|
|
5738
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
5739
|
+
kind: backupRunBadgeKind(lastRun),
|
|
5740
|
+
children: runStatusLabel(t, lastRun)
|
|
5741
|
+
}),
|
|
5742
|
+
lastRunDetail !== null && lastRunDetail !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5743
|
+
className: config_manager_module_css_default.hint,
|
|
5744
|
+
children: lastRunDetail
|
|
5745
|
+
})
|
|
5746
|
+
]
|
|
5747
|
+
}),
|
|
5748
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5749
|
+
className: config_manager_module_css_default.actionRow,
|
|
5750
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
5751
|
+
variant: "primary",
|
|
5752
|
+
disabled: busy || !dirty,
|
|
5753
|
+
onClick: save,
|
|
5754
|
+
title: dirty ? void 0 : t("backupSchedule.saved"),
|
|
5755
|
+
children: saving ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("backupSchedule.save") }) : t("backupSchedule.save")
|
|
5756
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
5757
|
+
disabled: busy || !(saved?.enabled ?? false),
|
|
5758
|
+
onClick: runNow,
|
|
5759
|
+
children: running ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("backupSchedule.running") }) : t("backupSchedule.runNow")
|
|
5760
|
+
})]
|
|
5761
|
+
})
|
|
5762
|
+
] }),
|
|
5763
|
+
flash !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
|
|
5764
|
+
kind: "ok",
|
|
5765
|
+
children: flash
|
|
5766
|
+
}),
|
|
5767
|
+
actionError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
|
|
5768
|
+
kind: "error",
|
|
5769
|
+
children: actionError
|
|
5770
|
+
})
|
|
5188
5771
|
]
|
|
5189
5772
|
});
|
|
5190
5773
|
}
|
|
@@ -5420,13 +6003,17 @@ function SyncHistoryView(props) {
|
|
|
5420
6003
|
const [loading, setLoading] = (0, react.useState)(true);
|
|
5421
6004
|
const [error, setError] = (0, react.useState)(null);
|
|
5422
6005
|
const [entries, setEntries] = (0, react.useState)([]);
|
|
6006
|
+
/** 重试计数(错误态点「重试」递增 → 重新加载) */
|
|
6007
|
+
const [reloadKey, setReloadKey] = (0, react.useState)(0);
|
|
5423
6008
|
(0, react.useEffect)(() => {
|
|
5424
6009
|
let cancelled = false;
|
|
6010
|
+
setLoading(true);
|
|
5425
6011
|
(async () => {
|
|
5426
6012
|
try {
|
|
5427
6013
|
const data = await api.history();
|
|
5428
6014
|
if (!cancelled) {
|
|
5429
6015
|
setEntries(data.entries);
|
|
6016
|
+
setError(null);
|
|
5430
6017
|
setLoading(false);
|
|
5431
6018
|
}
|
|
5432
6019
|
} catch (err) {
|
|
@@ -5439,7 +6026,7 @@ function SyncHistoryView(props) {
|
|
|
5439
6026
|
return () => {
|
|
5440
6027
|
cancelled = true;
|
|
5441
6028
|
};
|
|
5442
|
-
}, [api]);
|
|
6029
|
+
}, [api, reloadKey]);
|
|
5443
6030
|
const rows = (0, react.useMemo)(() => projectSyncHistoryEntries(entries), [entries]);
|
|
5444
6031
|
const snapshotRows = (0, react.useMemo)(() => rows.filter((r) => r.kind === "apply" || r.kind === "push" || r.kind === "pull" || r.kind === "rollback").map((r) => ({
|
|
5445
6032
|
id: r.id,
|
|
@@ -5448,10 +6035,14 @@ function SyncHistoryView(props) {
|
|
|
5448
6035
|
reviewCount: r.reviewCount ?? 0
|
|
5449
6036
|
})), [rows]);
|
|
5450
6037
|
if (loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("common.loading") });
|
|
5451
|
-
if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
6038
|
+
if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBanner, {
|
|
6039
|
+
error,
|
|
6040
|
+
onRetry: () => {
|
|
6041
|
+
setReloadKey((k) => k + 1);
|
|
6042
|
+
},
|
|
6043
|
+
retrying: loading,
|
|
6044
|
+
t: api.t
|
|
6045
|
+
});
|
|
5455
6046
|
if (rows.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("history.empty") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("history.emptyHint") })] });
|
|
5456
6047
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionTitle, { title: `${t("history.title")}(${rows.length})` }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
5457
6048
|
className: "sync-history-table",
|
|
@@ -7506,125 +8097,6 @@ const MARKET_UPSTREAM_OWNER = "xiajiajun516";
|
|
|
7506
8097
|
/** 官方收录目标仓库名(见 MARKET_UPSTREAM_OWNER) */
|
|
7507
8098
|
const MARKET_UPSTREAM_REPO = "dsh-config-market";
|
|
7508
8099
|
//#endregion
|
|
7509
|
-
//#region src/client/common/ConfirmDialog.tsx
|
|
7510
|
-
/**
|
|
7511
|
-
* ConfirmDialog —— 确认弹窗(危险/重要操作的二次确认,DESIGN.md §8.11 / §14)。
|
|
7512
|
-
*
|
|
7513
|
-
* 背景(2026-08-20):用户要求「删除按钮需要弹窗弹出二次确认」,替代原先的
|
|
7514
|
-
* 「行内两步确认」(已从 DESIGN.md §14 移除,见 t8 设计)。本项目此前无任何
|
|
7515
|
-
* modal / fixed / z-index 元素,本组件是首个浮动层 —— DESIGN.md §15 Anti-pattern #7
|
|
7516
|
-
* 已登记豁免(z-index 100;浮动层必须先设计进 DESIGN.md 的流程满足)。
|
|
7517
|
-
*
|
|
7518
|
-
* 交互约定:
|
|
7519
|
-
* - 受控组件:open=false 时不渲染;open=true 渲染遮罩 + 居中卡片;
|
|
7520
|
-
* - 关闭三途径:遮罩点击(仅 e.target === currentTarget)、Esc 键、取消按钮;
|
|
7521
|
-
* (可选 backdropClose:遮罩点击 / Esc 走此回调而非 onCancel —— 用于「不再提示」类
|
|
7522
|
-
* 弹窗,用户点遮罩只是暂时关闭、不算表态;缺省时三途径一致走 onCancel);
|
|
7523
|
-
* - busy=true(或 onConfirm 返回 Promise 的自管 busy)时禁用一切关闭途径与确认按钮,
|
|
7524
|
-
* 防重复提交;
|
|
7525
|
-
* - 初始焦点在取消按钮(危险确认不默认落破坏性按钮);关闭后还原焦点到触发按钮
|
|
7526
|
-
* (open 前的 document.activeElement);
|
|
7527
|
-
* - 不做完整 focus trap(两按钮场景风险可接受,DESIGN.md §8.11 已注明)。
|
|
7528
|
-
*
|
|
7529
|
-
* 安全:message 由调用方传入(渲染前已 redact 兜底,与全站一致);本组件不触碰任何凭据。
|
|
7530
|
-
* 样式:全部走 --dsw-* token(遮罩 color-mix 半透明、卡片 bg-layer-2 + border-l1),
|
|
7531
|
-
* 仅新增 dialogMask/dialogCard/dialogHeader/dialogBody 四个类(config-manager.module.css)。
|
|
7532
|
-
*/
|
|
7533
|
-
/**
|
|
7534
|
-
* 确认弹窗:遮罩 + 居中卡片 + 标题/正文/按钮区。
|
|
7535
|
-
* 自管 busy:onConfirm 返回 Promise 时置 busy 直到 resolve(reject 仍关闭 busy,错误由调用方处理)。
|
|
7536
|
-
*/
|
|
7537
|
-
function ConfirmDialog({ open, title, message, confirmLabel, cancelLabel, danger, busy: busyProp, onConfirm, onCancel, backdropClose, children }) {
|
|
7538
|
-
const [selfBusy, setSelfBusy] = (0, react.useState)(false);
|
|
7539
|
-
const busy = busyProp === true || selfBusy;
|
|
7540
|
-
/** open 前的活动元素(关闭后还原焦点) */
|
|
7541
|
-
const prevFocus = (0, react.useRef)(null);
|
|
7542
|
-
/** 取消按钮 ref(初始焦点) */
|
|
7543
|
-
const cancelRef = (0, react.useRef)(null);
|
|
7544
|
-
/** 遮罩/Esc 关闭回调(缺省 = 取消按钮同一回调) */
|
|
7545
|
-
const handleBackdropClose = backdropClose ?? onCancel;
|
|
7546
|
-
(0, react.useEffect)(() => {
|
|
7547
|
-
if (open) {
|
|
7548
|
-
prevFocus.current = document.activeElement;
|
|
7549
|
-
requestAnimationFrame(() => {
|
|
7550
|
-
cancelRef.current?.focus();
|
|
7551
|
-
});
|
|
7552
|
-
return;
|
|
7553
|
-
}
|
|
7554
|
-
if (prevFocus.current !== null && prevFocus.current instanceof HTMLElement) {
|
|
7555
|
-
prevFocus.current.focus();
|
|
7556
|
-
prevFocus.current = null;
|
|
7557
|
-
}
|
|
7558
|
-
}, [open]);
|
|
7559
|
-
(0, react.useEffect)(() => {
|
|
7560
|
-
if (!open || busy) return;
|
|
7561
|
-
const close = backdropClose ?? onCancel;
|
|
7562
|
-
const onKey = (e) => {
|
|
7563
|
-
if (e.key === "Escape") close();
|
|
7564
|
-
};
|
|
7565
|
-
window.addEventListener("keydown", onKey);
|
|
7566
|
-
return () => {
|
|
7567
|
-
window.removeEventListener("keydown", onKey);
|
|
7568
|
-
};
|
|
7569
|
-
}, [
|
|
7570
|
-
open,
|
|
7571
|
-
busy,
|
|
7572
|
-
backdropClose,
|
|
7573
|
-
onCancel
|
|
7574
|
-
]);
|
|
7575
|
-
if (!open) return null;
|
|
7576
|
-
const handleConfirm = () => {
|
|
7577
|
-
if (busy) return;
|
|
7578
|
-
const result = onConfirm();
|
|
7579
|
-
if (result instanceof Promise) {
|
|
7580
|
-
setSelfBusy(true);
|
|
7581
|
-
result.finally(() => {
|
|
7582
|
-
setSelfBusy(false);
|
|
7583
|
-
});
|
|
7584
|
-
}
|
|
7585
|
-
};
|
|
7586
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7587
|
-
className: config_manager_module_css_default.dialogMask,
|
|
7588
|
-
onMouseDown: (e) => {
|
|
7589
|
-
if (e.target === e.currentTarget && !busy) handleBackdropClose();
|
|
7590
|
-
},
|
|
7591
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7592
|
-
className: config_manager_module_css_default.dialogCard,
|
|
7593
|
-
role: "dialog",
|
|
7594
|
-
"aria-modal": "true",
|
|
7595
|
-
"aria-label": title,
|
|
7596
|
-
children: [
|
|
7597
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7598
|
-
className: config_manager_module_css_default.dialogHeader,
|
|
7599
|
-
children: title
|
|
7600
|
-
}),
|
|
7601
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7602
|
-
className: config_manager_module_css_default.dialogBody,
|
|
7603
|
-
children: [message !== void 0 && message !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: message }), children]
|
|
7604
|
-
}),
|
|
7605
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7606
|
-
className: config_manager_module_css_default.actionRow,
|
|
7607
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
7608
|
-
variant: danger === true ? "danger" : "primary",
|
|
7609
|
-
disabled: busy,
|
|
7610
|
-
onClick: () => {
|
|
7611
|
-
handleConfirm();
|
|
7612
|
-
},
|
|
7613
|
-
children: busy ? "…" : confirmLabel ?? ""
|
|
7614
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7615
|
-
ref: cancelRef,
|
|
7616
|
-
type: "button",
|
|
7617
|
-
className: config_manager_module_css_default.ghostButton,
|
|
7618
|
-
disabled: busy,
|
|
7619
|
-
onClick: onCancel,
|
|
7620
|
-
children: cancelLabel ?? ""
|
|
7621
|
-
})]
|
|
7622
|
-
})
|
|
7623
|
-
]
|
|
7624
|
-
})
|
|
7625
|
-
});
|
|
7626
|
-
}
|
|
7627
|
-
//#endregion
|
|
7628
8100
|
//#region src/client/market/disclaimer.ts
|
|
7629
8101
|
/** localStorage key 前缀(按 key 隔离三个操作) */
|
|
7630
8102
|
const DISCLAIMER_STORAGE_PREFIX = "dsh-cm-market.disclaimer.";
|
|
@@ -8651,7 +9123,9 @@ function MyConfigsView({ meApi, api, importApi, syncApi, t, myItems, myItemsErro
|
|
|
8651
9123
|
setDeletingId(null);
|
|
8652
9124
|
}
|
|
8653
9125
|
};
|
|
8654
|
-
/** 装回本地(复用市场下载 + 逐分区批准链路;条目内容在用户自己的公开仓库,必须带 repo 来源)
|
|
9126
|
+
/** 装回本地(复用市场下载 + 逐分区批准链路;条目内容在用户自己的公开仓库,必须带 repo 来源)
|
|
9127
|
+
* 竞态守卫:下载期间用户可切换装回另一条目(install.itemId 已变),晚到响应一律丢弃,
|
|
9128
|
+
* 防止「会话标题是 B、详情是 A」的串扰。 */
|
|
8655
9129
|
const runDownload = async (entry) => {
|
|
8656
9130
|
commitInstall({
|
|
8657
9131
|
itemId: entry.id,
|
|
@@ -8663,13 +9137,15 @@ function MyConfigsView({ meApi, api, importApi, syncApi, t, myItems, myItemsErro
|
|
|
8663
9137
|
});
|
|
8664
9138
|
try {
|
|
8665
9139
|
const detail = await api.download(entry.id, entry.repoUrl);
|
|
8666
|
-
if (installRef.current !==
|
|
9140
|
+
if (installRef.current === null || installRef.current.itemId !== entry.id) return;
|
|
9141
|
+
commitInstall({
|
|
8667
9142
|
...installRef.current,
|
|
8668
9143
|
detail,
|
|
8669
9144
|
approvals: defaultApprovals(detail.plan)
|
|
8670
9145
|
});
|
|
8671
9146
|
} catch (err) {
|
|
8672
|
-
if (installRef.current
|
|
9147
|
+
if (installRef.current === null || installRef.current.itemId !== entry.id) return;
|
|
9148
|
+
patchInstall({ error: err instanceof Error ? err.message : String(err) });
|
|
8673
9149
|
}
|
|
8674
9150
|
};
|
|
8675
9151
|
const runImport = async () => {
|
|
@@ -9235,7 +9711,7 @@ function MyConfigsView({ meApi, api, importApi, syncApi, t, myItems, myItemsErro
|
|
|
9235
9711
|
children: t("myconfigs.item.update")
|
|
9236
9712
|
}),
|
|
9237
9713
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
9238
|
-
disabled: install !== null && install.
|
|
9714
|
+
disabled: install !== null && install.detail === null,
|
|
9239
9715
|
onClick: () => {
|
|
9240
9716
|
openInstall(entry);
|
|
9241
9717
|
},
|
|
@@ -9680,7 +10156,9 @@ function MarketPanel({ api, myConfigsApi, importApi, syncApi, t }) {
|
|
|
9680
10156
|
});
|
|
9681
10157
|
}
|
|
9682
10158
|
};
|
|
9683
|
-
/** 下载 + 校验单条目 → dry-run 详情预览(零写入)。自托管条目(带 repo)必须携带来源仓库。
|
|
10159
|
+
/** 下载 + 校验单条目 → dry-run 详情预览(零写入)。自托管条目(带 repo)必须携带来源仓库。
|
|
10160
|
+
* 竞态守卫:下载期间用户可发起另一条目下载(downloadingId 已变),晚到响应一律丢弃,
|
|
10161
|
+
* 防止「弹窗标题是 B、详情是 A」的串扰。 */
|
|
9684
10162
|
const runDownload = async (item) => {
|
|
9685
10163
|
patch({
|
|
9686
10164
|
downloadingId: item.id,
|
|
@@ -9688,12 +10166,14 @@ function MarketPanel({ api, myConfigsApi, importApi, syncApi, t }) {
|
|
|
9688
10166
|
});
|
|
9689
10167
|
try {
|
|
9690
10168
|
const detail = await api.download(item.id, item.repo);
|
|
10169
|
+
if (stateRef.current.downloadingId !== item.id) return;
|
|
9691
10170
|
patch({
|
|
9692
10171
|
downloadingId: null,
|
|
9693
10172
|
detail,
|
|
9694
10173
|
approvals: defaultApprovals(detail.plan)
|
|
9695
10174
|
});
|
|
9696
10175
|
} catch (err) {
|
|
10176
|
+
if (stateRef.current.downloadingId !== item.id) return;
|
|
9697
10177
|
patch({
|
|
9698
10178
|
downloadingId: null,
|
|
9699
10179
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -9804,13 +10284,13 @@ function MarketPanel({ api, myConfigsApi, importApi, syncApi, t }) {
|
|
|
9804
10284
|
className: config_manager_module_css_default.actionRow,
|
|
9805
10285
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
9806
10286
|
variant: "primary",
|
|
9807
|
-
disabled: state.refreshing || state.importing,
|
|
10287
|
+
disabled: state.refreshing || state.importing || state.browsing,
|
|
9808
10288
|
onClick: () => {
|
|
9809
10289
|
runRefresh();
|
|
9810
10290
|
},
|
|
9811
10291
|
children: state.refreshing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("config.refreshing") }) : t("config.refresh")
|
|
9812
10292
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
9813
|
-
disabled: state.browsing,
|
|
10293
|
+
disabled: state.browsing || state.refreshing || state.importing,
|
|
9814
10294
|
onClick: () => {
|
|
9815
10295
|
runBrowse();
|
|
9816
10296
|
},
|
|
@@ -10116,7 +10596,7 @@ function MarketPanel({ api, myConfigsApi, importApi, syncApi, t }) {
|
|
|
10116
10596
|
})
|
|
10117
10597
|
]
|
|
10118
10598
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
10119
|
-
disabled: state.downloadingId
|
|
10599
|
+
disabled: state.downloadingId !== null,
|
|
10120
10600
|
onClick: () => {
|
|
10121
10601
|
openDownload(it);
|
|
10122
10602
|
},
|
|
@@ -10659,6 +11139,7 @@ const zh$2 = {
|
|
|
10659
11139
|
"import.importing": "正在导入…(插件安装较多时可能需要几分钟,请勿关闭页面)",
|
|
10660
11140
|
"import.log.title": "导入日志",
|
|
10661
11141
|
"import.log.empty": "等待命令输出…",
|
|
11142
|
+
"import.log.newOutput": "↓ 新输出",
|
|
10662
11143
|
"import.skipCurrent": "跳过当前插件",
|
|
10663
11144
|
"import.skipPending": "正在跳过…其余项将继续导入",
|
|
10664
11145
|
"import.retrySkipped": "重试失败/跳过的项 ({count})",
|
|
@@ -10704,6 +11185,7 @@ const zh$2 = {
|
|
|
10704
11185
|
"snapshots.noActions": "该快照无可用恢复动作(或全部跳过)。",
|
|
10705
11186
|
"snapshots.execute": "执行恢复",
|
|
10706
11187
|
"snapshots.executing": "恢复执行中…",
|
|
11188
|
+
"snapshots.confirmTitle": "确认恢复",
|
|
10707
11189
|
"snapshots.confirmRestore": "确认执行恢复?当前文件会先备份到快照的 pre-restore 目录,插件通过官方 dsh plugin remove 卸载。",
|
|
10708
11190
|
"snapshots.reportTitle": "恢复报告",
|
|
10709
11191
|
"snapshots.restored": "已还原",
|
|
@@ -10711,6 +11193,26 @@ const zh$2 = {
|
|
|
10711
11193
|
"snapshots.manualHints": "需人工处理",
|
|
10712
11194
|
"snapshots.failed": "失败",
|
|
10713
11195
|
"snapshots.skipped": "跳过",
|
|
11196
|
+
"backupSchedule.title": "定时全量备份",
|
|
11197
|
+
"backupSchedule.hint": "按固定间隔在后台自动导出全量备份 ZIP(恒不含 secret、不加密;要加密请手动导出)。配置存 sync/backup-schedule.json,随 self 分区备份 / 远程同步迁移。",
|
|
11198
|
+
"backupSchedule.enabled": "启用定时备份",
|
|
11199
|
+
"backupSchedule.enabledHint": "开启后 DSH 在后台按间隔自动备份;每次启动时立即执行一次(距上次运行不足 1 小时则跳过启动触发)。",
|
|
11200
|
+
"backupSchedule.interval": "备份间隔",
|
|
11201
|
+
"backupSchedule.interval.6h": "每 6 小时",
|
|
11202
|
+
"backupSchedule.interval.12h": "每 12 小时",
|
|
11203
|
+
"backupSchedule.interval.24h": "每 24 小时",
|
|
11204
|
+
"backupSchedule.interval.7d": "每 7 天",
|
|
11205
|
+
"backupSchedule.save": "保存设置",
|
|
11206
|
+
"backupSchedule.saved": "设置已保存",
|
|
11207
|
+
"backupSchedule.runNow": "立即备份",
|
|
11208
|
+
"backupSchedule.running": "备份中…",
|
|
11209
|
+
"backupSchedule.lastRun": "上次运行",
|
|
11210
|
+
"backupSchedule.never": "从未运行",
|
|
11211
|
+
"backupSchedule.status.success": "成功",
|
|
11212
|
+
"backupSchedule.status.skipped": "已跳过",
|
|
11213
|
+
"backupSchedule.status.failed": "失败",
|
|
11214
|
+
"backupSchedule.loading": "加载定时备份设置…",
|
|
11215
|
+
"backupSchedule.error": "定时备份设置加载失败",
|
|
10714
11216
|
"report.export.title": "导出完成",
|
|
10715
11217
|
"report.import.title": "导入结果",
|
|
10716
11218
|
"report.rollback.title": "回滚报告",
|
|
@@ -10808,6 +11310,7 @@ const en$2 = {
|
|
|
10808
11310
|
"import.importing": "Importing… (installing many plugins may take a few minutes; keep this page open)",
|
|
10809
11311
|
"import.log.title": "Import Log",
|
|
10810
11312
|
"import.log.empty": "Waiting for command output…",
|
|
11313
|
+
"import.log.newOutput": "↓ New output",
|
|
10811
11314
|
"import.skipCurrent": "Skip current plugin",
|
|
10812
11315
|
"import.skipPending": "Skipping… the rest of the import continues",
|
|
10813
11316
|
"import.retrySkipped": "Retry failed/skipped items ({count})",
|
|
@@ -10853,6 +11356,7 @@ const en$2 = {
|
|
|
10853
11356
|
"snapshots.noActions": "No restorable actions for this snapshot (or all skipped).",
|
|
10854
11357
|
"snapshots.execute": "Restore now",
|
|
10855
11358
|
"snapshots.executing": "Restoring…",
|
|
11359
|
+
"snapshots.confirmTitle": "Confirm restore",
|
|
10856
11360
|
"snapshots.confirmRestore": "Confirm restore? Current files are backed up to the snapshot's pre-restore folder first; plugins are removed via the official dsh plugin remove.",
|
|
10857
11361
|
"snapshots.reportTitle": "Restore Report",
|
|
10858
11362
|
"snapshots.restored": "Restored",
|
|
@@ -10860,6 +11364,26 @@ const en$2 = {
|
|
|
10860
11364
|
"snapshots.manualHints": "Manual action needed",
|
|
10861
11365
|
"snapshots.failed": "Failed",
|
|
10862
11366
|
"snapshots.skipped": "Skipped",
|
|
11367
|
+
"backupSchedule.title": "Scheduled Full Backups",
|
|
11368
|
+
"backupSchedule.hint": "Automatically export a full backup ZIP in the background on a fixed cadence (secrets are never included, never encrypted — use manual export for encryption). Config lives in sync/backup-schedule.json and migrates with the self section.",
|
|
11369
|
+
"backupSchedule.enabled": "Enable scheduled backups",
|
|
11370
|
+
"backupSchedule.enabledHint": "When on, DSH backs up automatically on the cadence below; one run fires on every startup (skipped if less than 1h since the last run).",
|
|
11371
|
+
"backupSchedule.interval": "Backup interval",
|
|
11372
|
+
"backupSchedule.interval.6h": "Every 6 hours",
|
|
11373
|
+
"backupSchedule.interval.12h": "Every 12 hours",
|
|
11374
|
+
"backupSchedule.interval.24h": "Every 24 hours",
|
|
11375
|
+
"backupSchedule.interval.7d": "Every 7 days",
|
|
11376
|
+
"backupSchedule.save": "Save settings",
|
|
11377
|
+
"backupSchedule.saved": "Settings saved",
|
|
11378
|
+
"backupSchedule.runNow": "Back up now",
|
|
11379
|
+
"backupSchedule.running": "Backing up…",
|
|
11380
|
+
"backupSchedule.lastRun": "Last run",
|
|
11381
|
+
"backupSchedule.never": "Never run",
|
|
11382
|
+
"backupSchedule.status.success": "Success",
|
|
11383
|
+
"backupSchedule.status.skipped": "Skipped",
|
|
11384
|
+
"backupSchedule.status.failed": "Failed",
|
|
11385
|
+
"backupSchedule.loading": "Loading scheduled backup settings…",
|
|
11386
|
+
"backupSchedule.error": "Failed to load scheduled backup settings",
|
|
10863
11387
|
"report.export.title": "Export Complete",
|
|
10864
11388
|
"report.import.title": "Import Result",
|
|
10865
11389
|
"report.rollback.title": "Rollback Report",
|