dsh-mesh-ui 0.0.11 → 0.0.13
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/lib/client.js +226 -26
- package/lib/collect.js +14 -3
- package/package.json +5 -2
package/lib/client.js
CHANGED
|
@@ -678,7 +678,7 @@ body.light .dm-edge-label { stroke: rgba(255,255,255,.92); }
|
|
|
678
678
|
h('span', null, '⋯')
|
|
679
679
|
)
|
|
680
680
|
),
|
|
681
|
-
h('div', { className: 'dm-hint' }, '拓扑
|
|
681
|
+
h('div', { className: 'dm-hint' }, '拓扑 v23 · 内容 ' + Math.round(layout.W) + '×' + Math.round(layout.H) + 'px · 卡 ' + Math.round(layout.cardW) + '×' + Math.round(layout.cardH) + ' · 缩放 ' + Math.round(view.scale * 100) + '% · 滚轮缩放 / 拖拽平移')
|
|
682
682
|
)
|
|
683
683
|
}
|
|
684
684
|
|
|
@@ -1453,7 +1453,40 @@ function deviceLinkState(data, peer, lanMap) {
|
|
|
1453
1453
|
// 2026-11-10:真刷新 —— 点击后等待后台采集完成再渲染;本地冷却 + 忙锁防止连点刷爆请求
|
|
1454
1454
|
var s6 = useState(""); var refreshHint = s6[0]; var setRefreshHint = s6[1]
|
|
1455
1455
|
var forceRef = useRef({ at: 0, busy: false })
|
|
1456
|
+
// 2026-11-10:verdict 防抖 —— 单次探测失败(远端忙/超时)只显示 degraded,连续 2 次才显示 down
|
|
1457
|
+
var verdictState = useState(''); var verdictShown = verdictState[0]; var setVerdictShown = verdictState[1]
|
|
1458
|
+
var verdictRef = useRef({ raw: '', streak: 0 })
|
|
1456
1459
|
var s5 = useState('local'); var selectedKey = s5[0]; var setSelectedKey = s5[1]
|
|
1460
|
+
// 双栏宽度:拖动分隔条调整并记忆,窄屏由 CSS 媒体查询自动堆叠。
|
|
1461
|
+
var dragState = useState(function () {
|
|
1462
|
+
try { var saved = Number(window.localStorage.getItem('dsh-mesh-split-w')); return Number.isFinite(saved) && saved > 0 ? saved : 0 } catch (error) { return 0 }
|
|
1463
|
+
})
|
|
1464
|
+
var splitW = dragState[0]; var setSplitW = dragState[1]
|
|
1465
|
+
|
|
1466
|
+
function startSplit(event) {
|
|
1467
|
+
event.preventDefault()
|
|
1468
|
+
var body = event.currentTarget.parentElement
|
|
1469
|
+
if (!body) return
|
|
1470
|
+
var rect = body.getBoundingClientRect()
|
|
1471
|
+
var startW = splitW > 0 ? splitW : Math.round(rect.width * 0.62)
|
|
1472
|
+
var currentW = startW
|
|
1473
|
+
function onMove(moveEvent) {
|
|
1474
|
+
var width = Math.max(300, Math.min(moveEvent.clientX - rect.left, Math.max(300, rect.width - 360)))
|
|
1475
|
+
currentW = width
|
|
1476
|
+
setSplitW(width)
|
|
1477
|
+
}
|
|
1478
|
+
function onUp() {
|
|
1479
|
+
window.removeEventListener('pointermove', onMove)
|
|
1480
|
+
window.removeEventListener('pointerup', onUp)
|
|
1481
|
+
document.body.style.cursor = ''
|
|
1482
|
+
document.body.style.userSelect = ''
|
|
1483
|
+
try { window.localStorage.setItem('dsh-mesh-split-w', String(currentW)) } catch (error) { /* ignore */ }
|
|
1484
|
+
}
|
|
1485
|
+
document.body.style.cursor = 'col-resize'
|
|
1486
|
+
document.body.style.userSelect = 'none'
|
|
1487
|
+
window.addEventListener('pointermove', onMove)
|
|
1488
|
+
window.addEventListener('pointerup', onUp)
|
|
1489
|
+
}
|
|
1457
1490
|
|
|
1458
1491
|
function load(force) {
|
|
1459
1492
|
if (!force) {
|
|
@@ -1515,6 +1548,24 @@ function deviceLinkState(data, peer, lanMap) {
|
|
|
1515
1548
|
}, [])
|
|
1516
1549
|
|
|
1517
1550
|
useEffect(function () { if (tick > 0 && !forceRef.current.busy) load(false) }, [tick])
|
|
1551
|
+
useEffect(function () {
|
|
1552
|
+
var raw = String((data && data.verdict) || '')
|
|
1553
|
+
if (!raw) return
|
|
1554
|
+
var st = verdictRef.current
|
|
1555
|
+
var at = String((data && data.at) || '')
|
|
1556
|
+
var fresh = Boolean(at) && at !== st.lastAt
|
|
1557
|
+
if (raw === 'down') {
|
|
1558
|
+
if (fresh && !st.downAt) st.downAt = Date.now()
|
|
1559
|
+
st.lastAt = at
|
|
1560
|
+
var persisted = Boolean(st.downAt) && Date.now() - st.downAt >= 15000
|
|
1561
|
+
setVerdictShown(persisted ? 'down' : 'degraded')
|
|
1562
|
+
} else {
|
|
1563
|
+
st.downAt = 0
|
|
1564
|
+
st.lastAt = at
|
|
1565
|
+
setVerdictShown(raw)
|
|
1566
|
+
}
|
|
1567
|
+
st.raw = raw
|
|
1568
|
+
}, [data])
|
|
1518
1569
|
|
|
1519
1570
|
function copyJson() {
|
|
1520
1571
|
var text = JSON.stringify(data || {}, null, 2)
|
|
@@ -1540,7 +1591,11 @@ function deviceLinkState(data, peer, lanMap) {
|
|
|
1540
1591
|
h('style', null, CSS),
|
|
1541
1592
|
h('div', { className: 'dm-head' },
|
|
1542
1593
|
h('span', { className: 'dm-title' }, 'AI 网络 · dsh-mesh'),
|
|
1543
|
-
h('span', {
|
|
1594
|
+
h('span', {
|
|
1595
|
+
className: 'dm-pill ' + (data.warming ? 'muted' : tone(verdictShown || data.verdict)),
|
|
1596
|
+
title: data.warming ? '' : (verdictShown === 'degraded' ? '探测波动:需持续 ≥15s 且新快照仍 down 才显示 down' : ''),
|
|
1597
|
+
style: verdictShown === 'degraded' ? { opacity: 0.75 } : undefined
|
|
1598
|
+
}, data.warming ? '预热中' : (verdictShown || data.verdict || '?')),
|
|
1544
1599
|
h('span', { className: 'dm-sub' }, (data.meshVersion ? 'mesh ' + data.meshVersion + ' · ' : '') + (data.at ? ago(data.at) : '')),
|
|
1545
1600
|
h('span', { style: { flex: 1 } }),
|
|
1546
1601
|
h('a', { className: 'dm-btn', href: API, target: '_blank', rel: 'noreferrer', style: { textDecoration: 'none' } }, '原始 JSON'),
|
|
@@ -1550,23 +1605,30 @@ function deviceLinkState(data, peer, lanMap) {
|
|
|
1550
1605
|
),
|
|
1551
1606
|
error ? h('div', { className: 'dm-err' }, error) : null,
|
|
1552
1607
|
|
|
1553
|
-
h(
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1608
|
+
h('div', { className: 'dm-body', style: splitW > 0 ? { '--dm-main-w': splitW + 'px' } : undefined },
|
|
1609
|
+
h('div', { className: 'dm-pane' },
|
|
1610
|
+
h(Viewport, { data: data, layout: computeLayout(1 + filterSelfPeers(dedupePeers(data.peers || []), data.nodeName).length), selectedKey: selectedKey, onSelect: setSelectedKey }),
|
|
1611
|
+
h(InfraPanels, { data: data }),
|
|
1612
|
+
h(DeviceDetails, { data: data, selectedKey: selectedKey })
|
|
1613
|
+
),
|
|
1614
|
+
h('div', { className: 'dm-divider', onPointerDown: startSplit, title: '↔ 拖拽调整左右宽度(双击恢复默认)', onDoubleClick: function () { setSplitW(0); try { window.localStorage.removeItem('dsh-mesh-split-w') } catch (error) { /* ignore */ } } }),
|
|
1615
|
+
h('div', { className: 'dm-pane' },
|
|
1616
|
+
h(Details, { title: '本机实例', badge: ((data.local && data.local.instances) || []).length, open: true }, h(InstanceList, { data: data })),
|
|
1617
|
+
h(Details, { title: '版本一致性', badge: versions.length ? versions.length + ' 处升级' : '一致' },
|
|
1618
|
+
versions.length
|
|
1619
|
+
? versions.map(function (item, i) { return h('div', { key: i, className: 'dm-sub', style: { marginTop: 4 } }, '· ' + (typeof item === 'string' ? item : JSON.stringify(item))) })
|
|
1620
|
+
: h('div', { className: 'dm-empty' }, '所有节点/实例版本一致')
|
|
1621
|
+
),
|
|
1622
|
+
h(Details, { title: '委托台账', badge: (data.delegations && data.delegations.total) || 0 }, h(DelegationTable, { data: data })),
|
|
1623
|
+
h(Details, { title: '告警历史', badge: ((data.alerts && data.alerts.history) || []).length }, h(AlertList, { data: data })),
|
|
1624
|
+
(issues.length || hints.length) ? h(Details, { title: '诊断建议', badge: issues.length + hints.length },
|
|
1625
|
+
h('div', null,
|
|
1626
|
+
issues.slice(0, 6).map(function (issue, i) { return h('div', { key: 'i' + i, className: 'dm-err', style: { marginTop: 3 } }, '· ' + issue) }),
|
|
1627
|
+
hints.slice(0, 6).map(function (hint, i) { return h('div', { key: 'h' + i, className: 'dm-sub', style: { marginTop: 3 } }, '· ' + hint) })
|
|
1628
|
+
)
|
|
1629
|
+
) : null
|
|
1568
1630
|
)
|
|
1569
|
-
)
|
|
1631
|
+
),
|
|
1570
1632
|
h('div', { className: 'dm-foot' }, '数据源:mesh-health / host status / peer host_self / 委托台账 / 告警历史 · ' + (data.repo || '') + ' · node ' + (data.nodeVersion || '') + ' · ' + REFRESH_MS / 1000 + 's 自动刷新(只读)')
|
|
1571
1633
|
)
|
|
1572
1634
|
}
|
|
@@ -1785,6 +1847,23 @@ body[data-ds-dark-theme] .dm-dg-fs-item.active { background:rgba(88,166,255,.28)
|
|
|
1785
1847
|
)
|
|
1786
1848
|
}
|
|
1787
1849
|
|
|
1850
|
+
/** 左侧栏「AI 网络」专用图标:中心节点 + 四向设备,和「委托」区分开。 */
|
|
1851
|
+
function networkPanelIcon(props) {
|
|
1852
|
+
var size = (props && props.size) || 16
|
|
1853
|
+
return h('svg', {
|
|
1854
|
+
'data-dsh-panel-entry': 'mesh-network', viewBox: '0 0 16 16', width: size, height: size,
|
|
1855
|
+
fill: 'none', stroke: 'currentColor', strokeWidth: '1.3', strokeLinecap: 'round', 'aria-hidden': 'true',
|
|
1856
|
+
style: { color: 'var(--dsh-brand-color, currentColor)' }
|
|
1857
|
+
},
|
|
1858
|
+
h('path', { d: 'M8 8 L8 2.6 M8 8 L13.4 8 M8 8 L8 13.4 M8 8 L2.6 8', opacity: '.55' }),
|
|
1859
|
+
h('circle', { cx: 8, cy: 8, r: 2.1, fill: 'none', strokeWidth: '1.4' }),
|
|
1860
|
+
h('circle', { cx: 8, cy: 2.6, r: 1.25, fill: 'currentColor', stroke: 'none' }),
|
|
1861
|
+
h('circle', { cx: 13.4, cy: 8, r: 1.25, fill: 'currentColor', stroke: 'none' }),
|
|
1862
|
+
h('circle', { cx: 8, cy: 13.4, r: 1.25, fill: 'currentColor', stroke: 'none' }),
|
|
1863
|
+
h('circle', { cx: 2.6, cy: 8, r: 1.25, fill: 'currentColor', stroke: 'none' })
|
|
1864
|
+
)
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1788
1867
|
function MeshDelegationPanel() {
|
|
1789
1868
|
var s0 = useState(null); var list = s0[0]; var setList = s0[1]
|
|
1790
1869
|
var s1 = useState(true); var loading = s1[0]; var setLoading = s1[1]
|
|
@@ -2224,6 +2303,111 @@ body[data-ds-dark-theme] .dm-dg-fs-item.active { background:rgba(88,166,255,.28)
|
|
|
2224
2303
|
)
|
|
2225
2304
|
}
|
|
2226
2305
|
|
|
2306
|
+
// 2026-11-10:AI 网络现在是左侧栏主页面,不再受设置面板的窄容器约束。
|
|
2307
|
+
// 主区域自身没有内边距,这里给页面统一留白并限制最大阅读宽度。
|
|
2308
|
+
var PAGE_CSS = `
|
|
2309
|
+
.dm-page { box-sizing:border-box; width:100%; height:100%; overflow:hidden; padding:clamp(16px, 2.2vw, 30px) clamp(18px, 2.6vw, 42px) 0; }
|
|
2310
|
+
.dm-content-shell { width:100%; height:100%; position:relative; --dm-content-w:920px; }
|
|
2311
|
+
.dm-content-scroll { height:100%; overflow:auto; padding-bottom:36px; scrollbar-gutter:stable; }
|
|
2312
|
+
.dm-content { width:min(100%, var(--dm-content-w)); max-width:100%; margin:0 auto; box-sizing:border-box; container-type:inline-size; container-name:dm-content; }
|
|
2313
|
+
.dm-content > .dm-root { width:100%; max-width:100%; margin:0 auto; }
|
|
2314
|
+
/* 与 DSH 对话区一致的宽度手柄:位于内容两侧外 24px,平时隐藏,hover/拖拽显示 2px 渐变线 */
|
|
2315
|
+
.dm-width-handle { z-index:0; position:absolute; top:0; bottom:0; cursor:col-resize; width:clamp(0px, calc((100% - var(--dm-content-w)) / 2 - 48px), 10px); }
|
|
2316
|
+
.dm-width-handle[data-side="left"] { right:calc(50% + var(--dm-content-w) / 2 + 24px); }
|
|
2317
|
+
.dm-width-handle[data-side="right"] { left:calc(50% + var(--dm-content-w) / 2 + 24px); }
|
|
2318
|
+
.dm-width-handle::after { content:''; pointer-events:none; opacity:0; position:absolute; top:0; bottom:0; width:2px; border-radius:2px; background:linear-gradient(to bottom, transparent calc(var(--dm-width-handle-pointer-y, 50%) - 36px), var(--dsw-alias-scrollbar-bg-l1, rgba(128,128,128,.55)) calc(var(--dm-width-handle-pointer-y, 50%) - 8px), var(--dsw-alias-scrollbar-bg-l1, rgba(128,128,128,.55)) calc(var(--dm-width-handle-pointer-y, 50%) + 8px), transparent calc(var(--dm-width-handle-pointer-y, 50%) + 36px)); }
|
|
2319
|
+
.dm-width-handle[data-side="left"]::after { right:4px; }
|
|
2320
|
+
.dm-width-handle[data-side="right"]::after { left:4px; }
|
|
2321
|
+
.dm-width-handle[data-dragging]::after { opacity:1; }
|
|
2322
|
+
@media (hover:hover) { .dm-width-handle:hover::after { opacity:1; } }
|
|
2323
|
+
@media (max-width: 1000px) { .dm-width-handle { display:none; } }
|
|
2324
|
+
.dm-body { display:grid; grid-template-columns:minmax(300px, var(--dm-main-w, 62%)) 8px minmax(0,1fr); gap:10px; align-items:start; }
|
|
2325
|
+
.dm-pane { min-width:0; display:flex; flex-direction:column; gap:12px; }
|
|
2326
|
+
.dm-divider { cursor:col-resize; align-self:stretch; min-height:120px; border-radius:4px; position:relative; }
|
|
2327
|
+
.dm-divider::before { content:''; position:absolute; top:0; bottom:0; left:3px; width:1px; background:rgba(128,128,128,.28); }
|
|
2328
|
+
.dm-divider:hover::before, .dm-divider:active::before { background:rgba(88,166,255,.7); width:2px; left:2.5px; }
|
|
2329
|
+
@container dm-content (max-width: 860px) { .dm-body { grid-template-columns:1fr; } .dm-divider { display:none; } }
|
|
2330
|
+
`
|
|
2331
|
+
function MeshNetworkPage() {
|
|
2332
|
+
// 宽度偏好:与 DSH 对话区相同语义——空 = 默认宽度,数字 = 用户拖拽固定值。
|
|
2333
|
+
var prefState = useState(function () {
|
|
2334
|
+
try { var saved = Number(window.localStorage.getItem('dsh-mesh.contentWidth')); return Number.isFinite(saved) && saved > 0 ? saved : 0 } catch (error) { return 0 }
|
|
2335
|
+
})
|
|
2336
|
+
var pref = prefState[0]; var setPref = prefState[1]
|
|
2337
|
+
var shellState = useState(0); var containerW = shellState[0]; var setContainerW = shellState[1]
|
|
2338
|
+
var shellRef = useRef(null)
|
|
2339
|
+
|
|
2340
|
+
useEffect(function () {
|
|
2341
|
+
var el = shellRef.current
|
|
2342
|
+
if (!el) return
|
|
2343
|
+
function measure() { setContainerW(el.clientWidth || 0) }
|
|
2344
|
+
measure()
|
|
2345
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
2346
|
+
var observer = new ResizeObserver(measure)
|
|
2347
|
+
observer.observe(el)
|
|
2348
|
+
return function () { observer.disconnect() }
|
|
2349
|
+
}
|
|
2350
|
+
window.addEventListener('resize', measure)
|
|
2351
|
+
return function () { window.removeEventListener('resize', measure) }
|
|
2352
|
+
}, [])
|
|
2353
|
+
|
|
2354
|
+
// DSH 对话区公式:默认 clamp(680, 64% 容器, 920);用户值 [640, 容器-176]。
|
|
2355
|
+
var widthNow = containerW || 920
|
|
2356
|
+
var maxW = Math.max(640, widthNow - 176)
|
|
2357
|
+
var contentW = pref > 0 ? Math.min(Math.max(pref, 640), maxW) : Math.max(680, Math.min(widthNow * 0.64, 920))
|
|
2358
|
+
|
|
2359
|
+
function beginWidthDrag(side, event) {
|
|
2360
|
+
event.preventDefault()
|
|
2361
|
+
var handle = event.currentTarget
|
|
2362
|
+
var shell = handle.closest ? handle.closest('.dm-content-shell') : (handle.parentElement && handle.parentElement.parentElement)
|
|
2363
|
+
if (!shell) return
|
|
2364
|
+
var max = Math.max(640, (shell.clientWidth || 920) - 176)
|
|
2365
|
+
var base = contentW
|
|
2366
|
+
var originX = event.clientX
|
|
2367
|
+
var latest = contentW
|
|
2368
|
+
var firstBox = handle.getBoundingClientRect()
|
|
2369
|
+
handle.style.setProperty('--dm-width-handle-pointer-y', (event.clientY - firstBox.top) + 'px')
|
|
2370
|
+
handle.toggleAttribute('data-dragging', true)
|
|
2371
|
+
document.body.style.cursor = 'col-resize'
|
|
2372
|
+
document.body.style.userSelect = 'none'
|
|
2373
|
+
function onMove(moveEvent) {
|
|
2374
|
+
var dx = moveEvent.clientX - originX
|
|
2375
|
+
var outward = side === 'right' ? dx : -dx
|
|
2376
|
+
// 只有拖拽时才更新高亮位置(与 DSH 一致);hover 时保持手柄高度的 50%。
|
|
2377
|
+
var box = handle.getBoundingClientRect()
|
|
2378
|
+
handle.style.setProperty('--dm-width-handle-pointer-y', (moveEvent.clientY - box.top) + 'px')
|
|
2379
|
+
// 两侧同时外扩/内收,所以内容宽度变化是手柄位移的 2 倍(与 DSH 一致)。
|
|
2380
|
+
latest = Math.min(Math.max(base + outward * 2, 640), max)
|
|
2381
|
+
setPref(latest)
|
|
2382
|
+
}
|
|
2383
|
+
function onUp() {
|
|
2384
|
+
handle.toggleAttribute('data-dragging', false)
|
|
2385
|
+
window.removeEventListener('pointermove', onMove)
|
|
2386
|
+
window.removeEventListener('pointerup', onUp)
|
|
2387
|
+
document.body.style.cursor = ''
|
|
2388
|
+
document.body.style.userSelect = ''
|
|
2389
|
+
try { window.localStorage.setItem('dsh-mesh.contentWidth', String(latest)) } catch (error) { /* ignore */ }
|
|
2390
|
+
}
|
|
2391
|
+
window.addEventListener('pointermove', onMove)
|
|
2392
|
+
window.addEventListener('pointerup', onUp)
|
|
2393
|
+
}
|
|
2394
|
+
function resetContentWidth() {
|
|
2395
|
+
setPref(0)
|
|
2396
|
+
try { window.localStorage.removeItem('dsh-mesh.contentWidth') } catch (error) { /* ignore */ }
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
return h('div', { className: 'dm-page' },
|
|
2400
|
+
h('style', null, PAGE_CSS),
|
|
2401
|
+
h('div', { className: 'dm-content-shell', ref: shellRef, style: { '--dm-content-w': contentW + 'px' } },
|
|
2402
|
+
h('div', { className: 'dm-width-handle', 'data-side': 'left', title: '拖拽调整内容宽度(双击恢复默认)', onPointerDown: function (event) { beginWidthDrag('left', event) }, onDoubleClick: resetContentWidth }),
|
|
2403
|
+
h('div', { className: 'dm-width-handle', 'data-side': 'right', title: '拖拽调整内容宽度(双击恢复默认)', onPointerDown: function (event) { beginWidthDrag('right', event) }, onDoubleClick: resetContentWidth }),
|
|
2404
|
+
h('div', { className: 'dm-content-scroll' },
|
|
2405
|
+
h('div', { className: 'dm-content', style: { width: 'min(100%, ' + contentW + 'px)' } }, h(MeshStatusSection))
|
|
2406
|
+
)
|
|
2407
|
+
)
|
|
2408
|
+
)
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2227
2411
|
var SECTION = { name: 'settings.section', id: 'dsh-mesh', order: 90, label: 'AI 网络' }
|
|
2228
2412
|
var inject = ['slots']
|
|
2229
2413
|
|
|
@@ -2265,12 +2449,22 @@ body[data-ds-dark-theme] .dm-dg-fs-item.active { background:rgba(88,166,255,.28)
|
|
|
2265
2449
|
var button = buttons[i]
|
|
2266
2450
|
var text = (button.textContent || '').trim()
|
|
2267
2451
|
if (text !== 'AI 网络' && text !== 'AI 网络(dsh-mesh)') continue
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
var
|
|
2271
|
-
var
|
|
2272
|
-
|
|
2273
|
-
|
|
2452
|
+
// 0.2.2 的默认图标可能包在 span 里;在默认图标的父容器里原地替换,
|
|
2453
|
+
// 保留间距/对齐,并清掉所有旧 SVG,避免齿轮 + 自绘图标双图标。
|
|
2454
|
+
var existingIcon = button.querySelector('svg[data-dsh-mesh-nav-icon]')
|
|
2455
|
+
var old = []
|
|
2456
|
+
var all = button.querySelectorAll('svg')
|
|
2457
|
+
for (var j = 0; j < all.length; j++) if (all[j] !== existingIcon) old.push(all[j])
|
|
2458
|
+
var reference = old[0] || null
|
|
2459
|
+
var parent = reference ? reference.parentNode : button
|
|
2460
|
+
var className = reference ? reference.getAttribute('class') || '' : ''
|
|
2461
|
+
if (!existingIcon) {
|
|
2462
|
+
var icon = navMeshIconDom(className)
|
|
2463
|
+
if (reference) parent.insertBefore(icon, reference)
|
|
2464
|
+
else if (button.firstChild) button.insertBefore(icon, button.firstChild)
|
|
2465
|
+
else button.appendChild(icon)
|
|
2466
|
+
}
|
|
2467
|
+
for (var k = 0; k < old.length; k++) if (old[k].parentNode) old[k].parentNode.removeChild(old[k])
|
|
2274
2468
|
}
|
|
2275
2469
|
} catch (error) {
|
|
2276
2470
|
/* ignore */
|
|
@@ -2279,10 +2473,16 @@ body[data-ds-dark-theme] .dm-dg-fs-item.active { background:rgba(88,166,255,.28)
|
|
|
2279
2473
|
|
|
2280
2474
|
function apply(ctx) {
|
|
2281
2475
|
var disposers = []
|
|
2476
|
+
// 2026-11-10:AI 网络从「设置」迁到左侧栏独立页面,使用更大的主区域。
|
|
2282
2477
|
try {
|
|
2283
|
-
disposers.push(ctx.slots.inject('
|
|
2478
|
+
disposers.push(ctx.slots.inject('sidebar.panellist', function () {
|
|
2479
|
+
return ctx.slots.register({ name: 'sidebar.panellist', id: 'mesh-network', order: 54, label: function () { return 'AI 网络' } }, networkPanelIcon)
|
|
2480
|
+
}))
|
|
2481
|
+
disposers.push(ctx.slots.inject('main', function () {
|
|
2482
|
+
return ctx.slots.register({ name: 'main', key: 'mesh-network', inject: function () { return {} } }, MeshNetworkPage)
|
|
2483
|
+
}))
|
|
2284
2484
|
} catch (error) {
|
|
2285
|
-
console.warn('[dsh-mesh-ui]
|
|
2485
|
+
console.warn('[dsh-mesh-ui] sidebar network page registration failed:', error)
|
|
2286
2486
|
}
|
|
2287
2487
|
// 2026-11-07 方案 A:左侧栏「委托」面板(sidebar.panellist + main)
|
|
2288
2488
|
try {
|
package/lib/collect.js
CHANGED
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
|
|
16
16
|
import { execFile } from 'node:child_process'
|
|
17
17
|
import { existsSync, readdirSync, readFileSync, statfsSync, writeFileSync } from 'node:fs'
|
|
18
|
+
import { createRequire } from 'node:module'
|
|
18
19
|
import { lookup as dnsLookup } from 'node:dns/promises'
|
|
19
20
|
import { connect as netConnect } from 'node:net'
|
|
20
21
|
import { arch as osArch, cpus as osCpus, freemem as osFreemem, homedir, platform as osPlatform, release as osRelease, totalmem as osTotalmem } from 'node:os'
|
|
21
|
-
import { join } from 'node:path'
|
|
22
|
+
import { dirname, join } from 'node:path'
|
|
22
23
|
|
|
23
24
|
const CLI_TIMEOUT_MS = 25_000
|
|
24
25
|
const ALERT_HISTORY_MAX = 12
|
|
@@ -29,9 +30,19 @@ export function meshHome() {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export function resolveRepo() {
|
|
33
|
+
// 1) 显式 DSH_MESH_REPO / 常见开发目录(源码/本地仓库)
|
|
32
34
|
const candidates = [process.env.DSH_MESH_REPO, join(homedir(), 'Code', 'dsh-mesh'), join(homedir(), 'Desktop', 'dsh-mesh')].filter(Boolean)
|
|
33
35
|
for (const candidate of candidates) {
|
|
34
|
-
if (existsSync(join(candidate, 'scripts', 'mesh-health.mjs'))) return candidate
|
|
36
|
+
if (existsSync(join(candidate, 'scripts', 'mesh-health.mjs')) && existsSync(join(candidate, 'src', 'cli.js'))) return candidate
|
|
37
|
+
}
|
|
38
|
+
// 2) 用户正常安装路径:dsh-mesh-ui 声明了 dsh-mesh-node 依赖,
|
|
39
|
+
// 直接解析已安装的 npm 包(随包带 scripts/mesh-health.mjs)。
|
|
40
|
+
try {
|
|
41
|
+
const requireFromPlugin = createRequire(import.meta.url)
|
|
42
|
+
const nodeRoot = dirname(requireFromPlugin.resolve('dsh-mesh-node/package.json'))
|
|
43
|
+
if (existsSync(join(nodeRoot, 'scripts', 'mesh-health.mjs')) && existsSync(join(nodeRoot, 'src', 'cli.js'))) return nodeRoot
|
|
44
|
+
} catch {
|
|
45
|
+
/* package not installed next to this plugin */
|
|
35
46
|
}
|
|
36
47
|
return null
|
|
37
48
|
}
|
|
@@ -391,7 +402,7 @@ async function overviewData(repo, force = false) {
|
|
|
391
402
|
*/
|
|
392
403
|
export async function collect(repo) {
|
|
393
404
|
const generatedAt = new Date().toISOString()
|
|
394
|
-
if (!repo) return { ok: false, error: 'dsh-mesh
|
|
405
|
+
if (!repo) return { ok: false, error: 'dsh-mesh-node is not installed for dsh-mesh-ui (install it with `npm i -g dsh-mesh-node`, or set DSH_MESH_REPO to a dsh-mesh checkout)', generatedAt }
|
|
395
406
|
|
|
396
407
|
const meshPromise = meshHealthJson(repo)
|
|
397
408
|
const overviewPromise = overviewData(repo)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mesh-ui",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "DSH
|
|
3
|
+
"version": "0.0.13",
|
|
4
|
+
"description": "DSH sidebar page for dsh-mesh: AI network topology, peer health, and delegation panel. Read-only; data is collected from the installed dsh-mesh-node package.",
|
|
5
5
|
"icon": "icon.svg",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
"platform": "web"
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"dsh-mesh-node": "0.0.12"
|
|
32
|
+
},
|
|
30
33
|
"peerDependencies": {
|
|
31
34
|
"@deepseek-ai/dsh": ">=0.2.0-rc.2 <0.3.0",
|
|
32
35
|
"react": "^18.2.0",
|