@matter-server/dashboard 0.7.2-alpha.0-20260601-e778038 → 0.8.0
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/components/dialogs/node-label-dialog/node-label-dialog.d.ts +28 -0
- package/dist/esm/components/dialogs/node-label-dialog/node-label-dialog.d.ts.map +1 -0
- package/dist/esm/components/dialogs/node-label-dialog/node-label-dialog.js +99 -0
- package/dist/esm/components/dialogs/node-label-dialog/node-label-dialog.js.map +6 -0
- package/dist/esm/components/dialogs/node-label-dialog/show-node-label-dialog.d.ts +8 -0
- package/dist/esm/components/dialogs/node-label-dialog/show-node-label-dialog.d.ts.map +1 -0
- package/dist/esm/components/dialogs/node-label-dialog/show-node-label-dialog.js +16 -0
- package/dist/esm/components/dialogs/node-label-dialog/show-node-label-dialog.js.map +6 -0
- package/dist/esm/components/dialogs/settings/log-level-section.d.ts.map +1 -1
- package/dist/esm/components/dialogs/settings/log-level-section.js +1 -0
- package/dist/esm/components/dialogs/settings/log-level-section.js.map +1 -1
- package/dist/esm/pages/cluster-commands/clusters/basic-information-commands.d.ts.map +1 -1
- package/dist/esm/pages/cluster-commands/clusters/basic-information-commands.js +7 -18
- package/dist/esm/pages/cluster-commands/clusters/basic-information-commands.js.map +1 -1
- package/dist/esm/pages/components/header.d.ts +2 -0
- package/dist/esm/pages/components/header.d.ts.map +1 -1
- package/dist/esm/pages/components/header.js +13 -4
- package/dist/esm/pages/components/header.js.map +1 -1
- package/dist/esm/pages/components/node-details.d.ts +1 -0
- package/dist/esm/pages/components/node-details.d.ts.map +1 -1
- package/dist/esm/pages/components/node-details.js +28 -2
- package/dist/esm/pages/components/node-details.js.map +1 -1
- package/dist/esm/pages/matter-network-view.js +4 -4
- package/dist/esm/pages/matter-network-view.js.map +1 -1
- package/dist/esm/pages/network/thread-graph.d.ts +5 -6
- package/dist/esm/pages/network/thread-graph.d.ts.map +1 -1
- package/dist/esm/pages/network/thread-graph.js +39 -20
- package/dist/esm/pages/network/thread-graph.js.map +1 -1
- package/dist/esm/util/device-icons.d.ts +12 -3
- package/dist/esm/util/device-icons.d.ts.map +1 -1
- package/dist/esm/util/device-icons.js +28 -13
- package/dist/esm/util/device-icons.js.map +1 -1
- package/dist/esm/util/node-label.d.ts +16 -0
- package/dist/esm/util/node-label.d.ts.map +1 -0
- package/dist/esm/util/node-label.js +20 -0
- package/dist/esm/util/node-label.js.map +6 -0
- package/dist/web/index.html +6 -0
- package/dist/web/js/{attribute-write-dialog-BfQ9Xflh.js → attribute-write-dialog-f6XnfRjW.js} +1 -1
- package/dist/web/js/{command-invoke-dialog-Zj6gySV_.js → command-invoke-dialog-jzzpwI81.js} +1 -1
- package/dist/web/js/{commission-node-dialog-BpEVqGkZ.js → commission-node-dialog-B3RBDYQU.js} +5 -5
- package/dist/web/js/{commission-node-existing-4zO8iG_s.js → commission-node-existing-Cf6SG9fC.js} +2 -2
- package/dist/web/js/{commission-node-thread-AHWmXDx1.js → commission-node-thread-BlC2ZhGB.js} +2 -2
- package/dist/web/js/{commission-node-wifi-C07wuota.js → commission-node-wifi-BzeVHU3H.js} +2 -2
- package/dist/web/js/{dialog-box-BVHU0m4j.js → dialog-box-8CugytjX.js} +1 -1
- package/dist/web/js/{fire_event-YKA6y_5c.js → fire_event-B0dFAh3R.js} +1 -1
- package/dist/web/js/main.js +4 -4
- package/dist/web/js/{matter-dashboard-app-DIak2OyX.js → matter-dashboard-app-D7-YX94X.js} +170 -66
- package/dist/web/js/{node-binding-dialog-DILw-ecn.js → node-binding-dialog-Dpjn-GtG.js} +1 -1
- package/dist/web/js/node-label-dialog-B31BzWy6.js +80 -0
- package/dist/web/js/{settings-dialog-CBVhNIXT.js → settings-dialog-Di2Qnh3-.js} +4 -1
- package/package.json +7 -7
- package/src/components/dialogs/node-label-dialog/node-label-dialog.ts +93 -0
- package/src/components/dialogs/node-label-dialog/show-node-label-dialog.ts +15 -0
- package/src/components/dialogs/settings/log-level-section.ts +1 -0
- package/src/pages/cluster-commands/clusters/basic-information-commands.ts +7 -26
- package/src/pages/components/header.ts +15 -4
- package/src/pages/components/node-details.ts +31 -2
- package/src/pages/matter-network-view.ts +4 -4
- package/src/pages/network/thread-graph.ts +46 -22
- package/src/util/device-icons.ts +59 -14
- package/src/util/node-label.ts +22 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { r, n, g as t, b as i, M as MAX_NODE_LABEL_LENGTH, d as b, w as writeNodeLabel, s as showAlertDialog } from './matter-dashboard-app-D7-YX94X.js';
|
|
2
|
+
import { p as preventDefault } from './prevent_default-D-ohDGsN.js';
|
|
3
|
+
import './main.js';
|
|
4
|
+
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
8
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
9
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
10
|
+
if (kind && result) __defProp(target, key, result);
|
|
11
|
+
return result;
|
|
12
|
+
};
|
|
13
|
+
let NodeLabelDialog = class extends i {
|
|
14
|
+
constructor() {
|
|
15
|
+
super(...arguments);
|
|
16
|
+
this._nodeLabel = "";
|
|
17
|
+
this._saving = false;
|
|
18
|
+
}
|
|
19
|
+
firstUpdated() {
|
|
20
|
+
this._nodeLabel = this.node.nodeLabel;
|
|
21
|
+
}
|
|
22
|
+
render() {
|
|
23
|
+
return b`
|
|
24
|
+
<md-dialog open @cancel=${preventDefault} @closed=${this._handleClosed}>
|
|
25
|
+
<div slot="headline">Edit Node Label</div>
|
|
26
|
+
<div slot="content">
|
|
27
|
+
<md-outlined-text-field
|
|
28
|
+
label="Node Label"
|
|
29
|
+
.value=${this._nodeLabel}
|
|
30
|
+
@input=${this._handleInput}
|
|
31
|
+
maxlength=${MAX_NODE_LABEL_LENGTH}
|
|
32
|
+
?disabled=${this._saving}
|
|
33
|
+
supporting-text="Max ${MAX_NODE_LABEL_LENGTH} characters"
|
|
34
|
+
style="width: 100%; margin-top: 8px;"
|
|
35
|
+
></md-outlined-text-field>
|
|
36
|
+
</div>
|
|
37
|
+
<div slot="actions">
|
|
38
|
+
<md-text-button @click=${this._close} ?disabled=${this._saving}>Cancel</md-text-button>
|
|
39
|
+
<md-text-button @click=${this._save} ?disabled=${this._saving}>Save</md-text-button>
|
|
40
|
+
</div>
|
|
41
|
+
</md-dialog>
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
_handleInput(e) {
|
|
45
|
+
const input = e.target;
|
|
46
|
+
this._nodeLabel = input.value;
|
|
47
|
+
}
|
|
48
|
+
_close() {
|
|
49
|
+
this.shadowRoot.querySelector("md-dialog").close();
|
|
50
|
+
}
|
|
51
|
+
_handleClosed() {
|
|
52
|
+
this.parentNode.removeChild(this);
|
|
53
|
+
}
|
|
54
|
+
async _save() {
|
|
55
|
+
this._saving = true;
|
|
56
|
+
try {
|
|
57
|
+
await writeNodeLabel(this.client, this.node, this._nodeLabel);
|
|
58
|
+
this._close();
|
|
59
|
+
} catch (error) {
|
|
60
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
61
|
+
showAlertDialog({
|
|
62
|
+
title: "Failed to set node label",
|
|
63
|
+
text: errorMessage
|
|
64
|
+
});
|
|
65
|
+
} finally {
|
|
66
|
+
this._saving = false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
__decorateClass([n({
|
|
71
|
+
attribute: false
|
|
72
|
+
})], NodeLabelDialog.prototype, "client", 2);
|
|
73
|
+
__decorateClass([n({
|
|
74
|
+
attribute: false
|
|
75
|
+
})], NodeLabelDialog.prototype, "node", 2);
|
|
76
|
+
__decorateClass([r()], NodeLabelDialog.prototype, "_nodeLabel", 2);
|
|
77
|
+
__decorateClass([r()], NodeLabelDialog.prototype, "_saving", 2);
|
|
78
|
+
NodeLabelDialog = __decorateClass([t("node-label-dialog")], NodeLabelDialog);
|
|
79
|
+
|
|
80
|
+
export { NodeLabelDialog };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i, c, a as clientContext,
|
|
1
|
+
import { i, c, a as clientContext, t as tickContext, r, e, b as i$1, f as fireAndForget, s as showAlertDialog, d as b, A, h as handleAsync, g as t, n, D as DevModeService, m as mdiWifi, j as mdiAccessPoint, k as mdiEyeOff, l as mdiEye } from './matter-dashboard-app-D7-YX94X.js';
|
|
2
2
|
import { p as preventDefault } from './prevent_default-D-ohDGsN.js';
|
|
3
3
|
import './main.js';
|
|
4
4
|
|
|
@@ -19,6 +19,9 @@ const LOG_LEVELS = [{
|
|
|
19
19
|
}, {
|
|
20
20
|
value: "warning",
|
|
21
21
|
label: "Warning"
|
|
22
|
+
}, {
|
|
23
|
+
value: "notice",
|
|
24
|
+
label: "Notice"
|
|
22
25
|
}, {
|
|
23
26
|
value: "info",
|
|
24
27
|
label: "Info"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matter-server/dashboard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
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.
|
|
37
|
-
"@matter-server/ws-client": "0.
|
|
36
|
+
"@matter-server/custom-clusters": "0.8.0",
|
|
37
|
+
"@matter-server/ws-client": "0.8.0",
|
|
38
38
|
"@mdi/js": "^7.4.47",
|
|
39
39
|
"lit": "^3.3.3",
|
|
40
40
|
"tslib": "^2.8.1",
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@babel/preset-env": "^7.29.7",
|
|
45
|
-
"@matter/main": "0.17.0",
|
|
46
|
-
"@rollup/plugin-babel": "^7.
|
|
47
|
-
"@rollup/plugin-commonjs": "^29.0.
|
|
45
|
+
"@matter/main": "0.17.1-alpha.0-20260602-24f28a953",
|
|
46
|
+
"@rollup/plugin-babel": "^7.1.0",
|
|
47
|
+
"@rollup/plugin-commonjs": "^29.0.3",
|
|
48
48
|
"@rollup/plugin-json": "^6.1.0",
|
|
49
49
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
50
50
|
"@rollup/plugin-terser": "^1.0.0",
|
|
51
51
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
52
|
-
"rollup": "^4.
|
|
52
|
+
"rollup": "^4.61.0",
|
|
53
53
|
"rollup-plugin-copy": "^3.5.0",
|
|
54
54
|
"serve": "^14.2.6"
|
|
55
55
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Open Home Foundation
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import "@material/web/button/text-button";
|
|
8
|
+
import "@material/web/dialog/dialog";
|
|
9
|
+
import "@material/web/textfield/outlined-text-field";
|
|
10
|
+
import type { MdDialog } from "@material/web/dialog/dialog.js";
|
|
11
|
+
import type { MatterClient, MatterNode } from "@matter-server/ws-client";
|
|
12
|
+
import { html, LitElement } from "lit";
|
|
13
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
14
|
+
import { MAX_NODE_LABEL_LENGTH, writeNodeLabel } from "../../../util/node-label.js";
|
|
15
|
+
import { preventDefault } from "../../../util/prevent_default.js";
|
|
16
|
+
import { showAlertDialog } from "../../dialog-box/show-dialog-box.js";
|
|
17
|
+
|
|
18
|
+
@customElement("node-label-dialog")
|
|
19
|
+
export class NodeLabelDialog extends LitElement {
|
|
20
|
+
@property({ attribute: false })
|
|
21
|
+
public client!: MatterClient;
|
|
22
|
+
|
|
23
|
+
@property({ attribute: false })
|
|
24
|
+
public node!: MatterNode;
|
|
25
|
+
|
|
26
|
+
@state()
|
|
27
|
+
private _nodeLabel: string = "";
|
|
28
|
+
|
|
29
|
+
@state()
|
|
30
|
+
private _saving: boolean = false;
|
|
31
|
+
|
|
32
|
+
protected override firstUpdated() {
|
|
33
|
+
this._nodeLabel = this.node.nodeLabel;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected override render() {
|
|
37
|
+
return html`
|
|
38
|
+
<md-dialog open @cancel=${preventDefault} @closed=${this._handleClosed}>
|
|
39
|
+
<div slot="headline">Edit Node Label</div>
|
|
40
|
+
<div slot="content">
|
|
41
|
+
<md-outlined-text-field
|
|
42
|
+
label="Node Label"
|
|
43
|
+
.value=${this._nodeLabel}
|
|
44
|
+
@input=${this._handleInput}
|
|
45
|
+
maxlength=${MAX_NODE_LABEL_LENGTH}
|
|
46
|
+
?disabled=${this._saving}
|
|
47
|
+
supporting-text="Max ${MAX_NODE_LABEL_LENGTH} characters"
|
|
48
|
+
style="width: 100%; margin-top: 8px;"
|
|
49
|
+
></md-outlined-text-field>
|
|
50
|
+
</div>
|
|
51
|
+
<div slot="actions">
|
|
52
|
+
<md-text-button @click=${this._close} ?disabled=${this._saving}>Cancel</md-text-button>
|
|
53
|
+
<md-text-button @click=${this._save} ?disabled=${this._saving}>Save</md-text-button>
|
|
54
|
+
</div>
|
|
55
|
+
</md-dialog>
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private _handleInput(e: Event) {
|
|
60
|
+
const input = e.target as HTMLInputElement;
|
|
61
|
+
this._nodeLabel = input.value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private _close() {
|
|
65
|
+
this.shadowRoot!.querySelector<MdDialog>("md-dialog")!.close();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private _handleClosed() {
|
|
69
|
+
this.parentNode!.removeChild(this);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private async _save() {
|
|
73
|
+
this._saving = true;
|
|
74
|
+
try {
|
|
75
|
+
await writeNodeLabel(this.client, this.node, this._nodeLabel);
|
|
76
|
+
this._close();
|
|
77
|
+
} catch (error) {
|
|
78
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
79
|
+
showAlertDialog({
|
|
80
|
+
title: "Failed to set node label",
|
|
81
|
+
text: errorMessage,
|
|
82
|
+
});
|
|
83
|
+
} finally {
|
|
84
|
+
this._saving = false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
declare global {
|
|
90
|
+
interface HTMLElementTagNameMap {
|
|
91
|
+
"node-label-dialog": NodeLabelDialog;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Open Home Foundation
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { MatterClient, MatterNode } from "@matter-server/ws-client";
|
|
8
|
+
|
|
9
|
+
export const showNodeLabelDialog = async (client: MatterClient, node: MatterNode) => {
|
|
10
|
+
await import("./node-label-dialog.js");
|
|
11
|
+
const dialog = document.createElement("node-label-dialog");
|
|
12
|
+
dialog.client = client;
|
|
13
|
+
dialog.node = node;
|
|
14
|
+
document.querySelector("matter-dashboard-app")?.renderRoot.appendChild(dialog);
|
|
15
|
+
};
|
|
@@ -20,6 +20,7 @@ const LOG_LEVELS: { value: LogLevelString; label: string }[] = [
|
|
|
20
20
|
{ value: "critical", label: "Critical" },
|
|
21
21
|
{ value: "error", label: "Error" },
|
|
22
22
|
{ value: "warning", label: "Warning" },
|
|
23
|
+
{ value: "notice", label: "Notice" },
|
|
23
24
|
{ value: "info", label: "Info" },
|
|
24
25
|
{ value: "debug", label: "Debug" },
|
|
25
26
|
];
|
|
@@ -10,13 +10,10 @@ import { css, html, nothing, type CSSResultGroup } from "lit";
|
|
|
10
10
|
import { customElement, state } from "lit/decorators.js";
|
|
11
11
|
import { showAlertDialog } from "../../../components/dialog-box/show-dialog-box.js";
|
|
12
12
|
import { handleAsync } from "../../../util/async-handler.js";
|
|
13
|
+
import { MAX_NODE_LABEL_LENGTH, NODE_LABEL_CLUSTER_ID, writeNodeLabel } from "../../../util/node-label.js";
|
|
13
14
|
import { BaseClusterCommands } from "../base-cluster-commands.js";
|
|
14
15
|
import { registerClusterCommands } from "../registry.js";
|
|
15
16
|
|
|
16
|
-
const CLUSTER_ID = 0x28; // BasicInformation cluster (40 decimal)
|
|
17
|
-
const NODE_LABEL_ATTRIBUTE_ID = 5;
|
|
18
|
-
const MAX_NODE_LABEL_LENGTH = 32;
|
|
19
|
-
|
|
20
17
|
/**
|
|
21
18
|
* Command panel for BasicInformation cluster (ID: 0x28 / 40).
|
|
22
19
|
* Provides ability to edit the NodeLabel attribute.
|
|
@@ -44,22 +41,7 @@ export class BasicInformationClusterCommands extends BaseClusterCommands {
|
|
|
44
41
|
if (!this.node) {
|
|
45
42
|
return;
|
|
46
43
|
}
|
|
47
|
-
|
|
48
|
-
// First check the direct nodeLabel property on the node
|
|
49
|
-
if (this.node.nodeLabel) {
|
|
50
|
-
this._nodeLabel = this.node.nodeLabel;
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Fallback to attribute path: endpoint/cluster/attribute = 0/40/5
|
|
55
|
-
// BasicInformation cluster is always on endpoint 0 per Matter specification
|
|
56
|
-
const attributePath = `0/${CLUSTER_ID}/${NODE_LABEL_ATTRIBUTE_ID}`;
|
|
57
|
-
const currentValue = this.node.attributes[attributePath];
|
|
58
|
-
if (typeof currentValue === "string") {
|
|
59
|
-
this._nodeLabel = currentValue;
|
|
60
|
-
} else {
|
|
61
|
-
this._nodeLabel = "";
|
|
62
|
-
}
|
|
44
|
+
this._nodeLabel = this.node.nodeLabel;
|
|
63
45
|
}
|
|
64
46
|
|
|
65
47
|
/**
|
|
@@ -115,13 +97,13 @@ export class BasicInformationClusterCommands extends BaseClusterCommands {
|
|
|
115
97
|
this._saving = true;
|
|
116
98
|
|
|
117
99
|
try {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
100
|
+
const label = this._nodeLabel.trim();
|
|
101
|
+
await writeNodeLabel(this.client, this.node, label);
|
|
102
|
+
this._nodeLabel = label;
|
|
121
103
|
|
|
122
104
|
showAlertDialog({
|
|
123
105
|
title: "Success",
|
|
124
|
-
text: `Node label set to "${
|
|
106
|
+
text: `Node label set to "${label}"`,
|
|
125
107
|
});
|
|
126
108
|
} catch (error) {
|
|
127
109
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -161,8 +143,7 @@ export class BasicInformationClusterCommands extends BaseClusterCommands {
|
|
|
161
143
|
];
|
|
162
144
|
}
|
|
163
145
|
|
|
164
|
-
|
|
165
|
-
registerClusterCommands(CLUSTER_ID, "basic-information-cluster-commands");
|
|
146
|
+
registerClusterCommands(NODE_LABEL_CLUSTER_ID, "basic-information-cluster-commands");
|
|
166
147
|
|
|
167
148
|
declare global {
|
|
168
149
|
interface HTMLElementTagNameMap {
|
|
@@ -11,7 +11,7 @@ import "@material/web/list/list";
|
|
|
11
11
|
import "@material/web/list/list-item";
|
|
12
12
|
import { consume } from "@lit/context";
|
|
13
13
|
import { MatterClient } from "@matter-server/ws-client";
|
|
14
|
-
import { mdiArrowLeft, mdiBrightnessAuto, mdiCog, mdiLogout, mdiWeatherNight, mdiWeatherSunny } from "@mdi/js";
|
|
14
|
+
import { mdiArrowLeft, mdiBrightnessAuto, mdiCog, mdiHome, mdiLogout, mdiWeatherNight, mdiWeatherSunny } from "@mdi/js";
|
|
15
15
|
import { LitElement, css, html, nothing } from "lit";
|
|
16
16
|
import { customElement, property, state } from "lit/decorators.js";
|
|
17
17
|
import { clientContext, tickContext } from "../../client/client-context.js";
|
|
@@ -67,6 +67,16 @@ export class DashboardHeader extends LitElement {
|
|
|
67
67
|
this._unsubscribeDevMode?.();
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
private _goBack() {
|
|
71
|
+
if (this.backButton) {
|
|
72
|
+
location.hash = this.backButton;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private _goHome() {
|
|
77
|
+
location.hash = "#";
|
|
78
|
+
}
|
|
79
|
+
|
|
70
80
|
private _cycleTheme() {
|
|
71
81
|
ThemeService.cycleTheme();
|
|
72
82
|
}
|
|
@@ -144,11 +154,12 @@ export class DashboardHeader extends LitElement {
|
|
|
144
154
|
<div class="header">
|
|
145
155
|
<!-- optional back button -->
|
|
146
156
|
${this.backButton
|
|
147
|
-
? html` <
|
|
148
|
-
<md-icon-button>
|
|
157
|
+
? html` <md-icon-button title="Back" aria-label="Back" @click=${this._goBack}>
|
|
149
158
|
<ha-svg-icon .path=${mdiArrowLeft}></ha-svg-icon>
|
|
150
159
|
</md-icon-button>
|
|
151
|
-
|
|
160
|
+
<md-icon-button title="Home" aria-label="Home" @click=${this._goHome}>
|
|
161
|
+
<ha-svg-icon .path=${mdiHome}></ha-svg-icon>
|
|
162
|
+
</md-icon-button>`
|
|
152
163
|
: ""}
|
|
153
164
|
|
|
154
165
|
<div class="title">${this.title ?? ""}</div>
|
|
@@ -13,13 +13,14 @@ import "@material/web/list/list";
|
|
|
13
13
|
import "@material/web/list/list-item";
|
|
14
14
|
import { consume } from "@lit/context";
|
|
15
15
|
import { MatterClient, MatterNode, UpdateSource } from "@matter-server/ws-client";
|
|
16
|
-
import { mdiChatProcessing, mdiLink, mdiShareVariant, mdiTrashCan, mdiUpdate, mdiVideo } from "@mdi/js";
|
|
16
|
+
import { mdiChatProcessing, mdiLink, mdiPencil, mdiShareVariant, mdiTrashCan, mdiUpdate, mdiVideo } from "@mdi/js";
|
|
17
17
|
import { LitElement, css, html, nothing } from "lit";
|
|
18
18
|
import { customElement, property, state } from "lit/decorators.js";
|
|
19
19
|
import { clientContext, tickContext } from "../../client/client-context.js";
|
|
20
20
|
import { DeviceType } from "../../client/models/descriptions.js";
|
|
21
21
|
import { showAlertDialog, showPromptDialog } from "../../components/dialog-box/show-dialog-box.js";
|
|
22
22
|
import { showNodeBindingDialog } from "../../components/dialogs/binding/show-node-binding-dialog.js";
|
|
23
|
+
import { showNodeLabelDialog } from "../../components/dialogs/node-label-dialog/show-node-label-dialog.js";
|
|
23
24
|
import { handleAsync } from "../../util/async-handler.js";
|
|
24
25
|
import "../../components/ha-svg-icon";
|
|
25
26
|
import "../camera-overlay.js";
|
|
@@ -86,8 +87,19 @@ export class NodeDetails extends LitElement {
|
|
|
86
87
|
<md-list>
|
|
87
88
|
<md-list-item>
|
|
88
89
|
<ha-svg-icon slot="start" class="device-icon" .path=${getDeviceIcon(this.node)}></ha-svg-icon>
|
|
89
|
-
<div slot="headline">
|
|
90
|
+
<div slot="headline" class="node-label-row">
|
|
90
91
|
<b>${this.node.nodeLabel || "Node Info"}</b>
|
|
92
|
+
${this.node.available
|
|
93
|
+
? html`
|
|
94
|
+
<md-icon-button
|
|
95
|
+
@click=${() => this._editNodeLabel()}
|
|
96
|
+
aria-label="Edit node label"
|
|
97
|
+
title="Edit node label"
|
|
98
|
+
>
|
|
99
|
+
<ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
|
|
100
|
+
</md-icon-button>
|
|
101
|
+
`
|
|
102
|
+
: nothing}
|
|
91
103
|
${this.node.available ? nothing : html` <span class="status">OFFLINE</span> `}
|
|
92
104
|
</div>
|
|
93
105
|
</md-list-item>
|
|
@@ -168,6 +180,10 @@ export class NodeDetails extends LitElement {
|
|
|
168
180
|
`;
|
|
169
181
|
}
|
|
170
182
|
|
|
183
|
+
private _editNodeLabel() {
|
|
184
|
+
showNodeLabelDialog(this.client, this.node!);
|
|
185
|
+
}
|
|
186
|
+
|
|
171
187
|
private async _reinterview() {
|
|
172
188
|
if (
|
|
173
189
|
!(await showPromptDialog({
|
|
@@ -318,6 +334,19 @@ export class NodeDetails extends LitElement {
|
|
|
318
334
|
}
|
|
319
335
|
|
|
320
336
|
static override styles = css`
|
|
337
|
+
.node-label-row {
|
|
338
|
+
display: flex;
|
|
339
|
+
align-items: center;
|
|
340
|
+
gap: 8px;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.node-label-row md-icon-button {
|
|
344
|
+
width: 24px;
|
|
345
|
+
height: 24px;
|
|
346
|
+
--md-icon-button-state-layer-width: 32px;
|
|
347
|
+
--md-icon-button-state-layer-height: 32px;
|
|
348
|
+
}
|
|
349
|
+
|
|
321
350
|
.device-icon {
|
|
322
351
|
--icon-primary-color: var(--md-sys-color-on-surface-variant, #666);
|
|
323
352
|
}
|
|
@@ -291,7 +291,7 @@ class MatterNetworkView extends LitElement {
|
|
|
291
291
|
return;
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
-
const found = graph.
|
|
294
|
+
const found = graph.selectBySearch(searchValue);
|
|
295
295
|
this._threadAddressSearchStatus = found ? "found" : "not-found";
|
|
296
296
|
}
|
|
297
297
|
|
|
@@ -306,8 +306,8 @@ class MatterNetworkView extends LitElement {
|
|
|
306
306
|
type="text"
|
|
307
307
|
.value=${this._threadAddressSearch}
|
|
308
308
|
@input=${this._handleThreadAddressSearchInput}
|
|
309
|
-
placeholder="Search
|
|
310
|
-
title="Find device by
|
|
309
|
+
placeholder="Search label, node id, or address"
|
|
310
|
+
title="Find a Thread device by node label, node id, or extended address (EUI-64)"
|
|
311
311
|
/>
|
|
312
312
|
<button type="submit" class="search-button">Find</button>
|
|
313
313
|
</form>
|
|
@@ -371,7 +371,7 @@ class MatterNetworkView extends LitElement {
|
|
|
371
371
|
: html`<div class="thread-search-status ${this._threadAddressSearchStatus}">
|
|
372
372
|
${this._threadAddressSearchStatus === "found"
|
|
373
373
|
? "Node highlighted."
|
|
374
|
-
: "No matching
|
|
374
|
+
: "No matching device found."}
|
|
375
375
|
</div>`}
|
|
376
376
|
<thread-graph
|
|
377
377
|
.nodes=${this.nodes}
|
|
@@ -114,36 +114,56 @@ export class ThreadGraph extends BaseNetworkGraph {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
|
-
* Searches for a Thread node (known or unknown)
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* -
|
|
121
|
-
* - 0xAABBCCDDEEFF0011
|
|
117
|
+
* Searches for a Thread node (known or unknown) and selects it. Matches, in priority order:
|
|
118
|
+
* 1. Extended address (EUI-64), accepting `AABBCCDDEEFF0011`, `AA:BB:...`, or `0x...` forms.
|
|
119
|
+
* 2. Node id (exact).
|
|
120
|
+
* 3. Visible device label (case-insensitive substring).
|
|
122
121
|
* Returns true when a match is found.
|
|
123
122
|
*/
|
|
124
|
-
public
|
|
125
|
-
const
|
|
126
|
-
if (!
|
|
123
|
+
public selectBySearch(query: string): boolean {
|
|
124
|
+
const trimmed = query.trim();
|
|
125
|
+
if (!trimmed) {
|
|
127
126
|
return false;
|
|
128
127
|
}
|
|
129
128
|
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
// Only visible nodes are selectable — focusing a hidden node would report a
|
|
130
|
+
// match the user can't see on the graph.
|
|
131
|
+
const threadNodes = Object.values(this.nodes).filter(
|
|
132
|
+
node => getNetworkType(node) === "thread" && !(this.hideOfflineNodes && node.available === false),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// 1. Extended address — commissioned devices first, then unknown/external neighbors.
|
|
136
|
+
const normalized = normalizeExtendedAddressInput(trimmed);
|
|
137
|
+
if (normalized) {
|
|
138
|
+
for (const node of threadNodes) {
|
|
139
|
+
const extAddressHex = getThreadExtendedAddressHex(node);
|
|
140
|
+
if (extAddressHex && extAddressHex === normalized) {
|
|
141
|
+
this.selectNode(String(node.node_id));
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
134
144
|
}
|
|
135
145
|
|
|
136
|
-
const
|
|
137
|
-
|
|
146
|
+
for (const [unknownId, unknown] of this._unknownDevicesMapCache) {
|
|
147
|
+
if (normalizeExtendedAddressInput(unknown.extAddressHex) === normalized) {
|
|
148
|
+
this.selectNode(unknownId);
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 2. Node id (exact).
|
|
155
|
+
for (const node of threadNodes) {
|
|
156
|
+
if (String(node.node_id) === trimmed) {
|
|
138
157
|
this.selectNode(String(node.node_id));
|
|
139
158
|
return true;
|
|
140
159
|
}
|
|
141
160
|
}
|
|
142
161
|
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
162
|
+
// 3. Device label as shown on the graph (case-insensitive substring).
|
|
163
|
+
const needle = trimmed.toLowerCase();
|
|
164
|
+
for (const node of threadNodes) {
|
|
165
|
+
if (getDeviceName(node).toLowerCase().includes(needle)) {
|
|
166
|
+
this.selectNode(String(node.node_id));
|
|
147
167
|
return true;
|
|
148
168
|
}
|
|
149
169
|
}
|
|
@@ -258,11 +278,13 @@ export class ThreadGraph extends BaseNetworkGraph {
|
|
|
258
278
|
? `\n${device.networkName}`
|
|
259
279
|
: "";
|
|
260
280
|
const label = `${top}${suffix}`;
|
|
261
|
-
const
|
|
281
|
+
const decodedState = decodeMeshcopStateBitmap(device.stateBitmapHex);
|
|
282
|
+
const isLeader = decodedState?.threadRoleValue === 3;
|
|
283
|
+
const isPrimaryBbr = decodedState?.bbr === true && decodedState.bbrFunction === "primary";
|
|
262
284
|
graphNodes.push({
|
|
263
285
|
id: device.id,
|
|
264
286
|
label,
|
|
265
|
-
image: createBorderRouterIconDataUrl(isSelected, isLeader),
|
|
287
|
+
image: createBorderRouterIconDataUrl(isSelected, isLeader, isPrimaryBbr),
|
|
266
288
|
shape: "image" as const,
|
|
267
289
|
networkType: "thread" as const,
|
|
268
290
|
isUnknown: false,
|
|
@@ -622,10 +644,12 @@ export class ThreadGraph extends BaseNetworkGraph {
|
|
|
622
644
|
}
|
|
623
645
|
const external = this._unknownDevicesMapCache.get(nodeId);
|
|
624
646
|
if (external?.kind === "br") {
|
|
625
|
-
const
|
|
647
|
+
const decodedState = decodeMeshcopStateBitmap(external.stateBitmapHex);
|
|
648
|
+
const isLeader = decodedState?.threadRoleValue === 3;
|
|
649
|
+
const isPrimaryBbr = decodedState?.bbr === true && decodedState.bbrFunction === "primary";
|
|
626
650
|
this._nodesDataSet.update({
|
|
627
651
|
id: nodeId,
|
|
628
|
-
image: createBorderRouterIconDataUrl(isHighlighted, isLeader),
|
|
652
|
+
image: createBorderRouterIconDataUrl(isHighlighted, isLeader, isPrimaryBbr),
|
|
629
653
|
});
|
|
630
654
|
} else if (nodeId.startsWith("unknown_") || nodeId.startsWith("br_")) {
|
|
631
655
|
this._nodesDataSet.update({
|