@matter-server/dashboard 0.6.2 → 0.6.3
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/dist/esm/pages/matter-network-view.d.ts +15 -0
- package/dist/esm/pages/matter-network-view.d.ts.map +1 -1
- package/dist/esm/pages/matter-network-view.js +171 -1
- package/dist/esm/pages/matter-network-view.js.map +1 -1
- package/dist/esm/pages/network/base-network-graph.d.ts +4 -0
- package/dist/esm/pages/network/base-network-graph.d.ts.map +1 -1
- package/dist/esm/pages/network/base-network-graph.js +9 -0
- package/dist/esm/pages/network/base-network-graph.js.map +1 -1
- package/dist/esm/pages/network/border-router-store.d.ts +20 -0
- package/dist/esm/pages/network/border-router-store.d.ts.map +1 -0
- package/dist/esm/pages/network/border-router-store.js +29 -0
- package/dist/esm/pages/network/border-router-store.js.map +6 -0
- package/dist/esm/pages/network/network-details.d.ts +40 -12
- package/dist/esm/pages/network/network-details.d.ts.map +1 -1
- package/dist/esm/pages/network/network-details.js +440 -112
- package/dist/esm/pages/network/network-details.js.map +1 -1
- package/dist/esm/pages/network/network-types.d.ts +76 -0
- package/dist/esm/pages/network/network-types.d.ts.map +1 -1
- package/dist/esm/pages/network/network-types.js.map +1 -1
- package/dist/esm/pages/network/network-utils.d.ts +89 -22
- package/dist/esm/pages/network/network-utils.d.ts.map +1 -1
- package/dist/esm/pages/network/network-utils.js +233 -95
- package/dist/esm/pages/network/network-utils.js.map +1 -1
- package/dist/esm/pages/network/thread-graph.d.ts +68 -9
- package/dist/esm/pages/network/thread-graph.d.ts.map +1 -1
- package/dist/esm/pages/network/thread-graph.js +388 -50
- package/dist/esm/pages/network/thread-graph.js.map +2 -2
- package/dist/esm/util/device-icons.d.ts +6 -0
- package/dist/esm/util/device-icons.d.ts.map +1 -1
- package/dist/esm/util/device-icons.js +6 -0
- package/dist/esm/util/device-icons.js.map +1 -1
- package/dist/web/js/{attribute-write-dialog-g4B6BoRt.js → attribute-write-dialog-DlMTUiLK.js} +1 -1
- package/dist/web/js/{command-invoke-dialog-D6G704VK.js → command-invoke-dialog-DO3IyFcm.js} +1 -1
- package/dist/web/js/{commission-node-dialog-Bg3oo5ub.js → commission-node-dialog-CMSvCm0i.js} +4 -4
- package/dist/web/js/{commission-node-existing-DO3g1aQJ.js → commission-node-existing-D08jghFu.js} +2 -2
- package/dist/web/js/{commission-node-thread-DM432aH1.js → commission-node-thread-D5waY758.js} +2 -2
- package/dist/web/js/{commission-node-wifi-Bx40FXij.js → commission-node-wifi-ClBlCFTZ.js} +2 -2
- package/dist/web/js/{dialog-box-DjyfULWB.js → dialog-box-D9vS2SmP.js} +1 -1
- package/dist/web/js/{fire_event-BstgNPuh.js → fire_event-BPhROjTC.js} +1 -1
- package/dist/web/js/main.js +1 -1
- package/dist/web/js/{matter-dashboard-app-Cj88TtbZ.js → matter-dashboard-app-C9zTE5uH.js} +1359 -302
- package/dist/web/js/{node-binding-dialog-9yy2LE3_.js → node-binding-dialog-B5p-gbim.js} +1 -1
- package/dist/web/js/{settings-dialog-Cs2xMsXb.js → settings-dialog-BMFhom0W.js} +1 -1
- package/package.json +4 -4
- package/src/pages/matter-network-view.ts +185 -1
- package/src/pages/network/base-network-graph.ts +10 -0
- package/src/pages/network/border-router-store.ts +38 -0
- package/src/pages/network/network-details.ts +535 -140
- package/src/pages/network/network-types.ts +76 -0
- package/src/pages/network/network-utils.ts +390 -171
- package/src/pages/network/thread-graph.ts +532 -73
- package/src/util/device-icons.ts +13 -0
|
@@ -24,13 +24,11 @@ import "../../components/ha-svg-icon";
|
|
|
24
24
|
import { formatNodeAddressFromAny, getEffectiveFabricIndex } from "../../util/format_hex.js";
|
|
25
25
|
import { getCssVar, reducedMotionStyles } from "../../util/shared-styles.js";
|
|
26
26
|
import {
|
|
27
|
-
|
|
28
|
-
buildRloc16Map,
|
|
27
|
+
decodeMeshcopStateBitmap,
|
|
29
28
|
getDeviceName,
|
|
30
29
|
getNetworkType,
|
|
31
|
-
|
|
30
|
+
getNodeConnectionsFromPairs,
|
|
32
31
|
getRoutableDestinationsCount,
|
|
33
|
-
getSignalColor,
|
|
34
32
|
getSignalColorFromRssi,
|
|
35
33
|
getThreadChannel,
|
|
36
34
|
getThreadExtendedAddressHex,
|
|
@@ -38,17 +36,22 @@ import {
|
|
|
38
36
|
getThreadRoleName,
|
|
39
37
|
getWiFiDiagnostics,
|
|
40
38
|
getWiFiSecurityTypeName,
|
|
41
|
-
getWiFiVersionName
|
|
42
|
-
parseNeighborTable
|
|
39
|
+
getWiFiVersionName
|
|
43
40
|
} from "./network-utils.js";
|
|
44
41
|
import "./update-connections-dialog.js";
|
|
45
42
|
let NetworkDetails = class extends LitElement {
|
|
46
43
|
constructor() {
|
|
47
44
|
super(...arguments);
|
|
48
45
|
this.selectedNodeId = null;
|
|
46
|
+
this.hideOfflineNodes = false;
|
|
47
|
+
this.hideWeakSignalEdges = false;
|
|
48
|
+
this.hideMediumSignalEdges = false;
|
|
49
|
+
this.hideStrongSignalEdges = false;
|
|
49
50
|
this.nodes = {};
|
|
50
51
|
this.unknownDevices = /* @__PURE__ */ new Map();
|
|
52
|
+
this.borderRouters = /* @__PURE__ */ new Map();
|
|
51
53
|
this.wifiAccessPoints = /* @__PURE__ */ new Map();
|
|
54
|
+
this.threadEdgePairs = /* @__PURE__ */ new Map();
|
|
52
55
|
this._showUpdateDialog = false;
|
|
53
56
|
}
|
|
54
57
|
_handleClose() {
|
|
@@ -75,16 +78,6 @@ let NetworkDetails = class extends LitElement {
|
|
|
75
78
|
this._handleSelectNode(nodeId);
|
|
76
79
|
}
|
|
77
80
|
}
|
|
78
|
-
_formatExtAddress(extAddr) {
|
|
79
|
-
if (extAddr === void 0 || extAddr === "") return "Unknown";
|
|
80
|
-
if (typeof extAddr === "bigint") {
|
|
81
|
-
return extAddr.toString(16).toUpperCase().padStart(16, "0");
|
|
82
|
-
}
|
|
83
|
-
return extAddr;
|
|
84
|
-
}
|
|
85
|
-
_getSignalIcon(neighbor) {
|
|
86
|
-
return this._getSignalIconFromColor(getSignalColor(neighbor));
|
|
87
|
-
}
|
|
88
81
|
_getSignalIconFromColor(color) {
|
|
89
82
|
const strongColor = getCssVar("--signal-color-strong", "#4caf50");
|
|
90
83
|
const mediumColor = getCssVar("--signal-color-medium", "#ff9800");
|
|
@@ -148,10 +141,13 @@ let NetworkDetails = class extends LitElement {
|
|
|
148
141
|
const threadRole = getThreadRole(node);
|
|
149
142
|
const channel = getThreadChannel(node);
|
|
150
143
|
const extAddressHex = getThreadExtendedAddressHex(node);
|
|
151
|
-
const extAddrMap = buildExtAddrMap(this.nodes);
|
|
152
|
-
const rloc16Map = buildRloc16Map(this.nodes);
|
|
153
144
|
const nodeId = String(node.node_id);
|
|
154
|
-
const connections =
|
|
145
|
+
const connections = getNodeConnectionsFromPairs(nodeId, this.threadEdgePairs, this.nodes, {
|
|
146
|
+
hideOfflineNodes: this.hideOfflineNodes,
|
|
147
|
+
hideWeakSignalEdges: this.hideWeakSignalEdges,
|
|
148
|
+
hideMediumSignalEdges: this.hideMediumSignalEdges,
|
|
149
|
+
hideStrongSignalEdges: this.hideStrongSignalEdges
|
|
150
|
+
});
|
|
155
151
|
return html`
|
|
156
152
|
<div class="section">
|
|
157
153
|
<h4>Thread Network</h4>
|
|
@@ -231,7 +227,13 @@ let NetworkDetails = class extends LitElement {
|
|
|
231
227
|
>` : nothing}${conn.pathCost !== void 0 ? html`<span class="route-info"
|
|
232
228
|
>, Cost: ${conn.pathCost}</span
|
|
233
229
|
>` : nothing}
|
|
234
|
-
${
|
|
230
|
+
${conn.isReverseOnly ? html`
|
|
231
|
+
<span
|
|
232
|
+
class="direction-hint reverse-only"
|
|
233
|
+
title="Peer reports this node but this node has no matching neighbor-table entry. Possible one-way visibility (range, TX power, or stale neighbor table)."
|
|
234
|
+
>← one-way</span
|
|
235
|
+
>
|
|
236
|
+
` : !conn.isOutgoing ? html` <span class="direction-hint">(reverse)</span> ` : nothing}
|
|
235
237
|
</div>
|
|
236
238
|
</div>
|
|
237
239
|
</div>
|
|
@@ -287,24 +289,12 @@ let NetworkDetails = class extends LitElement {
|
|
|
287
289
|
` : nothing}
|
|
288
290
|
`;
|
|
289
291
|
}
|
|
290
|
-
/**
|
|
291
|
-
* Find the neighbor entry for an unknown device from a node's neighbor table.
|
|
292
|
-
*/
|
|
293
|
-
_findNeighborEntry(node, unknownExtAddrHex) {
|
|
294
|
-
const neighbors = parseNeighborTable(node);
|
|
295
|
-
for (const neighbor of neighbors) {
|
|
296
|
-
const neighborHex = this._formatExtAddress(neighbor.extAddress);
|
|
297
|
-
if (neighborHex === unknownExtAddrHex) {
|
|
298
|
-
return neighbor;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return null;
|
|
302
|
-
}
|
|
303
292
|
_renderUnknownDeviceInfo(deviceId) {
|
|
304
|
-
const
|
|
305
|
-
if (!unknown) {
|
|
293
|
+
const device = this.unknownDevices.get(deviceId);
|
|
294
|
+
if (!device || device.kind !== "unknown") {
|
|
306
295
|
return html` <p>Unknown device data not available</p> `;
|
|
307
296
|
}
|
|
297
|
+
const unknown = device;
|
|
308
298
|
return html`
|
|
309
299
|
<div class="section">
|
|
310
300
|
<h4>Unknown Device</h4>
|
|
@@ -316,6 +306,18 @@ let NetworkDetails = class extends LitElement {
|
|
|
316
306
|
<span class="label">Extended Address:</span>
|
|
317
307
|
<span class="value mono">${unknown.extAddressHex}</span>
|
|
318
308
|
</div>
|
|
309
|
+
${unknown.networkName !== void 0 ? html`
|
|
310
|
+
<div class="info-row">
|
|
311
|
+
<span class="label">Thread Network:</span>
|
|
312
|
+
<span class="value">${unknown.networkName}</span>
|
|
313
|
+
</div>
|
|
314
|
+
` : nothing}
|
|
315
|
+
${unknown.extendedPanIdHex !== void 0 ? html`
|
|
316
|
+
<div class="info-row">
|
|
317
|
+
<span class="label">Extended PAN ID:</span>
|
|
318
|
+
<span class="value mono">${unknown.extendedPanIdHex}</span>
|
|
319
|
+
</div>
|
|
320
|
+
` : nothing}
|
|
319
321
|
${unknown.bestRssi !== null ? html`
|
|
320
322
|
<div class="info-row">
|
|
321
323
|
<span class="label">Best RSSI:</span>
|
|
@@ -324,71 +326,286 @@ let NetworkDetails = class extends LitElement {
|
|
|
324
326
|
` : nothing}
|
|
325
327
|
</div>
|
|
326
328
|
|
|
327
|
-
${
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
329
|
+
${this._renderExternalDeviceNeighbors(deviceId)}
|
|
330
|
+
|
|
331
|
+
<md-divider></md-divider>
|
|
332
|
+
<div class="section">
|
|
333
|
+
<p class="hint-text">
|
|
334
|
+
This device appears in Thread neighbor tables but is not commissioned to this fabric. It may be a
|
|
335
|
+
Thread Border Router whose Thread radio MAC differs from its MeshCoP border-agent ID (common with
|
|
336
|
+
Apple and Aqara), or a device from another Matter ecosystem.
|
|
337
|
+
</p>
|
|
338
|
+
</div>
|
|
339
|
+
`;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Neighbor list shared by external-device panels (unknown + BR). Uses the
|
|
343
|
+
* same edge pairs as the graph so panel and graph agree on which links
|
|
344
|
+
* survive filtering. Sorted by best RSSI/LQI signal, descending.
|
|
345
|
+
*/
|
|
346
|
+
_renderExternalDeviceNeighbors(deviceId) {
|
|
347
|
+
const connections = getNodeConnectionsFromPairs(deviceId, this.threadEdgePairs, this.nodes, {
|
|
348
|
+
hideOfflineNodes: this.hideOfflineNodes,
|
|
349
|
+
hideWeakSignalEdges: this.hideWeakSignalEdges,
|
|
350
|
+
hideMediumSignalEdges: this.hideMediumSignalEdges,
|
|
351
|
+
hideStrongSignalEdges: this.hideStrongSignalEdges
|
|
352
|
+
});
|
|
353
|
+
if (connections.length === 0) return nothing;
|
|
354
|
+
return html`
|
|
355
|
+
<md-divider></md-divider>
|
|
356
|
+
<div class="section">
|
|
357
|
+
<h4>Neighbors (${connections.length})</h4>
|
|
358
|
+
<div class="neighbors-list">
|
|
359
|
+
${connections.toSorted((a, b) => {
|
|
360
|
+
const score = (conn) => {
|
|
361
|
+
if (conn.rssi !== null && conn.rssi !== void 0) return conn.rssi;
|
|
362
|
+
if (conn.lqi !== null && conn.lqi !== void 0) return conn.lqi;
|
|
341
363
|
return -Infinity;
|
|
342
364
|
};
|
|
343
365
|
return score(b) - score(a);
|
|
344
|
-
}).map((
|
|
345
|
-
|
|
346
|
-
if (!node) return nothing;
|
|
347
|
-
const neighborEntry = this._findNeighborEntry(node, unknown.extAddressHex);
|
|
348
|
-
const signalColor = neighborEntry ? getSignalColor(neighborEntry) : getCssVar("--graph-node-fallback", "#999");
|
|
349
|
-
const rssi = neighborEntry?.avgRssi ?? neighborEntry?.lastRssi ?? null;
|
|
350
|
-
const lqi = neighborEntry?.lqi;
|
|
366
|
+
}).map((conn) => {
|
|
367
|
+
if (!conn.connectedNode) return nothing;
|
|
351
368
|
return html`
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
</div>
|
|
378
|
-
</div>
|
|
379
|
-
`;
|
|
369
|
+
<div
|
|
370
|
+
class="neighbor-item clickable"
|
|
371
|
+
role="button"
|
|
372
|
+
tabindex="0"
|
|
373
|
+
@click=${() => this._handleSelectNode(conn.connectedNodeId)}
|
|
374
|
+
@keydown=${(e) => this._handleKeyDown(e, conn.connectedNodeId)}
|
|
375
|
+
>
|
|
376
|
+
<ha-svg-icon
|
|
377
|
+
.path=${this._getSignalIconFromColor(conn.signalColor)}
|
|
378
|
+
style="--icon-primary-color: ${conn.signalColor}"
|
|
379
|
+
></ha-svg-icon>
|
|
380
|
+
<div class="neighbor-info">
|
|
381
|
+
<div class="neighbor-name">
|
|
382
|
+
Node ${conn.connectedNodeId}
|
|
383
|
+
<span class="node-id-hex"
|
|
384
|
+
>${this._formatNodeIdHex(conn.connectedNodeId)}</span
|
|
385
|
+
>: ${getDeviceName(conn.connectedNode)}
|
|
386
|
+
</div>
|
|
387
|
+
<div class="neighbor-signal">
|
|
388
|
+
${conn.rssi !== null ? html`RSSI: ${conn.rssi} dBm, ` : nothing}
|
|
389
|
+
${conn.lqi !== null ? html`LQI: ${conn.lqi}` : nothing}
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
`;
|
|
380
394
|
})}
|
|
381
|
-
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
`;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Identity rows for a Border Router (network name, vendor, model, Thread version, ext address).
|
|
401
|
+
* Caller controls the surrounding <div class="section"> + heading.
|
|
402
|
+
*/
|
|
403
|
+
_renderBorderRouterIdentityRows(br, includeExtAddr) {
|
|
404
|
+
return html`
|
|
405
|
+
${br.networkName ? html`
|
|
406
|
+
<div class="info-row">
|
|
407
|
+
<span class="label">Network name:</span>
|
|
408
|
+
<span class="value">${br.networkName}</span>
|
|
382
409
|
</div>
|
|
383
410
|
` : nothing}
|
|
384
|
-
|
|
385
|
-
|
|
411
|
+
${br.vendorName ? html`
|
|
412
|
+
<div class="info-row">
|
|
413
|
+
<span class="label">Vendor:</span>
|
|
414
|
+
<span class="value">${br.vendorName}</span>
|
|
415
|
+
</div>
|
|
416
|
+
` : nothing}
|
|
417
|
+
${br.modelName ? html`
|
|
418
|
+
<div class="info-row">
|
|
419
|
+
<span class="label">Model:</span>
|
|
420
|
+
<span class="value">${br.modelName}</span>
|
|
421
|
+
</div>
|
|
422
|
+
` : nothing}
|
|
423
|
+
${br.threadVersion ? html`
|
|
424
|
+
<div class="info-row">
|
|
425
|
+
<span class="label">Thread version:</span>
|
|
426
|
+
<span class="value">${br.threadVersion}</span>
|
|
427
|
+
</div>
|
|
428
|
+
` : nothing}
|
|
429
|
+
${includeExtAddr ? html`
|
|
430
|
+
<div class="info-row">
|
|
431
|
+
<span class="label">Extended Address:</span>
|
|
432
|
+
<span class="value mono">${br.extAddressHex}</span>
|
|
433
|
+
</div>
|
|
434
|
+
` : nothing}
|
|
435
|
+
`;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Render the MeshCoP state bitmap as decoded fields (BBR role, connection mode, Thread
|
|
439
|
+
* interface status, availability, ePSKc) plus the raw hex underneath. Reserved values are
|
|
440
|
+
* rendered as numeric so a future spec extension stays visible.
|
|
441
|
+
*/
|
|
442
|
+
_renderStateBitmap(hex) {
|
|
443
|
+
if (hex === void 0) return nothing;
|
|
444
|
+
const decoded = decodeMeshcopStateBitmap(hex);
|
|
445
|
+
if (decoded === void 0) {
|
|
446
|
+
return html`
|
|
447
|
+
<div class="info-row">
|
|
448
|
+
<span class="label">State bitmap:</span>
|
|
449
|
+
<span class="value mono">${hex}</span>
|
|
450
|
+
</div>
|
|
451
|
+
`;
|
|
452
|
+
}
|
|
453
|
+
const stateParts = new Array();
|
|
454
|
+
stateParts.push(decoded.bbr ? `BBR (${decoded.bbrFunction ?? "?"})` : "not BBR");
|
|
455
|
+
if (decoded.threadRole !== void 0) {
|
|
456
|
+
stateParts.push(`Thread ${decoded.threadRole}`);
|
|
457
|
+
}
|
|
458
|
+
if (decoded.threadInterfaceStatus !== void 0) {
|
|
459
|
+
stateParts.push(decoded.threadInterfaceStatus);
|
|
460
|
+
}
|
|
461
|
+
return html`
|
|
462
|
+
<div class="info-row">
|
|
463
|
+
<span class="label">State:</span>
|
|
464
|
+
<span class="value">${stateParts.join(", ")}</span>
|
|
465
|
+
</div>
|
|
466
|
+
<div class="info-row">
|
|
467
|
+
<span class="label">Connection:</span>
|
|
468
|
+
<span class="value">${decoded.connectionMode ?? `reserved (${decoded.connectionModeValue})`}</span>
|
|
469
|
+
</div>
|
|
470
|
+
<div class="info-row">
|
|
471
|
+
<span class="label">Availability:</span>
|
|
472
|
+
<span class="value">${decoded.availability ?? `reserved (${decoded.availabilityValue})`}</span>
|
|
473
|
+
</div>
|
|
474
|
+
<div class="info-row">
|
|
475
|
+
<span class="label">ePSKc:</span>
|
|
476
|
+
<span class="value">${decoded.epskcSupported ? "supported" : "not supported"}</span>
|
|
477
|
+
</div>
|
|
478
|
+
${decoded.multiAilStateValue !== 0 ? html`
|
|
479
|
+
<div class="info-row">
|
|
480
|
+
<span class="label">Multi-AIL:</span>
|
|
481
|
+
<span class="value">
|
|
482
|
+
${decoded.multiAilState ?? `reserved (${decoded.multiAilStateValue})`}
|
|
483
|
+
</span>
|
|
484
|
+
</div>
|
|
485
|
+
` : nothing}
|
|
486
|
+
<div class="info-row">
|
|
487
|
+
<span class="label">State bitmap (raw):</span>
|
|
488
|
+
<span class="value mono">${hex}</span>
|
|
489
|
+
</div>
|
|
490
|
+
`;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Network-info rows for a Border Router (extended PAN ID, partition, timestamps, state, domain, agent ID).
|
|
494
|
+
* Returns nothing if no fields are populated, so the caller can skip the surrounding section.
|
|
495
|
+
*/
|
|
496
|
+
_renderBorderRouterNetworkRows(br) {
|
|
497
|
+
const hasAny = br.extendedPanIdHex !== void 0 || br.partitionIdHex !== void 0 || br.activeTimestampHex !== void 0 || br.stateBitmapHex !== void 0 || br.domainName !== void 0 || br.borderAgentIdHex !== void 0;
|
|
498
|
+
if (!hasAny) return nothing;
|
|
499
|
+
return html`
|
|
500
|
+
${br.extendedPanIdHex ? html`
|
|
501
|
+
<div class="info-row">
|
|
502
|
+
<span class="label">Extended PAN ID:</span>
|
|
503
|
+
<span class="value mono">${br.extendedPanIdHex}</span>
|
|
504
|
+
</div>
|
|
505
|
+
` : nothing}
|
|
506
|
+
${br.partitionIdHex ? html`
|
|
507
|
+
<div class="info-row">
|
|
508
|
+
<span class="label">Partition ID:</span>
|
|
509
|
+
<span class="value mono">${br.partitionIdHex}</span>
|
|
510
|
+
</div>
|
|
511
|
+
` : nothing}
|
|
512
|
+
${br.activeTimestampHex ? html`
|
|
513
|
+
<div class="info-row">
|
|
514
|
+
<span class="label">Active timestamp:</span>
|
|
515
|
+
<span class="value mono">${br.activeTimestampHex}</span>
|
|
516
|
+
</div>
|
|
517
|
+
` : nothing}
|
|
518
|
+
${this._renderStateBitmap(br.stateBitmapHex)}
|
|
519
|
+
${br.domainName ? html`
|
|
520
|
+
<div class="info-row">
|
|
521
|
+
<span class="label">Domain:</span>
|
|
522
|
+
<span class="value">${br.domainName}</span>
|
|
523
|
+
</div>
|
|
524
|
+
` : nothing}
|
|
525
|
+
${br.borderAgentIdHex ? html`
|
|
526
|
+
<div class="info-row">
|
|
527
|
+
<span class="label">Border agent ID:</span>
|
|
528
|
+
<span class="value mono">${br.borderAgentIdHex}</span>
|
|
529
|
+
</div>
|
|
530
|
+
` : nothing}
|
|
531
|
+
`;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Address rows for a Border Router (hostname, IPs, ports, sources).
|
|
535
|
+
*/
|
|
536
|
+
_renderBorderRouterAddressRows(br) {
|
|
537
|
+
const hasAny = br.hostname !== void 0 || br.addresses.length > 0 || br.meshcopPort !== void 0 || br.trelPort !== void 0 || br.sources.length > 0;
|
|
538
|
+
if (!hasAny) return nothing;
|
|
539
|
+
return html`
|
|
540
|
+
${br.hostname ? html`
|
|
541
|
+
<div class="info-row">
|
|
542
|
+
<span class="label">Hostname:</span>
|
|
543
|
+
<span class="value mono">${br.hostname}</span>
|
|
544
|
+
</div>
|
|
545
|
+
` : nothing}
|
|
546
|
+
${br.addresses.map(
|
|
547
|
+
(addr) => html`
|
|
548
|
+
<div class="info-row">
|
|
549
|
+
<span class="label">Address:</span>
|
|
550
|
+
<span class="value mono">${addr}</span>
|
|
551
|
+
</div>
|
|
552
|
+
`
|
|
553
|
+
)}
|
|
554
|
+
${br.meshcopPort !== void 0 ? html`
|
|
555
|
+
<div class="info-row">
|
|
556
|
+
<span class="label">meshcop port:</span>
|
|
557
|
+
<span class="value">${br.meshcopPort}</span>
|
|
558
|
+
</div>
|
|
559
|
+
` : nothing}
|
|
560
|
+
${br.trelPort !== void 0 ? html`
|
|
561
|
+
<div class="info-row">
|
|
562
|
+
<span class="label">trel port:</span>
|
|
563
|
+
<span class="value">${br.trelPort}</span>
|
|
564
|
+
</div>
|
|
565
|
+
` : nothing}
|
|
566
|
+
${br.sources.length > 0 ? html`
|
|
567
|
+
<div class="info-row">
|
|
568
|
+
<span class="label">Sources:</span>
|
|
569
|
+
<span class="value">${br.sources.join(", ")}</span>
|
|
570
|
+
</div>
|
|
571
|
+
` : nothing}
|
|
572
|
+
`;
|
|
573
|
+
}
|
|
574
|
+
_renderBorderRouterInfo(deviceId) {
|
|
575
|
+
const device = this.unknownDevices.get(deviceId);
|
|
576
|
+
if (!device || device.kind !== "br") {
|
|
577
|
+
return html` <p>Border router data not available</p> `;
|
|
578
|
+
}
|
|
579
|
+
const br = device;
|
|
580
|
+
const networkRows = this._renderBorderRouterNetworkRows(br);
|
|
581
|
+
const addressRows = this._renderBorderRouterAddressRows(br);
|
|
582
|
+
return html`
|
|
386
583
|
<div class="section">
|
|
387
|
-
<
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
584
|
+
<h4>Border Router</h4>
|
|
585
|
+
${this._renderBorderRouterIdentityRows(br, true)}
|
|
586
|
+
${br.bestRssi !== null ? html`
|
|
587
|
+
<div class="info-row">
|
|
588
|
+
<span class="label">Best RSSI:</span>
|
|
589
|
+
<span class="value">${br.bestRssi} dBm</span>
|
|
590
|
+
</div>
|
|
591
|
+
` : nothing}
|
|
391
592
|
</div>
|
|
593
|
+
|
|
594
|
+
${networkRows !== nothing ? html`
|
|
595
|
+
<md-divider></md-divider>
|
|
596
|
+
<div class="section">
|
|
597
|
+
<h4>Thread Network</h4>
|
|
598
|
+
${networkRows}
|
|
599
|
+
</div>
|
|
600
|
+
` : nothing}
|
|
601
|
+
${addressRows !== nothing ? html`
|
|
602
|
+
<md-divider></md-divider>
|
|
603
|
+
<div class="section">
|
|
604
|
+
<h4>Addresses</h4>
|
|
605
|
+
${addressRows}
|
|
606
|
+
</div>
|
|
607
|
+
` : nothing}
|
|
608
|
+
${this._renderExternalDeviceNeighbors(deviceId)}
|
|
392
609
|
`;
|
|
393
610
|
}
|
|
394
611
|
/**
|
|
@@ -398,8 +615,8 @@ let NetworkDetails = class extends LitElement {
|
|
|
398
615
|
if (this.selectedNodeId === null) return false;
|
|
399
616
|
const isAccessPoint = typeof this.selectedNodeId === "string" && this.selectedNodeId.startsWith("ap_");
|
|
400
617
|
if (isAccessPoint) return false;
|
|
401
|
-
const
|
|
402
|
-
if (
|
|
618
|
+
const isExternal = typeof this.selectedNodeId === "string" && (this.selectedNodeId.startsWith("unknown_") || this.selectedNodeId.startsWith("br_"));
|
|
619
|
+
if (isExternal) {
|
|
403
620
|
return this._getOnlineSeenByNodes().length > 0;
|
|
404
621
|
}
|
|
405
622
|
const node = this.nodes[this.selectedNodeId.toString()];
|
|
@@ -412,7 +629,7 @@ let NetworkDetails = class extends LitElement {
|
|
|
412
629
|
* Get the type of the currently selected node for dialog variant.
|
|
413
630
|
*/
|
|
414
631
|
_getSelectedNodeType() {
|
|
415
|
-
if (typeof this.selectedNodeId === "string" && this.selectedNodeId.startsWith("unknown_")) {
|
|
632
|
+
if (typeof this.selectedNodeId === "string" && (this.selectedNodeId.startsWith("unknown_") || this.selectedNodeId.startsWith("br_"))) {
|
|
416
633
|
return "unknown";
|
|
417
634
|
}
|
|
418
635
|
const node = this.nodes[this.selectedNodeId.toString()];
|
|
@@ -429,15 +646,10 @@ let NetworkDetails = class extends LitElement {
|
|
|
429
646
|
if (!node) return [];
|
|
430
647
|
const networkType = getNetworkType(node);
|
|
431
648
|
if (networkType === "thread") {
|
|
432
|
-
const
|
|
433
|
-
const rloc16Map = buildRloc16Map(this.nodes);
|
|
434
|
-
const connections = getNodeConnections(nodeId, this.nodes, extAddrMap, rloc16Map);
|
|
649
|
+
const connections = getNodeConnectionsFromPairs(nodeId, this.threadEdgePairs, this.nodes);
|
|
435
650
|
return connections.filter((conn) => {
|
|
436
|
-
if (
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
const connectedNode = this.nodes[String(conn.connectedNodeId)];
|
|
440
|
-
return connectedNode?.available === true;
|
|
651
|
+
if (conn.isUnknown) return false;
|
|
652
|
+
return conn.connectedNode?.available === true;
|
|
441
653
|
}).map((conn) => String(conn.connectedNodeId));
|
|
442
654
|
}
|
|
443
655
|
return [];
|
|
@@ -446,12 +658,12 @@ let NetworkDetails = class extends LitElement {
|
|
|
446
658
|
* Get online nodes that see an unknown device.
|
|
447
659
|
*/
|
|
448
660
|
_getOnlineSeenByNodes() {
|
|
449
|
-
if (typeof this.selectedNodeId !== "string" || !this.selectedNodeId.startsWith("unknown_")) {
|
|
661
|
+
if (typeof this.selectedNodeId !== "string" || !this.selectedNodeId.startsWith("unknown_") && !this.selectedNodeId.startsWith("br_")) {
|
|
450
662
|
return [];
|
|
451
663
|
}
|
|
452
|
-
const
|
|
453
|
-
if (!
|
|
454
|
-
return
|
|
664
|
+
const device = this.unknownDevices.get(this.selectedNodeId);
|
|
665
|
+
if (!device) return [];
|
|
666
|
+
return device.seenBy.filter((nodeId) => {
|
|
455
667
|
const node = this.nodes[nodeId.toString()];
|
|
456
668
|
return node?.available === true;
|
|
457
669
|
});
|
|
@@ -460,11 +672,19 @@ let NetworkDetails = class extends LitElement {
|
|
|
460
672
|
* Get the name of the selected node for display in dialog.
|
|
461
673
|
*/
|
|
462
674
|
_getSelectedNodeName() {
|
|
463
|
-
if (typeof this.selectedNodeId === "string"
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
675
|
+
if (typeof this.selectedNodeId === "string") {
|
|
676
|
+
if (this.selectedNodeId.startsWith("br_")) {
|
|
677
|
+
const device = this.unknownDevices.get(this.selectedNodeId);
|
|
678
|
+
if (!device || device.kind !== "br") return "Border Router";
|
|
679
|
+
const label = device.networkName ?? device.vendorName ?? "Border Router";
|
|
680
|
+
return `${label} (${device.extAddressHex.slice(-8)})`;
|
|
681
|
+
}
|
|
682
|
+
if (this.selectedNodeId.startsWith("unknown_")) {
|
|
683
|
+
const device = this.unknownDevices.get(this.selectedNodeId);
|
|
684
|
+
if (!device || device.kind !== "unknown") return "External Device";
|
|
685
|
+
const typeLabel = device.isRouter ? "External Router" : "External Device";
|
|
686
|
+
return `${typeLabel} (${device.extAddressHex.slice(-8)})`;
|
|
687
|
+
}
|
|
468
688
|
}
|
|
469
689
|
const node = this.nodes[this.selectedNodeId.toString()];
|
|
470
690
|
return node ? getDeviceName(node) : "Unknown";
|
|
@@ -474,6 +694,12 @@ let NetworkDetails = class extends LitElement {
|
|
|
474
694
|
}
|
|
475
695
|
_handleDialogClose() {
|
|
476
696
|
this._showUpdateDialog = false;
|
|
697
|
+
this.dispatchEvent(
|
|
698
|
+
new CustomEvent("connections-updated", {
|
|
699
|
+
bubbles: true,
|
|
700
|
+
composed: true
|
|
701
|
+
})
|
|
702
|
+
);
|
|
477
703
|
}
|
|
478
704
|
_renderWiFiAccessPointInfo(apId) {
|
|
479
705
|
const ap = this.wifiAccessPoints.get(apId);
|
|
@@ -538,6 +764,33 @@ let NetworkDetails = class extends LitElement {
|
|
|
538
764
|
</div>
|
|
539
765
|
`;
|
|
540
766
|
}
|
|
767
|
+
/**
|
|
768
|
+
* Annotation shown on a commissioned Thread node that is also a discovered Border Router.
|
|
769
|
+
* Mirrors the BR Identity/Network/Addresses sections, sans the redundant ext-address row.
|
|
770
|
+
*/
|
|
771
|
+
_renderCommissionedNodeBorderRouterAnnotation(node) {
|
|
772
|
+
const xaHex = getThreadExtendedAddressHex(node);
|
|
773
|
+
if (!xaHex) return nothing;
|
|
774
|
+
const br = this.borderRouters.get(xaHex);
|
|
775
|
+
if (!br) return nothing;
|
|
776
|
+
const networkRows = this._renderBorderRouterNetworkRows(br);
|
|
777
|
+
const addressRows = this._renderBorderRouterAddressRows(br);
|
|
778
|
+
return html`
|
|
779
|
+
<md-divider></md-divider>
|
|
780
|
+
<div class="section">
|
|
781
|
+
<h4>Also a Border Router</h4>
|
|
782
|
+
${this._renderBorderRouterIdentityRows(br, false)}
|
|
783
|
+
${networkRows !== nothing ? html`
|
|
784
|
+
<div class="subsection-label">Thread Network</div>
|
|
785
|
+
${networkRows}
|
|
786
|
+
` : nothing}
|
|
787
|
+
${addressRows !== nothing ? html`
|
|
788
|
+
<div class="subsection-label">Addresses</div>
|
|
789
|
+
${addressRows}
|
|
790
|
+
` : nothing}
|
|
791
|
+
</div>
|
|
792
|
+
`;
|
|
793
|
+
}
|
|
541
794
|
render() {
|
|
542
795
|
if (this.selectedNodeId === null) {
|
|
543
796
|
return html`
|
|
@@ -584,6 +837,44 @@ let NetworkDetails = class extends LitElement {
|
|
|
584
837
|
` : nothing}
|
|
585
838
|
`;
|
|
586
839
|
}
|
|
840
|
+
const borderRouterId = typeof this.selectedNodeId === "string" && this.selectedNodeId.startsWith("br_") ? this.selectedNodeId : null;
|
|
841
|
+
if (borderRouterId !== null) {
|
|
842
|
+
const onlineSeenByNodes = this._getOnlineSeenByNodes();
|
|
843
|
+
return html`
|
|
844
|
+
<div class="details-panel">
|
|
845
|
+
<div class="header">
|
|
846
|
+
<h3>Border Router</h3>
|
|
847
|
+
<div class="header-actions">
|
|
848
|
+
${onlineSeenByNodes.length > 0 ? html`
|
|
849
|
+
<button
|
|
850
|
+
class="action-button"
|
|
851
|
+
@click=${this._handleUpdateConnections}
|
|
852
|
+
aria-label="Update connection data"
|
|
853
|
+
title="Update connection data"
|
|
854
|
+
>
|
|
855
|
+
<ha-svg-icon .path=${mdiRefresh}></ha-svg-icon>
|
|
856
|
+
</button>
|
|
857
|
+
` : nothing}
|
|
858
|
+
<button class="close-button" @click=${this._handleClose} aria-label="Close details panel">
|
|
859
|
+
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
|
860
|
+
</button>
|
|
861
|
+
</div>
|
|
862
|
+
</div>
|
|
863
|
+
<div class="content">${this._renderBorderRouterInfo(borderRouterId)}</div>
|
|
864
|
+
</div>
|
|
865
|
+
${this._showUpdateDialog ? html`
|
|
866
|
+
<update-connections-dialog
|
|
867
|
+
.client=${this.client}
|
|
868
|
+
.nodes=${this.nodes}
|
|
869
|
+
selectedNodeType="unknown"
|
|
870
|
+
.selectedNodeName=${this._getSelectedNodeName()}
|
|
871
|
+
.selectedNodeId=${this.selectedNodeId}
|
|
872
|
+
.onlineNeighborIds=${onlineSeenByNodes}
|
|
873
|
+
@dialog-closed=${this._handleDialogClose}
|
|
874
|
+
></update-connections-dialog>
|
|
875
|
+
` : nothing}
|
|
876
|
+
`;
|
|
877
|
+
}
|
|
587
878
|
const isAccessPoint = typeof this.selectedNodeId === "string" && this.selectedNodeId.startsWith("ap_");
|
|
588
879
|
if (isAccessPoint) {
|
|
589
880
|
return html`
|
|
@@ -632,7 +923,9 @@ let NetworkDetails = class extends LitElement {
|
|
|
632
923
|
</button>
|
|
633
924
|
</div>
|
|
634
925
|
</div>
|
|
635
|
-
<div class="content"
|
|
926
|
+
<div class="content">
|
|
927
|
+
${this._renderNodeInfo(node)}${this._renderCommissionedNodeBorderRouterAnnotation(node)}
|
|
928
|
+
</div>
|
|
636
929
|
<div class="footer">
|
|
637
930
|
<a href="#node/${this.selectedNodeId}" class="view-link">View node details</a>
|
|
638
931
|
</div>
|
|
@@ -765,6 +1058,15 @@ NetworkDetails.styles = [
|
|
|
765
1058
|
letter-spacing: 0.5px;
|
|
766
1059
|
}
|
|
767
1060
|
|
|
1061
|
+
.subsection-label {
|
|
1062
|
+
margin: 12px 0 4px 0;
|
|
1063
|
+
font-size: 0.75rem;
|
|
1064
|
+
font-weight: 500;
|
|
1065
|
+
color: var(--md-sys-color-on-surface-variant, #666);
|
|
1066
|
+
text-transform: uppercase;
|
|
1067
|
+
letter-spacing: 0.4px;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
768
1070
|
.info-row {
|
|
769
1071
|
display: flex;
|
|
770
1072
|
justify-content: space-between;
|
|
@@ -848,6 +1150,14 @@ NetworkDetails.styles = [
|
|
|
848
1150
|
opacity: 0.8;
|
|
849
1151
|
}
|
|
850
1152
|
|
|
1153
|
+
.direction-hint.reverse-only {
|
|
1154
|
+
font-style: normal;
|
|
1155
|
+
font-weight: 500;
|
|
1156
|
+
opacity: 1;
|
|
1157
|
+
color: var(--md-sys-color-error, #b3261e);
|
|
1158
|
+
cursor: help;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
851
1161
|
.route-info {
|
|
852
1162
|
color: var(--md-sys-color-tertiary, #7d5260);
|
|
853
1163
|
font-size: 0.85em;
|
|
@@ -922,15 +1232,33 @@ NetworkDetails.styles = [
|
|
|
922
1232
|
__decorateClass([
|
|
923
1233
|
property()
|
|
924
1234
|
], NetworkDetails.prototype, "selectedNodeId", 2);
|
|
1235
|
+
__decorateClass([
|
|
1236
|
+
property({ type: Boolean })
|
|
1237
|
+
], NetworkDetails.prototype, "hideOfflineNodes", 2);
|
|
1238
|
+
__decorateClass([
|
|
1239
|
+
property({ type: Boolean })
|
|
1240
|
+
], NetworkDetails.prototype, "hideWeakSignalEdges", 2);
|
|
1241
|
+
__decorateClass([
|
|
1242
|
+
property({ type: Boolean })
|
|
1243
|
+
], NetworkDetails.prototype, "hideMediumSignalEdges", 2);
|
|
1244
|
+
__decorateClass([
|
|
1245
|
+
property({ type: Boolean })
|
|
1246
|
+
], NetworkDetails.prototype, "hideStrongSignalEdges", 2);
|
|
925
1247
|
__decorateClass([
|
|
926
1248
|
property({ type: Object })
|
|
927
1249
|
], NetworkDetails.prototype, "nodes", 2);
|
|
928
1250
|
__decorateClass([
|
|
929
1251
|
property({ type: Object })
|
|
930
1252
|
], NetworkDetails.prototype, "unknownDevices", 2);
|
|
1253
|
+
__decorateClass([
|
|
1254
|
+
property({ attribute: false })
|
|
1255
|
+
], NetworkDetails.prototype, "borderRouters", 2);
|
|
931
1256
|
__decorateClass([
|
|
932
1257
|
property({ type: Object })
|
|
933
1258
|
], NetworkDetails.prototype, "wifiAccessPoints", 2);
|
|
1259
|
+
__decorateClass([
|
|
1260
|
+
property({ type: Object })
|
|
1261
|
+
], NetworkDetails.prototype, "threadEdgePairs", 2);
|
|
934
1262
|
__decorateClass([
|
|
935
1263
|
consume({ context: clientContext })
|
|
936
1264
|
], NetworkDetails.prototype, "client", 2);
|