dsh-config-manager 0.1.19 → 0.1.20
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.d.ts +51 -0
- package/lib/client.js +284 -89
- package/lib/client.js.map +1 -1
- package/lib/index.js +135 -2
- package/lib/index.js.map +1 -1
- package/lib/sync/ancestor.d.ts +17 -0
- package/lib/sync/ancestor.js +64 -0
- package/lib/sync/ancestor.js.map +1 -0
- package/lib/sync/merge.d.ts +42 -0
- package/lib/sync/merge.js +325 -0
- package/lib/sync/merge.js.map +1 -0
- package/lib/sync/review-queue.d.ts +51 -0
- package/lib/sync/review-queue.js +119 -0
- package/lib/sync/review-queue.js.map +1 -0
- package/lib/sync/risk.d.ts +59 -0
- package/lib/sync/risk.js +76 -0
- package/lib/sync/risk.js.map +1 -0
- package/lib/sync/sync-config.d.ts +7 -2
- package/lib/sync/sync-config.js +24 -4
- package/lib/sync/sync-config.js.map +1 -1
- package/lib/sync/sync-engine.d.ts +32 -1
- package/lib/sync/sync-engine.js +207 -8
- package/lib/sync/sync-engine.js.map +1 -1
- package/lib/sync/sync-state.d.ts +7 -2
- package/lib/sync/sync-state.js +9 -5
- package/lib/sync/sync-state.js.map +1 -1
- package/package.json +1 -1
- package/src/client/sync/SyncHistoryView.test.ts +40 -0
- package/src/client/sync/SyncHistoryView.tsx +73 -0
- package/src/client/sync/SyncPullPreviewView.test.ts +54 -0
- package/src/client/sync/SyncPullPreviewView.tsx +165 -0
- package/src/client/sync/SyncSettingsView.tsx +88 -2
- package/src/client/sync/history-model.ts +25 -0
- package/src/client/sync/pull-preview-model.ts +44 -0
- package/src/client/sync/sync-api.ts +28 -1
- package/src/client/sync/sync-view.ts +1 -1
- package/src/index.ts +127 -2
- package/src/sync/ancestor.test.ts +152 -0
- package/src/sync/ancestor.ts +81 -0
- package/src/sync/merge.test.ts +204 -0
- package/src/sync/merge.ts +360 -0
- package/src/sync/review-queue.test.ts +120 -0
- package/src/sync/review-queue.ts +167 -0
- package/src/sync/risk.test.ts +131 -0
- package/src/sync/risk.ts +122 -0
- package/src/sync/sync-config.test.ts +106 -0
- package/src/sync/sync-config.ts +23 -4
- package/src/sync/sync-engine.test.ts +292 -3
- package/src/sync/sync-engine.ts +219 -8
- package/src/sync/sync-state.test.ts +48 -2
- package/src/sync/sync-state.ts +11 -5
package/lib/client.d.ts
CHANGED
|
@@ -528,6 +528,25 @@ declare class ConfigManagerApi {
|
|
|
528
528
|
restoreSnapshot(snapshotId: string, dryRun: boolean): Promise<RestoreResponse>;
|
|
529
529
|
}
|
|
530
530
|
//#endregion
|
|
531
|
+
//#region src/sync/review-queue.d.ts
|
|
532
|
+
/** 单条待审项(决策前 description/local/remote/ancestor 必填;decision 可选) */
|
|
533
|
+
interface ReviewQueueItem {
|
|
534
|
+
id: string;
|
|
535
|
+
/** 分区 id(settings / providers / workspaces / plugins / mcp 等) */
|
|
536
|
+
sectionId: string;
|
|
537
|
+
/** 'key' | 'file' | 'section':冲突粒度(来自 MergeConflict.kind 或整分区冲突) */
|
|
538
|
+
kind: 'key' | 'file' | 'section';
|
|
539
|
+
description: string;
|
|
540
|
+
local?: unknown;
|
|
541
|
+
remote?: unknown;
|
|
542
|
+
ancestor?: unknown;
|
|
543
|
+
/** 用户决策(resolve 后写入) */
|
|
544
|
+
decision?: 'useRemote' | 'keepLocal' | 'skip';
|
|
545
|
+
decidedAt?: string;
|
|
546
|
+
/** 关联的远端快照 id(追溯来源) */
|
|
547
|
+
snapshotId?: string;
|
|
548
|
+
}
|
|
549
|
+
//#endregion
|
|
531
550
|
//#region src/sync/sync-engine.d.ts
|
|
532
551
|
interface SyncPushReport {
|
|
533
552
|
ok: boolean;
|
|
@@ -554,6 +573,20 @@ interface SyncPullReport {
|
|
|
554
573
|
needsReview: boolean;
|
|
555
574
|
message?: string;
|
|
556
575
|
}
|
|
576
|
+
/** 自动应用执行器(P2b)报告 */
|
|
577
|
+
interface ApplyReport {
|
|
578
|
+
ok: boolean;
|
|
579
|
+
/** 实际写入本地的分区 id 列表 */
|
|
580
|
+
applied: string[];
|
|
581
|
+
/** 应用前快照 id(UI 可借此一键回滚);失败时仍透传以便排查 */
|
|
582
|
+
restoreId: string;
|
|
583
|
+
/** 是否触发了整体回滚 */
|
|
584
|
+
rolledBack: boolean;
|
|
585
|
+
/** 失败时移到 review 队列的项(ReviewQueueItem 形态供 UI 直接渲染) */
|
|
586
|
+
review: ReviewQueueItem[];
|
|
587
|
+
/** 来自 Importer.ImportResult 的 warnings;UI 可用于红条提示 */
|
|
588
|
+
warnings: string[];
|
|
589
|
+
}
|
|
557
590
|
//#endregion
|
|
558
591
|
//#region src/client/sync/sync-api.d.ts
|
|
559
592
|
/** GET /sync/status 响应:配置/凭据/上次同步的只读事实(无任何 secret 值) */
|
|
@@ -632,6 +665,24 @@ declare class SyncApi {
|
|
|
632
665
|
githubCancel(flowId: string): Promise<{
|
|
633
666
|
ok: boolean;
|
|
634
667
|
}>;
|
|
668
|
+
/** 同步历史:列出 localSnapshotsDir 下的快照目录(manifest.json: id/createdAt/sectionHashes) */
|
|
669
|
+
history(): Promise<SyncHistoryEntry[]>;
|
|
670
|
+
/** 应用自动应用计划:写本地 + backup + (失败时) rollback + enqueue review;返回 ApplyReport */
|
|
671
|
+
apply(payload: SyncPushPayload): Promise<ApplyReport>;
|
|
672
|
+
/** 一键回滚:按 restoreId 调用 backup→rollback */
|
|
673
|
+
rollback(payload: {
|
|
674
|
+
restoreId: string;
|
|
675
|
+
}): Promise<{
|
|
676
|
+
ok: boolean;
|
|
677
|
+
full: boolean;
|
|
678
|
+
}>;
|
|
679
|
+
}
|
|
680
|
+
/** 同步历史条目(Host 端返回) */
|
|
681
|
+
interface SyncHistoryEntry {
|
|
682
|
+
id: string;
|
|
683
|
+
createdAt: string;
|
|
684
|
+
sectionCount: number;
|
|
685
|
+
reviewCount: number;
|
|
635
686
|
}
|
|
636
687
|
//#endregion
|
|
637
688
|
//#region src/client/client-types.d.ts
|
package/lib/client.js
CHANGED
|
@@ -1949,104 +1949,104 @@ if (typeof document !== "undefined" && document.querySelector("style[data-tag=\"
|
|
|
1949
1949
|
document.head.appendChild(tag);
|
|
1950
1950
|
}
|
|
1951
1951
|
var config_manager_module_css_default = {
|
|
1952
|
-
"
|
|
1953
|
-
"snapshotRow": "_367UGW_snapshotRow",
|
|
1954
|
-
"pathNew": "_367UGW_pathNew",
|
|
1955
|
-
"hiddenFile": "_367UGW_hiddenFile",
|
|
1956
|
-
"kindTag": "_367UGW_kindTag",
|
|
1957
|
-
"field": "_367UGW_field",
|
|
1958
|
-
"groupLabel": "_367UGW_groupLabel",
|
|
1959
|
-
"groupHeader": "_367UGW_groupHeader",
|
|
1960
|
-
"badgeWarn": "_367UGW_badgeWarn",
|
|
1961
|
-
"errorReason": "_367UGW_errorReason",
|
|
1962
|
-
"formError": "_367UGW_formError",
|
|
1963
|
-
"pathOld": "_367UGW_pathOld",
|
|
1964
|
-
"progressBlock": "_367UGW_progressBlock",
|
|
1965
|
-
"card": "_367UGW_card",
|
|
1952
|
+
"warnText": "_367UGW_warnText",
|
|
1966
1953
|
"checkboxRow": "_367UGW_checkboxRow",
|
|
1967
|
-
"
|
|
1968
|
-
"
|
|
1969
|
-
"
|
|
1970
|
-
"
|
|
1971
|
-
"
|
|
1972
|
-
"progressBadge": "_367UGW_progressBadge",
|
|
1954
|
+
"progressDetail": "_367UGW_progressDetail",
|
|
1955
|
+
"conflictDetail": "_367UGW_conflictDetail",
|
|
1956
|
+
"kindTag": "_367UGW_kindTag",
|
|
1957
|
+
"modeTab": "_367UGW_modeTab",
|
|
1958
|
+
"groupList": "_367UGW_groupList",
|
|
1973
1959
|
"progressBadgeSection": "_367UGW_progressBadgeSection",
|
|
1974
|
-
"
|
|
1975
|
-
"actionRow": "_367UGW_actionRow",
|
|
1976
|
-
"severityError": "_367UGW_severityError",
|
|
1977
|
-
"progressLabel": "_367UGW_progressLabel",
|
|
1978
|
-
"section": "_367UGW_section",
|
|
1979
|
-
"badge": "_367UGW_badge",
|
|
1980
|
-
"viewTab": "_367UGW_viewTab",
|
|
1981
|
-
"conflictId": "_367UGW_conflictId",
|
|
1982
|
-
"snapshotList": "_367UGW_snapshotList",
|
|
1983
|
-
"reportList": "_367UGW_reportList",
|
|
1984
|
-
"rowActions": "_367UGW_rowActions",
|
|
1985
|
-
"viewTabs": "_367UGW_viewTabs",
|
|
1986
|
-
"badgeInfo": "_367UGW_badgeInfo",
|
|
1987
|
-
"groupNote": "_367UGW_groupNote",
|
|
1988
|
-
"conflictList": "_367UGW_conflictList",
|
|
1989
|
-
"categoryDesc": "_367UGW_categoryDesc",
|
|
1990
|
-
"conflictItem": "_367UGW_conflictItem",
|
|
1991
|
-
"sectionTitle": "_367UGW_sectionTitle",
|
|
1992
|
-
"empty": "_367UGW_empty",
|
|
1960
|
+
"groupLabel": "_367UGW_groupLabel",
|
|
1993
1961
|
"fieldLabel": "_367UGW_fieldLabel",
|
|
1994
|
-
"
|
|
1962
|
+
"groupCard": "_367UGW_groupCard",
|
|
1963
|
+
"hiddenFile": "_367UGW_hiddenFile",
|
|
1964
|
+
"conflictId": "_367UGW_conflictId",
|
|
1965
|
+
"spinner": "_367UGW_spinner",
|
|
1966
|
+
"section": "_367UGW_section",
|
|
1967
|
+
"progressMeta": "_367UGW_progressMeta",
|
|
1995
1968
|
"reportScroll": "_367UGW_reportScroll",
|
|
1996
|
-
"
|
|
1997
|
-
"
|
|
1998
|
-
"
|
|
1969
|
+
"dshCmIndeterminate": "_367UGW_dshCmIndeterminate",
|
|
1970
|
+
"badgeOk": "_367UGW_badgeOk",
|
|
1971
|
+
"banner": "_367UGW_banner",
|
|
1972
|
+
"warnList": "_367UGW_warnList",
|
|
1973
|
+
"progressBadge": "_367UGW_progressBadge",
|
|
1974
|
+
"spinnerLabel": "_367UGW_spinnerLabel",
|
|
1975
|
+
"primaryButton": "_367UGW_primaryButton",
|
|
1976
|
+
"progressBarDone": "_367UGW_progressBarDone",
|
|
1999
1977
|
"secretFields": "_367UGW_secretFields",
|
|
2000
|
-
"reportView": "_367UGW_reportView",
|
|
2001
|
-
"errorFooter": "_367UGW_errorFooter",
|
|
2002
|
-
"pathMappingList": "_367UGW_pathMappingList",
|
|
2003
|
-
"pathValue": "_367UGW_pathValue",
|
|
2004
|
-
"spinner": "_367UGW_spinner",
|
|
2005
|
-
"input": "_367UGW_input",
|
|
2006
|
-
"progressIndeterminate": "_367UGW_progressIndeterminate",
|
|
2007
|
-
"groupList": "_367UGW_groupList",
|
|
2008
|
-
"progressDetail": "_367UGW_progressDetail",
|
|
2009
|
-
"optionsCard": "_367UGW_optionsCard",
|
|
2010
|
-
"ghostButton": "_367UGW_ghostButton",
|
|
2011
|
-
"errorLine": "_367UGW_errorLine",
|
|
2012
|
-
"modeTabs": "_367UGW_modeTabs",
|
|
2013
1978
|
"progressBadgeCount": "_367UGW_progressBadgeCount",
|
|
2014
|
-
"
|
|
2015
|
-
"errorTitle": "_367UGW_errorTitle",
|
|
1979
|
+
"pathValue": "_367UGW_pathValue",
|
|
2016
1980
|
"groupItems": "_367UGW_groupItems",
|
|
2017
|
-
"sectionHeader": "_367UGW_sectionHeader",
|
|
2018
1981
|
"errorAction": "_367UGW_errorAction",
|
|
2019
|
-
"
|
|
2020
|
-
"
|
|
2021
|
-
"
|
|
2022
|
-
"secretsList": "_367UGW_secretsList",
|
|
1982
|
+
"planScroll": "_367UGW_planScroll",
|
|
1983
|
+
"progressBlock": "_367UGW_progressBlock",
|
|
1984
|
+
"formError": "_367UGW_formError",
|
|
2023
1985
|
"radioLabel": "_367UGW_radioLabel",
|
|
2024
|
-
"
|
|
2025
|
-
"
|
|
2026
|
-
"
|
|
2027
|
-
"
|
|
2028
|
-
"progressBarDone": "_367UGW_progressBarDone",
|
|
2029
|
-
"snapshotRowHeader": "_367UGW_snapshotRowHeader",
|
|
2030
|
-
"badgeError": "_367UGW_badgeError",
|
|
2031
|
-
"banner": "_367UGW_banner",
|
|
2032
|
-
"modeHint": "_367UGW_modeHint",
|
|
2033
|
-
"sectionTitleBlock": "_367UGW_sectionTitleBlock",
|
|
2034
|
-
"primaryButton": "_367UGW_primaryButton",
|
|
2035
|
-
"categoryItem": "_367UGW_categoryItem",
|
|
1986
|
+
"badge": "_367UGW_badge",
|
|
1987
|
+
"categoryDesc": "_367UGW_categoryDesc",
|
|
1988
|
+
"dangerButton": "_367UGW_dangerButton",
|
|
1989
|
+
"progressPercent": "_367UGW_progressPercent",
|
|
2036
1990
|
"progressBar": "_367UGW_progressBar",
|
|
2037
|
-
"
|
|
2038
|
-
"
|
|
2039
|
-
"
|
|
1991
|
+
"ghostButton": "_367UGW_ghostButton",
|
|
1992
|
+
"errorBanner": "_367UGW_errorBanner",
|
|
1993
|
+
"categoryItem": "_367UGW_categoryItem",
|
|
1994
|
+
"errorList": "_367UGW_errorList",
|
|
1995
|
+
"reportText": "_367UGW_reportText",
|
|
1996
|
+
"conflictList": "_367UGW_conflictList",
|
|
1997
|
+
"errorReason": "_367UGW_errorReason",
|
|
1998
|
+
"severityError": "_367UGW_severityError",
|
|
1999
|
+
"rowActions": "_367UGW_rowActions",
|
|
2000
|
+
"rollbackBox": "_367UGW_rollbackBox",
|
|
2001
|
+
"badgeError": "_367UGW_badgeError",
|
|
2002
|
+
"badgeInfo": "_367UGW_badgeInfo",
|
|
2003
|
+
"optionsCard": "_367UGW_optionsCard",
|
|
2004
|
+
"dshCmSpin": "_367UGW_dshCmSpin",
|
|
2005
|
+
"input": "_367UGW_input",
|
|
2006
|
+
"errorItem": "_367UGW_errorItem",
|
|
2007
|
+
"groupHeader": "_367UGW_groupHeader",
|
|
2040
2008
|
"spinnerWrap": "_367UGW_spinnerWrap",
|
|
2041
|
-
"
|
|
2042
|
-
"
|
|
2043
|
-
"
|
|
2009
|
+
"pathOld": "_367UGW_pathOld",
|
|
2010
|
+
"snapshotList": "_367UGW_snapshotList",
|
|
2011
|
+
"viewTab": "_367UGW_viewTab",
|
|
2012
|
+
"sectionSubtitle": "_367UGW_sectionSubtitle",
|
|
2044
2013
|
"sectionBody": "_367UGW_sectionBody",
|
|
2045
|
-
"
|
|
2014
|
+
"actionRow": "_367UGW_actionRow",
|
|
2015
|
+
"modeHint": "_367UGW_modeHint",
|
|
2016
|
+
"statRow": "_367UGW_statRow",
|
|
2017
|
+
"categoryName": "_367UGW_categoryName",
|
|
2018
|
+
"progressTrack": "_367UGW_progressTrack",
|
|
2046
2019
|
"reportFooter": "_367UGW_reportFooter",
|
|
2047
|
-
"
|
|
2048
|
-
"
|
|
2049
|
-
"
|
|
2020
|
+
"progressIndeterminate": "_367UGW_progressIndeterminate",
|
|
2021
|
+
"sectionHeader": "_367UGW_sectionHeader",
|
|
2022
|
+
"badgeWarn": "_367UGW_badgeWarn",
|
|
2023
|
+
"conflictHead": "_367UGW_conflictHead",
|
|
2024
|
+
"conflictOptions": "_367UGW_conflictOptions",
|
|
2025
|
+
"viewBody": "_367UGW_viewBody",
|
|
2026
|
+
"pathIssueKind": "_367UGW_pathIssueKind",
|
|
2027
|
+
"snapshotRowHeader": "_367UGW_snapshotRowHeader",
|
|
2028
|
+
"snapshotRow": "_367UGW_snapshotRow",
|
|
2029
|
+
"reportList": "_367UGW_reportList",
|
|
2030
|
+
"empty": "_367UGW_empty",
|
|
2031
|
+
"field": "_367UGW_field",
|
|
2032
|
+
"sectionTitle": "_367UGW_sectionTitle",
|
|
2033
|
+
"errorFooter": "_367UGW_errorFooter",
|
|
2034
|
+
"pathMappingList": "_367UGW_pathMappingList",
|
|
2035
|
+
"reportView": "_367UGW_reportView",
|
|
2036
|
+
"secretsList": "_367UGW_secretsList",
|
|
2037
|
+
"pathRow": "_367UGW_pathRow",
|
|
2038
|
+
"groupNote": "_367UGW_groupNote",
|
|
2039
|
+
"card": "_367UGW_card",
|
|
2040
|
+
"sectionTitleBlock": "_367UGW_sectionTitleBlock",
|
|
2041
|
+
"conflictItem": "_367UGW_conflictItem",
|
|
2042
|
+
"modeTabs": "_367UGW_modeTabs",
|
|
2043
|
+
"viewTabs": "_367UGW_viewTabs",
|
|
2044
|
+
"errorTitle": "_367UGW_errorTitle",
|
|
2045
|
+
"errorLine": "_367UGW_errorLine",
|
|
2046
|
+
"pathNew": "_367UGW_pathNew",
|
|
2047
|
+
"progressLabel": "_367UGW_progressLabel",
|
|
2048
|
+
"hint": "_367UGW_hint",
|
|
2049
|
+
"errorActionLabel": "_367UGW_errorActionLabel"
|
|
2050
2050
|
};
|
|
2051
2051
|
//#endregion
|
|
2052
2052
|
//#region src/client/common/ui.tsx
|
|
@@ -3802,7 +3802,10 @@ const SYNC_API = {
|
|
|
3802
3802
|
pull: "/api/dsh-config-manager/sync/pull",
|
|
3803
3803
|
githubStart: "/api/dsh-config-manager/sync/github/start",
|
|
3804
3804
|
githubPoll: "/api/dsh-config-manager/sync/github/poll",
|
|
3805
|
-
githubCancel: "/api/dsh-config-manager/sync/github/cancel"
|
|
3805
|
+
githubCancel: "/api/dsh-config-manager/sync/github/cancel",
|
|
3806
|
+
history: "/api/dsh-config-manager/sync/history",
|
|
3807
|
+
apply: "/api/dsh-config-manager/sync/apply",
|
|
3808
|
+
rollback: "/api/dsh-config-manager/sync/rollback"
|
|
3806
3809
|
};
|
|
3807
3810
|
/** DSH credentials 中的同步 token 引用名(Host 半同值;仅供提示文案使用,值由 Host 读写) */
|
|
3808
3811
|
const SYNC_CREDENTIAL_REF = "DSH_CONFIG_MANAGER_SYNC_TOKEN";
|
|
@@ -3869,6 +3872,18 @@ var SyncApi = class {
|
|
|
3869
3872
|
async githubCancel(flowId) {
|
|
3870
3873
|
return postJson(SYNC_API.githubCancel, { flowId }, this.t);
|
|
3871
3874
|
}
|
|
3875
|
+
/** 同步历史:列出 localSnapshotsDir 下的快照目录(manifest.json: id/createdAt/sectionHashes) */
|
|
3876
|
+
async history() {
|
|
3877
|
+
return readJson(await fetch(SYNC_API.history), this.t);
|
|
3878
|
+
}
|
|
3879
|
+
/** 应用自动应用计划:写本地 + backup + (失败时) rollback + enqueue review;返回 ApplyReport */
|
|
3880
|
+
async apply(payload) {
|
|
3881
|
+
return postJson(SYNC_API.apply, payload, this.t);
|
|
3882
|
+
}
|
|
3883
|
+
/** 一键回滚:按 restoreId 调用 backup→rollback */
|
|
3884
|
+
async rollback(payload) {
|
|
3885
|
+
return postJson(SYNC_API.rollback, payload, this.t);
|
|
3886
|
+
}
|
|
3872
3887
|
};
|
|
3873
3888
|
//#endregion
|
|
3874
3889
|
//#region src/client/sync/sync-view.ts
|
|
@@ -3966,11 +3981,11 @@ function computeSyncStatus(statusInfo, loading, error, t = zhUiT) {
|
|
|
3966
3981
|
};
|
|
3967
3982
|
return {
|
|
3968
3983
|
kind: "ready",
|
|
3969
|
-
text: `${statusInfo.credentialConfigured ? t("sync.credConfigured") : t("sync.credMissing")} · ${statusInfo.lastSyncAt !== void 0 && statusInfo.lastSyncAt !== "" ? t("sync.lastSync", { time: formatDateTime(statusInfo.lastSyncAt) }) : t("sync.neverSynced")}${statusInfo.transport !== void 0 ? ` · ${statusInfo.transport.type}${statusInfo.transport.ref !== "" ? `/${statusInfo.transport.ref}` : ""}` : ""}`
|
|
3984
|
+
text: `${statusInfo.credentialConfigured ? t("sync.credConfigured") : t("sync.credMissing")} · ${statusInfo.lastSyncAt !== void 0 && statusInfo.lastSyncAt !== "" ? t("sync.lastSync", { time: formatDateTime$1(statusInfo.lastSyncAt) }) : t("sync.neverSynced")}${statusInfo.transport !== void 0 ? ` · ${statusInfo.transport.type}${statusInfo.transport.ref !== "" ? `/${statusInfo.transport.ref}` : ""}` : ""}`
|
|
3970
3985
|
};
|
|
3971
3986
|
}
|
|
3972
3987
|
/** ISO-8601 → 本地可读时间(YYYY-MM-DD HH:mm;非法输入原样返回) */
|
|
3973
|
-
function formatDateTime(iso) {
|
|
3988
|
+
function formatDateTime$1(iso) {
|
|
3974
3989
|
const d = new Date(iso);
|
|
3975
3990
|
if (Number.isNaN(d.getTime())) return iso;
|
|
3976
3991
|
const pad = (n) => String(n).padStart(2, "0");
|
|
@@ -4066,6 +4081,88 @@ function githubPollMessage(poll, t = zhUiT) {
|
|
|
4066
4081
|
}
|
|
4067
4082
|
}
|
|
4068
4083
|
//#endregion
|
|
4084
|
+
//#region src/client/sync/history-model.ts
|
|
4085
|
+
/** 把 entries 排序(createdAt 倒序)并组装展示字段。 */
|
|
4086
|
+
function projectHistoryRows(entries) {
|
|
4087
|
+
return [...entries].sort((a, b) => a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0);
|
|
4088
|
+
}
|
|
4089
|
+
/** ISO 时间 → 本地可读字符串(短格式) */
|
|
4090
|
+
function formatDateTime(iso) {
|
|
4091
|
+
if (!iso) return "—";
|
|
4092
|
+
const d = new Date(iso);
|
|
4093
|
+
if (isNaN(d.getTime())) return iso;
|
|
4094
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
4095
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
4096
|
+
}
|
|
4097
|
+
//#endregion
|
|
4098
|
+
//#region src/client/sync/SyncHistoryView.tsx
|
|
4099
|
+
/**
|
|
4100
|
+
* 同步历史视图(M6,P2b):列出 localSnapshotsDir 各快照目录的 manifest.json
|
|
4101
|
+
* (id + createdAt + sectionHashes);每行显示:快照 ID、时间、分区数、关联待审数(来自 sync-review-queue.json)。
|
|
4102
|
+
*
|
|
4103
|
+
* 数据获取:通过 Host 的 IPC 端点(与 SyncSettingsView 同模式)。本组件只负责渲染;
|
|
4104
|
+
* 数据加载由宿主 API 提供(不在浏览器侧直接读 fs)。
|
|
4105
|
+
*/
|
|
4106
|
+
function SyncHistoryView(props) {
|
|
4107
|
+
const { api } = props;
|
|
4108
|
+
const [loading, setLoading] = (0, react.useState)(true);
|
|
4109
|
+
const [error, setError] = (0, react.useState)(null);
|
|
4110
|
+
const [entries, setEntries] = (0, react.useState)([]);
|
|
4111
|
+
(0, react.useEffect)(() => {
|
|
4112
|
+
let cancelled = false;
|
|
4113
|
+
(async () => {
|
|
4114
|
+
try {
|
|
4115
|
+
const data = await api.history();
|
|
4116
|
+
if (!cancelled) {
|
|
4117
|
+
setEntries(data);
|
|
4118
|
+
setLoading(false);
|
|
4119
|
+
}
|
|
4120
|
+
} catch (err) {
|
|
4121
|
+
if (!cancelled) {
|
|
4122
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
4123
|
+
setLoading(false);
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
})();
|
|
4127
|
+
return () => {
|
|
4128
|
+
cancelled = true;
|
|
4129
|
+
};
|
|
4130
|
+
}, [api]);
|
|
4131
|
+
const rows = (0, react.useMemo)(() => projectHistoryRows(entries), [entries]);
|
|
4132
|
+
if (loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: "加载同步历史…" });
|
|
4133
|
+
if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4134
|
+
className: "error",
|
|
4135
|
+
children: error
|
|
4136
|
+
}) });
|
|
4137
|
+
if (rows.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "尚无同步历史" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: "完成首次 push/pull 后这里会显示快照记录。" })] });
|
|
4138
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionTitle, { title: `同步历史(${rows.length})` }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
4139
|
+
className: "sync-history-table",
|
|
4140
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
4141
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "快照 ID" }),
|
|
4142
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "时间" }),
|
|
4143
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "分区" }),
|
|
4144
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "待审" })
|
|
4145
|
+
] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: rows.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
4146
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: r.id }) }),
|
|
4147
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: formatDateTime(r.createdAt) }),
|
|
4148
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: r.sectionCount }),
|
|
4149
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: r.reviewCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: r.reviewCount }) : 0 })
|
|
4150
|
+
] }, r.id)) })]
|
|
4151
|
+
})] });
|
|
4152
|
+
}
|
|
4153
|
+
//#endregion
|
|
4154
|
+
//#region src/client/sync/SyncPullPreviewView.tsx
|
|
4155
|
+
/**
|
|
4156
|
+
* 拉取预览页(M5,P2b):消费 SyncApplyPlan,渲染
|
|
4157
|
+
* - 自动已应用列表(autoApplied)—— 绿色状态徽章 + sectionId/description;
|
|
4158
|
+
* - 待审变更列表(review)—— 每项三个按钮(用本地 / 用远端 / 跳过)+ 局部差异 diff;
|
|
4159
|
+
* - 整体 rollback 后「回滚到应用前」按钮(调用 restoreId);
|
|
4160
|
+
* - 全部 review 解决 → 触发 onAllResolved 回调(sync 完成)。
|
|
4161
|
+
*
|
|
4162
|
+
* 设计:与 SyncSettingsView 同模式 —— 全部渲染模型来自 ./sync-view.ts 纯函数,
|
|
4163
|
+
* 组件只做装配 + 状态;测试在 node --test 跑纯函数部分。
|
|
4164
|
+
*/
|
|
4165
|
+
//#endregion
|
|
4069
4166
|
//#region src/client/sync/SyncSettingsView.tsx
|
|
4070
4167
|
/**
|
|
4071
4168
|
* 远程同步面板(备份与迁移页的第 4 个 tab 内容)。
|
|
@@ -4102,6 +4199,7 @@ const initial = {
|
|
|
4102
4199
|
busy: null,
|
|
4103
4200
|
pushReport: null,
|
|
4104
4201
|
pullReport: null,
|
|
4202
|
+
applyReport: null,
|
|
4105
4203
|
error: null,
|
|
4106
4204
|
github: initialGithub
|
|
4107
4205
|
};
|
|
@@ -4267,6 +4365,45 @@ function SyncSettingsView({ api, t }) {
|
|
|
4267
4365
|
});
|
|
4268
4366
|
}
|
|
4269
4367
|
};
|
|
4368
|
+
/** P2:自动应用(Host 端 engine.merge + classifyMergePlan + applyMergePlan 走完整链路) */
|
|
4369
|
+
const runApply = async () => {
|
|
4370
|
+
patch({
|
|
4371
|
+
busy: "apply",
|
|
4372
|
+
error: null,
|
|
4373
|
+
applyReport: null
|
|
4374
|
+
});
|
|
4375
|
+
try {
|
|
4376
|
+
const report = await api.apply(payload());
|
|
4377
|
+
patch({
|
|
4378
|
+
busy: null,
|
|
4379
|
+
applyReport: report
|
|
4380
|
+
});
|
|
4381
|
+
} catch (err) {
|
|
4382
|
+
patch({
|
|
4383
|
+
busy: null,
|
|
4384
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4385
|
+
});
|
|
4386
|
+
}
|
|
4387
|
+
};
|
|
4388
|
+
/** P2:一键回滚(按 apply 返回的 restoreId 调 Host /sync/rollback) */
|
|
4389
|
+
const runRollback = async (restoreId) => {
|
|
4390
|
+
patch({
|
|
4391
|
+
busy: "rollback",
|
|
4392
|
+
error: null
|
|
4393
|
+
});
|
|
4394
|
+
try {
|
|
4395
|
+
await api.rollback({ restoreId });
|
|
4396
|
+
patch({
|
|
4397
|
+
busy: null,
|
|
4398
|
+
applyReport: null
|
|
4399
|
+
});
|
|
4400
|
+
} catch (err) {
|
|
4401
|
+
patch({
|
|
4402
|
+
busy: null,
|
|
4403
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4404
|
+
});
|
|
4405
|
+
}
|
|
4406
|
+
};
|
|
4270
4407
|
const status = computeSyncStatus(state.statusInfo, state.loading, state.loadError, uiT);
|
|
4271
4408
|
const buttons = computeSyncButtons(state.busy, state.repoUrl, uiT);
|
|
4272
4409
|
const pushView = pushReportView(state.pushReport, uiT);
|
|
@@ -4546,7 +4683,65 @@ function SyncSettingsView({ api, t }) {
|
|
|
4546
4683
|
kind: "info",
|
|
4547
4684
|
children: pullView.previewHint
|
|
4548
4685
|
})
|
|
4549
|
-
] })
|
|
4686
|
+
] }),
|
|
4687
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4688
|
+
className: config_manager_module_css_default.actionRow,
|
|
4689
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
4690
|
+
variant: "primary",
|
|
4691
|
+
disabled: state.busy !== null || state.pullReport === null,
|
|
4692
|
+
onClick: () => {
|
|
4693
|
+
runApply();
|
|
4694
|
+
},
|
|
4695
|
+
children: state.busy === "apply" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: "自动应用中…" }) : "自动应用(合并 + 写本地)"
|
|
4696
|
+
})
|
|
4697
|
+
}),
|
|
4698
|
+
state.applyReport !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
|
|
4699
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4700
|
+
className: config_manager_module_css_default.groupLabel,
|
|
4701
|
+
children: "自动应用结果"
|
|
4702
|
+
}),
|
|
4703
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
|
|
4704
|
+
kind: state.applyReport.ok ? "ok" : "error",
|
|
4705
|
+
children: state.applyReport.ok ? `已应用 ${state.applyReport.applied.length} 个分区(restoreId=${state.applyReport.restoreId})` : `自动应用失败(已整体回滚,restoreId=${state.applyReport.restoreId})`
|
|
4706
|
+
}),
|
|
4707
|
+
state.applyReport.applied.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4708
|
+
className: config_manager_module_css_default.fieldLabel,
|
|
4709
|
+
children: "已写入"
|
|
4710
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4711
|
+
className: config_manager_module_css_default.statRow,
|
|
4712
|
+
children: state.applyReport.applied.map((sid) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
4713
|
+
kind: "ok",
|
|
4714
|
+
children: sid
|
|
4715
|
+
}, sid))
|
|
4716
|
+
})] }),
|
|
4717
|
+
state.applyReport.warnings.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4718
|
+
className: config_manager_module_css_default.fieldLabel,
|
|
4719
|
+
children: "告警"
|
|
4720
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
4721
|
+
className: config_manager_module_css_default.warnList,
|
|
4722
|
+
children: state.applyReport.warnings.map((w, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: w }, i))
|
|
4723
|
+
})] }),
|
|
4724
|
+
state.applyReport.review.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4725
|
+
className: config_manager_module_css_default.fieldLabel,
|
|
4726
|
+
children: "待审(已入 sync-review-queue.json)"
|
|
4727
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
4728
|
+
className: config_manager_module_css_default.warnList,
|
|
4729
|
+
children: state.applyReport.review.map((r, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
4730
|
+
r.sectionId,
|
|
4731
|
+
" — ",
|
|
4732
|
+
r.description
|
|
4733
|
+
] }, i))
|
|
4734
|
+
})] }),
|
|
4735
|
+
!state.applyReport.ok && state.applyReport.restoreId !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
4736
|
+
variant: "danger",
|
|
4737
|
+
disabled: state.busy !== null,
|
|
4738
|
+
onClick: () => {
|
|
4739
|
+
runRollback(state.applyReport.restoreId);
|
|
4740
|
+
},
|
|
4741
|
+
children: state.busy === "rollback" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: "回滚中…" }) : "回滚到应用前"
|
|
4742
|
+
})
|
|
4743
|
+
] }),
|
|
4744
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SyncHistoryView, { api })
|
|
4550
4745
|
]
|
|
4551
4746
|
});
|
|
4552
4747
|
}
|