@matter-server/dashboard 0.2.2 → 0.2.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.
Files changed (42) hide show
  1. package/dist/esm/pages/cluster-commands/base-cluster-commands.d.ts +25 -0
  2. package/dist/esm/pages/cluster-commands/base-cluster-commands.d.ts.map +1 -0
  3. package/dist/esm/pages/cluster-commands/base-cluster-commands.js +129 -0
  4. package/dist/esm/pages/cluster-commands/base-cluster-commands.js.map +6 -0
  5. package/dist/esm/pages/cluster-commands/clusters/level-control-commands.d.ts +29 -0
  6. package/dist/esm/pages/cluster-commands/clusters/level-control-commands.d.ts.map +1 -0
  7. package/dist/esm/pages/cluster-commands/clusters/level-control-commands.js +125 -0
  8. package/dist/esm/pages/cluster-commands/clusters/level-control-commands.js.map +6 -0
  9. package/dist/esm/pages/cluster-commands/clusters/on-off-commands.d.ts +25 -0
  10. package/dist/esm/pages/cluster-commands/clusters/on-off-commands.d.ts.map +1 -0
  11. package/dist/esm/pages/cluster-commands/clusters/on-off-commands.js +52 -0
  12. package/dist/esm/pages/cluster-commands/clusters/on-off-commands.js.map +6 -0
  13. package/dist/esm/pages/cluster-commands/index.d.ts +14 -0
  14. package/dist/esm/pages/cluster-commands/index.d.ts.map +1 -0
  15. package/dist/esm/pages/cluster-commands/index.js +16 -0
  16. package/dist/esm/pages/cluster-commands/index.js.map +6 -0
  17. package/dist/esm/pages/cluster-commands/registry.d.ts +24 -0
  18. package/dist/esm/pages/cluster-commands/registry.d.ts.map +1 -0
  19. package/dist/esm/pages/cluster-commands/registry.js +21 -0
  20. package/dist/esm/pages/cluster-commands/registry.js.map +6 -0
  21. package/dist/esm/pages/matter-cluster-view.d.ts +2 -0
  22. package/dist/esm/pages/matter-cluster-view.d.ts.map +1 -1
  23. package/dist/esm/pages/matter-cluster-view.js +32 -1
  24. package/dist/esm/pages/matter-cluster-view.js.map +1 -1
  25. package/dist/web/js/{commission-node-dialog-BJsfA4IV.js → commission-node-dialog-B6EYNztD.js} +5 -5
  26. package/dist/web/js/{commission-node-existing-CzRtUgBm.js → commission-node-existing-CKKStTjd.js} +4 -4
  27. package/dist/web/js/{commission-node-thread-FcLFz84I.js → commission-node-thread-D_Hgo4-b.js} +4 -4
  28. package/dist/web/js/{commission-node-wifi-C8iGfy7c.js → commission-node-wifi-DQrWnfn9.js} +4 -4
  29. package/dist/web/js/{dialog-box-DN32sjfR.js → dialog-box-9i64nfcC.js} +2 -2
  30. package/dist/web/js/{fire_event-BlsbXpOL.js → fire_event-CGgNDbLy.js} +1 -1
  31. package/dist/web/js/main.js +1 -1
  32. package/dist/web/js/{matter-dashboard-app-5UjO1Ik8.js → matter-dashboard-app-DD29PeTW.js} +415 -80
  33. package/dist/web/js/{node-binding-dialog-2yitVn0R.js → node-binding-dialog-DnO2DL_u.js} +3 -3
  34. package/dist/web/js/{outlined-text-field-BMLYwwlc.js → outlined-text-field-tv3ZtxuK.js} +2 -2
  35. package/dist/web/js/{prevent_default-BsT53c0u.js → prevent_default-C72e35wq.js} +1 -1
  36. package/package.json +3 -3
  37. package/src/pages/cluster-commands/base-cluster-commands.ts +122 -0
  38. package/src/pages/cluster-commands/clusters/level-control-commands.ts +130 -0
  39. package/src/pages/cluster-commands/clusters/on-off-commands.ts +57 -0
  40. package/src/pages/cluster-commands/index.ts +20 -0
  41. package/src/pages/cluster-commands/registry.ts +40 -0
  42. package/src/pages/matter-cluster-view.ts +41 -1
@@ -1,6 +1,6 @@
1
- import { a as i, c, n, d as clientContext, e, i as i$1, A, b, t } from './matter-dashboard-app-5UjO1Ik8.js';
2
- import { p as preventDefault } from './prevent_default-BsT53c0u.js';
3
- import './outlined-text-field-BMLYwwlc.js';
1
+ import { a as i, c, n, d as clientContext, e, i as i$1, A, b, t } from './matter-dashboard-app-DD29PeTW.js';
2
+ import { p as preventDefault } from './prevent_default-C72e35wq.js';
3
+ import './outlined-text-field-tv3ZtxuK.js';
4
4
  import './main.js';
5
5
 
6
6
  var _staticBlock$1;
@@ -1,5 +1,5 @@
1
- import { _ as __decorate, n as n$1, o as o$1, r as r$1, e, i as i$1, f as e$1, A, b, D, E as EASING, a as i$2, t, g as e$2, h as i$3, j as t$1, k as E, l as internals, m as mixinDelegatesAria, p as mixinElementInternals, u, q as i$4 } from './matter-dashboard-app-5UjO1Ik8.js';
2
- import { r as redispatchEvent } from './prevent_default-BsT53c0u.js';
1
+ import { _ as __decorate, n as n$1, o as o$1, r as r$1, e, i as i$1, f as e$1, A, b, D, E as EASING, a as i$2, t, g as e$2, h as i$3, j as t$1, k as E, l as internals, m as mixinDelegatesAria, p as mixinElementInternals, u, q as i$4 } from './matter-dashboard-app-DD29PeTW.js';
2
+ import { r as redispatchEvent } from './prevent_default-C72e35wq.js';
3
3
 
4
4
  /**
5
5
  * @license
@@ -1,4 +1,4 @@
1
- import { E as EASING, m as mixinDelegatesAria, i, _ as __decorate, n, e, r, b, f as e$1, A, a as i$1, t } from './matter-dashboard-app-5UjO1Ik8.js';
1
+ import { E as EASING, m as mixinDelegatesAria, i, _ as __decorate, n, e, r, b, f as e$1, A, a as i$1, t } from './matter-dashboard-app-DD29PeTW.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.2",
3
+ "version": "0.2.3",
4
4
  "description": "Dashboard for OHF Matter Server",
5
5
  "bugs": {
6
6
  "url": "https://github.com/matter-js/matterjs-server/issues"
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "devDependencies": {
25
25
  "@babel/preset-env": "^7.28.5",
26
- "@matter/main": "0.16.2",
26
+ "@matter/main": "0.16.4",
27
27
  "@rollup/plugin-babel": "^6.1.0",
28
28
  "@rollup/plugin-commonjs": "^29.0.0",
29
29
  "rollup-plugin-copy": "^3.5.0",
@@ -37,7 +37,7 @@
37
37
  "dependencies": {
38
38
  "@lit/context": "^1.1.6",
39
39
  "@material/web": "^2.4.1",
40
- "@matter-server/ws-client": "0.2.2",
40
+ "@matter-server/ws-client": "0.2.3",
41
41
  "@mdi/js": "^7.4.47",
42
42
  "lit": "^3.3.1",
43
43
  "tslib": "^2.8.1"
@@ -0,0 +1,122 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025-2026 Open Home Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { MatterClient, MatterNode } from "@matter-server/ws-client";
8
+ import { LitElement, css } from "lit";
9
+ import { property } from "lit/decorators.js";
10
+
11
+ /**
12
+ * Base class for cluster-specific command panels.
13
+ * Provides shared properties, styling, and helper methods for sending commands.
14
+ */
15
+ export abstract class BaseClusterCommands extends LitElement {
16
+ @property({ attribute: false })
17
+ public client!: MatterClient;
18
+
19
+ @property({ attribute: false })
20
+ public node!: MatterNode;
21
+
22
+ @property({ type: Number })
23
+ public endpoint!: number;
24
+
25
+ @property({ type: Number })
26
+ public cluster!: number;
27
+
28
+ /**
29
+ * Send a command to the device.
30
+ * @param command - The command name (PascalCase, e.g., "On", "Off", "MoveToLevel")
31
+ * @param payload - Optional command payload
32
+ */
33
+ protected async sendCommand(command: string, payload?: Record<string, unknown>): Promise<void> {
34
+ try {
35
+ await this.client.deviceCommand(this.node.node_id, this.endpoint, this.cluster, command, payload ?? {});
36
+ } catch (error) {
37
+ console.error(`Failed to send command ${command}:`, error);
38
+ // Could dispatch an event here for error handling in parent
39
+ }
40
+ }
41
+
42
+ static override styles = css`
43
+ :host {
44
+ display: block;
45
+ }
46
+
47
+ details.command-panel {
48
+ background-color: var(--md-sys-color-surface-container);
49
+ border-radius: 12px;
50
+ overflow: hidden;
51
+ }
52
+
53
+ details.command-panel summary {
54
+ padding: 16px;
55
+ font-weight: 500;
56
+ color: var(--md-sys-color-on-surface);
57
+ cursor: pointer;
58
+ user-select: none;
59
+ display: flex;
60
+ align-items: center;
61
+ gap: 8px;
62
+ }
63
+
64
+ details.command-panel summary:hover {
65
+ background-color: var(--md-sys-color-surface-container-high);
66
+ }
67
+
68
+ details.command-panel summary::before {
69
+ content: "▶";
70
+ font-size: 12px;
71
+ transition: transform 0.2s ease;
72
+ }
73
+
74
+ details.command-panel[open] summary::before {
75
+ transform: rotate(90deg);
76
+ }
77
+
78
+ details.command-panel summary::-webkit-details-marker {
79
+ display: none;
80
+ }
81
+
82
+ .command-content {
83
+ padding: 0 16px 16px 16px;
84
+ }
85
+
86
+ .command-row {
87
+ display: flex;
88
+ align-items: center;
89
+ gap: 12px;
90
+ flex-wrap: wrap;
91
+ }
92
+
93
+ .command-row label {
94
+ font-size: 14px;
95
+ color: var(--md-sys-color-on-surface-variant);
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 4px;
99
+ }
100
+
101
+ .command-row input[type="number"] {
102
+ width: 70px;
103
+ padding: 8px;
104
+ border: 1px solid var(--md-sys-color-outline);
105
+ border-radius: 4px;
106
+ background: var(--md-sys-color-surface);
107
+ color: var(--md-sys-color-on-surface);
108
+ }
109
+
110
+ .command-row input[type="checkbox"] {
111
+ width: 16px;
112
+ height: 16px;
113
+ margin: 0;
114
+ }
115
+
116
+ md-filled-button,
117
+ md-outlined-button {
118
+ --md-filled-button-container-height: 36px;
119
+ --md-outlined-button-container-height: 36px;
120
+ }
121
+ `;
122
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025-2026 Open Home Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import "@material/web/button/filled-button";
8
+ import { html } from "lit";
9
+ import { customElement, state } from "lit/decorators.js";
10
+ import { BaseClusterCommands } from "../base-cluster-commands.js";
11
+ import { registerClusterCommands } from "../registry.js";
12
+
13
+ const CLUSTER_ID = 8; // LevelControl cluster
14
+
15
+ /**
16
+ * Command panel for LevelControl cluster (ID: 8).
17
+ * Provides MoveToLevel command with configurable parameters.
18
+ */
19
+ @customElement("level-control-cluster-commands")
20
+ class LevelControlClusterCommands extends BaseClusterCommands {
21
+ @state()
22
+ private _targetLevel = 128;
23
+
24
+ @state()
25
+ private _transitionTime = 0;
26
+
27
+ @state()
28
+ private _executeIfOff = false;
29
+
30
+ override render() {
31
+ return html`
32
+ <details class="command-panel">
33
+ <summary>Level Control Commands</summary>
34
+ <div class="command-content">
35
+ <div class="command-row">
36
+ <label for="targetLevel">Level:</label>
37
+ <input
38
+ id="targetLevel"
39
+ type="number"
40
+ min="1"
41
+ max="254"
42
+ .value=${String(this._targetLevel)}
43
+ @input=${this._handleTargetLevelChange}
44
+ />
45
+ <label for="transitionTime">Transition (0.1s):</label>
46
+ <input
47
+ id="transitionTime"
48
+ type="number"
49
+ min="0"
50
+ max="65535"
51
+ .value=${String(this._transitionTime)}
52
+ @input=${this._handleTransitionTimeChange}
53
+ />
54
+ <label for="executeIfOff">
55
+ <input
56
+ id="executeIfOff"
57
+ type="checkbox"
58
+ .checked=${this._executeIfOff}
59
+ @change=${this._handleExecuteIfOffChange}
60
+ />
61
+ Execute if Off
62
+ </label>
63
+ <md-outlined-button @click=${this._handleMoveToLevel}>MoveToLevel</md-outlined-button>
64
+ <md-outlined-button @click=${this._handleMoveToLevelWithOnOff}
65
+ >MoveToLevelWithOnOff</md-outlined-button
66
+ >
67
+ </div>
68
+ </div>
69
+ </details>
70
+ `;
71
+ }
72
+
73
+ private _handleTargetLevelChange(e: Event) {
74
+ const input = e.target as HTMLInputElement;
75
+ let value = parseInt(input.value, 10);
76
+ if (isNaN(value)) value = 1;
77
+ if (value < 1) value = 1;
78
+ if (value > 254) value = 254;
79
+ this._targetLevel = value;
80
+ }
81
+
82
+ private _handleTransitionTimeChange(e: Event) {
83
+ const input = e.target as HTMLInputElement;
84
+ let value = parseInt(input.value, 10);
85
+ if (isNaN(value)) value = 0;
86
+ if (value < 0) value = 0;
87
+ if (value > 65535) value = 65535;
88
+ this._transitionTime = value;
89
+ }
90
+
91
+ private _handleExecuteIfOffChange(e: Event) {
92
+ const input = e.target as HTMLInputElement;
93
+ this._executeIfOff = input.checked;
94
+ }
95
+
96
+ private async _handleMoveToLevel() {
97
+ // ExecuteIfOff is bit 0 of the Options bitmap
98
+ const optionsMask = this._executeIfOff ? { executeIfOff: true } : {};
99
+ const optionsOverride = this._executeIfOff ? { executeIfOff: true } : {};
100
+
101
+ await this.sendCommand("MoveToLevel", {
102
+ level: this._targetLevel,
103
+ transitionTime: this._transitionTime,
104
+ optionsMask,
105
+ optionsOverride,
106
+ });
107
+ }
108
+
109
+ private async _handleMoveToLevelWithOnOff() {
110
+ // ExecuteIfOff is bit 0 of the Options bitmap
111
+ const optionsMask = this._executeIfOff ? { executeIfOff: true } : {};
112
+ const optionsOverride = this._executeIfOff ? { executeIfOff: true } : {};
113
+
114
+ await this.sendCommand("MoveToLevelWithOnOff", {
115
+ level: this._targetLevel,
116
+ transitionTime: this._transitionTime,
117
+ optionsMask,
118
+ optionsOverride,
119
+ });
120
+ }
121
+ }
122
+
123
+ // Register this component for cluster ID 8
124
+ registerClusterCommands(CLUSTER_ID, "level-control-cluster-commands");
125
+
126
+ declare global {
127
+ interface HTMLElementTagNameMap {
128
+ "level-control-cluster-commands": LevelControlClusterCommands;
129
+ }
130
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025-2026 Open Home Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import "@material/web/button/filled-button";
8
+ import "@material/web/button/outlined-button";
9
+ import { html } from "lit";
10
+ import { customElement } from "lit/decorators.js";
11
+ import { BaseClusterCommands } from "../base-cluster-commands.js";
12
+ import { registerClusterCommands } from "../registry.js";
13
+
14
+ const CLUSTER_ID = 6; // OnOff cluster
15
+
16
+ /**
17
+ * Command panel for OnOff cluster (ID: 6).
18
+ * Provides On, Off, and Toggle commands.
19
+ */
20
+ @customElement("on-off-cluster-commands")
21
+ class OnOffClusterCommands extends BaseClusterCommands {
22
+ override render() {
23
+ return html`
24
+ <details class="command-panel">
25
+ <summary>OnOff Commands</summary>
26
+ <div class="command-content">
27
+ <div class="command-row">
28
+ <md-outlined-button @click=${this._handleOn}>On</md-outlined-button>
29
+ <md-outlined-button @click=${this._handleOff}>Off</md-outlined-button>
30
+ <md-outlined-button @click=${this._handleToggle}>Toggle</md-outlined-button>
31
+ </div>
32
+ </div>
33
+ </details>
34
+ `;
35
+ }
36
+
37
+ private async _handleOn() {
38
+ await this.sendCommand("On");
39
+ }
40
+
41
+ private async _handleOff() {
42
+ await this.sendCommand("Off");
43
+ }
44
+
45
+ private async _handleToggle() {
46
+ await this.sendCommand("Toggle");
47
+ }
48
+ }
49
+
50
+ // Register this component for cluster ID 6
51
+ registerClusterCommands(CLUSTER_ID, "on-off-cluster-commands");
52
+
53
+ declare global {
54
+ interface HTMLElementTagNameMap {
55
+ "on-off-cluster-commands": OnOffClusterCommands;
56
+ }
57
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025-2026 Open Home Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ /**
8
+ * Cluster-specific command panels.
9
+ * Import this file to register all cluster command components.
10
+ */
11
+
12
+ // Registry exports
13
+ export { getClusterCommandsTag, hasClusterCommands, registerClusterCommands } from "./registry.js";
14
+
15
+ // Base class for creating new cluster commands
16
+ export { BaseClusterCommands } from "./base-cluster-commands.js";
17
+
18
+ // Cluster command components (auto-register on import)
19
+ import "./clusters/level-control-commands.js";
20
+ import "./clusters/on-off-commands.js";
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025-2026 Open Home Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ /**
8
+ * Registry for cluster-specific command panel components.
9
+ * Components register themselves by cluster ID and are dynamically
10
+ * rendered in the cluster view when viewing that cluster.
11
+ */
12
+
13
+ const clusterCommandRegistry = new Map<number, string>();
14
+
15
+ /**
16
+ * Register a cluster command panel component.
17
+ * @param clusterId - The Matter cluster ID (e.g., 6 for OnOff, 8 for LevelControl)
18
+ * @param tagName - The custom element tag name (e.g., "on-off-cluster-commands")
19
+ */
20
+ export function registerClusterCommands(clusterId: number, tagName: string): void {
21
+ clusterCommandRegistry.set(clusterId, tagName);
22
+ }
23
+
24
+ /**
25
+ * Get the registered component tag name for a cluster ID.
26
+ * @param clusterId - The Matter cluster ID
27
+ * @returns The custom element tag name, or undefined if not registered
28
+ */
29
+ export function getClusterCommandsTag(clusterId: number): string | undefined {
30
+ return clusterCommandRegistry.get(clusterId);
31
+ }
32
+
33
+ /**
34
+ * Check if a cluster has a registered command panel.
35
+ * @param clusterId - The Matter cluster ID
36
+ * @returns true if a command panel is registered
37
+ */
38
+ export function hasClusterCommands(clusterId: number): boolean {
39
+ return clusterCommandRegistry.has(clusterId);
40
+ }
@@ -12,11 +12,14 @@ import "@material/web/list/list-item";
12
12
  import { MatterClient, MatterNode, toBigIntAwareJson } from "@matter-server/ws-client";
13
13
  import { LitElement, css, html } from "lit";
14
14
  import { customElement, property } from "lit/decorators.js";
15
+ import { unsafeHTML } from "lit/directives/unsafe-html.js";
15
16
  import { clusters } from "../client/models/descriptions.js";
16
17
  import { showAlertDialog } from "../components/dialog-box/show-dialog-box.js";
17
18
  import "../components/ha-svg-icon";
18
19
  import "../pages/components/node-details";
19
20
  import { bindingContext } from "./components/context.js";
21
+ // Cluster command components (auto-register on import)
22
+ import { getClusterCommandsTag } from "./cluster-commands/index.js";
20
23
 
21
24
  declare global {
22
25
  interface HTMLElementTagNameMap {
@@ -27,7 +30,7 @@ declare global {
27
30
  function clusterAttributes(attributes: { [key: string]: any }, endpoint: number, cluster: number) {
28
31
  // extract unique clusters from the node attributes, as (sorted) array
29
32
  return Object.keys(attributes)
30
- .filter(key => key.startsWith(`${endpoint}/${cluster}`))
33
+ .filter(key => key.startsWith(`${endpoint}/${cluster}/`))
31
34
  .map(key => {
32
35
  const attributeKey = Number(key.split("/")[2]);
33
36
  return { key: attributeKey, value: attributes[key] };
@@ -68,6 +71,9 @@ class MatterClusterView extends LitElement {
68
71
  <node-details .node=${this.node} .client=${this.client}></node-details>
69
72
  </div>
70
73
 
74
+ <!-- Cluster commands section (if available for this cluster) -->
75
+ ${this._renderClusterCommands()}
76
+
71
77
  <!-- Cluster attributes listing -->
72
78
  <div class="container">
73
79
  <md-list>
@@ -118,6 +124,40 @@ class MatterClusterView extends LitElement {
118
124
  });
119
125
  }
120
126
 
127
+ private _renderClusterCommands() {
128
+ if (this.cluster === undefined) return html``;
129
+ if (!this.node?.available) return html``; // Don't show commands when device is offline
130
+
131
+ const tagName = getClusterCommandsTag(this.cluster);
132
+ if (!tagName) return html``;
133
+
134
+ // Dynamically render the registered cluster command component
135
+ const componentHtml = `<${tagName}></${tagName}>`;
136
+ const element = unsafeHTML(componentHtml);
137
+
138
+ return html`
139
+ <div class="container">
140
+ <div id="cluster-commands-container">${element}</div>
141
+ </div>
142
+ `;
143
+ }
144
+
145
+ override updated(changedProperties: Map<string, unknown>) {
146
+ super.updated(changedProperties);
147
+
148
+ // After render, find and configure the cluster commands component
149
+ const container = this.shadowRoot?.getElementById("cluster-commands-container");
150
+ if (container) {
151
+ const commandsElement = container.firstElementChild as any;
152
+ if (commandsElement && this.node && this.client) {
153
+ commandsElement.client = this.client;
154
+ commandsElement.node = this.node;
155
+ commandsElement.endpoint = this.endpoint;
156
+ commandsElement.cluster = this.cluster;
157
+ }
158
+ }
159
+ }
160
+
121
161
  private _goBack() {
122
162
  history.back();
123
163
  }