@devicechain/widgets 0.24.0-rc.1 → 0.25.0-rc.2

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.
Files changed (67) hide show
  1. package/README.md +11 -1
  2. package/dist/absolute-range-picker.d.ts +22 -0
  3. package/dist/binding-policy.d.ts +1 -0
  4. package/dist/builtin-descriptors.d.ts +69 -0
  5. package/dist/builtin-registry.d.ts +68 -0
  6. package/dist/chunks/absolute-range-picker-E57G2TPX.js +139 -0
  7. package/dist/chunks/absolute-range-picker-E57G2TPX.js.map +7 -0
  8. package/dist/chunks/{aggregate-feed-4FSBQYJL.js → aggregate-feed-XX36GNY6.js} +2 -2
  9. package/dist/chunks/{aggregate-widgets-2ZQBW3YF.js → aggregate-widgets-72HMPMBU.js} +99 -57
  10. package/dist/chunks/aggregate-widgets-72HMPMBU.js.map +7 -0
  11. package/dist/chunks/{chunk-USRYKW2J.js → chunk-7BUCJQFP.js} +10 -2
  12. package/dist/chunks/chunk-7BUCJQFP.js.map +7 -0
  13. package/dist/chunks/chunk-AF4M5OXQ.js +23 -0
  14. package/dist/chunks/chunk-AF4M5OXQ.js.map +7 -0
  15. package/dist/chunks/{chunk-XQQQNTBX.js → chunk-EQOWBDS7.js} +1 -1
  16. package/dist/chunks/{chunk-XQQQNTBX.js.map → chunk-EQOWBDS7.js.map} +1 -1
  17. package/dist/chunks/chunk-JVC6JYUR.js +48 -0
  18. package/dist/chunks/chunk-JVC6JYUR.js.map +7 -0
  19. package/dist/chunks/{chunk-JFH4QMLA.js → chunk-NYHNQ5VR.js} +5 -1
  20. package/dist/chunks/{chunk-JFH4QMLA.js.map → chunk-NYHNQ5VR.js.map} +2 -2
  21. package/dist/chunks/chunk-VTGKDO44.js +87 -0
  22. package/dist/chunks/chunk-VTGKDO44.js.map +7 -0
  23. package/dist/chunks/chunk-XJ7UF2CP.js +90 -0
  24. package/dist/chunks/chunk-XJ7UF2CP.js.map +7 -0
  25. package/dist/chunks/{compare-feed-AY3CVKUZ.js → compare-feed-5OLZHFRL.js} +48 -23
  26. package/dist/chunks/compare-feed-5OLZHFRL.js.map +7 -0
  27. package/dist/chunks/{event-markers-feed-GEGTIHXT.js → event-markers-feed-B3MVFYUT.js} +4 -4
  28. package/dist/chunks/export-rows-OEWIBIUY.js +11 -0
  29. package/dist/chunks/{fleet-feed-2SKDR6ZQ.js → fleet-feed-D4T3LC25.js} +10 -10
  30. package/dist/chunks/history-widgets-NSNXDKEC.js +336 -0
  31. package/dist/chunks/history-widgets-NSNXDKEC.js.map +7 -0
  32. package/dist/chunks/{level-widgets-MJB7TOLU.js → level-widgets-HE6EDSWC.js} +13 -9
  33. package/dist/chunks/level-widgets-HE6EDSWC.js.map +7 -0
  34. package/dist/chunks/no-reading-B4MPXAS2.js +22 -0
  35. package/dist/chunks/no-reading-B4MPXAS2.js.map +7 -0
  36. package/dist/chunks/{presence-widgets-2LIUKN4Y.js → presence-widgets-M5VGG3OO.js} +12 -8
  37. package/dist/chunks/presence-widgets-M5VGG3OO.js.map +7 -0
  38. package/dist/chunks/table-CEVB3JZS.js +8 -0
  39. package/dist/chunks/table-CEVB3JZS.js.map +7 -0
  40. package/dist/compare-model.d.ts +1 -1
  41. package/dist/documented-descriptors.d.ts +206 -0
  42. package/dist/export-rows.d.ts +24 -0
  43. package/dist/history-feed.d.ts +10 -0
  44. package/dist/history-model.d.ts +45 -0
  45. package/dist/history-strings.d.ts +20 -0
  46. package/dist/index.d.ts +1 -1
  47. package/dist/index.js +544 -189
  48. package/dist/index.js.map +4 -4
  49. package/dist/level-reading.d.ts +1 -0
  50. package/dist/no-reading-core.d.ts +25 -0
  51. package/dist/no-reading.d.ts +10 -0
  52. package/dist/time-range-bar.d.ts +26 -2
  53. package/dist/widgets/history-lazy.d.ts +70 -0
  54. package/dist/widgets/history-table.d.ts +7 -0
  55. package/dist/widgets/history-widgets.d.ts +2 -0
  56. package/dist/widgets/no-reading-lazy.d.ts +7 -0
  57. package/package.json +8 -8
  58. package/dist/chunks/aggregate-widgets-2ZQBW3YF.js.map +0 -7
  59. package/dist/chunks/chunk-USRYKW2J.js.map +0 -7
  60. package/dist/chunks/compare-feed-AY3CVKUZ.js.map +0 -7
  61. package/dist/chunks/level-widgets-MJB7TOLU.js.map +0 -7
  62. package/dist/chunks/presence-widgets-2LIUKN4Y.js.map +0 -7
  63. package/dist/chunks/table-6EVV7QZH.js +0 -7
  64. /package/dist/chunks/{aggregate-feed-4FSBQYJL.js.map → aggregate-feed-XX36GNY6.js.map} +0 -0
  65. /package/dist/chunks/{event-markers-feed-GEGTIHXT.js.map → event-markers-feed-B3MVFYUT.js.map} +0 -0
  66. /package/dist/chunks/{table-6EVV7QZH.js.map → export-rows-OEWIBIUY.js.map} +0 -0
  67. /package/dist/chunks/{fleet-feed-2SKDR6ZQ.js.map → fleet-feed-D4T3LC25.js.map} +0 -0
package/README.md CHANGED
@@ -65,7 +65,7 @@ bound to a slot needs its datasource resolved first (`resolveWidgets` or
65
65
 
66
66
  | channel | widgets |
67
67
  | --- | --- |
68
- | measurement | `timeseries-chart`, `latest-card`, `gauge`, `linear-gauge`, `level-gauge`, `battery`, `status-light`, `signal-bars`, `compass`, `progress-ring`, `table`, `bar-chart`, `pie-chart`, `fleet-table`, `fleet-count`, `device-status`, `online-doughnut` |
68
+ | measurement | `timeseries-chart`, `latest-card`, `gauge`, `linear-gauge`, `level-gauge`, `battery`, `status-light`, `signal-bars`, `compass`, `progress-ring`, `table`, `bar-chart`, `pie-chart`, `fleet-table`, `fleet-count`, `device-status`, `online-doughnut`, `history-table` |
69
69
  | aggregate | `stat-card` |
70
70
  | none (static) | `label`, `image` |
71
71
  | alarm | `alarm-table`, `alarm-count`, `alarm-severity` |
@@ -83,6 +83,16 @@ each was last active) from device-state, not their readings. They ride the measu
83
83
  member feed (`data.members[].presence`), so the channel set is unchanged. `device-status` shows one
84
84
  device; `online-doughnut` takes a group.
85
85
 
86
+ `history-table` lists one device's stored history as rows, one per time bucket over the board's
87
+ span, each cell the bucket's average (the table says "avg per 5m"). It reads the store's buckets
88
+ (`binding.history`), not the live feed, and refuses a group.
89
+
90
+ A frame offers a data download from its title bar when its widget opts in: `timeseries-chart`,
91
+ `fleet-table` and `history-table` hand it a table (CSV), and the charts drawn on a canvas
92
+ (`timeseries-chart`, `gauge`, `compass`) a PNG. The file holds what the widget shows, for the
93
+ span and bucket on screen, and its name says so. A widget of your own opts in with the
94
+ optional `exportRows` prop of `WidgetFrame` (see the widget SDK).
95
+
86
96
  The time-series chart, the dial gauge and the compass are drawn with
87
97
  [Apache ECharts](https://echarts.apache.org); the other widgets are plain DOM
88
98
  and SVG. The map is [MapLibre GL](https://maplibre.org), loaded lazily so a board
@@ -0,0 +1,22 @@
1
+ import type { AbsoluteRange, AbsoluteRangeProblem } from '@devicechain/dashboards';
2
+ export interface AbsolutePickerLabels {
3
+ custom: string;
4
+ from: string;
5
+ to: string;
6
+ apply: string;
7
+ cancel: string;
8
+ zone: (zone: string, offset: string) => string;
9
+ problem: Record<AbsoluteRangeProblem, string>;
10
+ }
11
+ export declare function toLocalInput(ms: number): string;
12
+ export declare function fromLocalInput(value: string): number;
13
+ export declare function zoneInfo(at?: number): {
14
+ zone: string;
15
+ offset: string;
16
+ };
17
+ export default function AbsoluteRangePicker({ initial, labels, onApply, onCancel, }: {
18
+ initial?: AbsoluteRange;
19
+ labels: AbsolutePickerLabels;
20
+ onApply: (fromMs: number, toMs: number) => AbsoluteRangeProblem | undefined;
21
+ onCancel: () => void;
22
+ }): import("react").JSX.Element;
@@ -4,6 +4,7 @@ export declare function measurementSpan(descriptor: WidgetDescriptor, widget: Wi
4
4
  export declare function membersMode(descriptor: WidgetDescriptor, widget: WidgetInstance): boolean;
5
5
  export declare function measurementIsMembersOnly(descriptor: WidgetDescriptor): boolean;
6
6
  export declare function presenceMode(descriptor: WidgetDescriptor): boolean;
7
+ export declare function historyMode(descriptor: WidgetDescriptor): boolean;
7
8
  export declare function alarmSubscription(descriptor: WidgetDescriptor, widget: WidgetInstance): AlarmSubscription;
8
9
  export declare function groupsAlarmsByDeviceType(descriptor: WidgetDescriptor, widget: WidgetInstance): boolean;
9
10
  export declare function commandSubscription(descriptor: WidgetDescriptor, widget: WidgetInstance): CommandSubscription;
@@ -1628,6 +1628,75 @@ export declare const builtinWidgetDescriptors: {
1628
1628
  readonly helpId: 'widgetHintStaleAfter';
1629
1629
  };
1630
1630
  }>>;
1631
+ readonly 'history-table': WidgetDescriptor<"measurement", DocumentedSpecs<{
1632
+ readonly title: {
1633
+ readonly kind: 'string';
1634
+ readonly labelId: 'widgetLabelTitle';
1635
+ };
1636
+ readonly columns: {
1637
+ readonly kind: 'list';
1638
+ readonly maxItems: 24;
1639
+ readonly labelId: 'widgetLabelColumns';
1640
+ readonly helpId: 'widgetHintColumns';
1641
+ readonly item: {
1642
+ readonly measurement: {
1643
+ readonly kind: 'string';
1644
+ readonly required: true;
1645
+ readonly labelId: 'widgetLabelMeasurement';
1646
+ };
1647
+ readonly label: {
1648
+ readonly kind: 'string';
1649
+ readonly labelId: 'widgetLabelColumnHeading';
1650
+ };
1651
+ readonly unit: {
1652
+ readonly kind: 'string';
1653
+ readonly labelId: 'widgetLabelUnit';
1654
+ };
1655
+ readonly precision: {
1656
+ readonly kind: 'number';
1657
+ readonly integer: true;
1658
+ readonly min: 0;
1659
+ readonly max: 100;
1660
+ readonly labelId: 'widgetLabelPrecision';
1661
+ readonly helpId: 'widgetHintPrecision';
1662
+ };
1663
+ };
1664
+ };
1665
+ readonly precision: {
1666
+ readonly kind: 'number';
1667
+ readonly integer: true;
1668
+ readonly min: 0;
1669
+ readonly max: 100;
1670
+ readonly labelId: 'widgetLabelPrecision';
1671
+ readonly helpId: 'widgetHintPrecision';
1672
+ };
1673
+ readonly oldestFirst: {
1674
+ readonly kind: "boolean";
1675
+ readonly labelId: "widgetLabelOldestFirst";
1676
+ };
1677
+ readonly pageSize: {
1678
+ readonly kind: "number";
1679
+ readonly integer: true;
1680
+ readonly min: 5;
1681
+ readonly max: 100;
1682
+ readonly labelId: "widgetLabelRowsPerPage";
1683
+ };
1684
+ readonly staleAfterSeconds: {
1685
+ readonly kind: 'number';
1686
+ readonly integer: true;
1687
+ readonly min: 1;
1688
+ readonly labelId: 'widgetLabelStaleAfter';
1689
+ readonly helpId: 'widgetHintStaleAfter';
1690
+ };
1691
+ readonly windowSeconds: {
1692
+ readonly kind: 'number';
1693
+ readonly integer: true;
1694
+ readonly min: 60;
1695
+ readonly max: 604800;
1696
+ readonly labelId: 'widgetLabelWindowSeconds';
1697
+ readonly helpId: 'widgetHintWindowSeconds';
1698
+ };
1699
+ }>>;
1631
1700
  };
1632
1701
  export declare const builtinWidgetCatalog: import("@devicechain/dashboards").WidgetCatalog;
1633
1702
  export {};
@@ -1586,5 +1586,73 @@ export declare const builtinWidgetDefinitions: readonly [import("@devicechain/wi
1586
1586
  readonly labelId: 'widgetLabelStaleAfter';
1587
1587
  readonly helpId: 'widgetHintStaleAfter';
1588
1588
  };
1589
+ }>>, import("@devicechain/widget-sdk").WidgetDefinition<"measurement", import("./builtin-descriptors").DocumentedSpecs<{
1590
+ readonly title: {
1591
+ readonly kind: 'string';
1592
+ readonly labelId: 'widgetLabelTitle';
1593
+ };
1594
+ readonly columns: {
1595
+ readonly kind: 'list';
1596
+ readonly maxItems: 24;
1597
+ readonly labelId: 'widgetLabelColumns';
1598
+ readonly helpId: 'widgetHintColumns';
1599
+ readonly item: {
1600
+ readonly measurement: {
1601
+ readonly kind: 'string';
1602
+ readonly required: true;
1603
+ readonly labelId: 'widgetLabelMeasurement';
1604
+ };
1605
+ readonly label: {
1606
+ readonly kind: 'string';
1607
+ readonly labelId: 'widgetLabelColumnHeading';
1608
+ };
1609
+ readonly unit: {
1610
+ readonly kind: 'string';
1611
+ readonly labelId: 'widgetLabelUnit';
1612
+ };
1613
+ readonly precision: {
1614
+ readonly kind: 'number';
1615
+ readonly integer: true;
1616
+ readonly min: 0;
1617
+ readonly max: 100;
1618
+ readonly labelId: 'widgetLabelPrecision';
1619
+ readonly helpId: 'widgetHintPrecision';
1620
+ };
1621
+ };
1622
+ };
1623
+ readonly precision: {
1624
+ readonly kind: 'number';
1625
+ readonly integer: true;
1626
+ readonly min: 0;
1627
+ readonly max: 100;
1628
+ readonly labelId: 'widgetLabelPrecision';
1629
+ readonly helpId: 'widgetHintPrecision';
1630
+ };
1631
+ readonly oldestFirst: {
1632
+ readonly kind: "boolean";
1633
+ readonly labelId: "widgetLabelOldestFirst";
1634
+ };
1635
+ readonly pageSize: {
1636
+ readonly kind: "number";
1637
+ readonly integer: true;
1638
+ readonly min: 5;
1639
+ readonly max: 100;
1640
+ readonly labelId: "widgetLabelRowsPerPage";
1641
+ };
1642
+ readonly staleAfterSeconds: {
1643
+ readonly kind: 'number';
1644
+ readonly integer: true;
1645
+ readonly min: 1;
1646
+ readonly labelId: 'widgetLabelStaleAfter';
1647
+ readonly helpId: 'widgetHintStaleAfter';
1648
+ };
1649
+ readonly windowSeconds: {
1650
+ readonly kind: 'number';
1651
+ readonly integer: true;
1652
+ readonly min: 60;
1653
+ readonly max: 604800;
1654
+ readonly labelId: 'widgetLabelWindowSeconds';
1655
+ readonly helpId: 'widgetHintWindowSeconds';
1656
+ };
1589
1657
  }>>];
1590
1658
  export declare function createBuiltinWidgetRegistry(): WidgetRegistry;
@@ -0,0 +1,139 @@
1
+ // src/absolute-range-picker.tsx
2
+ import { useId, useState } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ var pad = (n) => String(n).padStart(2, "0");
5
+ function toLocalInput(ms) {
6
+ const d = new Date(ms);
7
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
8
+ }
9
+ function fromLocalInput(value) {
10
+ return value ? new Date(value).getTime() : Number.NaN;
11
+ }
12
+ function zoneInfo(at = Date.now()) {
13
+ const zone = Intl.DateTimeFormat().resolvedOptions().timeZone || "local";
14
+ const minutes = -new Date(at).getTimezoneOffset();
15
+ const sign = minutes < 0 ? "-" : "+";
16
+ const abs = Math.abs(minutes);
17
+ return { zone, offset: `UTC${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}` };
18
+ }
19
+ var field = {
20
+ display: "flex",
21
+ flexDirection: "column",
22
+ gap: 2,
23
+ fontSize: 12,
24
+ flex: "1 1 13rem",
25
+ minWidth: 0
26
+ };
27
+ var input = {
28
+ font: "inherit",
29
+ fontSize: 13,
30
+ padding: "3px 6px",
31
+ color: "hsl(var(--foreground))",
32
+ background: "hsl(var(--background))",
33
+ border: "1px solid hsl(var(--border))",
34
+ borderRadius: 4,
35
+ minWidth: 0
36
+ };
37
+ var action = (primary) => ({
38
+ padding: "3px 10px",
39
+ fontSize: 12,
40
+ cursor: "pointer",
41
+ color: primary ? "hsl(var(--background))" : "hsl(var(--foreground))",
42
+ background: primary ? "hsl(var(--foreground))" : "transparent",
43
+ border: "1px solid hsl(var(--border))",
44
+ borderRadius: 4
45
+ });
46
+ function AbsoluteRangePicker({
47
+ initial,
48
+ labels,
49
+ onApply,
50
+ onCancel
51
+ }) {
52
+ const id = useId();
53
+ const now = Date.now();
54
+ const [from, setFrom] = useState(() => toLocalInput(initial?.fromMs ?? now - 60 * 60 * 1e3));
55
+ const [to, setTo] = useState(() => toLocalInput(initial?.toMs ?? now));
56
+ const [problem, setProblem] = useState();
57
+ const { zone, offset } = zoneInfo();
58
+ const max = toLocalInput(now);
59
+ const submit = () => {
60
+ const p = onApply(fromLocalInput(from), fromLocalInput(to));
61
+ setProblem(p);
62
+ };
63
+ return /* @__PURE__ */ jsxs(
64
+ "form",
65
+ {
66
+ "data-absolute-picker": "",
67
+ "aria-label": labels.custom,
68
+ onSubmit: (e) => {
69
+ e.preventDefault();
70
+ submit();
71
+ },
72
+ onKeyDown: (e) => {
73
+ if (e.key === "Escape") onCancel();
74
+ },
75
+ style: {
76
+ display: "flex",
77
+ flexDirection: "column",
78
+ gap: 8,
79
+ padding: 10,
80
+ boxSizing: "border-box",
81
+ width: "min(24rem, calc(100vw - 32px))",
82
+ color: "hsl(var(--foreground))",
83
+ background: "hsl(var(--background))",
84
+ border: "1px solid hsl(var(--border))",
85
+ borderRadius: 6,
86
+ boxShadow: "0 4px 16px rgba(0,0,0,0.18)"
87
+ },
88
+ children: [
89
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: [
90
+ /* @__PURE__ */ jsxs("label", { htmlFor: `${id}-from`, style: field, children: [
91
+ labels.from,
92
+ /* @__PURE__ */ jsx(
93
+ "input",
94
+ {
95
+ id: `${id}-from`,
96
+ "data-absolute-from": "",
97
+ type: "datetime-local",
98
+ value: from,
99
+ max,
100
+ required: true,
101
+ onChange: (e) => setFrom(e.target.value),
102
+ style: input
103
+ }
104
+ )
105
+ ] }),
106
+ /* @__PURE__ */ jsxs("label", { htmlFor: `${id}-to`, style: field, children: [
107
+ labels.to,
108
+ /* @__PURE__ */ jsx(
109
+ "input",
110
+ {
111
+ id: `${id}-to`,
112
+ "data-absolute-to": "",
113
+ type: "datetime-local",
114
+ value: to,
115
+ max,
116
+ required: true,
117
+ onChange: (e) => setTo(e.target.value),
118
+ style: input
119
+ }
120
+ )
121
+ ] })
122
+ ] }),
123
+ /* @__PURE__ */ jsx("div", { "data-absolute-zone": "", style: { fontSize: 12, color: "hsl(var(--muted-foreground))" }, children: labels.zone(zone, offset) }),
124
+ problem ? /* @__PURE__ */ jsx("div", { role: "alert", "data-absolute-problem": problem, style: { fontSize: 12, color: "hsl(var(--destructive))" }, children: labels.problem[problem] }) : null,
125
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 6, justifyContent: "flex-end" }, children: [
126
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: onCancel, style: action(false), children: labels.cancel }),
127
+ /* @__PURE__ */ jsx("button", { type: "submit", "data-absolute-apply": "", style: action(true), children: labels.apply })
128
+ ] })
129
+ ]
130
+ }
131
+ );
132
+ }
133
+ export {
134
+ AbsoluteRangePicker as default,
135
+ fromLocalInput,
136
+ toLocalInput,
137
+ zoneInfo
138
+ };
139
+ //# sourceMappingURL=absolute-range-picker-E57G2TPX.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/absolute-range-picker.tsx"],
4
+ "sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The from-to picker for an absolute time range. Its own chunk: the time-range bar imports it on the\n// first click of \"Custom\", so a board that never opens it never downloads it (and the bar itself,\n// which every board with a header carries, stays small).\n//\n// The two fields are the browser's own `datetime-local` inputs, which read and write the VIEWER'S\n// zone. That zone is printed beside them, always, because \"02:00\" means nothing without one; the\n// window is stored as instants, and the link it offers is in UTC.\n\nimport type { AbsoluteRange, AbsoluteRangeProblem } from '@devicechain/dashboards';\nimport { useId, useState, type CSSProperties } from 'react';\n\nexport interface AbsolutePickerLabels {\n custom: string;\n from: string;\n to: string;\n apply: string;\n cancel: string;\n // \"Times are in Europe/Berlin (UTC+02:00). Shared links use UTC.\"\n zone: (zone: string, offset: string) => string;\n problem: Record<AbsoluteRangeProblem, string>;\n}\n\nconst pad = (n: number) => String(n).padStart(2, '0');\n\n// A `datetime-local` value (`2026-10-22T14:30`) for an instant, in the viewer's zone.\nexport function toLocalInput(ms: number): string {\n const d = new Date(ms);\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;\n}\n\n// The instant for a `datetime-local` value read in the viewer's zone, or NaN for an empty/invalid one.\nexport function fromLocalInput(value: string): number {\n return value ? new Date(value).getTime() : Number.NaN;\n}\n\nexport function zoneInfo(at: number = Date.now()): { zone: string; offset: string } {\n const zone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'local';\n const minutes = -new Date(at).getTimezoneOffset();\n const sign = minutes < 0 ? '-' : '+';\n const abs = Math.abs(minutes);\n return { zone, offset: `UTC${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}` };\n}\n\nconst field: CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n gap: 2,\n fontSize: 12,\n flex: '1 1 13rem',\n minWidth: 0,\n};\nconst input: CSSProperties = {\n font: 'inherit',\n fontSize: 13,\n padding: '3px 6px',\n color: 'hsl(var(--foreground))',\n background: 'hsl(var(--background))',\n border: '1px solid hsl(var(--border))',\n borderRadius: 4,\n minWidth: 0,\n};\nconst action = (primary: boolean): CSSProperties => ({\n padding: '3px 10px',\n fontSize: 12,\n cursor: 'pointer',\n color: primary ? 'hsl(var(--background))' : 'hsl(var(--foreground))',\n background: primary ? 'hsl(var(--foreground))' : 'transparent',\n border: '1px solid hsl(var(--border))',\n borderRadius: 4,\n});\n\nexport default function AbsoluteRangePicker({\n initial,\n labels,\n onApply,\n onCancel,\n}: {\n initial?: AbsoluteRange;\n labels: AbsolutePickerLabels;\n // Returns why the window was refused, or undefined when it was taken.\n onApply: (fromMs: number, toMs: number) => AbsoluteRangeProblem | undefined;\n onCancel: () => void;\n}) {\n const id = useId();\n const now = Date.now();\n // Open on the window already in force, or on the last hour as a starting point to edit.\n const [from, setFrom] = useState(() => toLocalInput(initial?.fromMs ?? now - 60 * 60 * 1000));\n const [to, setTo] = useState(() => toLocalInput(initial?.toMs ?? now));\n const [problem, setProblem] = useState<AbsoluteRangeProblem | undefined>();\n const { zone, offset } = zoneInfo();\n const max = toLocalInput(now);\n\n const submit = () => {\n const p = onApply(fromLocalInput(from), fromLocalInput(to));\n setProblem(p);\n };\n\n return (\n <form\n data-absolute-picker=\"\"\n aria-label={labels.custom}\n onSubmit={(e) => {\n e.preventDefault();\n submit();\n }}\n onKeyDown={(e) => {\n if (e.key === 'Escape') onCancel();\n }}\n style={{\n display: 'flex',\n flexDirection: 'column',\n gap: 8,\n padding: 10,\n boxSizing: 'border-box',\n width: 'min(24rem, calc(100vw - 32px))',\n color: 'hsl(var(--foreground))',\n background: 'hsl(var(--background))',\n border: '1px solid hsl(var(--border))',\n borderRadius: 6,\n boxShadow: '0 4px 16px rgba(0,0,0,0.18)',\n }}\n >\n <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>\n <label htmlFor={`${id}-from`} style={field}>\n {labels.from}\n <input\n id={`${id}-from`}\n data-absolute-from=\"\"\n type=\"datetime-local\"\n value={from}\n max={max}\n required\n onChange={(e) => setFrom(e.target.value)}\n style={input}\n />\n </label>\n <label htmlFor={`${id}-to`} style={field}>\n {labels.to}\n <input\n id={`${id}-to`}\n data-absolute-to=\"\"\n type=\"datetime-local\"\n value={to}\n max={max}\n required\n onChange={(e) => setTo(e.target.value)}\n style={input}\n />\n </label>\n </div>\n <div data-absolute-zone=\"\" style={{ fontSize: 12, color: 'hsl(var(--muted-foreground))' }}>\n {labels.zone(zone, offset)}\n </div>\n {problem ? (\n <div role=\"alert\" data-absolute-problem={problem} style={{ fontSize: 12, color: 'hsl(var(--destructive))' }}>\n {labels.problem[problem]}\n </div>\n ) : null}\n <div style={{ display: 'flex', gap: 6, justifyContent: 'flex-end' }}>\n <button type=\"button\" onClick={onCancel} style={action(false)}>\n {labels.cancel}\n </button>\n <button type=\"submit\" data-absolute-apply=\"\" style={action(true)}>\n {labels.apply}\n </button>\n </div>\n </form>\n );\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  measurementSpan
3
- } from "./chunk-JFH4QMLA.js";
3
+ } from "./chunk-NYHNQ5VR.js";
4
4
 
5
5
  // src/aggregate-feed.tsx
6
6
  import {
@@ -156,4 +156,4 @@ function AggregateFeed({ hub, widget, descriptor, Component, actions, ErrorFrame
156
156
  export {
157
157
  AggregateFeed as default
158
158
  };
159
- //# sourceMappingURL=aggregate-feed-4FSBQYJL.js.map
159
+ //# sourceMappingURL=aggregate-feed-XX36GNY6.js.map
@@ -1,13 +1,3 @@
1
- import {
2
- DEFAULT_PAGE_SIZE,
3
- cellFor,
4
- clampPage,
5
- columnNames,
6
- deviceBars,
7
- deviceLabel,
8
- pageCount,
9
- sortMembers
10
- } from "./chunk-62CHYCHI.js";
11
1
  import {
12
2
  niceDomain,
13
3
  pieSlices,
@@ -16,11 +6,31 @@ import {
16
6
  import {
17
7
  fleetStrings
18
8
  } from "./chunk-QMGKB6J3.js";
9
+ import {
10
+ Explained
11
+ } from "./chunk-AF4M5OXQ.js";
12
+ import {
13
+ NoReading
14
+ } from "./chunk-JVC6JYUR.js";
15
+ import {
16
+ fleetExport
17
+ } from "./chunk-VTGKDO44.js";
19
18
  import "./chunk-BRH66T5F.js";
20
19
  import {
21
20
  LevelEmpty,
22
21
  LevelReadout
23
- } from "./chunk-XQQQNTBX.js";
22
+ } from "./chunk-EQOWBDS7.js";
23
+ import "./chunk-XJ7UF2CP.js";
24
+ import {
25
+ DEFAULT_PAGE_SIZE,
26
+ cellFor,
27
+ clampPage,
28
+ columnNames,
29
+ deviceBars,
30
+ deviceLabel,
31
+ pageCount,
32
+ sortMembers
33
+ } from "./chunk-62CHYCHI.js";
24
34
 
25
35
  // src/widgets/bar-chart.tsx
26
36
  import { useMemo } from "react";
@@ -135,8 +145,8 @@ function DeviceBars({
135
145
  );
136
146
  if (!measurement) return /* @__PURE__ */ jsx(LevelEmpty, { message: strings.noMeasurement });
137
147
  if (!fleet || fleet.status === "loading") return /* @__PURE__ */ jsx(LevelEmpty, { message: strings.loading });
138
- if (fleet.status === "unbound") return /* @__PURE__ */ jsx(LevelEmpty, { message: strings.unbound });
139
- if (fleet.status === "forbidden") return /* @__PURE__ */ jsx(LevelEmpty, { message: strings.forbidden });
148
+ if (fleet.status === "unbound") return /* @__PURE__ */ jsx(Explained, { kind: "unbound", message: strings.unbound });
149
+ if (fleet.status === "forbidden") return /* @__PURE__ */ jsx(Explained, { kind: "forbidden", message: strings.forbidden });
140
150
  if (fleet.status === "unsupported") return /* @__PURE__ */ jsx(LevelEmpty, { message: strings.unsupported });
141
151
  if (bars.length === 0) return /* @__PURE__ */ jsx(LevelEmpty, { message: strings.noMembers });
142
152
  const drawn = bars.slice(0, MAX_DEVICE_BARS);
@@ -314,10 +324,10 @@ function FleetCount({ widget, data }) {
314
324
  const measurement = optString2(widget.options, "measurement") ?? primaryMeasurementName(widget);
315
325
  const thresholds = useMemo2(() => optThresholds(widget.options, "thresholds"), [widget.options]);
316
326
  const title = optString2(widget.options, "title");
317
- const empty = (message) => /* @__PURE__ */ jsx2(WidgetFrame2, { title, children: /* @__PURE__ */ jsx2(LevelEmpty, { message }) });
327
+ const empty = (message, kind) => /* @__PURE__ */ jsx2(WidgetFrame2, { title, children: kind ? /* @__PURE__ */ jsx2(Explained, { kind, message }) : /* @__PURE__ */ jsx2(LevelEmpty, { message }) });
318
328
  if (!fleet || fleet.status === "loading") return empty(strings.loading);
319
- if (fleet.status === "unbound") return empty(strings.unbound);
320
- if (fleet.status === "forbidden") return empty(strings.forbidden);
329
+ if (fleet.status === "unbound") return empty(strings.unbound, "unbound");
330
+ if (fleet.status === "forbidden") return empty(strings.forbidden, "forbidden");
321
331
  if (fleet.status === "unsupported") return empty(strings.unsupported);
322
332
  if (!showDevices) {
323
333
  if (measurement === void 0) return empty(strings.noMeasurementCount);
@@ -473,8 +483,8 @@ function FleetTable({ widget, data }) {
473
483
  if (!fleet || fleet.status === "loading") {
474
484
  return /* @__PURE__ */ jsx3(Frame, { title, children: /* @__PURE__ */ jsx3(LevelEmpty, { message: strings.loading }) });
475
485
  }
476
- if (fleet.status === "unbound") return /* @__PURE__ */ jsx3(Frame, { title, children: /* @__PURE__ */ jsx3(LevelEmpty, { message: strings.unbound }) });
477
- if (fleet.status === "forbidden") return /* @__PURE__ */ jsx3(Frame, { title, children: /* @__PURE__ */ jsx3(LevelEmpty, { message: strings.forbidden }) });
486
+ if (fleet.status === "unbound") return /* @__PURE__ */ jsx3(Frame, { title, children: /* @__PURE__ */ jsx3(Explained, { kind: "unbound", message: strings.unbound }) });
487
+ if (fleet.status === "forbidden") return /* @__PURE__ */ jsx3(Frame, { title, children: /* @__PURE__ */ jsx3(Explained, { kind: "forbidden", message: strings.forbidden }) });
478
488
  if (fleet.status === "unsupported") return /* @__PURE__ */ jsx3(Frame, { title, children: /* @__PURE__ */ jsx3(LevelEmpty, { message: strings.unsupported }) });
479
489
  if (fleet.members.length === 0) return /* @__PURE__ */ jsx3(Frame, { title, children: /* @__PURE__ */ jsx3(LevelEmpty, { message: strings.noMembers }) });
480
490
  return /* @__PURE__ */ jsx3(Table, { widget, fleet, strings });
@@ -626,44 +636,61 @@ function Table({
626
636
  ] }, member.token);
627
637
  })
628
638
  ] });
629
- return /* @__PURE__ */ jsx3(Frame, { title: optString3(widget.options, "title"), children: /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
630
- /* @__PURE__ */ jsx3(
631
- "div",
632
- {
633
- role: "region",
634
- "aria-label": optString3(widget.options, "title") ?? strings.device,
635
- tabIndex: 0,
636
- "data-fleet-scroll": "",
637
- style: { flex: 1, minHeight: 0, overflow: "auto" },
638
- children: /* @__PURE__ */ jsx3(Responsive, { wide, narrow })
639
- }
640
- ),
641
- (fleet.truncated || pages > 1) && /* @__PURE__ */ jsxs3(
642
- "div",
643
- {
644
- "data-fleet-footer": "",
645
- style: {
646
- display: "flex",
647
- flexWrap: "wrap",
648
- alignItems: "center",
649
- justifyContent: "space-between",
650
- gap: spaceVar2(2),
651
- padding: `${spaceVar2(1)} ${spaceVar2(3)}`,
652
- fontSize: fontVar2("sm"),
653
- color: css3("muted-foreground"),
654
- borderTop: `1px solid ${css3("border")}`
655
- },
656
- children: [
657
- /* @__PURE__ */ jsx3("span", { "data-fleet-truncated": fleet.truncated ? "" : void 0, children: fleet.truncated ? strings.truncated(fleet.members.length, fleet.total) : "" }),
658
- pages > 1 && /* @__PURE__ */ jsxs3("span", { style: { display: "inline-flex", alignItems: "center", gap: spaceVar2(2) }, children: [
659
- /* @__PURE__ */ jsx3("button", { type: "button", style: pagerButton, disabled: shownPage === 0, onClick: () => setPage(shownPage - 1), "aria-label": strings.previous, children: "\u2039" }),
660
- /* @__PURE__ */ jsx3("span", { children: strings.range(shownPage * pageSize + 1, shownPage * pageSize + rows.length, sorted.length) }),
661
- /* @__PURE__ */ jsx3("button", { type: "button", style: pagerButton, disabled: shownPage >= pages - 1, onClick: () => setPage(shownPage + 1), "aria-label": strings.next, children: "\u203A" })
662
- ] })
663
- ]
664
- }
665
- )
666
- ] }) });
639
+ return /* @__PURE__ */ jsx3(
640
+ WidgetFrame3,
641
+ {
642
+ title: optString3(widget.options, "title"),
643
+ flush: true,
644
+ exportRows: () => fleetExport({
645
+ subject: optString3(widget.options, "title") ?? "fleet",
646
+ members: fleet.members,
647
+ total: fleet.total,
648
+ columns: columns.map((name) => {
649
+ const f = formats.get(name);
650
+ return { name, heading: (f?.label ?? name) + (f?.unit ? ` (${f.unit})` : ""), precision: f?.precision };
651
+ }),
652
+ sort
653
+ }),
654
+ children: /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
655
+ /* @__PURE__ */ jsx3(
656
+ "div",
657
+ {
658
+ role: "region",
659
+ "aria-label": optString3(widget.options, "title") ?? strings.device,
660
+ tabIndex: 0,
661
+ "data-fleet-scroll": "",
662
+ style: { flex: 1, minHeight: 0, overflow: "auto" },
663
+ children: /* @__PURE__ */ jsx3(Responsive, { wide, narrow })
664
+ }
665
+ ),
666
+ (fleet.truncated || pages > 1) && /* @__PURE__ */ jsxs3(
667
+ "div",
668
+ {
669
+ "data-fleet-footer": "",
670
+ style: {
671
+ display: "flex",
672
+ flexWrap: "wrap",
673
+ alignItems: "center",
674
+ justifyContent: "space-between",
675
+ gap: spaceVar2(2),
676
+ padding: `${spaceVar2(1)} ${spaceVar2(3)}`,
677
+ fontSize: fontVar2("sm"),
678
+ color: css3("muted-foreground"),
679
+ borderTop: `1px solid ${css3("border")}`
680
+ },
681
+ children: [
682
+ /* @__PURE__ */ jsx3("span", { "data-fleet-truncated": fleet.truncated ? "" : void 0, children: fleet.truncated ? strings.truncated(fleet.members.length, fleet.total) : "" }),
683
+ pages > 1 && /* @__PURE__ */ jsxs3("span", { style: { display: "inline-flex", alignItems: "center", gap: spaceVar2(2) }, children: [
684
+ /* @__PURE__ */ jsx3("button", { type: "button", style: pagerButton, disabled: shownPage === 0, onClick: () => setPage(shownPage - 1), "aria-label": strings.previous, children: "\u2039" }),
685
+ /* @__PURE__ */ jsx3("span", { children: strings.range(shownPage * pageSize + 1, shownPage * pageSize + rows.length, sorted.length) }),
686
+ /* @__PURE__ */ jsx3("button", { type: "button", style: pagerButton, disabled: shownPage >= pages - 1, onClick: () => setPage(shownPage + 1), "aria-label": strings.next, children: "\u203A" })
687
+ ] })
688
+ ]
689
+ }
690
+ )
691
+ ] })
692
+ }
693
+ );
667
694
  }
668
695
  function positiveSeconds(v) {
669
696
  return v !== void 0 && Number.isFinite(v) && v > 0 ? v : void 0;
@@ -839,6 +866,7 @@ import {
839
866
  optNumber as optNumber4,
840
867
  optString as optString5,
841
868
  optThresholds as optThresholds2,
869
+ pickSample as pickSample2,
842
870
  spaceVar as spaceVar4,
843
871
  useSeverityColors as useSeverityColors3,
844
872
  WidgetFrame as WidgetFrame5,
@@ -981,6 +1009,20 @@ function StatCard({ widget, data }) {
981
1009
  const wantsTrend = optBoolean2(widget.options, "sparkline");
982
1010
  const card = /* @__PURE__ */ jsx6(WidgetFrame5, { title, sampleTime: stat === "last" ? reading.kind === "value" ? reading.time ?? null : null : void 0, children: ({ size, scale }) => {
983
1011
  if (reading.kind === "empty") {
1012
+ if (reading.reason === "unbound" || reading.reason === "noData" || reading.reason === "noReadings") {
1013
+ return /* @__PURE__ */ jsx6(
1014
+ NoReading,
1015
+ {
1016
+ unbound: reading.reason === "unbound",
1017
+ measurement: reading.name,
1018
+ lastSample: pickSample2(data.latest, reading.name),
1019
+ unit,
1020
+ precision,
1021
+ detail: reading.reason === "noReadings" ? emptyMessage(reading, strings) : void 0,
1022
+ waitMs: reading.reason === "noReadings" ? 0 : void 0
1023
+ }
1024
+ );
1025
+ }
984
1026
  return /* @__PURE__ */ jsx6(LevelEmpty, { message: emptyMessage(reading, strings) });
985
1027
  }
986
1028
  const isCount = reading.stat === "count";
@@ -1087,4 +1129,4 @@ export {
1087
1129
  PieChart,
1088
1130
  StatCard
1089
1131
  };
1090
- //# sourceMappingURL=aggregate-widgets-2ZQBW3YF.js.map
1132
+ //# sourceMappingURL=aggregate-widgets-72HMPMBU.js.map