@matter-server/dashboard 0.2.7-alpha.0-20260118-45c7af0 → 0.2.7-alpha.0-20260119-49e7237

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 (68) hide show
  1. package/dist/esm/components/dialogs/binding/node-binding-dialog.d.ts.map +1 -1
  2. package/dist/esm/components/dialogs/binding/node-binding-dialog.js +3 -2
  3. package/dist/esm/components/dialogs/binding/node-binding-dialog.js.map +1 -1
  4. package/dist/esm/components/dialogs/commission-node-dialog/commission-node-existing.d.ts.map +1 -1
  5. package/dist/esm/components/dialogs/commission-node-dialog/commission-node-existing.js +2 -1
  6. package/dist/esm/components/dialogs/commission-node-dialog/commission-node-existing.js.map +1 -1
  7. package/dist/esm/components/dialogs/commission-node-dialog/commission-node-thread.d.ts.map +1 -1
  8. package/dist/esm/components/dialogs/commission-node-dialog/commission-node-thread.js +3 -2
  9. package/dist/esm/components/dialogs/commission-node-dialog/commission-node-thread.js.map +1 -1
  10. package/dist/esm/components/dialogs/commission-node-dialog/commission-node-wifi.d.ts.map +1 -1
  11. package/dist/esm/components/dialogs/commission-node-dialog/commission-node-wifi.js +5 -4
  12. package/dist/esm/components/dialogs/commission-node-dialog/commission-node-wifi.js.map +1 -1
  13. package/dist/esm/components/dialogs/settings/log-level-dialog.d.ts.map +1 -1
  14. package/dist/esm/components/dialogs/settings/log-level-dialog.js +6 -2
  15. package/dist/esm/components/dialogs/settings/log-level-dialog.js.map +1 -1
  16. package/dist/esm/pages/cluster-commands/clusters/level-control-commands.d.ts.map +1 -1
  17. package/dist/esm/pages/cluster-commands/clusters/level-control-commands.js +5 -2
  18. package/dist/esm/pages/cluster-commands/clusters/level-control-commands.js.map +1 -1
  19. package/dist/esm/pages/cluster-commands/clusters/on-off-commands.d.ts.map +1 -1
  20. package/dist/esm/pages/cluster-commands/clusters/on-off-commands.js +6 -3
  21. package/dist/esm/pages/cluster-commands/clusters/on-off-commands.js.map +1 -1
  22. package/dist/esm/pages/components/node-details.d.ts.map +1 -1
  23. package/dist/esm/pages/components/node-details.js +6 -5
  24. package/dist/esm/pages/components/node-details.js.map +1 -1
  25. package/dist/esm/pages/components/server-details.d.ts.map +1 -1
  26. package/dist/esm/pages/components/server-details.js +3 -2
  27. package/dist/esm/pages/components/server-details.js.map +1 -1
  28. package/dist/esm/pages/matter-server-view.d.ts.map +1 -1
  29. package/dist/esm/pages/matter-server-view.js +14 -1
  30. package/dist/esm/pages/matter-server-view.js.map +1 -1
  31. package/dist/esm/util/async-handler.d.ts +50 -0
  32. package/dist/esm/util/async-handler.d.ts.map +1 -0
  33. package/dist/esm/util/async-handler.js +33 -0
  34. package/dist/esm/util/async-handler.js.map +6 -0
  35. package/dist/esm/util/fire_event.d.ts.map +1 -1
  36. package/dist/esm/util/fire_event.js +1 -2
  37. package/dist/esm/util/fire_event.js.map +1 -1
  38. package/dist/esm/util/format_hex.d.ts +13 -2
  39. package/dist/esm/util/format_hex.d.ts.map +1 -1
  40. package/dist/esm/util/format_hex.js +6 -1
  41. package/dist/esm/util/format_hex.js.map +1 -1
  42. package/dist/web/js/{commission-node-dialog-CoaDIV2Y.js → commission-node-dialog-B4_wgFye.js} +5 -5
  43. package/dist/web/js/{commission-node-existing-DEU_mJjO.js → commission-node-existing-BktL7vHX.js} +6 -6
  44. package/dist/web/js/{commission-node-thread-DZ6DghSs.js → commission-node-thread-ox6fB3dO.js} +7 -7
  45. package/dist/web/js/{commission-node-wifi-DOyin0q3.js → commission-node-wifi-Dlayi41Z.js} +9 -9
  46. package/dist/web/js/{dialog-box-B5sunUPv.js → dialog-box-DFs0hcPv.js} +2 -2
  47. package/dist/web/js/{fire_event-C9Duc1j-.js → fire_event-oVtV3_P5.js} +2 -3
  48. package/dist/web/js/{log-level-dialog-B7LsZYUL.js → log-level-dialog-BVxKJJ49.js} +8 -5
  49. package/dist/web/js/main.js +151 -73
  50. package/dist/web/js/{matter-dashboard-app-DlHSE_Qh.js → matter-dashboard-app-Dr-IYMsD.js} +62 -17
  51. package/dist/web/js/{node-binding-dialog-BifZsigR.js → node-binding-dialog-CqE3VuZN.js} +6 -6
  52. package/dist/web/js/{outlined-text-field-D2BOt1yD.js → outlined-text-field-BVcHUxiS.js} +3 -3
  53. package/dist/web/js/{prevent_default-CuW2EnKR.js → prevent_default-tLhqZmsK.js} +1 -1
  54. package/dist/web/js/{validator-MOJiFndw.js → validator-muF28wYx.js} +1 -1
  55. package/package.json +3 -3
  56. package/src/components/dialogs/binding/node-binding-dialog.ts +3 -2
  57. package/src/components/dialogs/commission-node-dialog/commission-node-existing.ts +2 -1
  58. package/src/components/dialogs/commission-node-dialog/commission-node-thread.ts +3 -2
  59. package/src/components/dialogs/commission-node-dialog/commission-node-wifi.ts +5 -4
  60. package/src/components/dialogs/settings/log-level-dialog.ts +6 -2
  61. package/src/pages/cluster-commands/clusters/level-control-commands.ts +5 -2
  62. package/src/pages/cluster-commands/clusters/on-off-commands.ts +6 -3
  63. package/src/pages/components/node-details.ts +6 -5
  64. package/src/pages/components/server-details.ts +3 -3
  65. package/src/pages/matter-server-view.ts +17 -2
  66. package/src/util/async-handler.ts +74 -0
  67. package/src/util/fire_event.ts +1 -3
  68. package/src/util/format_hex.ts +17 -2
@@ -190,6 +190,20 @@ class Connection {
190
190
  */
191
191
  class MatterError extends Error {}
192
192
  class InvalidServerVersion extends MatterError {}
193
+ class CommandTimeoutError extends MatterError {
194
+ constructor(command, timeoutMs) {
195
+ super(`Command '${command}' timed out after ${timeoutMs}ms`);
196
+ this.command = command;
197
+ this.timeoutMs = timeoutMs;
198
+ this.name = "CommandTimeoutError";
199
+ }
200
+ }
201
+ class ConnectionClosedError extends MatterError {
202
+ constructor(message = "Connection closed while command was pending") {
203
+ super(message);
204
+ this.name = "ConnectionClosedError";
205
+ }
206
+ }
193
207
 
194
208
  /**
195
209
  * @license
@@ -258,6 +272,7 @@ class MatterNode {
258
272
  function toNodeKey(nodeId) {
259
273
  return String(nodeId);
260
274
  }
275
+ const DEFAULT_COMMAND_TIMEOUT = 5 * 60 * 1e3;
261
276
  class MatterClient {
262
277
  /**
263
278
  * Create a new MatterClient.
@@ -277,9 +292,12 @@ class MatterClient {
277
292
  serverBaseAddress;
278
293
  /** Whether this client is connected to a production server (optional, for UI purposes) */
279
294
  isProduction = false;
295
+ /** Default timeout for commands in milliseconds. Set to 0 to disable timeouts. */
296
+ commandTimeout = DEFAULT_COMMAND_TIMEOUT;
280
297
  // Using 'unknown' for resolve since the actual types vary by command
281
298
  result_futures = {};
282
- msgId = 0;
299
+ // Start with random offset for defense-in-depth and easier debugging across sessions
300
+ msgId = Math.floor(Math.random() * 2147483647);
283
301
  eventListeners = {};
284
302
  get serverInfo() {
285
303
  return this.connection.serverInfo;
@@ -293,112 +311,112 @@ class MatterClient {
293
311
  this.eventListeners[event] = this.eventListeners[event].filter(l => l !== listener);
294
312
  };
295
313
  }
296
- async commissionWithCode(code, networkOnly = true) {
314
+ async commissionWithCode(code, networkOnly = true, timeout) {
297
315
  return await this.sendCommand("commission_with_code", 0, {
298
316
  code,
299
317
  network_only: networkOnly
300
- });
318
+ }, timeout);
301
319
  }
302
- async setWifiCredentials(ssid, credentials) {
320
+ async setWifiCredentials(ssid, credentials, timeout) {
303
321
  await this.sendCommand("set_wifi_credentials", 0, {
304
322
  ssid,
305
323
  credentials
306
- });
324
+ }, timeout);
307
325
  }
308
- async setThreadOperationalDataset(dataset) {
326
+ async setThreadOperationalDataset(dataset, timeout) {
309
327
  await this.sendCommand("set_thread_dataset", 0, {
310
328
  dataset
311
- });
329
+ }, timeout);
312
330
  }
313
- async openCommissioningWindow(nodeId, timeout, iteration, option, discriminator) {
331
+ async openCommissioningWindow(nodeId, windowTimeout, iteration, option, discriminator, timeout) {
314
332
  return await this.sendCommand("open_commissioning_window", 0, {
315
333
  node_id: nodeId,
316
- timeout,
334
+ timeout: windowTimeout,
317
335
  iteration,
318
336
  option,
319
337
  discriminator
320
- });
338
+ }, timeout);
321
339
  }
322
- async discoverCommissionableNodes() {
323
- return await this.sendCommand("discover_commissionable_nodes", 0, {});
340
+ async discoverCommissionableNodes(timeout) {
341
+ return await this.sendCommand("discover_commissionable_nodes", 0, {}, timeout);
324
342
  }
325
- async getMatterFabrics(nodeId) {
343
+ async getMatterFabrics(nodeId, timeout) {
326
344
  return await this.sendCommand("get_matter_fabrics", 3, {
327
345
  node_id: nodeId
328
- });
346
+ }, timeout);
329
347
  }
330
- async removeMatterFabric(nodeId, fabricIndex) {
348
+ async removeMatterFabric(nodeId, fabricIndex, timeout) {
331
349
  await this.sendCommand("remove_matter_fabric", 3, {
332
350
  node_id: nodeId,
333
351
  fabric_index: fabricIndex
334
- });
352
+ }, timeout);
335
353
  }
336
- async pingNode(nodeId, attempts = 1) {
354
+ async pingNode(nodeId, attempts = 1, timeout) {
337
355
  return await this.sendCommand("ping_node", 0, {
338
356
  node_id: nodeId,
339
357
  attempts
340
- });
358
+ }, timeout);
341
359
  }
342
- async getNodeIPAddresses(nodeId, preferCache, scoped) {
360
+ async getNodeIPAddresses(nodeId, preferCache, scoped, timeout) {
343
361
  return await this.sendCommand("get_node_ip_addresses", 8, {
344
362
  node_id: nodeId,
345
363
  prefer_cache: preferCache,
346
364
  scoped
347
- });
365
+ }, timeout);
348
366
  }
349
- async removeNode(nodeId) {
367
+ async removeNode(nodeId, timeout) {
350
368
  await this.sendCommand("remove_node", 0, {
351
369
  node_id: nodeId
352
- });
370
+ }, timeout);
353
371
  }
354
- async interviewNode(nodeId) {
372
+ async interviewNode(nodeId, timeout) {
355
373
  await this.sendCommand("interview_node", 0, {
356
374
  node_id: nodeId
357
- });
375
+ }, timeout);
358
376
  }
359
- async importTestNode(dump) {
377
+ async importTestNode(dump, timeout) {
360
378
  await this.sendCommand("import_test_node", 0, {
361
379
  dump
362
- });
380
+ }, timeout);
363
381
  }
364
- async readAttribute(nodeId, attributePath) {
382
+ async readAttribute(nodeId, attributePath, timeout) {
365
383
  return await this.sendCommand("read_attribute", 0, {
366
384
  node_id: nodeId,
367
385
  attribute_path: attributePath
368
- });
386
+ }, timeout);
369
387
  }
370
- async writeAttribute(nodeId, attributePath, value) {
388
+ async writeAttribute(nodeId, attributePath, value, timeout) {
371
389
  return await this.sendCommand("write_attribute", 0, {
372
390
  node_id: nodeId,
373
391
  attribute_path: attributePath,
374
392
  value
375
- });
393
+ }, timeout);
376
394
  }
377
- async checkNodeUpdate(nodeId) {
395
+ async checkNodeUpdate(nodeId, timeout) {
378
396
  return await this.sendCommand("check_node_update", 10, {
379
397
  node_id: nodeId
380
- });
398
+ }, timeout);
381
399
  }
382
- async updateNode(nodeId, softwareVersion) {
400
+ async updateNode(nodeId, softwareVersion, timeout) {
383
401
  await this.sendCommand("update_node", 10, {
384
402
  node_id: nodeId,
385
403
  software_version: softwareVersion
386
- });
404
+ }, timeout);
387
405
  }
388
- async setACLEntry(nodeId, entry) {
406
+ async setACLEntry(nodeId, entry, timeout) {
389
407
  return await this.sendCommand("set_acl_entry", 0, {
390
408
  node_id: nodeId,
391
409
  entry
392
- });
410
+ }, timeout);
393
411
  }
394
- async setNodeBinding(nodeId, endpoint, bindings) {
412
+ async setNodeBinding(nodeId, endpoint, bindings, timeout) {
395
413
  return await this.sendCommand("set_node_binding", 0, {
396
414
  node_id: nodeId,
397
415
  endpoint,
398
416
  bindings
399
- });
417
+ }, timeout);
400
418
  }
401
- async deviceCommand(nodeId, endpointId, clusterId, commandName, payload = {}) {
419
+ async deviceCommand(nodeId, endpointId, clusterId, commandName, payload = {}, timeout) {
402
420
  return await this.sendCommand("device_command", 0, {
403
421
  node_id: nodeId,
404
422
  endpoint_id: endpointId,
@@ -406,79 +424,147 @@ class MatterClient {
406
424
  command_name: commandName,
407
425
  payload,
408
426
  response_type: null
409
- });
427
+ }, timeout);
410
428
  }
411
- async getNodes(onlyAvailable = false) {
429
+ async getNodes(onlyAvailable = false, timeout) {
412
430
  return await this.sendCommand("get_nodes", 0, {
413
431
  only_available: onlyAvailable
414
- });
432
+ }, timeout);
415
433
  }
416
- async getNode(nodeId) {
434
+ async getNode(nodeId, timeout) {
417
435
  return await this.sendCommand("get_node", 0, {
418
436
  node_id: nodeId
419
- });
437
+ }, timeout);
420
438
  }
421
- async getVendorNames(filterVendors) {
439
+ async getVendorNames(filterVendors, timeout) {
422
440
  return await this.sendCommand("get_vendor_names", 0, {
423
441
  filter_vendors: filterVendors
424
- });
442
+ }, timeout);
425
443
  }
426
- async fetchServerInfo() {
427
- return await this.sendCommand("server_info", 0, {});
444
+ async fetchServerInfo(timeout) {
445
+ return await this.sendCommand("server_info", 0, {}, timeout);
428
446
  }
429
- async setDefaultFabricLabel(label) {
447
+ async setDefaultFabricLabel(label, timeout) {
430
448
  await this.sendCommand("set_default_fabric_label", 0, {
431
449
  label
432
- });
450
+ }, timeout);
433
451
  }
434
452
  /**
435
453
  * Get the current log levels for console and file logging.
454
+ * @param timeout Optional command timeout in milliseconds
436
455
  * @returns The current log level configuration
437
456
  */
438
- async getLogLevel() {
439
- return await this.sendCommand("get_loglevel", 0, {});
457
+ async getLogLevel(timeout) {
458
+ return await this.sendCommand("get_loglevel", 0, {}, timeout);
440
459
  }
441
460
  /**
442
461
  * Set the log level for console and/or file logging.
443
462
  * Changes are temporary and will be reset when the server restarts.
444
463
  * @param consoleLoglevel Console log level to set (optional)
445
464
  * @param fileLoglevel File log level to set, only applied if file logging is enabled (optional)
465
+ * @param timeout Optional command timeout in milliseconds
446
466
  * @returns The log level configuration after the change
447
467
  */
448
- async setLogLevel(consoleLoglevel, fileLoglevel) {
468
+ async setLogLevel(consoleLoglevel, fileLoglevel, timeout) {
449
469
  return await this.sendCommand("set_loglevel", 0, {
450
470
  console_loglevel: consoleLoglevel,
451
471
  file_loglevel: fileLoglevel
452
- });
472
+ }, timeout);
453
473
  }
454
- sendCommand(command, require_schema = void 0, args) {
474
+ /**
475
+ * Send a command to the Matter server.
476
+ * @param command The command name
477
+ * @param require_schema Minimum schema version required (0 for any version)
478
+ * @param args Command arguments
479
+ * @param timeout Optional timeout in milliseconds. Defaults to `commandTimeout`. Set to 0 to disable.
480
+ * @returns Promise that resolves with the command result
481
+ * @throws Error if the command times out or fails
482
+ */
483
+ sendCommand(command, require_schema = void 0, args, timeout = this.commandTimeout) {
455
484
  if (require_schema && this.serverInfo.schema_version < require_schema) {
456
485
  throw new InvalidServerVersion(`Command not available due to incompatible server version. Update the Matter Server to a version that supports at least api schema ${require_schema}.`);
457
486
  }
458
- const messageId = ++this.msgId;
487
+ if (this.msgId >= Number.MAX_SAFE_INTEGER) {
488
+ this.msgId = 0;
489
+ }
490
+ const messageId = String(++this.msgId);
459
491
  const message = {
460
- message_id: messageId.toString(),
492
+ message_id: messageId,
461
493
  command,
462
494
  args
463
495
  };
464
- const messagePromise = new Promise((resolve, reject) => {
496
+ return new Promise((resolve, reject) => {
497
+ let timeoutId;
498
+ if (timeout > 0) {
499
+ timeoutId = setTimeout(() => {
500
+ const pending = this.result_futures[messageId];
501
+ if (pending) {
502
+ if (pending.timeoutId) {
503
+ clearTimeout(pending.timeoutId);
504
+ }
505
+ delete this.result_futures[messageId];
506
+ reject(new CommandTimeoutError(command, timeout));
507
+ }
508
+ }, timeout);
509
+ }
465
510
  this.result_futures[messageId] = {
466
511
  resolve,
467
- reject
512
+ reject,
513
+ timeoutId
468
514
  };
469
515
  this.connection.sendMessage(message);
470
516
  });
471
- return messagePromise.finally(() => {
517
+ }
518
+ /**
519
+ * Safely resolve a pending command, ensuring it's only resolved once.
520
+ * Clears timeout and removes from pending futures before resolving.
521
+ */
522
+ _resolvePendingCommand(messageId, result) {
523
+ const pending = this.result_futures[messageId];
524
+ if (pending) {
525
+ if (pending.timeoutId) {
526
+ clearTimeout(pending.timeoutId);
527
+ }
472
528
  delete this.result_futures[messageId];
473
- });
529
+ pending.resolve(result);
530
+ }
531
+ }
532
+ /**
533
+ * Safely reject a pending command, ensuring it's only rejected once.
534
+ * Clears timeout and removes from pending futures before rejecting.
535
+ */
536
+ _rejectPendingCommand(messageId, error) {
537
+ const pending = this.result_futures[messageId];
538
+ if (pending) {
539
+ if (pending.timeoutId) {
540
+ clearTimeout(pending.timeoutId);
541
+ }
542
+ delete this.result_futures[messageId];
543
+ pending.reject(error);
544
+ }
545
+ }
546
+ /**
547
+ * Reject all pending commands with a ConnectionClosedError.
548
+ * Called when the connection is closed or lost.
549
+ */
550
+ _rejectAllPendingCommands() {
551
+ const error = new ConnectionClosedError();
552
+ const pendingIds = Object.keys(this.result_futures);
553
+ for (const messageId of pendingIds) {
554
+ this._rejectPendingCommand(messageId, error);
555
+ }
474
556
  }
475
557
  async connect() {
476
558
  if (this.connection.connected) {
477
559
  return;
478
560
  }
479
- await this.connection.connect(msg => this._handleIncomingMessage(msg), () => this.fireEvent("connection_lost"));
561
+ await this.connection.connect(msg => this._handleIncomingMessage(msg), () => {
562
+ this._rejectAllPendingCommands();
563
+ this.fireEvent("connection_lost");
564
+ });
480
565
  }
481
566
  disconnect(clearStorage = false) {
567
+ this._rejectAllPendingCommands();
482
568
  if (this.connection && this.connection.connected) {
483
569
  this.connection.disconnect();
484
570
  }
@@ -502,19 +588,11 @@ class MatterClient {
502
588
  return;
503
589
  }
504
590
  if ("error_code" in msg) {
505
- const promise = this.result_futures[msg.message_id];
506
- if (promise) {
507
- promise.reject(new Error(msg.details));
508
- delete this.result_futures[msg.message_id];
509
- }
591
+ this._rejectPendingCommand(msg.message_id, new Error(msg.details));
510
592
  return;
511
593
  }
512
594
  if ("result" in msg) {
513
- const promise = this.result_futures[msg.message_id];
514
- if (promise) {
515
- promise.resolve(msg.result);
516
- delete this.result_futures[msg.message_id];
517
- }
595
+ this._resolvePendingCommand(msg.message_id, msg.result);
518
596
  return;
519
597
  }
520
598
  console.warn("Received message with unknown format", msg);
@@ -664,7 +742,7 @@ const ThemeService = new ThemeServiceImpl();
664
742
  * SPDX-License-Identifier: Apache-2.0
665
743
  */
666
744
  async function main() {
667
- import('./matter-dashboard-app-DlHSE_Qh.js').then(function (n) { return n.z; });
745
+ import('./matter-dashboard-app-Dr-IYMsD.js').then(function (n) { return n.C; });
668
746
  let url = "";
669
747
  const isProductionServer = location.origin.includes(":5580") || location.href.includes("hassio_ingress") || location.href.includes("/api/ingress/");
670
748
  if (!isProductionServer) {
@@ -1,5 +1,16 @@
1
1
  import { T as ThemeService, t as toBigIntAwareJson } from './main.js';
2
2
 
3
+ /**
4
+ * @license
5
+ * Copyright 2025-2026 Open Home Foundation
6
+ * SPDX-License-Identifier: Apache-2.0
7
+ */
8
+ const TEST_NODE_START = 0xfffffffe00000000n;
9
+ function isTestNodeId(nodeId) {
10
+ const bigId = typeof nodeId === "bigint" ? nodeId : BigInt(nodeId);
11
+ return bigId >= TEST_NODE_START;
12
+ }
13
+
3
14
  /**
4
15
  * @license
5
16
  * Copyright 2021 Google LLC
@@ -4009,7 +4020,7 @@ MdListItem = __decorate([t$1('md-list-item')], MdListItem);
4009
4020
  */
4010
4021
  const showLogLevelDialog = async client => {
4011
4022
  var _document$querySelect;
4012
- await import('./log-level-dialog-B7LsZYUL.js');
4023
+ await import('./log-level-dialog-BVxKJJ49.js');
4013
4024
  const dialog = document.createElement("log-level-dialog");
4014
4025
  dialog.client = client;
4015
4026
  (_document$querySelect = document.querySelector("matter-dashboard-app")) === null || _document$querySelect === void 0 || _document$querySelect.renderRoot.appendChild(dialog);
@@ -15581,7 +15592,7 @@ const clusters = {
15581
15592
  * SPDX-License-Identifier: Apache-2.0
15582
15593
  */
15583
15594
  const showDialogBox = async (type, dialogParams) => {
15584
- await import('./dialog-box-B5sunUPv.js');
15595
+ await import('./dialog-box-DFs0hcPv.js');
15585
15596
  return new Promise(resolve => {
15586
15597
  const dialog = document.createElement("dialox-box");
15587
15598
  dialog.params = dialogParams;
@@ -15755,7 +15766,7 @@ MdTextButton = __decorate([t$1('md-text-button')], MdTextButton);
15755
15766
  */
15756
15767
  const showNodeBindingDialog = async (client, node, endpoint) => {
15757
15768
  var _document$querySelect;
15758
- await import('./node-binding-dialog-BifZsigR.js');
15769
+ await import('./node-binding-dialog-CqE3VuZN.js');
15759
15770
  const dialog = document.createElement("node-binding-dialog");
15760
15771
  dialog.client = client;
15761
15772
  dialog.node = node;
@@ -15763,6 +15774,24 @@ const showNodeBindingDialog = async (client, node, endpoint) => {
15763
15774
  (_document$querySelect = document.querySelector("matter-dashboard-app")) === null || _document$querySelect === void 0 || _document$querySelect.renderRoot.appendChild(dialog);
15764
15775
  };
15765
15776
 
15777
+ /**
15778
+ * @license
15779
+ * Copyright 2025-2026 Open Home Foundation
15780
+ * SPDX-License-Identifier: Apache-2.0
15781
+ */
15782
+ function handleAsync(fn, onError) {
15783
+ return () => {
15784
+ fn().catch(error => {
15785
+ console.error("Async operation failed:", error);
15786
+ });
15787
+ };
15788
+ }
15789
+ function fireAndForget(promise, onError) {
15790
+ promise.catch(error => {
15791
+ console.error("Async operation failed:", error);
15792
+ });
15793
+ }
15794
+
15766
15795
  /**
15767
15796
  * @license
15768
15797
  * Copyright 2018 Google LLC
@@ -15798,6 +15827,10 @@ function formatHex(value) {
15798
15827
  }
15799
15828
  return `0x${hex}`;
15800
15829
  }
15830
+ function formatNodeAddress(fabricIndex, nodeId) {
15831
+ const fabricPart = fabricIndex !== void 0 ? fabricIndex : "?";
15832
+ return `@${fabricPart}:${nodeId.toString(16)}`;
15833
+ }
15801
15834
 
15802
15835
  var __defProp$7 = Object.defineProperty;
15803
15836
  var __getOwnPropDesc$9 = Object.getOwnPropertyDescriptor;
@@ -15997,27 +16030,27 @@ let NodeDetails = class extends i$3 {
15997
16030
  </div>`}
15998
16031
  </md-list-item>
15999
16032
  <md-list-item class="btn">
16000
- <md-outlined-button @click=${this._reinterview}
16033
+ <md-outlined-button @click=${handleAsync(() => this._reinterview())}
16001
16034
  >Interview<ha-svg-icon slot="icon" .path=${mdiChatProcessing}></ha-svg-icon
16002
16035
  ></md-outlined-button>
16003
16036
  ${this._updateInitiated ? b` <md-outlined-button disabled
16004
16037
  >Checking for updates<ha-svg-icon slot="icon" .path=${mdiUpdate}></ha-svg-icon
16005
16038
  ></md-outlined-button>` : (this.node.updateState || 0) > 1 ? b` <md-outlined-button disabled
16006
16039
  >${getUpdateStateLabel(this.node.updateState, this.node.updateStateProgress)}<ha-svg-icon slot="icon" .path=${mdiUpdate}></ha-svg-icon
16007
- ></md-outlined-button>` : b`<md-outlined-button @click=${this._searchUpdate}
16040
+ ></md-outlined-button>` : b`<md-outlined-button @click=${handleAsync(() => this._searchUpdate())}
16008
16041
  >Update<ha-svg-icon slot="icon" .path=${mdiUpdate}></ha-svg-icon
16009
16042
  ></md-outlined-button>`}
16010
16043
  ${bindings ? b`
16011
- <md-outlined-button @click=${this._binding}>
16044
+ <md-outlined-button @click=${handleAsync(() => this._binding())}>
16012
16045
  Binding
16013
16046
  <ha-svg-icon slot="icon" .path=${mdiLink}></ha-svg-icon>
16014
16047
  </md-outlined-button>
16015
16048
  ` : A}
16016
16049
 
16017
- <md-outlined-button @click=${this._openCommissioningWindow}
16050
+ <md-outlined-button @click=${handleAsync(() => this._openCommissioningWindow())}
16018
16051
  >Share<ha-svg-icon slot="icon" .path=${mdiShareVariant}></ha-svg-icon
16019
16052
  ></md-outlined-button>
16020
- <md-outlined-button @click=${this._remove}
16053
+ <md-outlined-button @click=${handleAsync(() => this._remove())}
16021
16054
  >Remove<ha-svg-icon slot="icon" .path=${mdiTrashCan}></ha-svg-icon
16022
16055
  ></md-outlined-button>
16023
16056
  </md-list-item>
@@ -16332,8 +16365,10 @@ let LevelControlClusterCommands = class extends BaseClusterCommands {
16332
16365
  />
16333
16366
  Execute if Off
16334
16367
  </label>
16335
- <md-outlined-button @click=${this._handleMoveToLevel}>MoveToLevel</md-outlined-button>
16336
- <md-outlined-button @click=${this._handleMoveToLevelWithOnOff}
16368
+ <md-outlined-button @click=${handleAsync(() => this._handleMoveToLevel())}
16369
+ >MoveToLevel</md-outlined-button
16370
+ >
16371
+ <md-outlined-button @click=${handleAsync(() => this._handleMoveToLevelWithOnOff())}
16337
16372
  >MoveToLevelWithOnOff</md-outlined-button
16338
16373
  >
16339
16374
  </div>
@@ -16410,9 +16445,11 @@ let OnOffClusterCommands = class extends BaseClusterCommands {
16410
16445
  <summary>OnOff Commands</summary>
16411
16446
  <div class="command-content">
16412
16447
  <div class="command-row">
16413
- <md-outlined-button @click=${this._handleOn}>On</md-outlined-button>
16414
- <md-outlined-button @click=${this._handleOff}>Off</md-outlined-button>
16415
- <md-outlined-button @click=${this._handleToggle}>Toggle</md-outlined-button>
16448
+ <md-outlined-button @click=${handleAsync(() => this._handleOn())}>On</md-outlined-button>
16449
+ <md-outlined-button @click=${handleAsync(() => this._handleOff())}>Off</md-outlined-button>
16450
+ <md-outlined-button @click=${handleAsync(() => this._handleToggle())}
16451
+ >Toggle</md-outlined-button
16452
+ >
16416
16453
  </div>
16417
16454
  </div>
16418
16455
  </details>
@@ -16750,7 +16787,7 @@ DashboardFooter = __decorateClass$3([t$1("dashboard-footer")], DashboardFooter);
16750
16787
  */
16751
16788
  const showCommissionNodeDialog = async client => {
16752
16789
  var _document$querySelect;
16753
- await import('./commission-node-dialog-CoaDIV2Y.js');
16790
+ await import('./commission-node-dialog-B4_wgFye.js');
16754
16791
  const dialog = document.createElement("commission-node-dialog");
16755
16792
  dialog.client = client;
16756
16793
  (_document$querySelect = document.querySelector("matter-dashboard-app")) === null || _document$querySelect === void 0 || _document$querySelect.renderRoot.appendChild(dialog);
@@ -16816,7 +16853,7 @@ let ServerDetails = class extends i$3 {
16816
16853
  <md-list-item class="btn">
16817
16854
  <span>
16818
16855
  <md-outlined-button @click=${this._commissionNode}>Commission node<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon></md-outlined-button>
16819
- <md-outlined-button @click=${this._uploadDiagnosticsDumpFile}>Import node<ha-svg-icon slot="icon" .path=${mdiFile}></ha-svg-icon></md-outlined-button>
16856
+ <md-outlined-button @click=${handleAsync(() => this._uploadDiagnosticsDumpFile())}>Import node<ha-svg-icon slot="icon" .path=${mdiFile}></ha-svg-icon></md-outlined-button>
16820
16857
  </md-list-item>
16821
16858
  </md-list>
16822
16859
  <!-- hidden file element for the upload diagnostics -->
@@ -16841,7 +16878,7 @@ let ServerDetails = class extends i$3 {
16841
16878
  }))) {
16842
16879
  return;
16843
16880
  }
16844
- const fileElem = this.renderRoot.getElementById("fileElem");
16881
+ const fileElem = this.shadowRoot.getElementById("fileElem");
16845
16882
  fileElem.click();
16846
16883
  }
16847
16884
  };
@@ -16899,6 +16936,9 @@ let MatterServerView = class extends i$3 {
16899
16936
  <md-list-item type="link" href=${`#node/${node.node_id}`}>
16900
16937
  <div slot="headline">
16901
16938
  Node ${node.node_id}
16939
+ <span class="hex-id"
16940
+ >(${formatNodeAddress(isTestNodeId(node.node_id) || this.client.serverInfo.fabric_index === void 0 ? void 0 : this.client.serverInfo.fabric_index, node.node_id)})</span
16941
+ >
16902
16942
  ${node.available ? "" : b`<span class="status">OFFLINE</span>`}
16903
16943
  </div>
16904
16944
  <div slot="supporting-text">
@@ -16947,6 +16987,11 @@ MatterServerView.styles = i$6`
16947
16987
  font-weight: bold;
16948
16988
  font-size: 0.8em;
16949
16989
  }
16990
+
16991
+ .hex-id {
16992
+ color: var(--text-color, rgba(0, 0, 0, 0.6));
16993
+ font-size: 0.85em;
16994
+ }
16950
16995
  `;
16951
16996
  __decorateClass$1([n$1()], MatterServerView.prototype, "nodes", 2);
16952
16997
  MatterServerView = __decorateClass$1([t$1("matter-server-view")], MatterServerView);
@@ -17147,4 +17192,4 @@ var matterDashboardApp = /*#__PURE__*/Object.freeze({
17147
17192
  __proto__: null
17148
17193
  });
17149
17194
 
17150
- export { A, D, EASING as E, ListController as L, NavigableKeys as N, __decorate as _, e$5 as a, getLastActivatableItem as b, createAnimationSignal as c, getFirstActivatableItem as d, e$6 as e, e$2 as f, getActiveItem as g, b as h, i$3 as i, i$6 as j, mixinElementInternals as k, i$1 as l, mixinDelegatesAria as m, n$1 as n, o$2 as o, c$3 as p, clientContext as q, r$1 as r, e$3 as s, t$1 as t, u, i$2 as v, t as w, E as x, internals as y, matterDashboardApp as z };
17195
+ export { A, internals as B, matterDashboardApp as C, D, EASING as E, ListController as L, NavigableKeys as N, __decorate as _, e$5 as a, getLastActivatableItem as b, createAnimationSignal as c, getFirstActivatableItem as d, e$6 as e, e$2 as f, getActiveItem as g, b as h, i$3 as i, i$6 as j, mixinElementInternals as k, i$1 as l, mixinDelegatesAria as m, n$1 as n, o$2 as o, fireAndForget as p, handleAsync as q, r$1 as r, c$3 as s, t$1 as t, u, clientContext as v, e$3 as w, i$2 as x, t as y, E as z };
@@ -1,8 +1,8 @@
1
- import { j as i, p as c, n, q as clientContext, a as e, i as i$1, A, h as b, t } from './matter-dashboard-app-DlHSE_Qh.js';
2
- import { p as preventDefault } from './prevent_default-CuW2EnKR.js';
3
- import './outlined-text-field-D2BOt1yD.js';
1
+ import { j as i, s as c, n, v as clientContext, a as e, i as i$1, q as handleAsync, A, h as b, t } from './matter-dashboard-app-Dr-IYMsD.js';
2
+ import { p as preventDefault } from './prevent_default-tLhqZmsK.js';
3
+ import './outlined-text-field-BVcHUxiS.js';
4
4
  import './main.js';
5
- import './validator-MOJiFndw.js';
5
+ import './validator-muF28wYx.js';
6
6
 
7
7
  var _staticBlock$1;
8
8
  /**
@@ -331,7 +331,7 @@ let NodeBindingDialog = class extends i$1 {
331
331
  </div>
332
332
  <div slot="end">
333
333
  <md-text-button
334
- @click=${() => this.deleteBindingHandler(index)}
334
+ @click=${handleAsync(() => this.deleteBindingHandler(index))}
335
335
  >delete</md-text-button
336
336
  </div>
337
337
  </md-list-item>
@@ -384,7 +384,7 @@ let NodeBindingDialog = class extends i$1 {
384
384
  </div>
385
385
  </div>
386
386
  <div slot="actions">
387
- <md-text-button @click=${this.addBindingHandler}>Add</md-text-button>
387
+ <md-text-button @click=${handleAsync(() => this.addBindingHandler())}>Add</md-text-button>
388
388
  <md-text-button @click=${this._close}>Cancel</md-text-button>
389
389
  </div>
390
390
  </md-dialog>
@@ -1,6 +1,6 @@
1
- import { j as i, s as e, v as i$1, w as t, x as E, A, m as mixinDelegatesAria, k as mixinElementInternals, i as i$2, _ as __decorate, n, r as r$1, a as e$1, o, f as e$2, h as b, u, l as i$3, t as t$1 } from './matter-dashboard-app-DlHSE_Qh.js';
2
- import { V as Validator, m as mixinOnReportValidity, a as mixinConstraintValidation, b as mixinFormAssociated, o as o$1, g as getFormValue, d as createValidator, e as getValidityAnchor, c as onReportValidity } from './validator-MOJiFndw.js';
3
- import { r as redispatchEvent } from './prevent_default-CuW2EnKR.js';
1
+ import { j as i, w as e, x as i$1, y as t, z as E, A, m as mixinDelegatesAria, k as mixinElementInternals, i as i$2, _ as __decorate, n, r as r$1, a as e$1, o, f as e$2, h as b, u, l as i$3, t as t$1 } from './matter-dashboard-app-Dr-IYMsD.js';
2
+ import { V as Validator, m as mixinOnReportValidity, a as mixinConstraintValidation, b as mixinFormAssociated, o as o$1, g as getFormValue, d as createValidator, e as getValidityAnchor, c as onReportValidity } from './validator-muF28wYx.js';
3
+ import { r as redispatchEvent } from './prevent_default-tLhqZmsK.js';
4
4
 
5
5
  /**
6
6
  * @license
@@ -1,4 +1,4 @@
1
- import { E as EASING, m as mixinDelegatesAria, i, _ as __decorate, n, a as e, r, h as b, f as e$1, A, j as i$1, t } from './matter-dashboard-app-DlHSE_Qh.js';
1
+ import { E as EASING, m as mixinDelegatesAria, i, _ as __decorate, n, a as e, r, h as b, f as e$1, A, j as i$1, t } from './matter-dashboard-app-Dr-IYMsD.js';
2
2
 
3
3
  /**
4
4
  * @license
@@ -1,4 +1,4 @@
1
- import { _ as __decorate, n as n$1, o as o$1, r, a as e, i as i$1, f as e$1, A, h as b, D, E as EASING, j as i$2, t, s as e$2, v as i$3, w as t$1, x as E, y as internals } from './matter-dashboard-app-DlHSE_Qh.js';
1
+ import { _ as __decorate, n as n$1, o as o$1, r, a as e, i as i$1, f as e$1, A, h as b, D, E as EASING, j as i$2, t, w as e$2, x as i$3, y as t$1, z as E, B as internals } from './matter-dashboard-app-Dr-IYMsD.js';
2
2
 
3
3
  /**
4
4
  * @license
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matter-server/dashboard",
3
- "version": "0.2.7-alpha.0-20260118-45c7af0",
3
+ "version": "0.2.7-alpha.0-20260119-49e7237",
4
4
  "description": "Dashboard for OHF Matter Server",
5
5
  "bugs": {
6
6
  "url": "https://github.com/matter-js/matterjs-server/issues"
@@ -37,8 +37,8 @@
37
37
  "dependencies": {
38
38
  "@lit/context": "^1.1.6",
39
39
  "@material/web": "^2.4.1",
40
- "@matter-server/ws-client": "0.2.7-alpha.0-20260118-45c7af0",
41
- "@matter-server/custom-clusters": "0.2.7-alpha.0-20260118-45c7af0",
40
+ "@matter-server/ws-client": "0.2.7-alpha.0-20260119-49e7237",
41
+ "@matter-server/custom-clusters": "0.2.7-alpha.0-20260119-49e7237",
42
42
  "@mdi/js": "^7.4.47",
43
43
  "lit": "^3.3.1",
44
44
  "tslib": "^2.8.1"