ygc-dsh-sync 1.1.0 → 1.2.1
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 +266 -119
- package/index.js +141 -32
- package/lib/api.js +1815 -1510
- package/lib/core.js +484 -443
- package/lib/features.js +409 -0
- package/package.json +3 -2
- package/tools/draft-live.mjs +1 -5
- package/tools/release-local.mjs +2 -10
- package/tools/sync-lock.mjs +1 -3
- package/tools/verify-installed.mjs +1 -2
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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,84 @@ function makePanel(React) {
|
|
|
226
245
|
};
|
|
227
246
|
|
|
228
247
|
/**
|
|
229
|
-
*
|
|
248
|
+
* 采纳:在选中的目录里建本机特征文件夹(未注册的顺带注册成工作区)。
|
|
230
249
|
*
|
|
231
|
-
*
|
|
250
|
+
* 这是「检测到匹配的工作区文件夹 → 人工选择」的那个人工入口。
|
|
251
|
+
* 点完立刻生效:进侧边栏,参与备份与拉取。
|
|
232
252
|
*/
|
|
233
|
-
const
|
|
234
|
-
|
|
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('/
|
|
238
|
-
const
|
|
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
|
+
* 删工作区(= 删本机特征文件夹,1.2.1 起两者是同一件事)。
|
|
299
|
+
*
|
|
300
|
+
* 插件里不再提供「只删格子、留着工作区」的入口:那条路会让工作区变成
|
|
301
|
+
* 「已注册但没格子」,下一轮巡检必然把它列回人工队列,用户看到的就是
|
|
302
|
+
* 「删了又自己冒出来」。所以这里只保留一个动作,且必须删注册。
|
|
303
|
+
*/
|
|
304
|
+
const doDrop = async (paths) => {
|
|
305
|
+
const list = (paths || []).filter(Boolean);
|
|
306
|
+
if (!list.length) return;
|
|
307
|
+
if (typeof window !== 'undefined' && typeof window.confirm === 'function') {
|
|
308
|
+
if (
|
|
309
|
+
!window.confirm(
|
|
310
|
+
'会从侧边栏删除这个工作区,并清掉本机特征文件夹(本机不再同步它)。\n目录内容、会话文件、其他机器的备份都不受影响。继续?',
|
|
311
|
+
)
|
|
312
|
+
) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
setBusy('drop');
|
|
317
|
+
setNote(null);
|
|
318
|
+
try {
|
|
319
|
+
const r = await call('/features/drop', { paths: list });
|
|
320
|
+
const errs = (r.errors || []).length;
|
|
239
321
|
setNote({
|
|
240
|
-
tone:
|
|
241
|
-
text:
|
|
242
|
-
?
|
|
243
|
-
|
|
244
|
-
? `没有可补的会话;${s.failed} 个登记失败,多半是工作区路径当前不可访问`
|
|
245
|
-
: '补登记完成:没有需要补的会话(都已在正确的工作区里)',
|
|
322
|
+
tone: errs ? 'warn' : 'ok',
|
|
323
|
+
text: `已删除 ${r.dropped.length} 个工作区,并清掉对应本机特征文件夹${
|
|
324
|
+
errs ? `;${errs} 个失败:${r.errors[0].message}` : ''
|
|
325
|
+
}`,
|
|
246
326
|
});
|
|
247
327
|
await load();
|
|
248
328
|
} catch (e) {
|
|
@@ -274,13 +354,14 @@ function makePanel(React) {
|
|
|
274
354
|
try {
|
|
275
355
|
const r = await call('/patrol/scan', {});
|
|
276
356
|
setScan(r);
|
|
277
|
-
const
|
|
278
|
-
|
|
357
|
+
const np = (r.pending || []).length;
|
|
358
|
+
// 只勾选还没表态过的(被拒绝过的默认不勾,免得误操作)
|
|
359
|
+
setFeaturePicks((r.pending || []).filter((p) => p.why === 'unregistered' || p.why === 'existing').map((p) => p.path));
|
|
279
360
|
setNote({
|
|
280
|
-
tone:
|
|
281
|
-
text:
|
|
282
|
-
?
|
|
283
|
-
: '
|
|
361
|
+
tone: np ? 'warn' : 'ok',
|
|
362
|
+
text: np
|
|
363
|
+
? `巡检完成:${np} 个目录等你决定要不要在本机建特征文件夹`
|
|
364
|
+
: '巡检完成:没有需要你决定的项目',
|
|
284
365
|
});
|
|
285
366
|
await load();
|
|
286
367
|
} catch (e) {
|
|
@@ -290,38 +371,18 @@ function makePanel(React) {
|
|
|
290
371
|
}
|
|
291
372
|
};
|
|
292
373
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (!
|
|
298
|
-
setNote({ tone: 'err', text: '
|
|
374
|
+
/** 补建格子:只补面板上选中的工作区(不再「一点就全补」)。 */
|
|
375
|
+
const doRepair = async () => {
|
|
376
|
+
const ids = selected || [];
|
|
377
|
+
const targets = (state.workspaces || []).filter((w) => ids.includes(w.id)).map((w) => w.path);
|
|
378
|
+
if (!targets.length) {
|
|
379
|
+
setNote({ tone: 'err', text: '请先在「匹配的工作区」里勾选要补建特征文件夹的工作区' });
|
|
299
380
|
return;
|
|
300
381
|
}
|
|
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
382
|
setBusy('repair');
|
|
322
383
|
try {
|
|
323
|
-
const r = await call('/patrol/repair', {});
|
|
324
|
-
setNote({ tone: 'ok', text:
|
|
384
|
+
const r = await call('/patrol/repair', { paths: targets });
|
|
385
|
+
setNote({ tone: 'ok', text: `已补建 ${r.created.length} 个本机特征文件夹` });
|
|
325
386
|
await load();
|
|
326
387
|
} catch (e) {
|
|
327
388
|
setNote({ tone: 'err', text: String((e && e.message) || e) });
|
|
@@ -648,7 +709,7 @@ function makePanel(React) {
|
|
|
648
709
|
h(
|
|
649
710
|
'p',
|
|
650
711
|
{ className: 'dsb-sub', style: { marginBottom: 12 } },
|
|
651
|
-
'扫描特定文件夹下的直接子目录:含 .dsh-sync/
|
|
712
|
+
'扫描特定文件夹下的直接子目录:含 .dsh-sync/ 但本机还没建特征文件夹的会列出来等你在下面勾选;建了格子才参与备份与拉取。被别人/别的工具删掉的格子会自动补回来。',
|
|
652
713
|
),
|
|
653
714
|
h(
|
|
654
715
|
'div',
|
|
@@ -660,8 +721,8 @@ function makePanel(React) {
|
|
|
660
721
|
),
|
|
661
722
|
h(
|
|
662
723
|
'button',
|
|
663
|
-
{ className: 'dsb-btn dsb-btn-sm', onClick: doRepair, disabled: !!busy },
|
|
664
|
-
busy === 'repair' ? '
|
|
724
|
+
{ className: 'dsb-btn dsb-btn-sm', onClick: doRepair, disabled: !!busy || !(selected || []).length },
|
|
725
|
+
busy === 'repair' ? '补建中…' : '给选中项补建格子',
|
|
665
726
|
),
|
|
666
727
|
h(
|
|
667
728
|
'label',
|
|
@@ -695,71 +756,103 @@ function makePanel(React) {
|
|
|
695
756
|
h('span', { className: 'dsb-row-t' }, '分钟巡检一次'),
|
|
696
757
|
),
|
|
697
758
|
|
|
698
|
-
//
|
|
699
|
-
scan
|
|
700
|
-
?
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
759
|
+
// 巡检结果:待人工决定的采纳候选
|
|
760
|
+
(scan || (state.features && state.features.pendingCount))
|
|
761
|
+
? (() => {
|
|
762
|
+
const pendingList = (scan && scan.pending) || (state.features && state.features.pending) || [];
|
|
763
|
+
const whyText = {
|
|
764
|
+
// 矩阵里唯一要人决定的两档
|
|
765
|
+
othersOnly: '别的机器有特征文件夹,本机没有',
|
|
766
|
+
structureOnly: '目录里有同步结构,本机没建特征文件夹',
|
|
767
|
+
unregistered: '检测到匹配目录,本机没建特征文件夹',
|
|
768
|
+
existing: '已注册成工作区,本机没建特征文件夹',
|
|
769
|
+
declined: '你之前选了不建(可改主意)',
|
|
770
|
+
};
|
|
771
|
+
return h(
|
|
772
|
+
'div',
|
|
773
|
+
{ style: { marginTop: 14 } },
|
|
774
|
+
pendingList.length
|
|
775
|
+
? h(
|
|
776
|
+
'div',
|
|
777
|
+
null,
|
|
709
778
|
h(
|
|
710
779
|
'div',
|
|
711
|
-
{ className: 'dsb-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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
|
-
}),
|
|
780
|
+
{ className: 'dsb-row-t', style: { marginBottom: 6 } },
|
|
781
|
+
`${pendingList.length} 个目录等你决定:要不要在本机建特征文件夹`,
|
|
782
|
+
),
|
|
783
|
+
pendingList.map((d) =>
|
|
721
784
|
h(
|
|
722
785
|
'div',
|
|
723
|
-
{ className: 'dsb-ws
|
|
724
|
-
h('
|
|
725
|
-
|
|
786
|
+
{ className: 'dsb-ws', key: d.path },
|
|
787
|
+
h('input', {
|
|
788
|
+
type: 'checkbox',
|
|
789
|
+
className: 'dsb-chk',
|
|
790
|
+
checked: featurePicks.includes(d.path),
|
|
791
|
+
onChange: () =>
|
|
792
|
+
setFeaturePicks((cur) =>
|
|
793
|
+
cur.includes(d.path) ? cur.filter((x) => x !== d.path) : [...cur, d.path],
|
|
794
|
+
),
|
|
795
|
+
}),
|
|
796
|
+
h(
|
|
797
|
+
'div',
|
|
798
|
+
{ className: 'dsb-ws-body' },
|
|
799
|
+
h(
|
|
800
|
+
'div',
|
|
801
|
+
{ className: 'dsb-row-t' },
|
|
802
|
+
d.title,
|
|
803
|
+
h('span', { className: 'dsb-tag dsb-tag-warn' }, whyText[d.why] || d.why),
|
|
804
|
+
),
|
|
805
|
+
h('div', { className: 'dsb-row-d' }, d.path),
|
|
806
|
+
d.others && d.others.length
|
|
807
|
+
? h('div', { className: 'dsb-row-d' }, `别的机器:${d.others.join(' · ')}`)
|
|
808
|
+
: null,
|
|
809
|
+
),
|
|
726
810
|
),
|
|
727
811
|
),
|
|
728
|
-
),
|
|
729
|
-
h(
|
|
730
|
-
'div',
|
|
731
|
-
{ className: 'dsb-actions', style: { marginTop: 10 } },
|
|
732
812
|
h(
|
|
733
|
-
'
|
|
734
|
-
{
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
813
|
+
'div',
|
|
814
|
+
{ className: 'dsb-actions', style: { marginTop: 10 } },
|
|
815
|
+
h(
|
|
816
|
+
'button',
|
|
817
|
+
{
|
|
818
|
+
className: 'dsb-btn dsb-btn-primary dsb-btn-sm',
|
|
819
|
+
disabled: !!busy || !featurePicks.length,
|
|
820
|
+
onClick: () => doAdopt(),
|
|
821
|
+
},
|
|
822
|
+
busy === 'adopt' ? '建立中…' : `建特征文件夹并注册(${featurePicks.length})`,
|
|
823
|
+
),
|
|
824
|
+
h(
|
|
825
|
+
'button',
|
|
826
|
+
{
|
|
827
|
+
className: 'dsb-btn dsb-btn-sm',
|
|
828
|
+
disabled: !!busy || !featurePicks.length,
|
|
829
|
+
onClick: () => doDecline(),
|
|
830
|
+
},
|
|
831
|
+
busy === 'decline' ? '记录中…' : '跳过这些目录',
|
|
832
|
+
),
|
|
833
|
+
h(
|
|
834
|
+
'button',
|
|
835
|
+
{
|
|
836
|
+
className: 'dsb-btn dsb-btn-sm',
|
|
837
|
+
onClick: () => setFeaturePicks(pendingList.map((d) => d.path)),
|
|
838
|
+
},
|
|
839
|
+
'全选',
|
|
840
|
+
),
|
|
841
|
+
h(
|
|
842
|
+
'button',
|
|
843
|
+
{ className: 'dsb-btn dsb-btn-sm', onClick: () => setFeaturePicks([]) },
|
|
844
|
+
'清空选择',
|
|
845
|
+
),
|
|
740
846
|
),
|
|
741
847
|
h(
|
|
742
|
-
'
|
|
743
|
-
{
|
|
744
|
-
|
|
745
|
-
onClick: () => setRegisterPicks(scan.discover.map((d) => d.path)),
|
|
746
|
-
},
|
|
747
|
-
'全选',
|
|
848
|
+
'p',
|
|
849
|
+
{ className: 'dsb-sub', style: { marginTop: 8 } },
|
|
850
|
+
'建完立刻进侧边栏、参与备份与拉取;跳过的目录本机不建格子,也不参与同步。',
|
|
748
851
|
),
|
|
749
|
-
)
|
|
750
|
-
)
|
|
751
|
-
|
|
752
|
-
|
|
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
|
-
)
|
|
852
|
+
)
|
|
853
|
+
: h('div', { className: 'dsb-empty' }, '没有待你决定的目录'),
|
|
854
|
+
);
|
|
855
|
+
})()
|
|
763
856
|
: null,
|
|
764
857
|
),
|
|
765
858
|
|
|
@@ -837,20 +930,21 @@ function makePanel(React) {
|
|
|
837
930
|
{ className: 'dsb-actions', style: { marginTop: 14 } },
|
|
838
931
|
h(
|
|
839
932
|
'button',
|
|
840
|
-
{ className: 'dsb-btn dsb-btn-primary', onClick: doBackup, disabled: !!busy ||
|
|
841
|
-
busy === 'backup' ? '备份中…' :
|
|
933
|
+
{ className: 'dsb-btn dsb-btn-primary', onClick: doBackup, disabled: !!busy || !(selected || []).length },
|
|
934
|
+
busy === 'backup' ? '备份中…' : `备份选中的 ${(selected || []).length} 个`,
|
|
842
935
|
),
|
|
843
936
|
h(
|
|
844
937
|
'button',
|
|
845
|
-
{ className: 'dsb-btn', onClick: doPull, disabled: !!busy ||
|
|
846
|
-
busy === 'pull' ? '拉取中…' :
|
|
938
|
+
{ className: 'dsb-btn', onClick: doPull, disabled: !!busy || !(selected || []).length },
|
|
939
|
+
busy === 'pull' ? '拉取中…' : `拉取选中的 ${(selected || []).length} 个`,
|
|
847
940
|
),
|
|
848
941
|
h(
|
|
849
|
-
'
|
|
850
|
-
{ className: 'dsb-
|
|
851
|
-
|
|
942
|
+
'span',
|
|
943
|
+
{ className: 'dsb-row-d' },
|
|
944
|
+
(selected || []).length
|
|
945
|
+
? `已选 ${(selected || []).length} 个工作区(只对有本机特征文件夹的生效;拉取时自动登记归属)`
|
|
946
|
+
: '请先勾选工作区:按钮只执行选中项',
|
|
852
947
|
),
|
|
853
|
-
h('span', { className: 'dsb-row-d' }, `已选 ${(selected || []).length} 个工作区`),
|
|
854
948
|
),
|
|
855
949
|
),
|
|
856
950
|
|
|
@@ -860,7 +954,7 @@ function makePanel(React) {
|
|
|
860
954
|
h(
|
|
861
955
|
'div',
|
|
862
956
|
{ className: 'dsb-card-h' },
|
|
863
|
-
h('h3', { className: 'dsb-card-t' }, '
|
|
957
|
+
h('h3', { className: 'dsb-card-t' }, '匹配的工作区(有本机特征文件夹)'),
|
|
864
958
|
ws.length
|
|
865
959
|
? h(
|
|
866
960
|
'button',
|
|
@@ -873,7 +967,11 @@ function makePanel(React) {
|
|
|
873
967
|
: null,
|
|
874
968
|
),
|
|
875
969
|
ws.length === 0
|
|
876
|
-
? h(
|
|
970
|
+
? h(
|
|
971
|
+
'div',
|
|
972
|
+
{ className: 'dsb-empty' },
|
|
973
|
+
'还没有工作区建好本机特征文件夹。在上面的巡检结果里勾选目录、点「建特征文件夹并注册」才会出现在这里。',
|
|
974
|
+
)
|
|
877
975
|
: ws.map((w) =>
|
|
878
976
|
h(
|
|
879
977
|
'div',
|
|
@@ -912,9 +1010,58 @@ function makePanel(React) {
|
|
|
912
1010
|
w.others.map((o) => `${o.machineId}:${o.sessions} 个`).join(' · '),
|
|
913
1011
|
)
|
|
914
1012
|
: null,
|
|
1013
|
+
h(
|
|
1014
|
+
'div',
|
|
1015
|
+
{ className: 'dsb-actions', style: { marginTop: 6 } },
|
|
1016
|
+
h(
|
|
1017
|
+
'button',
|
|
1018
|
+
{
|
|
1019
|
+
className: 'dsb-btn dsb-btn-sm dsb-btn-danger',
|
|
1020
|
+
disabled: !!busy,
|
|
1021
|
+
title: '删除工作区,连带清掉本机特征文件夹;目录内容与别人的备份不动',
|
|
1022
|
+
onClick: () => doDrop([w.path]),
|
|
1023
|
+
},
|
|
1024
|
+
'删除工作区',
|
|
1025
|
+
),
|
|
1026
|
+
),
|
|
915
1027
|
),
|
|
916
1028
|
),
|
|
917
1029
|
),
|
|
1030
|
+
|
|
1031
|
+
// 匹配但没建格子的:列在这里,方便随时补上
|
|
1032
|
+
(state.inactive || []).length
|
|
1033
|
+
? h(
|
|
1034
|
+
'div',
|
|
1035
|
+
{ style: { marginTop: 14 } },
|
|
1036
|
+
h(
|
|
1037
|
+
'div',
|
|
1038
|
+
{ className: 'dsb-row-t', style: { marginBottom: 6 } },
|
|
1039
|
+
`未参与同步:${state.inactive.length} 个(没有本机特征文件夹)`,
|
|
1040
|
+
),
|
|
1041
|
+
(state.inactive || []).map((w) =>
|
|
1042
|
+
h(
|
|
1043
|
+
'div',
|
|
1044
|
+
{ className: 'dsb-row', key: w.id },
|
|
1045
|
+
h(
|
|
1046
|
+
'div',
|
|
1047
|
+
{ className: 'dsb-row-main' },
|
|
1048
|
+
h('div', { className: 'dsb-row-t' }, w.title),
|
|
1049
|
+
h('div', { className: 'dsb-row-d' }, w.reason || '未建本机特征文件夹'),
|
|
1050
|
+
h('div', { className: 'dsb-row-d' }, w.path),
|
|
1051
|
+
),
|
|
1052
|
+
h(
|
|
1053
|
+
'button',
|
|
1054
|
+
{
|
|
1055
|
+
className: 'dsb-btn dsb-btn-sm',
|
|
1056
|
+
disabled: !!busy,
|
|
1057
|
+
onClick: () => doAdopt([w.path]),
|
|
1058
|
+
},
|
|
1059
|
+
'建特征文件夹',
|
|
1060
|
+
),
|
|
1061
|
+
),
|
|
1062
|
+
),
|
|
1063
|
+
)
|
|
1064
|
+
: null,
|
|
918
1065
|
),
|
|
919
1066
|
|
|
920
1067
|
// 插件清单同步(1.0.0 从自动更新器迁入)
|