ygc-dsh-sync 1.1.0 → 1.2.0

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/client.js CHANGED
@@ -113,7 +113,8 @@ function makePanel(React) {
113
113
  const [note, setNote] = useState(null);
114
114
  const [midDraft, setMidDraft] = useState('');
115
115
  const [scan, setScan] = useState(null);
116
- const [registerPicks, setRegisterPicks] = useState([]);
116
+ // 特征文件夹采纳:面板上待人工决定的那些(路径数组)
117
+ const [featurePicks, setFeaturePicks] = useState([]);
117
118
  const [patrolTime, setPatrolTime] = useState('');
118
119
  const [skillScan, setSkillScan] = useState(null);
119
120
  const [skillPicks, setSkillPicks] = useState([]);
@@ -186,10 +187,19 @@ function makePanel(React) {
186
187
  setBusy('backup');
187
188
  setNote(null);
188
189
  try {
189
- const r = await call('/backup', { workspaceIds: selected || [] });
190
+ // 只执行「选中」的工作区;一个都没选就什么都不做(不默认全选)
191
+ const ids = selected || [];
192
+ if (!ids.length) {
193
+ setNote({ tone: 'err', text: '请先勾选要备份的工作区' });
194
+ return;
195
+ }
196
+ const r = await call('/backup', { workspaceIds: ids });
197
+ const skipped = (r.skipped || []).length;
190
198
  setNote({
191
199
  tone: 'ok',
192
- text: `备份完成:新增 ${r.summary.created}、更新 ${r.summary.updated},共 ${fmtBytes(r.summary.bytes)}`,
200
+ text:
201
+ `备份完成:新增 ${r.summary.created}、更新 ${r.summary.updated},共 ${fmtBytes(r.summary.bytes)}` +
202
+ (skipped ? `;跳过 ${skipped} 个没有本机特征文件夹的` : ''),
193
203
  });
194
204
  await load();
195
205
  } catch (e) {
@@ -203,17 +213,26 @@ function makePanel(React) {
203
213
  setBusy('pull');
204
214
  setNote(null);
205
215
  try {
206
- const r = await call('/pull', { workspaceIds: selected || [] });
216
+ const ids = selected || [];
217
+ if (!ids.length) {
218
+ setNote({ tone: 'err', text: '请先勾选要拉取的工作区' });
219
+ return;
220
+ }
221
+ const r = await call('/pull', { workspaceIds: ids });
207
222
  const s = r.summary;
208
223
  const parts = [`新增 ${s.imported}`, `更新 ${s.updated}`];
209
224
  if (s.forked) parts.push(`分叉 ${s.forked}`);
225
+ // 归属登记是拉取的一部分,不再单列按钮
210
226
  if (s.attached) parts.push(`登记归属 ${s.attached}`);
211
227
  if (s.attachFailed) parts.push(`登记失败 ${s.attachFailed}`);
228
+ if (r.unregistered && r.unregistered.length) {
229
+ parts.push(`${r.unregistered.length} 个目录要你先建特征文件夹`);
230
+ }
212
231
  setNote({
213
232
  tone: r.needsRestart ? 'warn' : 'ok',
214
233
  text: `拉取完成:${parts.join('、')}${
215
234
  r.needsRestart
216
- ? '。有会话没能登记进工作区,重启 DSH 后仍会落在「未分组」,请看状态栏提示。'
235
+ ? '。有会话没能登记进工作区,重启 DSH 后仍会落在「未分组」。'
217
236
  : '。会话已归到对应工作区,侧栏可直接看到。'
218
237
  }`,
219
238
  });
@@ -226,23 +245,78 @@ function makePanel(React) {
226
245
  };
227
246
 
228
247
  /**
229
- * 补登记:把旧版拉进来、但没进工作区归属账的会话修回去。
248
+ * 采纳:在选中的目录里建本机特征文件夹(未注册的顺带注册成工作区)。
230
249
  *
231
- * 默认只补拉取产物;自动化/子代理留下的会话不算,不会一次刷出上百条。
250
+ * 这是「检测到匹配的工作区文件夹 → 人工选择」的那个人工入口。
251
+ * 点完立刻生效:进侧边栏,参与备份与拉取。
232
252
  */
233
- const doAttachRepair = async () => {
234
- setBusy('attach');
253
+ const doAdopt = async (paths) => {
254
+ const list = (paths || featurePicks || []).filter(Boolean);
255
+ if (!list.length) {
256
+ setNote({ tone: 'err', text: '请先勾选要建特征文件夹的目录' });
257
+ return;
258
+ }
259
+ setBusy('adopt');
235
260
  setNote(null);
236
261
  try {
237
- const r = await call('/attach/repair', { workspaceIds: selected || [] });
238
- const s = r.summary;
262
+ const r = await call('/features/adopt', { paths: list });
263
+ const bits = [`已建本机特征文件夹 ${r.adopted.length} 个`];
264
+ if (r.registered && r.registered.length) bits.push(`注册工作区 ${r.registered.length} 个`);
265
+ if (r.errors && r.errors.length) bits.push(`${r.errors.length} 个失败`);
266
+ setNote({ tone: r.errors && r.errors.length ? 'warn' : 'ok', text: bits.join(',') });
267
+ setFeaturePicks([]);
268
+ await load();
269
+ } catch (e) {
270
+ setNote({ tone: 'err', text: String((e && e.message) || e) });
271
+ } finally {
272
+ setBusy('');
273
+ }
274
+ };
275
+
276
+ /** 跳过:这个目录本机不建格子,之后巡检不再反复问(可在面板上改主意)。 */
277
+ const doDecline = async (paths) => {
278
+ const list = (paths || featurePicks || []).filter(Boolean);
279
+ if (!list.length) {
280
+ setNote({ tone: 'err', text: '请先勾选要跳过的目录' });
281
+ return;
282
+ }
283
+ setBusy('decline');
284
+ setNote(null);
285
+ try {
286
+ await call('/features/decline', { paths: list });
287
+ setNote({ tone: 'ok', text: `已跳过 ${list.length} 个目录(本机不建特征文件夹)` });
288
+ setFeaturePicks([]);
289
+ await load();
290
+ } catch (e) {
291
+ setNote({ tone: 'err', text: String((e && e.message) || e) });
292
+ } finally {
293
+ setBusy('');
294
+ }
295
+ };
296
+
297
+ /**
298
+ * 删本机特征文件夹:本机不再参与这个工作区的同步。
299
+ *
300
+ * removeWorkspace=true 时连工作区注册一起去掉(侧边栏不再显示,
301
+ * 与「在侧边栏删除工作区」等价)。别人的格子与文件夹本身不动。
302
+ */
303
+ const doDrop = async (paths, removeWorkspace) => {
304
+ const list = (paths || []).filter(Boolean);
305
+ if (!list.length) return;
306
+ if (removeWorkspace && typeof window !== 'undefined' && typeof window.confirm === 'function') {
307
+ if (!window.confirm('删除后会从侧边栏移除这个工作区,并清掉本机特征文件夹。目录内容与会话文件不受影响。继续?')) {
308
+ return;
309
+ }
310
+ }
311
+ setBusy('drop');
312
+ setNote(null);
313
+ try {
314
+ const r = await call('/features/drop', { paths: list, removeWorkspace: !!removeWorkspace });
239
315
  setNote({
240
- tone: s.failed ? 'warn' : s.attached ? 'ok' : 'ok',
241
- text: s.attached
242
- ? `补登记完成:${s.attached} 个会话已归回工作区${s.failed ? `,${s.failed} 个失败(路径不可达)` : ''}`
243
- : s.failed
244
- ? `没有可补的会话;${s.failed} 个登记失败,多半是工作区路径当前不可访问`
245
- : '补登记完成:没有需要补的会话(都已在正确的工作区里)',
316
+ tone: r.errors && r.errors.length ? 'warn' : 'ok',
317
+ text: `已删除本机特征文件夹 ${r.dropped.length} 个${
318
+ r.errors && r.errors.length ? `,${r.errors.length} 个失败` : ''
319
+ }(本机不再同步该工作区)`,
246
320
  });
247
321
  await load();
248
322
  } catch (e) {
@@ -274,13 +348,14 @@ function makePanel(React) {
274
348
  try {
275
349
  const r = await call('/patrol/scan', {});
276
350
  setScan(r);
277
- const nd = r.discover.length;
278
- const nr = r.repair.length;
351
+ const np = (r.pending || []).length;
352
+ // 只勾选还没表态过的(被拒绝过的默认不勾,免得误操作)
353
+ setFeaturePicks((r.pending || []).filter((p) => p.why === 'unregistered' || p.why === 'existing').map((p) => p.path));
279
354
  setNote({
280
- tone: nd || nr ? 'warn' : 'ok',
281
- text: nd || nr
282
- ? `巡检完成:发现 ${nd} 个待注册文件夹、${nr} 个缺特征文件夹的工作区`
283
- : '巡检完成:没有需要处理的项目',
355
+ tone: np ? 'warn' : 'ok',
356
+ text: np
357
+ ? `巡检完成:${np} 个目录等你决定要不要在本机建特征文件夹`
358
+ : '巡检完成:没有需要你决定的项目',
284
359
  });
285
360
  await load();
286
361
  } catch (e) {
@@ -290,38 +365,18 @@ function makePanel(React) {
290
365
  }
291
366
  };
292
367
 
293
- const doRegister = async () => {
294
- const picked = (scan && scan.discover ? scan.discover : [])
295
- .filter((d) => (registerPicks || []).includes(d.path))
296
- .map((d) => d.path);
297
- if (!picked.length) {
298
- setNote({ tone: 'err', text: '请先勾选要注册的文件夹' });
368
+ /** 补建格子:只补面板上选中的工作区(不再「一点就全补」)。 */
369
+ const doRepair = async () => {
370
+ const ids = selected || [];
371
+ const targets = (state.workspaces || []).filter((w) => ids.includes(w.id)).map((w) => w.path);
372
+ if (!targets.length) {
373
+ setNote({ tone: 'err', text: '请先在「匹配的工作区」里勾选要补建特征文件夹的工作区' });
299
374
  return;
300
375
  }
301
- setBusy('register');
302
- try {
303
- const r = await call('/patrol/register', { paths: picked });
304
- setNote({
305
- tone: r.errors.length ? 'warn' : 'ok',
306
- text: `已注册 ${r.registered.length} 个工作区${
307
- r.errors.length ? `,${r.errors.length} 个失败` : ''
308
- },本机特征文件夹已补齐`,
309
- });
310
- setRegisterPicks([]);
311
- setScan(null);
312
- await load();
313
- } catch (e) {
314
- setNote({ tone: 'err', text: String((e && e.message) || e) });
315
- } finally {
316
- setBusy('');
317
- }
318
- };
319
-
320
- const doRepair = async () => {
321
376
  setBusy('repair');
322
377
  try {
323
- const r = await call('/patrol/repair', {});
324
- setNote({ tone: 'ok', text: `已补全 ${r.created.length} 个特征文件夹` });
378
+ const r = await call('/patrol/repair', { paths: targets });
379
+ setNote({ tone: 'ok', text: `已补建 ${r.created.length} 个本机特征文件夹` });
325
380
  await load();
326
381
  } catch (e) {
327
382
  setNote({ tone: 'err', text: String((e && e.message) || e) });
@@ -648,7 +703,7 @@ function makePanel(React) {
648
703
  h(
649
704
  'p',
650
705
  { className: 'dsb-sub', style: { marginBottom: 12 } },
651
- '扫描特定文件夹下的直接子目录:含 .dsh-sync/ 但未注册的会列出来等你确认;已注册但缺本机特征文件夹的自动补齐。',
706
+ '扫描特定文件夹下的直接子目录:含 .dsh-sync/ 但本机还没建特征文件夹的会列出来等你在下面勾选;建了格子才参与备份与拉取。被别人/别的工具删掉的格子会自动补回来。',
652
707
  ),
653
708
  h(
654
709
  'div',
@@ -660,8 +715,8 @@ function makePanel(React) {
660
715
  ),
661
716
  h(
662
717
  'button',
663
- { className: 'dsb-btn dsb-btn-sm', onClick: doRepair, disabled: !!busy },
664
- busy === 'repair' ? '补全中…' : '补全特征文件夹',
718
+ { className: 'dsb-btn dsb-btn-sm', onClick: doRepair, disabled: !!busy || !(selected || []).length },
719
+ busy === 'repair' ? '补建中…' : '给选中项补建格子',
665
720
  ),
666
721
  h(
667
722
  'label',
@@ -695,71 +750,98 @@ function makePanel(React) {
695
750
  h('span', { className: 'dsb-row-t' }, '分钟巡检一次'),
696
751
  ),
697
752
 
698
- // 扫描结果
699
- scan
700
- ? h(
701
- 'div',
702
- { style: { marginTop: 14 } },
703
- scan.discover.length
704
- ? h(
705
- 'div',
706
- null,
707
- h('div', { className: 'dsb-row-t', style: { marginBottom: 6 } }, `发现 ${scan.discover.length} 个待注册文件夹`),
708
- scan.discover.map((d) =>
753
+ // 巡检结果:待人工决定的采纳候选
754
+ (scan || (state.features && state.features.pendingCount))
755
+ ? (() => {
756
+ const pendingList = (scan && scan.pending) || (state.features && state.features.pending) || [];
757
+ const whyText = {
758
+ unregistered: '检测到匹配目录,本机还没建特征文件夹',
759
+ existing: '存量工作区,本机还没建特征文件夹',
760
+ awaiting: '等你决定',
761
+ declined: '你之前选了不建(可改主意)',
762
+ };
763
+ return h(
764
+ 'div',
765
+ { style: { marginTop: 14 } },
766
+ pendingList.length
767
+ ? h(
768
+ 'div',
769
+ null,
709
770
  h(
710
771
  'div',
711
- { className: 'dsb-ws', key: d.path },
712
- h('input', {
713
- type: 'checkbox',
714
- className: 'dsb-chk',
715
- checked: registerPicks.includes(d.path),
716
- onChange: () =>
717
- setRegisterPicks((cur) =>
718
- cur.includes(d.path) ? cur.filter((x) => x !== d.path) : [...cur, d.path],
719
- ),
720
- }),
772
+ { className: 'dsb-row-t', style: { marginBottom: 6 } },
773
+ `${pendingList.length} 个目录等你决定:要不要在本机建特征文件夹`,
774
+ ),
775
+ pendingList.map((d) =>
721
776
  h(
722
777
  'div',
723
- { className: 'dsb-ws-body' },
724
- h('div', { className: 'dsb-row-t' }, d.title),
725
- h('div', { className: 'dsb-row-d' }, d.path),
778
+ { className: 'dsb-ws', key: d.path },
779
+ h('input', {
780
+ type: 'checkbox',
781
+ className: 'dsb-chk',
782
+ checked: featurePicks.includes(d.path),
783
+ onChange: () =>
784
+ setFeaturePicks((cur) =>
785
+ cur.includes(d.path) ? cur.filter((x) => x !== d.path) : [...cur, d.path],
786
+ ),
787
+ }),
788
+ h(
789
+ 'div',
790
+ { className: 'dsb-ws-body' },
791
+ h(
792
+ 'div',
793
+ { className: 'dsb-row-t' },
794
+ d.title,
795
+ h('span', { className: 'dsb-tag dsb-tag-warn' }, whyText[d.why] || d.why),
796
+ ),
797
+ h('div', { className: 'dsb-row-d' }, d.path),
798
+ ),
726
799
  ),
727
800
  ),
728
- ),
729
- h(
730
- 'div',
731
- { className: 'dsb-actions', style: { marginTop: 10 } },
732
801
  h(
733
- 'button',
734
- {
735
- className: 'dsb-btn dsb-btn-primary dsb-btn-sm',
736
- disabled: !!busy || !registerPicks.length,
737
- onClick: doRegister,
738
- },
739
- busy === 'register' ? '注册中…' : `注册选中(${registerPicks.length})`,
802
+ 'div',
803
+ { className: 'dsb-actions', style: { marginTop: 10 } },
804
+ h(
805
+ 'button',
806
+ {
807
+ className: 'dsb-btn dsb-btn-primary dsb-btn-sm',
808
+ disabled: !!busy || !featurePicks.length,
809
+ onClick: () => doAdopt(),
810
+ },
811
+ busy === 'adopt' ? '建立中…' : `建特征文件夹并注册(${featurePicks.length})`,
812
+ ),
813
+ h(
814
+ 'button',
815
+ {
816
+ className: 'dsb-btn dsb-btn-sm',
817
+ disabled: !!busy || !featurePicks.length,
818
+ onClick: () => doDecline(),
819
+ },
820
+ busy === 'decline' ? '记录中…' : '跳过这些目录',
821
+ ),
822
+ h(
823
+ 'button',
824
+ {
825
+ className: 'dsb-btn dsb-btn-sm',
826
+ onClick: () => setFeaturePicks(pendingList.map((d) => d.path)),
827
+ },
828
+ '全选',
829
+ ),
830
+ h(
831
+ 'button',
832
+ { className: 'dsb-btn dsb-btn-sm', onClick: () => setFeaturePicks([]) },
833
+ '清空选择',
834
+ ),
740
835
  ),
741
836
  h(
742
- 'button',
743
- {
744
- className: 'dsb-btn dsb-btn-sm',
745
- onClick: () => setRegisterPicks(scan.discover.map((d) => d.path)),
746
- },
747
- '全选',
837
+ 'p',
838
+ { className: 'dsb-sub', style: { marginTop: 8 } },
839
+ '建完立刻进侧边栏、参与备份与拉取;跳过的目录本机不建格子,也不参与同步。',
748
840
  ),
749
- ),
750
- )
751
- : h('div', { className: 'dsb-empty' }, '没有待注册的文件夹'),
752
- scan.repair.length
753
- ? h(
754
- 'div',
755
- { style: { marginTop: 12 } },
756
- h('div', { className: 'dsb-row-t' }, `缺本机特征文件夹:${scan.repair.length} 个`),
757
- scan.repair.map((r) =>
758
- h('div', { className: 'dsb-row-d', key: r.path }, `${r.title} — ${r.path}`),
759
- ),
760
- )
761
- : null,
762
- )
841
+ )
842
+ : h('div', { className: 'dsb-empty' }, '没有待你决定的目录'),
843
+ );
844
+ })()
763
845
  : null,
764
846
  ),
765
847
 
@@ -837,20 +919,21 @@ function makePanel(React) {
837
919
  { className: 'dsb-actions', style: { marginTop: 14 } },
838
920
  h(
839
921
  'button',
840
- { className: 'dsb-btn dsb-btn-primary', onClick: doBackup, disabled: !!busy || ws.length === 0 },
841
- busy === 'backup' ? '备份中…' : '立即备份全部',
922
+ { className: 'dsb-btn dsb-btn-primary', onClick: doBackup, disabled: !!busy || !(selected || []).length },
923
+ busy === 'backup' ? '备份中…' : `备份选中的 ${(selected || []).length} 个`,
842
924
  ),
843
925
  h(
844
926
  'button',
845
- { className: 'dsb-btn', onClick: doPull, disabled: !!busy || ws.length === 0 },
846
- busy === 'pull' ? '拉取中…' : '一键拉取',
927
+ { className: 'dsb-btn', onClick: doPull, disabled: !!busy || !(selected || []).length },
928
+ busy === 'pull' ? '拉取中…' : `拉取选中的 ${(selected || []).length} 个`,
847
929
  ),
848
930
  h(
849
- 'button',
850
- { className: 'dsb-btn', onClick: doAttachRepair, disabled: !!busy || ws.length === 0 },
851
- busy === 'attach' ? '补登记中…' : '补登记归属',
931
+ 'span',
932
+ { className: 'dsb-row-d' },
933
+ (selected || []).length
934
+ ? `已选 ${(selected || []).length} 个工作区(只对有本机特征文件夹的生效;拉取时自动登记归属)`
935
+ : '请先勾选工作区:按钮只执行选中项',
852
936
  ),
853
- h('span', { className: 'dsb-row-d' }, `已选 ${(selected || []).length} 个工作区`),
854
937
  ),
855
938
  ),
856
939
 
@@ -860,7 +943,7 @@ function makePanel(React) {
860
943
  h(
861
944
  'div',
862
945
  { className: 'dsb-card-h' },
863
- h('h3', { className: 'dsb-card-t' }, '匹配的工作区'),
946
+ h('h3', { className: 'dsb-card-t' }, '匹配的工作区(有本机特征文件夹)'),
864
947
  ws.length
865
948
  ? h(
866
949
  'button',
@@ -873,7 +956,11 @@ function makePanel(React) {
873
956
  : null,
874
957
  ),
875
958
  ws.length === 0
876
- ? h('div', { className: 'dsb-empty' }, '没有工作区落在已设置的文件夹里。')
959
+ ? h(
960
+ 'div',
961
+ { className: 'dsb-empty' },
962
+ '还没有工作区建好本机特征文件夹。在上面的巡检结果里勾选目录、点「建特征文件夹并注册」才会出现在这里。',
963
+ )
877
964
  : ws.map((w) =>
878
965
  h(
879
966
  'div',
@@ -912,9 +999,58 @@ function makePanel(React) {
912
999
  w.others.map((o) => `${o.machineId}:${o.sessions} 个`).join(' · '),
913
1000
  )
914
1001
  : null,
1002
+ h(
1003
+ 'div',
1004
+ { className: 'dsb-actions', style: { marginTop: 6 } },
1005
+ h(
1006
+ 'button',
1007
+ {
1008
+ className: 'dsb-btn dsb-btn-sm dsb-btn-danger',
1009
+ disabled: !!busy,
1010
+ title: '删掉本机特征文件夹:本机不再参与这个工作区的同步;别人的格子不动',
1011
+ onClick: () => doDrop([w.path], false),
1012
+ },
1013
+ '删本机特征文件夹',
1014
+ ),
1015
+ ),
915
1016
  ),
916
1017
  ),
917
1018
  ),
1019
+
1020
+ // 匹配但没建格子的:列在这里,方便随时补上
1021
+ (state.inactive || []).length
1022
+ ? h(
1023
+ 'div',
1024
+ { style: { marginTop: 14 } },
1025
+ h(
1026
+ 'div',
1027
+ { className: 'dsb-row-t', style: { marginBottom: 6 } },
1028
+ `未参与同步:${state.inactive.length} 个(没有本机特征文件夹)`,
1029
+ ),
1030
+ (state.inactive || []).map((w) =>
1031
+ h(
1032
+ 'div',
1033
+ { className: 'dsb-row', key: w.id },
1034
+ h(
1035
+ 'div',
1036
+ { className: 'dsb-row-main' },
1037
+ h('div', { className: 'dsb-row-t' }, w.title),
1038
+ h('div', { className: 'dsb-row-d' }, w.reason || '未建本机特征文件夹'),
1039
+ h('div', { className: 'dsb-row-d' }, w.path),
1040
+ ),
1041
+ h(
1042
+ 'button',
1043
+ {
1044
+ className: 'dsb-btn dsb-btn-sm',
1045
+ disabled: !!busy,
1046
+ onClick: () => doAdopt([w.path]),
1047
+ },
1048
+ '建特征文件夹',
1049
+ ),
1050
+ ),
1051
+ ),
1052
+ )
1053
+ : null,
918
1054
  ),
919
1055
 
920
1056
  // 插件清单同步(1.0.0 从自动更新器迁入)