@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { q as i, P as c, Q as clientContext, n, d as e, i as i$1, R as showPromptDialog, J as showAlertDialog, A, j as b, K as handleAsync, t } from './matter-dashboard-app-
|
|
1
|
+
import { q as i, P as c, Q as clientContext, n, d as e, i as i$1, R as showPromptDialog, J as showAlertDialog, A, j as b, K as handleAsync, t } from './matter-dashboard-app-C9zTE5uH.js';
|
|
2
2
|
import { p as preventDefault } from './prevent_default-D-ohDGsN.js';
|
|
3
3
|
import './main.js';
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { e, m as mixinDelegatesAria, a as mixinConstraintValidation, b as mixinFormAssociated, c as mixinElementInternals, i, _ as __decorate, n as n$1, d as e$1, f as isActivationClick, g as dispatchActivationClick, A, h as e$2, j as b, r as redispatchEvent, k as getFormValue, l as getFormState, o as createValidator, C as CheckboxValidator, p as getValidityAnchor, q as i$1, t, N as NavigableKeys, s as o, u as r, v as createAnimationSignal, L as ListController, w as getActiveItem, x as getLastActivatableItem, y as getFirstActivatableItem, z as o$1, E as EASING, V as Validator, D, B as mixinOnReportValidity, F as onReportValidity, G as u, H as i$2, I as fireAndForget, J as showAlertDialog, K as handleAsync, M as DevModeService } from './matter-dashboard-app-
|
|
1
|
+
import { e, m as mixinDelegatesAria, a as mixinConstraintValidation, b as mixinFormAssociated, c as mixinElementInternals, i, _ as __decorate, n as n$1, d as e$1, f as isActivationClick, g as dispatchActivationClick, A, h as e$2, j as b, r as redispatchEvent, k as getFormValue, l as getFormState, o as createValidator, C as CheckboxValidator, p as getValidityAnchor, q as i$1, t, N as NavigableKeys, s as o, u as r, v as createAnimationSignal, L as ListController, w as getActiveItem, x as getLastActivatableItem, y as getFirstActivatableItem, z as o$1, E as EASING, V as Validator, D, B as mixinOnReportValidity, F as onReportValidity, G as u, H as i$2, I as fireAndForget, J as showAlertDialog, K as handleAsync, M as DevModeService } from './matter-dashboard-app-C9zTE5uH.js';
|
|
2
2
|
import { p as preventDefault } from './prevent_default-D-ohDGsN.js';
|
|
3
3
|
import './main.js';
|
|
4
4
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matter-server/dashboard",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Dashboard for OHF Matter Server",
|
|
5
5
|
"homepage": "https://github.com/matter-js/matterjs-server",
|
|
6
6
|
"bugs": {
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@lit/context": "^1.1.6",
|
|
35
35
|
"@material/web": "^2.4.1",
|
|
36
|
-
"@matter-server/custom-clusters": "0.6.
|
|
37
|
-
"@matter-server/ws-client": "0.6.
|
|
36
|
+
"@matter-server/custom-clusters": "0.6.3",
|
|
37
|
+
"@matter-server/ws-client": "0.6.3",
|
|
38
38
|
"@mdi/js": "^7.4.47",
|
|
39
39
|
"lit": "^3.3.2",
|
|
40
40
|
"tslib": "^2.8.1",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@babel/preset-env": "^7.29.2",
|
|
45
|
-
"@matter/main": "0.17.0-alpha.0-
|
|
45
|
+
"@matter/main": "0.17.0-alpha.0-20260429-8fd498888",
|
|
46
46
|
"@rollup/plugin-babel": "^7.0.0",
|
|
47
47
|
"@rollup/plugin-commonjs": "^29.0.2",
|
|
48
48
|
"@rollup/plugin-json": "^6.1.0",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { MatterClient, MatterNode } from "@matter-server/ws-client";
|
|
8
|
-
import { mdiFitToScreen, mdiMagnifyMinus, mdiMagnifyPlus, mdiPause, mdiPlay } from "@mdi/js";
|
|
8
|
+
import { mdiEyeOff, mdiFitToScreen, mdiMagnifyMinus, mdiMagnifyPlus, mdiPause, mdiPlay } from "@mdi/js";
|
|
9
9
|
import { css, html, LitElement } from "lit";
|
|
10
10
|
import { customElement, property, query, state } from "lit/decorators.js";
|
|
11
11
|
import "../components/ha-svg-icon";
|
|
@@ -13,6 +13,7 @@ import { reducedMotionStyles } from "../util/shared-styles.js";
|
|
|
13
13
|
import "./components/footer";
|
|
14
14
|
import "./components/header";
|
|
15
15
|
import type { ActiveView } from "./components/header.js";
|
|
16
|
+
import { BorderRouterStore } from "./network/border-router-store.js";
|
|
16
17
|
import "./network/device-panel";
|
|
17
18
|
import "./network/network-details";
|
|
18
19
|
import "./network/thread-graph";
|
|
@@ -26,6 +27,15 @@ declare global {
|
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
type HideOptionKey = "_hideOfflineNodes" | "_hideWeakSignalEdges" | "_hideMediumSignalEdges" | "_hideStrongSignalEdges";
|
|
31
|
+
|
|
32
|
+
const HIDE_OPTIONS: readonly { key: HideOptionKey; label: string }[] = [
|
|
33
|
+
{ key: "_hideOfflineNodes", label: "Offline nodes" },
|
|
34
|
+
{ key: "_hideWeakSignalEdges", label: "Weak signal edges" },
|
|
35
|
+
{ key: "_hideMediumSignalEdges", label: "Medium signal edges" },
|
|
36
|
+
{ key: "_hideStrongSignalEdges", label: "Strong signal edges" },
|
|
37
|
+
];
|
|
38
|
+
|
|
29
39
|
@customElement("matter-network-view")
|
|
30
40
|
class MatterNetworkView extends LitElement {
|
|
31
41
|
public client!: MatterClient;
|
|
@@ -61,6 +71,24 @@ class MatterNetworkView extends LitElement {
|
|
|
61
71
|
@state()
|
|
62
72
|
private _threadAddressSearchStatus: "idle" | "found" | "not-found" = "idle";
|
|
63
73
|
|
|
74
|
+
@state()
|
|
75
|
+
private _borderRouterStore = new BorderRouterStore();
|
|
76
|
+
|
|
77
|
+
@state()
|
|
78
|
+
private _showHideMenu = false;
|
|
79
|
+
|
|
80
|
+
@state()
|
|
81
|
+
private _hideOfflineNodes = false;
|
|
82
|
+
|
|
83
|
+
@state()
|
|
84
|
+
private _hideWeakSignalEdges = false;
|
|
85
|
+
|
|
86
|
+
@state()
|
|
87
|
+
private _hideMediumSignalEdges = false;
|
|
88
|
+
|
|
89
|
+
@state()
|
|
90
|
+
private _hideStrongSignalEdges = false;
|
|
91
|
+
|
|
64
92
|
private _initialSelectionApplied = false;
|
|
65
93
|
private _selectRetryTimer?: ReturnType<typeof setTimeout>;
|
|
66
94
|
|
|
@@ -78,9 +106,37 @@ class MatterNetworkView extends LitElement {
|
|
|
78
106
|
}
|
|
79
107
|
}
|
|
80
108
|
|
|
109
|
+
override firstUpdated(): void {
|
|
110
|
+
if (this.networkType === "thread") {
|
|
111
|
+
void this._refreshBorderRouters();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private async _refreshBorderRouters(): Promise<void> {
|
|
116
|
+
try {
|
|
117
|
+
await this._borderRouterStore.refresh(this.client);
|
|
118
|
+
this.requestUpdate();
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.warn("Failed to refresh border router store:", err);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private _handleConnectionsUpdated(): void {
|
|
125
|
+
// Skip the BR snapshot refresh when the user updated a WiFi node — the dialog fires
|
|
126
|
+
// the same event for both network types but BR data is Thread-only.
|
|
127
|
+
if (this.networkType !== "thread") return;
|
|
128
|
+
void this._refreshBorderRouters();
|
|
129
|
+
}
|
|
130
|
+
|
|
81
131
|
override updated(changedProperties: Map<string, unknown>): void {
|
|
82
132
|
super.updated(changedProperties);
|
|
83
133
|
|
|
134
|
+
// Lazily refresh the BR snapshot the first time the user switches into the Thread
|
|
135
|
+
// view, in case firstUpdated() fired while the WiFi view was active.
|
|
136
|
+
if (changedProperties.has("networkType") && this.networkType === "thread") {
|
|
137
|
+
void this._refreshBorderRouters();
|
|
138
|
+
}
|
|
139
|
+
|
|
84
140
|
// After render, select the node in the graph
|
|
85
141
|
if (!this._initialSelectionApplied && this.initialSelectedNodeId !== null) {
|
|
86
142
|
this._initialSelectionApplied = true;
|
|
@@ -89,8 +145,16 @@ class MatterNetworkView extends LitElement {
|
|
|
89
145
|
}
|
|
90
146
|
}
|
|
91
147
|
|
|
148
|
+
override connectedCallback(): void {
|
|
149
|
+
super.connectedCallback();
|
|
150
|
+
document.addEventListener("click", this._documentClickHandler);
|
|
151
|
+
document.addEventListener("keydown", this._documentKeyHandler);
|
|
152
|
+
}
|
|
153
|
+
|
|
92
154
|
override disconnectedCallback(): void {
|
|
93
155
|
super.disconnectedCallback();
|
|
156
|
+
document.removeEventListener("click", this._documentClickHandler);
|
|
157
|
+
document.removeEventListener("keydown", this._documentKeyHandler);
|
|
94
158
|
if (this._selectRetryTimer) {
|
|
95
159
|
clearTimeout(this._selectRetryTimer);
|
|
96
160
|
}
|
|
@@ -116,6 +180,12 @@ class MatterNetworkView extends LitElement {
|
|
|
116
180
|
|
|
117
181
|
private _handleDetailsClose(): void {
|
|
118
182
|
this._selectedNodeId = null;
|
|
183
|
+
// Tell the graph to deselect and clear highlights
|
|
184
|
+
if (this.networkType === "thread") {
|
|
185
|
+
this._threadGraph?.deselectAll();
|
|
186
|
+
} else {
|
|
187
|
+
this._wifiGraph?.deselectAll();
|
|
188
|
+
}
|
|
119
189
|
}
|
|
120
190
|
|
|
121
191
|
private _handleSelectNode(event: CustomEvent<{ nodeId: number | string }>): void {
|
|
@@ -153,6 +223,31 @@ class MatterNetworkView extends LitElement {
|
|
|
153
223
|
}
|
|
154
224
|
}
|
|
155
225
|
|
|
226
|
+
private _handleToggleHideMenu(): void {
|
|
227
|
+
this._showHideMenu = !this._showHideMenu;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private readonly _documentClickHandler = (event: MouseEvent): void => {
|
|
231
|
+
const path = event.composedPath();
|
|
232
|
+
if (!path.some(el => el instanceof HTMLElement && el.classList.contains("hide-menu-container"))) {
|
|
233
|
+
this._showHideMenu = false;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
private readonly _documentKeyHandler = (event: KeyboardEvent): void => {
|
|
238
|
+
if (event.key === "Escape" && this._showHideMenu) {
|
|
239
|
+
this._showHideMenu = false;
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
private _handleToggleHideOption(option: HideOptionKey): void {
|
|
244
|
+
this[option] = !this[option];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private _isAnyHideOptionActive(): boolean {
|
|
248
|
+
return HIDE_OPTIONS.some(option => this[option.key]);
|
|
249
|
+
}
|
|
250
|
+
|
|
156
251
|
private _handleTogglePhysics(): void {
|
|
157
252
|
const newState = !this._physicsEnabled;
|
|
158
253
|
this._physicsEnabled = newState;
|
|
@@ -231,6 +326,41 @@ class MatterNetworkView extends LitElement {
|
|
|
231
326
|
<button class="control-button" @click=${this._handleFitToScreen} title="Fit to screen">
|
|
232
327
|
<ha-svg-icon .path=${mdiFitToScreen}></ha-svg-icon>
|
|
233
328
|
</button>
|
|
329
|
+
<div class="hide-menu-container">
|
|
330
|
+
<button
|
|
331
|
+
class="control-button ${this._isAnyHideOptionActive() ? "active" : ""}"
|
|
332
|
+
@click=${this._handleToggleHideMenu}
|
|
333
|
+
title="Hide options"
|
|
334
|
+
aria-haspopup="true"
|
|
335
|
+
aria-expanded=${this._showHideMenu}
|
|
336
|
+
aria-controls="hide-options-menu"
|
|
337
|
+
>
|
|
338
|
+
<ha-svg-icon .path=${mdiEyeOff}></ha-svg-icon>
|
|
339
|
+
</button>
|
|
340
|
+
${this._showHideMenu
|
|
341
|
+
? html`
|
|
342
|
+
<div
|
|
343
|
+
id="hide-options-menu"
|
|
344
|
+
class="hide-dropdown"
|
|
345
|
+
role="group"
|
|
346
|
+
aria-label="Hide options"
|
|
347
|
+
>
|
|
348
|
+
${HIDE_OPTIONS.map(
|
|
349
|
+
option => html`
|
|
350
|
+
<label class="hide-option">
|
|
351
|
+
<input
|
|
352
|
+
type="checkbox"
|
|
353
|
+
.checked=${this[option.key]}
|
|
354
|
+
@change=${() => this._handleToggleHideOption(option.key)}
|
|
355
|
+
/>
|
|
356
|
+
<span>${option.label}</span>
|
|
357
|
+
</label>
|
|
358
|
+
`,
|
|
359
|
+
)}
|
|
360
|
+
</div>
|
|
361
|
+
`
|
|
362
|
+
: ""}
|
|
363
|
+
</div>
|
|
234
364
|
<button
|
|
235
365
|
class="control-button ${this._physicsEnabled ? "" : "active"}"
|
|
236
366
|
@click=${this._handleTogglePhysics}
|
|
@@ -250,6 +380,11 @@ class MatterNetworkView extends LitElement {
|
|
|
250
380
|
</div>`}
|
|
251
381
|
<thread-graph
|
|
252
382
|
.nodes=${this.nodes}
|
|
383
|
+
.borderRouters=${this._borderRouterStore.entries}
|
|
384
|
+
.hideOfflineNodes=${this._hideOfflineNodes}
|
|
385
|
+
.hideWeakSignalEdges=${this._hideWeakSignalEdges}
|
|
386
|
+
.hideMediumSignalEdges=${this._hideMediumSignalEdges}
|
|
387
|
+
.hideStrongSignalEdges=${this._hideStrongSignalEdges}
|
|
253
388
|
@node-selected=${this._handleNodeSelected}
|
|
254
389
|
@physics-changed=${this._handlePhysicsChanged}
|
|
255
390
|
></thread-graph>
|
|
@@ -294,6 +429,7 @@ class MatterNetworkView extends LitElement {
|
|
|
294
429
|
const showSidebar = this._selectedNodeId !== null;
|
|
295
430
|
const unknownDevices = this._threadGraph?.unknownDevicesMap ?? new Map();
|
|
296
431
|
const wifiAccessPoints = this._wifiGraph?.wifiAccessPointsMap ?? new Map();
|
|
432
|
+
const threadEdgePairs = this._threadGraph?.edgePairs ?? new Map();
|
|
297
433
|
|
|
298
434
|
return html`
|
|
299
435
|
<dashboard-header
|
|
@@ -314,9 +450,16 @@ class MatterNetworkView extends LitElement {
|
|
|
314
450
|
.selectedNodeId=${this._selectedNodeId}
|
|
315
451
|
.nodes=${this.nodes}
|
|
316
452
|
.unknownDevices=${unknownDevices}
|
|
453
|
+
.borderRouters=${this._borderRouterStore.entries}
|
|
317
454
|
.wifiAccessPoints=${wifiAccessPoints}
|
|
455
|
+
.threadEdgePairs=${threadEdgePairs}
|
|
456
|
+
.hideOfflineNodes=${this._hideOfflineNodes}
|
|
457
|
+
.hideWeakSignalEdges=${this._hideWeakSignalEdges}
|
|
458
|
+
.hideMediumSignalEdges=${this._hideMediumSignalEdges}
|
|
459
|
+
.hideStrongSignalEdges=${this._hideStrongSignalEdges}
|
|
318
460
|
@close=${this._handleDetailsClose}
|
|
319
461
|
@select-node=${this._handleSelectNode}
|
|
462
|
+
@connections-updated=${this._handleConnectionsUpdated}
|
|
320
463
|
></network-details>
|
|
321
464
|
</aside>
|
|
322
465
|
</div>
|
|
@@ -480,6 +623,47 @@ class MatterNetworkView extends LitElement {
|
|
|
480
623
|
--icon-primary-color: var(--md-sys-color-on-primary-container, #21005d);
|
|
481
624
|
}
|
|
482
625
|
|
|
626
|
+
.hide-menu-container {
|
|
627
|
+
position: relative;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
.hide-dropdown {
|
|
631
|
+
position: absolute;
|
|
632
|
+
top: calc(100% + 4px);
|
|
633
|
+
right: 0;
|
|
634
|
+
background: var(--md-sys-color-surface-container, #fff);
|
|
635
|
+
border: 1px solid var(--md-sys-color-outline-variant, #ccc);
|
|
636
|
+
border-radius: 4px;
|
|
637
|
+
box-shadow: var(--md-sys-elevation-level2, 0 2px 6px var(--md-sys-color-shadow, rgba(0, 0, 0, 0.15)));
|
|
638
|
+
min-width: 150px;
|
|
639
|
+
z-index: 100;
|
|
640
|
+
padding: 4px 0;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.hide-option {
|
|
644
|
+
display: flex;
|
|
645
|
+
align-items: center;
|
|
646
|
+
gap: 8px;
|
|
647
|
+
padding: 8px 12px;
|
|
648
|
+
cursor: pointer;
|
|
649
|
+
color: var(--md-sys-color-on-surface, #1c1b1f);
|
|
650
|
+
font-size: 0.9rem;
|
|
651
|
+
user-select: none;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.hide-option:hover {
|
|
655
|
+
background-color: var(--md-sys-color-surface-container-high, #e8e8e8);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.hide-option input[type="checkbox"] {
|
|
659
|
+
cursor: pointer;
|
|
660
|
+
margin: 0;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.hide-option span {
|
|
664
|
+
flex: 1;
|
|
665
|
+
}
|
|
666
|
+
|
|
483
667
|
.graph-section thread-graph,
|
|
484
668
|
.graph-section wifi-graph {
|
|
485
669
|
flex: 1 1 0;
|
|
@@ -406,6 +406,16 @@ export abstract class BaseNetworkGraph extends LitElement {
|
|
|
406
406
|
});
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
+
/**
|
|
410
|
+
* Deselects all nodes, clears highlights, and restores default styling.
|
|
411
|
+
*/
|
|
412
|
+
public deselectAll(): void {
|
|
413
|
+
if (!this._network) return;
|
|
414
|
+
this._selectedNodeId = null;
|
|
415
|
+
this._network.unselectAll();
|
|
416
|
+
this._clearHighlights();
|
|
417
|
+
}
|
|
418
|
+
|
|
409
419
|
protected _dispatchNodeSelected(nodeId: number | string | null): void {
|
|
410
420
|
this.dispatchEvent(
|
|
411
421
|
new CustomEvent("node-selected", {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025-2026 Open Home Foundation
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { BorderRouterEntry, MatterClient } from "@matter-server/ws-client";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Snapshot of mDNS-discovered Thread Border Routers.
|
|
11
|
+
*
|
|
12
|
+
* Refreshed on Thread-graph mount and on the reload-button click. The map is keyed by the
|
|
13
|
+
* uppercase 16-char xa hex so callers can join against neighbor-table extended addresses
|
|
14
|
+
* normalized to the same casing.
|
|
15
|
+
*/
|
|
16
|
+
export class BorderRouterStore {
|
|
17
|
+
// Replaced (not mutated) on every refresh so Lit consumers passing this map as a
|
|
18
|
+
// @property() detect the snapshot change via the default `===` identity compare and
|
|
19
|
+
// re-render. Mutating in place would keep the same reference and silently skip updates.
|
|
20
|
+
#entries: ReadonlyMap<string, BorderRouterEntry> = new Map();
|
|
21
|
+
|
|
22
|
+
get entries(): ReadonlyMap<string, BorderRouterEntry> {
|
|
23
|
+
return this.#entries;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async refresh(client: MatterClient): Promise<void> {
|
|
27
|
+
const list = await client.sendCommand("get_thread_border_routers", 0, {});
|
|
28
|
+
const next = new Map<string, BorderRouterEntry>();
|
|
29
|
+
for (const entry of list) {
|
|
30
|
+
next.set(entry.extAddressHex.toUpperCase(), entry);
|
|
31
|
+
}
|
|
32
|
+
this.#entries = next;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
reset(): void {
|
|
36
|
+
this.#entries = new Map();
|
|
37
|
+
}
|
|
38
|
+
}
|