dsh-mesh-ui 0.0.4 → 0.0.6
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 +48 -18
- package/lib/collect.js +19 -4
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -1254,22 +1254,55 @@ function deviceLinkState(data, peer, lanMap) {
|
|
|
1254
1254
|
var p = String(d.path || '').replace(/[\\/]+$/, '')
|
|
1255
1255
|
return (d.freeGB != null ? d.freeGB : '?') + ' / ' + d.totalGB + ' GB' + (p ? ' (' + p + ')' : '')
|
|
1256
1256
|
}
|
|
1257
|
+
// 设备类型:按平台/CPU 形态泛化推断(不依赖任何个人设备表)
|
|
1258
|
+
function formFactorOf(h) {
|
|
1259
|
+
if (!h) return '—'
|
|
1260
|
+
if (h.platform === 'linux' && String(h.arch || '').indexOf('arm') === 0) return '手机'
|
|
1261
|
+
var cpu = String(h.cpuModel || '')
|
|
1262
|
+
return cpu ? '主机' : '—'
|
|
1263
|
+
}
|
|
1264
|
+
function memTextOf(h) {
|
|
1265
|
+
return h && h.memTotalGB ? ((h.memFreeGB != null ? h.memFreeGB : '?') + ' / ' + h.memTotalGB + ' GB') : '—'
|
|
1266
|
+
}
|
|
1267
|
+
function cpuTextOf(h) {
|
|
1268
|
+
return h && h.cpuModel ? h.cpuModel + ' · ' + (h.cpuCores || '?') + ' 线程' : '—'
|
|
1269
|
+
}
|
|
1270
|
+
function ipKindOf(peer) {
|
|
1271
|
+
var t = peer.tunnel || {}
|
|
1272
|
+
if (t.kind === 'loopback') return '本机回环'
|
|
1273
|
+
if (t.kind === 'lan') return '局域网直连'
|
|
1274
|
+
if (t.kind === 'frp') return '公网穿透(域名)'
|
|
1275
|
+
if (t.kind === 'wan') return relayHosts[String(t.host || '').toLowerCase()] ? '公网(共享中转)' : '公网 IP 直连'
|
|
1276
|
+
return '—'
|
|
1277
|
+
}
|
|
1278
|
+
function relayKindOf(peer) {
|
|
1279
|
+
var t = peer.tunnel || {}
|
|
1280
|
+
if (t.kind === 'loopback') return '无(本机回环)'
|
|
1281
|
+
if (t.kind === 'lan') return '无(局域网直连)'
|
|
1282
|
+
if (t.kind === 'frp') return 'Sakura / 第三方 frp 穿透'
|
|
1283
|
+
if (t.kind === 'wan') return relayHosts[String(t.host || '').toLowerCase()] ? '自建公网中继(frps)' : '公网 IP 直连'
|
|
1284
|
+
return '—'
|
|
1285
|
+
}
|
|
1286
|
+
function dshVerOf(peer) {
|
|
1287
|
+
var v = peer.version ? 'v' + peer.version : '—'
|
|
1288
|
+
if (peer.plugin) v += ' · 插件 ' + peer.plugin
|
|
1289
|
+
return v
|
|
1290
|
+
}
|
|
1257
1291
|
|
|
1258
1292
|
var devices = [{
|
|
1259
1293
|
key: 'local',
|
|
1260
1294
|
name: nodeDisplay(data.nodeName) + '(本机)',
|
|
1261
1295
|
up: Boolean(endpoint.up),
|
|
1262
1296
|
rows: [
|
|
1297
|
+
['设备类型', formFactorOf(hw)],
|
|
1263
1298
|
['操作系统', hw.os || '—'],
|
|
1264
|
-
['
|
|
1265
|
-
['内存', memText],
|
|
1299
|
+
['内存', memTextOf(hw)],
|
|
1266
1300
|
['磁盘', diskText(hw.disk)],
|
|
1267
|
-
['
|
|
1268
|
-
['
|
|
1269
|
-
['
|
|
1270
|
-
|
|
1271
|
-
['
|
|
1272
|
-
['检测时间', clockText(data.at)]
|
|
1301
|
+
['CPU', cpuTextOf(hw)],
|
|
1302
|
+
['DSH 版本', data.meshVersion ? 'v' + data.meshVersion : '—'],
|
|
1303
|
+
['IP 类型', '本机回环'],
|
|
1304
|
+
['端口号', localTunnel.port != null ? String(localTunnel.port) : '—'],
|
|
1305
|
+
['公网穿透类型', mainSrc === 'self-hosted' ? '自建 frps 中继(主用)' : (mainSrc === 'sakura' ? 'Sakura 穿透(备用)' : '无(仅局域网)')]
|
|
1273
1306
|
]
|
|
1274
1307
|
}].concat(peers.map(function (peer) {
|
|
1275
1308
|
var t = peer.tunnel || {}
|
|
@@ -1279,18 +1312,15 @@ function deviceLinkState(data, peer, lanMap) {
|
|
|
1279
1312
|
name: nodeDisplay(peerDeviceKey(peer.name)),
|
|
1280
1313
|
up: Boolean(peer.up),
|
|
1281
1314
|
rows: [
|
|
1315
|
+
['设备类型', formFactorOf(hwp)],
|
|
1282
1316
|
['操作系统', hwp.os || peer.os || '—'],
|
|
1283
|
-
['
|
|
1284
|
-
['内存', hwp.memTotalGB ? ((hwp.memFreeGB != null ? hwp.memFreeGB : '?') + ' / ' + hwp.memTotalGB + ' GB') : '—'],
|
|
1317
|
+
['内存', memTextOf(hwp)],
|
|
1285
1318
|
['磁盘', diskText(hwp.disk)],
|
|
1286
|
-
['
|
|
1287
|
-
['DSH
|
|
1288
|
-
['
|
|
1289
|
-
['
|
|
1290
|
-
['
|
|
1291
|
-
['链路', commLabel(peer, relayHosts).text + ' · ' + (peer.up ? (peer.latencyMs == null ? '—' : peer.latencyMs + 'ms') : '离线') + ' · ' + (peer.tls ? 'TLS ✓' : 'TLS ✗') + ' · v' + (peer.version || '?')],
|
|
1292
|
-
['节点 pid / tools', (peer.nodePid == null ? '—' : peer.nodePid) + ' / ' + (peer.nodeTools == null ? '—' : peer.nodeTools)],
|
|
1293
|
-
['检测时间', clockText(peer.detectedAt || data.at)]
|
|
1319
|
+
['CPU', cpuTextOf(hwp)],
|
|
1320
|
+
['DSH 版本', dshVerOf(peer)],
|
|
1321
|
+
['IP 类型', ipKindOf(peer)],
|
|
1322
|
+
['端口号', t.port ? String(t.port) : '—'],
|
|
1323
|
+
['公网穿透类型', relayKindOf(peer)]
|
|
1294
1324
|
]
|
|
1295
1325
|
}
|
|
1296
1326
|
}))
|
package/lib/collect.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// 归宿:reusable(dsh-mesh/plugin/dsh-mesh-ui/lib/collect.js)
|
|
15
15
|
|
|
16
16
|
import { execFile } from 'node:child_process'
|
|
17
|
-
import { existsSync, readdirSync, readFileSync, statfsSync } from 'node:fs'
|
|
17
|
+
import { existsSync, readdirSync, readFileSync, statfsSync, writeFileSync } from 'node:fs'
|
|
18
18
|
import { lookup as dnsLookup } from 'node:dns/promises'
|
|
19
19
|
import { arch as osArch, cpus as osCpus, freemem as osFreemem, homedir, platform as osPlatform, release as osRelease, totalmem as osTotalmem } from 'node:os'
|
|
20
20
|
import { join } from 'node:path'
|
|
@@ -439,12 +439,17 @@ export async function collect(repo) {
|
|
|
439
439
|
list.push({ id: inst.id, port: 3080 + idx, up: Boolean(inst.up) })
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
-
// 远端节点:host_self
|
|
442
|
+
// 远端节点:host_self 并发(超时放宽到 12s —— 跨公网节点 6s 偶尔不够,会导致硬件字段丢失)
|
|
443
443
|
const meshPeers = mesh.peers || []
|
|
444
444
|
const peerSelfs = await Promise.all(meshPeers.map(async (peer) => {
|
|
445
445
|
if (!peer.up) return null
|
|
446
|
-
return await cliJson(repo, ['peer', 'call', 'host_self', '--to', peer.name, '--args', '{}'], { timeout:
|
|
446
|
+
return await cliJson(repo, ['peer', 'call', 'host_self', '--to', peer.name, '--args', '{}'], { timeout: 12000 })
|
|
447
447
|
}))
|
|
448
|
+
// 硬件缓存:跨公网 host_self 偶发超时时,用上次成功结果兜底,避免面板时有时无
|
|
449
|
+
const HW_CACHE_FILE = join(meshHome(), 'peer-hardware.json')
|
|
450
|
+
let hwCache = {}
|
|
451
|
+
try { hwCache = JSON.parse(readFileSync(HW_CACHE_FILE, 'utf8')) || {} } catch { hwCache = {} }
|
|
452
|
+
const devKeyOf = (name) => String(name || '').toLowerCase().replace(/-(lan|wan|exec|exe|import|local|ctl)$/i, '')
|
|
448
453
|
const peers = meshPeers.map((peer, index) => {
|
|
449
454
|
const self = unwrapPeerCall(peerSelfs[index])
|
|
450
455
|
const stats = agentStats(self?.agents)
|
|
@@ -460,7 +465,7 @@ export async function collect(repo) {
|
|
|
460
465
|
sessions: stats.sessions,
|
|
461
466
|
running: stats.running,
|
|
462
467
|
hostname: self?.node?.hostname || '',
|
|
463
|
-
hardware: self?.hardware || null,
|
|
468
|
+
hardware: self?.hardware || hwCache[devKeyOf(peer.name)] || null,
|
|
464
469
|
release: self?.node?.release || '',
|
|
465
470
|
os: osLabel(self?.node?.platform, self?.node?.release),
|
|
466
471
|
uptimeSec: overviewByNode[peer.name]?.uptimeSec ?? null,
|
|
@@ -473,6 +478,16 @@ export async function collect(repo) {
|
|
|
473
478
|
error: peer.error?.code || null
|
|
474
479
|
}
|
|
475
480
|
})
|
|
481
|
+
// 同一设备可能有多个入口(company / company-exec / -lan / -wan):硬件信息互通,
|
|
482
|
+
// 避免 UI 去重时选中了恰好没有 hardware 的那条记录。
|
|
483
|
+
const hwByDev = {}
|
|
484
|
+
for (const p of peers) { const k = devKeyOf(p.name); if (p.hardware) hwByDev[k] = p.hardware }
|
|
485
|
+
for (const p of peers) {
|
|
486
|
+
const k = devKeyOf(p.name)
|
|
487
|
+
if (!p.hardware && hwByDev[k]) p.hardware = hwByDev[k]
|
|
488
|
+
if (p.hardware) hwCache[k] = p.hardware
|
|
489
|
+
}
|
|
490
|
+
try { writeFileSync(HW_CACHE_FILE, JSON.stringify(hwCache, null, 1)) } catch { /* 缓存写失败不影响主流程 */ }
|
|
476
491
|
|
|
477
492
|
// 2026-11-10:LAN 链路要显示 IP(不是主机名)→ 对域名型 tunnel.host 做一次 DNS 解析(800ms 超时,失败则回落主机名)
|
|
478
493
|
function isIpLiteral(host) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mesh-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "DSH settings-page panel for dsh-mesh: AI network topology, peer health, and delegation panel. Read-only; data is collected from the local mesh CLI.",
|
|
5
5
|
"icon": "icon.svg",
|
|
6
6
|
"type": "module",
|