@matter-server/dashboard 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/pages/cluster-commands/base-cluster-commands.d.ts +2 -2
- package/dist/esm/pages/cluster-commands/base-cluster-commands.d.ts.map +1 -1
- package/dist/esm/pages/cluster-commands/base-cluster-commands.js.map +1 -1
- package/dist/esm/pages/cluster-commands/clusters/basic-information-commands.d.ts +36 -0
- package/dist/esm/pages/cluster-commands/clusters/basic-information-commands.d.ts.map +1 -0
- package/dist/esm/pages/cluster-commands/clusters/basic-information-commands.js +159 -0
- package/dist/esm/pages/cluster-commands/clusters/basic-information-commands.js.map +6 -0
- package/dist/esm/pages/cluster-commands/index.d.ts +1 -0
- package/dist/esm/pages/cluster-commands/index.d.ts.map +1 -1
- package/dist/esm/pages/cluster-commands/index.js +1 -0
- package/dist/esm/pages/cluster-commands/index.js.map +1 -1
- package/dist/esm/pages/components/footer.d.ts.map +1 -1
- package/dist/esm/pages/components/footer.js +4 -7
- package/dist/esm/pages/components/footer.js.map +1 -1
- package/dist/esm/pages/components/header.d.ts +5 -0
- package/dist/esm/pages/components/header.d.ts.map +1 -1
- package/dist/esm/pages/components/header.js +75 -0
- package/dist/esm/pages/components/header.js.map +1 -1
- package/dist/esm/pages/components/node-details.js +1 -1
- package/dist/esm/pages/components/node-details.js.map +1 -1
- package/dist/esm/pages/components/server-details.d.ts.map +1 -1
- package/dist/esm/pages/components/server-details.js +0 -1
- package/dist/esm/pages/components/server-details.js.map +1 -1
- package/dist/esm/pages/matter-dashboard-app.d.ts +12 -0
- package/dist/esm/pages/matter-dashboard-app.d.ts.map +1 -1
- package/dist/esm/pages/matter-dashboard-app.js +84 -4
- package/dist/esm/pages/matter-dashboard-app.js.map +1 -1
- package/dist/esm/pages/matter-network-view.d.ts +52 -0
- package/dist/esm/pages/matter-network-view.d.ts.map +1 -0
- package/dist/esm/pages/matter-network-view.js +309 -0
- package/dist/esm/pages/matter-network-view.js.map +6 -0
- package/dist/esm/pages/matter-node-view.d.ts.map +1 -1
- package/dist/esm/pages/matter-node-view.js +70 -1
- package/dist/esm/pages/matter-node-view.js.map +1 -1
- package/dist/esm/pages/matter-server-view.d.ts +4 -0
- package/dist/esm/pages/matter-server-view.d.ts.map +1 -1
- package/dist/esm/pages/matter-server-view.js +16 -1
- package/dist/esm/pages/matter-server-view.js.map +1 -1
- package/dist/esm/pages/network/base-network-graph.d.ts +74 -0
- package/dist/esm/pages/network/base-network-graph.d.ts.map +1 -0
- package/dist/esm/pages/network/base-network-graph.js +403 -0
- package/dist/esm/pages/network/base-network-graph.js.map +6 -0
- package/dist/esm/pages/network/device-icons.d.ts +52 -0
- package/dist/esm/pages/network/device-icons.d.ts.map +1 -0
- package/dist/esm/pages/network/device-icons.js +197 -0
- package/dist/esm/pages/network/device-icons.js.map +6 -0
- package/dist/esm/pages/network/device-panel.d.ts +31 -0
- package/dist/esm/pages/network/device-panel.d.ts.map +1 -0
- package/dist/esm/pages/network/device-panel.js +183 -0
- package/dist/esm/pages/network/device-panel.js.map +6 -0
- package/dist/esm/pages/network/network-details.d.ts +47 -0
- package/dist/esm/pages/network/network-details.d.ts.map +1 -0
- package/dist/esm/pages/network/network-details.js +686 -0
- package/dist/esm/pages/network/network-details.js.map +6 -0
- package/dist/esm/pages/network/network-types.d.ts +153 -0
- package/dist/esm/pages/network/network-types.d.ts.map +1 -0
- package/dist/esm/pages/network/network-types.js +19 -0
- package/dist/esm/pages/network/network-types.js.map +6 -0
- package/dist/esm/pages/network/network-utils.d.ts +170 -0
- package/dist/esm/pages/network/network-utils.d.ts.map +1 -0
- package/dist/esm/pages/network/network-utils.js +472 -0
- package/dist/esm/pages/network/network-utils.js.map +6 -0
- package/dist/esm/pages/network/thread-graph.d.ts +27 -0
- package/dist/esm/pages/network/thread-graph.d.ts.map +1 -0
- package/dist/esm/pages/network/thread-graph.js +134 -0
- package/dist/esm/pages/network/thread-graph.js.map +6 -0
- package/dist/esm/pages/network/wifi-graph.d.ts +27 -0
- package/dist/esm/pages/network/wifi-graph.d.ts.map +1 -0
- package/dist/esm/pages/network/wifi-graph.js +167 -0
- package/dist/esm/pages/network/wifi-graph.js.map +6 -0
- package/dist/web/js/{commission-node-dialog-CBSDiqRW.js → commission-node-dialog-B1_khzZb.js} +5 -5
- package/dist/web/js/{commission-node-existing-TP6s8Tez.js → commission-node-existing-RpdajrwF.js} +2 -5
- package/dist/web/js/{commission-node-thread-DOB8pu6x.js → commission-node-thread-5f2itkTG.js} +2 -5
- package/dist/web/js/{commission-node-wifi-tzavmk1j.js → commission-node-wifi-DZ_pWqsa.js} +2 -5
- package/dist/web/js/{dialog-box-Dknil_Be.js → dialog-box-DEUxM4B1.js} +2 -2
- package/dist/web/js/{fire_event-DRpOSjJR.js → fire_event-BczBMT8E.js} +1 -1
- package/dist/web/js/{log-level-dialog-TXkma-7Z.js → log-level-dialog-Cr3PfX1X.js} +2 -3
- package/dist/web/js/main.js +1 -1
- package/dist/web/js/matter-dashboard-app-BuCe_Jxf.js +29990 -0
- package/dist/web/js/{node-binding-dialog-D52FCBFP.js → node-binding-dialog-DMiHNDLA.js} +2 -4
- package/dist/web/js/{prevent_default-BPgSQsuY.js → prevent_default-D4FX_PIh.js} +2 -42
- package/package.json +5 -4
- package/src/pages/cluster-commands/base-cluster-commands.ts +2 -2
- package/src/pages/cluster-commands/clusters/basic-information-commands.ts +171 -0
- package/src/pages/cluster-commands/index.ts +1 -0
- package/src/pages/components/footer.ts +4 -7
- package/src/pages/components/header.ts +81 -0
- package/src/pages/components/node-details.ts +2 -2
- package/src/pages/components/server-details.ts +0 -1
- package/src/pages/matter-dashboard-app.ts +105 -5
- package/src/pages/matter-network-view.ts +325 -0
- package/src/pages/matter-node-view.ts +75 -1
- package/src/pages/matter-server-view.ts +17 -1
- package/src/pages/network/base-network-graph.ts +463 -0
- package/src/pages/network/device-icons.ts +283 -0
- package/src/pages/network/device-panel.ts +180 -0
- package/src/pages/network/network-details.ts +750 -0
- package/src/pages/network/network-types.ts +161 -0
- package/src/pages/network/network-utils.ts +752 -0
- package/src/pages/network/thread-graph.ts +164 -0
- package/src/pages/network/wifi-graph.ts +192 -0
- package/dist/web/js/matter-dashboard-app-B7GUghkC.js +0 -17254
- package/dist/web/js/outlined-text-field-D1DyKQY-.js +0 -968
- package/dist/web/js/validator-C735j770.js +0 -1122
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025-2026 Open Home Foundation
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { MatterNode } from "@matter-server/ws-client";
|
|
8
|
+
import {
|
|
9
|
+
mdiAccessPoint,
|
|
10
|
+
mdiCeilingLight,
|
|
11
|
+
mdiDoorOpen,
|
|
12
|
+
mdiFan,
|
|
13
|
+
mdiGauge,
|
|
14
|
+
mdiHelp,
|
|
15
|
+
mdiHome,
|
|
16
|
+
mdiLightbulb,
|
|
17
|
+
mdiLock,
|
|
18
|
+
mdiMotionSensor,
|
|
19
|
+
mdiPowerPlug,
|
|
20
|
+
mdiRouter,
|
|
21
|
+
mdiSpeaker,
|
|
22
|
+
mdiTelevision,
|
|
23
|
+
mdiThermometer,
|
|
24
|
+
mdiToggleSwitch,
|
|
25
|
+
mdiWater,
|
|
26
|
+
mdiWifi,
|
|
27
|
+
} from "@mdi/js";
|
|
28
|
+
import { ThemeService } from "../../util/theme-service.js";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get theme-aware default icon color.
|
|
32
|
+
*/
|
|
33
|
+
function getDefaultIconColor(): string {
|
|
34
|
+
return ThemeService.effectiveTheme === "dark" ? "#b0b0b0" : "#666666";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Device type IDs from Matter specification (from DeviceTypeList attribute 0/29/0).
|
|
39
|
+
*/
|
|
40
|
+
const DeviceTypes = {
|
|
41
|
+
// Lighting
|
|
42
|
+
ON_OFF_LIGHT: 0x0100,
|
|
43
|
+
DIMMABLE_LIGHT: 0x0101,
|
|
44
|
+
COLOR_TEMPERATURE_LIGHT: 0x010c,
|
|
45
|
+
EXTENDED_COLOR_LIGHT: 0x010d,
|
|
46
|
+
|
|
47
|
+
// Plugs/Outlets
|
|
48
|
+
ON_OFF_PLUG: 0x010a,
|
|
49
|
+
DIMMABLE_PLUG: 0x010b,
|
|
50
|
+
|
|
51
|
+
// Switches
|
|
52
|
+
ON_OFF_SWITCH: 0x0103,
|
|
53
|
+
DIMMER_SWITCH: 0x0104,
|
|
54
|
+
COLOR_DIMMER_SWITCH: 0x0105,
|
|
55
|
+
GENERIC_SWITCH: 0x000f,
|
|
56
|
+
|
|
57
|
+
// Sensors
|
|
58
|
+
CONTACT_SENSOR: 0x0015,
|
|
59
|
+
OCCUPANCY_SENSOR: 0x0107,
|
|
60
|
+
TEMPERATURE_SENSOR: 0x0302,
|
|
61
|
+
HUMIDITY_SENSOR: 0x0307,
|
|
62
|
+
LIGHT_SENSOR: 0x0106,
|
|
63
|
+
PRESSURE_SENSOR: 0x0305,
|
|
64
|
+
FLOW_SENSOR: 0x0306,
|
|
65
|
+
|
|
66
|
+
// HVAC
|
|
67
|
+
THERMOSTAT: 0x0301,
|
|
68
|
+
FAN: 0x002b,
|
|
69
|
+
|
|
70
|
+
// Closures
|
|
71
|
+
DOOR_LOCK: 0x000a,
|
|
72
|
+
WINDOW_COVERING: 0x0202,
|
|
73
|
+
|
|
74
|
+
// Media
|
|
75
|
+
SPEAKER: 0x0022,
|
|
76
|
+
BASIC_VIDEO_PLAYER: 0x0028,
|
|
77
|
+
TELEVISION: 0x0023,
|
|
78
|
+
|
|
79
|
+
// Infrastructure
|
|
80
|
+
ROOT_NODE: 0x0016,
|
|
81
|
+
BRIDGE: 0x000e,
|
|
82
|
+
AGGREGATOR: 0x000e, // Same as bridge
|
|
83
|
+
|
|
84
|
+
// Water
|
|
85
|
+
WATER_LEAK_DETECTOR: 0x0043,
|
|
86
|
+
WATER_VALVE: 0x0042,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Maps device type IDs to MDI icon paths.
|
|
91
|
+
*/
|
|
92
|
+
const deviceTypeToIcon: Record<number, string> = {
|
|
93
|
+
// Lighting
|
|
94
|
+
[DeviceTypes.ON_OFF_LIGHT]: mdiLightbulb,
|
|
95
|
+
[DeviceTypes.DIMMABLE_LIGHT]: mdiLightbulb,
|
|
96
|
+
[DeviceTypes.COLOR_TEMPERATURE_LIGHT]: mdiCeilingLight,
|
|
97
|
+
[DeviceTypes.EXTENDED_COLOR_LIGHT]: mdiCeilingLight,
|
|
98
|
+
|
|
99
|
+
// Plugs/Outlets
|
|
100
|
+
[DeviceTypes.ON_OFF_PLUG]: mdiPowerPlug,
|
|
101
|
+
[DeviceTypes.DIMMABLE_PLUG]: mdiPowerPlug,
|
|
102
|
+
|
|
103
|
+
// Switches
|
|
104
|
+
[DeviceTypes.ON_OFF_SWITCH]: mdiToggleSwitch,
|
|
105
|
+
[DeviceTypes.DIMMER_SWITCH]: mdiToggleSwitch,
|
|
106
|
+
[DeviceTypes.COLOR_DIMMER_SWITCH]: mdiToggleSwitch,
|
|
107
|
+
[DeviceTypes.GENERIC_SWITCH]: mdiToggleSwitch,
|
|
108
|
+
|
|
109
|
+
// Sensors
|
|
110
|
+
[DeviceTypes.CONTACT_SENSOR]: mdiDoorOpen,
|
|
111
|
+
[DeviceTypes.OCCUPANCY_SENSOR]: mdiMotionSensor,
|
|
112
|
+
[DeviceTypes.TEMPERATURE_SENSOR]: mdiThermometer,
|
|
113
|
+
[DeviceTypes.HUMIDITY_SENSOR]: mdiGauge,
|
|
114
|
+
[DeviceTypes.LIGHT_SENSOR]: mdiGauge,
|
|
115
|
+
[DeviceTypes.PRESSURE_SENSOR]: mdiGauge,
|
|
116
|
+
[DeviceTypes.FLOW_SENSOR]: mdiGauge,
|
|
117
|
+
|
|
118
|
+
// HVAC
|
|
119
|
+
[DeviceTypes.THERMOSTAT]: mdiThermometer,
|
|
120
|
+
[DeviceTypes.FAN]: mdiFan,
|
|
121
|
+
|
|
122
|
+
// Closures
|
|
123
|
+
[DeviceTypes.DOOR_LOCK]: mdiLock,
|
|
124
|
+
[DeviceTypes.WINDOW_COVERING]: mdiHome,
|
|
125
|
+
|
|
126
|
+
// Media
|
|
127
|
+
[DeviceTypes.SPEAKER]: mdiSpeaker,
|
|
128
|
+
[DeviceTypes.BASIC_VIDEO_PLAYER]: mdiTelevision,
|
|
129
|
+
[DeviceTypes.TELEVISION]: mdiTelevision,
|
|
130
|
+
|
|
131
|
+
// Infrastructure
|
|
132
|
+
[DeviceTypes.ROOT_NODE]: mdiHome,
|
|
133
|
+
[DeviceTypes.BRIDGE]: mdiRouter,
|
|
134
|
+
[DeviceTypes.AGGREGATOR]: mdiRouter,
|
|
135
|
+
|
|
136
|
+
// Water
|
|
137
|
+
[DeviceTypes.WATER_LEAK_DETECTOR]: mdiWater,
|
|
138
|
+
[DeviceTypes.WATER_VALVE]: mdiWater,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Maps Thread routing roles to MDI icon paths.
|
|
143
|
+
*/
|
|
144
|
+
const threadRoleToIcon: Record<number, string> = {
|
|
145
|
+
5: mdiRouter, // Router
|
|
146
|
+
6: mdiAccessPoint, // Leader
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Gets the primary device type ID for a node.
|
|
151
|
+
* Reads from DeviceTypeList attribute (0/29/0) on endpoint 1 or 0.
|
|
152
|
+
* The data comes as { 0: deviceTypeId, 1: revision } with numeric keys.
|
|
153
|
+
*/
|
|
154
|
+
export function getPrimaryDeviceType(node: MatterNode): number | undefined {
|
|
155
|
+
// Check endpoint 1 first (most common for Matter devices)
|
|
156
|
+
const deviceTypeList1 = node.attributes["1/29/0"] as Array<Record<string, number>> | undefined;
|
|
157
|
+
if (deviceTypeList1?.length) {
|
|
158
|
+
// Device type is at key "0" (numeric key as string)
|
|
159
|
+
const entry = deviceTypeList1[0];
|
|
160
|
+
return entry?.["0"] ?? entry?.deviceType;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Fall back to endpoint 0 (root node)
|
|
164
|
+
const deviceTypeList0 = node.attributes["0/29/0"] as Array<Record<string, number>> | undefined;
|
|
165
|
+
if (deviceTypeList0?.length) {
|
|
166
|
+
const entry = deviceTypeList0[0];
|
|
167
|
+
return entry?.["0"] ?? entry?.deviceType;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Gets the appropriate MDI icon path for a node.
|
|
175
|
+
* Considers device type and Thread role.
|
|
176
|
+
*/
|
|
177
|
+
export function getDeviceIcon(node: MatterNode, threadRole?: number): string {
|
|
178
|
+
// For Thread routers/leaders, show network infrastructure icons
|
|
179
|
+
if (threadRole !== undefined && threadRoleToIcon[threadRole]) {
|
|
180
|
+
// But only if the device is primarily an infrastructure device
|
|
181
|
+
const deviceType = getPrimaryDeviceType(node);
|
|
182
|
+
if (deviceType === DeviceTypes.ROOT_NODE || deviceType === DeviceTypes.BRIDGE || node.is_bridge) {
|
|
183
|
+
return threadRoleToIcon[threadRole];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Check for bridge first
|
|
188
|
+
if (node.is_bridge) {
|
|
189
|
+
return mdiRouter;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Look up by device type
|
|
193
|
+
const deviceType = getPrimaryDeviceType(node);
|
|
194
|
+
if (deviceType !== undefined && deviceTypeToIcon[deviceType]) {
|
|
195
|
+
return deviceTypeToIcon[deviceType];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Default icon
|
|
199
|
+
return mdiHome;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Gets the appropriate MDI icon path for a network type.
|
|
204
|
+
*/
|
|
205
|
+
export function getNetworkTypeIcon(networkType: string): string {
|
|
206
|
+
switch (networkType) {
|
|
207
|
+
case "thread":
|
|
208
|
+
return mdiAccessPoint;
|
|
209
|
+
case "wifi":
|
|
210
|
+
return mdiWifi;
|
|
211
|
+
case "ethernet":
|
|
212
|
+
return mdiRouter;
|
|
213
|
+
default:
|
|
214
|
+
return mdiHome;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Creates an SVG data URL from an MDI icon path for use in vis.js.
|
|
220
|
+
* @param iconPath - The MDI icon path
|
|
221
|
+
* @param color - The icon color (CSS color string)
|
|
222
|
+
* @param size - The icon size in pixels
|
|
223
|
+
* @returns A data URL containing the SVG
|
|
224
|
+
*/
|
|
225
|
+
export function createIconDataUrl(iconPath: string, color: string, size: number = 48): string {
|
|
226
|
+
// MDI icons use a 24x24 viewBox
|
|
227
|
+
const svg = `
|
|
228
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="${size}" height="${size}">
|
|
229
|
+
<circle cx="12" cy="12" r="11" fill="white" stroke="${color}" stroke-width="1"/>
|
|
230
|
+
<path d="${iconPath}" fill="${color}" transform="scale(0.6) translate(8,8)"/>
|
|
231
|
+
</svg>
|
|
232
|
+
`.trim();
|
|
233
|
+
|
|
234
|
+
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Creates an SVG data URL for a network graph node.
|
|
239
|
+
* @param node - The Matter node
|
|
240
|
+
* @param threadRole - Optional Thread routing role
|
|
241
|
+
* @param isSelected - Whether the node is selected
|
|
242
|
+
* @param isOffline - Whether the node is offline
|
|
243
|
+
* @returns A data URL containing the SVG
|
|
244
|
+
*/
|
|
245
|
+
export function createNodeIconDataUrl(
|
|
246
|
+
node: MatterNode,
|
|
247
|
+
threadRole?: number,
|
|
248
|
+
isSelected: boolean = false,
|
|
249
|
+
isOffline: boolean = false,
|
|
250
|
+
): string {
|
|
251
|
+
const iconPath = getDeviceIcon(node, threadRole);
|
|
252
|
+
let color: string;
|
|
253
|
+
if (isSelected) {
|
|
254
|
+
color = isOffline ? "#b71c1c" : "#1976d2"; // Dark red for selected+offline, blue for selected
|
|
255
|
+
} else if (isOffline) {
|
|
256
|
+
color = "#d32f2f"; // Red for offline
|
|
257
|
+
} else {
|
|
258
|
+
color = getDefaultIconColor(); // Theme-aware default
|
|
259
|
+
}
|
|
260
|
+
return createIconDataUrl(iconPath, color);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Creates an SVG data URL for an unknown Thread device (question mark).
|
|
265
|
+
* @param isRouter - Whether the device appears to be a router
|
|
266
|
+
* @param isSelected - Whether the node is selected
|
|
267
|
+
* @returns A data URL containing the SVG
|
|
268
|
+
*/
|
|
269
|
+
export function createUnknownDeviceIconDataUrl(isRouter: boolean = false, isSelected: boolean = false): string {
|
|
270
|
+
const iconPath = isRouter ? mdiAccessPoint : mdiHelp;
|
|
271
|
+
const color = isSelected ? "#1976d2" : "#ff9800"; // Orange for unknown
|
|
272
|
+
return createIconDataUrl(iconPath, color);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Creates an SVG data URL for a WiFi access point/router.
|
|
277
|
+
* @param isSelected - Whether the node is selected
|
|
278
|
+
* @returns A data URL containing the SVG
|
|
279
|
+
*/
|
|
280
|
+
export function createWiFiRouterIconDataUrl(isSelected: boolean = false): string {
|
|
281
|
+
const color = isSelected ? "#1976d2" : "#2196f3"; // Blue for WiFi AP
|
|
282
|
+
return createIconDataUrl(mdiWifi, color);
|
|
283
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025-2026 Open Home Foundation
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import "@material/web/list/list";
|
|
8
|
+
import "@material/web/list/list-item";
|
|
9
|
+
import type { MatterNode } from "@matter-server/ws-client";
|
|
10
|
+
import { mdiChevronDown, mdiChevronRight, mdiEthernet, mdiRouter, mdiWifi } from "@mdi/js";
|
|
11
|
+
import { LitElement, css, html, nothing } from "lit";
|
|
12
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
13
|
+
import "../../components/ha-svg-icon";
|
|
14
|
+
import { getDeviceName } from "./network-utils.js";
|
|
15
|
+
|
|
16
|
+
declare global {
|
|
17
|
+
interface HTMLElementTagNameMap {
|
|
18
|
+
"device-panel": DevicePanel;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type PanelType = "wifi" | "ethernet" | "bridges";
|
|
23
|
+
|
|
24
|
+
@customElement("device-panel")
|
|
25
|
+
export class DevicePanel extends LitElement {
|
|
26
|
+
@property()
|
|
27
|
+
public type: PanelType = "wifi";
|
|
28
|
+
|
|
29
|
+
@property({ type: Array })
|
|
30
|
+
public nodeIds: number[] = [];
|
|
31
|
+
|
|
32
|
+
@property({ type: Object })
|
|
33
|
+
public nodes: Record<string, MatterNode> = {};
|
|
34
|
+
|
|
35
|
+
@property({ type: Boolean })
|
|
36
|
+
public expanded = true;
|
|
37
|
+
|
|
38
|
+
@state()
|
|
39
|
+
private _isExpanded = true;
|
|
40
|
+
|
|
41
|
+
override willUpdate(changedProperties: Map<string, unknown>): void {
|
|
42
|
+
if (changedProperties.has("expanded")) {
|
|
43
|
+
this._isExpanded = this.expanded;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private _getIcon(): string {
|
|
48
|
+
switch (this.type) {
|
|
49
|
+
case "wifi":
|
|
50
|
+
return mdiWifi;
|
|
51
|
+
case "ethernet":
|
|
52
|
+
return mdiEthernet;
|
|
53
|
+
case "bridges":
|
|
54
|
+
return mdiRouter;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private _getTitle(): string {
|
|
59
|
+
switch (this.type) {
|
|
60
|
+
case "wifi":
|
|
61
|
+
return "WiFi Devices";
|
|
62
|
+
case "ethernet":
|
|
63
|
+
return "Ethernet Devices";
|
|
64
|
+
case "bridges":
|
|
65
|
+
return "Bridges";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private _toggleExpanded(): void {
|
|
70
|
+
this._isExpanded = !this._isExpanded;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private _handleNodeClick(nodeId: number): void {
|
|
74
|
+
this.dispatchEvent(
|
|
75
|
+
new CustomEvent("node-selected", {
|
|
76
|
+
detail: { nodeId },
|
|
77
|
+
bubbles: true,
|
|
78
|
+
composed: true,
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
override render() {
|
|
84
|
+
if (this.nodeIds.length === 0) {
|
|
85
|
+
return nothing;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return html`
|
|
89
|
+
<div class="panel">
|
|
90
|
+
<div class="header" @click=${this._toggleExpanded}>
|
|
91
|
+
<ha-svg-icon .path=${this._getIcon()} class="type-icon"></ha-svg-icon>
|
|
92
|
+
<span class="title">${this._getTitle()}</span>
|
|
93
|
+
<span class="count">(${this.nodeIds.length})</span>
|
|
94
|
+
<ha-svg-icon
|
|
95
|
+
.path=${this._isExpanded ? mdiChevronDown : mdiChevronRight}
|
|
96
|
+
class="expand-icon"
|
|
97
|
+
></ha-svg-icon>
|
|
98
|
+
</div>
|
|
99
|
+
${this._isExpanded
|
|
100
|
+
? html`
|
|
101
|
+
<md-list class="device-list">
|
|
102
|
+
${this.nodeIds.map(nodeId => {
|
|
103
|
+
const node = this.nodes[nodeId.toString()];
|
|
104
|
+
if (!node) return nothing;
|
|
105
|
+
|
|
106
|
+
return html`
|
|
107
|
+
<md-list-item type="button" @click=${() => this._handleNodeClick(nodeId)}>
|
|
108
|
+
<div slot="headline">Node ${nodeId}</div>
|
|
109
|
+
<div slot="supporting-text">${getDeviceName(node)}</div>
|
|
110
|
+
<ha-svg-icon slot="end" .path=${mdiChevronRight}></ha-svg-icon>
|
|
111
|
+
</md-list-item>
|
|
112
|
+
`;
|
|
113
|
+
})}
|
|
114
|
+
</md-list>
|
|
115
|
+
`
|
|
116
|
+
: nothing}
|
|
117
|
+
</div>
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static override styles = css`
|
|
122
|
+
:host {
|
|
123
|
+
display: block;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.panel {
|
|
127
|
+
background-color: var(--md-sys-color-surface, #fff);
|
|
128
|
+
border-radius: 8px;
|
|
129
|
+
border: 1px solid var(--md-sys-color-outline-variant, #ccc);
|
|
130
|
+
overflow: hidden;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.header {
|
|
134
|
+
display: flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
padding: 12px 16px;
|
|
137
|
+
cursor: pointer;
|
|
138
|
+
user-select: none;
|
|
139
|
+
background-color: var(--md-sys-color-surface-container, #f5f5f5);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.header:hover {
|
|
143
|
+
background-color: var(--md-sys-color-surface-container-high, #e8e8e8);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.type-icon {
|
|
147
|
+
--icon-primary-color: var(--md-sys-color-primary, #6200ee);
|
|
148
|
+
margin-right: 12px;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.title {
|
|
152
|
+
font-weight: 500;
|
|
153
|
+
color: var(--md-sys-color-on-surface, #333);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.count {
|
|
157
|
+
margin-left: 8px;
|
|
158
|
+
color: var(--md-sys-color-on-surface-variant, #666);
|
|
159
|
+
font-size: 0.875rem;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.expand-icon {
|
|
163
|
+
margin-left: auto;
|
|
164
|
+
--icon-primary-color: var(--md-sys-color-on-surface-variant, #666);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.device-list {
|
|
168
|
+
--md-list-item-leading-space: 16px;
|
|
169
|
+
--md-list-item-trailing-space: 16px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
md-list-item {
|
|
173
|
+
--md-list-item-one-line-container-height: 48px;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
md-list-item::part(focus-ring) {
|
|
177
|
+
display: none;
|
|
178
|
+
}
|
|
179
|
+
`;
|
|
180
|
+
}
|