@matter-server/dashboard 0.3.3 → 0.3.5-alpha.0-20260203-66f5d74

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 (84) hide show
  1. package/README.md +76 -0
  2. package/dist/esm/components/dialogs/binding/node-binding-dialog.d.ts.map +1 -1
  3. package/dist/esm/components/dialogs/binding/node-binding-dialog.js +44 -17
  4. package/dist/esm/components/dialogs/binding/node-binding-dialog.js.map +1 -1
  5. package/dist/esm/entrypoint/main.js +1 -1
  6. package/dist/esm/entrypoint/main.js.map +1 -1
  7. package/dist/esm/pages/components/node-details.js +1 -1
  8. package/dist/esm/pages/components/node-details.js.map +1 -1
  9. package/dist/esm/pages/matter-cluster-view.d.ts.map +1 -1
  10. package/dist/esm/pages/matter-cluster-view.js +9 -4
  11. package/dist/esm/pages/matter-cluster-view.js.map +1 -1
  12. package/dist/esm/pages/matter-endpoint-view.d.ts.map +1 -1
  13. package/dist/esm/pages/matter-endpoint-view.js +8 -2
  14. package/dist/esm/pages/matter-endpoint-view.js.map +1 -1
  15. package/dist/esm/pages/matter-network-view.d.ts +5 -0
  16. package/dist/esm/pages/matter-network-view.d.ts.map +1 -1
  17. package/dist/esm/pages/matter-network-view.js +96 -13
  18. package/dist/esm/pages/matter-network-view.js.map +1 -1
  19. package/dist/esm/pages/matter-node-view.d.ts.map +1 -1
  20. package/dist/esm/pages/matter-node-view.js +17 -3
  21. package/dist/esm/pages/matter-node-view.js.map +1 -1
  22. package/dist/esm/pages/network/base-network-graph.d.ts +34 -0
  23. package/dist/esm/pages/network/base-network-graph.d.ts.map +1 -1
  24. package/dist/esm/pages/network/base-network-graph.js +116 -25
  25. package/dist/esm/pages/network/base-network-graph.js.map +1 -1
  26. package/dist/esm/pages/network/device-icons.js +1 -1
  27. package/dist/esm/pages/network/network-details.d.ts +31 -1
  28. package/dist/esm/pages/network/network-details.d.ts.map +1 -1
  29. package/dist/esm/pages/network/network-details.js +233 -15
  30. package/dist/esm/pages/network/network-details.js.map +1 -1
  31. package/dist/esm/pages/network/network-types.d.ts +6 -0
  32. package/dist/esm/pages/network/network-types.d.ts.map +1 -1
  33. package/dist/esm/pages/network/network-utils.d.ts +26 -0
  34. package/dist/esm/pages/network/network-utils.d.ts.map +1 -1
  35. package/dist/esm/pages/network/network-utils.js +71 -3
  36. package/dist/esm/pages/network/network-utils.js.map +1 -1
  37. package/dist/esm/pages/network/thread-graph.d.ts.map +1 -1
  38. package/dist/esm/pages/network/thread-graph.js +5 -2
  39. package/dist/esm/pages/network/thread-graph.js.map +1 -1
  40. package/dist/esm/pages/network/update-connections-dialog.d.ts +55 -0
  41. package/dist/esm/pages/network/update-connections-dialog.d.ts.map +1 -0
  42. package/dist/esm/pages/network/update-connections-dialog.js +284 -0
  43. package/dist/esm/pages/network/update-connections-dialog.js.map +6 -0
  44. package/dist/esm/pages/network/wifi-graph.d.ts.map +1 -1
  45. package/dist/esm/pages/network/wifi-graph.js +5 -3
  46. package/dist/esm/pages/network/wifi-graph.js.map +1 -1
  47. package/dist/esm/util/format_hex.d.ts +18 -0
  48. package/dist/esm/util/format_hex.d.ts.map +1 -1
  49. package/dist/esm/util/format_hex.js +21 -1
  50. package/dist/esm/util/format_hex.js.map +1 -1
  51. package/dist/esm/util/matter-status.d.ts +49 -0
  52. package/dist/esm/util/matter-status.d.ts.map +1 -0
  53. package/dist/esm/util/matter-status.js +110 -0
  54. package/dist/esm/util/matter-status.js.map +6 -0
  55. package/dist/web/js/{commission-node-dialog-B1_khzZb.js → commission-node-dialog-DBugVQOl.js} +5 -5
  56. package/dist/web/js/{commission-node-existing-RpdajrwF.js → commission-node-existing-ts2MN2bQ.js} +2 -2
  57. package/dist/web/js/{commission-node-thread-5f2itkTG.js → commission-node-thread-CixfNuM3.js} +2 -2
  58. package/dist/web/js/{commission-node-wifi-DZ_pWqsa.js → commission-node-wifi-CWC30EYn.js} +2 -2
  59. package/dist/web/js/{dialog-box-DEUxM4B1.js → dialog-box-SeMuFiWx.js} +2 -2
  60. package/dist/web/js/{fire_event-BczBMT8E.js → fire_event-B63oGTcK.js} +1 -1
  61. package/dist/web/js/{log-level-dialog-Cr3PfX1X.js → log-level-dialog-J0gFiLLM.js} +2 -2
  62. package/dist/web/js/main.js +2 -2
  63. package/dist/web/js/{matter-dashboard-app-BuCe_Jxf.js → matter-dashboard-app-D7YWrgXj.js} +2041 -246
  64. package/dist/web/js/{node-binding-dialog-DMiHNDLA.js → node-binding-dialog-DSBwIJcQ.js} +147 -19
  65. package/dist/web/js/prevent_default-D-ohDGsN.js +8 -0
  66. package/package.json +4 -4
  67. package/src/components/dialogs/binding/node-binding-dialog.ts +45 -21
  68. package/src/entrypoint/main.ts +2 -0
  69. package/src/pages/components/node-details.ts +1 -1
  70. package/src/pages/matter-cluster-view.ts +11 -4
  71. package/src/pages/matter-endpoint-view.ts +10 -3
  72. package/src/pages/matter-network-view.ts +102 -13
  73. package/src/pages/matter-node-view.ts +19 -4
  74. package/src/pages/network/base-network-graph.ts +147 -33
  75. package/src/pages/network/device-icons.ts +1 -1
  76. package/src/pages/network/network-details.ts +281 -16
  77. package/src/pages/network/network-types.ts +6 -0
  78. package/src/pages/network/network-utils.ts +109 -0
  79. package/src/pages/network/thread-graph.ts +7 -1
  80. package/src/pages/network/update-connections-dialog.ts +327 -0
  81. package/src/pages/network/wifi-graph.ts +4 -3
  82. package/src/util/format_hex.ts +39 -0
  83. package/src/util/matter-status.ts +165 -0
  84. package/dist/web/js/prevent_default-D4FX_PIh.js +0 -774
@@ -8,12 +8,13 @@ import "@material/web/divider/divider";
8
8
  import "@material/web/iconbutton/icon-button";
9
9
  import "@material/web/list/list";
10
10
  import "@material/web/list/list-item";
11
- import { MatterClient, MatterNode } from "@matter-server/ws-client";
11
+ import { isTestNodeId, MatterClient, MatterNode } from "@matter-server/ws-client";
12
12
  import { mdiChevronRight, mdiGraphOutline } from "@mdi/js";
13
- import { LitElement, css, html } from "lit";
13
+ import { css, html, LitElement } from "lit";
14
14
  import { customElement, property } from "lit/decorators.js";
15
15
  import { guard } from "lit/directives/guard.js";
16
16
  import "../components/ha-svg-icon";
17
+ import { formatNodeAddress, getEffectiveFabricIndex } from "../util/format_hex.js";
17
18
  import "./components/header";
18
19
  import "./components/node-details";
19
20
  import { getEndpointDeviceTypes } from "./matter-endpoint-view.js";
@@ -54,9 +55,16 @@ class MatterNodeView extends LitElement {
54
55
  const graphViewType = networkType === "ethernet" ? "wifi" : networkType;
55
56
  const graphUrl = showGraphButton ? `#${graphViewType}/${this.node.node_id}` : null;
56
57
 
58
+ // Format node address for hex display
59
+ const fabricIndex = getEffectiveFabricIndex(
60
+ this.client.serverInfo.fabric_index,
61
+ isTestNodeId(this.node.node_id),
62
+ );
63
+ const nodeHex = formatNodeAddress(fabricIndex, this.node.node_id);
64
+
57
65
  return html`
58
66
  <dashboard-header
59
- .title=${"Node " + this.node.node_id}
67
+ .title=${`Node ${this.node.node_id} ${nodeHex}`}
60
68
  .client=${this.client}
61
69
  backButton="#"
62
70
  ></dashboard-header>
@@ -64,7 +72,7 @@ class MatterNodeView extends LitElement {
64
72
  <!-- node details section -->
65
73
  <div class="container">
66
74
  <div class="node-title-bar">
67
- <h2>Node ${this.node.node_id}</h2>
75
+ <h2>Node ${this.node.node_id} <span class="node-id-hex">${nodeHex}</span></h2>
68
76
  ${showGraphButton
69
77
  ? html`
70
78
  <a href=${graphUrl} class="show-in-graph-button" title="Show in ${graphViewType} graph">
@@ -156,6 +164,13 @@ class MatterNodeView extends LitElement {
156
164
  color: var(--md-sys-color-on-background, #333);
157
165
  }
158
166
 
167
+ .node-id-hex {
168
+ font-size: 0.75em;
169
+ font-weight: 400;
170
+ color: var(--md-sys-color-on-surface-variant, #666);
171
+ font-family: monospace;
172
+ }
173
+
159
174
  .show-in-graph-button {
160
175
  display: inline-flex;
161
176
  align-items: center;
@@ -23,6 +23,9 @@ export abstract class BaseNetworkGraph extends LitElement {
23
23
  @state()
24
24
  protected _selectedNodeId: number | string | null = null;
25
25
 
26
+ @state()
27
+ protected _physicsEnabled = true;
28
+
26
29
  protected _network?: Network;
27
30
  protected _nodesDataSet?: DataSet<NetworkGraphNode>;
28
31
  protected _edgesDataSet?: DataSet<NetworkGraphEdge>;
@@ -30,6 +33,11 @@ export abstract class BaseNetworkGraph extends LitElement {
30
33
  protected _resizeObserver?: ResizeObserver;
31
34
  protected _themeUnsubscribe?: () => void;
32
35
  protected _updateDebounceTimer?: ReturnType<typeof setTimeout>;
36
+ protected _autoFreezeTimer?: ReturnType<typeof setTimeout>;
37
+ /** Whether auto-freeze has already been applied (to avoid re-freezing after user unfreezes) */
38
+ private _autoFreezeApplied = false;
39
+ /** Whether initial fit has been done (to preserve user's zoom/pan after first load) */
40
+ private _initialFitDone = false;
33
41
 
34
42
  /** Store original edge colors for restoration after highlighting */
35
43
  private _originalEdgeColors: Map<string, { color: string; highlight: string }> = new Map();
@@ -133,17 +141,31 @@ export abstract class BaseNetworkGraph extends LitElement {
133
141
 
134
142
  // Subscribe to theme changes - refresh entire graph to update colors
135
143
  this._themeUnsubscribe = ThemeService.subscribe(() => {
136
- if (this._network) {
137
- // Update font colors
144
+ if (this._network && this._nodesDataSet) {
145
+ const fontColor = this._getFontColor();
146
+
147
+ // Update default font colors for new nodes
138
148
  this._network.setOptions({
139
149
  nodes: {
140
150
  font: {
141
- color: this._getFontColor(),
151
+ color: fontColor,
142
152
  },
143
153
  },
144
154
  });
155
+
145
156
  // Regenerate node icons and edges with new theme colors
146
157
  this._updateGraph();
158
+
159
+ // Update font color on all existing nodes in the dataset
160
+ const allNodes = this._nodesDataSet.get();
161
+ const nodeUpdates = allNodes.map((node: NetworkGraphNode) => ({
162
+ id: node.id,
163
+ font: { color: fontColor },
164
+ }));
165
+ this._nodesDataSet.update(nodeUpdates);
166
+
167
+ // Force redraw
168
+ this._network.redraw();
147
169
  }
148
170
  });
149
171
  }
@@ -152,6 +174,7 @@ export abstract class BaseNetworkGraph extends LitElement {
152
174
  super.disconnectedCallback();
153
175
  this._resizeObserver?.disconnect();
154
176
  this._themeUnsubscribe?.();
177
+ this._cancelAutoFreezeTimer();
155
178
  if (this._updateDebounceTimer) {
156
179
  clearTimeout(this._updateDebounceTimer);
157
180
  }
@@ -229,25 +252,21 @@ export abstract class BaseNetworkGraph extends LitElement {
229
252
  this._dispatchNodeSelected(null);
230
253
  });
231
254
 
232
- // Auto-fit after stabilization completes
255
+ // Auto-fit after initial stabilization completes (only once)
233
256
  this._network.on("stabilizationIterationsDone", () => {
234
- // Fit with padding to keep nodes away from edges
235
- this._network?.fit({
236
- animation: {
237
- duration: 500,
238
- easingFunction: "easeInOutQuad",
239
- },
240
- });
241
- });
257
+ if (!this._initialFitDone) {
258
+ // Fit with padding to keep nodes away from edges
259
+ this._network?.fit({
260
+ animation: {
261
+ duration: 500,
262
+ easingFunction: "easeInOutQuad",
263
+ },
264
+ });
265
+ this._initialFitDone = true;
242
266
 
243
- // Also fit when physics fully stops (catches any drift after initial stabilization)
244
- this._network.on("stabilized", () => {
245
- this._network?.fit({
246
- animation: {
247
- duration: 300,
248
- easingFunction: "easeInOutQuad",
249
- },
250
- });
267
+ // Start auto-freeze timer (5 seconds after initial stabilization)
268
+ this._startAutoFreezeTimer();
269
+ }
251
270
  });
252
271
 
253
272
  this._updateGraph();
@@ -272,6 +291,99 @@ export abstract class BaseNetworkGraph extends LitElement {
272
291
  });
273
292
  }
274
293
 
294
+ /**
295
+ * Zooms in by 20%.
296
+ */
297
+ public zoomIn(): void {
298
+ if (!this._network) return;
299
+ const scale = this._network.getScale();
300
+ this._network.moveTo({
301
+ scale: scale * 1.2,
302
+ animation: {
303
+ duration: 200,
304
+ easingFunction: "easeInOutQuad",
305
+ },
306
+ });
307
+ }
308
+
309
+ /**
310
+ * Zooms out by 20%.
311
+ */
312
+ public zoomOut(): void {
313
+ if (!this._network) return;
314
+ const scale = this._network.getScale();
315
+ this._network.moveTo({
316
+ scale: scale / 1.2,
317
+ animation: {
318
+ duration: 200,
319
+ easingFunction: "easeInOutQuad",
320
+ },
321
+ });
322
+ }
323
+
324
+ /**
325
+ * Returns whether physics simulation is currently enabled.
326
+ */
327
+ public get physicsEnabled(): boolean {
328
+ return this._physicsEnabled;
329
+ }
330
+
331
+ /**
332
+ * Enables or disables physics simulation (node movement/settling).
333
+ * When disabled, nodes freeze in place; when enabled, they resume settling.
334
+ * @param enabled Whether to enable physics
335
+ * @param isManual If true, cancels any pending auto-freeze (user manually toggled)
336
+ */
337
+ public setPhysicsEnabled(enabled: boolean, isManual = true): void {
338
+ // If user manually toggles, cancel auto-freeze to respect their choice
339
+ if (isManual) {
340
+ this._cancelAutoFreezeTimer();
341
+ this._autoFreezeApplied = true; // Prevent future auto-freeze
342
+ }
343
+
344
+ this._physicsEnabled = enabled;
345
+ this._network?.setOptions({
346
+ physics: { enabled },
347
+ });
348
+ }
349
+
350
+ /**
351
+ * Starts the auto-freeze timer. Physics will be disabled after 5 seconds
352
+ * unless the user manually toggles physics or the timer is cancelled.
353
+ */
354
+ private _startAutoFreezeTimer(): void {
355
+ // Don't auto-freeze if already applied or user has manually controlled physics
356
+ if (this._autoFreezeApplied) {
357
+ return;
358
+ }
359
+
360
+ this._cancelAutoFreezeTimer();
361
+ this._autoFreezeTimer = setTimeout(() => {
362
+ if (!this._autoFreezeApplied && this._physicsEnabled) {
363
+ this.setPhysicsEnabled(false, false); // false = not manual, don't mark as applied
364
+ this._autoFreezeApplied = true;
365
+ // Dispatch event so parent can update UI state
366
+ this.dispatchEvent(
367
+ new CustomEvent("physics-changed", {
368
+ detail: { enabled: false },
369
+ bubbles: true,
370
+ composed: true,
371
+ }),
372
+ );
373
+ }
374
+ }, 5000);
375
+ }
376
+
377
+ /**
378
+ * Cancels any pending auto-freeze timer.
379
+ */
380
+ private _cancelAutoFreezeTimer(): void {
381
+ if (this._autoFreezeTimer) {
382
+ clearTimeout(this._autoFreezeTimer);
383
+ this._autoFreezeTimer = undefined;
384
+ }
385
+ }
386
+
275
387
  /**
276
388
  * Selects a node by ID and focuses on it.
277
389
  */
@@ -309,16 +421,20 @@ export abstract class BaseNetworkGraph extends LitElement {
309
421
  protected _highlightConnections(nodeId: number | string): void {
310
422
  if (!this._edgesDataSet || !this._nodesDataSet) return;
311
423
 
312
- // Store original colors before highlighting
313
424
  const allEdges = this._edgesDataSet.get();
425
+
426
+ // First pass: Store original colors ONLY if not already stored.
427
+ // We must do this BEFORE any highlighting modifies edge colors.
428
+ // If edges were already highlighted (switching between nodes), the colors
429
+ // in the DataSet might be dimmed, so we rely on previously stored values.
314
430
  for (const edge of allEdges) {
315
431
  const edgeId = String(edge.id);
316
432
  if (!this._originalEdgeColors.has(edgeId)) {
317
- const colorObj = edge.color as { color: string; highlight: string };
318
- this._originalEdgeColors.set(edgeId, {
319
- color: colorObj?.color ?? "#999999",
320
- highlight: colorObj?.highlight ?? "#999999",
321
- });
433
+ const colorObj = edge.color as { color: string; highlight: string } | undefined;
434
+ // Extract colors, with fallbacks
435
+ const color = colorObj?.color ?? "#999999";
436
+ const highlight = colorObj?.highlight ?? color;
437
+ this._originalEdgeColors.set(edgeId, { color, highlight });
322
438
  }
323
439
  }
324
440
 
@@ -337,21 +453,19 @@ export abstract class BaseNetworkGraph extends LitElement {
337
453
  }
338
454
  }
339
455
 
340
- // Update edges - make connected ones thicker
456
+ // Update edges - make connected ones thicker, dim non-connected ones
341
457
  const dimmedColor = this._getDimmedEdgeColor();
342
458
  const edgeUpdates = allEdges.map((edge: NetworkGraphEdge) => {
343
459
  const isConnected = edge.from === nodeId || edge.to === nodeId;
344
460
  const originalColor = this._originalEdgeColors.get(String(edge.id));
461
+ // Use stored original color for connected edges, fallback to a default if somehow missing
462
+ const connectedColor = originalColor ?? { color: "#999999", highlight: "#999999" };
345
463
  return {
346
464
  id: edge.id,
347
465
  width: isConnected ? 3 : 1,
348
- // Dim non-connected edges
349
466
  color: isConnected
350
- ? { color: originalColor?.color, highlight: originalColor?.highlight }
351
- : {
352
- color: dimmedColor,
353
- highlight: dimmedColor,
354
- },
467
+ ? { color: connectedColor.color, highlight: connectedColor.highlight }
468
+ : { color: dimmedColor, highlight: dimmedColor },
355
469
  };
356
470
  });
357
471
  this._edgesDataSet.update(edgeUpdates);
@@ -278,6 +278,6 @@ export function createUnknownDeviceIconDataUrl(isRouter: boolean = false, isSele
278
278
  * @returns A data URL containing the SVG
279
279
  */
280
280
  export function createWiFiRouterIconDataUrl(isSelected: boolean = false): string {
281
- const color = isSelected ? "#1976d2" : "#2196f3"; // Blue for WiFi AP
281
+ const color = isSelected ? "#1976d2" : "#ff9800"; // Orange for external infrastructure (same as Thread unknown)
282
282
  return createIconDataUrl(mdiWifi, color);
283
283
  }