@flanksource/clicky-ui 0.2.3 → 0.2.5
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/dist/components/Combobox.cjs +291 -0
- package/dist/components/Combobox.cjs.map +1 -0
- package/dist/components/Combobox.d.ts +59 -0
- package/dist/components/Combobox.d.ts.map +1 -0
- package/dist/components/Combobox.js +291 -0
- package/dist/components/Combobox.js.map +1 -0
- package/dist/components/FilterBar.cjs +153 -124
- package/dist/components/FilterBar.cjs.map +1 -1
- package/dist/components/FilterBar.d.ts +25 -1
- package/dist/components/FilterBar.d.ts.map +1 -1
- package/dist/components/FilterBar.js +153 -124
- package/dist/components/FilterBar.js.map +1 -1
- package/dist/components/FormatOptionsDropdown.cjs +62 -0
- package/dist/components/FormatOptionsDropdown.cjs.map +1 -0
- package/dist/components/FormatOptionsDropdown.d.ts +37 -0
- package/dist/components/FormatOptionsDropdown.d.ts.map +1 -0
- package/dist/components/FormatOptionsDropdown.js +62 -0
- package/dist/components/FormatOptionsDropdown.js.map +1 -0
- package/dist/components/SplitButton.cjs +87 -0
- package/dist/components/SplitButton.cjs.map +1 -0
- package/dist/components/SplitButton.d.ts +32 -0
- package/dist/components/SplitButton.d.ts.map +1 -0
- package/dist/components/SplitButton.js +87 -0
- package/dist/components/SplitButton.js.map +1 -0
- package/dist/components/icon-menu-picker.cjs +14 -3
- package/dist/components/icon-menu-picker.cjs.map +1 -1
- package/dist/components/icon-menu-picker.d.ts.map +1 -1
- package/dist/components/icon-menu-picker.js +15 -4
- package/dist/components/icon-menu-picker.js.map +1 -1
- package/dist/components.cjs +7 -0
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.ts +3 -0
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +7 -0
- package/dist/components.js.map +1 -1
- package/dist/data/Badge.d.ts +1 -1
- package/dist/data/ProgressBar.cjs +4 -2
- package/dist/data/ProgressBar.cjs.map +1 -1
- package/dist/data/ProgressBar.d.ts +1 -1
- package/dist/data/ProgressBar.d.ts.map +1 -1
- package/dist/data/ProgressBar.js +4 -2
- package/dist/data/ProgressBar.js.map +1 -1
- package/dist/data/TaskManager.cjs +162 -0
- package/dist/data/TaskManager.cjs.map +1 -0
- package/dist/data/TaskManager.d.ts +21 -0
- package/dist/data/TaskManager.d.ts.map +1 -0
- package/dist/data/TaskManager.js +162 -0
- package/dist/data/TaskManager.js.map +1 -0
- package/dist/data/TaskProgress.cjs +125 -0
- package/dist/data/TaskProgress.cjs.map +1 -0
- package/dist/data/TaskProgress.d.ts +20 -0
- package/dist/data/TaskProgress.d.ts.map +1 -0
- package/dist/data/TaskProgress.js +125 -0
- package/dist/data/TaskProgress.js.map +1 -0
- package/dist/data/TaskSnapshot.cjs +23 -0
- package/dist/data/TaskSnapshot.cjs.map +1 -0
- package/dist/data/TaskSnapshot.d.ts +49 -0
- package/dist/data/TaskSnapshot.d.ts.map +1 -0
- package/dist/data/TaskSnapshot.js +23 -0
- package/dist/data/TaskSnapshot.js.map +1 -0
- package/dist/data/TimeseriesPanel.axes.cjs +39 -0
- package/dist/data/TimeseriesPanel.axes.cjs.map +1 -0
- package/dist/data/TimeseriesPanel.axes.d.ts +28 -0
- package/dist/data/TimeseriesPanel.axes.d.ts.map +1 -0
- package/dist/data/TimeseriesPanel.axes.js +39 -0
- package/dist/data/TimeseriesPanel.axes.js.map +1 -0
- package/dist/data/TimeseriesPanel.cjs +324 -0
- package/dist/data/TimeseriesPanel.cjs.map +1 -0
- package/dist/data/TimeseriesPanel.d.ts +141 -0
- package/dist/data/TimeseriesPanel.d.ts.map +1 -0
- package/dist/data/TimeseriesPanel.js +324 -0
- package/dist/data/TimeseriesPanel.js.map +1 -0
- package/dist/data/Tree.cjs.map +1 -1
- package/dist/data/Tree.d.ts.map +1 -1
- package/dist/data/Tree.js.map +1 -1
- package/dist/data/TreeNode.cjs +44 -7
- package/dist/data/TreeNode.cjs.map +1 -1
- package/dist/data/TreeNode.d.ts +19 -1
- package/dist/data/TreeNode.d.ts.map +1 -1
- package/dist/data/TreeNode.js +44 -7
- package/dist/data/TreeNode.js.map +1 -1
- package/dist/data/diagnostics/DiagnosticsDetailPanel.cjs +66 -44
- package/dist/data/diagnostics/DiagnosticsDetailPanel.cjs.map +1 -1
- package/dist/data/diagnostics/DiagnosticsDetailPanel.d.ts +21 -1
- package/dist/data/diagnostics/DiagnosticsDetailPanel.d.ts.map +1 -1
- package/dist/data/diagnostics/DiagnosticsDetailPanel.js +65 -43
- package/dist/data/diagnostics/DiagnosticsDetailPanel.js.map +1 -1
- package/dist/data/diagnostics/DiagnosticsTree.cjs +2 -1
- package/dist/data/diagnostics/DiagnosticsTree.cjs.map +1 -1
- package/dist/data/diagnostics/DiagnosticsTree.js +2 -1
- package/dist/data/diagnostics/DiagnosticsTree.js.map +1 -1
- package/dist/data/diagnostics/FrameSourceWindow.cjs +37 -0
- package/dist/data/diagnostics/FrameSourceWindow.cjs.map +1 -0
- package/dist/data/diagnostics/FrameSourceWindow.d.ts +15 -0
- package/dist/data/diagnostics/FrameSourceWindow.d.ts.map +1 -0
- package/dist/data/diagnostics/FrameSourceWindow.js +37 -0
- package/dist/data/diagnostics/FrameSourceWindow.js.map +1 -0
- package/dist/data/diagnostics/JvmStackTrace.cjs +25 -12
- package/dist/data/diagnostics/JvmStackTrace.cjs.map +1 -1
- package/dist/data/diagnostics/JvmStackTrace.d.ts +8 -1
- package/dist/data/diagnostics/JvmStackTrace.d.ts.map +1 -1
- package/dist/data/diagnostics/JvmStackTrace.js +26 -13
- package/dist/data/diagnostics/JvmStackTrace.js.map +1 -1
- package/dist/data/diagnostics/ThreadCard.cjs +9 -2
- package/dist/data/diagnostics/ThreadCard.cjs.map +1 -1
- package/dist/data/diagnostics/ThreadCard.d.ts +3 -1
- package/dist/data/diagnostics/ThreadCard.d.ts.map +1 -1
- package/dist/data/diagnostics/ThreadCard.js +9 -2
- package/dist/data/diagnostics/ThreadCard.js.map +1 -1
- package/dist/data/diagnostics/jvm-stacktrace.cjs +10 -1
- package/dist/data/diagnostics/jvm-stacktrace.cjs.map +1 -1
- package/dist/data/diagnostics/jvm-stacktrace.d.ts.map +1 -1
- package/dist/data/diagnostics/jvm-stacktrace.js +10 -1
- package/dist/data/diagnostics/jvm-stacktrace.js.map +1 -1
- package/dist/data/diagnostics/utils.cjs +0 -12
- package/dist/data/diagnostics/utils.cjs.map +1 -1
- package/dist/data/diagnostics/utils.d.ts +1 -1
- package/dist/data/diagnostics/utils.d.ts.map +1 -1
- package/dist/data/diagnostics/utils.js +0 -12
- package/dist/data/diagnostics/utils.js.map +1 -1
- package/dist/data/task-status.cjs +108 -0
- package/dist/data/task-status.cjs.map +1 -0
- package/dist/data/task-status.d.ts +21 -0
- package/dist/data/task-status.d.ts.map +1 -0
- package/dist/data/task-status.js +108 -0
- package/dist/data/task-status.js.map +1 -0
- package/dist/data.cjs +21 -0
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.ts +7 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +21 -0
- package/dist/data.js.map +1 -1
- package/dist/hooks/use-task-run.cjs +137 -0
- package/dist/hooks/use-task-run.cjs.map +1 -0
- package/dist/hooks/use-task-run.d.ts +35 -0
- package/dist/hooks/use-task-run.d.ts.map +1 -0
- package/dist/hooks/use-task-run.js +137 -0
- package/dist/hooks/use-task-run.js.map +1 -0
- package/dist/hooks.cjs +3 -0
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.ts +1 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +3 -0
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +37 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/color.cjs +33 -0
- package/dist/lib/color.cjs.map +1 -0
- package/dist/lib/color.d.ts +10 -0
- package/dist/lib/color.d.ts.map +1 -0
- package/dist/lib/color.js +33 -0
- package/dist/lib/color.js.map +1 -0
- package/dist/lib/format.cjs +77 -0
- package/dist/lib/format.cjs.map +1 -0
- package/dist/lib/format.d.ts +30 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +77 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/overlay/DropdownMenu.cjs +18 -2
- package/dist/overlay/DropdownMenu.cjs.map +1 -1
- package/dist/overlay/DropdownMenu.d.ts +3 -1
- package/dist/overlay/DropdownMenu.d.ts.map +1 -1
- package/dist/overlay/DropdownMenu.js +18 -2
- package/dist/overlay/DropdownMenu.js.map +1 -1
- package/dist/rpc/types.cjs.map +1 -1
- package/dist/rpc/types.d.ts +4 -0
- package/dist/rpc/types.d.ts.map +1 -1
- package/dist/rpc/types.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/utils.cjs +6 -0
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +6 -0
- package/dist/utils.js.map +1 -1
- package/package.json +4 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TaskSnapshot.js","sources":["../../src/data/TaskSnapshot.ts"],"sourcesContent":["// TaskSnapshot mirrors the Go `task.TaskSnapshot` wire format emitted by clicky's\n// JSONHandler / SSEHandler (github.com/flanksource/clicky/task/snapshot.go). Field\n// names match the JSON tags exactly. This module is generic — it carries no\n// application-specific concepts.\n\nexport interface LogEntry {\n level: string;\n message: string;\n}\n\nexport interface TaskSnapshot {\n id: string;\n name: string;\n /** \"task\" or \"group\". */\n type: \"task\" | \"group\";\n /** Parent group NAME (groups: empty). */\n group?: string;\n status: string;\n duration?: string;\n error?: string;\n /** Latest log line. */\n message?: string;\n logs?: LogEntry[];\n /** Group aggregates. */\n total?: number;\n completed?: number;\n failed?: number;\n running?: number;\n /** Stable parent-run id (tasks) or the run's own id (groups). */\n groupId?: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n /** RFC3339. */\n startedAt?: string;\n /** RFC3339. */\n finishedAt?: string;\n}\n\n// TaskRunMeta mirrors the Go `task.RunMeta` listing summary returned by\n// GET {prefix}/tasks. Named TaskRunMeta to avoid colliding with the unrelated\n// diagnostics `RunMeta` already exported by this package.\nexport interface TaskRunMeta {\n id: string;\n name: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n total: number;\n completed: number;\n failed: number;\n running: number;\n}\n\n// Terminal run/group statuses (not running/pending). Used to decide when an SSE\n// stream is done or when polling should stop.\nconst TERMINAL_STATUSES = new Set([\n \"success\",\n \"failed\",\n \"warning\",\n \"canceled\",\n \"PASS\",\n \"FAIL\",\n \"ERR\",\n \"SKIP\",\n]);\n\nexport function isTerminalStatus(status: string | undefined): boolean {\n return !!status && TERMINAL_STATUSES.has(status);\n}\n\n// allGroupsTerminal reports whether every group snapshot has reached a terminal\n// status — i.e. the run(s) are complete and an SSE stream / poll can stop.\nexport function allGroupsTerminal(snapshots: TaskSnapshot[]): boolean {\n const groups = snapshots.filter((s) => s.type === \"group\");\n if (groups.length === 0) return false;\n return groups.every((g) => isTerminalStatus(g.status));\n}\n"],"names":[],"mappings":"AA2DA,MAAM,wCAAwB,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,SAAS,iBAAiB,QAAqC;AACpE,SAAO,CAAC,CAAC,UAAU,kBAAkB,IAAI,MAAM;AACjD;AAIO,SAAS,kBAAkB,WAAoC;AACpE,QAAM,SAAS,UAAU,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO;AACzD,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAO,OAAO,MAAM,CAAC,MAAM,iBAAiB,EAAE,MAAM,CAAC;AACvD;"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const NO_UNIT = "\0__noUnit";
|
|
4
|
+
function assignAxes(series) {
|
|
5
|
+
const order = [];
|
|
6
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7
|
+
for (const s of series) {
|
|
8
|
+
const u = s.unit ?? NO_UNIT;
|
|
9
|
+
if (!seen.has(u)) {
|
|
10
|
+
seen.add(u);
|
|
11
|
+
order.push(u);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const unitToAxis = /* @__PURE__ */ new Map();
|
|
15
|
+
const axes = [];
|
|
16
|
+
order.forEach((u, i) => {
|
|
17
|
+
const orientation = i === 0 ? "left" : "right";
|
|
18
|
+
unitToAxis.set(u, orientation);
|
|
19
|
+
if (i <= 1) {
|
|
20
|
+
axes.push({ id: orientation, orientation, ...u !== NO_UNIT ? { unit: u } : {} });
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
const axisOfKey = /* @__PURE__ */ new Map();
|
|
24
|
+
for (const s of series) {
|
|
25
|
+
axisOfKey.set(s.key, unitToAxis.get(s.unit ?? NO_UNIT) ?? "left");
|
|
26
|
+
}
|
|
27
|
+
return { axes, axisOfKey };
|
|
28
|
+
}
|
|
29
|
+
function latestOf(rows, key) {
|
|
30
|
+
var _a;
|
|
31
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
32
|
+
const v = (_a = rows[i]) == null ? void 0 : _a[key];
|
|
33
|
+
if (typeof v === "number") return v;
|
|
34
|
+
}
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
exports.assignAxes = assignAxes;
|
|
38
|
+
exports.latestOf = latestOf;
|
|
39
|
+
//# sourceMappingURL=TimeseriesPanel.axes.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimeseriesPanel.axes.cjs","sources":["../../src/data/TimeseriesPanel.axes.ts"],"sourcesContent":["import type { MergedRow, ResolvedSeries } from \"./TimeseriesPanel\";\n\nexport type AxisOrientation = \"left\" | \"right\";\n\n/** A rendered Y-axis: its recharts id (== orientation) and the unit it formats with. */\nexport interface AxisSpec {\n id: AxisOrientation;\n orientation: AxisOrientation;\n /** Unit the axis ticks format with; undefined for a unitless axis. */\n unit?: string;\n}\n\nexport interface AxisAssignment {\n /** 1 or 2 axes in render order (left first). */\n axes: AxisSpec[];\n /** Maps each series.key to the axis it draws against. */\n axisOfKey: Map<string, AxisOrientation>;\n}\n\n/** Sentinel grouping unitless series; never surfaced as an AxisSpec.unit. */\nconst NO_UNIT = \"\u0000__noUnit\";\n\n/**\n * Assigns series to one or two Y-axes by distinct unit, in first-seen order:\n * the first distinct unit takes the left axis, the second takes the right.\n * A third-or-later distinct unit shares the right axis (its series still plot,\n * but the right ticks read in the second unit's scale — a known approximation;\n * true N-axis support is out of scope). A single distinct unit yields one left\n * axis only. Undefined units form their own bucket so unitless series still get\n * an axis (recharts drops any series whose yAxisId is missing). Pure.\n */\nexport function assignAxes(series: ResolvedSeries[]): AxisAssignment {\n const order: string[] = [];\n const seen = new Set<string>();\n for (const s of series) {\n const u = s.unit ?? NO_UNIT;\n if (!seen.has(u)) {\n seen.add(u);\n order.push(u);\n }\n }\n\n const unitToAxis = new Map<string, AxisOrientation>();\n const axes: AxisSpec[] = [];\n order.forEach((u, i) => {\n const orientation: AxisOrientation = i === 0 ? \"left\" : \"right\";\n unitToAxis.set(u, orientation);\n if (i <= 1) {\n axes.push({ id: orientation, orientation, ...(u !== NO_UNIT ? { unit: u } : {}) });\n }\n });\n\n const axisOfKey = new Map<string, AxisOrientation>();\n for (const s of series) {\n axisOfKey.set(s.key, unitToAxis.get(s.unit ?? NO_UNIT) ?? \"left\");\n }\n\n return { axes, axisOfKey };\n}\n\n/** Last numeric value for a series key, scanning from newest row; undefined if none. Pure. */\nexport function latestOf(rows: MergedRow[], key: string): number | undefined {\n for (let i = rows.length - 1; i >= 0; i--) {\n const v = rows[i]?.[key];\n if (typeof v === \"number\") return v;\n }\n return undefined;\n}\n"],"names":[],"mappings":";;AAoBA,MAAM,UAAU;AAWT,SAAS,WAAW,QAA0C;AACnE,QAAM,QAAkB,CAAA;AACxB,QAAM,2BAAW,IAAA;AACjB,aAAW,KAAK,QAAQ;AACtB,UAAM,IAAI,EAAE,QAAQ;AACpB,QAAI,CAAC,KAAK,IAAI,CAAC,GAAG;AAChB,WAAK,IAAI,CAAC;AACV,YAAM,KAAK,CAAC;AAAA,IACd;AAAA,EACF;AAEA,QAAM,iCAAiB,IAAA;AACvB,QAAM,OAAmB,CAAA;AACzB,QAAM,QAAQ,CAAC,GAAG,MAAM;AACtB,UAAM,cAA+B,MAAM,IAAI,SAAS;AACxD,eAAW,IAAI,GAAG,WAAW;AAC7B,QAAI,KAAK,GAAG;AACV,WAAK,KAAK,EAAE,IAAI,aAAa,aAAa,GAAI,MAAM,UAAU,EAAE,MAAM,EAAA,IAAM,CAAA,GAAK;AAAA,IACnF;AAAA,EACF,CAAC;AAED,QAAM,gCAAgB,IAAA;AACtB,aAAW,KAAK,QAAQ;AACtB,cAAU,IAAI,EAAE,KAAK,WAAW,IAAI,EAAE,QAAQ,OAAO,KAAK,MAAM;AAAA,EAClE;AAEA,SAAO,EAAE,MAAM,UAAA;AACjB;AAGO,SAAS,SAAS,MAAmB,KAAiC;;AAC3E,WAAS,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK;AACzC,UAAM,KAAI,UAAK,CAAC,MAAN,mBAAU;AACpB,QAAI,OAAO,MAAM,SAAU,QAAO;AAAA,EACpC;AACA,SAAO;AACT;;;"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { MergedRow, ResolvedSeries } from './TimeseriesPanel';
|
|
2
|
+
export type AxisOrientation = "left" | "right";
|
|
3
|
+
/** A rendered Y-axis: its recharts id (== orientation) and the unit it formats with. */
|
|
4
|
+
export interface AxisSpec {
|
|
5
|
+
id: AxisOrientation;
|
|
6
|
+
orientation: AxisOrientation;
|
|
7
|
+
/** Unit the axis ticks format with; undefined for a unitless axis. */
|
|
8
|
+
unit?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface AxisAssignment {
|
|
11
|
+
/** 1 or 2 axes in render order (left first). */
|
|
12
|
+
axes: AxisSpec[];
|
|
13
|
+
/** Maps each series.key to the axis it draws against. */
|
|
14
|
+
axisOfKey: Map<string, AxisOrientation>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Assigns series to one or two Y-axes by distinct unit, in first-seen order:
|
|
18
|
+
* the first distinct unit takes the left axis, the second takes the right.
|
|
19
|
+
* A third-or-later distinct unit shares the right axis (its series still plot,
|
|
20
|
+
* but the right ticks read in the second unit's scale — a known approximation;
|
|
21
|
+
* true N-axis support is out of scope). A single distinct unit yields one left
|
|
22
|
+
* axis only. Undefined units form their own bucket so unitless series still get
|
|
23
|
+
* an axis (recharts drops any series whose yAxisId is missing). Pure.
|
|
24
|
+
*/
|
|
25
|
+
export declare function assignAxes(series: ResolvedSeries[]): AxisAssignment;
|
|
26
|
+
/** Last numeric value for a series key, scanning from newest row; undefined if none. Pure. */
|
|
27
|
+
export declare function latestOf(rows: MergedRow[], key: string): number | undefined;
|
|
28
|
+
//# sourceMappingURL=TimeseriesPanel.axes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimeseriesPanel.axes.d.ts","sourceRoot":"","sources":["../../src/data/TimeseriesPanel.axes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,OAAO,CAAC;AAE/C,wFAAwF;AACxF,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,eAAe,CAAC;IACpB,WAAW,EAAE,eAAe,CAAC;IAC7B,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,yDAAyD;IACzD,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CACzC;AAKD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,cAAc,CA2BnE;AAED,8FAA8F;AAC9F,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAM3E"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const NO_UNIT = "\0__noUnit";
|
|
2
|
+
function assignAxes(series) {
|
|
3
|
+
const order = [];
|
|
4
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5
|
+
for (const s of series) {
|
|
6
|
+
const u = s.unit ?? NO_UNIT;
|
|
7
|
+
if (!seen.has(u)) {
|
|
8
|
+
seen.add(u);
|
|
9
|
+
order.push(u);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const unitToAxis = /* @__PURE__ */ new Map();
|
|
13
|
+
const axes = [];
|
|
14
|
+
order.forEach((u, i) => {
|
|
15
|
+
const orientation = i === 0 ? "left" : "right";
|
|
16
|
+
unitToAxis.set(u, orientation);
|
|
17
|
+
if (i <= 1) {
|
|
18
|
+
axes.push({ id: orientation, orientation, ...u !== NO_UNIT ? { unit: u } : {} });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
const axisOfKey = /* @__PURE__ */ new Map();
|
|
22
|
+
for (const s of series) {
|
|
23
|
+
axisOfKey.set(s.key, unitToAxis.get(s.unit ?? NO_UNIT) ?? "left");
|
|
24
|
+
}
|
|
25
|
+
return { axes, axisOfKey };
|
|
26
|
+
}
|
|
27
|
+
function latestOf(rows, key) {
|
|
28
|
+
var _a;
|
|
29
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
30
|
+
const v = (_a = rows[i]) == null ? void 0 : _a[key];
|
|
31
|
+
if (typeof v === "number") return v;
|
|
32
|
+
}
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
assignAxes,
|
|
37
|
+
latestOf
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=TimeseriesPanel.axes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimeseriesPanel.axes.js","sources":["../../src/data/TimeseriesPanel.axes.ts"],"sourcesContent":["import type { MergedRow, ResolvedSeries } from \"./TimeseriesPanel\";\n\nexport type AxisOrientation = \"left\" | \"right\";\n\n/** A rendered Y-axis: its recharts id (== orientation) and the unit it formats with. */\nexport interface AxisSpec {\n id: AxisOrientation;\n orientation: AxisOrientation;\n /** Unit the axis ticks format with; undefined for a unitless axis. */\n unit?: string;\n}\n\nexport interface AxisAssignment {\n /** 1 or 2 axes in render order (left first). */\n axes: AxisSpec[];\n /** Maps each series.key to the axis it draws against. */\n axisOfKey: Map<string, AxisOrientation>;\n}\n\n/** Sentinel grouping unitless series; never surfaced as an AxisSpec.unit. */\nconst NO_UNIT = \"\u0000__noUnit\";\n\n/**\n * Assigns series to one or two Y-axes by distinct unit, in first-seen order:\n * the first distinct unit takes the left axis, the second takes the right.\n * A third-or-later distinct unit shares the right axis (its series still plot,\n * but the right ticks read in the second unit's scale — a known approximation;\n * true N-axis support is out of scope). A single distinct unit yields one left\n * axis only. Undefined units form their own bucket so unitless series still get\n * an axis (recharts drops any series whose yAxisId is missing). Pure.\n */\nexport function assignAxes(series: ResolvedSeries[]): AxisAssignment {\n const order: string[] = [];\n const seen = new Set<string>();\n for (const s of series) {\n const u = s.unit ?? NO_UNIT;\n if (!seen.has(u)) {\n seen.add(u);\n order.push(u);\n }\n }\n\n const unitToAxis = new Map<string, AxisOrientation>();\n const axes: AxisSpec[] = [];\n order.forEach((u, i) => {\n const orientation: AxisOrientation = i === 0 ? \"left\" : \"right\";\n unitToAxis.set(u, orientation);\n if (i <= 1) {\n axes.push({ id: orientation, orientation, ...(u !== NO_UNIT ? { unit: u } : {}) });\n }\n });\n\n const axisOfKey = new Map<string, AxisOrientation>();\n for (const s of series) {\n axisOfKey.set(s.key, unitToAxis.get(s.unit ?? NO_UNIT) ?? \"left\");\n }\n\n return { axes, axisOfKey };\n}\n\n/** Last numeric value for a series key, scanning from newest row; undefined if none. Pure. */\nexport function latestOf(rows: MergedRow[], key: string): number | undefined {\n for (let i = rows.length - 1; i >= 0; i--) {\n const v = rows[i]?.[key];\n if (typeof v === \"number\") return v;\n }\n return undefined;\n}\n"],"names":[],"mappings":"AAoBA,MAAM,UAAU;AAWT,SAAS,WAAW,QAA0C;AACnE,QAAM,QAAkB,CAAA;AACxB,QAAM,2BAAW,IAAA;AACjB,aAAW,KAAK,QAAQ;AACtB,UAAM,IAAI,EAAE,QAAQ;AACpB,QAAI,CAAC,KAAK,IAAI,CAAC,GAAG;AAChB,WAAK,IAAI,CAAC;AACV,YAAM,KAAK,CAAC;AAAA,IACd;AAAA,EACF;AAEA,QAAM,iCAAiB,IAAA;AACvB,QAAM,OAAmB,CAAA;AACzB,QAAM,QAAQ,CAAC,GAAG,MAAM;AACtB,UAAM,cAA+B,MAAM,IAAI,SAAS;AACxD,eAAW,IAAI,GAAG,WAAW;AAC7B,QAAI,KAAK,GAAG;AACV,WAAK,KAAK,EAAE,IAAI,aAAa,aAAa,GAAI,MAAM,UAAU,EAAE,MAAM,EAAA,IAAM,CAAA,GAAK;AAAA,IACnF;AAAA,EACF,CAAC;AAED,QAAM,gCAAgB,IAAA;AACtB,aAAW,KAAK,QAAQ;AACtB,cAAU,IAAI,EAAE,KAAK,WAAW,IAAI,EAAE,QAAQ,OAAO,KAAK,MAAM;AAAA,EAClE;AAEA,SAAO,EAAE,MAAM,UAAA;AACjB;AAGO,SAAS,SAAS,MAAmB,KAAiC;AAzC7E;AA0CE,WAAS,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK;AACzC,UAAM,KAAI,UAAK,CAAC,MAAN,mBAAU;AACpB,QAAI,OAAO,MAAM,SAAU,QAAO;AAAA,EACpC;AACA,SAAO;AACT;"}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const reactQuery = require("@tanstack/react-query");
|
|
5
|
+
const react = require("react");
|
|
6
|
+
const recharts = require("recharts");
|
|
7
|
+
const utils = require("../lib/utils.cjs");
|
|
8
|
+
const color = require("../lib/color.cjs");
|
|
9
|
+
const format = require("../lib/format.cjs");
|
|
10
|
+
const Modal = require("../overlay/Modal.cjs");
|
|
11
|
+
const ProgressBar = require("./ProgressBar.cjs");
|
|
12
|
+
const Icon = require("./Icon.cjs");
|
|
13
|
+
const TimeseriesPanel_axes = require("./TimeseriesPanel.axes.cjs");
|
|
14
|
+
const UiFullscreen = require("../icons/components/UiFullscreen.cjs");
|
|
15
|
+
const SERIES_PALETTE = [
|
|
16
|
+
"var(--chart-1, #3b82f6)",
|
|
17
|
+
"var(--chart-2, #ef4444)",
|
|
18
|
+
"var(--chart-3, #10b981)",
|
|
19
|
+
"var(--chart-4, #f59e0b)"
|
|
20
|
+
];
|
|
21
|
+
function paletteColor(index) {
|
|
22
|
+
return SERIES_PALETTE[index % SERIES_PALETTE.length] ?? SERIES_PALETTE[0];
|
|
23
|
+
}
|
|
24
|
+
function resolveSeries(props) {
|
|
25
|
+
const base = props.baseUrl ?? "";
|
|
26
|
+
const list = props.series ?? (props.url ? [{ id: props.url }] : []);
|
|
27
|
+
return list.map((s, i) => ({
|
|
28
|
+
key: s.id,
|
|
29
|
+
url: base + s.id,
|
|
30
|
+
label: s.label ?? s.id,
|
|
31
|
+
color: s.color ?? paletteColor(i),
|
|
32
|
+
...s.unit ?? props.unit ? { unit: s.unit ?? props.unit } : {},
|
|
33
|
+
...s.transform ? { transform: s.transform } : {},
|
|
34
|
+
...s.current !== void 0 ? { current: s.current } : {}
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
function resolveBreakdown(series, rows, total) {
|
|
38
|
+
const withValue = series.map((s) => ({ s, value: s.current ?? TimeseriesPanel_axes.latestOf(rows, s.key) ?? 0 }));
|
|
39
|
+
const denom = total ?? withValue.reduce((acc, w) => acc + w.value, 0);
|
|
40
|
+
const items = withValue.map(({ s, value }) => ({
|
|
41
|
+
key: s.key,
|
|
42
|
+
label: s.label,
|
|
43
|
+
color: s.color,
|
|
44
|
+
...s.unit ? { unit: s.unit } : {},
|
|
45
|
+
value,
|
|
46
|
+
percent: denom > 0 ? Math.min(100, Math.max(0, value / denom * 100)) : 0
|
|
47
|
+
}));
|
|
48
|
+
return { items, total: denom };
|
|
49
|
+
}
|
|
50
|
+
const defaultFetcher = async (url) => {
|
|
51
|
+
const res = await fetch(url);
|
|
52
|
+
if (!res.ok) throw new Error(`metrics request failed: ${res.status}`);
|
|
53
|
+
return res.json();
|
|
54
|
+
};
|
|
55
|
+
function formatClock(at) {
|
|
56
|
+
const date = new Date(at);
|
|
57
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
58
|
+
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
59
|
+
}
|
|
60
|
+
function mergeSeries(series, pointsBySeries) {
|
|
61
|
+
const rows = /* @__PURE__ */ new Map();
|
|
62
|
+
series.forEach((s, i) => {
|
|
63
|
+
for (const p of pointsBySeries[i] ?? []) {
|
|
64
|
+
const row = rows.get(p.at) ?? { at: p.at };
|
|
65
|
+
row[s.key] = s.transform ? s.transform(p.value) : p.value;
|
|
66
|
+
rows.set(p.at, row);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return [...rows.values()].sort((a, b) => a.at.localeCompare(b.at));
|
|
70
|
+
}
|
|
71
|
+
function TimeseriesPanel(props) {
|
|
72
|
+
var _a;
|
|
73
|
+
const {
|
|
74
|
+
title,
|
|
75
|
+
icon,
|
|
76
|
+
unit,
|
|
77
|
+
range = "1h",
|
|
78
|
+
refreshMs = 5e3,
|
|
79
|
+
height = 160,
|
|
80
|
+
variant = "area",
|
|
81
|
+
expandable = true,
|
|
82
|
+
total,
|
|
83
|
+
fetcher = defaultFetcher,
|
|
84
|
+
className
|
|
85
|
+
} = props;
|
|
86
|
+
const isBreakdown = variant === "breakdown";
|
|
87
|
+
const chartVariant = isBreakdown ? "area" : variant;
|
|
88
|
+
const expandVariant = props.expandVariant ?? chartVariant;
|
|
89
|
+
const [expanded, setExpanded] = react.useState(false);
|
|
90
|
+
const series = react.useMemo(() => resolveSeries(props), [props]);
|
|
91
|
+
const requestUrls = react.useMemo(
|
|
92
|
+
() => series.map((s) => {
|
|
93
|
+
const u = new URL(s.url, window.location.origin);
|
|
94
|
+
if (range) u.searchParams.set("since", range);
|
|
95
|
+
return u.pathname + u.search;
|
|
96
|
+
}),
|
|
97
|
+
[series, range]
|
|
98
|
+
);
|
|
99
|
+
const results = reactQuery.useQueries({
|
|
100
|
+
queries: requestUrls.map((requestUrl) => ({
|
|
101
|
+
queryKey: ["timeseries", requestUrl],
|
|
102
|
+
queryFn: () => fetcher(requestUrl),
|
|
103
|
+
refetchInterval: refreshMs > 0 ? refreshMs : false,
|
|
104
|
+
staleTime: 0,
|
|
105
|
+
retry: 0
|
|
106
|
+
}))
|
|
107
|
+
});
|
|
108
|
+
const isError = results.some((r) => r.isError);
|
|
109
|
+
const error = (_a = results.find((r) => r.isError)) == null ? void 0 : _a.error;
|
|
110
|
+
const isLoading = results.some((r) => r.isLoading) && results.every((r) => !r.data);
|
|
111
|
+
const pointsBySeries = results.map((r) => {
|
|
112
|
+
var _a2;
|
|
113
|
+
return (_a2 = r.data) == null ? void 0 : _a2.points;
|
|
114
|
+
});
|
|
115
|
+
const rows = react.useMemo(() => mergeSeries(series, pointsBySeries), [series, pointsBySeries]);
|
|
116
|
+
const hasMirrored = series.some((s) => s.transform);
|
|
117
|
+
const breakdown = react.useMemo(
|
|
118
|
+
() => isBreakdown ? resolveBreakdown(series, rows, total) : null,
|
|
119
|
+
[isBreakdown, series, rows, total]
|
|
120
|
+
);
|
|
121
|
+
const canExpand = expandable && rows.length >= 2;
|
|
122
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("flex flex-col rounded-lg border border-border bg-card p-3", className), children: [
|
|
123
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2 text-xs text-muted-foreground", children: [
|
|
124
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5", children: [
|
|
125
|
+
icon ? /* @__PURE__ */ jsxRuntime.jsx(PanelIcon, { icon }) : null,
|
|
126
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate font-medium text-foreground", children: title })
|
|
127
|
+
] }),
|
|
128
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex shrink-0 items-center gap-1.5", children: [
|
|
129
|
+
breakdown ? /* @__PURE__ */ jsxRuntime.jsx(BreakdownReadout, { total: breakdown.total, unit, items: breakdown.items }) : /* @__PURE__ */ jsxRuntime.jsx(LatestReadout, { series, rows }),
|
|
130
|
+
canExpand ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
131
|
+
"button",
|
|
132
|
+
{
|
|
133
|
+
type: "button",
|
|
134
|
+
"aria-label": "Expand chart",
|
|
135
|
+
onClick: () => setExpanded(true),
|
|
136
|
+
className: "text-muted-foreground transition-colors hover:text-foreground",
|
|
137
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiFullscreen.UiFullscreen, width: 14, height: 14 })
|
|
138
|
+
}
|
|
139
|
+
) : null
|
|
140
|
+
] })
|
|
141
|
+
] }),
|
|
142
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2", style: breakdown ? void 0 : { height }, children: breakdown ? /* @__PURE__ */ jsxRuntime.jsx(BreakdownView, { total: breakdown.total, unit, items: breakdown.items }) : isError ? /* @__PURE__ */ jsxRuntime.jsx(PanelMessage, { children: (error == null ? void 0 : error.message) ?? "Failed to load metric" }) : isLoading ? /* @__PURE__ */ jsxRuntime.jsx(PanelMessage, { children: "Loading…" }) : rows.length < 2 ? /* @__PURE__ */ jsxRuntime.jsx(PanelMessage, { children: "Collecting data…" }) : /* @__PURE__ */ jsxRuntime.jsx(Chart, { rows, series, variant: chartVariant, unit, showZeroLine: hasMirrored }) }),
|
|
143
|
+
!breakdown && !isError && !isLoading && rows.length >= 2 && series.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx(ChartLegend, { series, rows }) : null,
|
|
144
|
+
canExpand ? /* @__PURE__ */ jsxRuntime.jsxs(Modal.Modal, { open: expanded, onClose: () => setExpanded(false), title, size: "xl", children: [
|
|
145
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 360 }, children: /* @__PURE__ */ jsxRuntime.jsx(Chart, { rows, series, variant: expandVariant, unit, showZeroLine: hasMirrored }) }),
|
|
146
|
+
series.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx(ChartLegend, { series, rows }) : null
|
|
147
|
+
] }) : null
|
|
148
|
+
] });
|
|
149
|
+
}
|
|
150
|
+
function LatestReadout({ series, rows }) {
|
|
151
|
+
const only = series.length === 1 ? series[0] : void 0;
|
|
152
|
+
if (!only) return null;
|
|
153
|
+
const v = TimeseriesPanel_axes.latestOf(rows, only.key);
|
|
154
|
+
if (v === void 0) return null;
|
|
155
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: format.formatUnit(Math.abs(v), only.unit) });
|
|
156
|
+
}
|
|
157
|
+
function ChartLegend({ series, rows }) {
|
|
158
|
+
return /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-1.5 flex flex-wrap gap-x-3 gap-y-1 text-xs text-muted-foreground", children: series.map((s) => {
|
|
159
|
+
const v = TimeseriesPanel_axes.latestOf(rows, s.key);
|
|
160
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "inline-flex min-w-0 items-center gap-1.5", children: [
|
|
161
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
162
|
+
"span",
|
|
163
|
+
{
|
|
164
|
+
className: "h-2 w-2 shrink-0 rounded-full",
|
|
165
|
+
style: { backgroundColor: color.resolveCssColor(s.color) },
|
|
166
|
+
"aria-hidden": true
|
|
167
|
+
}
|
|
168
|
+
),
|
|
169
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-foreground", children: s.label }),
|
|
170
|
+
v !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: format.formatUnit(Math.abs(v), s.unit) }) : null
|
|
171
|
+
] }, s.key);
|
|
172
|
+
}) });
|
|
173
|
+
}
|
|
174
|
+
function BreakdownReadout({
|
|
175
|
+
total,
|
|
176
|
+
unit,
|
|
177
|
+
items
|
|
178
|
+
}) {
|
|
179
|
+
const used = items.reduce((acc, it) => acc + it.value, 0);
|
|
180
|
+
const pct = total > 0 ? Math.min(100, used / total * 100) : 0;
|
|
181
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono tabular-nums", children: [
|
|
182
|
+
format.formatUnit(used, unit),
|
|
183
|
+
" / ",
|
|
184
|
+
format.formatUnit(total, unit),
|
|
185
|
+
" · ",
|
|
186
|
+
pct.toFixed(0),
|
|
187
|
+
"%"
|
|
188
|
+
] });
|
|
189
|
+
}
|
|
190
|
+
function BreakdownView({
|
|
191
|
+
total,
|
|
192
|
+
unit,
|
|
193
|
+
items
|
|
194
|
+
}) {
|
|
195
|
+
const segments = items.map((it) => ({
|
|
196
|
+
count: it.value,
|
|
197
|
+
color: it.color,
|
|
198
|
+
label: it.label
|
|
199
|
+
}));
|
|
200
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
201
|
+
/* @__PURE__ */ jsxRuntime.jsx(ProgressBar.ProgressBar, { segments, total: total > 0 ? total : 1, height: "h-3" }),
|
|
202
|
+
items.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "grid grid-cols-1 gap-y-1 text-xs", children: items.map((it) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-2", children: [
|
|
203
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
204
|
+
"span",
|
|
205
|
+
{
|
|
206
|
+
className: "h-2 w-2 shrink-0 rounded-full",
|
|
207
|
+
style: { backgroundColor: color.resolveCssColor(it.color) },
|
|
208
|
+
"aria-hidden": true
|
|
209
|
+
}
|
|
210
|
+
),
|
|
211
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate font-mono text-muted-foreground", children: it.label }),
|
|
212
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono tabular-nums text-foreground", children: format.formatUnit(it.value, it.unit ?? unit) }),
|
|
213
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "w-10 text-right font-mono tabular-nums text-muted-foreground", children: [
|
|
214
|
+
it.percent.toFixed(0),
|
|
215
|
+
"%"
|
|
216
|
+
] })
|
|
217
|
+
] }, it.key)) }) : null
|
|
218
|
+
] });
|
|
219
|
+
}
|
|
220
|
+
function Chart({
|
|
221
|
+
rows,
|
|
222
|
+
series,
|
|
223
|
+
variant,
|
|
224
|
+
unit,
|
|
225
|
+
showZeroLine
|
|
226
|
+
}) {
|
|
227
|
+
const stacked = variant === "stacked";
|
|
228
|
+
const seriesByKey = new Map(series.map((s) => [s.key, s]));
|
|
229
|
+
const strokeOf = (s) => color.resolveCssColor(s.color);
|
|
230
|
+
const { axes: yAxes, axisOfKey } = react.useMemo(() => TimeseriesPanel_axes.assignAxes(series), [series]);
|
|
231
|
+
const axisOf = (key) => axisOfKey.get(key) ?? "left";
|
|
232
|
+
const axes = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
233
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeOpacity: 0.12, vertical: false }),
|
|
234
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
235
|
+
recharts.XAxis,
|
|
236
|
+
{
|
|
237
|
+
dataKey: "at",
|
|
238
|
+
tickFormatter: formatClock,
|
|
239
|
+
tick: { fontSize: 10 },
|
|
240
|
+
stroke: "currentColor",
|
|
241
|
+
strokeOpacity: 0.35,
|
|
242
|
+
minTickGap: 32
|
|
243
|
+
}
|
|
244
|
+
),
|
|
245
|
+
yAxes.map((ax) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
246
|
+
recharts.YAxis,
|
|
247
|
+
{
|
|
248
|
+
yAxisId: ax.id,
|
|
249
|
+
orientation: ax.orientation,
|
|
250
|
+
tick: { fontSize: 10 },
|
|
251
|
+
stroke: "currentColor",
|
|
252
|
+
strokeOpacity: 0.35,
|
|
253
|
+
width: 40,
|
|
254
|
+
tickFormatter: (v) => format.formatUnit(Math.abs(v), ax.unit)
|
|
255
|
+
},
|
|
256
|
+
ax.id
|
|
257
|
+
)),
|
|
258
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
259
|
+
recharts.Tooltip,
|
|
260
|
+
{
|
|
261
|
+
labelFormatter: (label) => formatClock(String(label)),
|
|
262
|
+
formatter: (value, name) => {
|
|
263
|
+
const s = seriesByKey.get(String(name));
|
|
264
|
+
return [format.formatUnit(Math.abs(Number(value) || 0), (s == null ? void 0 : s.unit) ?? unit), (s == null ? void 0 : s.label) ?? String(name)];
|
|
265
|
+
},
|
|
266
|
+
contentStyle: { fontSize: 12 }
|
|
267
|
+
}
|
|
268
|
+
),
|
|
269
|
+
showZeroLine ? /* @__PURE__ */ jsxRuntime.jsx(recharts.ReferenceLine, { yAxisId: "left", y: 0, stroke: "currentColor", strokeOpacity: 0.35 }) : null
|
|
270
|
+
] });
|
|
271
|
+
return /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: variant === "line" ? /* @__PURE__ */ jsxRuntime.jsxs(recharts.LineChart, { data: rows, margin: { top: 4, right: 8, bottom: 0, left: 0 }, children: [
|
|
272
|
+
axes,
|
|
273
|
+
series.map((s) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
274
|
+
recharts.Line,
|
|
275
|
+
{
|
|
276
|
+
yAxisId: axisOf(s.key),
|
|
277
|
+
type: "monotone",
|
|
278
|
+
dataKey: s.key,
|
|
279
|
+
name: s.key,
|
|
280
|
+
stroke: strokeOf(s),
|
|
281
|
+
strokeWidth: 2,
|
|
282
|
+
dot: false,
|
|
283
|
+
connectNulls: true,
|
|
284
|
+
isAnimationActive: false
|
|
285
|
+
},
|
|
286
|
+
s.key
|
|
287
|
+
))
|
|
288
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(recharts.AreaChart, { data: rows, margin: { top: 4, right: 8, bottom: 0, left: 0 }, children: [
|
|
289
|
+
axes,
|
|
290
|
+
series.map((s) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
291
|
+
recharts.Area,
|
|
292
|
+
{
|
|
293
|
+
yAxisId: axisOf(s.key),
|
|
294
|
+
type: "monotone",
|
|
295
|
+
dataKey: s.key,
|
|
296
|
+
name: s.key,
|
|
297
|
+
stroke: strokeOf(s),
|
|
298
|
+
fill: strokeOf(s),
|
|
299
|
+
...stacked ? { stackId: `stack-${axisOf(s.key)}` } : {},
|
|
300
|
+
fillOpacity: stacked ? 0.7 : 0.12,
|
|
301
|
+
strokeWidth: stacked ? 0 : 2,
|
|
302
|
+
connectNulls: true,
|
|
303
|
+
isAnimationActive: false
|
|
304
|
+
},
|
|
305
|
+
s.key
|
|
306
|
+
))
|
|
307
|
+
] }) });
|
|
308
|
+
}
|
|
309
|
+
function PanelMessage({ children }) {
|
|
310
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center text-xs text-muted-foreground", children });
|
|
311
|
+
}
|
|
312
|
+
function PanelIcon({ icon }) {
|
|
313
|
+
if (typeof icon === "string") {
|
|
314
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { name: icon, width: 16, height: 16, className: "text-muted-foreground" });
|
|
315
|
+
}
|
|
316
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon, width: 16, height: 16, className: "text-muted-foreground" });
|
|
317
|
+
}
|
|
318
|
+
exports.assignAxes = TimeseriesPanel_axes.assignAxes;
|
|
319
|
+
exports.latestOf = TimeseriesPanel_axes.latestOf;
|
|
320
|
+
exports.TimeseriesPanel = TimeseriesPanel;
|
|
321
|
+
exports.mergeSeries = mergeSeries;
|
|
322
|
+
exports.resolveBreakdown = resolveBreakdown;
|
|
323
|
+
exports.resolveSeries = resolveSeries;
|
|
324
|
+
//# sourceMappingURL=TimeseriesPanel.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimeseriesPanel.cjs","sources":["../../src/data/TimeseriesPanel.tsx"],"sourcesContent":["import { useQueries } from \"@tanstack/react-query\";\nimport { useMemo, useState } from \"react\";\nimport {\n Area,\n AreaChart,\n CartesianGrid,\n Line,\n LineChart,\n ReferenceLine,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from \"recharts\";\nimport { cn } from \"../lib/utils\";\nimport { resolveCssColor } from \"../lib/color\";\nimport { formatUnit } from \"../lib/format\";\nimport { Modal } from \"../overlay/Modal\";\nimport { ProgressBar, type ProgressSegment } from \"./ProgressBar\";\nimport { UiFullscreen } from \"../icons\";\nimport { Icon, type StaticIconComponent } from \"./Icon\";\nimport { assignAxes, latestOf } from \"./TimeseriesPanel.axes\";\n\nexport { assignAxes, latestOf } from \"./TimeseriesPanel.axes\";\nexport type { AxisAssignment, AxisOrientation, AxisSpec } from \"./TimeseriesPanel.axes\";\n\nexport interface TimeseriesPoint {\n at: string;\n value: number;\n}\n\nexport interface TimeseriesResponse {\n id: string;\n points: TimeseriesPoint[];\n}\n\n/** One metric plotted on a panel. Its request URL is `baseUrl + id`. */\nexport interface TimeseriesSeries {\n /** Metric id appended to the panel's baseUrl, e.g. \"sqlserver.iops.read\". */\n id: string;\n /** Tooltip/legend label; defaults to the id. */\n label?: string;\n /**\n * Maps each point's value before plotting — e.g. writes pass `(v) => -v` to\n * render below the zero axis. Identity when omitted.\n */\n transform?: (value: number) => number;\n /**\n * Stroke/fill override as a CSS color value (e.g. \"#10b981\", \"var(--chart-3)\")\n * or a Tailwind color class (e.g. \"bg-emerald-500\"); Tailwind classes are\n * resolved to a CSS value for the chart. Defaults cycle a small palette by index.\n */\n color?: string;\n /**\n * Grafana-style unit key for this series' readout/tooltip (e.g. \"percent\",\n * \"short\"). Falls back to the panel-level `unit`. Lets one panel mix units\n * (e.g. a percent series alongside a plain-count series).\n */\n unit?: string;\n /**\n * Instantaneous value for the `breakdown` variant — the size of this series'\n * bar segment and its legend readout. When omitted, the breakdown falls back\n * to the latest polled point. Ignored by the time-series variants.\n */\n current?: number;\n}\n\nexport interface TimeseriesPanelProps {\n /**\n * Single-metric endpoint returning a TimeseriesResponse,\n * e.g. \"/api/v1/metrics/sqlserver.cpu\". Mutually exclusive with baseUrl/series.\n */\n url?: string;\n /** Common prefix for a multi-series panel; each series requests `baseUrl + series.id`. */\n baseUrl?: string;\n /** Metrics to plot on one chart. Provide together with baseUrl. */\n series?: TimeseriesSeries[];\n title: string;\n /** Iconify name or static icon component shown beside the title. */\n icon?: string | StaticIconComponent;\n /**\n * Default Grafana-style unit key for the panel (e.g. \"bytes\", \"percent\",\n * \"ms\", \"short\"); each series may override it. Unknown strings are appended\n * as a raw suffix (so \"%\" still works). Used for the readout, axis, and tooltip.\n */\n unit?: string;\n /** Look-back window passed as ?since=; defaults to \"1h\". */\n range?: string;\n /** Poll interval in ms; defaults to 5000. Pass 0 to disable polling. */\n refreshMs?: number;\n /** Chart height in px; defaults to 160. */\n height?: number;\n /**\n * \"area\" (overlaid, default), \"line\", \"stacked\" (areas sum vertically), or\n * \"breakdown\" (an instantaneous stacked bar + legend; the expand modal still\n * shows a time-series graph of the polled history).\n */\n variant?: \"area\" | \"line\" | \"stacked\" | \"breakdown\";\n /**\n * Chart variant rendered in the expand modal. Defaults to `variant` for the\n * time-series variants and to \"area\" for `breakdown` (which has no inline chart).\n */\n expandVariant?: \"area\" | \"line\" | \"stacked\";\n /**\n * Denominator for the `breakdown` bar and per-segment percentages. Defaults to\n * the sum of the series' `current` values when omitted.\n */\n total?: number;\n /** Show a header button that opens the chart larger in a modal. Default true. */\n expandable?: boolean;\n /** Override the default fetch (e.g. to route through an app's API client). */\n fetcher?: (url: string) => Promise<TimeseriesResponse>;\n className?: string;\n}\n\nconst SERIES_PALETTE = [\n \"var(--chart-1, #3b82f6)\",\n \"var(--chart-2, #ef4444)\",\n \"var(--chart-3, #10b981)\",\n \"var(--chart-4, #f59e0b)\",\n] as const;\n\nfunction paletteColor(index: number): string {\n return SERIES_PALETTE[index % SERIES_PALETTE.length] ?? SERIES_PALETTE[0];\n}\n\n/** A series with its full request URL, resolved label, color, and unit. Exported for tests. */\nexport interface ResolvedSeries {\n /** Stable key used as the merged-row field and recharts dataKey. */\n key: string;\n url: string;\n label: string;\n color: string;\n /** Resolved Grafana unit key: the series' own unit, else the panel default. */\n unit?: string;\n transform?: (value: number) => number;\n /** Instantaneous value used by the `breakdown` variant. */\n current?: number;\n}\n\n/** Normalises the single-url and baseUrl+series forms to one list. Exported for tests. */\nexport function resolveSeries(props: TimeseriesPanelProps): ResolvedSeries[] {\n const base = props.baseUrl ?? \"\";\n const list = props.series ?? (props.url ? [{ id: props.url }] : []);\n return list.map((s, i) => ({\n key: s.id,\n url: base + s.id,\n label: s.label ?? s.id,\n color: s.color ?? paletteColor(i),\n ...(s.unit ?? props.unit ? { unit: s.unit ?? props.unit } : {}),\n ...(s.transform ? { transform: s.transform } : {}),\n ...(s.current !== undefined ? { current: s.current } : {}),\n }));\n}\n\n/** One legend/bar row of the breakdown variant: resolved value, color, label, unit, percentage. */\nexport interface BreakdownItem {\n key: string;\n label: string;\n color: string;\n unit?: string;\n value: number;\n /** Share of `total`, clamped to 0..100. */\n percent: number;\n}\n\n/**\n * Derives the breakdown bar segments and per-item percentages from the resolved\n * series' `current` values. `total` defaults to the sum of those values; the\n * percentage is each value's share of that denominator, clamped to 0..100.\n * Latest polled point fills in for a series with no explicit `current`. Pure —\n * exported for tests.\n */\nexport function resolveBreakdown(\n series: ResolvedSeries[],\n rows: MergedRow[],\n total?: number,\n): { items: BreakdownItem[]; total: number } {\n const withValue = series.map((s) => ({ s, value: s.current ?? latestOf(rows, s.key) ?? 0 }));\n const denom = total ?? withValue.reduce((acc, w) => acc + w.value, 0);\n const items = withValue.map(({ s, value }) => ({\n key: s.key,\n label: s.label,\n color: s.color,\n ...(s.unit ? { unit: s.unit } : {}),\n value,\n percent: denom > 0 ? Math.min(100, Math.max(0, (value / denom) * 100)) : 0,\n }));\n return { items, total: denom };\n}\n\nconst defaultFetcher = async (url: string): Promise<TimeseriesResponse> => {\n const res = await fetch(url);\n if (!res.ok) throw new Error(`metrics request failed: ${res.status}`);\n return res.json();\n};\n\nfunction formatClock(at: string): string {\n const date = new Date(at);\n if (Number.isNaN(date.getTime())) return \"\";\n return date.toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\" });\n}\n\n/**\n * One row of the merged chart data: the timestamp in `at` plus each series'\n * value under its key. Values are absent (undefined) where a series has no\n * point at that timestamp.\n */\nexport interface MergedRow {\n at: string;\n [seriesKey: string]: string | number | undefined;\n}\n\n/**\n * Joins per-series point lists into rows keyed by timestamp and applies each\n * series' transform. Timestamps present in any series produce a row; missing\n * values are left undefined so recharts gaps the line rather than drawing zero.\n */\nexport function mergeSeries(\n series: ResolvedSeries[],\n pointsBySeries: (TimeseriesPoint[] | undefined)[],\n): MergedRow[] {\n const rows = new Map<string, MergedRow>();\n series.forEach((s, i) => {\n for (const p of pointsBySeries[i] ?? []) {\n const row = rows.get(p.at) ?? { at: p.at };\n row[s.key] = s.transform ? s.transform(p.value) : p.value;\n rows.set(p.at, row);\n }\n });\n return [...rows.values()].sort((a, b) => a.at.localeCompare(b.at));\n}\n\nexport function TimeseriesPanel(props: TimeseriesPanelProps) {\n const {\n title,\n icon,\n unit,\n range = \"1h\",\n refreshMs = 5000,\n height = 160,\n variant = \"area\",\n expandable = true,\n total,\n fetcher = defaultFetcher,\n className,\n } = props;\n const isBreakdown = variant === \"breakdown\";\n // The inline chart (non-breakdown only) and the expand modal both need a\n // time-series variant; breakdown has no inline chart and expands to \"area\".\n const chartVariant: ChartVariant = isBreakdown ? \"area\" : variant;\n const expandVariant = props.expandVariant ?? chartVariant;\n\n const [expanded, setExpanded] = useState(false);\n\n const series = useMemo(() => resolveSeries(props), [props]);\n const requestUrls = useMemo(\n () =>\n series.map((s) => {\n const u = new URL(s.url, window.location.origin);\n if (range) u.searchParams.set(\"since\", range);\n return u.pathname + u.search;\n }),\n [series, range],\n );\n\n const results = useQueries({\n queries: requestUrls.map((requestUrl) => ({\n queryKey: [\"timeseries\", requestUrl],\n queryFn: () => fetcher(requestUrl),\n refetchInterval: refreshMs > 0 ? refreshMs : false,\n staleTime: 0,\n retry: 0,\n })),\n });\n\n const isError = results.some((r) => r.isError);\n const error = results.find((r) => r.isError)?.error;\n const isLoading = results.some((r) => r.isLoading) && results.every((r) => !r.data);\n const pointsBySeries = results.map((r) => r.data?.points);\n const rows = useMemo(() => mergeSeries(series, pointsBySeries), [series, pointsBySeries]);\n const hasMirrored = series.some((s) => s.transform);\n const breakdown = useMemo(\n () => (isBreakdown ? resolveBreakdown(series, rows, total) : null),\n [isBreakdown, series, rows, total],\n );\n\n // Breakdown renders instantly from `current`; the modal still needs polled\n // history. Other variants gate on having ≥2 points to draw a line.\n const canExpand = expandable && rows.length >= 2;\n\n return (\n <div className={cn(\"flex flex-col rounded-lg border border-border bg-card p-3\", className)}>\n <div className=\"flex items-center justify-between gap-2 text-xs text-muted-foreground\">\n <span className=\"inline-flex min-w-0 items-center gap-1.5\">\n {icon ? <PanelIcon icon={icon} /> : null}\n <span className=\"truncate font-medium text-foreground\">{title}</span>\n </span>\n <span className=\"inline-flex shrink-0 items-center gap-1.5\">\n {breakdown ? (\n <BreakdownReadout total={breakdown.total} unit={unit} items={breakdown.items} />\n ) : (\n <LatestReadout series={series} rows={rows} />\n )}\n {canExpand ? (\n <button\n type=\"button\"\n aria-label=\"Expand chart\"\n onClick={() => setExpanded(true)}\n className=\"text-muted-foreground transition-colors hover:text-foreground\"\n >\n <Icon icon={UiFullscreen} width={14} height={14} />\n </button>\n ) : null}\n </span>\n </div>\n <div className=\"mt-2\" style={breakdown ? undefined : { height }}>\n {breakdown ? (\n <BreakdownView total={breakdown.total} unit={unit} items={breakdown.items} />\n ) : isError ? (\n <PanelMessage>{(error as Error)?.message ?? \"Failed to load metric\"}</PanelMessage>\n ) : isLoading ? (\n <PanelMessage>Loading…</PanelMessage>\n ) : rows.length < 2 ? (\n <PanelMessage>Collecting data…</PanelMessage>\n ) : (\n <Chart rows={rows} series={series} variant={chartVariant} unit={unit} showZeroLine={hasMirrored} />\n )}\n </div>\n {!breakdown && !isError && !isLoading && rows.length >= 2 && series.length > 1 ? (\n <ChartLegend series={series} rows={rows} />\n ) : null}\n {canExpand ? (\n <Modal open={expanded} onClose={() => setExpanded(false)} title={title} size=\"xl\">\n <div style={{ height: 360 }}>\n <Chart rows={rows} series={series} variant={expandVariant} unit={unit} showZeroLine={hasMirrored} />\n </div>\n {series.length > 1 ? <ChartLegend series={series} rows={rows} /> : null}\n </Modal>\n ) : null}\n </div>\n );\n}\n\n/**\n * Header readout for a single-series panel: the latest value, formatted with the\n * series' unit. Multi-series panels render the per-series values in {@link ChartLegend}\n * below the chart instead, so this returns null for them to avoid overlapping the title.\n */\nfunction LatestReadout({ series, rows }: { series: ResolvedSeries[]; rows: MergedRow[] }) {\n const only = series.length === 1 ? series[0] : undefined;\n if (!only) return null;\n const v = latestOf(rows, only.key);\n if (v === undefined) return null;\n return <span className=\"tabular-nums\">{formatUnit(Math.abs(v), only.unit)}</span>;\n}\n\n/** Wrapping color-keyed legend for the time-series variants: dot + label + latest value. */\nfunction ChartLegend({ series, rows }: { series: ResolvedSeries[]; rows: MergedRow[] }) {\n return (\n <ul className=\"mt-1.5 flex flex-wrap gap-x-3 gap-y-1 text-xs text-muted-foreground\">\n {series.map((s) => {\n const v = latestOf(rows, s.key);\n return (\n <li key={s.key} className=\"inline-flex min-w-0 items-center gap-1.5\">\n <span\n className=\"h-2 w-2 shrink-0 rounded-full\"\n style={{ backgroundColor: resolveCssColor(s.color) }}\n aria-hidden\n />\n <span className=\"truncate text-foreground\">{s.label}</span>\n {v !== undefined ? (\n <span className=\"tabular-nums\">{formatUnit(Math.abs(v), s.unit)}</span>\n ) : null}\n </li>\n );\n })}\n </ul>\n );\n}\n\n/** Breakdown header readout: `used / total · pct%`, where used is the sum of segments. */\nfunction BreakdownReadout({\n total,\n unit,\n items,\n}: {\n total: number;\n unit: string | undefined;\n items: BreakdownItem[];\n}) {\n const used = items.reduce((acc, it) => acc + it.value, 0);\n const pct = total > 0 ? Math.min(100, (used / total) * 100) : 0;\n return (\n <span className=\"font-mono tabular-nums\">\n {formatUnit(used, unit)} / {formatUnit(total, unit)} · {pct.toFixed(0)}%\n </span>\n );\n}\n\n/** Instantaneous stacked bar + legend list. The expand button (in the panel header) opens the graph. */\nfunction BreakdownView({\n total,\n unit,\n items,\n}: {\n total: number;\n unit: string | undefined;\n items: BreakdownItem[];\n}) {\n const segments: ProgressSegment[] = items.map((it) => ({\n count: it.value,\n color: it.color,\n label: it.label,\n }));\n return (\n <div className=\"flex flex-col gap-2\">\n <ProgressBar segments={segments} total={total > 0 ? total : 1} height=\"h-3\" />\n {items.length > 0 ? (\n <ul className=\"grid grid-cols-1 gap-y-1 text-xs\">\n {items.map((it) => (\n <li key={it.key} className=\"flex items-center gap-2\">\n <span\n className=\"h-2 w-2 shrink-0 rounded-full\"\n style={{ backgroundColor: resolveCssColor(it.color) }}\n aria-hidden\n />\n <span className=\"min-w-0 flex-1 truncate font-mono text-muted-foreground\">\n {it.label}\n </span>\n <span className=\"font-mono tabular-nums text-foreground\">\n {formatUnit(it.value, it.unit ?? unit)}\n </span>\n <span className=\"w-10 text-right font-mono tabular-nums text-muted-foreground\">\n {it.percent.toFixed(0)}%\n </span>\n </li>\n ))}\n </ul>\n ) : null}\n </div>\n );\n}\n\ntype ChartVariant = \"area\" | \"line\" | \"stacked\";\n\nfunction Chart({\n rows,\n series,\n variant,\n unit,\n showZeroLine,\n}: {\n rows: MergedRow[];\n series: ResolvedSeries[];\n variant: ChartVariant;\n unit: string | undefined;\n showZeroLine: boolean;\n}) {\n const stacked = variant === \"stacked\";\n // Mirrored series carry negative values; show magnitudes on axis and tooltip.\n const seriesByKey = new Map(series.map((s) => [s.key, s]));\n // recharts needs a CSS color value; a series may carry a Tailwind class.\n const strokeOf = (s: ResolvedSeries) => resolveCssColor(s.color);\n // Distinct units map to a left and (when present) right axis; each series draws\n // against the axis for its unit so mixed-unit panels keep independent scales.\n const { axes: yAxes, axisOfKey } = useMemo(() => assignAxes(series), [series]);\n const axisOf = (key: string): \"left\" | \"right\" => axisOfKey.get(key) ?? \"left\";\n const axes = (\n <>\n <CartesianGrid strokeOpacity={0.12} vertical={false} />\n <XAxis\n dataKey=\"at\"\n tickFormatter={formatClock}\n tick={{ fontSize: 10 }}\n stroke=\"currentColor\"\n strokeOpacity={0.35}\n minTickGap={32}\n />\n {yAxes.map((ax) => (\n <YAxis\n key={ax.id}\n yAxisId={ax.id}\n orientation={ax.orientation}\n tick={{ fontSize: 10 }}\n stroke=\"currentColor\"\n strokeOpacity={0.35}\n width={40}\n tickFormatter={(v: number) => formatUnit(Math.abs(v), ax.unit)}\n />\n ))}\n <Tooltip\n labelFormatter={(label) => formatClock(String(label))}\n formatter={(value, name) => {\n const s = seriesByKey.get(String(name));\n return [formatUnit(Math.abs(Number(value) || 0), s?.unit ?? unit), s?.label ?? String(name)];\n }}\n contentStyle={{ fontSize: 12 }}\n />\n {showZeroLine ? (\n <ReferenceLine yAxisId=\"left\" y={0} stroke=\"currentColor\" strokeOpacity={0.35} />\n ) : null}\n </>\n );\n\n return (\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n {variant === \"line\" ? (\n <LineChart data={rows} margin={{ top: 4, right: 8, bottom: 0, left: 0 }}>\n {axes}\n {series.map((s) => (\n <Line\n key={s.key}\n yAxisId={axisOf(s.key)}\n type=\"monotone\"\n dataKey={s.key}\n name={s.key}\n stroke={strokeOf(s)}\n strokeWidth={2}\n dot={false}\n connectNulls\n isAnimationActive={false}\n />\n ))}\n </LineChart>\n ) : (\n <AreaChart data={rows} margin={{ top: 4, right: 8, bottom: 0, left: 0 }}>\n {axes}\n {series.map((s) => (\n <Area\n key={s.key}\n yAxisId={axisOf(s.key)}\n type=\"monotone\"\n dataKey={s.key}\n name={s.key}\n stroke={strokeOf(s)}\n fill={strokeOf(s)}\n {...(stacked ? { stackId: `stack-${axisOf(s.key)}` } : {})}\n fillOpacity={stacked ? 0.7 : 0.12}\n strokeWidth={stacked ? 0 : 2}\n connectNulls\n isAnimationActive={false}\n />\n ))}\n </AreaChart>\n )}\n </ResponsiveContainer>\n );\n}\n\nfunction PanelMessage({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"flex h-full items-center justify-center text-xs text-muted-foreground\">\n {children}\n </div>\n );\n}\n\nfunction PanelIcon({ icon }: { icon: string | StaticIconComponent }) {\n if (typeof icon === \"string\") {\n return <Icon name={icon} width={16} height={16} className=\"text-muted-foreground\" />;\n }\n return <Icon icon={icon} width={16} height={16} className=\"text-muted-foreground\" />;\n}\n"],"names":["latestOf","useState","useMemo","useQueries","_a","cn","jsxs","jsx","Icon","UiFullscreen","Modal","formatUnit","resolveCssColor","ProgressBar","assignAxes","Fragment","CartesianGrid","XAxis","YAxis","Tooltip","ReferenceLine","ResponsiveContainer","LineChart","Line","AreaChart","Area"],"mappings":"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"}
|