dsh-config-manager 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -1587,102 +1587,102 @@ if (typeof document !== "undefined" && document.querySelector("style[data-tag=\"
1587
1587
  document.head.appendChild(tag);
1588
1588
  }
1589
1589
  var config_manager_module_css_default = {
1590
- "badgeWarn": "_367UGW_badgeWarn",
1591
- "conflictList": "_367UGW_conflictList",
1592
- "warnText": "_367UGW_warnText",
1593
- "groupItems": "_367UGW_groupItems",
1594
- "errorItem": "_367UGW_errorItem",
1595
- "groupCard": "_367UGW_groupCard",
1596
- "progressBlock": "_367UGW_progressBlock",
1597
- "sectionHeader": "_367UGW_sectionHeader",
1598
- "sectionTitle": "_367UGW_sectionTitle",
1599
- "progressDetail": "_367UGW_progressDetail",
1600
- "reportText": "_367UGW_reportText",
1601
- "spinnerLabel": "_367UGW_spinnerLabel",
1602
- "errorActionLabel": "_367UGW_errorActionLabel",
1603
- "conflictItem": "_367UGW_conflictItem",
1604
- "hiddenFile": "_367UGW_hiddenFile",
1605
- "errorAction": "_367UGW_errorAction",
1606
- "errorBanner": "_367UGW_errorBanner",
1607
- "severityError": "_367UGW_severityError",
1608
- "badgeOk": "_367UGW_badgeOk",
1609
- "viewTab": "_367UGW_viewTab",
1610
- "sectionTitleBlock": "_367UGW_sectionTitleBlock",
1611
- "field": "_367UGW_field",
1612
- "pathNew": "_367UGW_pathNew",
1613
- "spinner": "_367UGW_spinner",
1614
- "progressIndeterminate": "_367UGW_progressIndeterminate",
1615
- "modeHint": "_367UGW_modeHint",
1616
- "secretFields": "_367UGW_secretFields",
1617
- "progressLabel": "_367UGW_progressLabel",
1618
- "categoryName": "_367UGW_categoryName",
1619
- "progressBadgeCount": "_367UGW_progressBadgeCount",
1620
- "reportList": "_367UGW_reportList",
1590
+ "groupNote": "_367UGW_groupNote",
1591
+ "reportView": "_367UGW_reportView",
1592
+ "categoryDesc": "_367UGW_categoryDesc",
1593
+ "actionRow": "_367UGW_actionRow",
1621
1594
  "banner": "_367UGW_banner",
1622
1595
  "progressPercent": "_367UGW_progressPercent",
1623
- "errorReason": "_367UGW_errorReason",
1624
- "sectionSubtitle": "_367UGW_sectionSubtitle",
1625
- "errorFooter": "_367UGW_errorFooter",
1626
- "rowActions": "_367UGW_rowActions",
1627
- "viewTabs": "_367UGW_viewTabs",
1628
- "progressBarDone": "_367UGW_progressBarDone",
1629
- "checkboxRow": "_367UGW_checkboxRow",
1630
- "hint": "_367UGW_hint",
1631
- "categoryDesc": "_367UGW_categoryDesc",
1632
- "progressBadge": "_367UGW_progressBadge",
1633
- "dangerButton": "_367UGW_dangerButton",
1596
+ "dshCmSpin": "_367UGW_dshCmSpin",
1634
1597
  "reportFooter": "_367UGW_reportFooter",
1635
- "pathIssueKind": "_367UGW_pathIssueKind",
1636
- "secretsList": "_367UGW_secretsList",
1637
- "groupLabel": "_367UGW_groupLabel",
1638
- "section": "_367UGW_section",
1598
+ "conflictId": "_367UGW_conflictId",
1599
+ "snapshotRowHeader": "_367UGW_snapshotRowHeader",
1639
1600
  "card": "_367UGW_card",
1640
- "formError": "_367UGW_formError",
1641
- "snapshotRow": "_367UGW_snapshotRow",
1642
- "kindTag": "_367UGW_kindTag",
1643
- "ghostButton": "_367UGW_ghostButton",
1644
- "viewBody": "_367UGW_viewBody",
1645
- "conflictDetail": "_367UGW_conflictDetail",
1646
- "groupHeader": "_367UGW_groupHeader",
1647
- "badge": "_367UGW_badge",
1601
+ "sectionHeader": "_367UGW_sectionHeader",
1648
1602
  "errorTitle": "_367UGW_errorTitle",
1649
- "conflictHead": "_367UGW_conflictHead",
1650
- "modeTab": "_367UGW_modeTab",
1651
- "warnList": "_367UGW_warnList",
1652
- "pathMappingList": "_367UGW_pathMappingList",
1653
- "pathRow": "_367UGW_pathRow",
1654
1603
  "primaryButton": "_367UGW_primaryButton",
1604
+ "conflictDetail": "_367UGW_conflictDetail",
1605
+ "secretsList": "_367UGW_secretsList",
1606
+ "errorList": "_367UGW_errorList",
1607
+ "rowActions": "_367UGW_rowActions",
1608
+ "progressBlock": "_367UGW_progressBlock",
1609
+ "kindTag": "_367UGW_kindTag",
1610
+ "reportList": "_367UGW_reportList",
1611
+ "statRow": "_367UGW_statRow",
1612
+ "dshCmIndeterminate": "_367UGW_dshCmIndeterminate",
1613
+ "dangerButton": "_367UGW_dangerButton",
1655
1614
  "progressBar": "_367UGW_progressBar",
1615
+ "badgeInfo": "_367UGW_badgeInfo",
1616
+ "groupCard": "_367UGW_groupCard",
1617
+ "hint": "_367UGW_hint",
1618
+ "warnList": "_367UGW_warnList",
1619
+ "errorAction": "_367UGW_errorAction",
1620
+ "spinnerWrap": "_367UGW_spinnerWrap",
1621
+ "spinnerLabel": "_367UGW_spinnerLabel",
1622
+ "warnText": "_367UGW_warnText",
1623
+ "viewTab": "_367UGW_viewTab",
1624
+ "errorReason": "_367UGW_errorReason",
1625
+ "conflictOptions": "_367UGW_conflictOptions",
1626
+ "sectionTitleBlock": "_367UGW_sectionTitleBlock",
1627
+ "viewTabs": "_367UGW_viewTabs",
1628
+ "progressTrack": "_367UGW_progressTrack",
1629
+ "section": "_367UGW_section",
1630
+ "viewBody": "_367UGW_viewBody",
1631
+ "radioLabel": "_367UGW_radioLabel",
1632
+ "errorBanner": "_367UGW_errorBanner",
1656
1633
  "errorLine": "_367UGW_errorLine",
1634
+ "conflictList": "_367UGW_conflictList",
1657
1635
  "modeTabs": "_367UGW_modeTabs",
1658
- "actionRow": "_367UGW_actionRow",
1659
- "reportView": "_367UGW_reportView",
1660
- "progressTrack": "_367UGW_progressTrack",
1661
- "progressBadgeSection": "_367UGW_progressBadgeSection",
1662
- "dshCmIndeterminate": "_367UGW_dshCmIndeterminate",
1663
- "progressMeta": "_367UGW_progressMeta",
1664
- "optionsCard": "_367UGW_optionsCard",
1665
- "conflictId": "_367UGW_conflictId",
1666
- "sectionBody": "_367UGW_sectionBody",
1667
- "badgeInfo": "_367UGW_badgeInfo",
1668
- "groupNote": "_367UGW_groupNote",
1669
- "statRow": "_367UGW_statRow",
1670
- "fieldLabel": "_367UGW_fieldLabel",
1636
+ "errorFooter": "_367UGW_errorFooter",
1637
+ "badge": "_367UGW_badge",
1638
+ "modeTab": "_367UGW_modeTab",
1639
+ "groupItems": "_367UGW_groupItems",
1640
+ "groupList": "_367UGW_groupList",
1641
+ "sectionSubtitle": "_367UGW_sectionSubtitle",
1642
+ "secretFields": "_367UGW_secretFields",
1643
+ "categoryName": "_367UGW_categoryName",
1644
+ "checkboxRow": "_367UGW_checkboxRow",
1671
1645
  "categoryItem": "_367UGW_categoryItem",
1646
+ "severityError": "_367UGW_severityError",
1647
+ "conflictItem": "_367UGW_conflictItem",
1648
+ "snapshotList": "_367UGW_snapshotList",
1649
+ "progressMeta": "_367UGW_progressMeta",
1672
1650
  "pathValue": "_367UGW_pathValue",
1673
- "empty": "_367UGW_empty",
1674
- "spinnerWrap": "_367UGW_spinnerWrap",
1675
- "input": "_367UGW_input",
1676
- "dshCmSpin": "_367UGW_dshCmSpin",
1677
- "radioLabel": "_367UGW_radioLabel",
1678
- "pathOld": "_367UGW_pathOld",
1651
+ "badgeWarn": "_367UGW_badgeWarn",
1652
+ "progressBadge": "_367UGW_progressBadge",
1679
1653
  "badgeError": "_367UGW_badgeError",
1654
+ "modeHint": "_367UGW_modeHint",
1680
1655
  "rollbackBox": "_367UGW_rollbackBox",
1681
- "errorList": "_367UGW_errorList",
1682
- "snapshotList": "_367UGW_snapshotList",
1683
- "snapshotRowHeader": "_367UGW_snapshotRowHeader",
1684
- "groupList": "_367UGW_groupList",
1685
- "conflictOptions": "_367UGW_conflictOptions"
1656
+ "empty": "_367UGW_empty",
1657
+ "fieldLabel": "_367UGW_fieldLabel",
1658
+ "reportText": "_367UGW_reportText",
1659
+ "pathOld": "_367UGW_pathOld",
1660
+ "progressLabel": "_367UGW_progressLabel",
1661
+ "input": "_367UGW_input",
1662
+ "field": "_367UGW_field",
1663
+ "sectionBody": "_367UGW_sectionBody",
1664
+ "badgeOk": "_367UGW_badgeOk",
1665
+ "formError": "_367UGW_formError",
1666
+ "errorItem": "_367UGW_errorItem",
1667
+ "pathMappingList": "_367UGW_pathMappingList",
1668
+ "pathRow": "_367UGW_pathRow",
1669
+ "sectionTitle": "_367UGW_sectionTitle",
1670
+ "snapshotRow": "_367UGW_snapshotRow",
1671
+ "hiddenFile": "_367UGW_hiddenFile",
1672
+ "progressBarDone": "_367UGW_progressBarDone",
1673
+ "errorActionLabel": "_367UGW_errorActionLabel",
1674
+ "pathIssueKind": "_367UGW_pathIssueKind",
1675
+ "conflictHead": "_367UGW_conflictHead",
1676
+ "groupHeader": "_367UGW_groupHeader",
1677
+ "groupLabel": "_367UGW_groupLabel",
1678
+ "progressDetail": "_367UGW_progressDetail",
1679
+ "spinner": "_367UGW_spinner",
1680
+ "progressIndeterminate": "_367UGW_progressIndeterminate",
1681
+ "progressBadgeSection": "_367UGW_progressBadgeSection",
1682
+ "ghostButton": "_367UGW_ghostButton",
1683
+ "progressBadgeCount": "_367UGW_progressBadgeCount",
1684
+ "pathNew": "_367UGW_pathNew",
1685
+ "optionsCard": "_367UGW_optionsCard"
1686
1686
  };
1687
1687
  //#endregion
1688
1688
  //#region src/client/common/ui.tsx
@@ -3335,383 +3335,70 @@ function SnapshotsPanel({ api, t }) {
3335
3335
  });
3336
3336
  }
3337
3337
  //#endregion
3338
- //#region src/client/ConfigManagerSection.tsx
3339
- /**
3340
- * Config Manager 设置页(settings.section 入口的主页面容器)。
3341
- *
3342
- * 业务面(api)由注册时的 inject face 注入;t 由 locale seat 注入。
3343
- * 关闭按钮由 settings shell 自带,本页不再渲染。内部两个主视图:
3344
- * Export(Quick/Custom)与 Import(九步向导)。
3345
- *
3346
- * m2:主视图 tab(view)与全部子视图状态统一由模块级 runStore 持有
3347
- * (sessionStorage 持久化 + 切 tab/关面板不重建控制器实例);挂载时
3348
- * 经 GET /runs + 轮询 /progress 恢复进行中的 run(刷新/重开面板后)。
3349
- */
3338
+ //#region src/client/sync/sync-api.ts
3339
+ /** 同步端点常量(与 Host 半 src/index.ts API 常量保持一致) */
3340
+ const SYNC_API = {
3341
+ base: "/api/dsh-config-manager/sync",
3342
+ status: "/api/dsh-config-manager/sync/status",
3343
+ push: "/api/dsh-config-manager/sync/push",
3344
+ pull: "/api/dsh-config-manager/sync/pull"
3345
+ };
3346
+ /** DSH credentials 中的同步 token 引用名(Host 半同值;仅供提示文案使用,值由 Host 读写) */
3347
+ const SYNC_CREDENTIAL_REF = "DSH_CONFIG_MANAGER_SYNC_TOKEN";
3348
+ /** 同步请求超时(ms):与 Host 半 ROUTE_TIMEOUT_MS 对齐(git 网络操作可能较慢) */
3349
+ const SYNC_TIMEOUT_MS = 300 * 1e3;
3350
+ /** 解析 JSON 响应;非 2xx 时抛出带路由 error 消息的 ConfigManagerApiError(与 api.ts 同款) */
3351
+ async function readJson(response) {
3352
+ const notMountedMessage = "config-manager 服务未挂载(插件未加载):请确认 profile 中已安装 dsh-config-manager 并重启 DSH";
3353
+ let body;
3354
+ try {
3355
+ body = await response.json();
3356
+ } catch {
3357
+ if (response.status === 404) throw new ConfigManagerApiError(notMountedMessage);
3358
+ throw new ConfigManagerApiError(`HTTP ${response.status}: invalid JSON response`);
3359
+ }
3360
+ if (!response.ok) throw new ConfigManagerApiError(typeof body === "object" && body !== null && typeof body.error === "string" ? body.error : response.status === 404 ? notMountedMessage : `HTTP ${response.status}`);
3361
+ return body;
3362
+ }
3363
+ /** POST JSON 请求(带超时:宿主卡死时 UI 拿到明确错误而不是永远转圈) */
3364
+ async function postJson(path, body) {
3365
+ const controller = new AbortController();
3366
+ const timer = setTimeout(() => controller.abort(), SYNC_TIMEOUT_MS);
3367
+ try {
3368
+ return await readJson(await fetch(path, {
3369
+ method: "POST",
3370
+ headers: { "content-type": "application/json" },
3371
+ body: JSON.stringify(body),
3372
+ signal: controller.signal
3373
+ }));
3374
+ } catch (err) {
3375
+ if (controller.signal.aborted) throw new ConfigManagerApiError(`同步请求超时(${Math.round(SYNC_TIMEOUT_MS / 6e4)} 分钟):请检查网络与仓库可达性后重试`);
3376
+ throw err;
3377
+ } finally {
3378
+ clearTimeout(timer);
3379
+ }
3380
+ }
3381
+ /** 远程同步浏览器半数据入口(备份与迁移页第 4 个 tab 的注入业务面) */
3382
+ var SyncApi = class {
3383
+ /** 读取同步状态(配置 / 凭据 / 上次同步时间 / 分区数) */
3384
+ async status() {
3385
+ return readJson(await fetch(SYNC_API.status));
3386
+ }
3387
+ /** 推送:导出 portable 分区 → 提交到私有 Git 仓库 → 更新 sync-state */
3388
+ async push(payload) {
3389
+ return postJson(SYNC_API.push, payload);
3390
+ }
3391
+ /** 拉取差异预览:拉取远端最新快照 → 只读分析(绝不执行导入) */
3392
+ async pull(payload) {
3393
+ return postJson(SYNC_API.pull, payload);
3394
+ }
3395
+ };
3396
+ //#endregion
3397
+ //#region src/client/sync/sync-view.ts
3350
3398
  /**
3351
- * 设置页容器:Export / Import / Snapshots 三视图切换。
3352
- * Export/Import 状态在模块级 store(切 tab 不丢失);快照恢复面板为低频显式
3353
- * 操作,其状态组件内自持(local state,不进 sessionStorage)。
3354
- */
3355
- function ConfigManagerSection({ api, t }) {
3356
- const view = (0, react.useSyncExternalStore)(runStore.subscribe, runStore.getSnapshot).view;
3357
- const [snapshotsOpen, setSnapshotsOpen] = (0, react.useState)(false);
3358
- (0, react.useEffect)(() => {
3359
- runStore.resume(api);
3360
- return () => {
3361
- runStore.stopResume();
3362
- };
3363
- }, [api]);
3364
- const setView = (next) => {
3365
- setSnapshotsOpen(false);
3366
- runStore.patch({ view: next });
3367
- };
3368
- const activeTab = snapshotsOpen ? "snapshots" : view;
3369
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3370
- className: config_manager_module_css_default.section,
3371
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3372
- className: config_manager_module_css_default.sectionHeader,
3373
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3374
- className: config_manager_module_css_default.viewTabs,
3375
- role: "tablist",
3376
- children: [
3377
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3378
- type: "button",
3379
- role: "tab",
3380
- "aria-selected": activeTab === "export",
3381
- "data-active": activeTab === "export" ? "" : void 0,
3382
- className: config_manager_module_css_default.viewTab,
3383
- onClick: () => {
3384
- setView("export");
3385
- },
3386
- children: t("view.export")
3387
- }),
3388
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3389
- type: "button",
3390
- role: "tab",
3391
- "aria-selected": activeTab === "import",
3392
- "data-active": activeTab === "import" ? "" : void 0,
3393
- className: config_manager_module_css_default.viewTab,
3394
- onClick: () => {
3395
- setView("import");
3396
- },
3397
- children: t("view.import")
3398
- }),
3399
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3400
- type: "button",
3401
- role: "tab",
3402
- "aria-selected": activeTab === "snapshots",
3403
- "data-active": snapshotsOpen ? "" : void 0,
3404
- className: config_manager_module_css_default.viewTab,
3405
- onClick: () => {
3406
- setSnapshotsOpen(true);
3407
- },
3408
- children: t("view.snapshots")
3409
- })
3410
- ]
3411
- })
3412
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3413
- className: config_manager_module_css_default.sectionBody,
3414
- children: snapshotsOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SnapshotsPanel, {
3415
- api,
3416
- t
3417
- }) : view === "export" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExportView, {
3418
- api,
3419
- t
3420
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImportWizardView, {
3421
- api,
3422
- t
3423
- })
3424
- })]
3425
- });
3426
- }
3427
- //#endregion
3428
- //#region src/client/locales.ts
3429
- /**
3430
- * Config Manager 表面文案:zh 为源语言,en 镜像每个键。
3431
- * 字典 namespace 由 client/index.ts 注册(declare module 合并进 LocaleNamespaceMap)。
3432
- * 键集合通过 `ConfigManagerKey` 类型在注册处做编译期校验(缺键/多键即编译错误)。
3433
- */
3434
- const zh$1 = {
3435
- "section.label": "备份与迁移",
3436
- "section.description": "导出 / 导入 / 迁移 DSH 配置(备份不含密钥,加密备份可选)",
3437
- "view.export": "导出备份",
3438
- "view.import": "导入恢复",
3439
- "view.snapshots": "快照恢复",
3440
- "common.close": "关闭",
3441
- "common.cancel": "取消",
3442
- "common.back": "上一步",
3443
- "common.next": "下一步",
3444
- "common.done": "完成",
3445
- "common.retry": "重试",
3446
- "common.confirm": "确认",
3447
- "common.loading": "加载中…",
3448
- "common.unknownError": "未知错误",
3449
- "status.ready": "Config Manager 已就绪(插件 {version} · DSH {dsh} · {platform})",
3450
- "status.unavailable": "Config Manager 服务未就绪,请确认插件已正确挂载。",
3451
- "export.mode.quick": "快速导出",
3452
- "export.mode.quickHint": "一键导出推荐分区(可迁移、不含设备专属数据),适合常规备份。",
3453
- "export.mode.custom": "自定义导出",
3454
- "export.mode.customHint": "按分组逐项勾选要导出的分区。",
3455
- "export.includeSecrets": "包含真实密钥(加密备份)",
3456
- "export.includeSecretsHint": "勾选后导出将包含密钥,并以密码加密(AES-256-GCM)。密码仅用于本次加密,不会写入备份或任何配置文件。",
3457
- "export.password": "备份加密密码",
3458
- "export.passwordConfirm": "确认密码",
3459
- "export.passwordMismatch": "两次输入的密码不一致",
3460
- "export.passwordRequired": "包含密钥时必须设置加密密码",
3461
- "export.run": "开始导出",
3462
- "export.running": "正在导出…",
3463
- "export.download": "下载备份文件",
3464
- "export.selectionWarnings": "以下分区为设备相关数据,跨设备导入时可能不适用:",
3465
- "export.group.unknown": "其他",
3466
- "export.included": "已包含",
3467
- "export.excluded": "未包含",
3468
- "import.select.title": "选择备份文件",
3469
- "import.select.hint": "选择本机导出的 dsh-config-*.zip 备份文件开始导入。分析阶段只读,不会修改任何配置。",
3470
- "import.select.browse": "选择 ZIP 文件",
3471
- "import.select.file": "已选择:{name}",
3472
- "import.analyzing": "正在分析备份…",
3473
- "import.compatibility.title": "兼容性",
3474
- "import.compatibility.score": "兼容性评分:{score}",
3475
- "import.compatibility.ok": "备份可导入",
3476
- "import.compatibility.fail": "备份分析失败:{reason}",
3477
- "import.preview.title": "导入预览",
3478
- "import.preview.willChange": "将变更 {count} 项",
3479
- "import.preview.unchanged": "已一致跳过 {count} 项",
3480
- "import.preview.settings": "设置更新 {count}",
3481
- "import.preview.plugins": "插件待安装 {count}",
3482
- "import.preview.mcp": "MCP 新增 {count}",
3483
- "import.preview.prompts": "提示词 {count}",
3484
- "import.preview.paths": "路径映射 {count}",
3485
- "import.preview.secrets": "密钥待补录 {count}",
3486
- "import.preview.conflicts": "冲突 {count}",
3487
- "import.preview.restart": "导入后需重启 DSH 生效",
3488
- "import.confirm.execute": "确认导入",
3489
- "import.confirm.warning": "导入将修改当前 DSH 配置;执行前会自动创建安全快照,失败时整体回滚。",
3490
- "import.rollbackOnError": "失败时整体回滚(推荐)",
3491
- "import.importing": "正在导入…(插件安装较多时可能需要几分钟,请勿关闭页面)",
3492
- "import.secrets.title": "补录密钥",
3493
- "import.secrets.hint": "以下凭据未随备份导出(安全设计)。如需在目标 DSH 使用,请手动补录;留空将跳过。",
3494
- "import.secrets.optional": "(可选)",
3495
- "import.secrets.required": "(必需)",
3496
- "import.conflicts.title": "解决冲突",
3497
- "import.conflicts.hint": "以下配置项在目标 DSH 已存在且与备份不同,请逐项决策。",
3498
- "import.conflicts.keepCurrent": "保留当前",
3499
- "import.conflicts.useImported": "使用备份",
3500
- "import.conflicts.review": "稍后决定",
3501
- "import.conflicts.unresolved": "还有 {count} 项未决策",
3502
- "import.paths.title": "路径映射",
3503
- "import.paths.hint": "备份中的绝对路径在目标机器可能不存在,请为每条路径指定新位置(留空将跳过该路径)。",
3504
- "import.paths.old": "原路径",
3505
- "import.paths.new": "新路径",
3506
- "import.paths.unresolved": "还有 {count} 条路径未映射",
3507
- "snapshots.title": "快照恢复",
3508
- "snapshots.hint": "恢复到导入前状态:整文件还原 settings/patch、卸载导入期间新增插件、补偿被修改文件。执行前会把当前文件备份到快照的 pre-restore 目录。",
3509
- "snapshots.empty": "暂无快照。执行导入时会自动创建安全快照。",
3510
- "snapshots.loading": "加载快照中…",
3511
- "snapshots.selectHint": "选择快照以预览恢复计划(dry-run,零写入):",
3512
- "snapshots.createdAt": "创建时间",
3513
- "snapshots.sourceZip": "来源备份",
3514
- "snapshots.status": "状态",
3515
- "snapshots.entries": "条目",
3516
- "snapshots.plugins": "插件",
3517
- "snapshots.status.pending": "进行中",
3518
- "snapshots.status.done": "已完成",
3519
- "snapshots.status.rolled-back": "已回滚",
3520
- "snapshots.status.unknown": "未知",
3521
- "snapshots.planTitle": "恢复计划预览",
3522
- "snapshots.noActions": "该快照无可用恢复动作(或全部跳过)。",
3523
- "snapshots.execute": "执行恢复",
3524
- "snapshots.executing": "恢复执行中…",
3525
- "snapshots.confirmRestore": "确认执行恢复?当前文件会先备份到快照的 pre-restore 目录,插件通过官方 dsh plugin remove 卸载。",
3526
- "snapshots.reportTitle": "恢复报告",
3527
- "snapshots.restored": "已还原",
3528
- "snapshots.removedPlugins": "已卸载插件",
3529
- "snapshots.manualHints": "需人工处理",
3530
- "snapshots.failed": "失败",
3531
- "snapshots.skipped": "跳过",
3532
- "report.export.title": "导出完成",
3533
- "report.import.title": "导入结果",
3534
- "report.rollback.title": "回滚报告",
3535
- "report.action.fixIssues": "查看失败项",
3536
- "report.action.viewDetails": "查看详情",
3537
- "report.action.done": "完成",
3538
- "report.needsRestart": "插件 / MCP 变更将在重启 DSH 后生效。",
3539
- "error.title": "操作失败",
3540
- "error.hint": "错误消息已自动脱敏,不会泄露任何密钥信息。"
3541
- };
3542
- const en$1 = {
3543
- "section.label": "Backup & Migration",
3544
- "section.description": "Export / import / migrate DSH configuration (backups exclude secrets; encrypted backups optional)",
3545
- "view.export": "Export",
3546
- "view.import": "Import",
3547
- "view.snapshots": "Snapshots & Restore",
3548
- "common.close": "Close",
3549
- "common.cancel": "Cancel",
3550
- "common.back": "Back",
3551
- "common.next": "Next",
3552
- "common.done": "Done",
3553
- "common.retry": "Retry",
3554
- "common.confirm": "Confirm",
3555
- "common.loading": "Loading…",
3556
- "common.unknownError": "Unknown error",
3557
- "status.ready": "Config Manager ready (plugin {version} · DSH {dsh} · {platform})",
3558
- "status.unavailable": "Config Manager service unavailable — verify the plugin is mounted.",
3559
- "export.mode.quick": "Quick Export",
3560
- "export.mode.quickHint": "One-click export of recommended sections (portable, no device-specific data) for routine backups.",
3561
- "export.mode.custom": "Custom Export",
3562
- "export.mode.customHint": "Select sections per group.",
3563
- "export.includeSecrets": "Include real secrets (encrypted backup)",
3564
- "export.includeSecretsHint": "When enabled the backup contains secrets, encrypted with a password (AES-256-GCM). The password is used once and never written into the backup or any config file.",
3565
- "export.password": "Backup encryption password",
3566
- "export.passwordConfirm": "Confirm password",
3567
- "export.passwordMismatch": "Passwords do not match",
3568
- "export.passwordRequired": "An encryption password is required when including secrets",
3569
- "export.run": "Start Export",
3570
- "export.running": "Exporting…",
3571
- "export.download": "Download backup file",
3572
- "export.selectionWarnings": "Device-specific sections may not apply on another machine:",
3573
- "export.group.unknown": "Other",
3574
- "export.included": "Included",
3575
- "export.excluded": "Excluded",
3576
- "import.select.title": "Select Backup File",
3577
- "import.select.hint": "Pick a dsh-config-*.zip backup exported from DSH to begin. Analysis is read-only and changes nothing.",
3578
- "import.select.browse": "Choose ZIP file",
3579
- "import.select.file": "Selected: {name}",
3580
- "import.analyzing": "Analyzing backup…",
3581
- "import.compatibility.title": "Compatibility",
3582
- "import.compatibility.score": "Compatibility score: {score}",
3583
- "import.compatibility.ok": "The backup can be imported",
3584
- "import.compatibility.fail": "Backup analysis failed: {reason}",
3585
- "import.preview.title": "Import Preview",
3586
- "import.preview.willChange": "{count} item(s) will change",
3587
- "import.preview.unchanged": "{count} item(s) already identical (skipped)",
3588
- "import.preview.settings": "{count} settings update(s)",
3589
- "import.preview.plugins": "{count} plugin(s) to install",
3590
- "import.preview.mcp": "{count} MCP server(s) added",
3591
- "import.preview.prompts": "{count} prompt(s)",
3592
- "import.preview.paths": "{count} path mapping(s) needed",
3593
- "import.preview.secrets": "{count} secret(s) to re-enter",
3594
- "import.preview.conflicts": "{count} conflict(s)",
3595
- "import.preview.restart": "A DSH restart is required after import",
3596
- "import.confirm.execute": "Confirm Import",
3597
- "import.confirm.warning": "Import modifies the current DSH configuration; a safety snapshot is created first and everything rolls back on failure.",
3598
- "import.rollbackOnError": "Roll back everything on failure (recommended)",
3599
- "import.importing": "Importing… (installing many plugins may take a few minutes; keep this page open)",
3600
- "import.secrets.title": "Re-enter Secrets",
3601
- "import.secrets.hint": "These credentials were not exported with the backup (security by design). Enter them manually to use them here; leave blank to skip.",
3602
- "import.secrets.optional": "(optional)",
3603
- "import.secrets.required": "(required)",
3604
- "import.conflicts.title": "Resolve Conflicts",
3605
- "import.conflicts.hint": "These items already exist on this machine and differ from the backup. Decide per item.",
3606
- "import.conflicts.keepCurrent": "Keep current",
3607
- "import.conflicts.useImported": "Use backup",
3608
- "import.conflicts.review": "Decide later",
3609
- "import.conflicts.unresolved": "{count} item(s) unresolved",
3610
- "import.paths.title": "Path Mapping",
3611
- "import.paths.hint": "Absolute paths in the backup may not exist on this machine. Map each path to a new location (leave blank to skip).",
3612
- "import.paths.old": "Original path",
3613
- "import.paths.new": "New path",
3614
- "import.paths.unresolved": "{count} path(s) unmapped",
3615
- "snapshots.title": "Snapshot Restore",
3616
- "snapshots.hint": "Restore to the pre-import state: whole-file restore of settings/patch, uninstall plugins added during import, compensate modified files. Current files are backed up to the snapshot's pre-restore folder before any change.",
3617
- "snapshots.empty": "No snapshots yet. A safety snapshot is created automatically on import.",
3618
- "snapshots.loading": "Loading snapshots…",
3619
- "snapshots.selectHint": "Select a snapshot to preview its restore plan (dry-run, zero writes):",
3620
- "snapshots.createdAt": "Created",
3621
- "snapshots.sourceZip": "Source",
3622
- "snapshots.status": "Status",
3623
- "snapshots.entries": "Entries",
3624
- "snapshots.plugins": "Plugins",
3625
- "snapshots.status.pending": "Pending",
3626
- "snapshots.status.done": "Done",
3627
- "snapshots.status.rolled-back": "Rolled back",
3628
- "snapshots.status.unknown": "Unknown",
3629
- "snapshots.planTitle": "Restore Plan Preview",
3630
- "snapshots.noActions": "No restorable actions for this snapshot (or all skipped).",
3631
- "snapshots.execute": "Restore now",
3632
- "snapshots.executing": "Restoring…",
3633
- "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.",
3634
- "snapshots.reportTitle": "Restore Report",
3635
- "snapshots.restored": "Restored",
3636
- "snapshots.removedPlugins": "Removed plugins",
3637
- "snapshots.manualHints": "Manual action needed",
3638
- "snapshots.failed": "Failed",
3639
- "snapshots.skipped": "Skipped",
3640
- "report.export.title": "Export Complete",
3641
- "report.import.title": "Import Result",
3642
- "report.rollback.title": "Rollback Report",
3643
- "report.action.fixIssues": "View failures",
3644
- "report.action.viewDetails": "View details",
3645
- "report.action.done": "Done",
3646
- "report.needsRestart": "Plugin / MCP changes take effect after restarting DSH.",
3647
- "error.title": "Operation failed",
3648
- "error.hint": "Error messages are auto-redacted and never leak secrets."
3649
- };
3650
- //#endregion
3651
- //#region src/client/sync/sync-api.ts
3652
- /** 同步端点常量(与 Host 半 src/index.ts API 常量保持一致) */
3653
- const SYNC_API = {
3654
- base: "/api/dsh-config-manager/sync",
3655
- status: "/api/dsh-config-manager/sync/status",
3656
- push: "/api/dsh-config-manager/sync/push",
3657
- pull: "/api/dsh-config-manager/sync/pull"
3658
- };
3659
- /** DSH credentials 中的同步 token 引用名(Host 半同值;仅供提示文案使用,值由 Host 读写) */
3660
- const SYNC_CREDENTIAL_REF = "DSH_CONFIG_MANAGER_SYNC_TOKEN";
3661
- /** 同步请求超时(ms):与 Host 半 ROUTE_TIMEOUT_MS 对齐(git 网络操作可能较慢) */
3662
- const SYNC_TIMEOUT_MS = 300 * 1e3;
3663
- /** 解析 JSON 响应;非 2xx 时抛出带路由 error 消息的 ConfigManagerApiError(与 api.ts 同款) */
3664
- async function readJson(response) {
3665
- const notMountedMessage = "config-manager 服务未挂载(插件未加载):请确认 profile 中已安装 dsh-config-manager 并重启 DSH";
3666
- let body;
3667
- try {
3668
- body = await response.json();
3669
- } catch {
3670
- if (response.status === 404) throw new ConfigManagerApiError(notMountedMessage);
3671
- throw new ConfigManagerApiError(`HTTP ${response.status}: invalid JSON response`);
3672
- }
3673
- if (!response.ok) throw new ConfigManagerApiError(typeof body === "object" && body !== null && typeof body.error === "string" ? body.error : response.status === 404 ? notMountedMessage : `HTTP ${response.status}`);
3674
- return body;
3675
- }
3676
- /** POST JSON 请求(带超时:宿主卡死时 UI 拿到明确错误而不是永远转圈) */
3677
- async function postJson(path, body) {
3678
- const controller = new AbortController();
3679
- const timer = setTimeout(() => controller.abort(), SYNC_TIMEOUT_MS);
3680
- try {
3681
- return await readJson(await fetch(path, {
3682
- method: "POST",
3683
- headers: { "content-type": "application/json" },
3684
- body: JSON.stringify(body),
3685
- signal: controller.signal
3686
- }));
3687
- } catch (err) {
3688
- if (controller.signal.aborted) throw new ConfigManagerApiError(`同步请求超时(${Math.round(SYNC_TIMEOUT_MS / 6e4)} 分钟):请检查网络与仓库可达性后重试`);
3689
- throw err;
3690
- } finally {
3691
- clearTimeout(timer);
3692
- }
3693
- }
3694
- /** 远程同步浏览器半数据入口(settings.section: config-manager-sync 的注入业务面) */
3695
- var SyncApi = class {
3696
- /** 读取同步状态(配置 / 凭据 / 上次同步时间 / 分区数) */
3697
- async status() {
3698
- return readJson(await fetch(SYNC_API.status));
3699
- }
3700
- /** 推送:导出 portable 分区 → 提交到私有 Git 仓库 → 更新 sync-state */
3701
- async push(payload) {
3702
- return postJson(SYNC_API.push, payload);
3703
- }
3704
- /** 拉取差异预览:拉取远端最新快照 → 只读分析(绝不执行导入) */
3705
- async pull(payload) {
3706
- return postJson(SYNC_API.pull, payload);
3707
- }
3708
- };
3709
- //#endregion
3710
- //#region src/client/sync/sync-view.ts
3711
- /**
3712
- * 私有仓库强制提示文案(Settings 区块常驻警示横幅)。
3713
- * 安全约束:同步内容为可移植配置,public 仓库会公开配置 → 必须私有;
3714
- * token 仅用于认证,绝不写入同步文件/提交内容/日志。
3399
+ * 私有仓库强制提示文案(Settings 区块常驻警示横幅)。
3400
+ * 安全约束:同步内容为可移植配置,public 仓库会公开配置 → 必须私有;
3401
+ * token 仅用于认证,绝不写入同步文件/提交内容/日志。
3715
3402
  */
3716
3403
  const PRIVATE_REPO_HINT = "安全要求:同步仓库必须为私有仓库(public 仓库会公开你的配置内容)。认证 token 仅用于仓库访问,绝不写入同步文件、提交内容或日志。";
3717
3404
  /** 需要人工决策的 PlanItem 类型(与 SyncEngine.pull 的 needsReview 判定一致) */
@@ -3851,9 +3538,10 @@ function pullReportView(report) {
3851
3538
  //#endregion
3852
3539
  //#region src/client/sync/SyncSettingsView.tsx
3853
3540
  /**
3854
- * 远程同步设置区块(settings.section: config-manager-sync)。
3541
+ * 远程同步面板(备份与迁移页的第 4 个 tab 内容)。
3855
3542
  *
3856
- * 独立 settings 页(不触碰并行会话的 ConfigManagerSection.tsx 三 tab 容器):
3543
+ * 独立设置页壳(sectionHeader/close/自身 tab)已移除 —— tab 容器由
3544
+ * ConfigManagerSection 统一渲染,本组件只输出内容体:
3857
3545
  * - 仓库配置表单:repoUrl(必填)+ 认证 token(可选,写入 DSH credentials 的提示)
3858
3546
  * + gitBin(可选);
3859
3547
  * - 私有仓库强制提示横幅(常驻);
@@ -3878,7 +3566,7 @@ const initial = {
3878
3566
  pullReport: null,
3879
3567
  error: null
3880
3568
  };
3881
- function SyncSettingsView({ close, api, t }) {
3569
+ function SyncSettingsView({ api, t }) {
3882
3570
  const [state, setState] = (0, react.useState)(initial);
3883
3571
  const patch = (p) => setState((s) => ({
3884
3572
  ...s,
@@ -3961,253 +3649,563 @@ function SyncSettingsView({ close, api, t }) {
3961
3649
  const pushView = pushReportView(state.pushReport);
3962
3650
  const pullView = pullReportView(state.pullReport);
3963
3651
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3964
- className: config_manager_module_css_default.section,
3965
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3966
- className: config_manager_module_css_default.sectionHeader,
3967
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3968
- className: config_manager_module_css_default.viewTabs,
3969
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3970
- role: "tab",
3971
- "aria-selected": "true",
3972
- "data-active": true,
3973
- className: config_manager_module_css_default.viewTab,
3974
- children: t("section.label")
3975
- })
3976
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3977
- type: "button",
3978
- className: config_manager_module_css_default.iconButton,
3979
- title: t("common.close"),
3980
- "aria-label": t("common.close"),
3981
- onClick: close,
3982
- children: "×"
3983
- })]
3984
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3985
- className: config_manager_module_css_default.sectionBody,
3986
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3987
- className: config_manager_module_css_default.viewBody,
3988
- children: [
3989
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionTitle, {
3990
- title: t("section.label"),
3991
- subtitle: t("section.description")
3992
- }),
3993
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
3994
- kind: "warn",
3995
- children: PRIVATE_REPO_HINT
3996
- }),
3997
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
3652
+ className: config_manager_module_css_default.viewBody,
3653
+ children: [
3654
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionTitle, {
3655
+ title: t("section.label"),
3656
+ subtitle: t("section.description")
3657
+ }),
3658
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
3659
+ kind: "warn",
3660
+ children: PRIVATE_REPO_HINT
3661
+ }),
3662
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
3663
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3664
+ className: config_manager_module_css_default.groupLabel,
3665
+ children: t("config.title")
3666
+ }),
3667
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3668
+ className: config_manager_module_css_default.field,
3669
+ children: [
3998
3670
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3999
- className: config_manager_module_css_default.groupLabel,
4000
- children: t("config.title")
3671
+ className: config_manager_module_css_default.fieldLabel,
3672
+ children: t("config.repoUrl")
4001
3673
  }),
4002
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4003
- className: config_manager_module_css_default.field,
4004
- children: [
4005
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4006
- className: config_manager_module_css_default.fieldLabel,
4007
- children: t("config.repoUrl")
4008
- }),
4009
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4010
- type: "text",
4011
- className: config_manager_module_css_default.input,
4012
- value: state.repoUrl,
4013
- placeholder: "https://github.com/user/private-repo.git",
4014
- disabled: state.busy !== null,
4015
- onChange: (e) => {
4016
- patch({ repoUrl: e.target.value });
4017
- }
4018
- }),
4019
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4020
- className: config_manager_module_css_default.hint,
4021
- children: t("config.repoUrlHint")
4022
- })
4023
- ]
3674
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3675
+ type: "text",
3676
+ className: config_manager_module_css_default.input,
3677
+ value: state.repoUrl,
3678
+ placeholder: "https://github.com/user/private-repo.git",
3679
+ disabled: state.busy !== null,
3680
+ onChange: (e) => {
3681
+ patch({ repoUrl: e.target.value });
3682
+ }
4024
3683
  }),
4025
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4026
- className: config_manager_module_css_default.field,
3684
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3685
+ className: config_manager_module_css_default.hint,
3686
+ children: t("config.repoUrlHint")
3687
+ })
3688
+ ]
3689
+ }),
3690
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3691
+ className: config_manager_module_css_default.field,
3692
+ children: [
3693
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3694
+ className: config_manager_module_css_default.fieldLabel,
4027
3695
  children: [
4028
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4029
- className: config_manager_module_css_default.fieldLabel,
4030
- children: [
4031
- t("config.token"),
4032
- " ",
4033
- state.statusInfo?.credentialConfigured === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
4034
- kind: "ok",
4035
- children: t("config.tokenSaved")
4036
- })
4037
- ]
4038
- }),
4039
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4040
- type: "password",
4041
- className: config_manager_module_css_default.input,
4042
- value: state.token,
4043
- autoComplete: "off",
4044
- placeholder: "ghp_…(可选)",
4045
- disabled: state.busy !== null,
4046
- onChange: (e) => {
4047
- patch({ token: e.target.value });
4048
- }
4049
- }),
4050
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4051
- className: config_manager_module_css_default.hint,
4052
- children: t("config.tokenHint", { ref: SYNC_CREDENTIAL_REF })
3696
+ t("config.token"),
3697
+ " ",
3698
+ state.statusInfo?.credentialConfigured === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
3699
+ kind: "ok",
3700
+ children: t("config.tokenSaved")
4053
3701
  })
4054
3702
  ]
4055
3703
  }),
4056
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4057
- className: config_manager_module_css_default.field,
4058
- children: [
4059
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4060
- className: config_manager_module_css_default.fieldLabel,
4061
- children: t("config.gitBin")
4062
- }),
4063
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4064
- type: "text",
4065
- className: config_manager_module_css_default.input,
4066
- value: state.gitBin,
4067
- placeholder: "git",
4068
- disabled: state.busy !== null,
4069
- onChange: (e) => {
4070
- patch({ gitBin: e.target.value });
4071
- }
4072
- }),
4073
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4074
- className: config_manager_module_css_default.hint,
4075
- children: t("config.gitBinHint")
4076
- })
4077
- ]
4078
- })
4079
- ] }),
4080
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4081
- className: config_manager_module_css_default.groupLabel,
4082
- children: t("status.title")
4083
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4084
- className: config_manager_module_css_default.statRow,
4085
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
4086
- kind: status.kind === "ready" ? "ok" : status.kind === "error" ? "error" : "warn",
4087
- children: status.text
3704
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3705
+ type: "password",
3706
+ className: config_manager_module_css_default.input,
3707
+ value: state.token,
3708
+ autoComplete: "off",
3709
+ placeholder: "ghp_…(可选)",
3710
+ disabled: state.busy !== null,
3711
+ onChange: (e) => {
3712
+ patch({ token: e.target.value });
3713
+ }
3714
+ }),
3715
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3716
+ className: config_manager_module_css_default.hint,
3717
+ children: t("config.tokenHint", { ref: SYNC_CREDENTIAL_REF })
4088
3718
  })
4089
- })] }),
4090
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4091
- className: config_manager_module_css_default.actionRow,
4092
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
4093
- variant: "primary",
4094
- disabled: !buttons.canPush,
4095
- onClick: () => {
4096
- runPush();
4097
- },
4098
- children: state.busy === "push" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: buttons.pushLabel }) : buttons.pushLabel
4099
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
4100
- disabled: !buttons.canPull,
4101
- onClick: () => {
4102
- runPull();
4103
- },
4104
- children: state.busy === "pull" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: buttons.pullLabel }) : buttons.pullLabel
4105
- })]
4106
- }),
4107
- state.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBanner, { error: state.error }),
4108
- pushView !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
3719
+ ]
3720
+ }),
3721
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3722
+ className: config_manager_module_css_default.field,
3723
+ children: [
4109
3724
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4110
- className: config_manager_module_css_default.groupLabel,
4111
- children: t("push.title")
4112
- }),
4113
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
4114
- kind: pushView.kind === "ok" ? "ok" : "error",
4115
- children: pushView.headline
4116
- }),
4117
- pushView.sections.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4118
- className: config_manager_module_css_default.fieldLabel,
4119
- children: t("sections.title")
4120
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4121
- className: config_manager_module_css_default.statRow,
4122
- children: pushView.sections.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
4123
- kind: "info",
4124
- children: s
4125
- }, s))
4126
- })] }),
4127
- pushView.warnings.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4128
3725
  className: config_manager_module_css_default.fieldLabel,
4129
- children: t("warnings.title")
4130
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
4131
- className: config_manager_module_css_default.warnList,
4132
- children: pushView.warnings.map((w, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: w }, i))
4133
- })] })
4134
- ] }),
4135
- pullView !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
4136
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4137
- className: config_manager_module_css_default.groupLabel,
4138
- children: t("pull.title")
3726
+ children: t("config.gitBin")
4139
3727
  }),
4140
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
4141
- kind: pullView.kind === "ok" ? "info" : pullView.kind === "empty" ? "ok" : "error",
4142
- children: pullView.headline
3728
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3729
+ type: "text",
3730
+ className: config_manager_module_css_default.input,
3731
+ value: state.gitBin,
3732
+ placeholder: "git",
3733
+ disabled: state.busy !== null,
3734
+ onChange: (e) => {
3735
+ patch({ gitBin: e.target.value });
3736
+ }
4143
3737
  }),
4144
- pullView.summary !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4145
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4146
- className: config_manager_module_css_default.statRow,
3738
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3739
+ className: config_manager_module_css_default.hint,
3740
+ children: t("config.gitBinHint")
3741
+ })
3742
+ ]
3743
+ })
3744
+ ] }),
3745
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3746
+ className: config_manager_module_css_default.groupLabel,
3747
+ children: t("status.title")
3748
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3749
+ className: config_manager_module_css_default.statRow,
3750
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
3751
+ kind: status.kind === "ready" ? "ok" : status.kind === "error" ? "error" : "warn",
3752
+ children: status.text
3753
+ })
3754
+ })] }),
3755
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3756
+ className: config_manager_module_css_default.actionRow,
3757
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
3758
+ variant: "primary",
3759
+ disabled: !buttons.canPush,
3760
+ onClick: () => {
3761
+ runPush();
3762
+ },
3763
+ children: state.busy === "push" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: buttons.pushLabel }) : buttons.pushLabel
3764
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
3765
+ disabled: !buttons.canPull,
3766
+ onClick: () => {
3767
+ runPull();
3768
+ },
3769
+ children: state.busy === "pull" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: buttons.pullLabel }) : buttons.pullLabel
3770
+ })]
3771
+ }),
3772
+ state.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBanner, { error: state.error }),
3773
+ pushView !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
3774
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3775
+ className: config_manager_module_css_default.groupLabel,
3776
+ children: t("push.title")
3777
+ }),
3778
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
3779
+ kind: pushView.kind === "ok" ? "ok" : "error",
3780
+ children: pushView.headline
3781
+ }),
3782
+ pushView.sections.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3783
+ className: config_manager_module_css_default.fieldLabel,
3784
+ children: t("sections.title")
3785
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3786
+ className: config_manager_module_css_default.statRow,
3787
+ children: pushView.sections.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
3788
+ kind: "info",
3789
+ children: s
3790
+ }, s))
3791
+ })] }),
3792
+ pushView.warnings.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3793
+ className: config_manager_module_css_default.fieldLabel,
3794
+ children: t("warnings.title")
3795
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
3796
+ className: config_manager_module_css_default.warnList,
3797
+ children: pushView.warnings.map((w, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: w }, i))
3798
+ })] })
3799
+ ] }),
3800
+ pullView !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
3801
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3802
+ className: config_manager_module_css_default.groupLabel,
3803
+ children: t("pull.title")
3804
+ }),
3805
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
3806
+ kind: pullView.kind === "ok" ? "info" : pullView.kind === "empty" ? "ok" : "error",
3807
+ children: pullView.headline
3808
+ }),
3809
+ pullView.summary !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3810
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3811
+ className: config_manager_module_css_default.statRow,
3812
+ children: [
3813
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
3814
+ kind: "info",
3815
+ children: t("change.total", { total: pullView.summary.total })
3816
+ }),
3817
+ pullView.summary.error > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Badge, {
3818
+ kind: "error",
4147
3819
  children: [
4148
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
4149
- kind: "info",
4150
- children: t("change.total", { total: pullView.summary.total })
4151
- }),
4152
- pullView.summary.error > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Badge, {
4153
- kind: "error",
4154
- children: [
4155
- severityLabel("error"),
4156
- " × ",
4157
- pullView.summary.error
4158
- ]
4159
- }),
4160
- pullView.summary.warning > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Badge, {
4161
- kind: "warn",
4162
- children: [
4163
- severityLabel("warning"),
4164
- " × ",
4165
- pullView.summary.warning
4166
- ]
4167
- }),
4168
- pullView.summary.info > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Badge, {
4169
- kind: "info",
4170
- children: [
4171
- severityLabel("info"),
4172
- " × ",
4173
- pullView.summary.info
4174
- ]
4175
- })
3820
+ severityLabel("error"),
3821
+ " × ",
3822
+ pullView.summary.error
4176
3823
  ]
4177
3824
  }),
4178
- pullView.summary.needsReview && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
3825
+ pullView.summary.warning > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Badge, {
4179
3826
  kind: "warn",
4180
- children: t("pull.needsReview")
3827
+ children: [
3828
+ severityLabel("warning"),
3829
+ " × ",
3830
+ pullView.summary.warning
3831
+ ]
4181
3832
  }),
4182
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4183
- className: config_manager_module_css_default.reportList,
4184
- children: pullView.summary.items.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4185
- className: config_manager_module_css_default.statRow,
4186
- children: [
4187
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4188
- className: config_manager_module_css_default.kindTag,
4189
- children: kindLabel(c.kind)
4190
- }),
4191
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
4192
- kind: c.severity === "error" ? "error" : c.severity === "warning" ? "warn" : "info",
4193
- children: severityLabel(c.severity)
4194
- }),
4195
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: c.description })
4196
- ]
4197
- }, c.id))
3833
+ pullView.summary.info > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Badge, {
3834
+ kind: "info",
3835
+ children: [
3836
+ severityLabel("info"),
3837
+ " × ",
3838
+ pullView.summary.info
3839
+ ]
4198
3840
  })
4199
- ] }),
4200
- pullView.previewHint !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
4201
- kind: "info",
4202
- children: pullView.previewHint
4203
- })
4204
- ] })
3841
+ ]
3842
+ }),
3843
+ pullView.summary.needsReview && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
3844
+ kind: "warn",
3845
+ children: t("pull.needsReview")
3846
+ }),
3847
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3848
+ className: config_manager_module_css_default.reportList,
3849
+ children: pullView.summary.items.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3850
+ className: config_manager_module_css_default.statRow,
3851
+ children: [
3852
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3853
+ className: config_manager_module_css_default.kindTag,
3854
+ children: kindLabel(c.kind)
3855
+ }),
3856
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
3857
+ kind: c.severity === "error" ? "error" : c.severity === "warning" ? "warn" : "info",
3858
+ children: severityLabel(c.severity)
3859
+ }),
3860
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: c.description })
3861
+ ]
3862
+ }, c.id))
3863
+ })
3864
+ ] }),
3865
+ pullView.previewHint !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
3866
+ kind: "info",
3867
+ children: pullView.previewHint
3868
+ })
3869
+ ] })
3870
+ ]
3871
+ });
3872
+ }
3873
+ //#endregion
3874
+ //#region src/client/ConfigManagerSection.tsx
3875
+ /**
3876
+ * Config Manager 设置页(settings.section 入口的主页面容器)。
3877
+ *
3878
+ * 业务面(api)由注册时的 inject face 注入;t 由 locale seat 注入。
3879
+ * 关闭按钮由 settings shell 自带,本页不再渲染。内部两个主视图:
3880
+ * Export(Quick/Custom)与 Import(九步向导)。
3881
+ *
3882
+ * m2:主视图 tab(view)与全部子视图状态统一由模块级 runStore 持有
3883
+ * (sessionStorage 持久化 + 切 tab/关面板不重建控制器实例);挂载时
3884
+ * 经 GET /runs + 轮询 /progress 恢复进行中的 run(刷新/重开面板后)。
3885
+ */
3886
+ /**
3887
+ * 设置页容器:Export / Import / Snapshots / Sync 四视图切换。
3888
+ * Export/Import 状态在模块级 store(切 tab 不丢失);快照恢复与远程同步为低频显式
3889
+ * 操作,其状态组件内自持(local state,不进 sessionStorage)。
3890
+ */
3891
+ function ConfigManagerSection({ api, syncApi, syncT, t }) {
3892
+ const view = (0, react.useSyncExternalStore)(runStore.subscribe, runStore.getSnapshot).view;
3893
+ const [snapshotsOpen, setSnapshotsOpen] = (0, react.useState)(false);
3894
+ const [syncOpen, setSyncOpen] = (0, react.useState)(false);
3895
+ (0, react.useEffect)(() => {
3896
+ runStore.resume(api);
3897
+ return () => {
3898
+ runStore.stopResume();
3899
+ };
3900
+ }, [api]);
3901
+ const setView = (next) => {
3902
+ setSnapshotsOpen(false);
3903
+ setSyncOpen(false);
3904
+ runStore.patch({ view: next });
3905
+ };
3906
+ const openSnapshots = () => {
3907
+ setSnapshotsOpen(true);
3908
+ setSyncOpen(false);
3909
+ };
3910
+ const openSync = () => {
3911
+ setSyncOpen(true);
3912
+ setSnapshotsOpen(false);
3913
+ };
3914
+ const activeTab = syncOpen ? "sync" : snapshotsOpen ? "snapshots" : view;
3915
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3916
+ className: config_manager_module_css_default.section,
3917
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3918
+ className: config_manager_module_css_default.sectionHeader,
3919
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3920
+ className: config_manager_module_css_default.viewTabs,
3921
+ role: "tablist",
3922
+ children: [
3923
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3924
+ type: "button",
3925
+ role: "tab",
3926
+ "aria-selected": activeTab === "export",
3927
+ "data-active": activeTab === "export" ? "" : void 0,
3928
+ className: config_manager_module_css_default.viewTab,
3929
+ onClick: () => {
3930
+ setView("export");
3931
+ },
3932
+ children: t("view.export")
3933
+ }),
3934
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3935
+ type: "button",
3936
+ role: "tab",
3937
+ "aria-selected": activeTab === "import",
3938
+ "data-active": activeTab === "import" ? "" : void 0,
3939
+ className: config_manager_module_css_default.viewTab,
3940
+ onClick: () => {
3941
+ setView("import");
3942
+ },
3943
+ children: t("view.import")
3944
+ }),
3945
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3946
+ type: "button",
3947
+ role: "tab",
3948
+ "aria-selected": activeTab === "snapshots",
3949
+ "data-active": snapshotsOpen ? "" : void 0,
3950
+ className: config_manager_module_css_default.viewTab,
3951
+ onClick: openSnapshots,
3952
+ children: t("view.snapshots")
3953
+ }),
3954
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3955
+ type: "button",
3956
+ role: "tab",
3957
+ "aria-selected": activeTab === "sync",
3958
+ "data-active": syncOpen ? "" : void 0,
3959
+ className: config_manager_module_css_default.viewTab,
3960
+ onClick: openSync,
3961
+ children: t("view.sync")
3962
+ })
4205
3963
  ]
4206
3964
  })
3965
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3966
+ className: config_manager_module_css_default.sectionBody,
3967
+ children: syncOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SyncSettingsView, {
3968
+ api: syncApi,
3969
+ t: syncT
3970
+ }) : snapshotsOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SnapshotsPanel, {
3971
+ api,
3972
+ t
3973
+ }) : view === "export" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExportView, {
3974
+ api,
3975
+ t
3976
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImportWizardView, {
3977
+ api,
3978
+ t
3979
+ })
4207
3980
  })]
4208
3981
  });
4209
3982
  }
4210
3983
  //#endregion
3984
+ //#region src/client/locales.ts
3985
+ /**
3986
+ * Config Manager 表面文案:zh 为源语言,en 镜像每个键。
3987
+ * 字典 namespace 由 client/index.ts 注册(declare module 合并进 LocaleNamespaceMap)。
3988
+ * 键集合通过 `ConfigManagerKey` 类型在注册处做编译期校验(缺键/多键即编译错误)。
3989
+ */
3990
+ const zh$1 = {
3991
+ "section.label": "备份与迁移",
3992
+ "section.description": "导出 / 导入 / 迁移 DSH 配置(备份不含密钥,加密备份可选)",
3993
+ "view.export": "导出备份",
3994
+ "view.import": "导入恢复",
3995
+ "view.snapshots": "快照恢复",
3996
+ "view.sync": "远程同步",
3997
+ "common.close": "关闭",
3998
+ "common.cancel": "取消",
3999
+ "common.back": "上一步",
4000
+ "common.next": "下一步",
4001
+ "common.done": "完成",
4002
+ "common.retry": "重试",
4003
+ "common.confirm": "确认",
4004
+ "common.loading": "加载中…",
4005
+ "common.unknownError": "未知错误",
4006
+ "status.ready": "Config Manager 已就绪(插件 {version} · DSH {dsh} · {platform})",
4007
+ "status.unavailable": "Config Manager 服务未就绪,请确认插件已正确挂载。",
4008
+ "export.mode.quick": "快速导出",
4009
+ "export.mode.quickHint": "一键导出推荐分区(可迁移、不含设备专属数据),适合常规备份。",
4010
+ "export.mode.custom": "自定义导出",
4011
+ "export.mode.customHint": "按分组逐项勾选要导出的分区。",
4012
+ "export.includeSecrets": "包含真实密钥(加密备份)",
4013
+ "export.includeSecretsHint": "勾选后导出将包含密钥,并以密码加密(AES-256-GCM)。密码仅用于本次加密,不会写入备份或任何配置文件。",
4014
+ "export.password": "备份加密密码",
4015
+ "export.passwordConfirm": "确认密码",
4016
+ "export.passwordMismatch": "两次输入的密码不一致",
4017
+ "export.passwordRequired": "包含密钥时必须设置加密密码",
4018
+ "export.run": "开始导出",
4019
+ "export.running": "正在导出…",
4020
+ "export.download": "下载备份文件",
4021
+ "export.selectionWarnings": "以下分区为设备相关数据,跨设备导入时可能不适用:",
4022
+ "export.group.unknown": "其他",
4023
+ "export.included": "已包含",
4024
+ "export.excluded": "未包含",
4025
+ "import.select.title": "选择备份文件",
4026
+ "import.select.hint": "选择本机导出的 dsh-config-*.zip 备份文件开始导入。分析阶段只读,不会修改任何配置。",
4027
+ "import.select.browse": "选择 ZIP 文件",
4028
+ "import.select.file": "已选择:{name}",
4029
+ "import.analyzing": "正在分析备份…",
4030
+ "import.compatibility.title": "兼容性",
4031
+ "import.compatibility.score": "兼容性评分:{score}",
4032
+ "import.compatibility.ok": "备份可导入",
4033
+ "import.compatibility.fail": "备份分析失败:{reason}",
4034
+ "import.preview.title": "导入预览",
4035
+ "import.preview.willChange": "将变更 {count} 项",
4036
+ "import.preview.unchanged": "已一致跳过 {count} 项",
4037
+ "import.preview.settings": "设置更新 {count}",
4038
+ "import.preview.plugins": "插件待安装 {count}",
4039
+ "import.preview.mcp": "MCP 新增 {count}",
4040
+ "import.preview.prompts": "提示词 {count}",
4041
+ "import.preview.paths": "路径映射 {count}",
4042
+ "import.preview.secrets": "密钥待补录 {count}",
4043
+ "import.preview.conflicts": "冲突 {count}",
4044
+ "import.preview.restart": "导入后需重启 DSH 生效",
4045
+ "import.confirm.execute": "确认导入",
4046
+ "import.confirm.warning": "导入将修改当前 DSH 配置;执行前会自动创建安全快照,失败时整体回滚。",
4047
+ "import.rollbackOnError": "失败时整体回滚(推荐)",
4048
+ "import.importing": "正在导入…(插件安装较多时可能需要几分钟,请勿关闭页面)",
4049
+ "import.secrets.title": "补录密钥",
4050
+ "import.secrets.hint": "以下凭据未随备份导出(安全设计)。如需在目标 DSH 使用,请手动补录;留空将跳过。",
4051
+ "import.secrets.optional": "(可选)",
4052
+ "import.secrets.required": "(必需)",
4053
+ "import.conflicts.title": "解决冲突",
4054
+ "import.conflicts.hint": "以下配置项在目标 DSH 已存在且与备份不同,请逐项决策。",
4055
+ "import.conflicts.keepCurrent": "保留当前",
4056
+ "import.conflicts.useImported": "使用备份",
4057
+ "import.conflicts.review": "稍后决定",
4058
+ "import.conflicts.unresolved": "还有 {count} 项未决策",
4059
+ "import.paths.title": "路径映射",
4060
+ "import.paths.hint": "备份中的绝对路径在目标机器可能不存在,请为每条路径指定新位置(留空将跳过该路径)。",
4061
+ "import.paths.old": "原路径",
4062
+ "import.paths.new": "新路径",
4063
+ "import.paths.unresolved": "还有 {count} 条路径未映射",
4064
+ "snapshots.title": "快照恢复",
4065
+ "snapshots.hint": "恢复到导入前状态:整文件还原 settings/patch、卸载导入期间新增插件、补偿被修改文件。执行前会把当前文件备份到快照的 pre-restore 目录。",
4066
+ "snapshots.empty": "暂无快照。执行导入时会自动创建安全快照。",
4067
+ "snapshots.loading": "加载快照中…",
4068
+ "snapshots.selectHint": "选择快照以预览恢复计划(dry-run,零写入):",
4069
+ "snapshots.createdAt": "创建时间",
4070
+ "snapshots.sourceZip": "来源备份",
4071
+ "snapshots.status": "状态",
4072
+ "snapshots.entries": "条目",
4073
+ "snapshots.plugins": "插件",
4074
+ "snapshots.status.pending": "进行中",
4075
+ "snapshots.status.done": "已完成",
4076
+ "snapshots.status.rolled-back": "已回滚",
4077
+ "snapshots.status.unknown": "未知",
4078
+ "snapshots.planTitle": "恢复计划预览",
4079
+ "snapshots.noActions": "该快照无可用恢复动作(或全部跳过)。",
4080
+ "snapshots.execute": "执行恢复",
4081
+ "snapshots.executing": "恢复执行中…",
4082
+ "snapshots.confirmRestore": "确认执行恢复?当前文件会先备份到快照的 pre-restore 目录,插件通过官方 dsh plugin remove 卸载。",
4083
+ "snapshots.reportTitle": "恢复报告",
4084
+ "snapshots.restored": "已还原",
4085
+ "snapshots.removedPlugins": "已卸载插件",
4086
+ "snapshots.manualHints": "需人工处理",
4087
+ "snapshots.failed": "失败",
4088
+ "snapshots.skipped": "跳过",
4089
+ "report.export.title": "导出完成",
4090
+ "report.import.title": "导入结果",
4091
+ "report.rollback.title": "回滚报告",
4092
+ "report.action.fixIssues": "查看失败项",
4093
+ "report.action.viewDetails": "查看详情",
4094
+ "report.action.done": "完成",
4095
+ "report.needsRestart": "插件 / MCP 变更将在重启 DSH 后生效。",
4096
+ "error.title": "操作失败",
4097
+ "error.hint": "错误消息已自动脱敏,不会泄露任何密钥信息。"
4098
+ };
4099
+ const en$1 = {
4100
+ "section.label": "Backup & Migration",
4101
+ "section.description": "Export / import / migrate DSH configuration (backups exclude secrets; encrypted backups optional)",
4102
+ "view.export": "Export",
4103
+ "view.import": "Import",
4104
+ "view.snapshots": "Snapshots & Restore",
4105
+ "view.sync": "Remote Sync",
4106
+ "common.close": "Close",
4107
+ "common.cancel": "Cancel",
4108
+ "common.back": "Back",
4109
+ "common.next": "Next",
4110
+ "common.done": "Done",
4111
+ "common.retry": "Retry",
4112
+ "common.confirm": "Confirm",
4113
+ "common.loading": "Loading…",
4114
+ "common.unknownError": "Unknown error",
4115
+ "status.ready": "Config Manager ready (plugin {version} · DSH {dsh} · {platform})",
4116
+ "status.unavailable": "Config Manager service unavailable — verify the plugin is mounted.",
4117
+ "export.mode.quick": "Quick Export",
4118
+ "export.mode.quickHint": "One-click export of recommended sections (portable, no device-specific data) for routine backups.",
4119
+ "export.mode.custom": "Custom Export",
4120
+ "export.mode.customHint": "Select sections per group.",
4121
+ "export.includeSecrets": "Include real secrets (encrypted backup)",
4122
+ "export.includeSecretsHint": "When enabled the backup contains secrets, encrypted with a password (AES-256-GCM). The password is used once and never written into the backup or any config file.",
4123
+ "export.password": "Backup encryption password",
4124
+ "export.passwordConfirm": "Confirm password",
4125
+ "export.passwordMismatch": "Passwords do not match",
4126
+ "export.passwordRequired": "An encryption password is required when including secrets",
4127
+ "export.run": "Start Export",
4128
+ "export.running": "Exporting…",
4129
+ "export.download": "Download backup file",
4130
+ "export.selectionWarnings": "Device-specific sections may not apply on another machine:",
4131
+ "export.group.unknown": "Other",
4132
+ "export.included": "Included",
4133
+ "export.excluded": "Excluded",
4134
+ "import.select.title": "Select Backup File",
4135
+ "import.select.hint": "Pick a dsh-config-*.zip backup exported from DSH to begin. Analysis is read-only and changes nothing.",
4136
+ "import.select.browse": "Choose ZIP file",
4137
+ "import.select.file": "Selected: {name}",
4138
+ "import.analyzing": "Analyzing backup…",
4139
+ "import.compatibility.title": "Compatibility",
4140
+ "import.compatibility.score": "Compatibility score: {score}",
4141
+ "import.compatibility.ok": "The backup can be imported",
4142
+ "import.compatibility.fail": "Backup analysis failed: {reason}",
4143
+ "import.preview.title": "Import Preview",
4144
+ "import.preview.willChange": "{count} item(s) will change",
4145
+ "import.preview.unchanged": "{count} item(s) already identical (skipped)",
4146
+ "import.preview.settings": "{count} settings update(s)",
4147
+ "import.preview.plugins": "{count} plugin(s) to install",
4148
+ "import.preview.mcp": "{count} MCP server(s) added",
4149
+ "import.preview.prompts": "{count} prompt(s)",
4150
+ "import.preview.paths": "{count} path mapping(s) needed",
4151
+ "import.preview.secrets": "{count} secret(s) to re-enter",
4152
+ "import.preview.conflicts": "{count} conflict(s)",
4153
+ "import.preview.restart": "A DSH restart is required after import",
4154
+ "import.confirm.execute": "Confirm Import",
4155
+ "import.confirm.warning": "Import modifies the current DSH configuration; a safety snapshot is created first and everything rolls back on failure.",
4156
+ "import.rollbackOnError": "Roll back everything on failure (recommended)",
4157
+ "import.importing": "Importing… (installing many plugins may take a few minutes; keep this page open)",
4158
+ "import.secrets.title": "Re-enter Secrets",
4159
+ "import.secrets.hint": "These credentials were not exported with the backup (security by design). Enter them manually to use them here; leave blank to skip.",
4160
+ "import.secrets.optional": "(optional)",
4161
+ "import.secrets.required": "(required)",
4162
+ "import.conflicts.title": "Resolve Conflicts",
4163
+ "import.conflicts.hint": "These items already exist on this machine and differ from the backup. Decide per item.",
4164
+ "import.conflicts.keepCurrent": "Keep current",
4165
+ "import.conflicts.useImported": "Use backup",
4166
+ "import.conflicts.review": "Decide later",
4167
+ "import.conflicts.unresolved": "{count} item(s) unresolved",
4168
+ "import.paths.title": "Path Mapping",
4169
+ "import.paths.hint": "Absolute paths in the backup may not exist on this machine. Map each path to a new location (leave blank to skip).",
4170
+ "import.paths.old": "Original path",
4171
+ "import.paths.new": "New path",
4172
+ "import.paths.unresolved": "{count} path(s) unmapped",
4173
+ "snapshots.title": "Snapshot Restore",
4174
+ "snapshots.hint": "Restore to the pre-import state: whole-file restore of settings/patch, uninstall plugins added during import, compensate modified files. Current files are backed up to the snapshot's pre-restore folder before any change.",
4175
+ "snapshots.empty": "No snapshots yet. A safety snapshot is created automatically on import.",
4176
+ "snapshots.loading": "Loading snapshots…",
4177
+ "snapshots.selectHint": "Select a snapshot to preview its restore plan (dry-run, zero writes):",
4178
+ "snapshots.createdAt": "Created",
4179
+ "snapshots.sourceZip": "Source",
4180
+ "snapshots.status": "Status",
4181
+ "snapshots.entries": "Entries",
4182
+ "snapshots.plugins": "Plugins",
4183
+ "snapshots.status.pending": "Pending",
4184
+ "snapshots.status.done": "Done",
4185
+ "snapshots.status.rolled-back": "Rolled back",
4186
+ "snapshots.status.unknown": "Unknown",
4187
+ "snapshots.planTitle": "Restore Plan Preview",
4188
+ "snapshots.noActions": "No restorable actions for this snapshot (or all skipped).",
4189
+ "snapshots.execute": "Restore now",
4190
+ "snapshots.executing": "Restoring…",
4191
+ "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.",
4192
+ "snapshots.reportTitle": "Restore Report",
4193
+ "snapshots.restored": "Restored",
4194
+ "snapshots.removedPlugins": "Removed plugins",
4195
+ "snapshots.manualHints": "Manual action needed",
4196
+ "snapshots.failed": "Failed",
4197
+ "snapshots.skipped": "Skipped",
4198
+ "report.export.title": "Export Complete",
4199
+ "report.import.title": "Import Result",
4200
+ "report.rollback.title": "Rollback Report",
4201
+ "report.action.fixIssues": "View failures",
4202
+ "report.action.viewDetails": "View details",
4203
+ "report.action.done": "Done",
4204
+ "report.needsRestart": "Plugin / MCP changes take effect after restarting DSH.",
4205
+ "error.title": "Operation failed",
4206
+ "error.hint": "Error messages are auto-redacted and never leak secrets."
4207
+ };
4208
+ //#endregion
4211
4209
  //#region src/client/sync/sync-locales.ts
4212
4210
  /**
4213
4211
  * 远程同步设置区块(config-manager-sync)表面文案:zh 为源语言,en 镜像每个键。
@@ -4305,16 +4303,12 @@ function apply(ctx) {
4305
4303
  order: 60,
4306
4304
  label: () => t("section.label"),
4307
4305
  locale: NS,
4308
- inject: () => ({ api })
4306
+ inject: () => ({
4307
+ api,
4308
+ syncApi,
4309
+ syncT
4310
+ })
4309
4311
  }, ConfigManagerSection));
4310
- ctx.slots.inject("settings.section", () => ctx.slots.register({
4311
- name: "settings.section",
4312
- id: "config-manager-sync",
4313
- order: 70,
4314
- label: () => syncT("section.label"),
4315
- locale: SYNC_NS,
4316
- inject: () => ({ api: syncApi })
4317
- }, SyncSettingsView));
4318
4312
  }
4319
4313
  //#endregion
4320
4314
  exports.apply = apply;