@matter-server/dashboard 1.0.0 → 1.1.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/acl/acl-actions.d.ts +12 -0
- package/dist/esm/components/dialogs/acl/acl-actions.d.ts.map +1 -0
- package/dist/esm/components/dialogs/acl/acl-actions.js +56 -0
- package/dist/esm/components/dialogs/acl/acl-actions.js.map +6 -0
- package/dist/esm/components/dialogs/acl/model.d.ts +2 -2
- package/dist/esm/components/dialogs/acl/model.d.ts.map +1 -1
- package/dist/esm/components/dialogs/acl/model.js +11 -15
- package/dist/esm/components/dialogs/acl/model.js.map +1 -1
- package/dist/esm/components/dialogs/acl/node-acl-add-dialog.d.ts +43 -0
- package/dist/esm/components/dialogs/acl/node-acl-add-dialog.d.ts.map +1 -0
- package/dist/esm/components/dialogs/acl/node-acl-add-dialog.js +353 -0
- package/dist/esm/components/dialogs/acl/node-acl-add-dialog.js.map +6 -0
- package/dist/esm/components/dialogs/acl/show-node-acl-add-dialog.d.ts +8 -0
- package/dist/esm/components/dialogs/acl/show-node-acl-add-dialog.d.ts.map +1 -0
- package/dist/esm/components/dialogs/acl/show-node-acl-add-dialog.js +15 -0
- package/dist/esm/components/dialogs/acl/show-node-acl-add-dialog.js.map +6 -0
- package/dist/esm/components/dialogs/binding/binding-actions.d.ts +17 -0
- package/dist/esm/components/dialogs/binding/binding-actions.d.ts.map +1 -0
- package/dist/esm/components/dialogs/binding/binding-actions.js +135 -0
- package/dist/esm/components/dialogs/binding/binding-actions.js.map +6 -0
- package/dist/esm/components/dialogs/binding/model.d.ts +1 -1
- package/dist/esm/components/dialogs/binding/model.d.ts.map +1 -1
- package/dist/esm/components/dialogs/binding/model.js +8 -3
- package/dist/esm/components/dialogs/binding/model.js.map +1 -1
- package/dist/esm/components/dialogs/binding/node-binding-dialog.d.ts +16 -24
- package/dist/esm/components/dialogs/binding/node-binding-dialog.d.ts.map +1 -1
- package/dist/esm/components/dialogs/binding/node-binding-dialog.js +210 -332
- package/dist/esm/components/dialogs/binding/node-binding-dialog.js.map +1 -1
- package/dist/esm/pages/cluster-commands/clusters/access-control-commands.d.ts +37 -0
- package/dist/esm/pages/cluster-commands/clusters/access-control-commands.d.ts.map +1 -0
- package/dist/esm/pages/cluster-commands/clusters/access-control-commands.js +387 -0
- package/dist/esm/pages/cluster-commands/clusters/access-control-commands.js.map +6 -0
- package/dist/esm/pages/cluster-commands/clusters/binding-commands.d.ts +35 -0
- package/dist/esm/pages/cluster-commands/clusters/binding-commands.d.ts.map +1 -0
- package/dist/esm/pages/cluster-commands/clusters/binding-commands.js +254 -0
- package/dist/esm/pages/cluster-commands/clusters/binding-commands.js.map +6 -0
- package/dist/esm/pages/cluster-commands/index.d.ts +2 -0
- package/dist/esm/pages/cluster-commands/index.d.ts.map +1 -1
- package/dist/esm/pages/cluster-commands/index.js +2 -0
- package/dist/esm/pages/cluster-commands/index.js.map +1 -1
- package/dist/esm/pages/components/node-details.d.ts +0 -1
- package/dist/esm/pages/components/node-details.d.ts.map +1 -1
- package/dist/esm/pages/components/node-details.js +2 -18
- package/dist/esm/pages/components/node-details.js.map +1 -1
- package/dist/esm/pages/components/server-details.js +3 -3
- package/dist/esm/pages/components/server-details.js.map +1 -1
- package/dist/esm/pages/matter-cluster-view.d.ts.map +1 -1
- package/dist/esm/pages/matter-cluster-view.js +2 -1
- package/dist/esm/pages/matter-cluster-view.js.map +1 -1
- package/dist/esm/pages/matter-endpoint-view.d.ts +3 -3
- package/dist/esm/pages/matter-endpoint-view.d.ts.map +1 -1
- package/dist/esm/pages/matter-endpoint-view.js +13 -10
- package/dist/esm/pages/matter-endpoint-view.js.map +1 -1
- package/dist/esm/pages/matter-node-view.js +2 -2
- package/dist/esm/pages/matter-node-view.js.map +1 -1
- package/dist/esm/pages/network/network-utils.d.ts +1 -5
- package/dist/esm/pages/network/network-utils.d.ts.map +1 -1
- package/dist/esm/pages/network/network-utils.js +1 -11
- package/dist/esm/pages/network/network-utils.js.map +1 -1
- package/dist/esm/util/access-control.d.ts +54 -0
- package/dist/esm/util/access-control.d.ts.map +1 -0
- package/dist/esm/util/access-control.js +100 -0
- package/dist/esm/util/access-control.js.map +6 -0
- package/dist/esm/util/binding.d.ts +54 -0
- package/dist/esm/util/binding.d.ts.map +1 -0
- package/dist/esm/util/binding.js +113 -0
- package/dist/esm/util/binding.js.map +6 -0
- package/dist/esm/util/endpoints.d.ts +9 -0
- package/dist/esm/util/endpoints.d.ts.map +1 -0
- package/dist/esm/util/endpoints.js +18 -0
- package/dist/esm/util/endpoints.js.map +6 -0
- package/dist/esm/util/node-name.d.ts +12 -0
- package/dist/esm/util/node-name.d.ts.map +1 -0
- package/dist/esm/util/node-name.js +15 -0
- package/dist/esm/util/node-name.js.map +6 -0
- package/dist/web/js/{attribute-write-dialog-W7xpCE2E.js → attribute-write-dialog-CqqdRniU.js} +1 -1
- package/dist/web/js/{command-invoke-dialog-BAqAAdJw.js → command-invoke-dialog-BuvBOrdC.js} +1 -1
- package/dist/web/js/{commission-node-dialog-BTzCGgdy.js → commission-node-dialog-nVZp3go0.js} +5 -5
- package/dist/web/js/{commission-node-existing-B2M2hyDh.js → commission-node-existing-Cx3Ahk2t.js} +2 -2
- package/dist/web/js/{commission-node-thread-djdz2dXW.js → commission-node-thread-CI8mWWPs.js} +2 -2
- package/dist/web/js/{commission-node-wifi-DxAYNS1A.js → commission-node-wifi-lUX4LK2R.js} +2 -2
- package/dist/web/js/{dialog-box-tHvPVxDN.js → dialog-box-bAdbnf-T.js} +1 -1
- package/dist/web/js/{fire_event-BleYfTLc.js → fire_event-tWhqPfdz.js} +1 -1
- package/dist/web/js/main.js +1 -1
- package/dist/web/js/{matter-dashboard-app-CVi_GDky.js → matter-dashboard-app-CONA_608.js} +1485 -390
- package/dist/web/js/node-acl-add-dialog-DlR-sF-b.js +320 -0
- package/dist/web/js/node-binding-dialog-DhnX_86M.js +267 -0
- package/dist/web/js/{node-label-dialog-DVZSjsXU.js → node-label-dialog-T3nPG-Qy.js} +1 -1
- package/dist/web/js/{settings-dialog-BG5MgZcO.js → settings-dialog-DrHzJtsi.js} +1 -1
- package/package.json +4 -4
- package/src/components/dialogs/acl/acl-actions.ts +71 -0
- package/src/components/dialogs/acl/model.ts +18 -17
- package/src/components/dialogs/acl/node-acl-add-dialog.ts +350 -0
- package/src/components/dialogs/acl/show-node-acl-add-dialog.ts +14 -0
- package/src/components/dialogs/binding/binding-actions.ts +201 -0
- package/src/components/dialogs/binding/model.ts +11 -4
- package/src/components/dialogs/binding/node-binding-dialog.ts +221 -399
- package/src/pages/cluster-commands/clusters/access-control-commands.ts +407 -0
- package/src/pages/cluster-commands/clusters/binding-commands.ts +273 -0
- package/src/pages/cluster-commands/index.ts +2 -0
- package/src/pages/components/node-details.ts +2 -21
- package/src/pages/components/server-details.ts +3 -3
- package/src/pages/matter-cluster-view.ts +4 -1
- package/src/pages/matter-endpoint-view.ts +16 -10
- package/src/pages/matter-node-view.ts +2 -2
- package/src/pages/network/network-utils.ts +1 -18
- package/src/util/access-control.ts +135 -0
- package/src/util/binding.ts +182 -0
- package/src/util/endpoints.ts +17 -0
- package/src/util/node-name.ts +18 -0
- package/dist/web/js/node-binding-dialog-B9IdqHrZ.js +0 -624
|
@@ -1,624 +0,0 @@
|
|
|
1
|
-
import { i, c, a as clientContext, n, e, b as i$1, p as showPromptDialog, s as showAlertDialog, A, d as b, h as handleAsync, g as t } from './matter-dashboard-app-CVi_GDky.js';
|
|
2
|
-
import { p as preventDefault } from './prevent_default-D-ohDGsN.js';
|
|
3
|
-
import './main.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @license
|
|
7
|
-
* Copyright 2025-2026 Open Home Foundation
|
|
8
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
9
|
-
*/
|
|
10
|
-
const MatterStatusNames = {
|
|
11
|
-
0: "Success",
|
|
12
|
-
1: "Failure",
|
|
13
|
-
125: "InvalidSubscription",
|
|
14
|
-
126: "UnsupportedAccess",
|
|
15
|
-
127: "UnsupportedEndpoint",
|
|
16
|
-
128: "InvalidAction",
|
|
17
|
-
129: "UnsupportedCommand",
|
|
18
|
-
133: "InvalidCommand",
|
|
19
|
-
134: "UnsupportedAttribute",
|
|
20
|
-
135: "ConstraintError",
|
|
21
|
-
136: "UnsupportedWrite",
|
|
22
|
-
137: "ResourceExhausted",
|
|
23
|
-
139: "NotFound",
|
|
24
|
-
140: "UnreportableAttribute",
|
|
25
|
-
141: "InvalidDataType",
|
|
26
|
-
143: "UnsupportedRead",
|
|
27
|
-
146: "DataVersionMismatch",
|
|
28
|
-
148: "Timeout",
|
|
29
|
-
155: "UnsupportedNode",
|
|
30
|
-
156: "Busy",
|
|
31
|
-
157: "AccessRestricted",
|
|
32
|
-
195: "UnsupportedCluster",
|
|
33
|
-
197: "NoUpstreamSubscription",
|
|
34
|
-
198: "NeedsTimedInteraction",
|
|
35
|
-
199: "UnsupportedEvent",
|
|
36
|
-
200: "PathsExhausted",
|
|
37
|
-
201: "TimedRequestMismatch",
|
|
38
|
-
202: "FailsafeRequired",
|
|
39
|
-
203: "InvalidInState",
|
|
40
|
-
204: "NoCommandResponse",
|
|
41
|
-
205: "TermsAndConditionsChanged",
|
|
42
|
-
206: "MaintenanceRequired",
|
|
43
|
-
207: "DynamicConstraintError",
|
|
44
|
-
208: "AlreadyExists",
|
|
45
|
-
209: "InvalidTransportType"
|
|
46
|
-
};
|
|
47
|
-
function getMatterStatusName(status) {
|
|
48
|
-
return MatterStatusNames[status] ?? `Unknown(${status})`;
|
|
49
|
-
}
|
|
50
|
-
function analyzeBatchResults(results) {
|
|
51
|
-
if (!results || results.length === 0) {
|
|
52
|
-
return {
|
|
53
|
-
outcome: "all_failed",
|
|
54
|
-
successCount: 0,
|
|
55
|
-
failureCount: 0,
|
|
56
|
-
errorCounts: {},
|
|
57
|
-
message: "No response/results returned"
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
let successCount = 0;
|
|
61
|
-
let failureCount = 0;
|
|
62
|
-
const errorCounts = {};
|
|
63
|
-
for (const result of results) {
|
|
64
|
-
if (result.status === 0) {
|
|
65
|
-
successCount++;
|
|
66
|
-
} else {
|
|
67
|
-
failureCount++;
|
|
68
|
-
errorCounts[result.status] = (errorCounts[result.status] ?? 0) + 1;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
let outcome;
|
|
72
|
-
if (failureCount === 0) {
|
|
73
|
-
outcome = "all_success";
|
|
74
|
-
} else if (successCount === 0) {
|
|
75
|
-
outcome = "all_failed";
|
|
76
|
-
} else {
|
|
77
|
-
outcome = "partial";
|
|
78
|
-
}
|
|
79
|
-
const message = formatBatchMessage(outcome, successCount, failureCount, errorCounts);
|
|
80
|
-
return {
|
|
81
|
-
outcome,
|
|
82
|
-
successCount,
|
|
83
|
-
failureCount,
|
|
84
|
-
errorCounts,
|
|
85
|
-
message
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
function formatBatchMessage(outcome, successCount, failureCount, errorCounts) {
|
|
89
|
-
const entryWord = count => count === 1 ? "entry" : "entries";
|
|
90
|
-
if (outcome === "all_success") {
|
|
91
|
-
return "Write successful";
|
|
92
|
-
}
|
|
93
|
-
const errorParts = Object.entries(errorCounts).map(([code, count]) => {
|
|
94
|
-
const statusCode = parseInt(code, 10);
|
|
95
|
-
return `${count}x ${getMatterStatusName(statusCode)} (${code})`;
|
|
96
|
-
}).join(", ");
|
|
97
|
-
if (outcome === "all_failed") {
|
|
98
|
-
return `All ${failureCount} ${entryWord(failureCount)} failed: ${errorParts}`;
|
|
99
|
-
}
|
|
100
|
-
return `Partial failure: ${successCount} ${entryWord(successCount)} succeeded, ${failureCount} failed (${errorParts}). Please verify.`;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
var _staticBlock$1, _staticBlock2;
|
|
104
|
-
/**
|
|
105
|
-
* @license
|
|
106
|
-
* Copyright 2025-2026 Open Home Foundation
|
|
107
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
108
|
-
*/
|
|
109
|
-
class AccessControlTargetTransformer {
|
|
110
|
-
static transform(input) {
|
|
111
|
-
if (!input || typeof input !== "object") {
|
|
112
|
-
throw new Error("Invalid input: expected an object");
|
|
113
|
-
}
|
|
114
|
-
const result = {};
|
|
115
|
-
const keyMapping = AccessControlTargetTransformer.KEY_MAPPING;
|
|
116
|
-
for (const key in input) {
|
|
117
|
-
if (key in keyMapping) {
|
|
118
|
-
const mappedKey = keyMapping[key];
|
|
119
|
-
if (mappedKey) {
|
|
120
|
-
const value = input[key];
|
|
121
|
-
if (value === void 0) continue;
|
|
122
|
-
result[mappedKey] = value;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return result;
|
|
127
|
-
}
|
|
128
|
-
static #_ = _staticBlock$1 = () => this.KEY_MAPPING = {
|
|
129
|
-
"0": "cluster",
|
|
130
|
-
"1": "endpoint",
|
|
131
|
-
"2": "deviceType"
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
_staticBlock$1();
|
|
135
|
-
class AccessControlEntryDataTransformer {
|
|
136
|
-
static transform(input) {
|
|
137
|
-
if (!input || typeof input !== "object") {
|
|
138
|
-
throw new Error("Invalid input: expected an object");
|
|
139
|
-
}
|
|
140
|
-
const result = {};
|
|
141
|
-
const keyMapping = AccessControlEntryDataTransformer.KEY_MAPPING;
|
|
142
|
-
for (const key in input) {
|
|
143
|
-
if (key in keyMapping) {
|
|
144
|
-
const mappedKey = keyMapping[key];
|
|
145
|
-
if (mappedKey) {
|
|
146
|
-
const value = input[key];
|
|
147
|
-
if (value === void 0) continue;
|
|
148
|
-
if (mappedKey === "subjects") {
|
|
149
|
-
result[mappedKey] = Array.isArray(value) ? value : void 0;
|
|
150
|
-
} else if (mappedKey === "targets") {
|
|
151
|
-
if (Array.isArray(value)) {
|
|
152
|
-
const _targets = Object.values(value).map(val => AccessControlTargetTransformer.transform(val));
|
|
153
|
-
result[mappedKey] = _targets;
|
|
154
|
-
} else {
|
|
155
|
-
result[mappedKey] = void 0;
|
|
156
|
-
}
|
|
157
|
-
} else {
|
|
158
|
-
result[mappedKey] = value;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return result;
|
|
164
|
-
}
|
|
165
|
-
static #_ = _staticBlock2 = () => this.KEY_MAPPING = {
|
|
166
|
-
"1": "privilege",
|
|
167
|
-
"2": "authMode",
|
|
168
|
-
"3": "subjects",
|
|
169
|
-
"4": "targets",
|
|
170
|
-
"254": "fabricIndex"
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
_staticBlock2();
|
|
174
|
-
|
|
175
|
-
var _staticBlock;
|
|
176
|
-
/**
|
|
177
|
-
* @license
|
|
178
|
-
* Copyright 2025-2026 Open Home Foundation
|
|
179
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
180
|
-
*/
|
|
181
|
-
class BindingEntryDataTransformer {
|
|
182
|
-
static transform(input) {
|
|
183
|
-
if (!input || typeof input !== "object") {
|
|
184
|
-
throw new Error("Invalid input: expected an object");
|
|
185
|
-
}
|
|
186
|
-
const result = {};
|
|
187
|
-
const keyMapping = BindingEntryDataTransformer.KEY_MAPPING;
|
|
188
|
-
for (const key in input) {
|
|
189
|
-
if (key in keyMapping) {
|
|
190
|
-
const mappedKey = keyMapping[key];
|
|
191
|
-
if (mappedKey) {
|
|
192
|
-
const value = input[key];
|
|
193
|
-
if (value === void 0) {
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
if (mappedKey === "node") {
|
|
197
|
-
result[mappedKey] = value;
|
|
198
|
-
} else {
|
|
199
|
-
result[mappedKey] = Number(value);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return result;
|
|
205
|
-
}
|
|
206
|
-
static #_ = _staticBlock = () => this.KEY_MAPPING = {
|
|
207
|
-
"1": "node",
|
|
208
|
-
"2": "group",
|
|
209
|
-
"3": "endpoint",
|
|
210
|
-
"4": "cluster",
|
|
211
|
-
"254": "fabricIndex"
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
_staticBlock();
|
|
215
|
-
|
|
216
|
-
var __defProp = Object.defineProperty;
|
|
217
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
218
|
-
var __decorateClass = (decorators, target, key, kind) => {
|
|
219
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
220
|
-
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
221
|
-
if (kind && result) __defProp(target, key, result);
|
|
222
|
-
return result;
|
|
223
|
-
};
|
|
224
|
-
let NodeBindingDialog = class extends i$1 {
|
|
225
|
-
fetchBindingEntry() {
|
|
226
|
-
const bindings_raw = this.node.attributes[this.endpoint + "/30/0"];
|
|
227
|
-
if (!bindings_raw) return [];
|
|
228
|
-
return Object.values(bindings_raw).map(value => BindingEntryDataTransformer.transform(value));
|
|
229
|
-
}
|
|
230
|
-
fetchACLEntry(targetNodeId) {
|
|
231
|
-
var _this$client$nodes$St;
|
|
232
|
-
const acl_cluster_raw = (_this$client$nodes$St = this.client.nodes[String(targetNodeId)]) === null || _this$client$nodes$St === void 0 ? void 0 : _this$client$nodes$St.attributes["0/31/0"];
|
|
233
|
-
if (!acl_cluster_raw) return [];
|
|
234
|
-
return Object.values(acl_cluster_raw).map(value => AccessControlEntryDataTransformer.transform(value));
|
|
235
|
-
}
|
|
236
|
-
async deleteBindingHandler(index) {
|
|
237
|
-
const rawBindings = this.fetchBindingEntry();
|
|
238
|
-
try {
|
|
239
|
-
const targetNodeId = rawBindings[index].node;
|
|
240
|
-
const endpoint = rawBindings[index].endpoint;
|
|
241
|
-
if (targetNodeId === void 0 || endpoint === void 0) return;
|
|
242
|
-
let aclCleanedUp = false;
|
|
243
|
-
try {
|
|
244
|
-
await this.removeNodeAtACLEntry(this.node.node_id, endpoint, targetNodeId);
|
|
245
|
-
aclCleanedUp = true;
|
|
246
|
-
} catch (aclError) {
|
|
247
|
-
const errorMessage = aclError instanceof Error ? aclError.message : String(aclError);
|
|
248
|
-
const proceed = await showPromptDialog({
|
|
249
|
-
title: "ACL cleanup failed",
|
|
250
|
-
text: `Could not clean up ACL on target node ${targetNodeId}: ${errorMessage}. The target node may no longer exist or be unreachable. Do you want to remove the binding anyway? Note: The target device may retain an outdated ACL entry.`,
|
|
251
|
-
confirmText: "Remove binding"
|
|
252
|
-
});
|
|
253
|
-
if (!proceed) return;
|
|
254
|
-
}
|
|
255
|
-
try {
|
|
256
|
-
const updatedBindings = this.removeBindingAtIndex(rawBindings, index);
|
|
257
|
-
await this.syncBindingUpdates(updatedBindings, index);
|
|
258
|
-
} catch (bindingError) {
|
|
259
|
-
const errorMessage = bindingError instanceof Error ? bindingError.message : String(bindingError);
|
|
260
|
-
await showAlertDialog({
|
|
261
|
-
title: "Binding removal failed",
|
|
262
|
-
text: `Failed to remove the binding: ${errorMessage}. ` + (aclCleanedUp ? "The ACL on the target device was already updated. The binding and ACL may now be out of sync." : "No changes were made.")
|
|
263
|
-
});
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
} catch (error) {
|
|
267
|
-
this.handleBindingDeletionError(error);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
async removeNodeAtACLEntry(sourceNodeId, sourceEndpoint, targetNodeId) {
|
|
271
|
-
const aclEntries = this.fetchACLEntry(targetNodeId);
|
|
272
|
-
const updatedACLEntries = aclEntries.map(entry => this.removeEntryAtACL(sourceNodeId, sourceEndpoint, entry)).filter(entry => entry !== void 0);
|
|
273
|
-
const apiEntries = updatedACLEntries.map(e => this.toAccessControlEntry(e));
|
|
274
|
-
await this.client.setACLEntry(targetNodeId, apiEntries);
|
|
275
|
-
}
|
|
276
|
-
removeEntryAtACL(nodeId, sourceEndpoint, entry) {
|
|
277
|
-
const hasSubject = entry.subjects.includes(nodeId);
|
|
278
|
-
if (!hasSubject) return entry;
|
|
279
|
-
const hasTarget = entry.targets.filter(item => item.endpoint === sourceEndpoint);
|
|
280
|
-
return hasTarget.length > 0 ? void 0 : entry;
|
|
281
|
-
}
|
|
282
|
-
removeBindingAtIndex(bindings, index) {
|
|
283
|
-
return [...bindings.slice(0, index), ...bindings.slice(index + 1)];
|
|
284
|
-
}
|
|
285
|
-
async syncBindingUpdates(updatedBindings, index) {
|
|
286
|
-
const apiBindings = updatedBindings.map(b => this.toBindingTarget(b));
|
|
287
|
-
await this.client.setNodeBinding(this.node.node_id, this.endpoint, apiBindings);
|
|
288
|
-
const attributePath = `${this.endpoint}/30/0`;
|
|
289
|
-
const currentBindings = this.node.attributes[attributePath];
|
|
290
|
-
const updatedAttributes = {
|
|
291
|
-
...this.node.attributes,
|
|
292
|
-
[attributePath]: currentBindings ? this.removeBindingAtIndex(currentBindings, index) : []
|
|
293
|
-
};
|
|
294
|
-
this.node.attributes = updatedAttributes;
|
|
295
|
-
this.requestUpdate();
|
|
296
|
-
}
|
|
297
|
-
handleBindingDeletionError(error) {
|
|
298
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
299
|
-
console.error(`Binding deletion failed: ${errorMessage}`);
|
|
300
|
-
}
|
|
301
|
-
async add_target_acl(targetNodeId, entry) {
|
|
302
|
-
try {
|
|
303
|
-
var _this$client$nodes$St2;
|
|
304
|
-
const rawEntries = (_this$client$nodes$St2 = this.client.nodes[String(targetNodeId)]) === null || _this$client$nodes$St2 === void 0 ? void 0 : _this$client$nodes$St2.attributes["0/31/0"];
|
|
305
|
-
const entries = rawEntries ? Object.values(rawEntries).map(v => AccessControlEntryDataTransformer.transform(v)) : [];
|
|
306
|
-
entries.push(entry);
|
|
307
|
-
const apiEntries = entries.map(e => this.toAccessControlEntry(e));
|
|
308
|
-
const results = await this.client.setACLEntry(targetNodeId, apiEntries);
|
|
309
|
-
const batchResult = analyzeBatchResults(results);
|
|
310
|
-
if (batchResult.outcome !== "all_success") {
|
|
311
|
-
console.error(`Set ACL entry: ${batchResult.message}`);
|
|
312
|
-
}
|
|
313
|
-
return batchResult;
|
|
314
|
-
} catch (err) {
|
|
315
|
-
console.error("Add ACL error:", err);
|
|
316
|
-
return {
|
|
317
|
-
outcome: "all_failed",
|
|
318
|
-
successCount: 0,
|
|
319
|
-
failureCount: 1,
|
|
320
|
-
errorCounts: {
|
|
321
|
-
1: 1
|
|
322
|
-
},
|
|
323
|
-
message: `Exception: ${err instanceof Error ? err.message : String(err)}`
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
/** Convert local BindingEntryStruct to API BindingTarget (without fabricIndex) */
|
|
328
|
-
toBindingTarget(entry) {
|
|
329
|
-
return {
|
|
330
|
-
node: entry.node ?? null,
|
|
331
|
-
group: entry.group ?? null,
|
|
332
|
-
endpoint: entry.endpoint ?? null,
|
|
333
|
-
cluster: entry.cluster ?? null
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
/** Convert local AccessControlEntryStruct to API AccessControlEntry (without fabricIndex) */
|
|
337
|
-
toAccessControlEntry(entry) {
|
|
338
|
-
var _entry$targets;
|
|
339
|
-
return {
|
|
340
|
-
privilege: entry.privilege,
|
|
341
|
-
auth_mode: entry.authMode,
|
|
342
|
-
subjects: entry.subjects ?? null,
|
|
343
|
-
targets: ((_entry$targets = entry.targets) === null || _entry$targets === void 0 ? void 0 : _entry$targets.map(t => ({
|
|
344
|
-
cluster: t.cluster ?? null,
|
|
345
|
-
endpoint: t.endpoint ?? null,
|
|
346
|
-
device_type: t.deviceType ?? null
|
|
347
|
-
}))) ?? null
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
async add_bindings(endpoint, bindingEntry) {
|
|
351
|
-
const bindings = this.fetchBindingEntry();
|
|
352
|
-
bindings.push(bindingEntry);
|
|
353
|
-
try {
|
|
354
|
-
const apiBindings = bindings.map(b => this.toBindingTarget(b));
|
|
355
|
-
const results = await this.client.setNodeBinding(this.node.node_id, endpoint, apiBindings);
|
|
356
|
-
const batchResult = analyzeBatchResults(results);
|
|
357
|
-
if (batchResult.outcome !== "all_success") {
|
|
358
|
-
console.error(`Set binding: ${batchResult.message}`);
|
|
359
|
-
}
|
|
360
|
-
return batchResult;
|
|
361
|
-
} catch (err) {
|
|
362
|
-
console.error("Add bindings error:", err);
|
|
363
|
-
return {
|
|
364
|
-
outcome: "all_failed",
|
|
365
|
-
successCount: 0,
|
|
366
|
-
failureCount: 1,
|
|
367
|
-
errorCounts: {
|
|
368
|
-
1: 1
|
|
369
|
-
},
|
|
370
|
-
message: `Exception: ${err instanceof Error ? err.message : String(err)}`
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
async addBindingHandler() {
|
|
375
|
-
var _this$_targetNodeId$v;
|
|
376
|
-
let targetNodeId;
|
|
377
|
-
const rawNodeId = (_this$_targetNodeId$v = this._targetNodeId.value) === null || _this$_targetNodeId$v === void 0 ? void 0 : _this$_targetNodeId$v.trim();
|
|
378
|
-
if (rawNodeId) {
|
|
379
|
-
if (!/^\d+$/.test(rawNodeId)) {
|
|
380
|
-
showAlertDialog({
|
|
381
|
-
title: "Validation error",
|
|
382
|
-
text: "Please enter a valid target node ID"
|
|
383
|
-
});
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
targetNodeId = BigInt(rawNodeId);
|
|
387
|
-
}
|
|
388
|
-
const targetEndpoint = this._targetEndpoint.value ? parseInt(this._targetEndpoint.value, 10) : void 0;
|
|
389
|
-
const targetCluster = this._targetCluster.value ? parseInt(this._targetCluster.value, 10) : void 0;
|
|
390
|
-
if (targetNodeId === void 0 || targetNodeId <= 0n) {
|
|
391
|
-
showAlertDialog({
|
|
392
|
-
title: "Validation error",
|
|
393
|
-
text: "Please enter a valid target node ID"
|
|
394
|
-
});
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
if (targetEndpoint === void 0 || targetEndpoint <= 0 || targetEndpoint > 65534) {
|
|
398
|
-
showAlertDialog({
|
|
399
|
-
title: "Validation error",
|
|
400
|
-
text: "Please enter a valid target endpoint"
|
|
401
|
-
});
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
if (targetCluster !== void 0) {
|
|
405
|
-
if (targetCluster < 0 || targetCluster > 32767) {
|
|
406
|
-
showAlertDialog({
|
|
407
|
-
title: "Validation error",
|
|
408
|
-
text: "Please enter a valid target cluster"
|
|
409
|
-
});
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
const targets = {
|
|
414
|
-
endpoint: targetEndpoint,
|
|
415
|
-
cluster: targetCluster,
|
|
416
|
-
deviceType: void 0
|
|
417
|
-
};
|
|
418
|
-
const acl_entry = {
|
|
419
|
-
privilege: 3,
|
|
420
|
-
authMode: 2,
|
|
421
|
-
subjects: [this.node.node_id],
|
|
422
|
-
targets: [targets],
|
|
423
|
-
fabricIndex: 0
|
|
424
|
-
// Placeholder - server will use correct fabric index
|
|
425
|
-
};
|
|
426
|
-
const aclResult = await this.add_target_acl(targetNodeId, acl_entry);
|
|
427
|
-
if (aclResult.outcome === "all_failed") {
|
|
428
|
-
showAlertDialog({
|
|
429
|
-
title: "Failed to add ACL entry",
|
|
430
|
-
text: aclResult.message
|
|
431
|
-
});
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
if (aclResult.outcome === "partial") {
|
|
435
|
-
showAlertDialog({
|
|
436
|
-
title: "ACL entry partially failed",
|
|
437
|
-
text: aclResult.message
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
const endpoint = this.endpoint;
|
|
441
|
-
const bindingEntry = {
|
|
442
|
-
node: targetNodeId,
|
|
443
|
-
endpoint: targetEndpoint,
|
|
444
|
-
group: void 0,
|
|
445
|
-
cluster: targetCluster,
|
|
446
|
-
fabricIndex: void 0
|
|
447
|
-
// Server will use correct fabric index
|
|
448
|
-
};
|
|
449
|
-
const bindingResult = await this.add_bindings(endpoint, bindingEntry);
|
|
450
|
-
if (bindingResult.outcome === "all_success") {
|
|
451
|
-
this._targetNodeId.value = "";
|
|
452
|
-
this._targetEndpoint.value = "";
|
|
453
|
-
this._targetCluster.value = "";
|
|
454
|
-
this.requestUpdate();
|
|
455
|
-
} else if (bindingResult.outcome === "partial") {
|
|
456
|
-
showAlertDialog({
|
|
457
|
-
title: "Binding partially failed",
|
|
458
|
-
text: bindingResult.message
|
|
459
|
-
});
|
|
460
|
-
this.requestUpdate();
|
|
461
|
-
} else {
|
|
462
|
-
showAlertDialog({
|
|
463
|
-
title: "Failed to add binding",
|
|
464
|
-
text: bindingResult.message
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
_close() {
|
|
469
|
-
this.shadowRoot.querySelector("md-dialog").close();
|
|
470
|
-
}
|
|
471
|
-
_handleClosed() {
|
|
472
|
-
this.parentNode.removeChild(this);
|
|
473
|
-
}
|
|
474
|
-
onChange(e) {
|
|
475
|
-
const textfield = e.target;
|
|
476
|
-
if (textfield.type === "number" && textfield.max && textfield.min) {
|
|
477
|
-
const value = parseInt(textfield.value, 10);
|
|
478
|
-
if (parseInt(textfield.max, 10) < value || value < parseInt(textfield.min, 10)) {
|
|
479
|
-
textfield.error = true;
|
|
480
|
-
textfield.errorText = "value error";
|
|
481
|
-
} else {
|
|
482
|
-
textfield.error = false;
|
|
483
|
-
}
|
|
484
|
-
} else {
|
|
485
|
-
textfield.error = textfield.value !== "" && !/^[0-9]+$/.test(textfield.value);
|
|
486
|
-
if (textfield.error) {
|
|
487
|
-
textfield.errorText = "must be a numeric value";
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
render() {
|
|
492
|
-
const rawBindings = this.node.attributes[this.endpoint + "/30/0"];
|
|
493
|
-
const bindings = rawBindings ? Object.values(rawBindings).map(entry => BindingEntryDataTransformer.transform(entry)) : [];
|
|
494
|
-
return b`
|
|
495
|
-
<md-dialog open @cancel=${preventDefault} @closed=${this._handleClosed}>
|
|
496
|
-
<div slot="headline">
|
|
497
|
-
<div>Binding</div>
|
|
498
|
-
</div>
|
|
499
|
-
<div slot="content">
|
|
500
|
-
<div>
|
|
501
|
-
<md-list style="padding-bottom:16px;">
|
|
502
|
-
${Object.values(bindings).map((entry, index) => b`
|
|
503
|
-
<md-list-item class="binding-item">
|
|
504
|
-
<div style="display:flex;gap:8px;">
|
|
505
|
-
<div>node:${entry["node"]}</div>
|
|
506
|
-
<div>endpoint:${entry["endpoint"]}</div>
|
|
507
|
-
${entry["cluster"] ? b` <div>cluster:${entry["cluster"]}</div> ` : A}
|
|
508
|
-
</div>
|
|
509
|
-
<div slot="end">
|
|
510
|
-
<md-text-button
|
|
511
|
-
@click=${handleAsync(() => this.deleteBindingHandler(index))}
|
|
512
|
-
>delete</md-text-button
|
|
513
|
-
</div>
|
|
514
|
-
</md-list-item>
|
|
515
|
-
`)}
|
|
516
|
-
</md-list>
|
|
517
|
-
<div class="inline-group">
|
|
518
|
-
<div class="group-label">target</div>
|
|
519
|
-
<div class="group-input">
|
|
520
|
-
<md-outlined-text-field
|
|
521
|
-
label="node id"
|
|
522
|
-
name="NodeId"
|
|
523
|
-
type="text"
|
|
524
|
-
pattern="[0-9]+"
|
|
525
|
-
class="target-item"
|
|
526
|
-
@change=${this.onChange}
|
|
527
|
-
supporting-text="required"
|
|
528
|
-
></md-outlined-text-field>
|
|
529
|
-
<md-outlined-text-field
|
|
530
|
-
label="endpoint"
|
|
531
|
-
name="Endpoint"
|
|
532
|
-
type="number"
|
|
533
|
-
min="0"
|
|
534
|
-
max="65534"
|
|
535
|
-
@change=${this.onChange}
|
|
536
|
-
class="target-item"
|
|
537
|
-
supporting-text="required"
|
|
538
|
-
></md-outlined-text-field>
|
|
539
|
-
<md-outlined-text-field
|
|
540
|
-
label="cluster"
|
|
541
|
-
name="Cluster"
|
|
542
|
-
type="number"
|
|
543
|
-
min="0"
|
|
544
|
-
max="32767"
|
|
545
|
-
@change=${this.onChange}
|
|
546
|
-
class="target-item"
|
|
547
|
-
supporting-text="optional"
|
|
548
|
-
></md-outlined-text-field>
|
|
549
|
-
</div>
|
|
550
|
-
</div>
|
|
551
|
-
<div style="margin:8px;">
|
|
552
|
-
<span style="font-size: 0.75rem;font-style: italic;font-weight: bold;">
|
|
553
|
-
Note: The Cluster ID field is optional according to the Matter specification. If you
|
|
554
|
-
leave it blank, the binding applies to all eligible clusters on the target endpoint.
|
|
555
|
-
However, some devices may require a specific cluster to be set in order for the binding
|
|
556
|
-
to function correctly. If you experience unexpected behavior, try specifying the cluster
|
|
557
|
-
explicitly.
|
|
558
|
-
</span>
|
|
559
|
-
</div>
|
|
560
|
-
</div>
|
|
561
|
-
</div>
|
|
562
|
-
<div slot="actions">
|
|
563
|
-
<md-text-button @click=${handleAsync(() => this.addBindingHandler())}>Add</md-text-button>
|
|
564
|
-
<md-text-button @click=${this._close}>Cancel</md-text-button>
|
|
565
|
-
</div>
|
|
566
|
-
</md-dialog>
|
|
567
|
-
`;
|
|
568
|
-
}
|
|
569
|
-
};
|
|
570
|
-
NodeBindingDialog.styles = i`
|
|
571
|
-
.binding-item {
|
|
572
|
-
background: var(--md-sys-color-surface-container-high);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
.inline-group {
|
|
576
|
-
display: flex;
|
|
577
|
-
border: 2px solid var(--md-sys-color-primary);
|
|
578
|
-
padding: 1px;
|
|
579
|
-
border-radius: 8px;
|
|
580
|
-
position: relative;
|
|
581
|
-
margin: 8px;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
.group-input {
|
|
585
|
-
display: flex;
|
|
586
|
-
width: -webkit-fill-available;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
.target-item {
|
|
590
|
-
display: inline-block;
|
|
591
|
-
padding: 16px 8px 8px 8px;
|
|
592
|
-
border-radius: 4px;
|
|
593
|
-
vertical-align: middle;
|
|
594
|
-
min-width: 80px;
|
|
595
|
-
text-align: center;
|
|
596
|
-
width: -webkit-fill-available;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
.group-label {
|
|
600
|
-
position: absolute;
|
|
601
|
-
left: 16px;
|
|
602
|
-
top: -12px;
|
|
603
|
-
background: var(--md-sys-color-primary);
|
|
604
|
-
color: var(--md-sys-color-on-primary);
|
|
605
|
-
padding: 4px 16px;
|
|
606
|
-
border-radius: 4px;
|
|
607
|
-
}
|
|
608
|
-
`;
|
|
609
|
-
__decorateClass([c({
|
|
610
|
-
context: clientContext,
|
|
611
|
-
subscribe: true
|
|
612
|
-
}), n({
|
|
613
|
-
attribute: false
|
|
614
|
-
})], NodeBindingDialog.prototype, "client", 2);
|
|
615
|
-
__decorateClass([n()], NodeBindingDialog.prototype, "node", 2);
|
|
616
|
-
__decorateClass([n({
|
|
617
|
-
attribute: false
|
|
618
|
-
})], NodeBindingDialog.prototype, "endpoint", 2);
|
|
619
|
-
__decorateClass([e("md-outlined-text-field[name='NodeId']")], NodeBindingDialog.prototype, "_targetNodeId", 2);
|
|
620
|
-
__decorateClass([e("md-outlined-text-field[name='Endpoint']")], NodeBindingDialog.prototype, "_targetEndpoint", 2);
|
|
621
|
-
__decorateClass([e("md-outlined-text-field[name='Cluster']")], NodeBindingDialog.prototype, "_targetCluster", 2);
|
|
622
|
-
NodeBindingDialog = __decorateClass([t("node-binding-dialog")], NodeBindingDialog);
|
|
623
|
-
|
|
624
|
-
export { NodeBindingDialog };
|