dsh-mesh-ui 0.0.12 → 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.
Files changed (2) hide show
  1. package/lib/client.js +177 -18
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -1457,6 +1457,36 @@ function deviceLinkState(data, peer, lanMap) {
1457
1457
  var verdictState = useState(''); var verdictShown = verdictState[0]; var setVerdictShown = verdictState[1]
1458
1458
  var verdictRef = useRef({ raw: '', streak: 0 })
1459
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
+ }
1460
1490
 
1461
1491
  function load(force) {
1462
1492
  if (!force) {
@@ -1575,23 +1605,30 @@ function deviceLinkState(data, peer, lanMap) {
1575
1605
  ),
1576
1606
  error ? h('div', { className: 'dm-err' }, error) : null,
1577
1607
 
1578
- h(Viewport, { data: data, layout: computeLayout(1 + filterSelfPeers(dedupePeers(data.peers || []), data.nodeName).length), selectedKey: selectedKey, onSelect: setSelectedKey }),
1579
- h(InfraPanels, { data: data }),
1580
- h(DeviceDetails, { data: data, selectedKey: selectedKey }),
1581
- h(Details, { title: '本机实例', badge: ((data.local && data.local.instances) || []).length, open: true }, h(InstanceList, { data: data })),
1582
- h(Details, { title: '版本一致性', badge: versions.length ? versions.length + ' 处升级' : '一致' },
1583
- versions.length
1584
- ? versions.map(function (item, i) { return h('div', { key: i, className: 'dm-sub', style: { marginTop: 4 } }, '· ' + (typeof item === 'string' ? item : JSON.stringify(item))) })
1585
- : h('div', { className: 'dm-empty' }, '所有节点/实例版本一致')
1586
- ),
1587
- h(Details, { title: '委托台账', badge: (data.delegations && data.delegations.total) || 0 }, h(DelegationTable, { data: data })),
1588
- h(Details, { title: '告警历史', badge: ((data.alerts && data.alerts.history) || []).length }, h(AlertList, { data: data })),
1589
- (issues.length || hints.length) ? h(Details, { title: '诊断建议', badge: issues.length + hints.length },
1590
- h('div', null,
1591
- issues.slice(0, 6).map(function (issue, i) { return h('div', { key: 'i' + i, className: 'dm-err', style: { marginTop: 3 } }, '· ' + issue) }),
1592
- hints.slice(0, 6).map(function (hint, i) { return h('div', { key: 'h' + i, className: 'dm-sub', style: { marginTop: 3 } }, '· ' + hint) })
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
1593
1630
  )
1594
- ) : null,
1631
+ ),
1595
1632
  h('div', { className: 'dm-foot' }, '数据源:mesh-health / host status / peer host_self / 委托台账 / 告警历史 · ' + (data.repo || '') + ' · node ' + (data.nodeVersion || '') + ' · ' + REFRESH_MS / 1000 + 's 自动刷新(只读)')
1596
1633
  )
1597
1634
  }
@@ -1810,6 +1847,23 @@ body[data-ds-dark-theme] .dm-dg-fs-item.active { background:rgba(88,166,255,.28)
1810
1847
  )
1811
1848
  }
1812
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
+
1813
1867
  function MeshDelegationPanel() {
1814
1868
  var s0 = useState(null); var list = s0[0]; var setList = s0[1]
1815
1869
  var s1 = useState(true); var loading = s1[0]; var setLoading = s1[1]
@@ -2249,6 +2303,111 @@ body[data-ds-dark-theme] .dm-dg-fs-item.active { background:rgba(88,166,255,.28)
2249
2303
  )
2250
2304
  }
2251
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
+
2252
2411
  var SECTION = { name: 'settings.section', id: 'dsh-mesh', order: 90, label: 'AI 网络' }
2253
2412
  var inject = ['slots']
2254
2413
 
@@ -2317,10 +2476,10 @@ body[data-ds-dark-theme] .dm-dg-fs-item.active { background:rgba(88,166,255,.28)
2317
2476
  // 2026-11-10:AI 网络从「设置」迁到左侧栏独立页面,使用更大的主区域。
2318
2477
  try {
2319
2478
  disposers.push(ctx.slots.inject('sidebar.panellist', function () {
2320
- return ctx.slots.register({ name: 'sidebar.panellist', id: 'mesh-network', order: 54, label: function () { return 'AI 网络' } }, meshPanelIcon)
2479
+ return ctx.slots.register({ name: 'sidebar.panellist', id: 'mesh-network', order: 54, label: function () { return 'AI 网络' } }, networkPanelIcon)
2321
2480
  }))
2322
2481
  disposers.push(ctx.slots.inject('main', function () {
2323
- return ctx.slots.register({ name: 'main', key: 'mesh-network', inject: function () { return {} } }, MeshStatusSection)
2482
+ return ctx.slots.register({ name: 'main', key: 'mesh-network', inject: function () { return {} } }, MeshNetworkPage)
2324
2483
  }))
2325
2484
  } catch (error) {
2326
2485
  console.warn('[dsh-mesh-ui] sidebar network page registration failed:', error)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mesh-ui",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
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",