@sukramj/fabric-ui 0.1.2 → 0.2.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/README.md +14 -1
- package/package.json +1 -1
- package/src/lib/api/client.ts +134 -1
- package/src/lib/api/schema.gen.ts +1838 -264
- package/src/lib/api/stream.ts +40 -1
- package/src/lib/context.ts +15 -2
- package/src/lib/controller-store.svelte.ts +175 -0
- package/src/lib/i18n/de.json +212 -1
- package/src/lib/i18n/en.json +212 -1
- package/src/lib/index.ts +1 -0
- package/src/lib/pages/controller/Commission.svelte +180 -0
- package/src/lib/pages/controller/Node.svelte +91 -0
- package/src/lib/pages/controller/Nodes.svelte +156 -0
- package/src/lib/pages/controller/Settings.svelte +160 -0
- package/src/lib/pages/controller/format.ts +49 -0
- package/src/lib/pages/controller/node/Acl.svelte +151 -0
- package/src/lib/pages/controller/node/Attributes.svelte +100 -0
- package/src/lib/pages/controller/node/Bindings.svelte +114 -0
- package/src/lib/pages/controller/node/Endpoints.svelte +49 -0
- package/src/lib/pages/controller/node/Fabrics.svelte +83 -0
- package/src/lib/pages/controller/node/Icd.svelte +92 -0
- package/src/lib/pages/controller/node/Overview.svelte +81 -0
- package/src/lib/pages/controller/node/Updates.svelte +75 -0
- package/src/lib/pages/controller/node/Window.svelte +80 -0
- package/src/lib/pages/index.ts +7 -1
- package/src/lib/pages/node/Selection.svelte +11 -6
- package/src/lib/pages/node/fields.ts +47 -12
package/README.md
CHANGED
|
@@ -43,11 +43,20 @@ Pages: `NodeStatus`, `NodePair`, `NodeFabrics` (with maintenance),
|
|
|
43
43
|
context; none names a host concept. The selection page is driven by the
|
|
44
44
|
provider's schema (see the repository's `docs/provider.md`).
|
|
45
45
|
|
|
46
|
+
Controller pages (v0.2.0): `ControllerNodes`, `ControllerCommission`,
|
|
47
|
+
`ControllerSettings`, and `ControllerNode` with the props `nodeId` (the
|
|
48
|
+
16-digit hex `node_id_hex`) and an optional first `tab` (`overview`,
|
|
49
|
+
`endpoints`, `attributes`, `acl`, `bindings`, `window`, `icd`, `fabrics`,
|
|
50
|
+
`updates`). They read `ui.controller` (`createControllerStore`); the
|
|
51
|
+
routes `controllerNodes`, `controllerCommission`, `controllerSettings` and
|
|
52
|
+
the prefix `controllerNode` link them. `ui.controller.loadStatus()` sets
|
|
53
|
+
`present` to false on a 404: the host serves no controller.
|
|
54
|
+
|
|
46
55
|
### Translator
|
|
47
56
|
|
|
48
57
|
`t(key, vars)` is the host's. `createTranslator(locale, extra)` builds one
|
|
49
58
|
over the package's catalogues (`catalogs.en`, `catalogs.de`, keys
|
|
50
|
-
`node.*`, `common.*`) plus the host's own; a `locale` getter that reads
|
|
59
|
+
`node.*`, `controller.*`, `shell.*`, `common.*`) plus the host's own; a `locale` getter that reads
|
|
51
60
|
reactive state re-renders the pages on a language switch.
|
|
52
61
|
|
|
53
62
|
### Stylesheet
|
|
@@ -80,6 +89,10 @@ createFabricUI({ client, subscribe: (onEvent) => hub.on("fabric-ui", onEvent) })
|
|
|
80
89
|
const close = subscribeStream(client.streamUrl(), (ev) => hub.publish(ev.type, ev.data));
|
|
81
90
|
```
|
|
82
91
|
|
|
92
|
+
`ui.controller.connect()` opens `GET /controller/stream` the same way;
|
|
93
|
+
`subscribeControllerStream` and the `subscribeController` option of
|
|
94
|
+
`createFabricUI` are its twins.
|
|
95
|
+
|
|
83
96
|
## Developing
|
|
84
97
|
|
|
85
98
|
```sh
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sukramj/fabric-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The web interface of go-fabric nodes: Svelte 5 pages, UI kit, design tokens with a Home Assistant theme bridge, and the generated API client.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "SukramJ",
|
package/src/lib/api/client.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The client of the node routes, typed from contract/openapi.yaml
|
|
1
|
+
// The client of the node and controller routes, typed from contract/openapi.yaml
|
|
2
2
|
// (schema.gen.ts). It is the TypeScript half of the contract: the tests in
|
|
3
3
|
// client.test.ts read the same golden fixtures as the Go handler tests.
|
|
4
4
|
// Mirrors the matter* functions of openccu-loom
|
|
@@ -40,6 +40,49 @@ export type FabricEvent = S["FabricEvent"];
|
|
|
40
40
|
export type WindowEvent = S["WindowEvent"];
|
|
41
41
|
export type ExposureEvent = S["ExposureEvent"];
|
|
42
42
|
|
|
43
|
+
// The controller family (contract 0.2.0).
|
|
44
|
+
export type ControllerStatus = S["ControllerStatus"];
|
|
45
|
+
export type ControllerCapabilities = S["ControllerCapabilities"];
|
|
46
|
+
export type ConnectionState = S["ConnectionState"];
|
|
47
|
+
export type ControllerNodeSummary = S["ControllerNodeSummary"];
|
|
48
|
+
export type ControllerNodeList = S["ControllerNodeList"];
|
|
49
|
+
export type ControllerNodeDetail = S["ControllerNodeDetail"];
|
|
50
|
+
export type NodeRecord = S["NodeRecord"];
|
|
51
|
+
export type NodeProperties = S["NodeProperties"];
|
|
52
|
+
export type NodeEndpoint = S["NodeEndpoint"];
|
|
53
|
+
export type NodeCluster = S["NodeCluster"];
|
|
54
|
+
export type NodeDeviceType = S["NodeDeviceType"];
|
|
55
|
+
export type NodeWindow = S["NodeWindow"];
|
|
56
|
+
export type AttributePath = S["AttributePath"];
|
|
57
|
+
export type AttributeValue = S["AttributeValue"];
|
|
58
|
+
export type AttributeValueList = S["AttributeValueList"];
|
|
59
|
+
export type WriteRequest = S["WriteRequest"];
|
|
60
|
+
export type WriteResult = S["WriteResult"];
|
|
61
|
+
export type InvokeRequest = S["InvokeRequest"];
|
|
62
|
+
export type InvokeResult = S["InvokeResult"];
|
|
63
|
+
export type AclEntry = S["AclEntry"];
|
|
64
|
+
export type AclTarget = S["AclTarget"];
|
|
65
|
+
export type BindingTarget = S["BindingTarget"];
|
|
66
|
+
export type EndpointBindings = S["EndpointBindings"];
|
|
67
|
+
export type BindingList = S["BindingList"];
|
|
68
|
+
export type IcdState = S["IcdState"];
|
|
69
|
+
export type NodeFabric = S["NodeFabric"];
|
|
70
|
+
export type NodeFabricList = S["NodeFabricList"];
|
|
71
|
+
export type CommissionRequest = S["CommissionRequest"];
|
|
72
|
+
export type Discovered = S["Discovered"];
|
|
73
|
+
export type DiscoveredList = S["DiscoveredList"];
|
|
74
|
+
export type Credential = S["Credential"];
|
|
75
|
+
export type CredentialList = S["CredentialList"];
|
|
76
|
+
export type CredentialRequest = S["CredentialRequest"];
|
|
77
|
+
export type PendingUpdate = S["PendingUpdate"];
|
|
78
|
+
export type UpdateQueue = S["UpdateQueue"];
|
|
79
|
+
export type UpdateConsentRequest = S["UpdateConsentRequest"];
|
|
80
|
+
export type AvailableUpdate = S["AvailableUpdate"];
|
|
81
|
+
export type AvailableUpdateList = S["AvailableUpdateList"];
|
|
82
|
+
export type ControllerEvent = S["ControllerEvent"];
|
|
83
|
+
/** What GET /controller/discovery selects by. */
|
|
84
|
+
export type DiscoverFilter = { filter?: "any" | "long_discriminator" | "short_discriminator" | "vendor" | "device_type"; value?: number; timeout?: number };
|
|
85
|
+
|
|
43
86
|
/** The header the admin token travels in (token mode). */
|
|
44
87
|
export const TOKEN_HEADER = "X-Fabric-UI-Token";
|
|
45
88
|
|
|
@@ -83,6 +126,42 @@ export interface FabricClient {
|
|
|
83
126
|
factoryReset(): Promise<void>;
|
|
84
127
|
/** The URL of the event stream. */
|
|
85
128
|
streamUrl(): string;
|
|
129
|
+
|
|
130
|
+
// The controller family. Every node id is the 16-digit hex of node_id_hex.
|
|
131
|
+
/** The controller's status; it sets the admin token from csrf_token, as status() does. 404 when the host serves no controller. */
|
|
132
|
+
controllerStatus(): Promise<ControllerStatus>;
|
|
133
|
+
controllerNodes(): Promise<ControllerNodeList>;
|
|
134
|
+
controllerNode(id: string): Promise<ControllerNodeDetail>;
|
|
135
|
+
decommissionNode(id: string): Promise<void>;
|
|
136
|
+
interviewNode(id: string): Promise<void>;
|
|
137
|
+
enableNode(id: string): Promise<void>;
|
|
138
|
+
disableNode(id: string): Promise<void>;
|
|
139
|
+
setNodeAutoSubscribe(id: string, enabled: boolean): Promise<void>;
|
|
140
|
+
openNodeWindow(id: string, timeoutSeconds?: number): Promise<NodeWindow>;
|
|
141
|
+
closeNodeWindow(id: string): Promise<void>;
|
|
142
|
+
readAttributes(id: string, paths: AttributePath[], fabricFiltered?: boolean): Promise<AttributeValueList>;
|
|
143
|
+
writeAttribute(id: string, req: WriteRequest): Promise<WriteResult>;
|
|
144
|
+
invokeCommand(id: string, req: InvokeRequest): Promise<InvokeResult>;
|
|
145
|
+
nodeAcl(id: string): Promise<{ entries: AclEntry[] }>;
|
|
146
|
+
setNodeAcl(id: string, entries: AclEntry[]): Promise<WriteResult>;
|
|
147
|
+
nodeBindings(id: string): Promise<BindingList>;
|
|
148
|
+
setNodeBindings(id: string, endpoint: number, bindings: BindingTarget[]): Promise<WriteResult>;
|
|
149
|
+
nodeIcd(id: string): Promise<IcdState>;
|
|
150
|
+
registerNodeIcd(id: string, allowMultiAdmin?: boolean): Promise<IcdState>;
|
|
151
|
+
unregisterNodeIcd(id: string, force?: boolean): Promise<IcdState>;
|
|
152
|
+
nodeFabrics(id: string): Promise<NodeFabricList>;
|
|
153
|
+
removeNodeFabric(id: string, fabricIndex: number): Promise<void>;
|
|
154
|
+
commission(req: CommissionRequest): Promise<ControllerNodeDetail>;
|
|
155
|
+
discover(filter?: DiscoverFilter): Promise<DiscoveredList>;
|
|
156
|
+
credentials(): Promise<CredentialList>;
|
|
157
|
+
setCredential(req: CredentialRequest): Promise<void>;
|
|
158
|
+
removeCredential(kind: Credential["kind"], id: string): Promise<void>;
|
|
159
|
+
setFabricLabel(label: string): Promise<void>;
|
|
160
|
+
updates(): Promise<UpdateQueue>;
|
|
161
|
+
consentUpdate(req: UpdateConsentRequest): Promise<void>;
|
|
162
|
+
checkUpdates(): Promise<AvailableUpdateList>;
|
|
163
|
+
/** The URL of the controller's event stream. */
|
|
164
|
+
controllerStreamUrl(): string;
|
|
86
165
|
}
|
|
87
166
|
|
|
88
167
|
type Fetch = (input: string, init?: RequestInit) => Promise<Response>;
|
|
@@ -147,9 +226,63 @@ export function createClient(baseUrl: string, fetchImpl: Fetch = (i, init) => fe
|
|
|
147
226
|
factoryReset: () =>
|
|
148
227
|
call("POST", "/node/maintenance/factory-reset", { admin: true, body: { confirm: FACTORY_RESET_CONFIRMATION } }),
|
|
149
228
|
streamUrl: () => base + "/node/stream",
|
|
229
|
+
|
|
230
|
+
async controllerStatus() {
|
|
231
|
+
const s = await call<ControllerStatus>("GET", "/controller/status");
|
|
232
|
+
if (s.csrf_token) token = s.csrf_token;
|
|
233
|
+
return s;
|
|
234
|
+
},
|
|
235
|
+
controllerNodes: () => call("GET", "/controller/nodes"),
|
|
236
|
+
controllerNode: (id) => call("GET", node(id)),
|
|
237
|
+
decommissionNode: (id) => call("DELETE", node(id), { admin: true }),
|
|
238
|
+
interviewNode: (id) => call("POST", node(id) + "/interview", { admin: true }),
|
|
239
|
+
enableNode: (id) => call("POST", node(id) + "/enable", { admin: true }),
|
|
240
|
+
disableNode: (id) => call("POST", node(id) + "/disable", { admin: true }),
|
|
241
|
+
setNodeAutoSubscribe: (id, enabled) => call("POST", node(id) + "/auto-subscribe", { admin: true, body: { enabled } }),
|
|
242
|
+
openNodeWindow: (id, timeoutSeconds) =>
|
|
243
|
+
call("POST", node(id) + "/window", { admin: true, body: timeoutSeconds === undefined ? {} : { timeout_seconds: timeoutSeconds } }),
|
|
244
|
+
closeNodeWindow: (id) => call("POST", node(id) + "/window/close", { admin: true }),
|
|
245
|
+
readAttributes: (id, paths, fabricFiltered) =>
|
|
246
|
+
call("POST", node(id) + "/attributes", { body: fabricFiltered === undefined ? { paths } : { paths, fabric_filtered: fabricFiltered } }),
|
|
247
|
+
writeAttribute: (id, req) => call("PUT", node(id) + "/attributes", { admin: true, body: req }),
|
|
248
|
+
invokeCommand: (id, req) => call("POST", node(id) + "/commands", { admin: true, body: req }),
|
|
249
|
+
nodeAcl: (id) => call("GET", node(id) + "/acl"),
|
|
250
|
+
setNodeAcl: (id, entries) => call("PUT", node(id) + "/acl", { admin: true, body: { entries } }),
|
|
251
|
+
nodeBindings: (id) => call("GET", node(id) + "/bindings"),
|
|
252
|
+
setNodeBindings: (id, endpoint, bindings) => call("PUT", node(id) + "/bindings", { admin: true, body: { endpoint, bindings } }),
|
|
253
|
+
nodeIcd: (id) => call("GET", node(id) + "/icd"),
|
|
254
|
+
registerNodeIcd: (id, allowMultiAdmin) =>
|
|
255
|
+
call("POST", node(id) + "/icd/register", { admin: true, body: allowMultiAdmin === undefined ? {} : { allow_multi_admin: allowMultiAdmin } }),
|
|
256
|
+
unregisterNodeIcd: (id, force) =>
|
|
257
|
+
call("POST", node(id) + "/icd/unregister", { admin: true, body: force === undefined ? {} : { force } }),
|
|
258
|
+
nodeFabrics: (id) => call("GET", node(id) + "/fabrics"),
|
|
259
|
+
removeNodeFabric: (id, i) => call("DELETE", `${node(id)}/fabrics/${encodeURIComponent(String(i))}`, { admin: true }),
|
|
260
|
+
commission: (req) => call("POST", "/controller/commission", { admin: true, body: req }),
|
|
261
|
+
discover(f = {}) {
|
|
262
|
+
const q = new URLSearchParams();
|
|
263
|
+
if (f.filter) q.set("filter", f.filter);
|
|
264
|
+
if (f.value !== undefined) q.set("value", String(f.value));
|
|
265
|
+
if (f.timeout !== undefined) q.set("timeout", String(f.timeout));
|
|
266
|
+
const qs = q.toString();
|
|
267
|
+
return call("GET", "/controller/discovery" + (qs ? "?" + qs : ""), { admin: true });
|
|
268
|
+
},
|
|
269
|
+
credentials: () => call("GET", "/controller/credentials"),
|
|
270
|
+
setCredential: (req) => call("PUT", "/controller/credentials", { admin: true, body: req }),
|
|
271
|
+
removeCredential: (kind, id) =>
|
|
272
|
+
call("DELETE", `/controller/credentials?${new URLSearchParams({ kind, id }).toString()}`, { admin: true }),
|
|
273
|
+
setFabricLabel: (label) => call("PUT", "/controller/fabric/label", { admin: true, body: { label } }),
|
|
274
|
+
updates: () => call("GET", "/controller/updates"),
|
|
275
|
+
consentUpdate: (req) => call("POST", "/controller/updates/consent", { admin: true, body: req }),
|
|
276
|
+
checkUpdates: () => call("POST", "/controller/updates/check", { admin: true }),
|
|
277
|
+
controllerStreamUrl: () => base + "/controller/stream",
|
|
150
278
|
};
|
|
151
279
|
}
|
|
152
280
|
|
|
281
|
+
/** The path of a controller node. */
|
|
282
|
+
function node(id: string): string {
|
|
283
|
+
return `/controller/nodes/${encodeURIComponent(id)}`;
|
|
284
|
+
}
|
|
285
|
+
|
|
153
286
|
function isProblem(v: unknown): v is Problem {
|
|
154
287
|
return typeof v === "object" && v !== null && "code" in v && "title" in v;
|
|
155
288
|
}
|