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 +255 -119
- package/index.js +113 -31
- package/lib/api.js +306 -35
- package/lib/core.js +42 -1
- package/lib/features.js +409 -0
- package/package.json +62 -61
- 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,78 @@ 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
|
+
* 删本机特征文件夹:本机不再参与这个工作区的同步。
|
|
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:
|
|
241
|
-
text:
|
|
242
|
-
|
|
243
|
-
|
|
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
|
|
278
|
-
|
|
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:
|
|
281
|
-
text:
|
|
282
|
-
?
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (!
|
|
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:
|
|
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
|
-
?
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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-
|
|
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
|
-
}),
|
|
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
|
|
724
|
-
h('
|
|
725
|
-
|
|
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
|
-
'
|
|
734
|
-
{
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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
|
-
'
|
|
743
|
-
{
|
|
744
|
-
|
|
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
|
-
|
|
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
|
-
)
|
|
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 ||
|
|
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 ||
|
|
846
|
-
busy === 'pull' ? '拉取中…' :
|
|
927
|
+
{ className: 'dsb-btn', onClick: doPull, disabled: !!busy || !(selected || []).length },
|
|
928
|
+
busy === 'pull' ? '拉取中…' : `拉取选中的 ${(selected || []).length} 个`,
|
|
847
929
|
),
|
|
848
930
|
h(
|
|
849
|
-
'
|
|
850
|
-
{ className: 'dsb-
|
|
851
|
-
|
|
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(
|
|
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 从自动更新器迁入)
|