@devicechain/widgets 0.23.0-rc.1 → 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
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useHeld
|
|
3
|
+
} from "./chunk-XNEBAY4J.js";
|
|
4
|
+
import {
|
|
5
|
+
arcPath
|
|
6
|
+
} from "./chunk-YYJV54BV.js";
|
|
7
|
+
import {
|
|
8
|
+
fleetStrings
|
|
9
|
+
} from "./chunk-QMGKB6J3.js";
|
|
10
|
+
import "./chunk-BRH66T5F.js";
|
|
11
|
+
import {
|
|
12
|
+
LevelEmpty,
|
|
13
|
+
LevelReadout
|
|
14
|
+
} from "./chunk-XQQQNTBX.js";
|
|
15
|
+
|
|
16
|
+
// src/widgets/device-status.tsx
|
|
17
|
+
import {
|
|
18
|
+
css,
|
|
19
|
+
FIT_MIN_FONT_PX,
|
|
20
|
+
fontVar,
|
|
21
|
+
levelColor,
|
|
22
|
+
optString,
|
|
23
|
+
spaceVar,
|
|
24
|
+
useSeverityColors,
|
|
25
|
+
viewerLocale as viewerLocale2,
|
|
26
|
+
WidgetFrame
|
|
27
|
+
} from "@devicechain/widget-sdk";
|
|
28
|
+
|
|
29
|
+
// src/presence-model.ts
|
|
30
|
+
function presenceKind(presence) {
|
|
31
|
+
if (!presence) return "pending";
|
|
32
|
+
if (presence.status === "ready") return presence.active ? "online" : "offline";
|
|
33
|
+
return presence.status;
|
|
34
|
+
}
|
|
35
|
+
function lastActivityMs(presence) {
|
|
36
|
+
if (presence?.status !== "ready" || !presence.lastActivityTime) return void 0;
|
|
37
|
+
const at = Date.parse(presence.lastActivityTime);
|
|
38
|
+
return Number.isNaN(at) ? void 0 : at;
|
|
39
|
+
}
|
|
40
|
+
function tallyPresence(members) {
|
|
41
|
+
const tally = { online: 0, offline: 0, unknown: 0, pending: 0, failed: 0, newest: void 0 };
|
|
42
|
+
for (const m of members) {
|
|
43
|
+
tally[presenceKind(m.presence)]++;
|
|
44
|
+
const at = lastActivityMs(m.presence);
|
|
45
|
+
if (at !== void 0 && (tally.newest === void 0 || at > tally.newest)) tally.newest = at;
|
|
46
|
+
}
|
|
47
|
+
return tally;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/presence-strings.ts
|
|
51
|
+
import { viewerLocale } from "@devicechain/widget-sdk";
|
|
52
|
+
var PRESENCE_STRINGS = {
|
|
53
|
+
en: {
|
|
54
|
+
online: "Online",
|
|
55
|
+
offline: "Offline",
|
|
56
|
+
unknown: "No status yet",
|
|
57
|
+
neverSeen: "Never seen",
|
|
58
|
+
lastSeen: (when) => `Last seen ${when}`,
|
|
59
|
+
failed: "Status could not be read",
|
|
60
|
+
unsupported: "This data source cannot read device status",
|
|
61
|
+
unbound: "Choose a device for this widget",
|
|
62
|
+
oneDevice: "This card shows one device, and it is bound to a group.",
|
|
63
|
+
useDoughnut: "Bind it to one device, or show the group in an online / offline doughnut.",
|
|
64
|
+
ofTotal: (total) => `of ${total}`,
|
|
65
|
+
onlineCaption: "online",
|
|
66
|
+
notRead: (n) => `${n} could not be read`,
|
|
67
|
+
noStatusYet: (n) => `${n} with no status yet`,
|
|
68
|
+
chartLabel: (online, offline, unknown) => `${online} online, ${offline} offline, ${unknown} with no status yet`
|
|
69
|
+
},
|
|
70
|
+
es: {
|
|
71
|
+
online: "En l\xEDnea",
|
|
72
|
+
offline: "Sin conexi\xF3n",
|
|
73
|
+
unknown: "Sin estado todav\xEDa",
|
|
74
|
+
neverSeen: "Nunca visto",
|
|
75
|
+
lastSeen: (when) => `Visto por \xFAltima vez ${when}`,
|
|
76
|
+
failed: "No se pudo leer el estado",
|
|
77
|
+
unsupported: "Esta fuente de datos no puede leer el estado de los dispositivos",
|
|
78
|
+
unbound: "Elija un dispositivo para este widget",
|
|
79
|
+
oneDevice: "Esta tarjeta muestra un solo dispositivo y est\xE1 vinculada a un grupo.",
|
|
80
|
+
useDoughnut: "Vinc\xFAlela a un dispositivo o muestre el grupo en un anillo de conectados / desconectados.",
|
|
81
|
+
ofTotal: (total) => `de ${total}`,
|
|
82
|
+
onlineCaption: "en l\xEDnea",
|
|
83
|
+
notRead: (n) => `${n} sin leer`,
|
|
84
|
+
noStatusYet: (n) => `${n} sin estado todav\xEDa`,
|
|
85
|
+
chartLabel: (online, offline, unknown) => `${online} en l\xEDnea, ${offline} sin conexi\xF3n, ${unknown} sin estado todav\xEDa`
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
function presenceStrings(locale = viewerLocale()) {
|
|
89
|
+
return locale?.toLowerCase().split("-")[0] === "es" ? PRESENCE_STRINGS.es : PRESENCE_STRINGS.en;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/widgets/device-status.tsx
|
|
93
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
94
|
+
var OFFLINE_LEVEL = "major";
|
|
95
|
+
var CAPTION_ROOM_PX = 22;
|
|
96
|
+
function formatLastSeen(at, locale = viewerLocale2()) {
|
|
97
|
+
try {
|
|
98
|
+
return new Intl.DateTimeFormat(locale, { dateStyle: "medium", timeStyle: "short" }).format(at);
|
|
99
|
+
} catch {
|
|
100
|
+
return new Intl.DateTimeFormat(void 0, { dateStyle: "medium", timeStyle: "short" }).format(at);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function DeviceStatus({ widget, data }) {
|
|
104
|
+
const strings = presenceStrings();
|
|
105
|
+
const fleet = fleetStrings();
|
|
106
|
+
const severity = useSeverityColors();
|
|
107
|
+
const feed = data.members;
|
|
108
|
+
const title = optString(widget.options, "title");
|
|
109
|
+
const empty = (message) => /* @__PURE__ */ jsx(WidgetFrame, { title, children: /* @__PURE__ */ jsx(LevelEmpty, { message }) });
|
|
110
|
+
if (!feed || feed.status === "loading") return empty(fleet.loading);
|
|
111
|
+
if (feed.status === "unbound") return empty(strings.unbound);
|
|
112
|
+
if (feed.status === "forbidden") return empty(fleet.forbidden);
|
|
113
|
+
if (feed.status === "unsupported") return empty(strings.unsupported);
|
|
114
|
+
if (feed.members.length === 0) return empty(fleet.noMembers);
|
|
115
|
+
if (feed.members.length > 1 || feed.total > 1) return empty(`${strings.oneDevice} ${strings.useDoughnut}`);
|
|
116
|
+
const presence = feed.members[0].presence;
|
|
117
|
+
const kind = presenceKind(presence);
|
|
118
|
+
if (kind === "pending") return empty(fleet.loading);
|
|
119
|
+
if (kind === "failed") return empty(strings.failed);
|
|
120
|
+
const figure = kind === "online" ? strings.online : kind === "offline" ? strings.offline : strings.unknown;
|
|
121
|
+
const color = kind === "offline" ? levelColor(severity, OFFLINE_LEVEL) : kind === "unknown" ? css("muted-foreground") : void 0;
|
|
122
|
+
const seen = lastActivityMs(presence);
|
|
123
|
+
const caption = seen !== void 0 ? strings.lastSeen(formatLastSeen(seen)) : strings.neverSeen;
|
|
124
|
+
return /* @__PURE__ */ jsx(WidgetFrame, { title, children: ({ size, scale }) => {
|
|
125
|
+
const room = Math.round(CAPTION_ROOM_PX * scale);
|
|
126
|
+
const fit = { width: size.width, height: Math.max(Math.round(FIT_MIN_FONT_PX * scale), size.height - room) };
|
|
127
|
+
return /* @__PURE__ */ jsxs(
|
|
128
|
+
"div",
|
|
129
|
+
{
|
|
130
|
+
"data-device-status": kind,
|
|
131
|
+
style: { display: "flex", flexDirection: "column", justifyContent: "safe center", height: "100%", overflow: "hidden" },
|
|
132
|
+
children: [
|
|
133
|
+
/* @__PURE__ */ jsx("div", { style: { flex: "1 1 0", minHeight: 0, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx(LevelReadout, { text: figure, color, size: fit, scale }) }),
|
|
134
|
+
/* @__PURE__ */ jsx(
|
|
135
|
+
"div",
|
|
136
|
+
{
|
|
137
|
+
"data-device-status-caption": "",
|
|
138
|
+
style: {
|
|
139
|
+
flex: "0 0 auto",
|
|
140
|
+
marginTop: spaceVar(1),
|
|
141
|
+
textAlign: "center",
|
|
142
|
+
fontSize: fontVar("sm"),
|
|
143
|
+
color: css("muted-foreground"),
|
|
144
|
+
overflow: "hidden",
|
|
145
|
+
textOverflow: "ellipsis",
|
|
146
|
+
whiteSpace: "nowrap"
|
|
147
|
+
},
|
|
148
|
+
children: caption
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
} });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/widgets/online-doughnut.tsx
|
|
158
|
+
import {
|
|
159
|
+
css as css2,
|
|
160
|
+
FIT_MIN_FONT_PX as FIT_MIN_FONT_PX2,
|
|
161
|
+
fontVar as fontVar2,
|
|
162
|
+
levelColor as levelColor2,
|
|
163
|
+
optString as optString2,
|
|
164
|
+
spaceVar as spaceVar2,
|
|
165
|
+
useSeverityColors as useSeverityColors2,
|
|
166
|
+
WidgetFrame as WidgetFrame2
|
|
167
|
+
} from "@devicechain/widget-sdk";
|
|
168
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
169
|
+
var RADIUS = 38;
|
|
170
|
+
var THICKNESS = 14;
|
|
171
|
+
var UNKNOWN_OPACITY = 0.3;
|
|
172
|
+
var LEGEND_LINE_PX = 18;
|
|
173
|
+
function OnlineDoughnut({ widget, data }) {
|
|
174
|
+
const strings = presenceStrings();
|
|
175
|
+
const fleet = fleetStrings();
|
|
176
|
+
const severity = useSeverityColors2();
|
|
177
|
+
const feed = data.members;
|
|
178
|
+
const title = optString2(widget.options, "title");
|
|
179
|
+
const empty = (message) => /* @__PURE__ */ jsx2(WidgetFrame2, { title, children: /* @__PURE__ */ jsx2(LevelEmpty, { message }) });
|
|
180
|
+
if (!feed || feed.status === "loading") return empty(fleet.loading);
|
|
181
|
+
if (feed.status === "unbound") return empty(fleet.unbound);
|
|
182
|
+
if (feed.status === "forbidden") return empty(fleet.forbidden);
|
|
183
|
+
if (feed.status === "unsupported") return empty(strings.unsupported);
|
|
184
|
+
if (feed.members.length === 0) return empty(fleet.noMembers);
|
|
185
|
+
const tally = tallyPresence(feed.members);
|
|
186
|
+
if (tally.pending > 0) return empty(fleet.loading);
|
|
187
|
+
const counted = tally.online + tally.offline + tally.unknown;
|
|
188
|
+
if (counted === 0) return empty(strings.failed);
|
|
189
|
+
const offlineColor = levelColor2(severity, OFFLINE_LEVEL) ?? css2("foreground");
|
|
190
|
+
const segments = [
|
|
191
|
+
{ kind: "online", count: tally.online, stroke: css2("muted-foreground"), opacity: 1, label: strings.online },
|
|
192
|
+
{ kind: "offline", count: tally.offline, stroke: offlineColor, opacity: 1, label: strings.offline },
|
|
193
|
+
// The same neutral as online, pale: a device we know nothing of is less than one that is connected.
|
|
194
|
+
{ kind: "unknown", count: tally.unknown, stroke: css2("muted-foreground"), opacity: UNKNOWN_OPACITY, label: strings.unknown }
|
|
195
|
+
];
|
|
196
|
+
let at = 0;
|
|
197
|
+
const arcs = segments.map((s) => {
|
|
198
|
+
const from = at;
|
|
199
|
+
at += s.count / counted;
|
|
200
|
+
return { ...s, from, to: at };
|
|
201
|
+
});
|
|
202
|
+
const legend = arcs.filter((a) => a.kind !== "unknown" || a.count > 0);
|
|
203
|
+
const notes = [];
|
|
204
|
+
if (tally.failed > 0) notes.push(strings.notRead(tally.failed));
|
|
205
|
+
if (feed.truncated) notes.push(fleet.truncated(feed.members.length, feed.total));
|
|
206
|
+
return /* @__PURE__ */ jsx2(WidgetFrame2, { title, children: ({ size, scale }) => {
|
|
207
|
+
const legendRoom = Math.round(LEGEND_LINE_PX * scale) * (legend.length + notes.length);
|
|
208
|
+
const box = Math.max(0, Math.min(size.width, size.height - legendRoom));
|
|
209
|
+
const figure = Math.max(Math.round(FIT_MIN_FONT_PX2 * scale), Math.round(box * 0.2));
|
|
210
|
+
return /* @__PURE__ */ jsxs2(
|
|
211
|
+
"div",
|
|
212
|
+
{
|
|
213
|
+
"data-online-doughnut": "",
|
|
214
|
+
"data-online": tally.online,
|
|
215
|
+
"data-offline": tally.offline,
|
|
216
|
+
"data-no-status": tally.unknown,
|
|
217
|
+
style: { display: "flex", flexDirection: "column", height: "100%", overflow: "hidden", gap: spaceVar2(1) },
|
|
218
|
+
children: [
|
|
219
|
+
/* @__PURE__ */ jsxs2("div", { style: { position: "relative", flex: "1 1 0", minHeight: 0 }, children: [
|
|
220
|
+
/* @__PURE__ */ jsx2(
|
|
221
|
+
"svg",
|
|
222
|
+
{
|
|
223
|
+
"data-doughnut": "",
|
|
224
|
+
viewBox: "0 0 100 100",
|
|
225
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
226
|
+
role: "img",
|
|
227
|
+
"aria-label": strings.chartLabel(tally.online, tally.offline, tally.unknown),
|
|
228
|
+
style: { position: "absolute", inset: 0, width: "100%", height: "100%", display: "block" },
|
|
229
|
+
children: arcs.map((a) => {
|
|
230
|
+
if (a.count === 0) return null;
|
|
231
|
+
const common = { fill: "none", stroke: a.stroke, strokeWidth: THICKNESS, strokeOpacity: a.opacity };
|
|
232
|
+
const d = arcPath(50, 50, RADIUS, a.from, a.to);
|
|
233
|
+
return d ? /* @__PURE__ */ jsx2("path", { "data-segment": a.kind, "data-count": a.count, d, style: common }, a.kind) : /* @__PURE__ */ jsx2("circle", { "data-segment": a.kind, "data-count": a.count, cx: 50, cy: 50, r: RADIUS, style: common }, a.kind);
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
),
|
|
237
|
+
/* @__PURE__ */ jsxs2(
|
|
238
|
+
"div",
|
|
239
|
+
{
|
|
240
|
+
"data-doughnut-figure": "",
|
|
241
|
+
style: {
|
|
242
|
+
position: "absolute",
|
|
243
|
+
inset: 0,
|
|
244
|
+
display: "flex",
|
|
245
|
+
flexDirection: "column",
|
|
246
|
+
alignItems: "center",
|
|
247
|
+
justifyContent: "center",
|
|
248
|
+
fontVariantNumeric: "tabular-nums",
|
|
249
|
+
pointerEvents: "none"
|
|
250
|
+
},
|
|
251
|
+
children: [
|
|
252
|
+
/* @__PURE__ */ jsx2("span", { style: { fontSize: figure, fontWeight: 600, lineHeight: 1.1, color: css2("foreground") }, children: tally.online }),
|
|
253
|
+
/* @__PURE__ */ jsx2("span", { style: { fontSize: fontVar2("xs"), color: css2("muted-foreground") }, children: `${strings.ofTotal(counted)} ${strings.onlineCaption}` })
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
] }),
|
|
258
|
+
/* @__PURE__ */ jsxs2(
|
|
259
|
+
"ul",
|
|
260
|
+
{
|
|
261
|
+
"data-doughnut-legend": "",
|
|
262
|
+
style: { flex: "0 0 auto", margin: 0, padding: 0, listStyle: "none", fontSize: fontVar2("xs"), color: css2("muted-foreground") },
|
|
263
|
+
children: [
|
|
264
|
+
legend.map((a) => /* @__PURE__ */ jsxs2("li", { "data-legend": a.kind, style: { display: "flex", alignItems: "center", gap: spaceVar2(1), whiteSpace: "nowrap", overflow: "hidden" }, children: [
|
|
265
|
+
/* @__PURE__ */ jsx2(
|
|
266
|
+
"span",
|
|
267
|
+
{
|
|
268
|
+
"aria-hidden": "true",
|
|
269
|
+
style: {
|
|
270
|
+
flex: "0 0 auto",
|
|
271
|
+
width: "0.7em",
|
|
272
|
+
height: "0.7em",
|
|
273
|
+
borderRadius: "50%",
|
|
274
|
+
background: a.stroke,
|
|
275
|
+
opacity: a.opacity
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
),
|
|
279
|
+
/* @__PURE__ */ jsx2("span", { style: { flex: "1 1 auto", overflow: "hidden", textOverflow: "ellipsis" }, children: a.label }),
|
|
280
|
+
/* @__PURE__ */ jsx2("span", { style: { color: a.kind === "offline" && a.count > 0 ? offlineColor : css2("foreground"), fontVariantNumeric: "tabular-nums" }, children: a.count })
|
|
281
|
+
] }, a.kind)),
|
|
282
|
+
notes.map((note) => /* @__PURE__ */ jsx2("li", { "data-doughnut-note": "", style: { whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: note }, note))
|
|
283
|
+
]
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
]
|
|
287
|
+
}
|
|
288
|
+
);
|
|
289
|
+
} });
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/presence-feed.tsx
|
|
293
|
+
import { resolveStaleAfterSeconds, useWidgetTimeRange, WidgetFreshnessProvider } from "@devicechain/widget-sdk";
|
|
294
|
+
import { useEffect, useMemo, useState } from "react";
|
|
295
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
296
|
+
var emptyStore = (key) => ({ key, roster: null, states: null, forbidden: false, error: null, readAt: null });
|
|
297
|
+
function buildPresenceFleet(store) {
|
|
298
|
+
if (store.forbidden) return { status: "forbidden", members: [], total: 0, truncated: false };
|
|
299
|
+
if (!store.roster) return { status: "loading", members: [], total: 0, truncated: false };
|
|
300
|
+
if (store.roster.unbound) return { status: "unbound", members: [], total: 0, truncated: false };
|
|
301
|
+
const { states, error } = store;
|
|
302
|
+
const members = store.roster.members.map((ref) => {
|
|
303
|
+
const state = states?.get(ref.token);
|
|
304
|
+
return {
|
|
305
|
+
token: ref.token,
|
|
306
|
+
name: ref.name,
|
|
307
|
+
latest: {},
|
|
308
|
+
seed: "ready",
|
|
309
|
+
observed: [],
|
|
310
|
+
presence: state ? { status: "ready", active: state.active, lastActivityTime: state.lastActivityTime, inactivityTimeout: state.inactivityTimeout } : states ? { status: "unknown" } : error ? { status: "failed" } : { status: "pending" }
|
|
311
|
+
};
|
|
312
|
+
});
|
|
313
|
+
return { status: "ready", members, total: store.roster.total, truncated: store.roster.total > members.length };
|
|
314
|
+
}
|
|
315
|
+
function PresenceFeed({ hub, widget, descriptor, Component, actions }) {
|
|
316
|
+
const range = useWidgetTimeRange();
|
|
317
|
+
const ds = widget.datasource;
|
|
318
|
+
const dsKey = JSON.stringify(ds ?? null);
|
|
319
|
+
const supported = hub.subscribePresence !== void 0;
|
|
320
|
+
const [store, setStore] = useState(() => emptyStore(dsKey));
|
|
321
|
+
useEffect(() => {
|
|
322
|
+
setStore(emptyStore(dsKey));
|
|
323
|
+
if (!ds || !supported) return;
|
|
324
|
+
return hub.subscribePresence(ds, {
|
|
325
|
+
roster: (roster) => setStore((s) => ({ ...s, roster })),
|
|
326
|
+
next: (states) => setStore((s) => ({ ...s, states: new Map(states.map((p) => [p.deviceToken, p])), error: null, readAt: Date.now() })),
|
|
327
|
+
forbidden: () => setStore((s) => ({ ...s, forbidden: true })),
|
|
328
|
+
error: (error) => setStore((s) => ({ ...s, error }))
|
|
329
|
+
});
|
|
330
|
+
}, [hub, dsKey, supported]);
|
|
331
|
+
const live = useMemo(() => {
|
|
332
|
+
if (!supported) return { fleet: { status: "unsupported", members: [], total: 0, truncated: false }, readAt: null };
|
|
333
|
+
const current = store.key === dsKey ? store : emptyStore(dsKey);
|
|
334
|
+
return { fleet: buildPresenceFleet(current), readAt: current.readAt };
|
|
335
|
+
}, [store, dsKey, supported]);
|
|
336
|
+
const held = useHeld(live, range?.paused === true, (s) => s.fleet.status !== "loading");
|
|
337
|
+
const members = held.fleet;
|
|
338
|
+
const freshness = useMemo(
|
|
339
|
+
() => descriptor.freshness === true ? { at: held.readAt ?? void 0, staleAfterSeconds: resolveStaleAfterSeconds(widget.options) } : void 0,
|
|
340
|
+
[descriptor, widget.options, held.readAt]
|
|
341
|
+
);
|
|
342
|
+
const data = { latest: {}, samples: [], error: null, members };
|
|
343
|
+
return /* @__PURE__ */ jsx3(WidgetFreshnessProvider, { freshness, children: /* @__PURE__ */ jsx3(Component, { widget, data, actions }) });
|
|
344
|
+
}
|
|
345
|
+
export {
|
|
346
|
+
DeviceStatus,
|
|
347
|
+
OnlineDoughnut,
|
|
348
|
+
PresenceFeed
|
|
349
|
+
};
|
|
350
|
+
//# sourceMappingURL=presence-widgets-2LIUKN4Y.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/widgets/device-status.tsx", "../../src/presence-model.ts", "../../src/presence-strings.ts", "../../src/widgets/online-doughnut.tsx", "../../src/presence-feed.tsx"],
|
|
4
|
+
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// device-status -- one device's connectivity: Online or Offline in words, and when it was last seen.\n// It reads device-state through the presence feed (presence-feed.tsx), not the device's readings.\n//\n// - ISA-101: the normal state is neutral. Online keeps the foreground; only Offline takes a colour,\n// and the word says which, so colour is never the only signal;\n// - a device with no state row has never sent anything: it reads \"No status yet\", muted, and is not\n// painted as an outage;\n// - \"last seen\" is the device's last activity. The frame's age readout is the age of the STATUS\n// (when device-state was last read), with a stale badge past `staleAfterSeconds` -- never the\n// device's silence, which would grey out the very Offline it explains (presence-feed.tsx);\n// - the card shows ONE device: a group reaching it is refused in words, with where the group can be shown.\n//\n// DOM text, so the readability gate sees it.\n\nimport {\n css,\n FIT_MIN_FONT_PX,\n fontVar,\n levelColor,\n optString,\n spaceVar,\n useSeverityColors,\n viewerLocale,\n WidgetFrame,\n type WidgetProps,\n} from '@devicechain/widget-sdk';\n\nimport { fleetStrings } from '../fleet-strings';\nimport { LevelEmpty, LevelReadout } from '../level-readout';\nimport { lastActivityMs, presenceKind } from '../presence-model';\nimport { presenceStrings } from '../presence-strings';\n\n// The level an offline device is drawn at: attention, short of the alarm a rule would raise.\nexport const OFFLINE_LEVEL = 'major' as const;\n\n// The room the caption line needs, before the density scale.\nconst CAPTION_ROOM_PX = 22;\n\n// \"Oct 21, 2026, 10:32 AM\" in the viewer's language; an unparseable <html lang> falls back to the runtime's.\nexport function formatLastSeen(at: number, locale: string | undefined = viewerLocale()): string {\n try {\n return new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short' }).format(at);\n } catch {\n return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(at);\n }\n}\n\nexport function DeviceStatus({ widget, data }: WidgetProps) {\n const strings = presenceStrings();\n const fleet = fleetStrings();\n const severity = useSeverityColors();\n const feed = data.members;\n const title = optString(widget.options, 'title');\n\n const empty = (message: string) => (\n <WidgetFrame title={title}>\n <LevelEmpty message={message} />\n </WidgetFrame>\n );\n if (!feed || feed.status === 'loading') return empty(fleet.loading);\n if (feed.status === 'unbound') return empty(strings.unbound);\n if (feed.status === 'forbidden') return empty(fleet.forbidden);\n if (feed.status === 'unsupported') return empty(strings.unsupported);\n if (feed.members.length === 0) return empty(fleet.noMembers);\n if (feed.members.length > 1 || feed.total > 1) return empty(`${strings.oneDevice} ${strings.useDoughnut}`);\n\n const presence = feed.members[0].presence;\n const kind = presenceKind(presence);\n if (kind === 'pending') return empty(fleet.loading);\n if (kind === 'failed') return empty(strings.failed);\n\n const figure = kind === 'online' ? strings.online : kind === 'offline' ? strings.offline : strings.unknown;\n const color = kind === 'offline' ? levelColor(severity, OFFLINE_LEVEL) : kind === 'unknown' ? css('muted-foreground') : undefined;\n const seen = lastActivityMs(presence);\n const caption = seen !== undefined ? strings.lastSeen(formatLastSeen(seen)) : strings.neverSeen;\n\n return (\n <WidgetFrame title={title}>\n {({ size, scale }) => {\n const room = Math.round(CAPTION_ROOM_PX * scale);\n const fit = { width: size.width, height: Math.max(Math.round(FIT_MIN_FONT_PX * scale), size.height - room) };\n return (\n <div\n data-device-status={kind}\n style={{ display: 'flex', flexDirection: 'column', justifyContent: 'safe center', height: '100%', overflow: 'hidden' }}\n >\n <div style={{ flex: '1 1 0', minHeight: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>\n <LevelReadout text={figure} color={color} size={fit} scale={scale} />\n </div>\n <div\n data-device-status-caption=\"\"\n style={{\n flex: '0 0 auto',\n marginTop: spaceVar(1),\n textAlign: 'center',\n fontSize: fontVar('sm'),\n color: css('muted-foreground'),\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {caption}\n </div>\n </div>\n );\n }}\n </WidgetFrame>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// What the device status card and the online doughnut say, decided apart from the drawing. Pure, so\n// each rule is a test.\n//\n// - a device is ONLINE when device-state considers it connected, and OFFLINE when it does not.\n// Which of the two it is comes from the platform (an inferred device goes offline after its\n// inactivity timeout; an asserted one follows its transport's connect and disconnect), never\n// from this widget's idea of how long is too long;\n// - a device with NO state row has never sent anything: it is neither online nor offline, and is\n// counted apart (\"no status yet\"), never folded into the offline figure -- a device that was never\n// commissioned is not an outage;\n// - a member whose read is still pending, or failed, is counted apart again. While any is pending\n// there is no tally: a share over the members read so far would be an answer that looks final;\n// - \"last seen\" is the device's last activity (device-state's lastActivityTime), the time of its\n// newest event, not the time it was last marked connected.\n\nimport type { FleetMember, MemberPresence } from '@devicechain/dashboards';\n\nexport type PresenceKind = 'online' | 'offline' | 'unknown' | 'pending' | 'failed';\n\nexport function presenceKind(presence: MemberPresence | undefined): PresenceKind {\n if (!presence) return 'pending';\n if (presence.status === 'ready') return presence.active ? 'online' : 'offline';\n return presence.status;\n}\n\n// The device's last activity (epoch ms), or undefined when it has none or it does not parse.\nexport function lastActivityMs(presence: MemberPresence | undefined): number | undefined {\n if (presence?.status !== 'ready' || !presence.lastActivityTime) return undefined;\n const at = Date.parse(presence.lastActivityTime);\n return Number.isNaN(at) ? undefined : at;\n}\n\nexport interface PresenceTally {\n online: number;\n offline: number;\n // No state row: the device has never sent anything.\n unknown: number;\n pending: number;\n failed: number;\n // The newest last-activity time among the members (epoch ms), when any has one.\n newest: number | undefined;\n}\n\nexport function tallyPresence(members: readonly FleetMember[]): PresenceTally {\n const tally: PresenceTally = { online: 0, offline: 0, unknown: 0, pending: 0, failed: 0, newest: undefined };\n for (const m of members) {\n tally[presenceKind(m.presence)]++;\n const at = lastActivityMs(m.presence);\n if (at !== undefined && (tally.newest === undefined || at > tally.newest)) tally.newest = at;\n }\n return tally;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The fixed words the device status card and the online doughnut render themselves. The widget\n// packages carry no i18n runtime, so this is the one small per-language table (modelled on\n// fleet-strings.ts); option labels are translated in the console's catalogs, not here.\n\nimport { viewerLocale } from '@devicechain/widget-sdk';\n\nexport interface PresenceStrings {\n online: string;\n offline: string;\n // The device has never sent anything, so device-state holds no row for it.\n unknown: string;\n neverSeen: string;\n lastSeen: (when: string) => string;\n failed: string;\n unsupported: string;\n // The card is bound to a slot with nothing chosen in it yet.\n unbound: string;\n // The card is bound to a group: it shows one device.\n oneDevice: string;\n useDoughnut: string;\n // The doughnut's figure and its caption.\n ofTotal: (total: number) => string;\n onlineCaption: string;\n notRead: (n: number) => string;\n noStatusYet: (n: number) => string;\n chartLabel: (online: number, offline: number, unknown: number) => string;\n}\n\nexport const PRESENCE_STRINGS: Record<'en' | 'es', PresenceStrings> = {\n en: {\n online: 'Online',\n offline: 'Offline',\n unknown: 'No status yet',\n neverSeen: 'Never seen',\n lastSeen: (when) => `Last seen ${when}`,\n failed: 'Status could not be read',\n unsupported: 'This data source cannot read device status',\n unbound: 'Choose a device for this widget',\n oneDevice: 'This card shows one device, and it is bound to a group.',\n useDoughnut: 'Bind it to one device, or show the group in an online / offline doughnut.',\n ofTotal: (total) => `of ${total}`,\n onlineCaption: 'online',\n notRead: (n) => `${n} could not be read`,\n noStatusYet: (n) => `${n} with no status yet`,\n chartLabel: (online, offline, unknown) => `${online} online, ${offline} offline, ${unknown} with no status yet`,\n },\n es: {\n online: 'En l\u00EDnea',\n offline: 'Sin conexi\u00F3n',\n unknown: 'Sin estado todav\u00EDa',\n neverSeen: 'Nunca visto',\n lastSeen: (when) => `Visto por \u00FAltima vez ${when}`,\n failed: 'No se pudo leer el estado',\n unsupported: 'Esta fuente de datos no puede leer el estado de los dispositivos',\n unbound: 'Elija un dispositivo para este widget',\n oneDevice: 'Esta tarjeta muestra un solo dispositivo y est\u00E1 vinculada a un grupo.',\n useDoughnut: 'Vinc\u00FAlela a un dispositivo o muestre el grupo en un anillo de conectados / desconectados.',\n ofTotal: (total) => `de ${total}`,\n onlineCaption: 'en l\u00EDnea',\n notRead: (n) => `${n} sin leer`,\n noStatusYet: (n) => `${n} sin estado todav\u00EDa`,\n chartLabel: (online, offline, unknown) => `${online} en l\u00EDnea, ${offline} sin conexi\u00F3n, ${unknown} sin estado todav\u00EDa`,\n },\n};\n\nexport function presenceStrings(locale: string | undefined = viewerLocale()): PresenceStrings {\n return locale?.toLowerCase().split('-')[0] === 'es' ? PRESENCE_STRINGS.es : PRESENCE_STRINGS.en;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// online-doughnut -- how many of a group's devices are online: a ring of online, offline and\n// \"no status yet\", with the online count in the middle and every figure in a legend. Reads\n// device-state through the presence feed (presence-feed.tsx).\n//\n// - ISA-101: the normal state is neutral. Online is the quiet grey that fills most of a healthy\n// ring; only the offline slice takes a colour, and the legend names every count, so colour is\n// never the only signal. A group with nothing offline draws no colour at all;\n// - a device with no state row has never sent anything: its own pale slice (\"no status yet\"), not\n// offline. A device whose read failed is noted apart and left out of the ring;\n// - while any member's read is pending there is no ring: a share over the members read so far would\n// be an answer that looks final;\n// - a group larger than the feed reads says so (\"Showing 500 of 812 devices\") instead of presenting\n// the subset as the fleet.\n//\n// Drawn as SVG strokes with the figures as DOM text (the readability gate sees it).\n\nimport {\n css,\n FIT_MIN_FONT_PX,\n fontVar,\n levelColor,\n optString,\n spaceVar,\n useSeverityColors,\n WidgetFrame,\n type WidgetProps,\n} from '@devicechain/widget-sdk';\n\nimport { arcPath } from '../category-geometry';\nimport { fleetStrings } from '../fleet-strings';\nimport { LevelEmpty } from '../level-readout';\nimport { tallyPresence } from '../presence-model';\nimport { presenceStrings } from '../presence-strings';\nimport { OFFLINE_LEVEL } from './device-status';\n\n// The ring, in a 100-unit box.\nconst RADIUS = 38;\nconst THICKNESS = 14;\n// How much of the online grey the no-status slice keeps.\nconst UNKNOWN_OPACITY = 0.3;\n// The room one legend line needs below the ring, before the density scale.\nconst LEGEND_LINE_PX = 18;\n\nexport function OnlineDoughnut({ widget, data }: WidgetProps) {\n const strings = presenceStrings();\n const fleet = fleetStrings();\n const severity = useSeverityColors();\n const feed = data.members;\n const title = optString(widget.options, 'title');\n\n const empty = (message: string) => (\n <WidgetFrame title={title}>\n <LevelEmpty message={message} />\n </WidgetFrame>\n );\n if (!feed || feed.status === 'loading') return empty(fleet.loading);\n if (feed.status === 'unbound') return empty(fleet.unbound);\n if (feed.status === 'forbidden') return empty(fleet.forbidden);\n if (feed.status === 'unsupported') return empty(strings.unsupported);\n if (feed.members.length === 0) return empty(fleet.noMembers);\n const tally = tallyPresence(feed.members);\n // A share over the members read so far would be an undercount that looks like an answer.\n if (tally.pending > 0) return empty(fleet.loading);\n const counted = tally.online + tally.offline + tally.unknown;\n if (counted === 0) return empty(strings.failed);\n\n const offlineColor = levelColor(severity, OFFLINE_LEVEL) ?? css('foreground');\n const segments = [\n { kind: 'online', count: tally.online, stroke: css('muted-foreground'), opacity: 1, label: strings.online },\n { kind: 'offline', count: tally.offline, stroke: offlineColor, opacity: 1, label: strings.offline },\n // The same neutral as online, pale: a device we know nothing of is less than one that is connected.\n { kind: 'unknown', count: tally.unknown, stroke: css('muted-foreground'), opacity: UNKNOWN_OPACITY, label: strings.unknown },\n ];\n let at = 0;\n const arcs = segments.map((s) => {\n const from = at;\n at += s.count / counted;\n return { ...s, from, to: at };\n });\n // Online and offline always have a line (a zero is an answer); no-status only when some device has none.\n const legend = arcs.filter((a) => a.kind !== 'unknown' || a.count > 0);\n const notes: string[] = [];\n if (tally.failed > 0) notes.push(strings.notRead(tally.failed));\n if (feed.truncated) notes.push(fleet.truncated(feed.members.length, feed.total));\n\n return (\n <WidgetFrame title={title}>\n {({ size, scale }) => {\n const legendRoom = Math.round(LEGEND_LINE_PX * scale) * (legend.length + notes.length);\n const box = Math.max(0, Math.min(size.width, size.height - legendRoom));\n const figure = Math.max(Math.round(FIT_MIN_FONT_PX * scale), Math.round(box * 0.2));\n return (\n <div\n data-online-doughnut=\"\"\n data-online={tally.online}\n data-offline={tally.offline}\n data-no-status={tally.unknown}\n style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden', gap: spaceVar(1) }}\n >\n <div style={{ position: 'relative', flex: '1 1 0', minHeight: 0 }}>\n <svg\n data-doughnut=\"\"\n viewBox=\"0 0 100 100\"\n preserveAspectRatio=\"xMidYMid meet\"\n role=\"img\"\n aria-label={strings.chartLabel(tally.online, tally.offline, tally.unknown)}\n style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', display: 'block' }}\n >\n {arcs.map((a) => {\n if (a.count === 0) return null;\n const common = { fill: 'none', stroke: a.stroke, strokeWidth: THICKNESS, strokeOpacity: a.opacity } as const;\n const d = arcPath(50, 50, RADIUS, a.from, a.to);\n return d ? (\n <path key={a.kind} data-segment={a.kind} data-count={a.count} d={d} style={common} />\n ) : (\n <circle key={a.kind} data-segment={a.kind} data-count={a.count} cx={50} cy={50} r={RADIUS} style={common} />\n );\n })}\n </svg>\n <div\n data-doughnut-figure=\"\"\n style={{\n position: 'absolute',\n inset: 0,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n fontVariantNumeric: 'tabular-nums',\n pointerEvents: 'none',\n }}\n >\n <span style={{ fontSize: figure, fontWeight: 600, lineHeight: 1.1, color: css('foreground') }}>{tally.online}</span>\n <span style={{ fontSize: fontVar('xs'), color: css('muted-foreground') }}>{`${strings.ofTotal(counted)} ${strings.onlineCaption}`}</span>\n </div>\n </div>\n <ul\n data-doughnut-legend=\"\"\n style={{ flex: '0 0 auto', margin: 0, padding: 0, listStyle: 'none', fontSize: fontVar('xs'), color: css('muted-foreground') }}\n >\n {legend.map((a) => (\n <li key={a.kind} data-legend={a.kind} style={{ display: 'flex', alignItems: 'center', gap: spaceVar(1), whiteSpace: 'nowrap', overflow: 'hidden' }}>\n <span\n aria-hidden=\"true\"\n style={{\n flex: '0 0 auto',\n width: '0.7em',\n height: '0.7em',\n borderRadius: '50%',\n background: a.stroke,\n opacity: a.opacity,\n }}\n />\n <span style={{ flex: '1 1 auto', overflow: 'hidden', textOverflow: 'ellipsis' }}>{a.label}</span>\n <span style={{ color: a.kind === 'offline' && a.count > 0 ? offlineColor : css('foreground'), fontVariantNumeric: 'tabular-nums' }}>{a.count}</span>\n </li>\n ))}\n {notes.map((note) => (\n <li key={note} data-doughnut-note=\"\" style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>\n {note}\n </li>\n ))}\n </ul>\n </div>\n );\n }}\n </WidgetFrame>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The data feed of a presence widget (the device status card, the online doughnut): the roster of\n// the datasource, then device-state's connectivity for it on a poll. Loaded on first use\n// (connected-widget.tsx imports it dynamically), so a board with no presence widget never downloads it.\n//\n// It hands the widget the SAME `data.members` a fleet widget reads, with each member's `presence`\n// filled and no readings -- connectivity folded into the measurement channel's member feed rather than\n// a channel of its own, so the exported channel union does not move.\n//\n// - ONE subscription per widget (hub.subscribePresence), one batch read per poll however many members;\n// - a member the snapshot does not carry has no state row (it has never sent anything): `unknown`,\n// a settled answer, never `pending`;\n// - a read that fails before any snapshot has landed leaves every member `failed`; once one has, a\n// later blip keeps the last good answer (the poll heals it), as the alarm and location channels do;\n// - a paused board holds the snapshot on screen (useHeld) rather than moving under a \"Paused\" badge;\n// - the frame's freshness is the age of the STATUS -- when device-state was last read successfully --\n// not the devices' last activity. What these widgets show is the platform's verdict (online or\n// offline), and that verdict is current as long as it keeps being read: an offline device is silent\n// by definition, and an asserted device can be connected and quiet, so greying a card out for the\n// device's silence would strip the one colour an outage takes, or grey out a true \"Online\". A read\n// that keeps failing is what makes the answer old, and that is what goes stale. \"Last seen\" (the\n// device's last activity) is the widget's own caption.\n\nimport {\n type DevicePresence,\n type FleetMember,\n type FleetState,\n type MeasurementStreamState,\n type MemberRoster,\n type WidgetActions,\n type WidgetDataSource,\n type WidgetDescriptor,\n type WidgetInstance,\n} from '@devicechain/dashboards';\nimport { resolveStaleAfterSeconds, useWidgetTimeRange, WidgetFreshnessProvider } from '@devicechain/widget-sdk';\nimport { useEffect, useMemo, useState, type ComponentType } from 'react';\n\nimport { useHeld } from './use-held';\n\nexport interface PresenceFeedProps {\n hub: WidgetDataSource;\n widget: WidgetInstance;\n descriptor: WidgetDescriptor;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Component: ComponentType<any>;\n actions: WidgetActions | undefined;\n ErrorFrame: ComponentType<{ widget: WidgetInstance; error: unknown }>;\n}\n\ninterface Store {\n // The selector this store was filled for: a render carrying a new selector must not show the\n // previous one's members under it while the effect that resets the store has yet to run.\n key: string | null;\n roster: MemberRoster | null;\n states: Map<string, DevicePresence> | null;\n forbidden: boolean;\n error: unknown | null;\n // When the last snapshot landed (epoch ms): the age of the status on screen.\n readAt: number | null;\n}\n\nconst emptyStore = (key: string | null): Store => ({ key, roster: null, states: null, forbidden: false, error: null, readAt: null });\n\n// The state a presence widget renders from, built from the store. Exported for the tests.\nexport function buildPresenceFleet(store: Pick<Store, 'roster' | 'states' | 'forbidden' | 'error'>): FleetState {\n if (store.forbidden) return { status: 'forbidden', members: [], total: 0, truncated: false };\n if (!store.roster) return { status: 'loading', members: [], total: 0, truncated: false };\n if (store.roster.unbound) return { status: 'unbound', members: [], total: 0, truncated: false };\n const { states, error } = store;\n const members = store.roster.members.map((ref): FleetMember => {\n const state = states?.get(ref.token);\n return {\n token: ref.token,\n name: ref.name,\n latest: {},\n seed: 'ready',\n observed: [],\n presence: state\n ? { status: 'ready', active: state.active, lastActivityTime: state.lastActivityTime, inactivityTimeout: state.inactivityTimeout }\n : states\n ? { status: 'unknown' }\n : error\n ? { status: 'failed' }\n : { status: 'pending' },\n };\n });\n return { status: 'ready', members, total: store.roster.total, truncated: store.roster.total > members.length };\n}\n\nexport default function PresenceFeed({ hub, widget, descriptor, Component, actions }: PresenceFeedProps) {\n const range = useWidgetTimeRange();\n const ds = widget.datasource;\n const dsKey = JSON.stringify(ds ?? null);\n const supported = hub.subscribePresence !== undefined;\n const [store, setStore] = useState<Store>(() => emptyStore(dsKey));\n\n useEffect(() => {\n setStore(emptyStore(dsKey));\n if (!ds || !supported) return;\n return hub.subscribePresence!(ds, {\n roster: (roster) => setStore((s) => ({ ...s, roster })),\n next: (states) => setStore((s) => ({ ...s, states: new Map(states.map((p) => [p.deviceToken, p])), error: null, readAt: Date.now() })),\n forbidden: () => setStore((s) => ({ ...s, forbidden: true })),\n error: (error) => setStore((s) => ({ ...s, error })),\n });\n // `ds` is read through `dsKey` (value identity), not by reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hub, dsKey, supported]);\n\n const live = useMemo<{ fleet: FleetState; readAt: number | null }>(() => {\n if (!supported) return { fleet: { status: 'unsupported', members: [], total: 0, truncated: false }, readAt: null };\n const current = store.key === dsKey ? store : emptyStore(dsKey);\n return { fleet: buildPresenceFleet(current), readAt: current.readAt };\n }, [store, dsKey, supported]);\n // Held together, so a paused board's frame ages from the snapshot it is showing.\n const held = useHeld(live, range?.paused === true, (s) => s.fleet.status !== 'loading');\n const members = held.fleet;\n\n const freshness = useMemo(\n () =>\n descriptor.freshness === true\n ? { at: held.readAt ?? undefined, staleAfterSeconds: resolveStaleAfterSeconds(widget.options) }\n : undefined,\n [descriptor, widget.options, held.readAt],\n );\n const data: MeasurementStreamState = { latest: {}, samples: [], error: null, members };\n return (\n <WidgetFreshnessProvider freshness={freshness}>\n <Component widget={widget} data={data} actions={actions} />\n </WidgetFreshnessProvider>\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["viewerLocale", "viewerLocale", "css", "FIT_MIN_FONT_PX", "fontVar", "levelColor", "optString", "spaceVar", "useSeverityColors", "WidgetFrame", "jsx", "jsxs", "useSeverityColors", "optString", "WidgetFrame", "levelColor", "css", "FIT_MIN_FONT_PX", "spaceVar", "fontVar", "jsx"]
|
|
7
|
+
}
|
|
@@ -16,6 +16,11 @@ export interface ControlStrings {
|
|
|
16
16
|
noValue: string;
|
|
17
17
|
unsent: string;
|
|
18
18
|
sliderHint: string;
|
|
19
|
+
stepperHint: string;
|
|
20
|
+
stepperUnsent: string;
|
|
21
|
+
send: string;
|
|
22
|
+
decrease: string;
|
|
23
|
+
increase: string;
|
|
19
24
|
pending: string;
|
|
20
25
|
delivered: string;
|
|
21
26
|
failed: string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type WidgetDataSource, type WidgetDescriptor, type WidgetInstance } from '@devicechain/dashboards';
|
|
2
|
+
import { type ComponentType } from 'react';
|
|
3
|
+
export declare const FLUSH_MS = 250;
|
|
4
|
+
export interface CompareFeedProps {
|
|
5
|
+
hub: WidgetDataSource;
|
|
6
|
+
widget: WidgetInstance;
|
|
7
|
+
descriptor: WidgetDescriptor;
|
|
8
|
+
ErrorFrame: ComponentType<{
|
|
9
|
+
widget: WidgetInstance;
|
|
10
|
+
error: unknown;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
export default function CompareFeed({ hub, widget, descriptor, ErrorFrame }: CompareFeedProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { MeasurementBucket, MeasurementSample, MemberRef } from '@devicechain/dashboards';
|
|
2
|
+
export declare const DEFAULT_MAX_SERIES = 5;
|
|
3
|
+
export declare const MAX_SERIES_LIMIT = 12;
|
|
4
|
+
export declare const LIVE_PER_MEMBER = 600;
|
|
5
|
+
export declare function seriesLimit(value: number | undefined): number;
|
|
6
|
+
export interface CappedMembers {
|
|
7
|
+
shown: MemberRef[];
|
|
8
|
+
total: number;
|
|
9
|
+
capped: boolean;
|
|
10
|
+
pagedBySource: boolean;
|
|
11
|
+
read: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function capMembers(roster: {
|
|
14
|
+
members: readonly MemberRef[];
|
|
15
|
+
total: number;
|
|
16
|
+
}, limit: number): CappedMembers;
|
|
17
|
+
export declare function memberLabels(members: readonly MemberRef[]): Map<string, string>;
|
|
18
|
+
export declare function bucketSamples(token: string, measurement: string, buckets: readonly MeasurementBucket[]): MeasurementSample[];
|
|
19
|
+
export interface MemberHistory {
|
|
20
|
+
samples: MeasurementSample[];
|
|
21
|
+
end: number;
|
|
22
|
+
}
|
|
23
|
+
export declare function pushLive(buffer: MeasurementSample[], sample: MeasurementSample): void;
|
|
24
|
+
export interface ComposeInputs {
|
|
25
|
+
shown: readonly MemberRef[];
|
|
26
|
+
labels: ReadonlyMap<string, string>;
|
|
27
|
+
measurement: string;
|
|
28
|
+
history: ReadonlyMap<string, MemberHistory | 'failed'>;
|
|
29
|
+
live: ReadonlyMap<string, readonly MeasurementSample[]>;
|
|
30
|
+
since: number;
|
|
31
|
+
until: number;
|
|
32
|
+
}
|
|
33
|
+
export interface Composed {
|
|
34
|
+
samples: MeasurementSample[];
|
|
35
|
+
historyFailed: number;
|
|
36
|
+
newest: number | undefined;
|
|
37
|
+
}
|
|
38
|
+
export declare function composeSeries(i: ComposeInputs): Composed;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface CompareStrings {
|
|
2
|
+
capped: (shown: number, total: number) => string;
|
|
3
|
+
cappedPage: (shown: number, read: number, total: number) => string;
|
|
4
|
+
bucketed: (bucket: string) => string;
|
|
5
|
+
bucketedTitle: (bucket: string) => string;
|
|
6
|
+
thresholdNote: string;
|
|
7
|
+
historyFailed: (n: number) => string;
|
|
8
|
+
cappedShort: (shown: number, total: number) => string;
|
|
9
|
+
thresholdShort: string;
|
|
10
|
+
historyFailedShort: (n: number) => string;
|
|
11
|
+
}
|
|
12
|
+
export declare const COMPARE_STRINGS: Record<'en' | 'es', CompareStrings>;
|
|
13
|
+
export declare function compareStrings(locale?: string | undefined): CompareStrings;
|