dsh-mesh-ui 0.0.17 → 0.0.18

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 +126 -28
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -313,12 +313,22 @@ body.light .dm-edge-label { stroke: rgba(255,255,255,.92); }
313
313
  .dm-details > summary::-webkit-details-marker { display:none; }
314
314
  .dm-details > summary::before { content:'▸'; font-size:10px; opacity:.5; transition:transform .12s; }
315
315
  .dm-details[open] > summary::before { transform:rotate(90deg); }
316
- .dm-details > .dm-body { padding:0 12px 11px; }
316
+ .dm-details > .dm-details-body { padding:0 12px 11px; }
317
317
  .dm-table { width:100%; border-collapse:collapse; }
318
318
  .dm-table th { text-align:left; font-weight:600; font-size:11px; opacity:.5; padding:2px 8px 5px 0; border-bottom:1px solid rgba(128,128,128,.2); white-space:nowrap; }
319
319
  .dm-table td { padding:5px 8px 5px 0; border-bottom:1px solid rgba(128,128,128,.1); font-size:12px; }
320
320
  .dm-table tr:last-child td { border-bottom:none; }
321
- .dm-trunc { max-width:190px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; display:inline-block; vertical-align:bottom; }
321
+ /* 2026-11-10:委托台账表改为固定列宽 + 单行省略;非固定布局下 5 列会把「节点/任务」压到
322
+ 每行一个字,就是用户看到的竖排。完整内容看 title 悬浮提示。 */
323
+ .dm-details .dm-table { table-layout:fixed; min-width:420px; }
324
+ .dm-table-wrap { overflow-x:auto; }
325
+ .dm-details .dm-table th, .dm-details .dm-table td { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
326
+ .dm-details .dm-table th:nth-child(1), .dm-details .dm-table td:nth-child(1) { width:16%; }
327
+ .dm-details .dm-table th:nth-child(2), .dm-details .dm-table td:nth-child(2) { width:18%; }
328
+ .dm-details .dm-table th:nth-child(3), .dm-details .dm-table td:nth-child(3) { width:22%; }
329
+ .dm-details .dm-table th:nth-child(4), .dm-details .dm-table td:nth-child(4) { width:10%; }
330
+ /* 目标列不写死百分比:让它吃掉剩余空间(单元格右内边距不参与百分比,写死 34% 会溢出 1 条横向滚动条) */
331
+ .dm-trunc { max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; display:inline-block; vertical-align:bottom; }
322
332
  .dm-instrow { display:flex; align-items:center; justify-content:space-between; gap:10px; padding:6px 0; border-bottom:1px solid rgba(128,128,128,.1); }
323
333
  .dm-instrow:last-child { border-bottom:none; }
324
334
  .dm-alertrow { display:flex; gap:8px; align-items:baseline; padding:5px 0; border-bottom:1px dashed rgba(128,128,128,.16); font-size:12px; }
@@ -1441,7 +1451,9 @@ function deviceLinkState(data, peer, lanMap) {
1441
1451
  }
1442
1452
  return h('details', { className: 'dm-details', open: open, onToggle: onToggle },
1443
1453
  h('summary', null, props.title, props.badge != null ? h('span', { className: 'dm-pill muted' }, String(props.badge)) : null),
1444
- h('div', { className: 'dm-body' }, props.children)
1454
+ // 2026-11-10:折叠面板正文用独立容器整栏铺满;原先借用 .dm-body(62% 双栏 grid)
1455
+ // 会把「委托台账」这类单列表格压到 300px,中文被逐字折成竖排。
1456
+ h('div', { className: 'dm-details-body' }, props.children)
1445
1457
  )
1446
1458
  }
1447
1459
 
@@ -1472,17 +1484,20 @@ function deviceLinkState(data, peer, lanMap) {
1472
1484
  if (!list.length) return h('div', { className: 'dm-empty' }, '暂无委托记录')
1473
1485
  return h('div', null,
1474
1486
  h('div', { className: 'dm-sub', style: { marginBottom: 5 } }, '共 ' + (d.total || 0) + ' 条 · ' + Object.keys(stats).map(function (k) { return k + ' ' + stats[k] }).join(' · ')),
1475
- h('table', { className: 'dm-table' },
1476
- h('thead', null, h('tr', null, h('th', null, '状态'), h('th', null, '节点'), h('th', null, '任务'), h('th', null, '耗时'), h('th', null, '目标'))),
1477
- h('tbody', null, list.map(function (task) {
1478
- return h('tr', { key: task.taskId },
1479
- h('td', null, stateChip(task.state)),
1480
- h('td', null, nodeLabel(task.node)),
1481
- h('td', { className: 'dm-mono', style: { fontSize: 11 } }, String(task.taskId || '').replace(/^T-\d{8}-/, 'T-…')),
1482
- h('td', { className: 'dm-mono' }, dur(task.durationSec)),
1483
- h('td', null, h('span', { className: 'dm-trunc', title: task.goal }, task.goal || ''))
1484
- )
1485
- }))
1487
+ // 2026-11-10:外层给横向滚动兜底——栏窄到连固定列宽都放不下时滚动,而不是压缩成竖排。
1488
+ h('div', { className: 'dm-table-wrap' },
1489
+ h('table', { className: 'dm-table' },
1490
+ h('thead', null, h('tr', null, h('th', null, '状态'), h('th', null, '节点'), h('th', null, '任务'), h('th', null, '耗时'), h('th', null, '目标'))),
1491
+ h('tbody', null, list.map(function (task) {
1492
+ return h('tr', { key: task.taskId },
1493
+ h('td', null, stateChip(task.state)),
1494
+ h('td', { title: nodeLabel(task.node) }, nodeLabel(task.node)),
1495
+ h('td', { className: 'dm-mono', style: { fontSize: 11 }, title: String(task.taskId || '') }, String(task.taskId || '').replace(/^T-\d{8}-/, 'T-…')),
1496
+ h('td', { className: 'dm-mono' }, dur(task.durationSec)),
1497
+ h('td', null, h('span', { className: 'dm-trunc', title: task.goal }, task.goal || ''))
1498
+ )
1499
+ }))
1500
+ )
1486
1501
  )
1487
1502
  )
1488
1503
  }
@@ -2441,17 +2456,26 @@ body[data-ds-dark-theme] .dm-dg-fs-item.active { background:rgba(88,166,255,.28)
2441
2456
  }
2442
2457
 
2443
2458
  // 2026-11-10:AI 网络现在是左侧栏主页面,不再受设置面板的窄容器约束。
2444
- // 主区域自身没有内边距,这里给页面统一留白并限制最大阅读宽度。
2459
+ // 2026-11-10(二次):内边距从 .dm-page 挪到滚动容器上——滚动条属于滚动容器,只有在滚动容器
2460
+ // 铺满整页时才会像 DSH 对话区那样贴到最右边;内容留白靠滚动容器自己的 padding 提供。
2445
2461
  var PAGE_CSS = `
2446
- .dm-page { box-sizing:border-box; width:100%; height:100%; overflow:hidden; padding:clamp(16px, 2.2vw, 30px) clamp(18px, 2.6vw, 42px) 0; }
2462
+ .dm-page { box-sizing:border-box; width:100%; height:100%; overflow:hidden; padding:0; --dm-pad-x:clamp(18px, 2.6vw, 42px); --dm-pad-y:clamp(16px, 2.2vw, 30px); --dm-scroll-inset:2px; }
2447
2463
  .dm-content-shell { width:100%; height:100%; position:relative; --dm-content-w:920px; }
2448
- .dm-content-scroll { height:100%; overflow:auto; padding-bottom:36px; scrollbar-gutter:stable; }
2464
+ /* 纵向滚动条:对齐 DSH 对话区 wSkVaW_scrollBody 的观感——滚动容器 margin-right:2px
2465
+ (滚动条离面板边 2px,不贴死),滑块 track 留 2px(滑块上下不顶到边)。
2466
+ 滚动条只占右侧会把内容挤得偏左,所以左侧补等宽内边距(--dm-sb-pad)。 */
2467
+ /* border-box 必须加:height:100% + 上下 padding 否则把滚动容器撑出面板高度,
2468
+ 底部 padding 和页脚会被 .dm-page 的 overflow:hidden 裁掉(2026-11-10 实测 1051 > 985)。 */
2469
+ .dm-content-scroll { box-sizing:border-box; height:100%; overflow:auto; scrollbar-gutter:stable; margin-right:var(--dm-scroll-inset); padding:var(--dm-pad-y) var(--dm-pad-x) 36px; padding-left:calc(var(--dm-pad-x) + var(--dm-sb-pad, 0px) + var(--dm-scroll-inset)); }
2470
+ .dm-content-scroll::-webkit-scrollbar-track { margin:2px; }
2449
2471
  .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; }
2450
2472
  .dm-content > .dm-root { width:100%; max-width:100%; margin:0 auto; }
2451
- /* 与 DSH 对话区一致的宽度手柄:位于内容两侧外 24px,平时隐藏,hover/拖拽显示 2px 渐变线 */
2452
- .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); }
2453
- .dm-width-handle[data-side="left"] { right:calc(50% + var(--dm-content-w) / 2 + 24px); }
2454
- .dm-width-handle[data-side="right"] { left:calc(50% + var(--dm-content-w) / 2 + 24px); }
2473
+ /* 宽度手柄:位置由 JS 实测的「内容边距 + 页面留白」算出高亮线中心点,写进 --dm-bar-l / --dm-bar-r。
2474
+ 高亮线在 10px 手柄内偏内侧 5px(::after 4px + 2px 线宽的一半),所以手柄 = 中心点 ∓ 5px。
2475
+ 这样留白窄时(拖到最大)高亮线自动居中在「内容边 ↔ 页面边」中间,留白宽时保持 24px 间隙。 */
2476
+ .dm-width-handle { z-index:1; position:absolute; top:0; bottom:0; cursor:col-resize; width:10px; }
2477
+ .dm-width-handle[data-side="left"] { left:calc(var(--dm-bar-l, -100px) - 5px); }
2478
+ .dm-width-handle[data-side="right"] { right:calc(var(--dm-bar-r, -100px) - 5px); }
2455
2479
  .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)); }
2456
2480
  .dm-width-handle[data-side="left"]::after { right:4px; }
2457
2481
  .dm-width-handle[data-side="right"]::after { left:4px; }
@@ -2481,12 +2505,29 @@ body[data-ds-dark-theme] .dm-dg-fs-item.active { background:rgba(88,166,255,.28)
2481
2505
  })
2482
2506
  var pref = prefState[0]; var setPref = prefState[1]
2483
2507
  var shellState = useState(0); var containerW = shellState[0]; var setContainerW = shellState[1]
2508
+ // 滚动条实际占位宽度(px):左侧补等宽内边距,让内容在 shell 上真正居中。
2509
+ var sbState = useState(0); var sbPad = sbState[0]; var setSbPad = sbState[1]
2510
+ // 手柄几何:内容元素到 shell 左右边的实测 px(null = 还没量到)。
2511
+ var geoState = useState(null); var geo = geoState[0]; var setGeo = geoState[1]
2484
2512
  var shellRef = useRef(null)
2485
2513
 
2514
+ // 2026-11-10:宽度基准 =「内容可用宽度」= 滚动容器内容盒宽(clientWidth - 左右 padding);
2515
+ // 滚动条贴页面右边,其占位由 CSS 在左侧补等宽 padding 抵消(--dm-sb-pad)。
2486
2516
  useEffect(function () {
2487
2517
  var el = shellRef.current
2488
2518
  if (!el) return
2489
- function measure() { setContainerW(el.clientWidth || 0) }
2519
+ function measure() {
2520
+ var scroller = el.querySelector('.dm-content-scroll')
2521
+ if (!scroller) { setContainerW(el.clientWidth || 0); return }
2522
+ var sb = Math.max(0, (scroller.offsetWidth || 0) - (scroller.clientWidth || 0))
2523
+ var scs = getComputedStyle(scroller)
2524
+ var padL = parseFloat(scs.paddingLeft) || 0
2525
+ var padR = parseFloat(scs.paddingRight) || 0
2526
+ // 内容盒宽 = clientWidth - 实际左右 padding(左侧含滚动条补偿与 2px 滚动条外留白)
2527
+ var avail = Math.max(0, (scroller.clientWidth || 0) - padL - padR)
2528
+ setContainerW(avail)
2529
+ setSbPad(function (prev) { return Math.abs(prev - sb) >= 0.5 ? sb : prev })
2530
+ }
2490
2531
  measure()
2491
2532
  if (typeof ResizeObserver !== 'undefined') {
2492
2533
  var observer = new ResizeObserver(measure)
@@ -2495,19 +2536,67 @@ body[data-ds-dark-theme] .dm-dg-fs-item.active { background:rgba(88,166,255,.28)
2495
2536
  }
2496
2537
  window.addEventListener('resize', measure)
2497
2538
  return function () { window.removeEventListener('resize', measure) }
2498
- }, [])
2539
+ }, [sbPad])
2499
2540
 
2500
- // DSH 对话区公式:默认 clamp(680, 64% 容器, 920);用户值 [640, 容器-176]。
2541
+ // 手柄定位:高亮线在「内容边 ↔ 页面边」的留白里自动居中(留白宽时退回 24px 间隙)。
2542
+ // 滚动容器已铺满整页,所以内容到 shell 左右边的距离就是完整留白(右侧含滚动条)。 */
2543
+ useEffect(function () {
2544
+ var el = shellRef.current
2545
+ if (!el) return
2546
+ var raf = 0
2547
+ function sync() {
2548
+ var content = el.querySelector('.dm-content')
2549
+ if (!content) return
2550
+ var sr = el.getBoundingClientRect()
2551
+ var cr = content.getBoundingClientRect()
2552
+ // 留白量到 shell(页面)边:滚动条外那 2px 也算留白,高亮线才居中在视觉留白里
2553
+ var offL = cr.left - sr.left
2554
+ var offR = sr.right - cr.right
2555
+ // 高亮线中心到内容边的距离:留白的一半,但夹在 [12, 29](29 = 24px 间隙 + 5px 线中心偏移)
2556
+ var gapL = Math.max(12, Math.min(29, offL / 2))
2557
+ var gapR = Math.max(12, Math.min(29, offR / 2))
2558
+ var bl = Math.round((offL - gapL) * 10) / 10
2559
+ var br = Math.round((offR - gapR) * 10) / 10
2560
+ setGeo(function (prev) {
2561
+ if (prev && Math.abs(prev.l - bl) < 0.5 && Math.abs(prev.r - br) < 0.5) return prev
2562
+ return { l: bl, r: br }
2563
+ })
2564
+ }
2565
+ sync()
2566
+ raf = window.requestAnimationFrame(sync)
2567
+ if (typeof ResizeObserver !== 'undefined') {
2568
+ var observer = new ResizeObserver(sync)
2569
+ var content = el.querySelector('.dm-content')
2570
+ if (content) observer.observe(content)
2571
+ observer.observe(el)
2572
+ return function () { window.cancelAnimationFrame(raf); observer.disconnect() }
2573
+ }
2574
+ return function () { window.cancelAnimationFrame(raf) }
2575
+ }, [contentW, sbPad])
2576
+
2577
+ // 默认宽度 = 内容可用宽度 x DEFAULT_W_RATIO;拖拽限制在 [640, 可用宽度],
2578
+ // 双击手柄清掉偏好后回到默认。想留一点内边距就把 DEFAULT_W_RATIO 调到 0.98/0.96。
2579
+ var DEFAULT_W_RATIO = 1
2501
2580
  var widthNow = containerW || 920
2502
- var maxW = Math.max(640, widthNow - 176)
2503
- var contentW = pref > 0 ? Math.min(Math.max(pref, 640), maxW) : Math.max(680, Math.min(widthNow * 0.64, 920))
2581
+ var maxW = Math.max(640, widthNow)
2582
+ var contentW = pref > 0 ? Math.min(Math.max(pref, 640), maxW) : Math.round(widthNow * DEFAULT_W_RATIO)
2504
2583
 
2505
2584
  function beginWidthDrag(side, event) {
2506
2585
  event.preventDefault()
2507
2586
  var handle = event.currentTarget
2508
2587
  var shell = handle.closest ? handle.closest('.dm-content-shell') : (handle.parentElement && handle.parentElement.parentElement)
2509
2588
  if (!shell) return
2510
- var max = Math.max(640, (shell.clientWidth || 920) - 176)
2589
+ var scroller = shell.querySelector('.dm-content-scroll')
2590
+ var max = 920
2591
+ if (scroller) {
2592
+ var scs2 = getComputedStyle(scroller)
2593
+ var padL2 = parseFloat(scs2.paddingLeft) || 0
2594
+ var padR2 = parseFloat(scs2.paddingRight) || 0
2595
+ max = Math.max(0, (scroller.clientWidth || 0) - padL2 - padR2)
2596
+ } else if (shell.clientWidth) {
2597
+ max = shell.clientWidth
2598
+ }
2599
+ max = Math.max(640, max || 920)
2511
2600
  var base = contentW
2512
2601
  var originX = event.clientX
2513
2602
  var latest = contentW
@@ -2544,7 +2633,16 @@ body[data-ds-dark-theme] .dm-dg-fs-item.active { background:rgba(88,166,255,.28)
2544
2633
 
2545
2634
  return h('div', { className: 'dm-page' },
2546
2635
  h('style', null, PAGE_CSS),
2547
- h('div', { className: 'dm-content-shell', ref: shellRef, style: { '--dm-content-w': contentW + 'px' } },
2636
+ h('div', {
2637
+ className: 'dm-content-shell',
2638
+ ref: shellRef,
2639
+ style: {
2640
+ '--dm-content-w': contentW + 'px',
2641
+ '--dm-sb-pad': sbPad + 'px',
2642
+ '--dm-bar-l': (geo ? geo.l : -100) + 'px',
2643
+ '--dm-bar-r': (geo ? geo.r : -100) + 'px'
2644
+ }
2645
+ },
2548
2646
  h('div', { className: 'dm-width-handle', 'data-side': 'left', title: '拖拽调整内容宽度(双击恢复默认)', onPointerDown: function (event) { beginWidthDrag('left', event) }, onDoubleClick: resetContentWidth }),
2549
2647
  h('div', { className: 'dm-width-handle', 'data-side': 'right', title: '拖拽调整内容宽度(双击恢复默认)', onPointerDown: function (event) { beginWidthDrag('right', event) }, onDoubleClick: resetContentWidth }),
2550
2648
  h('div', { className: 'dm-content-scroll' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mesh-ui",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
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",