@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
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/event-markers-feed.tsx", "../../src/event-markers-model.ts", "../../src/event-markers-strings.ts"],
|
|
4
|
+
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The event-marker feed of a time-series chart: the alarms and commands of the chart's device inside\n// the chart's range, drawn as markers (event-markers-model.ts) and listed under the chart. Loaded on\n// first use (connected-widget.tsx imports it dynamically), so a board whose charts have the markers\n// off never downloads it, and the ECharts pieces it draws with load only when a marker is drawn.\n//\n// Where the events come from: the hub's own alarm and command channels (the queries the alarm table\n// and the command button already read), scoped to the chart's ONE device and to the chart's range,\n// which device-management and command-delivery apply before paging. So:\n//\n// - one page of MAX_EVENT_MARKERS is read per channel, and each page's total is the exact count in\n// range, so the note says exactly how many were left out, for a range of any age;\n// - a change of range, and a pause, read again (the subscription is keyed by the span and the\n// pause instant), so the note and the markers are never those of another range;\n// - a chart with no device reads nothing. It is NEVER given an unscoped subscription: on the alarm\n// channel \"no datasource\" means every alarm in the tenant;\n// - a paused board holds the events it showed at the pause (useHeld), as its readings are held;\n// - the alarm scope ('series', or 'device' when unset) picks which of the device's alarms are DRAWN:\n// 'series' keeps those about a measurement the chart plots, and the others are listed apart\n// (\"other alarms on this device\"), never drawn and never dropped;\n// - a source that cannot supply a channel (a recording without alarms or commands) says so in a\n// note instead of drawing none and implying there were none.\n\nimport {\n DEFAULT_TIME_RANGE_SECONDS,\n type AlarmSnapshot,\n type CommandSnapshot,\n type MeasurementStreamState,\n type WidgetActions,\n type WidgetDataSource,\n type WidgetDescriptor,\n type WidgetInstance,\n} from '@devicechain/dashboards';\nimport {\n css,\n fontVar,\n formatDateTime,\n formatTimestamp,\n optBoolean,\n optString,\n spaceVar,\n useWidgetTimeRange,\n useWidgetTimeZone,\n} from '@devicechain/widget-sdk';\nimport { useChartTheme } from '@devicechain/widget-sdk/echarts';\nimport { useEffect, useMemo, useRef, useState, type ComponentType, type ReactNode } from 'react';\n\nimport { measurementSpan } from './binding-policy';\nimport {\n alarmEvents,\n alarmScope,\n commandEvents,\n MAX_EVENT_MARKERS,\n planMarkers,\n splitByScope,\n type EventItem,\n type EventRange,\n} from './event-markers-model';\nimport { eventMarkerStrings, type EventKind } from './event-markers-strings';\nimport type { EventMarkersView } from './event-markers-types';\nimport { useHeld } from './use-held';\n\nconst DAY_MS = 86_400_000;\n\nexport interface EventMarkersFeedProps {\n hub: WidgetDataSource;\n widget: WidgetInstance;\n descriptor: WidgetDescriptor;\n data: MeasurementStreamState;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Component: ComponentType<any>;\n actions: WidgetActions | undefined;\n}\n\n// One channel's latest answer, tagged with the subscription it answers: a render that carries a new\n// key must not show the previous key's rows, so a mismatch reads as loading.\ninterface Channel<S> {\n key: string;\n value?: S;\n error?: unknown;\n}\n\ninterface Sink<S> {\n next: (value: S) => void;\n error?: (err: unknown) => void;\n}\n\nfunction useChannel<S>(subscribe: ((sink: Sink<S>) => () => void) | null, key: string, paused: boolean): Channel<S> {\n const [state, setState] = useState<Channel<S>>({ key: '' });\n const subscribeRef = useRef(subscribe);\n subscribeRef.current = subscribe;\n const enabled = subscribe !== null;\n useEffect(() => {\n const open = subscribeRef.current;\n if (!open) return;\n return open({\n next: (value) => setState({ key, value }),\n // A later error keeps the rows already read: the channel polls and heals.\n error: (error) => setState((prev) => ({ key, value: prev.key === key ? prev.value : undefined, error })),\n });\n }, [key, enabled]);\n const live: Channel<S> = enabled && state.key === key ? state : { key };\n return useHeld(live, paused, (c) => c.value !== undefined || c.error !== undefined);\n}\n\nconst isNotInRecording = (err: unknown): boolean => err instanceof Error && err.name === 'NotInRecordingError';\n\nexport default function EventMarkersFeed({ hub, widget, descriptor, data, Component, actions }: EventMarkersFeedProps) {\n const range = useWidgetTimeRange();\n const timeZone = useWidgetTimeZone();\n const theme = useChartTheme();\n const words = eventMarkerStrings();\n const now = useMemo(() => (hub.now ? () => hub.now!() : Date.now), [hub]);\n const spanSeconds = measurementSpan(descriptor, widget, range?.spanSeconds) ?? DEFAULT_TIME_RANGE_SECONDS;\n const paused = range?.paused === true;\n const pausedAt = paused ? range?.pausedAt : undefined;\n const ds = widget.datasource;\n const bound = ds !== undefined && ds.kind !== 'unbound';\n const wantAlarms = bound && optBoolean(widget.options, 'markAlarms') === true;\n const scope = alarmScope(optString(widget.options, 'markAlarmsScope'));\n const wantCommands = bound && optBoolean(widget.options, 'markCommands') === true;\n\n // The range is part of the key: a new span, or a pause (which fixes the range's end), reads\n // again, and the answer for one range is never shown under another.\n const key = JSON.stringify([ds ?? null, spanSeconds, pausedAt ?? null]);\n const window = { spanSeconds, ...(pausedAt !== undefined ? { endAt: pausedAt } : {}) };\n const alarms = useChannel<AlarmSnapshot>(\n wantAlarms ? (sink) => hub.subscribeAlarms({ datasource: ds, pageSize: MAX_EVENT_MARKERS, window }, sink) : null,\n `a${key}`,\n paused,\n );\n const commands = useChannel<CommandSnapshot>(\n wantCommands ? (sink) => hub.subscribeCommands({ datasource: ds, pageSize: MAX_EVENT_MARKERS, window }, sink) : null,\n `c${key}`,\n paused,\n );\n\n const view = useMemo((): EventMarkersView | undefined => {\n if (!wantAlarms && !wantCommands) return undefined;\n const to = pausedAt ?? now();\n const span: EventRange = { from: to - spanSeconds * 1000, to };\n const fmt = (at: number): string =>\n spanSeconds * 1000 > DAY_MS\n ? formatDateTime(new Date(at).toISOString(), timeZone)\n : formatTimestamp(new Date(at).toISOString(), timeZone);\n\n // What the chart plots: the measurements it names, else those its readings carry.\n const named = ds?.measurements ?? [];\n const charted = new Set<string>(named.length > 0 ? named : data.samples.map((s) => s.name));\n\n const deviceToken = ds?.kind === 'device' ? ds.deviceToken : undefined;\n const found: EventItem[] = [];\n const notes: string[] = [];\n let unloaded = 0;\n\n const channel = (kind: EventKind, c: Channel<unknown>): void => {\n if (c.value === undefined && c.error !== undefined) {\n notes.push(isNotInRecording(c.error) ? words.notInRecording[kind] : words.failed[kind]);\n }\n };\n if (wantAlarms) {\n channel('alarm', alarms);\n const snap = alarms.value;\n if (snap) {\n found.push(...alarmEvents(snap.alarms, deviceToken, span, words, fmt));\n // The source counted the range before paging, so the rows past the page are exact.\n unloaded += Math.max(0, snap.total - snap.alarms.length);\n }\n }\n if (wantCommands) {\n channel('command', commands);\n const snap = commands.value;\n if (snap) {\n found.push(...commandEvents(snap.commands, span, words, fmt));\n unloaded += Math.max(0, snap.total - snap.commands.length);\n }\n }\n\n const { drawn, other } = splitByScope(found, scope, charted);\n const plan = planMarkers(drawn, span, theme);\n const more = plan.hidden + unloaded;\n if (more > 0) notes.push(words.more(more));\n const listed = other.map((e) => ({\n ...e,\n detail: `${e.detail} \u00B7 ${e.metric ? words.aboutMeasurement(e.metric) : words.noMeasurement}`,\n }));\n return { decorate: plan.decorate, footer: footerOf(plan.shown, listed, notes, theme.severity) };\n // `ds` is read through the keys (value identity). `alarms`/`commands` are the channels' values.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [wantAlarms, wantCommands, scope, alarms, commands, data.samples, theme, words, spanSeconds, pausedAt, now, timeZone, key]);\n\n return <Component widget={widget} data={data} actions={actions} markers={view} />;\n}\n\nconst FOOTER_STYLE = {\n flex: 'none',\n fontSize: fontVar('xs'),\n color: css('muted-foreground'),\n padding: `0 ${spaceVar(2)}`,\n} as const;\n\nfunction footerOf(\n shown: readonly EventItem[],\n other: readonly EventItem[],\n notes: readonly string[],\n severity: Record<string, string>,\n): ReactNode {\n if (shown.length === 0 && other.length === 0 && notes.length === 0) return null;\n const words = eventMarkerStrings();\n return (\n <div data-chart-events=\"\" style={FOOTER_STYLE}>\n {notes.map((note) => (\n <div key={note} data-chart-events-note=\"\">\n {note}\n </div>\n ))}\n {shown.length > 0 && <EventList events={shown} label={words.events(shown.length)} severity={severity} />}\n {other.length > 0 && <EventList events={other} label={words.otherAlarms(other.length)} severity={severity} other />}\n </div>\n );\n}\n\nfunction EventList({\n events,\n label,\n severity,\n other,\n}: {\n events: readonly EventItem[];\n label: string;\n severity: Record<string, string>;\n other?: boolean;\n}) {\n return (\n // A native disclosure: reachable and operable from the keyboard, with the canvas's tooltips\n // as its pointer-and-touch counterpart.\n <details data-chart-events-other={other ? '' : undefined}>\n <summary style={{ cursor: 'pointer' }}>{label}</summary>\n {/* The list scrolls past a few rows, so it takes focus: a keyboard can only scroll what it can reach. */}\n <ul tabIndex={0} aria-label={label} style={{ margin: 0, padding: 0, listStyle: 'none', maxHeight: 96, overflowY: 'auto' }}>\n {[...events].reverse().map((e) => (\n <li key={e.id} data-chart-event={e.kind} style={{ display: 'flex', gap: spaceVar(1), alignItems: 'baseline' }}>\n <span\n aria-hidden=\"true\"\n style={{\n flex: 'none',\n width: 8,\n height: 8,\n background: e.level ? severity[e.level] : css('muted-foreground'),\n borderRadius: e.kind === 'command' ? 4 : 0,\n }}\n />\n <span>\n {e.title} \u00B7 {e.detail}\n </span>\n </li>\n ))}\n </ul>\n </details>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The event markers of a time-series chart as plain data: which alarms and commands fall in the\n// chart's range, which of them are drawn, and the ECharts pieces that draw them. Pure (no React, no\n// ECharts import), and part of the markers' lazy chunk (event-markers-feed.tsx).\n//\n// - An ALARM is a span: a faint band from the raise to the clear (or to the end of the range while\n// it is still active) under a thin strip along the top edge, with a solid line at the raise. The\n// span takes its severity's colour; an\n// INDETERMINATE alarm and the clear line are neutral, because colour means severity and\n// \"back to normal\" has none (the ISA-101 rule the threshold bands follow).\n// - A chart whose alarm scope is 'series' draws only the alarms about a measurement it charts. The\n// rest (another measurement's, or none known) are not drawn but are LISTED, so nothing is hidden.\n// - A COMMAND is a dotted neutral line at the time it was sent (queued, when it never was), named\n// by the command, with its delivery outcome where it has one.\n// - At most MAX_EVENT_MARKERS events are drawn, the newest ones; the rest are COUNTED, never\n// dropped in silence (the feed prints the count).\n// - The markers sit on a helper series of their own, so the reading series are untouched.\n\nimport type { AlarmRow, CommandRow } from '@devicechain/dashboards';\nimport { levelColor, type AlarmLevel } from '@devicechain/widget-sdk';\nimport { severityFill, type ChartTheme, type EChartOption } from '@devicechain/widget-sdk/echarts';\n\nimport type { EventKind, EventMarkerStrings } from './event-markers-strings';\n\n// The most events drawn on one chart. Past it the chart is a comb, not a timeline.\nexport const MAX_EVENT_MARKERS = 30;\n\n// The opacity of an alarm's span behind the readings: a zone, not a wall.\nconst SPAN_ALPHA = 0.05;\n// The strip along the top edge of the plot that carries the span's colour: the band is a hint, the\n// strip is what reads as the alarm's severity.\nconst STRIP_PX = 3;\nconst STRIP_ALPHA = 0.85;\n// Labels are written on the canvas only on a roomy chart with few lines; the tooltip and the list\n// carry the same text everywhere.\nconst LABEL_MIN_WIDTH = 480;\nconst LABEL_MAX_LINES = 8;\n\nexport interface EventRange {\n from: number; // epoch ms\n to: number;\n}\n\nexport interface EventItem {\n id: string;\n kind: EventKind;\n // The raise time of an alarm; the send time of a command.\n at: number;\n // An alarm's clear time; null while it is active (and always null for a command).\n end: number | null;\n level: AlarmLevel | undefined;\n // The command's name, or the alarm's key.\n name: string;\n title: string;\n detail: string;\n // An alarm's measurement (its metricKey); empty when none is known. Absent on a command.\n metric?: string;\n}\n\nconst LEVELS: Record<string, AlarmLevel> = { CRITICAL: 'critical', MAJOR: 'major', MINOR: 'minor', WARNING: 'warning' };\n\n// INDETERMINATE (and any severity a newer platform adds) has no level: it draws neutral.\nexport function alarmLevel(severity: string): AlarmLevel | undefined {\n return LEVELS[severity.toUpperCase()];\n}\n\nfunction ms(iso: string | null): number | null {\n if (!iso) return null;\n const t = Date.parse(iso);\n return Number.isNaN(t) ? null : t;\n}\n\n// The alarms that overlap the range, as events. A row with no raise time cannot be placed and is\n// skipped; with `deviceToken`, only that device's rows are kept (the platform already limits a\n// viewer to the devices they may see, and this keeps another device's alarm off this device's chart\n// whatever a source returns).\nexport function alarmEvents(\n rows: readonly AlarmRow[],\n deviceToken: string | undefined,\n range: EventRange,\n words: EventMarkerStrings,\n fmt: (at: number) => string,\n): EventItem[] {\n const out: EventItem[] = [];\n for (const r of rows) {\n if (deviceToken !== undefined && r.originatorToken !== deviceToken) continue;\n const at = ms(r.raisedTime);\n if (at === null) continue;\n const end = ms(r.clearedTime);\n if (at > range.to || (end ?? Infinity) < range.from) continue;\n const sev = words.severity[r.severity.toUpperCase()] ?? r.severity;\n out.push({\n id: r.token,\n kind: 'alarm',\n at,\n end,\n level: alarmLevel(r.severity),\n name: r.alarmKey,\n metric: r.metricKey,\n title: `${words.alarm}: ${r.alarmKey}`,\n detail: [sev, words.raised(fmt(at)), end === null ? words.stillActive : words.cleared(fmt(end))].join(' \u00B7 '),\n });\n }\n return out;\n}\n\nexport type AlarmScope = 'series' | 'device';\n\n// Unset is 'device': a chart saved before the option existed marked every alarm on its device.\nexport function alarmScope(value: unknown): AlarmScope {\n return value === 'series' ? 'series' : 'device';\n}\n\n// The alarms a series-scoped chart draws are those about a measurement it charts; the others are\n// returned apart, for the list. An alarm with no known measurement is never drawn. Commands pass.\nexport function splitByScope(\n items: readonly EventItem[],\n scope: AlarmScope,\n charted: ReadonlySet<string>,\n): { drawn: EventItem[]; other: EventItem[] } {\n if (scope === 'device') return { drawn: [...items], other: [] };\n const drawn: EventItem[] = [];\n const other: EventItem[] = [];\n for (const e of items) (e.kind === 'alarm' && !(e.metric && charted.has(e.metric)) ? other : drawn).push(e);\n return { drawn, other };\n}\n\n// The commands sent inside the range, as events.\nexport function commandEvents(\n rows: readonly CommandRow[],\n range: EventRange,\n words: EventMarkerStrings,\n fmt: (at: number) => string,\n): EventItem[] {\n const out: EventItem[] = [];\n for (const r of rows) {\n const at = ms(r.sentTime) ?? ms(r.queuedTime);\n if (at === null || at < range.from || at > range.to) continue;\n out.push({\n id: r.token,\n kind: 'command',\n at,\n end: null,\n level: undefined,\n name: r.name,\n title: `${words.command}: ${r.name}`,\n detail: [words.sent(fmt(at)), words.outcome[r.status] ?? r.status].join(' \u00B7 '),\n });\n }\n return out;\n}\n\nexport interface MarkerPlan {\n // The events drawn, oldest first.\n shown: EventItem[];\n // Events in the range that were not drawn because of the cap.\n hidden: number;\n decorate: (option: EChartOption, size?: { width: number; height: number }) => EChartOption;\n}\n\nconst HTML_ESCAPES: Record<string, string> = { '&': '&', '<': '<', '>': '>', '\"': '"', \"'\": ''' };\nconst escapeHtml = (s: string): string => s.replace(/[&<>\"']/g, (c) => HTML_ESCAPES[c]);\n\nexport function planMarkers(items: readonly EventItem[], range: EventRange, theme: ChartTheme): MarkerPlan {\n // Newest first, so the cap keeps what just happened; drawn and listed oldest first.\n const byNewest = [...items].sort((a, b) => b.at - a.at || (a.id < b.id ? -1 : 1));\n const shown = byNewest.slice(0, MAX_EVENT_MARKERS).reverse();\n const hidden = byNewest.length - shown.length;\n\n const neutral = theme.mutedForeground;\n const tips: Record<string, string> = {};\n const lines: Record<string, unknown>[] = [];\n const areas: unknown[] = [];\n const strips: Array<(top: number) => unknown> = [];\n for (const e of shown) {\n const tip = `<b>${escapeHtml(e.title)}</b><br/>${escapeHtml(e.detail)}`;\n if (e.kind === 'command') {\n tips[e.id] = tip;\n lines.push({ name: e.id, xAxis: e.at, lineStyle: { color: neutral, type: 'dotted', width: 2 }, labelText: e.name });\n continue;\n }\n const ink = levelColor(theme.severity, e.level) ?? neutral;\n const start = Math.max(e.at, range.from);\n const stop = Math.min(e.end ?? range.to, range.to);\n tips[`${e.id}:span`] = tip;\n areas.push([\n { name: `${e.id}:span`, xAxis: start, itemStyle: { color: e.level ? severityFill(theme, e.level, SPAN_ALPHA) : theme.border } },\n { xAxis: stop },\n ]);\n // Pixels, not data: the strip hugs the top of the plot whatever the value axis does, so its\n // y is known only when the option is (decorate).\n tips[`${e.id}:strip`] = tip;\n strips.push((top) => [\n { name: `${e.id}:strip`, xAxis: start, y: top, itemStyle: { color: e.level ? severityFill(theme, e.level, STRIP_ALPHA) : neutral } },\n { xAxis: stop, y: top + STRIP_PX },\n ]);\n if (e.at >= range.from) {\n tips[`${e.id}:raised`] = tip;\n lines.push({ name: `${e.id}:raised`, xAxis: e.at, lineStyle: { color: ink, type: 'solid', width: 2 } });\n }\n if (e.end !== null && e.end >= range.from && e.end <= range.to) {\n tips[`${e.id}:cleared`] = tip;\n lines.push({ name: `${e.id}:cleared`, xAxis: e.end, lineStyle: { color: neutral, type: 'dashed', width: 1 } });\n }\n }\n\n const tooltip = { trigger: 'item', formatter: (p: { name?: string }) => tips[p.name ?? ''] ?? '' };\n\n const decorate: MarkerPlan['decorate'] = (option, size) => {\n if (lines.length === 0 && areas.length === 0) return option;\n const base = option as { series?: unknown; xAxis?: object; legend?: object; textStyle?: { fontSize?: number }; grid?: { top?: unknown } };\n const top = typeof base.grid?.top === 'number' ? base.grid.top : 0;\n const series = Array.isArray(base.series) ? base.series : base.series ? [base.series] : [];\n const labelled = (size?.width ?? 0) >= LABEL_MIN_WIDTH && lines.length <= LABEL_MAX_LINES;\n const fontSize = base.textStyle?.fontSize ?? 10;\n const lineData = lines.map(({ labelText, ...line }) =>\n labelled && typeof labelText === 'string'\n ? { ...line, label: { show: true, position: 'insideEndTop', rotate: 90, color: neutral, fontSize, formatter: labelText } }\n : { ...line, label: { show: false } },\n );\n return {\n ...option,\n xAxis: { ...base.xAxis, min: range.from, max: range.to },\n // The legend lists the readings, not the helper series that carries the markers.\n ...(base.legend ? { legend: { ...base.legend, data: series.map((s: { name?: string }) => s.name) } } : {}),\n series: [\n ...series,\n {\n type: 'line',\n name: '',\n data: [],\n silent: false,\n z: 1,\n markArea: { silent: false, animation: false, label: { show: false }, tooltip, data: [...areas, ...strips.map((strip) => strip(top))] },\n markLine: { silent: false, animation: false, symbol: 'none', tooltip, data: lineData },\n },\n ],\n };\n };\n\n return { shown, hidden, decorate };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The words the chart's event markers print: the tooltip and list text, and the notes under the\n// chart. The widget packages carry no i18n runtime, so this is the one small per-language table\n// (modelled on aggregate-strings.ts). Option labels are translated in the console's catalogs.\n\nimport { viewerLocale } from '@devicechain/widget-sdk';\n\nexport type EventKind = 'alarm' | 'command';\n\nexport interface EventMarkerStrings {\n // The disclosure's summary, with the number of events listed.\n events: (count: number) => string;\n // The disclosure summary of the alarms a series-scoped chart does not draw.\n otherAlarms: (count: number) => string;\n // Said of an alarm in that list when it names no measurement.\n noMeasurement: string;\n // Said of an alarm in that list that is about another measurement.\n aboutMeasurement: (name: string) => string;\n alarm: string;\n command: string;\n severity: Record<string, string>;\n raised: (time: string) => string;\n cleared: (time: string) => string;\n stillActive: string;\n sent: (time: string) => string;\n // A command's delivery status, where it has settled into one; otherwise the raw status.\n outcome: Record<string, string>;\n // Events in the range beyond the cap, exact.\n more: (count: number) => string;\n // A channel the source cannot supply (a recording without it) or failed to read.\n notInRecording: Record<EventKind, string>;\n failed: Record<EventKind, string>;\n}\n\nexport const EVENT_MARKER_STRINGS: Record<'en' | 'es', EventMarkerStrings> = {\n en: {\n events: (n) => `Events in this range (${n})`,\n otherAlarms: (n) => `Other alarms on this device (${n})`,\n noMeasurement: 'no measurement known',\n aboutMeasurement: (m) => `about ${m}`,\n alarm: 'Alarm',\n command: 'Command',\n severity: { CRITICAL: 'Critical', MAJOR: 'Major', MINOR: 'Minor', WARNING: 'Warning', INDETERMINATE: 'Indeterminate' },\n raised: (t) => `raised ${t}`,\n cleared: (t) => `cleared ${t}`,\n stillActive: 'still active',\n sent: (t) => `sent ${t}`,\n outcome: {\n SUCCESSFUL: 'succeeded',\n FAILED: 'failed',\n TIMEOUT: 'timed out',\n EXPIRED: 'expired',\n CANCELLED: 'cancelled',\n QUEUED: 'queued',\n SENT: 'sent, awaiting reply',\n HELD: 'held',\n PARKED: 'parked',\n },\n more: (n) => `${n} more not shown`,\n notInRecording: { alarm: 'Alarm markers: not in this recording', command: 'Command markers: not in this recording' },\n failed: { alarm: 'Alarm markers could not be loaded', command: 'Command markers could not be loaded' },\n },\n es: {\n events: (n) => `Eventos en este intervalo (${n})`,\n otherAlarms: (n) => `Otras alarmas de este dispositivo (${n})`,\n noMeasurement: 'medici\u00F3n desconocida',\n aboutMeasurement: (m) => `sobre ${m}`,\n alarm: 'Alarma',\n command: 'Comando',\n severity: { CRITICAL: 'Cr\u00EDtica', MAJOR: 'Mayor', MINOR: 'Menor', WARNING: 'Advertencia', INDETERMINATE: 'Indeterminada' },\n raised: (t) => `activada ${t}`,\n cleared: (t) => `despejada ${t}`,\n stillActive: 'sigue activa',\n sent: (t) => `enviado ${t}`,\n outcome: {\n SUCCESSFUL: 'correcto',\n FAILED: 'fallido',\n TIMEOUT: 'sin respuesta a tiempo',\n EXPIRED: 'caducado',\n CANCELLED: 'cancelado',\n QUEUED: 'en cola',\n SENT: 'enviado, esperando respuesta',\n HELD: 'retenido',\n PARKED: 'en espera',\n },\n more: (n) => `${n} m\u00E1s sin mostrar`,\n notInRecording: { alarm: 'Marcadores de alarmas: no est\u00E1n en esta grabaci\u00F3n', command: 'Marcadores de comandos: no est\u00E1n en esta grabaci\u00F3n' },\n failed: { alarm: 'No se pudieron cargar los marcadores de alarmas', command: 'No se pudieron cargar los marcadores de comandos' },\n },\n};\n\nexport function eventMarkerStrings(locale: string | undefined = viewerLocale()): EventMarkerStrings {\n return locale?.toLowerCase().split('-')[0] === 'es' ? EVENT_MARKER_STRINGS.es : EVENT_MARKER_STRINGS.en;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChunkLoadingFrame,
|
|
3
|
+
lazyChunk
|
|
4
|
+
} from "./chunk-BMFQOTYG.js";
|
|
1
5
|
import {
|
|
2
6
|
measurementSpan
|
|
3
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-JFH4QMLA.js";
|
|
4
8
|
import {
|
|
5
9
|
buildFleet,
|
|
6
|
-
fleetStrings,
|
|
7
10
|
newestMemberTime,
|
|
8
11
|
seedWindow
|
|
9
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-62CHYCHI.js";
|
|
13
|
+
import {
|
|
14
|
+
fleetStrings
|
|
15
|
+
} from "./chunk-QMGKB6J3.js";
|
|
10
16
|
import "./chunk-BRH66T5F.js";
|
|
11
17
|
import {
|
|
12
18
|
LevelEmpty
|
|
@@ -116,7 +122,7 @@ var FLUSH_MS = 250;
|
|
|
116
122
|
var AGE_OUT_MS = 3e4;
|
|
117
123
|
var RESEED_SETTLE_MS = 2e3;
|
|
118
124
|
var MINUTE_MS2 = 6e4;
|
|
119
|
-
var emptyStore = (key) => ({ key, roster: null, live: /* @__PURE__ */ new Map(), seeds: /* @__PURE__ */ new Map(), forbidden: false, error: null });
|
|
125
|
+
var emptyStore = (key) => ({ key, roster: null, live: /* @__PURE__ */ new Map(), seeds: /* @__PURE__ */ new Map(), declared: /* @__PURE__ */ new Map(), forbidden: false, error: null });
|
|
120
126
|
function record(into, sample) {
|
|
121
127
|
let byName = into.get(sample.deviceToken);
|
|
122
128
|
if (!byName) into.set(sample.deviceToken, byName = /* @__PURE__ */ new Map());
|
|
@@ -127,15 +133,23 @@ function record(into, sample) {
|
|
|
127
133
|
function GroupRefusal({ widget, descriptor }) {
|
|
128
134
|
const strings = fleetStrings();
|
|
129
135
|
const perDevice = descriptor.binding.members !== void 0;
|
|
130
|
-
return /* @__PURE__ */ jsx(WidgetFrame, { title: optString(widget.options, "title"), children: /* @__PURE__ */ jsx("div", { "data-group-refused": "", style: { display: "contents" }, children: /* @__PURE__ */ jsx(LevelEmpty, { message: `${strings.oneDevice} ${perDevice ? strings.usePerDevice : strings.useFleetTable}` }) }) });
|
|
136
|
+
return /* @__PURE__ */ jsx(WidgetFrame, { title: optString(widget.options, "title"), children: /* @__PURE__ */ jsx("div", { "data-group-refused": "", style: { display: "contents" }, children: /* @__PURE__ */ jsx(LevelEmpty, { message: `${strings.oneDevice} ${perDevice ? comparesMembers(descriptor) ? strings.useLinePerDevice : strings.usePerDevice : strings.useFleetTable}` }) }) });
|
|
131
137
|
}
|
|
132
138
|
function shownMeasurements(widget) {
|
|
133
139
|
const one = widget.options?.measurement;
|
|
134
140
|
const named = [...widget.datasource?.measurements ?? [], ...typeof one === "string" && one !== "" ? [one] : []];
|
|
135
141
|
return [...new Set(named)];
|
|
136
142
|
}
|
|
143
|
+
function comparesMembers(descriptor) {
|
|
144
|
+
return descriptor.options.maxSeries !== void 0;
|
|
145
|
+
}
|
|
146
|
+
var CompareFeed = lazyChunk(() => import("./compare-feed-AY3CVKUZ.js")).component("default", void 0, ChunkLoadingFrame);
|
|
137
147
|
function FleetEntry(props) {
|
|
138
|
-
|
|
148
|
+
if (props.refuse) return /* @__PURE__ */ jsx(GroupRefusal, { widget: props.widget, descriptor: props.descriptor });
|
|
149
|
+
if (comparesMembers(props.descriptor)) {
|
|
150
|
+
return /* @__PURE__ */ jsx(CompareFeed, { hub: props.hub, widget: props.widget, descriptor: props.descriptor, ErrorFrame: props.ErrorFrame });
|
|
151
|
+
}
|
|
152
|
+
return /* @__PURE__ */ jsx(FleetFeed, { ...props });
|
|
139
153
|
}
|
|
140
154
|
function FleetFeed({ hub, widget, descriptor, Component, actions, ErrorFrame }) {
|
|
141
155
|
const range = useWidgetTimeRange();
|
|
@@ -152,6 +166,7 @@ function FleetFeed({ hub, widget, descriptor, Component, actions, ErrorFrame })
|
|
|
152
166
|
const pausedRef = useRef(pausedAt !== void 0);
|
|
153
167
|
const [version, bump] = useReducer((n) => n + 1, 0);
|
|
154
168
|
const [rosterKey, setRosterKey] = useState(null);
|
|
169
|
+
const generation = useRef(0);
|
|
155
170
|
const flushTimer = useRef(void 0);
|
|
156
171
|
const [resumes, resumed] = useReducer((n) => n + 1, 0);
|
|
157
172
|
const boundaryTimer = useRef(void 0);
|
|
@@ -168,11 +183,13 @@ function FleetFeed({ hub, widget, descriptor, Component, actions, ErrorFrame })
|
|
|
168
183
|
queued.current = /* @__PURE__ */ new Map();
|
|
169
184
|
setRosterKey(null);
|
|
170
185
|
bump();
|
|
186
|
+
const gen = ++generation.current;
|
|
171
187
|
if (!ds || !supported) return;
|
|
172
188
|
const dispose = hub.subscribeMembers(ds, {
|
|
173
189
|
roster: (roster) => {
|
|
174
190
|
s.roster = roster;
|
|
175
|
-
setRosterKey(
|
|
191
|
+
setRosterKey(`${gen}
|
|
192
|
+
${roster.members.map((m) => m.token).join("\n")}`);
|
|
176
193
|
schedule();
|
|
177
194
|
},
|
|
178
195
|
next: (sample) => {
|
|
@@ -237,6 +254,26 @@ function FleetFeed({ hub, widget, descriptor, Component, actions, ErrorFrame })
|
|
|
237
254
|
cancelled = true;
|
|
238
255
|
};
|
|
239
256
|
}, [hub, supported, dsKey, rosterKey, spanSeconds, pausedAt, namesKey, resumes]);
|
|
257
|
+
useEffect(() => {
|
|
258
|
+
const s = store.current;
|
|
259
|
+
if (!supported || !s.roster || rosterKey === null || !hub.declaredMetrics) return;
|
|
260
|
+
const types = [...new Set(s.roster.members.map((m) => m.deviceType).filter((t) => !!t))].filter((t) => !s.declared.has(t));
|
|
261
|
+
let cancelled = false;
|
|
262
|
+
for (const type of types) {
|
|
263
|
+
hub.declaredMetrics(type).then(
|
|
264
|
+
(names2) => {
|
|
265
|
+
if (cancelled) return;
|
|
266
|
+
s.declared.set(type, names2);
|
|
267
|
+
schedule();
|
|
268
|
+
},
|
|
269
|
+
() => {
|
|
270
|
+
}
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
return () => {
|
|
274
|
+
cancelled = true;
|
|
275
|
+
};
|
|
276
|
+
}, [hub, supported, dsKey, rosterKey]);
|
|
240
277
|
useEffect(() => {
|
|
241
278
|
if (pausedAt !== void 0) return;
|
|
242
279
|
const id = setInterval(bump, AGE_OUT_MS);
|
|
@@ -249,6 +286,7 @@ function FleetFeed({ hub, widget, descriptor, Component, actions, ErrorFrame })
|
|
|
249
286
|
roster: s.roster,
|
|
250
287
|
seeds: s.seeds,
|
|
251
288
|
live: s.live,
|
|
289
|
+
declared: s.declared,
|
|
252
290
|
since: (pausedAt ?? now()) - spanSeconds * 1e3,
|
|
253
291
|
forbidden: s.forbidden
|
|
254
292
|
});
|
|
@@ -266,7 +304,8 @@ export {
|
|
|
266
304
|
FLUSH_MS,
|
|
267
305
|
GroupRefusal,
|
|
268
306
|
RESEED_SETTLE_MS,
|
|
307
|
+
comparesMembers,
|
|
269
308
|
FleetEntry as default,
|
|
270
309
|
shownMeasurements
|
|
271
310
|
};
|
|
272
|
-
//# sourceMappingURL=fleet-feed-
|
|
311
|
+
//# sourceMappingURL=fleet-feed-2SKDR6ZQ.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/fleet-feed.tsx", "../../src/fleet-seed.ts"],
|
|
4
|
+
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The data feed of a fleet widget (one row or one bar per member of an anchor): the roster, each\n// member's own live readings, and one seed read per member for what it last reported before the\n// board was opened. Loaded on first use (connected-widget.tsx imports it dynamically), so a board\n// with no fleet widget never downloads it.\n//\n// - ONE subscription per widget (hub.subscribeMembers). The samples it delivers carry their\n// device, and everything here is keyed by device and measurement together -- the measurement\n// channel's name-only `latest` is exactly what this exists to get away from;\n// - the seed is read per member, bounded hub-wide and shared between widgets that ask the same\n// question; what is read is fleet-seed.ts's plan (the last few minutes first, the span only for\n// what that left empty). The window is the widget's pinned span, else the board's, ending at the\n// freeze instant while paused;\n// - what each device's profile DECLARES is read once per distinct device type (hub.declaredMetrics),\n// and is what tells \"does not report this\" from \"not reporting\". A lookup that fails or has not\n// landed leaves the declaration unknown, which counts the device as declared;\n// - paused, live readings queue (newest per device and measurement) and merge on resume, so the\n// table on screen does not move under a \"Paused\" badge;\n// - a change of span re-reads the seeds; a reading older than the span is no longer shown;\n// - a member stream that resumes after an interruption re-reads the seeds, at once and again just\n// after the next minute boundary: the readings sent during the outage were never delivered, and\n// a seed window ends on a minute boundary, so only the second read reaches the outage's last\n// minute. A re-read that fails keeps the seed it would have replaced.\n//\n// Rebuilding the whole state per sample would re-render a 500-row table hundreds of times a\n// second, so changes are folded into a store and flushed at most once per FLUSH_MS.\n\nimport {\n DEFAULT_TIME_RANGE_SECONDS,\n type FleetState,\n type MeasurementSample,\n type MeasurementStreamState,\n type MemberRoster,\n type MemberSeed,\n type WidgetActions,\n type WidgetDataSource,\n type WidgetDescriptor,\n type WidgetInstance,\n} from '@devicechain/dashboards';\nimport { optString, resolveStaleAfterSeconds, useWidgetTimeRange, WidgetFrame, WidgetFreshnessProvider } from '@devicechain/widget-sdk';\nimport { useEffect, useMemo, useReducer, useRef, useState, type ComponentType } from 'react';\n\nimport { measurementSpan } from './binding-policy';\nimport type { CompareFeedProps } from './compare-feed';\nimport { buildFleet, newestMemberTime, seedWindow } from './fleet-model';\nimport { memberSeedPlan } from './fleet-seed';\nimport { fleetStrings } from './fleet-strings';\nimport { LevelEmpty } from './level-readout';\nimport { ChunkLoadingFrame, lazyChunk } from './widgets/lazy-chunk';\n\n// The most often the state is rebuilt from live samples.\nexport const FLUSH_MS = 250;\n// How often a reading that has aged out of the span is dropped when nothing else changes.\nconst AGE_OUT_MS = 30_000;\n// How long past a minute boundary the second re-read after an interruption waits, so the minute\n// that just closed is in the store's one-minute summaries before it is read.\nexport const RESEED_SETTLE_MS = 2_000;\nconst MINUTE_MS = 60_000;\n\ninterface Store {\n // The selector this store was filled for: a render that carries a new selector must not show the\n // previous fleet's rows under it while the effect that resets the store has yet to run.\n key: string | null;\n roster: MemberRoster | null;\n live: Map<string, Map<string, MeasurementSample>>;\n seeds: Map<string, MemberSeed | 'failed'>;\n // What each device type declares, by type token, as the lookups settle.\n declared: Map<string, readonly string[] | null>;\n forbidden: boolean;\n error: unknown | null;\n}\n\nconst emptyStore = (key: string | null): Store => ({ key, roster: null, live: new Map(), seeds: new Map(), declared: new Map(), forbidden: false, error: null });\n\nfunction record(into: Map<string, Map<string, MeasurementSample>>, sample: MeasurementSample): void {\n let byName = into.get(sample.deviceToken);\n if (!byName) into.set(sample.deviceToken, (byName = new Map()));\n // Newest wins; an out-of-order older sample must not roll a cell back.\n const held = byName.get(sample.name);\n if (held?.occurredTime && sample.occurredTime && Date.parse(sample.occurredTime) < Date.parse(held.occurredTime)) return;\n byName.set(sample.name, sample);\n}\n\nexport interface FleetFeedProps {\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 // Not a fleet widget, but bound to a group: say so instead (GroupRefusal).\n refuse?: boolean;\n}\n\n// What a widget that shows ONE device's readings keyed by measurement name renders when it is bound\n// to a group: every device reporting \"temperature\" would overwrite the others in it, so it shows a\n// sentence instead of whichever device spoke last. A widget with a per-device mode (the bar chart's\n// one bar per device) is pointed at that mode; any other at the fleet table.\nexport function GroupRefusal({ widget, descriptor }: { widget: WidgetInstance; descriptor: WidgetDescriptor }) {\n const strings = fleetStrings();\n const perDevice = (descriptor.binding as { members?: unknown }).members !== undefined;\n return (\n <WidgetFrame title={optString(widget.options, 'title')}>\n <div data-group-refused=\"\" style={{ display: 'contents' }}>\n <LevelEmpty message={`${strings.oneDevice} ${perDevice ? (comparesMembers(descriptor) ? strings.useLinePerDevice : strings.usePerDevice) : strings.useFleetTable}`} />\n </div>\n </WidgetFrame>\n );\n}\n\n// The measurements a fleet widget shows, and so the ones its seed must find AND its live feed must\n// carry: the datasource's, and the `measurement` option a per-device bar chart compares. Empty: every\n// measurement reported. One list for both, because the hub's live feed delivers only the names its\n// selector lists: a bar chart comparing a measurement its datasource does not name would otherwise\n// be seeded with it and then never hear of it again.\nexport function shownMeasurements(widget: WidgetInstance): string[] {\n const one = widget.options?.measurement;\n const named = [...(widget.datasource?.measurements ?? []), ...(typeof one === 'string' && one !== '' ? [one] : [])];\n return [...new Set(named)];\n}\n\n// A members-mode widget that draws a line per member over the span (the time-series chart set to one\n// line per device) reads history, not a latest cell, so it has its own feed. The descriptor says so by\n// declaring the option that limits those lines; the widget id is never consulted.\nexport function comparesMembers(descriptor: WidgetDescriptor): boolean {\n return (descriptor.options as Record<string, unknown>).maxSeries !== undefined;\n}\n\n// Its own chunk inside this one: a board with a fleet table but no compare chart never loads it.\nconst CompareFeed = lazyChunk<typeof import('./compare-feed')>(() => import('./compare-feed')).component<never, CompareFeedProps>('default', undefined, ChunkLoadingFrame);\n\nexport default function FleetEntry(props: FleetFeedProps) {\n if (props.refuse) return <GroupRefusal widget={props.widget} descriptor={props.descriptor} />;\n if (comparesMembers(props.descriptor)) {\n return <CompareFeed hub={props.hub} widget={props.widget} descriptor={props.descriptor} ErrorFrame={props.ErrorFrame} />;\n }\n return <FleetFeed {...props} />;\n}\n\nfunction FleetFeed({ hub, widget, descriptor, Component, actions, ErrorFrame }: FleetFeedProps) {\n const range = useWidgetTimeRange();\n const now = useMemo(() => (hub.now ? () => hub.now!() : Date.now), [hub]);\n const spanSeconds = measurementSpan(descriptor, widget, range?.spanSeconds) ?? range?.spanSeconds ?? DEFAULT_TIME_RANGE_SECONDS;\n const pausedAt = range?.paused ? range.pausedAt : undefined;\n const names = shownMeasurements(widget);\n const ds = widget.datasource && { ...widget.datasource, measurements: names };\n const dsKey = JSON.stringify(ds ?? null);\n const namesKey = JSON.stringify(names);\n const supported = hub.subscribeMembers !== undefined && hub.readMemberSeeds !== undefined;\n\n const store = useRef<Store>(emptyStore(dsKey));\n // Live samples that arrive while paused, newest per device and measurement.\n const queued = useRef<Map<string, Map<string, MeasurementSample>>>(new Map());\n const pausedRef = useRef(pausedAt !== undefined);\n const [version, bump] = useReducer((n: number) => n + 1, 0);\n // Which roster the seeds and declarations were read for: the subscription's generation and the\n // member tokens. The generation is what makes a NEW subscription's roster a new key even when its\n // members are the same (board-replay builds a new source after every seek, over the same\n // devices). By tokens alone, the reset to null and the new roster could land in one batch, React\n // saw no change, and the new store's seeds and declarations were never read: after a seek every\n // cell a live reading had not filled stayed \"loading\".\n const [rosterKey, setRosterKey] = useState<string | null>(null);\n const generation = useRef(0);\n const flushTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n // Re-reads asked for by a member stream that resumed; the seeds effect re-runs on each.\n const [resumes, resumed] = useReducer((n: number) => n + 1, 0);\n const boundaryTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n const schedule = (): void => {\n if (flushTimer.current !== undefined) return;\n flushTimer.current = setTimeout(() => {\n flushTimer.current = undefined;\n bump();\n }, FLUSH_MS);\n };\n\n // The subscription: the roster, then the live samples. A new selector starts from nothing.\n useEffect(() => {\n const s = emptyStore(dsKey);\n store.current = s;\n queued.current = new Map();\n setRosterKey(null);\n bump();\n const gen = ++generation.current;\n if (!ds || !supported) return;\n const dispose = hub.subscribeMembers!(ds, {\n roster: (roster) => {\n s.roster = roster;\n setRosterKey(`${gen}\\n${roster.members.map((m) => m.token).join('\\n')}`);\n schedule();\n },\n next: (sample) => {\n record(pausedRef.current ? queued.current : s.live, sample);\n if (!pausedRef.current) schedule();\n },\n forbidden: () => {\n s.forbidden = true;\n schedule();\n },\n error: (err) => {\n s.error = err;\n schedule();\n },\n // Every member stream on the socket resumes at once; the reducer folds them into one render,\n // and one boundary read covers them all.\n reconnected: () => {\n resumed();\n if (boundaryTimer.current !== undefined) return;\n const at = now();\n boundaryTimer.current = setTimeout(() => {\n boundaryTimer.current = undefined;\n resumed();\n }, MINUTE_MS - (at % MINUTE_MS) + RESEED_SETTLE_MS);\n },\n });\n return () => {\n dispose();\n if (flushTimer.current !== undefined) clearTimeout(flushTimer.current);\n flushTimer.current = undefined;\n if (boundaryTimer.current !== undefined) clearTimeout(boundaryTimer.current);\n boundaryTimer.current = undefined;\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 // The pause gate: resuming merges what queued while frozen.\n useEffect(() => {\n const was = pausedRef.current;\n pausedRef.current = pausedAt !== undefined;\n if (was && pausedAt === undefined) {\n for (const byName of queued.current.values()) for (const sample of byName.values()) record(store.current.live, sample);\n queued.current = new Map();\n schedule();\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [pausedAt]);\n\n // The seeds: one read per member for the window, again when the span or the freeze instant changes.\n useEffect(() => {\n const s = store.current;\n if (!supported || !s.roster || rosterKey === null) return;\n const tokens = s.roster.members.map((m) => m.token);\n if (tokens.length === 0) return;\n let cancelled = false;\n void hub.readMemberSeeds!(\n tokens,\n memberSeedPlan(seedWindow(spanSeconds, pausedAt ?? now()), names),\n (token, seed, failure) => {\n if (failure === 'forbidden') s.forbidden = true;\n else if (seed) s.seeds.set(token, seed);\n // A re-read that failed keeps the seed it would have replaced: that reading is still true.\n else if (s.seeds.get(token) === undefined) s.seeds.set(token, 'failed');\n schedule();\n },\n () => cancelled,\n );\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hub, supported, dsKey, rosterKey, spanSeconds, pausedAt, namesKey, resumes]);\n\n // The declarations: one lookup per distinct device type in the roster, however many devices share it.\n useEffect(() => {\n const s = store.current;\n if (!supported || !s.roster || rosterKey === null || !hub.declaredMetrics) return;\n const types = [...new Set(s.roster.members.map((m) => m.deviceType).filter((t): t is string => !!t))].filter((t) => !s.declared.has(t));\n let cancelled = false;\n for (const type of types) {\n hub.declaredMetrics(type).then(\n (names) => {\n if (cancelled) return;\n s.declared.set(type, names);\n schedule();\n },\n () => {\n // Unknown stays unknown: the members are counted as declared.\n },\n );\n }\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hub, supported, dsKey, rosterKey]);\n\n // A reading ages out of the span with no event to say so.\n useEffect(() => {\n if (pausedAt !== undefined) return;\n const id = setInterval(bump, AGE_OUT_MS);\n return () => clearInterval(id);\n }, [pausedAt]);\n\n const members = useMemo<FleetState>(() => {\n const s = store.current.key === dsKey ? store.current : emptyStore(dsKey);\n if (!supported) return { status: 'unsupported', members: [], total: 0, truncated: false };\n return buildFleet({\n roster: s.roster,\n seeds: s.seeds,\n live: s.live,\n declared: s.declared,\n since: (pausedAt ?? now()) - spanSeconds * 1000,\n forbidden: s.forbidden,\n });\n // `version` is the store's change counter; the store itself is mutable.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [version, supported, dsKey, spanSeconds, pausedAt, now]);\n\n const error = store.current.key === dsKey ? store.current.error : null;\n const data: MeasurementStreamState = { latest: {}, samples: [], error, members };\n\n // Opt-in through the descriptor, as for any measurement widget. The frame's age is the newest\n // reading anywhere in the fleet; a widget that shows ages per row takes them from the members.\n const freshness = useMemo(\n () =>\n descriptor.freshness === true\n ? { at: newestMemberTime(members), staleAfterSeconds: resolveStaleAfterSeconds(widget.options) }\n : undefined,\n [descriptor, widget.options, members],\n );\n if (error) return <ErrorFrame widget={widget} error={error} />;\n return (\n <WidgetFreshnessProvider freshness={freshness}>\n <Component widget={widget} data={data} actions={actions} />\n </WidgetFreshnessProvider>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// What a fleet widget reads to fill its cells before the live feed has, and how it makes a seed of\n// the answer. Loaded with the fleet feed (a lazy chunk), never by a board without a fleet widget.\n//\n// A fleet of 500 is 500 devices to ask, so the cost per device is what matters. The obvious read --\n// every measurement of the device over the whole span in the span's buckets -- returns up to 240\n// buckets per measurement per device (a megabyte or more per device on a long span, tens of\n// megabytes per viewer) to find ONE number per cell. The plan instead asks the question the cell\n// asks, cheapest first:\n//\n// 1. THE LAST FEW MINUTES (TAIL_SECONDS, in one-minute buckets): at most five rows per measurement,\n// read from the store's one-minute summaries. A device that is reporting -- the ordinary case,\n// and the expensive one, because a busy device is the one with the most history -- is done here,\n// with its newest minute.\n// 2. Only for what that left empty, the whole span. One read PER missing measurement, narrowed to\n// that name (the store aggregates one name or all), when the device did report in the tail --\n// which is also what tells \"does not report this\" from \"sent nothing\", so the narrow read loses\n// nothing. Otherwise (a quiet device, or more missing names than are worth a read each) one read\n// of every measurement over the span, as before.\n//\n// A table that names no columns gets the span read alone: its columns ARE the names its devices\n// reported in the span, which only a read of the whole span can list.\n//\n// 3. A value a span read found is the AVERAGE of a span bucket (42 minutes wide on a week), not the\n// device's latest reading: a machine that read 14, was refuelled to 40 and then parked would\n// show 27. So the newest span bucket is read again in one-minute buckets -- at most a bucket's\n// worth of rows -- and its newest minute is what the cell shows, as for a device seen in the\n// tail. Names whose newest buckets coincide share one such read, and a device gets at most\n// MAX_NARROW_READS of them, newest bucket first. A name left past that bound is not shown\n// rather than shown as an average (an average is never the latest reading), and is marked\n// `unread`: the device has data for it, so its cell says \"not loaded\", never \"no data\".\n//\n// Ages. The store returns a bucket's start, not the time of its newest reading, so a seeded reading is\n// stamped at the start of its bucket (or of the span, when the bucket began before it). The reading\n// happened at or after that instant, so its age is OVERSTATED, never understated: by less than a\n// minute for a device seen in the tail, by up to one span bucket (bucketSecondsFor) otherwise. The\n// first live reading replaces the stamp with the real time.\n\nimport type {\n HistoryWindow,\n MeasurementBucket,\n MeasurementSample,\n MemberBucketCriteria,\n MemberSeed,\n MemberSeedPlan,\n} from '@devicechain/dashboards';\n\nconst MINUTE_MS = 60_000;\n\n// The tail read first: long enough to catch a device that reports every minute or two, short enough\n// that it is a handful of rows per measurement.\nexport const TAIL_SECONDS = 300;\n\n// The most narrowed (one-name) span reads one device gets before a single read of every name is the\n// cheaper question.\nexport const MAX_NARROW_READS = 2;\n\nconst has = (o: object, key: string): boolean => Object.prototype.hasOwnProperty.call(o, key);\n\n// foldMemberSeed turns one device's buckets into its seed: the newest bucket with a mean wins per\n// name, stamped at max(bucket start, `notBefore`) (see \"Ages\" above). A name whose buckets hold\n// readings is observed even when none has a mean.\nexport function foldMemberSeed(deviceToken: string, buckets: readonly MeasurementBucket[], notBefore?: string): MemberSeed {\n const floor = notBefore === undefined ? Number.NaN : Date.parse(notBefore);\n const latest: Record<string, MeasurementSample> = {};\n const at: Record<string, number> = {};\n const observed = new Set<string>();\n for (const b of buckets) {\n if (b.count === undefined ? b.avg != null : b.count > 0) observed.add(b.name);\n if (b.avg == null) continue;\n const start = Date.parse(b.bucketStart);\n if (has(at, b.name) && at[b.name] >= start) continue;\n at[b.name] = start;\n latest[b.name] = {\n id: `seed-${deviceToken}-${b.name}-${b.bucketStart}`,\n deviceToken,\n eventType: 0,\n occurredTime: start < floor ? new Date(floor).toISOString() : b.bucketStart,\n name: b.name,\n value: b.avg,\n classifier: null,\n };\n }\n return { latest, observed: [...observed] };\n}\n\n// Two seeds of one device: `newer` (read over a later stretch) wins per name; observed is the union.\nexport function mergeSeeds(newer: MemberSeed, older: MemberSeed): MemberSeed {\n return {\n latest: { ...older.latest, ...newer.latest },\n observed: [...new Set([...newer.observed, ...older.observed])],\n };\n}\n\n// The tail of a window: its last TAIL_SECONDS, on whole minutes, in one-minute buckets. Undefined\n// when the window is no longer than that (the window itself is then the cheap read).\nexport function tailWindow(window: HistoryWindow): HistoryWindow | undefined {\n const start = Math.floor((Date.parse(window.endTime) + 1) / MINUTE_MS) * MINUTE_MS - TAIL_SECONDS * 1000;\n if (!(start > Date.parse(window.startTime))) return undefined;\n return { startTime: new Date(start).toISOString(), endTime: window.endTime, intervalSeconds: 60 };\n}\n\n// The plan a fleet widget hands the hub: the window is the widget's span, `names` the measurements\n// it shows (empty: every measurement its devices report).\nexport function memberSeedPlan(window: HistoryWindow, names: readonly string[]): MemberSeedPlan {\n const wanted = [...new Set(names)];\n return {\n async seed(deviceToken, read) {\n const fold = (c: MemberBucketCriteria) => read(c).then((b) => foldMemberSeed(deviceToken, b, c.startTime));\n const tail = tailWindow(window);\n let seed: MemberSeed;\n let recent: MemberSeed | undefined;\n if (wanted.length === 0 || !tail) seed = await fold(window);\n else {\n recent = await fold(tail);\n const missing = wanted.filter((name) => !has(recent!.latest, name));\n // Nothing missing falls through the loop below with no read: the tail was the whole answer.\n if (recent.observed.length === 0 || missing.length > MAX_NARROW_READS) seed = mergeSeeds(recent, await fold(window));\n else {\n seed = recent;\n for (const name of missing) seed = mergeSeeds(seed, await fold({ ...window, name }));\n }\n }\n return window.intervalSeconds > 60 ? sharpen(seed, recent, window, fold) : seed;\n },\n };\n}\n\n// Step 3 above: replaces each value a span read found (any not from the tail's minutes) with the\n// newest minute of its span bucket. A span value is stamped at its bucket's start (or the window's),\n// so the bucket's readings lie between that stamp and one bucket width after it.\nasync function sharpen(\n seed: MemberSeed,\n recent: MemberSeed | undefined,\n window: HistoryWindow,\n fold: (c: MemberBucketCriteria) => Promise<MemberSeed>,\n): Promise<MemberSeed> {\n const byStamp = new Map<number, string[]>();\n for (const [name, sample] of Object.entries(seed.latest)) {\n if (recent && has(recent.latest, name)) continue;\n const at = Date.parse(sample.occurredTime ?? '');\n byStamp.set(at, [...(byStamp.get(at) ?? []), name]);\n }\n const latest = { ...seed.latest };\n const unread: string[] = [];\n const end = Date.parse(window.endTime) + 1;\n const stamps = [...byStamp.keys()].sort((a, b) => b - a);\n for (const [i, at] of stamps.entries()) {\n const names = byStamp.get(at)!;\n const minutes =\n i < MAX_NARROW_READS\n ? (\n await fold({\n startTime: new Date(at).toISOString(),\n endTime: new Date(Math.min(at + window.intervalSeconds * 1000, end) - 1).toISOString(),\n intervalSeconds: 60,\n ...(names.length === 1 ? { name: names[0] } : {}),\n })\n ).latest\n : {};\n for (const name of names) {\n if (has(minutes, name)) latest[name] = minutes[name];\n else {\n delete latest[name];\n unread.push(name);\n }\n }\n }\n return { latest, observed: seed.observed, unread };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["MINUTE_MS", "names"]
|
|
7
|
+
}
|