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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-config-manager",
3
- "version": "0.1.13",
4
- "description": "DSH Config Manager — backup / export / import / migrate DSH configuration (dual-face Cordis plugin: host engine + web UI). Manifest shape mirrors @linxin666/dsh-ssh@0.1.12.",
3
+ "version": "0.1.14",
4
+ "description": "DSH Config Manager — backup / export / import / migrate DSH configuration (dual-face Cordis plugin: host engine + web UI). Manifest shape mirrors @linxin666/dsh-ssh@0.1.12.",
5
5
  "keywords": [
6
6
  "dsh",
7
7
  "deepseek-harness",
@@ -16,6 +16,7 @@ import { runStore, type MainView } from './run-store.ts'
16
16
  import { ExportView } from './export/ExportView.tsx'
17
17
  import { ImportWizardView } from './import/ImportWizardView.tsx'
18
18
  import { SnapshotsPanel } from './snapshots/SnapshotsPanel.tsx'
19
+ import { SyncSettingsView } from './sync/SyncSettingsView.tsx'
19
20
  import css from './config-manager.module.css'
20
21
 
21
22
  export type ConfigManagerSectionProps =
@@ -24,14 +25,15 @@ export type ConfigManagerSectionProps =
24
25
  & { t: TranslateNS<'config-manager'> }
25
26
 
26
27
  /**
27
- * 设置页容器:Export / Import / Snapshots 三视图切换。
28
- * Export/Import 状态在模块级 store(切 tab 不丢失);快照恢复面板为低频显式
28
+ * 设置页容器:Export / Import / Snapshots / Sync 四视图切换。
29
+ * Export/Import 状态在模块级 store(切 tab 不丢失);快照恢复与远程同步为低频显式
29
30
  * 操作,其状态组件内自持(local state,不进 sessionStorage)。
30
31
  */
31
- export function ConfigManagerSection({ api, t }: ConfigManagerSectionProps) {
32
+ export function ConfigManagerSection({ api, syncApi, syncT, t }: ConfigManagerSectionProps) {
32
33
  const state = useSyncExternalStore(runStore.subscribe, runStore.getSnapshot)
33
34
  const view = state.view
34
35
  const [snapshotsOpen, setSnapshotsOpen] = useState(false)
36
+ const [syncOpen, setSyncOpen] = useState(false)
35
37
 
36
38
  // m2-resume:挂载时重新订阅进行中的 run(刷新 / 重开面板后服务端继续执行,
37
39
  // 这里经 /runs 找回活跃 runId 再轮询 /progress);卸载时停止轮询,重开再订阅。
@@ -44,10 +46,21 @@ export function ConfigManagerSection({ api, t }: ConfigManagerSectionProps) {
44
46
 
45
47
  const setView = (next: MainView): void => {
46
48
  setSnapshotsOpen(false)
49
+ setSyncOpen(false)
47
50
  runStore.patch({ view: next })
48
51
  }
49
52
 
50
- const activeTab: MainView | 'snapshots' = snapshotsOpen ? 'snapshots' : view
53
+ const openSnapshots = (): void => {
54
+ setSnapshotsOpen(true)
55
+ setSyncOpen(false)
56
+ }
57
+
58
+ const openSync = (): void => {
59
+ setSyncOpen(true)
60
+ setSnapshotsOpen(false)
61
+ }
62
+
63
+ const activeTab: MainView | 'snapshots' | 'sync' = syncOpen ? 'sync' : snapshotsOpen ? 'snapshots' : view
51
64
 
52
65
  return (
53
66
  <div className={css.section}>
@@ -79,16 +92,28 @@ export function ConfigManagerSection({ api, t }: ConfigManagerSectionProps) {
79
92
  aria-selected={activeTab === 'snapshots'}
80
93
  data-active={snapshotsOpen ? '' : undefined}
81
94
  className={css.viewTab}
82
- onClick={() => { setSnapshotsOpen(true) }}
95
+ onClick={openSnapshots}
83
96
  >
84
97
  {t('view.snapshots')}
85
98
  </button>
99
+ <button
100
+ type="button"
101
+ role="tab"
102
+ aria-selected={activeTab === 'sync'}
103
+ data-active={syncOpen ? '' : undefined}
104
+ className={css.viewTab}
105
+ onClick={openSync}
106
+ >
107
+ {t('view.sync')}
108
+ </button>
86
109
  </div>
87
110
  </div>
88
111
  <div className={css.sectionBody}>
89
- {snapshotsOpen
90
- ? <SnapshotsPanel api={api} t={t} />
91
- : view === 'export' ? <ExportView api={api} t={t} /> : <ImportWizardView api={api} t={t} />}
112
+ {syncOpen
113
+ ? <SyncSettingsView api={syncApi} t={syncT} />
114
+ : snapshotsOpen
115
+ ? <SnapshotsPanel api={api} t={t} />
116
+ : view === 'export' ? <ExportView api={api} t={t} /> : <ImportWizardView api={api} t={t} />}
92
117
  </div>
93
118
  </div>
94
119
  )
@@ -18,4 +18,8 @@ export type { TranslateNS }
18
18
  /** 本插件 Client 半的注入业务面:每个 settings.section 注册项都拿到同一个 api 实例 */
19
19
  export interface ConfigManagerSectionInjected {
20
20
  api: import('./api.ts').ConfigManagerApi
21
+ /** 远程同步 API(备份与迁移页第 4 个 tab 使用,主 section 注册时注入) */
22
+ syncApi: import('./sync/sync-api.ts').SyncApi
23
+ /** 远程同步 locale(config-manager-sync 命名空间,主 section 注册时注入) */
24
+ syncT: import('@deepseek-ai/dsh-client-ui-slots').TranslateNS<'config-manager-sync'>
21
25
  }
@@ -66,22 +66,16 @@ export function apply(ctx: ClientContext): void {
66
66
  const syncT = ctx.locale.bind(SYNC_NS)
67
67
  const syncApi = new SyncApi()
68
68
 
69
+ // 单一 settings.section:备份与迁移页(内部 Export/Import/Snapshots/Sync 四 tab)。
70
+ // 远程同步不再注册独立设置页 —— 并入主 section 的 inject 面(syncApi/syncT),
71
+ // 由 ConfigManagerSection 渲染第 4 个 tab。避免第二个 settings.section 注册在
72
+ // 目标 DSH 渲染 section 列表时抛错导致整页空白。
69
73
  ctx.slots.inject('settings.section', () => ctx.slots.register({
70
74
  name: 'settings.section',
71
75
  id: 'config-manager',
72
76
  order: 60,
73
77
  label: () => t('section.label'),
74
78
  locale: NS,
75
- inject: () => ({ api }),
79
+ inject: () => ({ api, syncApi, syncT }),
76
80
  }, ConfigManagerSection))
77
-
78
- // m-sync-ui:远程同步独立设置页(不触碰 ConfigManagerSection 的 tab 容器)。
79
- ctx.slots.inject('settings.section', () => ctx.slots.register({
80
- name: 'settings.section',
81
- id: 'config-manager-sync',
82
- order: 70,
83
- label: () => syncT('section.label'),
84
- locale: SYNC_NS,
85
- inject: () => ({ api: syncApi }),
86
- }, SyncSettingsView))
87
81
  }
@@ -12,6 +12,7 @@ export const zh = {
12
12
  'view.export': '导出备份',
13
13
  'view.import': '导入恢复',
14
14
  'view.snapshots': '快照恢复',
15
+ 'view.sync': '远程同步',
15
16
  // 公共
16
17
  'common.close': '关闭',
17
18
  'common.cancel': '取消',
@@ -128,6 +129,7 @@ export const en: Record<keyof typeof zh, string> = {
128
129
  'view.export': 'Export',
129
130
  'view.import': 'Import',
130
131
  'view.snapshots': 'Snapshots & Restore',
132
+ 'view.sync': 'Remote Sync',
131
133
  'common.close': 'Close',
132
134
  'common.cancel': 'Cancel',
133
135
  'common.back': 'Back',
@@ -1,7 +1,8 @@
1
1
  /**
2
- * 远程同步设置区块(settings.section: config-manager-sync)。
2
+ * 远程同步面板(备份与迁移页的第 4 个 tab 内容)。
3
3
  *
4
- * 独立 settings 页(不触碰并行会话的 ConfigManagerSection.tsx 三 tab 容器):
4
+ * 独立设置页壳(sectionHeader/close/自身 tab)已移除 —— tab 容器由
5
+ * ConfigManagerSection 统一渲染,本组件只输出内容体:
5
6
  * - 仓库配置表单:repoUrl(必填)+ 认证 token(可选,写入 DSH credentials 的提示)
6
7
  * + gitBin(可选);
7
8
  * - 私有仓库强制提示横幅(常驻);
@@ -16,7 +17,6 @@
16
17
  */
17
18
  import { useEffect, useState } from 'react'
18
19
  import type { ChangeEvent } from 'react'
19
- import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
20
20
  import type { TranslateNS } from '../client-types.ts'
21
21
  import type { SyncPullReport, SyncPushReport } from '../../sync/sync-engine.ts'
22
22
  import { Badge, Banner, Button, Card, SectionTitle, Spinner } from '../common/ui.tsx'
@@ -29,15 +29,11 @@ import {
29
29
  } from './sync-view.ts'
30
30
  import css from '../config-manager.module.css'
31
31
 
32
- export interface SyncSettingsSectionInjected {
32
+ export interface SyncSettingsViewProps {
33
33
  api: SyncApi
34
+ t: TranslateNS<'config-manager-sync'>
34
35
  }
35
36
 
36
- export type SyncSettingsViewProps =
37
- & PropsRuntime<'settings.section'>
38
- & SyncSettingsSectionInjected
39
- & { t: TranslateNS<'config-manager-sync'> }
40
-
41
37
  interface SyncUiState {
42
38
  loading: boolean
43
39
  loadError: string | null
@@ -65,7 +61,7 @@ const initial: SyncUiState = {
65
61
  error: null,
66
62
  }
67
63
 
68
- export function SyncSettingsView({ close, api, t }: SyncSettingsViewProps) {
64
+ export function SyncSettingsView({ api, t }: SyncSettingsViewProps) {
69
65
  const [state, setState] = useState<SyncUiState>(initial)
70
66
  const patch = (p: Partial<SyncUiState>): void => setState((s) => ({ ...s, ...p }))
71
67
 
@@ -120,16 +116,8 @@ export function SyncSettingsView({ close, api, t }: SyncSettingsViewProps) {
120
116
  const pullView = pullReportView(state.pullReport)
121
117
 
122
118
  return (
123
- <div className={css.section}>
124
- <div className={css.sectionHeader}>
125
- <div className={css.viewTabs}>
126
- <span role="tab" aria-selected="true" data-active className={css.viewTab}>{t('section.label')}</span>
127
- </div>
128
- <button type="button" className={css.iconButton} title={t('common.close')} aria-label={t('common.close')} onClick={close}>×</button>
129
- </div>
130
- <div className={css.sectionBody}>
131
- <div className={css.viewBody}>
132
- <SectionTitle title={t('section.label')} subtitle={t('section.description')} />
119
+ <div className={css.viewBody}>
120
+ <SectionTitle title={t('section.label')} subtitle={t('section.description')} />
133
121
 
134
122
  {/* 私有仓库强制提示 */}
135
123
  <Banner kind="warn">{PRIVATE_REPO_HINT}</Banner>
@@ -254,8 +242,6 @@ export function SyncSettingsView({ close, api, t }: SyncSettingsViewProps) {
254
242
  {pullView.previewHint !== '' && <Banner kind="info">{pullView.previewHint}</Banner>}
255
243
  </Card>
256
244
  )}
257
- </div>
258
- </div>
259
245
  </div>
260
246
  )
261
- }
247
+ }
@@ -112,7 +112,7 @@ async function postJson<T>(path: string, body: unknown): Promise<T> {
112
112
  }
113
113
  }
114
114
 
115
- /** 远程同步浏览器半数据入口(settings.section: config-manager-sync 的注入业务面) */
115
+ /** 远程同步浏览器半数据入口(备份与迁移页第 4 个 tab 的注入业务面) */
116
116
  export class SyncApi {
117
117
  /** 读取同步状态(配置 / 凭据 / 上次同步时间 / 分区数) */
118
118
  async status(): Promise<SyncStatusResponse> {