@devicechain/widgets 0.23.0 → 0.24.0-rc.1
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 +8 -3
- package/dist/aggregate-fold.d.ts +3 -2
- package/dist/aggregate-strings.d.ts +1 -0
- package/dist/alarm-strings.d.ts +18 -0
- package/dist/binding-policy.d.ts +4 -0
- package/dist/bound-devices-notice.d.ts +30 -0
- package/dist/builtin-descriptors.d.ts +290 -249
- package/dist/builtin-option-docs.d.ts +2 -0
- package/dist/builtin-registry.d.ts +249 -226
- package/dist/category-geometry.d.ts +1 -0
- package/dist/chunks/{aggregate-feed-BD3CHNFU.js → aggregate-feed-4FSBQYJL.js} +39 -11
- package/dist/chunks/aggregate-feed-4FSBQYJL.js.map +7 -0
- package/dist/chunks/{aggregate-widgets-HZQI5HTX.js → aggregate-widgets-2ZQBW3YF.js} +376 -182
- package/dist/chunks/aggregate-widgets-2ZQBW3YF.js.map +7 -0
- package/dist/chunks/alarm-widgets-X4PX5MYJ.js +72 -0
- package/dist/chunks/alarm-widgets-X4PX5MYJ.js.map +7 -0
- package/dist/chunks/chunk-546TK2QG.js +722 -0
- package/dist/chunks/chunk-546TK2QG.js.map +7 -0
- package/dist/chunks/{chunk-WBKHYZU2.js → chunk-62CHYCHI.js} +6 -62
- package/dist/chunks/chunk-62CHYCHI.js.map +7 -0
- package/dist/chunks/chunk-BMFQOTYG.js +41 -0
- package/dist/chunks/chunk-BMFQOTYG.js.map +7 -0
- package/dist/chunks/{chunk-2D7JNKFE.js → chunk-JFH4QMLA.js} +28 -4
- package/dist/chunks/chunk-JFH4QMLA.js.map +7 -0
- package/dist/chunks/chunk-QMGKB6J3.js +84 -0
- package/dist/chunks/chunk-QMGKB6J3.js.map +7 -0
- package/dist/chunks/chunk-XNEBAY4J.js +19 -0
- package/dist/chunks/chunk-XNEBAY4J.js.map +7 -0
- package/dist/chunks/chunk-YYJV54BV.js +58 -0
- package/dist/chunks/chunk-YYJV54BV.js.map +7 -0
- package/dist/chunks/compare-feed-AY3CVKUZ.js +346 -0
- package/dist/chunks/compare-feed-AY3CVKUZ.js.map +7 -0
- package/dist/chunks/{control-widgets-3V2LSSRF.js → control-widgets-UCTJJJ2D.js} +267 -78
- package/dist/chunks/control-widgets-UCTJJJ2D.js.map +7 -0
- package/dist/chunks/event-markers-feed-GEGTIHXT.js +373 -0
- package/dist/chunks/event-markers-feed-GEGTIHXT.js.map +7 -0
- package/dist/chunks/{fleet-feed-5QGDLOV5.js → fleet-feed-2SKDR6ZQ.js} +47 -8
- package/dist/chunks/fleet-feed-2SKDR6ZQ.js.map +7 -0
- package/dist/chunks/presence-widgets-2LIUKN4Y.js +350 -0
- package/dist/chunks/presence-widgets-2LIUKN4Y.js.map +7 -0
- package/dist/command-control-strings.d.ts +5 -0
- package/dist/compare-feed.d.ts +13 -0
- package/dist/compare-model.d.ts +38 -0
- package/dist/compare-strings.d.ts +13 -0
- package/dist/documented-descriptors.d.ts +4826 -0
- package/dist/event-markers-feed.d.ts +11 -0
- package/dist/event-markers-model.d.ts +38 -0
- package/dist/event-markers-strings.d.ts +20 -0
- package/dist/event-markers-types.d.ts +7 -0
- package/dist/fleet-count-model.d.ts +13 -0
- package/dist/fleet-feed.d.ts +1 -0
- package/dist/fleet-model.d.ts +1 -0
- package/dist/fleet-strings.d.ts +11 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +946 -398
- package/dist/index.js.map +4 -4
- package/dist/presence-feed.d.ts +24 -0
- package/dist/presence-model.d.ts +13 -0
- package/dist/presence-strings.d.ts +19 -0
- package/dist/sparkline.d.ts +23 -0
- package/dist/use-bound-devices.d.ts +26 -0
- package/dist/widgets/aggregate-lazy.d.ts +81 -46
- package/dist/widgets/aggregate-widgets.d.ts +1 -0
- package/dist/widgets/alarm-grouped.d.ts +17 -0
- package/dist/widgets/alarm-lazy.d.ts +63 -16
- package/dist/widgets/alarm-severity.d.ts +8 -0
- package/dist/widgets/alarm-table.d.ts +2 -0
- package/dist/widgets/alarm-type-tabs.d.ts +10 -0
- package/dist/widgets/alarm-widgets.d.ts +2 -0
- package/dist/widgets/command-stepper.d.ts +22 -0
- package/dist/widgets/control-lazy.d.ts +16 -24
- package/dist/widgets/device-status.d.ts +4 -0
- package/dist/widgets/fleet-count.d.ts +2 -0
- package/dist/widgets/gauge.d.ts +2 -15
- package/dist/widgets/image.d.ts +2 -6
- package/dist/widgets/label.d.ts +2 -6
- package/dist/widgets/latest-card.d.ts +2 -13
- package/dist/widgets/lazy-chunk.d.ts +7 -2
- package/dist/widgets/level-lazy.d.ts +14 -96
- package/dist/widgets/map.d.ts +8 -5
- package/dist/widgets/online-doughnut.d.ts +2 -0
- package/dist/widgets/presence-lazy.d.ts +27 -0
- package/dist/widgets/presence-widgets.d.ts +3 -0
- package/dist/widgets/selection-lazy.d.ts +2 -4
- package/dist/widgets/table-lazy.d.ts +2 -7
- package/dist/widgets/time-series-chart.d.ts +62 -12
- package/package.json +13 -13
- package/dist/chunks/aggregate-feed-BD3CHNFU.js.map +0 -7
- package/dist/chunks/aggregate-widgets-HZQI5HTX.js.map +0 -7
- package/dist/chunks/alarm-widgets-7UY6MIYZ.js +0 -9
- package/dist/chunks/alarm-widgets-7UY6MIYZ.js.map +0 -7
- package/dist/chunks/chunk-2D7JNKFE.js.map +0 -7
- package/dist/chunks/chunk-6OGI4UFJ.js +0 -367
- package/dist/chunks/chunk-6OGI4UFJ.js.map +0 -7
- package/dist/chunks/chunk-WBKHYZU2.js.map +0 -7
- package/dist/chunks/control-widgets-3V2LSSRF.js.map +0 -7
- package/dist/chunks/fleet-feed-5QGDLOV5.js.map +0 -7
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
// src/widgets/severity.ts
|
|
2
|
-
import { css } from "@devicechain/widget-sdk";
|
|
3
|
-
var LEVEL_OF = {
|
|
4
|
-
CRITICAL: "critical",
|
|
5
|
-
MAJOR: "major",
|
|
6
|
-
MINOR: "minor",
|
|
7
|
-
WARNING: "warning"
|
|
8
|
-
};
|
|
9
|
-
var SEVERITY_ORDER = {
|
|
10
|
-
CRITICAL: 0,
|
|
11
|
-
MAJOR: 1,
|
|
12
|
-
MINOR: 2,
|
|
13
|
-
WARNING: 3,
|
|
14
|
-
INDETERMINATE: 4
|
|
15
|
-
};
|
|
16
|
-
function severityColor(colors, severity) {
|
|
17
|
-
const level = LEVEL_OF[severity];
|
|
18
|
-
return level ? colors[level] : css("muted-foreground");
|
|
19
|
-
}
|
|
20
|
-
function severityRank(severity) {
|
|
21
|
-
return SEVERITY_ORDER[severity] ?? 99;
|
|
22
|
-
}
|
|
23
|
-
function severityLabel(severity) {
|
|
24
|
-
if (!severity) return "";
|
|
25
|
-
return severity.charAt(0).toUpperCase() + severity.slice(1).toLowerCase();
|
|
26
|
-
}
|
|
27
|
-
function highestSeverity(severities) {
|
|
28
|
-
let best;
|
|
29
|
-
for (const s of severities) {
|
|
30
|
-
if (best === void 0 || severityRank(s) < severityRank(best)) best = s;
|
|
31
|
-
}
|
|
32
|
-
return best;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// src/widgets/alarm-table.tsx
|
|
36
|
-
import { Fragment, useState } from "react";
|
|
37
|
-
import {
|
|
38
|
-
css as css2,
|
|
39
|
-
fontVar,
|
|
40
|
-
FlashValue,
|
|
41
|
-
formatDateTime,
|
|
42
|
-
optBoolean,
|
|
43
|
-
optNumber,
|
|
44
|
-
optString,
|
|
45
|
-
useElementSize,
|
|
46
|
-
useSeverityColors,
|
|
47
|
-
useWidgetSelect,
|
|
48
|
-
useWidgetTimeZone,
|
|
49
|
-
WidgetFrame
|
|
50
|
-
} from "@devicechain/widget-sdk";
|
|
51
|
-
import { Fragment as Fragment2, jsx, jsxs } from "react/jsx-runtime";
|
|
52
|
-
var cell = { padding: "6px 10px", textAlign: "left", whiteSpace: "nowrap" };
|
|
53
|
-
var headCell = {
|
|
54
|
-
...cell,
|
|
55
|
-
position: "sticky",
|
|
56
|
-
top: 0,
|
|
57
|
-
background: css2("card"),
|
|
58
|
-
fontSize: fontVar("xs"),
|
|
59
|
-
fontWeight: 600,
|
|
60
|
-
textTransform: "uppercase",
|
|
61
|
-
letterSpacing: "0.04em",
|
|
62
|
-
color: css2("muted-foreground"),
|
|
63
|
-
borderBottom: `1px solid ${css2("border")}`
|
|
64
|
-
};
|
|
65
|
-
var ALARM_TABLE_NARROW_PX = 480;
|
|
66
|
-
var narrowCell = {
|
|
67
|
-
...cell,
|
|
68
|
-
padding: "6px 6px",
|
|
69
|
-
whiteSpace: "normal",
|
|
70
|
-
overflowWrap: "break-word",
|
|
71
|
-
wordBreak: "normal"
|
|
72
|
-
};
|
|
73
|
-
function hyphenBreaks(text) {
|
|
74
|
-
const parts = text.split("-").map((p, i, all) => i < all.length - 1 ? `${p}-` : p);
|
|
75
|
-
return parts.map((part, i) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
76
|
-
i > 0 ? /* @__PURE__ */ jsx("wbr", {}) : null,
|
|
77
|
-
part
|
|
78
|
-
] }, i));
|
|
79
|
-
}
|
|
80
|
-
function statusLabel(alarm) {
|
|
81
|
-
if (alarm.state === "CLEARED") return "Cleared";
|
|
82
|
-
return alarm.acknowledged ? "Acknowledged" : "Active";
|
|
83
|
-
}
|
|
84
|
-
function AlarmTable({ widget, data, actions }) {
|
|
85
|
-
const { alarms, total, loading } = data;
|
|
86
|
-
const canAct = actions?.can("alarm:write") ?? false;
|
|
87
|
-
const [sizeRef, size] = useElementSize();
|
|
88
|
-
const severity = useSeverityColors();
|
|
89
|
-
const narrow = size.width > 0 && size.width < ALARM_TABLE_NARROW_PX;
|
|
90
|
-
const columns = 5 + (narrow ? 0 : 2) + (canAct ? 1 : 0);
|
|
91
|
-
const text = narrow ? narrowCell : cell;
|
|
92
|
-
const precision = optNumber(widget.options, "precision");
|
|
93
|
-
const flash = optBoolean(widget.options, "flashOnChange");
|
|
94
|
-
const select = useWidgetSelect();
|
|
95
|
-
const timeZone = useWidgetTimeZone();
|
|
96
|
-
const selectionTarget = optString(widget.options, "selectionTarget");
|
|
97
|
-
const drillTo = select && selectionTarget ? { select, slot: selectionTarget } : void 0;
|
|
98
|
-
return /* @__PURE__ */ jsx(WidgetFrame, { title: optString(widget.options, "title"), flush: true, children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%" }, children: [
|
|
99
|
-
/* @__PURE__ */ jsx("div", { ref: sizeRef, style: { flex: 1, minHeight: 0, overflow: "auto" }, children: /* @__PURE__ */ jsxs(
|
|
100
|
-
"table",
|
|
101
|
-
{
|
|
102
|
-
style: {
|
|
103
|
-
width: "100%",
|
|
104
|
-
borderCollapse: "collapse",
|
|
105
|
-
fontSize: fontVar("md"),
|
|
106
|
-
color: css2("foreground"),
|
|
107
|
-
fontVariantNumeric: "tabular-nums"
|
|
108
|
-
},
|
|
109
|
-
children: [
|
|
110
|
-
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
111
|
-
/* @__PURE__ */ jsx("th", { style: { ...headCell, width: 4, padding: 0 }, "aria-hidden": "true" }),
|
|
112
|
-
/* @__PURE__ */ jsx("th", { scope: "col", style: headCell, children: "Severity" }),
|
|
113
|
-
narrow ? null : /* @__PURE__ */ jsx("th", { scope: "col", style: headCell, children: "Status" }),
|
|
114
|
-
/* @__PURE__ */ jsx("th", { scope: "col", style: headCell, children: "Originator" }),
|
|
115
|
-
/* @__PURE__ */ jsx("th", { scope: "col", style: headCell, children: "Alarm" }),
|
|
116
|
-
/* @__PURE__ */ jsx("th", { scope: "col", style: { ...headCell, textAlign: "right" }, children: "Value" }),
|
|
117
|
-
narrow ? null : /* @__PURE__ */ jsx("th", { scope: "col", style: headCell, children: "Raised" }),
|
|
118
|
-
canAct ? /* @__PURE__ */ jsx("th", { scope: "col", style: { ...headCell, textAlign: "right" }, children: "Actions" }) : null
|
|
119
|
-
] }) }),
|
|
120
|
-
/* @__PURE__ */ jsx("tbody", { children: alarms.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: columns, style: { ...cell, textAlign: "center", color: css2("muted-foreground") }, children: loading ? "Loading\u2026" : "No alarms" }) }) : alarms.map((alarm) => {
|
|
121
|
-
const color = severityColor(severity, alarm.severity);
|
|
122
|
-
return /* @__PURE__ */ jsxs("tr", { style: { borderBottom: `1px solid ${css2("border")}` }, children: [
|
|
123
|
-
/* @__PURE__ */ jsx("td", { "aria-hidden": "true", style: { padding: 0, width: 4, background: color } }),
|
|
124
|
-
/* @__PURE__ */ jsx("td", { style: cell, children: /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 6 }, children: [
|
|
125
|
-
/* @__PURE__ */ jsx(
|
|
126
|
-
"span",
|
|
127
|
-
{
|
|
128
|
-
"aria-hidden": "true",
|
|
129
|
-
style: { width: 8, height: 8, borderRadius: "50%", background: color, flexShrink: 0 }
|
|
130
|
-
}
|
|
131
|
-
),
|
|
132
|
-
severityLabel(alarm.severity)
|
|
133
|
-
] }) }),
|
|
134
|
-
narrow ? null : /* @__PURE__ */ jsx("td", { style: { ...cell, color: css2("muted-foreground") }, children: statusLabel(alarm) }),
|
|
135
|
-
/* @__PURE__ */ jsx("td", { style: text, children: /* @__PURE__ */ jsx(OriginatorCell, { alarm, drillTo }) }),
|
|
136
|
-
/* @__PURE__ */ jsx("td", { style: text, children: hyphenBreaks(alarm.alarmKey) }),
|
|
137
|
-
/* @__PURE__ */ jsx("td", { style: { ...cell, textAlign: "right" }, children: /* @__PURE__ */ jsx(
|
|
138
|
-
FlashValue,
|
|
139
|
-
{
|
|
140
|
-
value: alarm.lastValue,
|
|
141
|
-
precision,
|
|
142
|
-
enabled: flash,
|
|
143
|
-
identity: alarm.token
|
|
144
|
-
}
|
|
145
|
-
) }),
|
|
146
|
-
narrow ? null : /* @__PURE__ */ jsx("td", { style: { ...cell, color: css2("muted-foreground") }, children: formatDateTime(alarm.raisedTime, timeZone) }),
|
|
147
|
-
canAct ? /* @__PURE__ */ jsx("td", { style: { ...cell, textAlign: "right" }, children: /* @__PURE__ */ jsx(AlarmRowActions, { alarm, actions }) }) : null
|
|
148
|
-
] }, alarm.token);
|
|
149
|
-
}) })
|
|
150
|
-
]
|
|
151
|
-
}
|
|
152
|
-
) }),
|
|
153
|
-
total > alarms.length ? /* @__PURE__ */ jsxs(
|
|
154
|
-
"div",
|
|
155
|
-
{
|
|
156
|
-
style: {
|
|
157
|
-
padding: "4px 10px",
|
|
158
|
-
fontSize: fontVar("xs"),
|
|
159
|
-
color: css2("muted-foreground"),
|
|
160
|
-
borderTop: `1px solid ${css2("border")}`
|
|
161
|
-
},
|
|
162
|
-
children: [
|
|
163
|
-
"Showing ",
|
|
164
|
-
alarms.length,
|
|
165
|
-
" of ",
|
|
166
|
-
total
|
|
167
|
-
]
|
|
168
|
-
}
|
|
169
|
-
) : null
|
|
170
|
-
] }) });
|
|
171
|
-
}
|
|
172
|
-
function OriginatorCell({
|
|
173
|
-
alarm,
|
|
174
|
-
drillTo
|
|
175
|
-
}) {
|
|
176
|
-
if (drillTo && alarm.originatorType === "device" && alarm.originatorToken) {
|
|
177
|
-
const token = alarm.originatorToken;
|
|
178
|
-
return /* @__PURE__ */ jsx(
|
|
179
|
-
"button",
|
|
180
|
-
{
|
|
181
|
-
type: "button",
|
|
182
|
-
onClick: () => drillTo.select({ slot: drillTo.slot, binding: { kind: "device", deviceToken: token } }),
|
|
183
|
-
title: `Show ${token} in the linked widgets`,
|
|
184
|
-
style: {
|
|
185
|
-
padding: 0,
|
|
186
|
-
border: "none",
|
|
187
|
-
background: "none",
|
|
188
|
-
font: "inherit",
|
|
189
|
-
color: css2("primary"),
|
|
190
|
-
cursor: "pointer",
|
|
191
|
-
textDecoration: "underline",
|
|
192
|
-
textUnderlineOffset: 2
|
|
193
|
-
},
|
|
194
|
-
children: hyphenBreaks(token)
|
|
195
|
-
}
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
return /* @__PURE__ */ jsx(Fragment2, { children: hyphenBreaks(alarm.originatorToken ?? alarm.originatorType) });
|
|
199
|
-
}
|
|
200
|
-
var actionButton = {
|
|
201
|
-
padding: "2px 8px",
|
|
202
|
-
fontSize: fontVar("sm"),
|
|
203
|
-
lineHeight: 1.4,
|
|
204
|
-
borderRadius: 4,
|
|
205
|
-
border: `1px solid ${css2("border")}`,
|
|
206
|
-
background: css2("card"),
|
|
207
|
-
color: css2("foreground"),
|
|
208
|
-
cursor: "pointer"
|
|
209
|
-
};
|
|
210
|
-
function AlarmRowActions({ alarm, actions }) {
|
|
211
|
-
const severity = useSeverityColors();
|
|
212
|
-
const [pending, setPending] = useState(null);
|
|
213
|
-
const [failed, setFailed] = useState(false);
|
|
214
|
-
if (alarm.state !== "ACTIVE") {
|
|
215
|
-
return /* @__PURE__ */ jsx("span", { style: { color: css2("muted-foreground") }, children: "\u2014" });
|
|
216
|
-
}
|
|
217
|
-
const run = (kind, op) => {
|
|
218
|
-
setPending(kind);
|
|
219
|
-
setFailed(false);
|
|
220
|
-
let promise;
|
|
221
|
-
try {
|
|
222
|
-
promise = op();
|
|
223
|
-
} catch {
|
|
224
|
-
setFailed(true);
|
|
225
|
-
setPending(null);
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
promise.catch(() => setFailed(true)).finally(() => setPending(null));
|
|
229
|
-
};
|
|
230
|
-
return /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", gap: 6, justifyContent: "flex-end", alignItems: "center" }, children: [
|
|
231
|
-
failed ? /* @__PURE__ */ jsx(
|
|
232
|
-
"span",
|
|
233
|
-
{
|
|
234
|
-
role: "img",
|
|
235
|
-
"aria-label": "Action failed \u2014 retry",
|
|
236
|
-
title: "Action failed \u2014 retry",
|
|
237
|
-
style: { color: severityColor(severity, "CRITICAL"), fontSize: fontVar("sm") },
|
|
238
|
-
children: "\u26A0"
|
|
239
|
-
}
|
|
240
|
-
) : null,
|
|
241
|
-
!alarm.acknowledged ? /* @__PURE__ */ jsx(
|
|
242
|
-
"button",
|
|
243
|
-
{
|
|
244
|
-
type: "button",
|
|
245
|
-
style: { ...actionButton, opacity: pending ? 0.6 : 1 },
|
|
246
|
-
disabled: pending !== null,
|
|
247
|
-
"aria-busy": pending === "ack",
|
|
248
|
-
"aria-label": `Acknowledge alarm ${alarm.alarmKey}`,
|
|
249
|
-
onClick: () => run("ack", () => actions.acknowledgeAlarm(alarm.token)),
|
|
250
|
-
children: pending === "ack" ? "\u2026" : "Ack"
|
|
251
|
-
}
|
|
252
|
-
) : null,
|
|
253
|
-
/* @__PURE__ */ jsx(
|
|
254
|
-
"button",
|
|
255
|
-
{
|
|
256
|
-
type: "button",
|
|
257
|
-
style: { ...actionButton, opacity: pending ? 0.6 : 1 },
|
|
258
|
-
disabled: pending !== null,
|
|
259
|
-
"aria-busy": pending === "clear",
|
|
260
|
-
"aria-label": `Clear alarm ${alarm.alarmKey}`,
|
|
261
|
-
onClick: () => run("clear", () => actions.clearAlarm(alarm.token)),
|
|
262
|
-
children: pending === "clear" ? "\u2026" : "Clear"
|
|
263
|
-
}
|
|
264
|
-
)
|
|
265
|
-
] });
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// src/widgets/alarm-count.tsx
|
|
269
|
-
import {
|
|
270
|
-
css as css3,
|
|
271
|
-
fitValueFont,
|
|
272
|
-
fitValueFontCss,
|
|
273
|
-
FIT_MIN_FONT_PX,
|
|
274
|
-
fontVar as fontVar2,
|
|
275
|
-
optString as optString2,
|
|
276
|
-
spaceVar,
|
|
277
|
-
useSeverityColors as useSeverityColors2,
|
|
278
|
-
useStickyChars,
|
|
279
|
-
useWidgetScale,
|
|
280
|
-
WidgetFrame as WidgetFrame2
|
|
281
|
-
} from "@devicechain/widget-sdk";
|
|
282
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
283
|
-
var ALARM_COUNT_CAPTION_ROOM_PX = 20;
|
|
284
|
-
function AlarmCountBody({ data }) {
|
|
285
|
-
const { size, scale } = useWidgetScale();
|
|
286
|
-
const severity = useSeverityColors2();
|
|
287
|
-
const { total, alarms, loading } = data;
|
|
288
|
-
const worst = highestSeverity(alarms.map((a) => a.severity));
|
|
289
|
-
const alerting = total > 0 && worst != null;
|
|
290
|
-
const color = alerting ? severityColor(severity, worst) : css3("muted-foreground");
|
|
291
|
-
const display = loading && total === 0 ? "\u2014" : String(total);
|
|
292
|
-
const chars = useStickyChars(display.length);
|
|
293
|
-
const floor = Math.round(FIT_MIN_FONT_PX * scale);
|
|
294
|
-
const measured = size.width > 0 && size.height > 0;
|
|
295
|
-
const valueFont = measured ? fitValueFont(
|
|
296
|
-
{ width: size.width, height: Math.max(1, size.height - ALARM_COUNT_CAPTION_ROOM_PX * scale) },
|
|
297
|
-
chars,
|
|
298
|
-
{ floor }
|
|
299
|
-
) : (
|
|
300
|
-
// First frame: the same curve in cq units, resolved by the browser against the frame's
|
|
301
|
-
// column (it does not take the caption's room off; the first measure does).
|
|
302
|
-
fitValueFontCss(chars, { floor })
|
|
303
|
-
);
|
|
304
|
-
const unit = total === 1 ? "alarm" : "alarms";
|
|
305
|
-
const ariaLabel = alerting ? `${total} ${unit}, worst severity ${severityLabel(worst)}` : `${total} ${unit}`;
|
|
306
|
-
return /* @__PURE__ */ jsxs2(
|
|
307
|
-
"div",
|
|
308
|
-
{
|
|
309
|
-
style: {
|
|
310
|
-
display: "flex",
|
|
311
|
-
flexDirection: "column",
|
|
312
|
-
justifyContent: "safe center",
|
|
313
|
-
alignItems: "center",
|
|
314
|
-
height: "100%",
|
|
315
|
-
boxSizing: "border-box",
|
|
316
|
-
overflow: "hidden",
|
|
317
|
-
textAlign: "center"
|
|
318
|
-
},
|
|
319
|
-
children: [
|
|
320
|
-
/* @__PURE__ */ jsx2(
|
|
321
|
-
"div",
|
|
322
|
-
{
|
|
323
|
-
"aria-label": ariaLabel,
|
|
324
|
-
style: {
|
|
325
|
-
fontSize: valueFont,
|
|
326
|
-
fontWeight: 700,
|
|
327
|
-
lineHeight: 1,
|
|
328
|
-
whiteSpace: "nowrap",
|
|
329
|
-
fontVariantNumeric: "tabular-nums",
|
|
330
|
-
color
|
|
331
|
-
},
|
|
332
|
-
children: display
|
|
333
|
-
}
|
|
334
|
-
),
|
|
335
|
-
/* @__PURE__ */ jsxs2(
|
|
336
|
-
"div",
|
|
337
|
-
{
|
|
338
|
-
style: {
|
|
339
|
-
fontSize: fontVar2("xs"),
|
|
340
|
-
color: css3("muted-foreground"),
|
|
341
|
-
marginTop: spaceVar(1),
|
|
342
|
-
textTransform: "uppercase",
|
|
343
|
-
letterSpacing: "0.05em"
|
|
344
|
-
},
|
|
345
|
-
children: [
|
|
346
|
-
/* @__PURE__ */ jsx2("span", { children: unit }),
|
|
347
|
-
alerting ? /* @__PURE__ */ jsx2("span", { children: ` \xB7 worst ${severityLabel(worst)}` }) : null
|
|
348
|
-
]
|
|
349
|
-
}
|
|
350
|
-
)
|
|
351
|
-
]
|
|
352
|
-
}
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
function AlarmCount({ widget, data }) {
|
|
356
|
-
return /* @__PURE__ */ jsx2(WidgetFrame2, { title: optString2(widget.options, "title"), children: /* @__PURE__ */ jsx2(AlarmCountBody, { data }) });
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
export {
|
|
360
|
-
severityColor,
|
|
361
|
-
severityRank,
|
|
362
|
-
severityLabel,
|
|
363
|
-
highestSeverity,
|
|
364
|
-
AlarmTable,
|
|
365
|
-
AlarmCount
|
|
366
|
-
};
|
|
367
|
-
//# sourceMappingURL=chunk-6OGI4UFJ.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/widgets/severity.ts", "../../src/widgets/alarm-table.tsx", "../../src/widgets/alarm-count.tsx"],
|
|
4
|
-
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Alarm-severity presentation shared by the alarm widgets (ADR-041 vocabulary:\n// CRITICAL | MAJOR | MINOR | WARNING | INDETERMINATE). Severity is SEMANTIC color --\n// deliberately fixed, not derived from the dashboard accent -- so a critical alarm reads the\n// same red on every tenant's theme.\n//\n// \uD83D\uDD11 ONE PALETTE. The hues are the SDK's severity tokens (`--sev-*`, widget-sdk/severity.ts):\n// the same colour a threshold band uses is the colour an alarm row uses, in light and dark, and\n// a host that re-themes a token re-themes both. This file keeps only the VOCABULARY mapping\n// (the backend's UPPER_CASE severities onto the token levels). INDETERMINATE -- and anything\n// unrecognized -- has no alarm hue: it renders in the muted foreground, the same neutral an\n// absent alarm gets.\n\nimport { css, type AlarmLevel } from '@devicechain/widget-sdk';\n\nconst LEVEL_OF: Record<string, AlarmLevel> = {\n CRITICAL: 'critical',\n MAJOR: 'major',\n MINOR: 'minor',\n WARNING: 'warning',\n};\n\nconst SEVERITY_ORDER: Record<string, number> = {\n CRITICAL: 0,\n MAJOR: 1,\n MINOR: 2,\n WARNING: 3,\n INDETERMINATE: 4,\n};\n\n// severityColor maps a severity to its badge/stripe color. `colors` is useSeverityColors() --\n// the concrete token colours in scope -- so the result follows the host's theme and dark mode.\n// An INDETERMINATE or unrecognized severity (a hand-edited or future one) takes the muted\n// foreground rather than a hue it has not earned.\nexport function severityColor(colors: Readonly<Record<AlarmLevel, string>>, severity: string): string {\n const level = LEVEL_OF[severity];\n return level ? colors[level] : css('muted-foreground');\n}\n\n// severityRank orders severities most-severe-first; unknown severities sort last.\nexport function severityRank(severity: string): number {\n return SEVERITY_ORDER[severity] ?? 99;\n}\n\n// severityLabel renders a severity in Title Case ('CRITICAL' \u2192 'Critical').\nexport function severityLabel(severity: string): string {\n if (!severity) return '';\n return severity.charAt(0).toUpperCase() + severity.slice(1).toLowerCase();\n}\n\n// highestSeverity returns the most severe severity present in a list, or undefined for\n// an empty list \u2014 the accent an alarm-count uses to signal \"how bad is the worst one\".\nexport function highestSeverity(severities: string[]): string | undefined {\n let best: string | undefined;\n for (const s of severities) {\n if (best === undefined || severityRank(s) < severityRank(best)) best = s;\n }\n return best;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// alarm-table \u2014 the raised alarms in the widget's scope (ADR-041), newest first, with\n// a severity stripe per row. When the runtime supplies an action seam AND the viewer\n// holds alarm:write, each active row gets Acknowledge / Clear controls; otherwise the\n// table is read-only. Bound through the hub's alarm channel, so it renders identically\n// from live data or the synthetic preview source.\n\nimport type { AlarmRow, WidgetActions } from '@devicechain/dashboards';\nimport { Fragment, useState, type CSSProperties, type ReactNode } from 'react';\n\nimport {\n type AlarmStreamState,\n css,\n fontVar,\n FlashValue,\n formatDateTime,\n optBoolean,\n optNumber,\n optString,\n useElementSize,\n useSeverityColors,\n useWidgetSelect,\n useWidgetTimeZone,\n WidgetFrame,\n type WidgetProps,\n type WidgetSelect,\n} from '@devicechain/widget-sdk';\n\nimport { severityColor, severityLabel } from './severity';\n\nconst cell: CSSProperties = { padding: '6px 10px', textAlign: 'left', whiteSpace: 'nowrap' };\nconst headCell: CSSProperties = {\n ...cell,\n position: 'sticky',\n top: 0,\n background: css('card'),\n fontSize: fontVar('xs'),\n fontWeight: 600,\n textTransform: 'uppercase',\n letterSpacing: '0.04em',\n color: css('muted-foreground'),\n borderBottom: `1px solid ${css('border')}`,\n};\n\n// statusLabel collapses the 4-state model (state \u00D7 acknowledged) into the one signal an\n// operator reads, matching the console's alarm badges: cleared wins regardless of the\n// acknowledged flag; an active alarm is \"Acknowledged\" once owned, else \"Active\".\n// Below this measured width the table drops its lowest-value columns (Status, Raised) rather\n// than clipping or scrolling sideways: severity, originator, alarm and value are what an\n// operator reads at a glance, and the text columns wrap instead of pushing the value off the\n// right edge (a phone's single column is ~390 px). Unmeasured (first frame, jsdom) keeps every\n// column on one line.\nexport const ALARM_TABLE_NARROW_PX = 480;\n\n// A narrow table's text cells: tighter padding, and a long originator or alarm key wraps\n// across lines rather than widening the table past its box. Identifiers break at their\n// hyphens first (see hyphenBreaks); `break-word` splits inside a word only when a single\n// segment is wider than the cell. `anywhere` is deliberately NOT used: it also lowers the\n// column's min-content width, so a squeezed column was split mid-word (\"engine-overhea|t\")\n// instead of at a hyphen.\nconst narrowCell: CSSProperties = {\n ...cell,\n padding: '6px 6px',\n whiteSpace: 'normal',\n overflowWrap: 'break-word',\n wordBreak: 'normal',\n};\n\n// hyphenBreaks renders an identifier with a break opportunity after each hyphen, so a\n// hyphenated key (\"tyre-pressure-low\") wraps as \"tyre-/pressure-/low\". The <wbr> also keeps\n// the column's min-content width at the longest segment, which is what lets the table\n// shrink at 390 px without pushing the Value column off the edge.\nexport function hyphenBreaks(text: string): ReactNode {\n // Split after each hyphen, keeping it on the left part. Not a lookbehind regex: this is a\n // published package, and a lookbehind literal is a parse error in older WebKit.\n const parts = text.split('-').map((p, i, all) => (i < all.length - 1 ? `${p}-` : p));\n return parts.map((part, i) => (\n <Fragment key={i}>\n {i > 0 ? <wbr /> : null}\n {part}\n </Fragment>\n ));\n}\n\nfunction statusLabel(alarm: AlarmRow): string {\n if (alarm.state === 'CLEARED') return 'Cleared';\n return alarm.acknowledged ? 'Acknowledged' : 'Active';\n}\n\nexport function AlarmTable({ widget, data, actions }: WidgetProps<AlarmStreamState>) {\n const { alarms, total, loading } = data;\n // Actions render only when the runtime supplies a seam AND the viewer may write.\n const canAct = actions?.can('alarm:write') ?? false;\n const [sizeRef, size] = useElementSize<HTMLDivElement>();\n const severity = useSeverityColors();\n const narrow = size.width > 0 && size.width < ALARM_TABLE_NARROW_PX;\n const columns = 5 + (narrow ? 0 : 2) + (canAct ? 1 : 0);\n const text = narrow ? narrowCell : cell;\n // Round the triggering value to a fixed precision when configured; unset keeps the raw\n // value (formatValue's default), matching the table widget. An alarm's raw lastValue is\n // otherwise a full-width float (e.g. 31.19073834583732).\n const precision = optNumber(widget.options, 'precision');\n // Opt-in directional flash on the triggering value, per row (keyed by alarm token).\n const flash = optBoolean(widget.options, 'flashOnChange');\n // Drill: when a `selectionTarget` slot is configured AND the renderer wired a selection\n // callback (view mode, a host that supports it), a device originator becomes a link that\n // re-points that slot \u2014 driving the per-device widgets to the clicked thermostat\n // (ADR-039 selection amendment). Absent either, the originator is plain text.\n const select = useWidgetSelect();\n const timeZone = useWidgetTimeZone();\n const selectionTarget = optString(widget.options, 'selectionTarget');\n const drillTo = select && selectionTarget ? { select, slot: selectionTarget } : undefined;\n\n return (\n <WidgetFrame title={optString(widget.options, 'title')} flush>\n <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>\n <div ref={sizeRef} style={{ flex: 1, minHeight: 0, overflow: 'auto' }}>\n <table\n style={{\n width: '100%',\n borderCollapse: 'collapse',\n fontSize: fontVar('md'),\n color: css('foreground'),\n fontVariantNumeric: 'tabular-nums',\n }}\n >\n <thead>\n <tr>\n <th style={{ ...headCell, width: 4, padding: 0 }} aria-hidden=\"true\" />\n <th scope=\"col\" style={headCell}>\n Severity\n </th>\n {narrow ? null : (\n <th scope=\"col\" style={headCell}>\n Status\n </th>\n )}\n <th scope=\"col\" style={headCell}>\n Originator\n </th>\n <th scope=\"col\" style={headCell}>\n Alarm\n </th>\n <th scope=\"col\" style={{ ...headCell, textAlign: 'right' }}>\n Value\n </th>\n {narrow ? null : (\n <th scope=\"col\" style={headCell}>\n Raised\n </th>\n )}\n {canAct ? (\n <th scope=\"col\" style={{ ...headCell, textAlign: 'right' }}>\n Actions\n </th>\n ) : null}\n </tr>\n </thead>\n <tbody>\n {alarms.length === 0 ? (\n <tr>\n <td colSpan={columns} style={{ ...cell, textAlign: 'center', color: css('muted-foreground') }}>\n {loading ? 'Loading\u2026' : 'No alarms'}\n </td>\n </tr>\n ) : (\n alarms.map((alarm) => {\n const color = severityColor(severity, alarm.severity);\n return (\n <tr key={alarm.token} style={{ borderBottom: `1px solid ${css('border')}` }}>\n <td aria-hidden=\"true\" style={{ padding: 0, width: 4, background: color }} />\n <td style={cell}>\n <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>\n <span\n aria-hidden=\"true\"\n style={{ width: 8, height: 8, borderRadius: '50%', background: color, flexShrink: 0 }}\n />\n {severityLabel(alarm.severity)}\n </span>\n </td>\n {narrow ? null : <td style={{ ...cell, color: css('muted-foreground') }}>{statusLabel(alarm)}</td>}\n <td style={text}>\n <OriginatorCell alarm={alarm} drillTo={drillTo} />\n </td>\n <td style={text}>\n {hyphenBreaks(alarm.alarmKey)}\n </td>\n <td style={{ ...cell, textAlign: 'right' }}>\n <FlashValue\n value={alarm.lastValue}\n precision={precision}\n enabled={flash}\n identity={alarm.token}\n />\n </td>\n {narrow ? null : (\n <td style={{ ...cell, color: css('muted-foreground') }}>\n {formatDateTime(alarm.raisedTime, timeZone)}\n </td>\n )}\n {canAct ? (\n <td style={{ ...cell, textAlign: 'right' }}>\n {/* actions is defined whenever canAct is true. */}\n <AlarmRowActions alarm={alarm} actions={actions as WidgetActions} />\n </td>\n ) : null}\n </tr>\n );\n })\n )}\n </tbody>\n </table>\n </div>\n {total > alarms.length ? (\n <div\n style={{\n padding: '4px 10px',\n fontSize: fontVar('xs'),\n color: css('muted-foreground'),\n borderTop: `1px solid ${css('border')}`,\n }}\n >\n Showing {alarms.length} of {total}\n </div>\n ) : null}\n </div>\n </WidgetFrame>\n );\n}\n\n// OriginatorCell renders the alarm's originator. When drill is wired AND the originator\n// is a device (it carries a token), it is a link that re-points the target slot to that\n// device; otherwise it is plain text (the token, or the originator type when tokenless).\n// The drill target is a device binding \u2014 a building-scoped alarm table's originators are\n// already members of the context, so the drill stays in-context.\nfunction OriginatorCell({\n alarm,\n drillTo,\n}: {\n alarm: AlarmRow;\n drillTo: { select: WidgetSelect; slot: string } | undefined;\n}) {\n if (drillTo && alarm.originatorType === 'device' && alarm.originatorToken) {\n const token = alarm.originatorToken;\n return (\n <button\n type=\"button\"\n onClick={() => drillTo.select({ slot: drillTo.slot, binding: { kind: 'device', deviceToken: token } })}\n title={`Show ${token} in the linked widgets`}\n style={{\n padding: 0,\n border: 'none',\n background: 'none',\n font: 'inherit',\n color: css('primary'),\n cursor: 'pointer',\n textDecoration: 'underline',\n textUnderlineOffset: 2,\n }}\n >\n {hyphenBreaks(token)}\n </button>\n );\n }\n return <>{hyphenBreaks(alarm.originatorToken ?? alarm.originatorType)}</>;\n}\n\nconst actionButton: CSSProperties = {\n padding: '2px 8px',\n fontSize: fontVar('sm'),\n lineHeight: 1.4,\n borderRadius: 4,\n border: `1px solid ${css('border')}`,\n background: css('card'),\n color: css('foreground'),\n cursor: 'pointer',\n};\n\n// AlarmRowActions owns the pending/error state for one row's Acknowledge / Clear. Only\n// an ACTIVE alarm is actionable (a cleared \u2014 or any future non-active \u2014 state shows\n// \"\u2014\"); an acknowledged one still shows Clear. After a successful mutation the hub\n// reconciles the table, so the row updates itself.\nfunction AlarmRowActions({ alarm, actions }: { alarm: AlarmRow; actions: WidgetActions }) {\n const severity = useSeverityColors();\n const [pending, setPending] = useState<'ack' | 'clear' | null>(null);\n const [failed, setFailed] = useState(false);\n\n if (alarm.state !== 'ACTIVE') {\n return <span style={{ color: css('muted-foreground') }}>\u2014</span>;\n }\n\n const run = (kind: 'ack' | 'clear', op: () => Promise<void>) => {\n setPending(kind);\n setFailed(false);\n // Call op() synchronously (so an immediate dispatch is observable) but guard a\n // synchronous throw too, so a misbehaving seam can't leave the row stuck-disabled.\n let promise: Promise<void>;\n try {\n promise = op();\n } catch {\n setFailed(true);\n setPending(null);\n return;\n }\n promise.catch(() => setFailed(true)).finally(() => setPending(null));\n };\n\n return (\n <span style={{ display: 'inline-flex', gap: 6, justifyContent: 'flex-end', alignItems: 'center' }}>\n {failed ? (\n <span\n role=\"img\"\n aria-label=\"Action failed \u2014 retry\"\n title=\"Action failed \u2014 retry\"\n style={{ color: severityColor(severity, 'CRITICAL'), fontSize: fontVar('sm') }}\n >\n \u26A0\n </span>\n ) : null}\n {!alarm.acknowledged ? (\n <button\n type=\"button\"\n style={{ ...actionButton, opacity: pending ? 0.6 : 1 }}\n disabled={pending !== null}\n aria-busy={pending === 'ack'}\n aria-label={`Acknowledge alarm ${alarm.alarmKey}`}\n onClick={() => run('ack', () => actions.acknowledgeAlarm(alarm.token))}\n >\n {pending === 'ack' ? '\u2026' : 'Ack'}\n </button>\n ) : null}\n <button\n type=\"button\"\n style={{ ...actionButton, opacity: pending ? 0.6 : 1 }}\n disabled={pending !== null}\n aria-busy={pending === 'clear'}\n aria-label={`Clear alarm ${alarm.alarmKey}`}\n onClick={() => run('clear', () => actions.clearAlarm(alarm.token))}\n >\n {pending === 'clear' ? '\u2026' : 'Clear'}\n </button>\n </span>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// alarm-count \u2014 a KPI card of how many alarms match the widget's scope+filter\n// (ADR-041). The number is the server total (past the loaded page); its color is the\n// most severe among the CURRENTLY-LOADED alarms (the newest page), and the worst\n// severity is also spelled out as text/aria so the signal never reads on hue alone (a\n// color-blind or screen-reader viewer still gets it). At zero it stays muted.\n\nimport {\n type AlarmStreamState,\n css,\n fitValueFont,\n fitValueFontCss,\n FIT_MIN_FONT_PX,\n fontVar,\n optString,\n spaceVar,\n useSeverityColors,\n useStickyChars,\n useWidgetScale,\n WidgetFrame,\n type WidgetProps,\n} from '@devicechain/widget-sdk';\n\nimport { highestSeverity, severityColor, severityLabel } from './severity';\n\n// The caption under the count (11 px text plus its 6 px margin). It always shows -- it names\n// the worst severity -- so the count is fitted to what is left rather than pushing it out.\nexport const ALARM_COUNT_CAPTION_ROOM_PX = 20;\n\n// Rendered INSIDE the frame: the widget scale is the frame's content column, which is only in\n// context below the frame.\nfunction AlarmCountBody({ data }: { data: AlarmStreamState }) {\n const { size, scale } = useWidgetScale();\n const severity = useSeverityColors();\n const { total, alarms, loading } = data;\n\n const worst = highestSeverity(alarms.map((a) => a.severity));\n // At zero (or before any alarm is loaded) stay neutral; otherwise take the worst\n // loaded severity's color as the alert accent.\n const alerting = total > 0 && worst != null;\n const color = alerting ? severityColor(severity, worst) : css('muted-foreground');\n\n const display = loading && total === 0 ? '\u2014' : String(total);\n const chars = useStickyChars(display.length);\n const floor = Math.round(FIT_MIN_FONT_PX * scale);\n const measured = size.width > 0 && size.height > 0;\n const valueFont = measured\n ? fitValueFont(\n { width: size.width, height: Math.max(1, size.height - ALARM_COUNT_CAPTION_ROOM_PX * scale) },\n chars,\n { floor },\n )\n : // First frame: the same curve in cq units, resolved by the browser against the frame's\n // column (it does not take the caption's room off; the first measure does).\n fitValueFontCss(chars, { floor });\n const unit = total === 1 ? 'alarm' : 'alarms';\n const ariaLabel = alerting ? `${total} ${unit}, worst severity ${severityLabel(worst)}` : `${total} ${unit}`;\n\n return (\n <div\n style={{\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'safe center',\n alignItems: 'center',\n height: '100%',\n boxSizing: 'border-box',\n overflow: 'hidden',\n textAlign: 'center',\n }}\n >\n <div\n aria-label={ariaLabel}\n style={{\n fontSize: valueFont,\n fontWeight: 700,\n lineHeight: 1,\n whiteSpace: 'nowrap',\n fontVariantNumeric: 'tabular-nums',\n color,\n }}\n >\n {display}\n </div>\n <div\n style={{\n fontSize: fontVar('xs'),\n color: css('muted-foreground'),\n marginTop: spaceVar(1),\n textTransform: 'uppercase',\n letterSpacing: '0.05em',\n }}\n >\n <span>{unit}</span>\n {alerting ? <span>{` \u00B7 worst ${severityLabel(worst)}`}</span> : null}\n </div>\n </div>\n );\n}\n\nexport function AlarmCount({ widget, data }: WidgetProps<AlarmStreamState>) {\n return (\n <WidgetFrame title={optString(widget.options, 'title')}>\n <AlarmCountBody data={data} />\n </WidgetFrame>\n );\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["css", "Fragment", "css", "css", "fontVar", "optString", "useSeverityColors", "WidgetFrame", "jsx", "jsxs", "useSeverityColors", "css", "fontVar", "WidgetFrame", "optString"]
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/fleet-model.ts", "../../src/fleet-strings.ts"],
|
|
4
|
-
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The fleet widgets' state, decided apart from the drawing and from the hub: which reading a cell\n// shows, why one is empty, how the rows are ordered and paged. Pure, so each rule is a test.\n//\n// Everything is keyed by DEVICE and measurement together. The measurement channel keys its latest\n// values by name alone, so two thermostats reporting \"temperature\" overwrite each other there;\n// nothing in this file ever looks a value up by name without the device beside it.\n\nimport {\n bucketSecondsFor,\n type FleetMember,\n type FleetState,\n type HistoryWindow,\n type MeasurementSample,\n type MemberRoster,\n type MemberSeed,\n} from '@devicechain/dashboards';\n\nconst MINUTE_MS = 60_000;\n\n// The window a fleet's seed reads: whole minutes ending at the last minute boundary at or before\n// `at`, in buckets of bucketSecondsFor(span) (a multiple of 60, so the store answers from its\n// one-minute summaries). Whole minutes are what let widgets reading the same span on the same\n// board share one request per device (the hub keys in-flight reads by window), and what keep the\n// window from holding a reading from before the span. The end is the instant just before the\n// boundary, because the store's end is inclusive.\nexport function seedWindow(spanSeconds: number, at: number): HistoryWindow {\n const end = Math.floor(at / MINUTE_MS) * MINUTE_MS;\n const start = Math.ceil((end - spanSeconds * 1000) / MINUTE_MS) * MINUTE_MS;\n return {\n startTime: new Date(start).toISOString(),\n endTime: new Date(end - 1).toISOString(),\n intervalSeconds: bucketSecondsFor(Math.max(60, (end - start) / 1000)),\n };\n}\n\n// ---- Building the state ------------------------------------------------------\n\n// What the feed has gathered. `seeds` holds a settled seed per device (`'failed'` for a read that\n// did not come back); a device absent from it is still loading. `live` is every device's newest\n// sample per measurement name since the board opened.\nexport interface FleetInputs {\n roster: MemberRoster | null;\n seeds: ReadonlyMap<string, MemberSeed | 'failed'>;\n live: ReadonlyMap<string, ReadonlyMap<string, MeasurementSample>>;\n // The start of the span (epoch ms): a reading older than this is not \"in range\".\n since: number;\n forbidden: boolean;\n}\n\nfunction timeOf(sample: MeasurementSample | undefined): number {\n if (!sample?.occurredTime) return Number.NaN;\n return Date.parse(sample.occurredTime);\n}\n\n// A sample with no readable time is taken as current: only a live sample can lack one, and it just\n// arrived.\nfunction inRange(sample: MeasurementSample, since: number): boolean {\n const at = timeOf(sample);\n return Number.isNaN(at) || at >= since;\n}\n\nfunction newer(a: MeasurementSample | undefined, b: MeasurementSample | undefined): MeasurementSample | undefined {\n if (!a) return b;\n if (!b) return a;\n const ta = timeOf(a);\n const tb = timeOf(b);\n if (Number.isNaN(ta)) return a;\n if (Number.isNaN(tb)) return b;\n return tb >= ta ? b : a;\n}\n\nexport function buildFleet(i: FleetInputs): FleetState {\n if (i.forbidden) return { status: 'forbidden', members: [], total: 0, truncated: false };\n if (!i.roster) return { status: 'loading', members: [], total: 0, truncated: false };\n if (i.roster.unbound) return { status: 'unbound', members: [], total: 0, truncated: false };\n\n // A seeded reading is stamped at the start of its minute (fleet-seed.ts, \"Ages\") and happened\n // somewhere in that minute, so it is in range while its minute is. Held to the exact instant, a\n // reading 59 minutes 45 seconds old on an hour's board was dropped as out of range.\n const seedSince = Math.floor(i.since / MINUTE_MS) * MINUTE_MS;\n const members = i.roster.members.map((ref): FleetMember => {\n const seed = i.seeds.get(ref.token);\n const seeded = seed && seed !== 'failed' ? seed : undefined;\n const live = i.live.get(ref.token);\n const latest: Record<string, MeasurementSample> = {};\n const names = new Set<string>([...Object.keys(seeded?.latest ?? {}), ...(live?.keys() ?? [])]);\n for (const name of names) {\n const best = newer(seeded?.latest[name], live?.get(name));\n if (best && inRange(best, best === seeded?.latest[name] ? seedSince : i.since)) latest[name] = best;\n }\n const observed = new Set<string>(seeded?.observed ?? []);\n for (const name of Object.keys(latest)) observed.add(name);\n return {\n token: ref.token,\n name: ref.name,\n latest,\n seed: seed === undefined ? 'pending' : seed === 'failed' ? 'failed' : 'ready',\n observed: [...observed],\n // A live reading of an unread name is its value: cellFor looks at `latest` first.\n ...(seeded?.unread?.length ? { unread: seeded.unread } : {}),\n };\n });\n return {\n status: 'ready',\n members,\n total: i.roster.total,\n truncated: i.roster.total > members.length,\n };\n}\n\n// The newest reading anywhere in the fleet (epoch ms), or undefined when none carries a time.\nexport function newestMemberTime(fleet: FleetState): number | undefined {\n let newest: number | undefined;\n for (const m of fleet.members) {\n for (const sample of Object.values(m.latest)) {\n const at = timeOf(sample);\n if (!Number.isNaN(at) && (newest === undefined || at > newest)) newest = at;\n }\n }\n return newest;\n}\n\n// ---- Cells -------------------------------------------------------------------\n\n// What one (device, measurement) cell holds, and when it holds nothing, why.\nexport type Cell =\n | { kind: 'value'; sample: MeasurementSample; value: number }\n | { kind: 'loading' }\n // The device's history read failed and no live reading has arrived since.\n | { kind: 'failed' }\n // The device reported other measurements in the span, but never this one.\n | { kind: 'not-reported' }\n // The device reported this in the span, but its newest reading was not loaded (MemberSeed.unread).\n | { kind: 'unread' }\n // The device reported nothing at all in the span.\n | { kind: 'no-data' };\n\nexport function cellFor(member: FleetMember, measurement: string): Cell {\n const sample = member.latest[measurement];\n if (sample && typeof sample.value === 'number' && Number.isFinite(sample.value)) {\n return { kind: 'value', sample, value: sample.value };\n }\n if (member.seed === 'pending') return { kind: 'loading' };\n if (member.seed === 'failed') return { kind: 'failed' };\n if (member.unread?.includes(measurement)) return { kind: 'unread' };\n return member.observed.length > 0 && !member.observed.includes(measurement) ? { kind: 'not-reported' } : { kind: 'no-data' };\n}\n\n// The columns: the measurements the datasource names, in that order; with none named, every\n// measurement any member reported, alphabetically (stable across renders however samples arrive).\nexport function columnNames(named: readonly string[], members: readonly FleetMember[]): string[] {\n if (named.length > 0) return [...new Set(named)];\n const seen = new Set<string>();\n for (const m of members) for (const name of m.observed) seen.add(name);\n return [...seen].sort((a, b) => a.localeCompare(b));\n}\n\n// ---- Order and pages ------------------------------------------------------------\n\nexport interface SortKey {\n // A measurement name, or null for the device-name column.\n by: string | null;\n descending: boolean;\n}\n\nexport function deviceLabel(member: Pick<FleetMember, 'name' | 'token'>): string {\n return member.name && member.name !== '' ? member.name : member.token;\n}\n\nconst collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });\n\n// Ordered by the key, ties broken by device name then token, so the order never depends on the\n// order samples arrived in. A device with no value for the sorted measurement goes LAST in either\n// direction: \"largest first\" must not open with the devices that reported nothing.\nexport function sortMembers(members: readonly FleetMember[], key: SortKey): FleetMember[] {\n const byDevice = (a: FleetMember, b: FleetMember): number =>\n collator.compare(deviceLabel(a), deviceLabel(b)) || (a.token < b.token ? -1 : a.token > b.token ? 1 : 0);\n const sign = key.descending ? -1 : 1;\n return [...members].sort((a, b) => {\n if (key.by === null) return sign * byDevice(a, b);\n const ca = cellFor(a, key.by);\n const cb = cellFor(b, key.by);\n if (ca.kind !== 'value' || cb.kind !== 'value') {\n if (ca.kind === cb.kind) return byDevice(a, b);\n return ca.kind === 'value' ? -1 : cb.kind === 'value' ? 1 : byDevice(a, b);\n }\n return sign * (ca.value - cb.value) || byDevice(a, b);\n });\n}\n\nexport const DEFAULT_PAGE_SIZE = 20;\n\nexport function pageCount(rows: number, pageSize: number): number {\n return Math.max(1, Math.ceil(rows / Math.max(1, pageSize)));\n}\n\n// The page asked for, held inside what exists (a fleet that shrinks must not leave the viewer on a\n// page past the end).\nexport function clampPage(page: number, rows: number, pageSize: number): number {\n return Math.min(Math.max(0, page), pageCount(rows, pageSize) - 1);\n}\n\n// ---- Bars ---------------------------------------------------------------------------\n\n// One bar per member for one measurement, in the shape the bar chart already draws (category\n// items), ordered like the categorical charts order theirs. A device with no number keeps its\n// label with no bar and sorts after those that have one.\nexport interface DeviceBar {\n label: string;\n token: string;\n value: number | null;\n}\n\nexport function deviceBars(members: readonly FleetMember[], measurement: string, sort: 'none' | 'ascending' | 'descending'): DeviceBar[] {\n const bars = members.map((m): DeviceBar => {\n const cell = cellFor(m, measurement);\n return { label: deviceLabel(m), token: m.token, value: cell.kind === 'value' ? cell.value : null };\n });\n if (sort === 'none') return bars;\n const sign = sort === 'ascending' ? 1 : -1;\n return bars\n .map((bar, i) => ({ bar, i }))\n .sort((a, b) => {\n if (a.bar.value === null || b.bar.value === null) {\n return a.bar.value === b.bar.value ? a.i - b.i : a.bar.value === null ? 1 : -1;\n }\n return sign * (a.bar.value - b.bar.value) || a.i - b.i;\n })\n .map((x) => x.bar);\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The fixed words the fleet table and the per-device bar chart render themselves: the device\n// column, the reason a cell holds no number, the truncation and paging captions. The widget\n// packages carry no i18n runtime, so this is the one small per-language table (modelled on\n// aggregate-strings.ts); option labels are translated in the console's catalogs, not here.\n\nimport { viewerLocale } from '@devicechain/widget-sdk';\nimport { ACCESS_STRINGS } from './access-strings';\n\nexport interface FleetStrings {\n device: string;\n loading: string;\n // The reason a cell holds no number (D-5): each is a different fact about the device.\n notReported: string;\n // It reported this, but its newest reading was not loaded: there IS data.\n unread: string;\n noData: string;\n failed: string;\n // Whole-widget states.\n unbound: string;\n forbidden: string;\n unsupported: string;\n noMembers: string;\n noMeasurement: string;\n // A widget that shows one device, bound to a group: refused, with where the group can be shown.\n oneDevice: string;\n useFleetTable: string;\n usePerDevice: string;\n stale: string;\n // \"Showing 500 of 812 devices\": a fleet past what is read must say so.\n truncated: (shown: number, total: number) => string;\n range: (from: number, to: number, of: number) => string;\n previous: string;\n next: string;\n sortBy: (column: string) => string;\n drill: (device: string) => string;\n}\n\nexport const FLEET_STRINGS: Record<'en' | 'es', FleetStrings> = {\n en: {\n device: 'Device',\n loading: 'Loading',\n notReported: 'does not report this',\n unread: 'not loaded',\n noData: 'no data in range',\n failed: 'could not be read',\n unbound: 'Choose a device group for this widget',\n forbidden: ACCESS_STRINGS.en.forbidden,\n unsupported: 'This data source cannot list a fleet',\n noMembers: 'No devices in this group',\n noMeasurement: 'Choose the measurement to compare',\n oneDevice: 'This widget shows one device, and it is bound to a group of devices whose readings it would mix together.',\n useFleetTable: 'Bind it to one device, or show the group in a fleet table.',\n usePerDevice: 'Set it to one bar per device to compare them, or bind it to one device.',\n stale: 'stale',\n truncated: (shown, total) => `Showing ${shown} of ${total} devices`,\n range: (from, to, of) => `${from}\u2013${to} of ${of}`,\n previous: 'Previous page',\n next: 'Next page',\n sortBy: (column) => `Sort by ${column}`,\n drill: (device) => `Show ${device} in the linked widgets`,\n },\n es: {\n device: 'Dispositivo',\n loading: 'Cargando',\n notReported: 'no informa esta medici\u00F3n',\n unread: 'no cargado',\n noData: 'sin datos en el rango',\n failed: 'no se pudo leer',\n unbound: 'Elija un grupo de dispositivos para este widget',\n forbidden: ACCESS_STRINGS.es.forbidden,\n unsupported: 'Esta fuente de datos no puede listar una flota',\n noMembers: 'No hay dispositivos en este grupo',\n noMeasurement: 'Elija la medici\u00F3n que comparar',\n oneDevice: 'Este widget muestra un solo dispositivo y est\u00E1 vinculado a un grupo de dispositivos cuyas lecturas mezclar\u00EDa.',\n useFleetTable: 'Vinc\u00FAlelo a un dispositivo o muestre el grupo en una tabla de flota.',\n usePerDevice: 'Config\u00FArelo con una barra por dispositivo para compararlos, o vinc\u00FAlelo a un dispositivo.',\n stale: 'obsoleto',\n truncated: (shown, total) => `Mostrando ${shown} de ${total} dispositivos`,\n range: (from, to, of) => `${from}\u2013${to} de ${of}`,\n previous: 'P\u00E1gina anterior',\n next: 'P\u00E1gina siguiente',\n sortBy: (column) => `Ordenar por ${column}`,\n drill: (device) => `Mostrar ${device} en los widgets enlazados`,\n },\n};\n\nexport function fleetStrings(locale: string | undefined = viewerLocale()): FleetStrings {\n return locale?.toLowerCase().split('-')[0] === 'es' ? FLEET_STRINGS.es : FLEET_STRINGS.en;\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|