@qic-9821/dsh-plugin-sync 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/client.js +47 -8
- package/lib/engine.js +1 -0
- package/lib/http.js +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,6 +40,8 @@ dsh plugin --profile <profile> add @qic-9821/dsh-plugin-sync
|
|
|
40
40
|
| 允许对端改动本机 | `capRemoteApply` | 关掉后对端连读都读不到 |
|
|
41
41
|
| 同步密钥字段 | `capSecrets` | 默认关;`appSecret` / `token` / `*key*` 一律写成 `[redacted]` |
|
|
42
42
|
|
|
43
|
+
默认只开「插件清单与启停」和「允许对端改动本机」;配置、安装、复制二进制、密钥四项要自己在设置页打开,打开后下一次自动同步就会真的落地对应操作。
|
|
44
|
+
|
|
43
45
|
安装、升级、卸载、启停全部走宿主的 `pluginManager` 服务(`installBundle` / `updateBundle` / `removeBundle` / `setBundleEnabled` / `setPluginEnabled`),不自己拼 pnpm 命令行、不手写 profile 的 `package.json`,失败由宿主回滚它自己的改动。
|
|
44
46
|
|
|
45
47
|
## 怎么连
|
|
@@ -55,9 +57,11 @@ dsh plugin --profile <profile> add @qic-9821/dsh-plugin-sync
|
|
|
55
57
|
|
|
56
58
|
- `autoStart`:DSH 启动后约 25 秒跑一轮(等宿主把插件挂完)。
|
|
57
59
|
- `autoRestart`:进程重启后再补一轮(约 2 分钟),处理对端还在启动的情况;服务器重启后的同步就是这条。
|
|
58
|
-
- `intervalMinutes`:固定间隔(0 =
|
|
60
|
+
- `intervalMinutes`:固定间隔(0 = 关)。只在 DSH 运行期间生效,每 N 分钟跑一轮完整同步;和「启动时」「重启后」两轮互不影响。
|
|
59
61
|
- 手动:面板/设置页按钮,或工具 `plugin_sync` `action="run"`。
|
|
60
62
|
|
|
63
|
+
总开关 `enabled` 关掉后,以上任何自动同步与手动同步都不执行(仍可在面板上「只看计划」)。
|
|
64
|
+
|
|
61
65
|
## 冲突策略
|
|
62
66
|
|
|
63
67
|
- `local`:以发起方为准(默认)
|
package/client.js
CHANGED
|
@@ -94,6 +94,9 @@ window.__ModuleLoader__.load({
|
|
|
94
94
|
}),
|
|
95
95
|
h('span', { key: 'text' }, [
|
|
96
96
|
h('b', { key: 'name' }, props.label),
|
|
97
|
+
props.dirty
|
|
98
|
+
? h('span', { key: 'dirty', style: { marginLeft: 6, fontSize: 11, color: 'var(--dsh-accent, #3b82f6)' } }, '未保存')
|
|
99
|
+
: null,
|
|
97
100
|
props.desc ? h('div', { key: 'desc', style: MUTED }, props.desc) : null,
|
|
98
101
|
]),
|
|
99
102
|
],
|
|
@@ -125,8 +128,18 @@ window.__ModuleLoader__.load({
|
|
|
125
128
|
}, [load])
|
|
126
129
|
|
|
127
130
|
const config = state?.config || {}
|
|
128
|
-
const
|
|
131
|
+
const caps = config.capabilities || {}
|
|
132
|
+
// draft wins, then the nested capabilities map, then the flat key
|
|
133
|
+
const value = useCallback(
|
|
134
|
+
(key) => (draft[key] !== undefined ? draft[key] : caps[key] !== undefined ? caps[key] : config[key]),
|
|
135
|
+
[draft, caps, config],
|
|
136
|
+
)
|
|
137
|
+
const dirty = useCallback((key) => draft[key] !== undefined, [draft])
|
|
129
138
|
const setValue = useCallback((key, next) => setDraft((current) => ({ ...current, [key]: next })), [])
|
|
139
|
+
const capsOn = useMemo(
|
|
140
|
+
() => (state?.capabilityCatalog || []).filter((cap) => Boolean(value(cap.key))).length,
|
|
141
|
+
[state, value],
|
|
142
|
+
)
|
|
130
143
|
|
|
131
144
|
const save = useCallback(async () => {
|
|
132
145
|
if (Object.keys(draft).length === 0) return
|
|
@@ -182,6 +195,7 @@ window.__ModuleLoader__.load({
|
|
|
182
195
|
: status.localError || '读取失败',
|
|
183
196
|
],
|
|
184
197
|
['profile 目录', status.profileDir || '?'],
|
|
198
|
+
['版本', status.package ? `${status.package.name} ${status.package.version || ''}`.trim() : '?'],
|
|
185
199
|
['对端', status.peer?.describe || '?'],
|
|
186
200
|
[
|
|
187
201
|
'上次同步',
|
|
@@ -189,9 +203,10 @@ window.__ModuleLoader__.load({
|
|
|
189
203
|
? `${formatTime(status.lastRun.at)} · ${status.lastRun.ok ? '成功' : `失败:${status.lastRun.error || ''}`}`
|
|
190
204
|
: '还没跑过',
|
|
191
205
|
],
|
|
206
|
+
['设置保存于', state?.updatedAt ? formatTime(state.updatedAt) : '还没保存过'],
|
|
192
207
|
['当前', status.running ? '同步中…' : '空闲'],
|
|
193
208
|
]
|
|
194
|
-
}, [status])
|
|
209
|
+
}, [status, state?.updatedAt])
|
|
195
210
|
|
|
196
211
|
if (!state) {
|
|
197
212
|
return h('div', { style: { padding: 16, fontSize: 13 } }, error ? `读取失败:${error}` : '读取中…')
|
|
@@ -259,7 +274,8 @@ window.__ModuleLoader__.load({
|
|
|
259
274
|
h(Toggle, {
|
|
260
275
|
key: 'enable',
|
|
261
276
|
label: '总开关',
|
|
262
|
-
desc: '
|
|
277
|
+
desc: '关掉后任何自动与手动同步都不执行(仍可「只看计划」)',
|
|
278
|
+
dirty: dirty('enabled'),
|
|
263
279
|
checked: Boolean(value('enabled')),
|
|
264
280
|
onChange: (next) => setValue('enabled', next),
|
|
265
281
|
}),
|
|
@@ -267,6 +283,7 @@ window.__ModuleLoader__.load({
|
|
|
267
283
|
key: 'startup',
|
|
268
284
|
label: '软件启动时同步',
|
|
269
285
|
desc: 'DSH 启动后约 25 秒跑一轮(等宿主把插件都挂上)',
|
|
286
|
+
dirty: dirty('autoStart'),
|
|
270
287
|
checked: Boolean(value('autoStart')),
|
|
271
288
|
onChange: (next) => setValue('autoStart', next),
|
|
272
289
|
}),
|
|
@@ -274,22 +291,38 @@ window.__ModuleLoader__.load({
|
|
|
274
291
|
key: 'restart',
|
|
275
292
|
label: '服务器重启后同步',
|
|
276
293
|
desc: '进程重启之后再补一轮,处理对端还在启动的情况',
|
|
294
|
+
dirty: dirty('autoRestart'),
|
|
277
295
|
checked: Boolean(value('autoRestart')),
|
|
278
296
|
onChange: (next) => setValue('autoRestart', next),
|
|
279
297
|
}),
|
|
280
|
-
h(
|
|
281
|
-
|
|
282
|
-
|
|
298
|
+
h(Toggle, {
|
|
299
|
+
key: 'interval',
|
|
300
|
+
label: '定时同步',
|
|
301
|
+
desc: 'DSH 运行期间每 N 分钟跑一轮;关掉就是「不按间隔」,和上面两轮互不影响',
|
|
302
|
+
dirty: dirty('intervalMinutes'),
|
|
303
|
+
checked: Number(value('intervalMinutes')) > 0,
|
|
304
|
+
onChange: (next) =>
|
|
305
|
+
setValue('intervalMinutes', next ? Number(value('intervalMinutes')) || 15 : 0),
|
|
306
|
+
}),
|
|
307
|
+
Number(value('intervalMinutes')) > 0
|
|
308
|
+
? h(
|
|
309
|
+
Field,
|
|
310
|
+
{ key: 'intervalValue', label: '间隔分钟数(1–1440)' },
|
|
311
|
+
input(value('intervalMinutes'), '15', (next) => setValue('intervalMinutes', Math.max(0, Number(next) || 0))),
|
|
312
|
+
)
|
|
313
|
+
: null,
|
|
314
|
+
h(Field, { key: 'delay', label: '启动后延迟秒数(只影响「软件启动时同步」)' },
|
|
283
315
|
input(value('startupDelaySec'), '25', (next) => setValue('startupDelaySec', Number(next) || 0))),
|
|
284
316
|
]),
|
|
285
317
|
]),
|
|
286
318
|
|
|
287
|
-
h(Card, { key: 'caps', title:
|
|
319
|
+
h(Card, { key: 'caps', title: `权限(已开启 ${capsOn} / ${(state.capabilityCatalog || []).length},每一项可以单独关)` },
|
|
288
320
|
(state.capabilityCatalog || []).map((cap) =>
|
|
289
321
|
h(Toggle, {
|
|
290
322
|
key: cap.key,
|
|
291
323
|
label: cap.label,
|
|
292
324
|
desc: cap.desc,
|
|
325
|
+
dirty: dirty(cap.key),
|
|
293
326
|
checked: Boolean(value(cap.key)),
|
|
294
327
|
onChange: (next) => setValue(cap.key, next),
|
|
295
328
|
}),
|
|
@@ -298,7 +331,13 @@ window.__ModuleLoader__.load({
|
|
|
298
331
|
|
|
299
332
|
h('div', { key: 'save', style: { display: 'flex', gap: 8, alignItems: 'center' } }, [
|
|
300
333
|
h('button', { key: 'save', style: BUTTON_PRIMARY, disabled: Boolean(busy) || Object.keys(draft).length === 0, onClick: () => void save() }, '保存设置'),
|
|
301
|
-
h(
|
|
334
|
+
h(
|
|
335
|
+
'span',
|
|
336
|
+
{ key: 'hint', style: MUTED },
|
|
337
|
+
Object.keys(draft).length === 0
|
|
338
|
+
? `已保存 · ${state?.updatedAt ? formatTime(state.updatedAt) : '(本次打开后还没改过)'}`
|
|
339
|
+
: `${Object.keys(draft).length} 项待保存`,
|
|
340
|
+
),
|
|
302
341
|
]),
|
|
303
342
|
|
|
304
343
|
out ? h('pre', { key: 'out', style: { marginTop: 12, whiteSpace: 'pre-wrap', fontSize: 11.5, maxHeight: 260, overflow: 'auto', background: 'rgba(127,127,127,.1)', padding: 10, borderRadius: 8 } }, out) : null,
|
package/lib/engine.js
CHANGED
|
@@ -90,6 +90,7 @@ export class Engine {
|
|
|
90
90
|
},
|
|
91
91
|
direction: cfg.direction,
|
|
92
92
|
conflict: cfg.conflict,
|
|
93
|
+
package: { name: PACKAGE_NAME, version: this.version || null },
|
|
93
94
|
capabilities: Object.fromEntries(CAPABILITY_KEYS.map((key) => [key, Boolean(cfg[key])])),
|
|
94
95
|
local,
|
|
95
96
|
localError,
|
package/lib/http.js
CHANGED
|
@@ -148,6 +148,8 @@ export async function statePayload(engine, cfg) {
|
|
|
148
148
|
capabilities: Object.fromEntries(
|
|
149
149
|
Object.keys(CAPABILITY_LABELS).map((key) => [key, Boolean(cfg[key])]),
|
|
150
150
|
),
|
|
151
|
+
// flat mirror of the capabilities, so consumers can read `capPlugins` directly
|
|
152
|
+
...Object.fromEntries(Object.keys(CAPABILITY_LABELS).map((key) => [key, Boolean(cfg[key])])),
|
|
151
153
|
include: cfg.include,
|
|
152
154
|
exclude: cfg.exclude,
|
|
153
155
|
},
|
|
@@ -319,6 +321,7 @@ function render() {
|
|
|
319
321
|
const s = state.status
|
|
320
322
|
$('status').innerHTML = [
|
|
321
323
|
['本机', s.local ? (s.local.node || '?') + ' · ' + (s.local.profile || '') + ' · ' + s.local.bundles + ' 包 / ' + s.local.plugins + ' 条目' : '读取失败'],
|
|
324
|
+
['版本', s.package ? s.package.name + ' ' + (s.package.version || '') : '?'],
|
|
322
325
|
['profile', s.profileDir || '?'],
|
|
323
326
|
['对端', s.peer.describe],
|
|
324
327
|
['上次同步', s.lastRun ? (s.lastRun.at + ' · ' + (s.lastRun.ok ? '成功' : '失败:' + (s.lastRun.error || ''))) : '还没跑过'],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qic-9821/dsh-plugin-sync",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Keep the DSH plugin set (inventory, configs, enable state, package installs, local bundle binaries) in sync between two machines, with per-capability switches, schedule controls and a settings/panel UI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "QIC-9821",
|