@flanksource/clicky-ui 0.2.5 → 0.2.7

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.
@@ -0,0 +1,144 @@
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 utils = require("../lib/utils.cjs");
7
+ const format = require("../lib/format.cjs");
8
+ const Modal = require("../overlay/Modal.cjs");
9
+ const Icon = require("./Icon.cjs");
10
+ const TimeseriesPanel = require("./TimeseriesPanel.cjs");
11
+ const UiFullscreen = require("../icons/components/UiFullscreen.cjs");
12
+ const defaultFetcher = async (url) => {
13
+ const res = await fetch(url);
14
+ if (!res.ok) throw new Error(`metrics request failed: ${res.status}`);
15
+ return res.json();
16
+ };
17
+ const GAUGE_RADIUS = 40;
18
+ const GAUGE_ARC = Math.PI * GAUGE_RADIUS;
19
+ const GAUGE_PATH = `M 10 50 A ${GAUGE_RADIUS} ${GAUGE_RADIUS} 0 0 1 90 50`;
20
+ function toneClass(pct, [warn, danger]) {
21
+ if (pct >= danger) return "text-red-500";
22
+ if (pct >= warn) return "text-amber-500";
23
+ return "text-emerald-500";
24
+ }
25
+ function latestValue(resp) {
26
+ var _a;
27
+ const points = resp == null ? void 0 : resp.points;
28
+ if (!points || points.length === 0) return void 0;
29
+ return (_a = points[points.length - 1]) == null ? void 0 : _a.value;
30
+ }
31
+ function GaugeIcon({ icon }) {
32
+ if (typeof icon === "string") {
33
+ return /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { name: icon, width: 14, height: 14, className: "text-muted-foreground" });
34
+ }
35
+ return /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon, width: 14, height: 14, className: "text-muted-foreground" });
36
+ }
37
+ function TimeseriesGauge({
38
+ baseUrl = "",
39
+ value,
40
+ max,
41
+ title,
42
+ icon,
43
+ unit,
44
+ range = "1h",
45
+ refreshMs = 5e3,
46
+ expandable = true,
47
+ thresholds = [75, 90],
48
+ centerDisplay = "value",
49
+ fetcher = defaultFetcher,
50
+ className
51
+ }) {
52
+ var _a, _b;
53
+ const [expanded, setExpanded] = react.useState(false);
54
+ const maxIsSeries = typeof max === "object";
55
+ const maxSeries = maxIsSeries ? max : void 0;
56
+ const ids = react.useMemo(() => {
57
+ const list = [value.id];
58
+ if (maxSeries) list.push(maxSeries.id);
59
+ return list;
60
+ }, [value.id, maxSeries]);
61
+ const results = reactQuery.useQueries({
62
+ queries: ids.map((id) => {
63
+ const u = new URL(baseUrl + id, window.location.origin);
64
+ if (range) u.searchParams.set("since", range);
65
+ const requestUrl = u.pathname + u.search;
66
+ return {
67
+ queryKey: ["timeseries", requestUrl],
68
+ queryFn: () => fetcher(requestUrl),
69
+ refetchInterval: refreshMs > 0 ? refreshMs : false,
70
+ staleTime: 0,
71
+ retry: 0
72
+ };
73
+ })
74
+ });
75
+ const rawValue = latestValue((_a = results[0]) == null ? void 0 : _a.data);
76
+ const hasValue = rawValue !== void 0;
77
+ const usage = value.transform ? value.transform(rawValue ?? 0) : rawValue ?? 0;
78
+ let limit;
79
+ if (typeof max === "number") {
80
+ limit = max;
81
+ } else if (maxSeries) {
82
+ const rawMax = latestValue((_b = results[1]) == null ? void 0 : _b.data);
83
+ limit = rawMax === void 0 ? void 0 : maxSeries.transform ? maxSeries.transform(rawMax) : rawMax;
84
+ }
85
+ const bounded = hasValue && limit !== void 0 && limit > 0;
86
+ const pct = bounded ? Math.min(100, Math.round(usage / limit * 100)) : 0;
87
+ const tone = toneClass(pct, thresholds);
88
+ const chartSeries = react.useMemo(() => {
89
+ const s = [{ id: value.id, label: "value", ...value.transform ? { transform: value.transform } : {} }];
90
+ if (maxSeries) s.push({ id: maxSeries.id, label: "max", ...maxSeries.transform ? { transform: maxSeries.transform } : {} });
91
+ return s;
92
+ }, [value.id, value.transform, maxSeries]);
93
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("flex flex-col items-center gap-1", className), children: [
94
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative h-10 w-20", children: [
95
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 100 50", className: "h-full w-full overflow-visible", children: [
96
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: GAUGE_PATH, fill: "none", stroke: "currentColor", strokeWidth: 9, strokeLinecap: "round", className: "text-muted" }),
97
+ /* @__PURE__ */ jsxRuntime.jsx(
98
+ "path",
99
+ {
100
+ d: GAUGE_PATH,
101
+ fill: "none",
102
+ stroke: "currentColor",
103
+ strokeWidth: 9,
104
+ strokeLinecap: "round",
105
+ className: tone,
106
+ strokeDasharray: GAUGE_ARC,
107
+ strokeDashoffset: GAUGE_ARC * (1 - pct / 100)
108
+ }
109
+ )
110
+ ] }),
111
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-x-0 bottom-0 text-center text-sm font-semibold text-foreground", children: centerDisplay === "percent" ? bounded ? `${pct}%` : hasValue ? "n/a" : "—" : hasValue ? format.formatUnit(usage, unit) : "—" }),
112
+ expandable && /* @__PURE__ */ jsxRuntime.jsx(
113
+ "button",
114
+ {
115
+ type: "button",
116
+ "aria-label": "Expand chart",
117
+ onClick: () => setExpanded(true),
118
+ className: "absolute right-0 top-0 text-muted-foreground hover:text-foreground",
119
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiFullscreen.UiFullscreen, width: 12, height: 12 })
120
+ }
121
+ )
122
+ ] }),
123
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
124
+ icon ? /* @__PURE__ */ jsxRuntime.jsx(GaugeIcon, { icon }) : null,
125
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: title })
126
+ ] }),
127
+ expandable && /* @__PURE__ */ jsxRuntime.jsx(Modal.Modal, { open: expanded, onClose: () => setExpanded(false), title, size: "xl", children: /* @__PURE__ */ jsxRuntime.jsx(
128
+ TimeseriesPanel.TimeseriesPanel,
129
+ {
130
+ title,
131
+ ...icon ? { icon } : {},
132
+ ...unit ? { unit } : {},
133
+ baseUrl,
134
+ series: chartSeries,
135
+ range,
136
+ refreshMs,
137
+ expandable: false,
138
+ fetcher
139
+ }
140
+ ) })
141
+ ] });
142
+ }
143
+ exports.TimeseriesGauge = TimeseriesGauge;
144
+ //# sourceMappingURL=TimeseriesGauge.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimeseriesGauge.cjs","sources":["../../src/data/TimeseriesGauge.tsx"],"sourcesContent":["import { useQueries } from \"@tanstack/react-query\";\nimport { useMemo, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { formatUnit } from \"../lib/format\";\nimport { Modal } from \"../overlay/Modal\";\nimport { UiFullscreen } from \"../icons\";\nimport { Icon, type StaticIconComponent } from \"./Icon\";\nimport {\n TimeseriesPanel,\n type TimeseriesResponse,\n type TimeseriesSeries,\n} from \"./TimeseriesPanel\";\n\n/** A metric series whose latest value drives the gauge fill or its maximum. */\nexport interface GaugeSeries {\n /** Metric id appended to the gauge's baseUrl, e.g. \"k8s.statefulset.cycle.cpu.usage\". */\n id: string;\n /** Maps the latest value before display (e.g. unit scaling). Identity when omitted. */\n transform?: (value: number) => number;\n}\n\nexport interface TimeseriesGaugeProps {\n /** Common prefix; the value/max requests are `baseUrl + id`. */\n baseUrl?: string;\n /** The metric whose latest value fills the gauge. */\n value: GaugeSeries;\n /**\n * The gauge maximum. Either a metric series (its latest value, e.g. a `.limit`\n * metric) or a fixed number. When omitted, or when the resolved max is 0, the\n * gauge shows the raw value with no fill (an unbounded reading).\n */\n max?: GaugeSeries | number;\n title: string;\n /** Iconify name or static icon component shown beside the label. */\n icon?: string | StaticIconComponent;\n /** Grafana-style unit key for the readout (e.g. \"bytes\", \"percent\", \"short\", \"ms\"). */\n unit?: string;\n /** Look-back window for the expand chart, 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 /** Show an expand button that opens the value/max time-series chart in a modal. Default true. */\n expandable?: boolean;\n /**\n * Utilisation thresholds (percent of max) at which the arc turns amber then\n * red. Defaults to [75, 90].\n */\n thresholds?: [warning: number, danger: number];\n /**\n * What to print in the centre: the formatted value (default) or the\n * utilisation percentage of max (e.g. CPU usage out of its millicore limit).\n */\n centerDisplay?: \"value\" | \"percent\";\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 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\n// Half-gauge SVG geometry: a 100×50 viewBox semicircle (radius 40, centre 50,50)\n// swept 180°. The arc length is π·r; stroke-dashoffset reveals value/max of it.\nconst GAUGE_RADIUS = 40;\nconst GAUGE_ARC = Math.PI * GAUGE_RADIUS;\nconst GAUGE_PATH = `M 10 50 A ${GAUGE_RADIUS} ${GAUGE_RADIUS} 0 0 1 90 50`;\n\nfunction toneClass(pct: number, [warn, danger]: [number, number]): string {\n if (pct >= danger) return \"text-red-500\";\n if (pct >= warn) return \"text-amber-500\";\n return \"text-emerald-500\";\n}\n\nfunction latestValue(resp: TimeseriesResponse | undefined): number | undefined {\n const points = resp?.points;\n if (!points || points.length === 0) return undefined;\n return points[points.length - 1]?.value;\n}\n\nfunction GaugeIcon({ icon }: { icon: string | StaticIconComponent }) {\n if (typeof icon === \"string\") {\n return <Icon name={icon} width={14} height={14} className=\"text-muted-foreground\" />;\n }\n return <Icon icon={icon} width={14} height={14} className=\"text-muted-foreground\" />;\n}\n\n/**\n * TimeseriesGauge renders a half (180°) radial gauge whose fill is the latest\n * value of a metric over its maximum (a `.limit`-style metric or a fixed number),\n * both read live from the timeseries store. The centre shows the utilisation\n * percentage, with the value/max caption below; the arc colour crosses warning\n * and danger thresholds. An expand button opens the full value/max time-series\n * chart in a modal (a `TimeseriesPanel`), so the gauge gives the at-a-glance\n * reading and the chart the trend.\n */\nexport function TimeseriesGauge({\n baseUrl = \"\",\n value,\n max,\n title,\n icon,\n unit,\n range = \"1h\",\n refreshMs = 5000,\n expandable = true,\n thresholds = [75, 90],\n centerDisplay = \"value\",\n fetcher = defaultFetcher,\n className,\n}: TimeseriesGaugeProps) {\n const [expanded, setExpanded] = useState(false);\n\n const maxIsSeries = typeof max === \"object\";\n const maxSeries = maxIsSeries ? max : undefined;\n const ids = useMemo(() => {\n const list = [value.id];\n if (maxSeries) list.push(maxSeries.id);\n return list;\n }, [value.id, maxSeries]);\n\n const results = useQueries({\n queries: ids.map((id) => {\n const u = new URL(baseUrl + id, window.location.origin);\n if (range) u.searchParams.set(\"since\", range);\n const requestUrl = u.pathname + u.search;\n return {\n queryKey: [\"timeseries\", requestUrl],\n queryFn: () => fetcher(requestUrl),\n refetchInterval: refreshMs > 0 ? refreshMs : false,\n staleTime: 0,\n retry: 0,\n };\n }),\n });\n\n const rawValue = latestValue(results[0]?.data);\n const hasValue = rawValue !== undefined;\n const usage = value.transform ? value.transform(rawValue ?? 0) : (rawValue ?? 0);\n\n let limit: number | undefined;\n if (typeof max === \"number\") {\n limit = max;\n } else if (maxSeries) {\n const rawMax = latestValue(results[1]?.data);\n limit = rawMax === undefined ? undefined : maxSeries.transform ? maxSeries.transform(rawMax) : rawMax;\n }\n\n const bounded = hasValue && limit !== undefined && limit > 0;\n const pct = bounded ? Math.min(100, Math.round((usage / (limit as number)) * 100)) : 0;\n const tone = toneClass(pct, thresholds);\n\n const chartSeries: TimeseriesSeries[] = useMemo(() => {\n const s: TimeseriesSeries[] = [{ id: value.id, label: \"value\", ...(value.transform ? { transform: value.transform } : {}) }];\n if (maxSeries) s.push({ id: maxSeries.id, label: \"max\", ...(maxSeries.transform ? { transform: maxSeries.transform } : {}) });\n return s;\n }, [value.id, value.transform, maxSeries]);\n\n return (\n <div className={cn(\"flex flex-col items-center gap-1\", className)}>\n <div className=\"relative h-10 w-20\">\n <svg viewBox=\"0 0 100 50\" className=\"h-full w-full overflow-visible\">\n <path d={GAUGE_PATH} fill=\"none\" stroke=\"currentColor\" strokeWidth={9} strokeLinecap=\"round\" className=\"text-muted\" />\n <path\n d={GAUGE_PATH}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={9}\n strokeLinecap=\"round\"\n className={tone}\n strokeDasharray={GAUGE_ARC}\n strokeDashoffset={GAUGE_ARC * (1 - pct / 100)}\n />\n </svg>\n <span className=\"absolute inset-x-0 bottom-0 text-center text-sm font-semibold text-foreground\">\n {centerDisplay === \"percent\"\n ? bounded\n ? `${pct}%`\n : hasValue\n ? \"n/a\"\n : \"—\"\n : hasValue\n ? formatUnit(usage, unit)\n : \"—\"}\n </span>\n {expandable && (\n <button\n type=\"button\"\n aria-label=\"Expand chart\"\n onClick={() => setExpanded(true)}\n className=\"absolute right-0 top-0 text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={UiFullscreen} width={12} height={12} />\n </button>\n )}\n </div>\n <div className=\"flex items-center gap-1 text-xs text-muted-foreground\">\n {icon ? <GaugeIcon icon={icon} /> : null}\n <span>{title}</span>\n </div>\n\n {expandable && (\n <Modal open={expanded} onClose={() => setExpanded(false)} title={title} size=\"xl\">\n <TimeseriesPanel\n title={title}\n {...(icon ? { icon } : {})}\n {...(unit ? { unit } : {})}\n baseUrl={baseUrl}\n series={chartSeries}\n range={range}\n refreshMs={refreshMs}\n expandable={false}\n fetcher={fetcher}\n />\n </Modal>\n )}\n </div>\n );\n}\n"],"names":["jsx","Icon","useState","useMemo","useQueries","cn","jsxs","formatUnit","UiFullscreen","Modal","TimeseriesPanel"],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import { StaticIconComponent } from './Icon';
2
+ import { TimeseriesResponse } from './TimeseriesPanel';
3
+ /** A metric series whose latest value drives the gauge fill or its maximum. */
4
+ export interface GaugeSeries {
5
+ /** Metric id appended to the gauge's baseUrl, e.g. "k8s.statefulset.cycle.cpu.usage". */
6
+ id: string;
7
+ /** Maps the latest value before display (e.g. unit scaling). Identity when omitted. */
8
+ transform?: (value: number) => number;
9
+ }
10
+ export interface TimeseriesGaugeProps {
11
+ /** Common prefix; the value/max requests are `baseUrl + id`. */
12
+ baseUrl?: string;
13
+ /** The metric whose latest value fills the gauge. */
14
+ value: GaugeSeries;
15
+ /**
16
+ * The gauge maximum. Either a metric series (its latest value, e.g. a `.limit`
17
+ * metric) or a fixed number. When omitted, or when the resolved max is 0, the
18
+ * gauge shows the raw value with no fill (an unbounded reading).
19
+ */
20
+ max?: GaugeSeries | number;
21
+ title: string;
22
+ /** Iconify name or static icon component shown beside the label. */
23
+ icon?: string | StaticIconComponent;
24
+ /** Grafana-style unit key for the readout (e.g. "bytes", "percent", "short", "ms"). */
25
+ unit?: string;
26
+ /** Look-back window for the expand chart, passed as ?since=; defaults to "1h". */
27
+ range?: string;
28
+ /** Poll interval in ms; defaults to 5000. Pass 0 to disable polling. */
29
+ refreshMs?: number;
30
+ /** Show an expand button that opens the value/max time-series chart in a modal. Default true. */
31
+ expandable?: boolean;
32
+ /**
33
+ * Utilisation thresholds (percent of max) at which the arc turns amber then
34
+ * red. Defaults to [75, 90].
35
+ */
36
+ thresholds?: [warning: number, danger: number];
37
+ /**
38
+ * What to print in the centre: the formatted value (default) or the
39
+ * utilisation percentage of max (e.g. CPU usage out of its millicore limit).
40
+ */
41
+ centerDisplay?: "value" | "percent";
42
+ /** Override the default fetch (e.g. to route through an app's API client). */
43
+ fetcher?: (url: string) => Promise<TimeseriesResponse>;
44
+ className?: string;
45
+ }
46
+ /**
47
+ * TimeseriesGauge renders a half (180°) radial gauge whose fill is the latest
48
+ * value of a metric over its maximum (a `.limit`-style metric or a fixed number),
49
+ * both read live from the timeseries store. The centre shows the utilisation
50
+ * percentage, with the value/max caption below; the arc colour crosses warning
51
+ * and danger thresholds. An expand button opens the full value/max time-series
52
+ * chart in a modal (a `TimeseriesPanel`), so the gauge gives the at-a-glance
53
+ * reading and the chart the trend.
54
+ */
55
+ export declare function TimeseriesGauge({ baseUrl, value, max, title, icon, unit, range, refreshMs, expandable, thresholds, centerDisplay, fetcher, className, }: TimeseriesGaugeProps): import("react/jsx-runtime").JSX.Element;
56
+ //# sourceMappingURL=TimeseriesGauge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimeseriesGauge.d.ts","sourceRoot":"","sources":["../../src/data/TimeseriesGauge.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAQ,KAAK,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AACxD,OAAO,EAEL,KAAK,kBAAkB,EAExB,MAAM,mBAAmB,CAAC;AAE3B,+EAA+E;AAC/E,MAAM,WAAW,WAAW;IAC1B,yFAAyF;IACzF,EAAE,EAAE,MAAM,CAAC;IACX,uFAAuF;IACvF,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACvC;AAED,MAAM,WAAW,oBAAoB;IACnC,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,KAAK,EAAE,WAAW,CAAC;IACnB;;;;OAIG;IACH,GAAG,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAAC;IACpC,uFAAuF;IACvF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iGAAiG;IACjG,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,8EAA8E;IAC9E,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAiCD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,EAC9B,OAAY,EACZ,KAAK,EACL,GAAG,EACH,KAAK,EACL,IAAI,EACJ,IAAI,EACJ,KAAY,EACZ,SAAgB,EAChB,UAAiB,EACjB,UAAqB,EACrB,aAAuB,EACvB,OAAwB,EACxB,SAAS,GACV,EAAE,oBAAoB,2CA4GtB"}
@@ -0,0 +1,144 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useQueries } from "@tanstack/react-query";
3
+ import { useState, useMemo } from "react";
4
+ import { cn } from "../lib/utils.js";
5
+ import { formatUnit } from "../lib/format.js";
6
+ import { Modal } from "../overlay/Modal.js";
7
+ import { Icon } from "./Icon.js";
8
+ import { TimeseriesPanel } from "./TimeseriesPanel.js";
9
+ import { UiFullscreen } from "../icons/components/UiFullscreen.js";
10
+ const defaultFetcher = async (url) => {
11
+ const res = await fetch(url);
12
+ if (!res.ok) throw new Error(`metrics request failed: ${res.status}`);
13
+ return res.json();
14
+ };
15
+ const GAUGE_RADIUS = 40;
16
+ const GAUGE_ARC = Math.PI * GAUGE_RADIUS;
17
+ const GAUGE_PATH = `M 10 50 A ${GAUGE_RADIUS} ${GAUGE_RADIUS} 0 0 1 90 50`;
18
+ function toneClass(pct, [warn, danger]) {
19
+ if (pct >= danger) return "text-red-500";
20
+ if (pct >= warn) return "text-amber-500";
21
+ return "text-emerald-500";
22
+ }
23
+ function latestValue(resp) {
24
+ var _a;
25
+ const points = resp == null ? void 0 : resp.points;
26
+ if (!points || points.length === 0) return void 0;
27
+ return (_a = points[points.length - 1]) == null ? void 0 : _a.value;
28
+ }
29
+ function GaugeIcon({ icon }) {
30
+ if (typeof icon === "string") {
31
+ return /* @__PURE__ */ jsx(Icon, { name: icon, width: 14, height: 14, className: "text-muted-foreground" });
32
+ }
33
+ return /* @__PURE__ */ jsx(Icon, { icon, width: 14, height: 14, className: "text-muted-foreground" });
34
+ }
35
+ function TimeseriesGauge({
36
+ baseUrl = "",
37
+ value,
38
+ max,
39
+ title,
40
+ icon,
41
+ unit,
42
+ range = "1h",
43
+ refreshMs = 5e3,
44
+ expandable = true,
45
+ thresholds = [75, 90],
46
+ centerDisplay = "value",
47
+ fetcher = defaultFetcher,
48
+ className
49
+ }) {
50
+ var _a, _b;
51
+ const [expanded, setExpanded] = useState(false);
52
+ const maxIsSeries = typeof max === "object";
53
+ const maxSeries = maxIsSeries ? max : void 0;
54
+ const ids = useMemo(() => {
55
+ const list = [value.id];
56
+ if (maxSeries) list.push(maxSeries.id);
57
+ return list;
58
+ }, [value.id, maxSeries]);
59
+ const results = useQueries({
60
+ queries: ids.map((id) => {
61
+ const u = new URL(baseUrl + id, window.location.origin);
62
+ if (range) u.searchParams.set("since", range);
63
+ const requestUrl = u.pathname + u.search;
64
+ return {
65
+ queryKey: ["timeseries", requestUrl],
66
+ queryFn: () => fetcher(requestUrl),
67
+ refetchInterval: refreshMs > 0 ? refreshMs : false,
68
+ staleTime: 0,
69
+ retry: 0
70
+ };
71
+ })
72
+ });
73
+ const rawValue = latestValue((_a = results[0]) == null ? void 0 : _a.data);
74
+ const hasValue = rawValue !== void 0;
75
+ const usage = value.transform ? value.transform(rawValue ?? 0) : rawValue ?? 0;
76
+ let limit;
77
+ if (typeof max === "number") {
78
+ limit = max;
79
+ } else if (maxSeries) {
80
+ const rawMax = latestValue((_b = results[1]) == null ? void 0 : _b.data);
81
+ limit = rawMax === void 0 ? void 0 : maxSeries.transform ? maxSeries.transform(rawMax) : rawMax;
82
+ }
83
+ const bounded = hasValue && limit !== void 0 && limit > 0;
84
+ const pct = bounded ? Math.min(100, Math.round(usage / limit * 100)) : 0;
85
+ const tone = toneClass(pct, thresholds);
86
+ const chartSeries = useMemo(() => {
87
+ const s = [{ id: value.id, label: "value", ...value.transform ? { transform: value.transform } : {} }];
88
+ if (maxSeries) s.push({ id: maxSeries.id, label: "max", ...maxSeries.transform ? { transform: maxSeries.transform } : {} });
89
+ return s;
90
+ }, [value.id, value.transform, maxSeries]);
91
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center gap-1", className), children: [
92
+ /* @__PURE__ */ jsxs("div", { className: "relative h-10 w-20", children: [
93
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 100 50", className: "h-full w-full overflow-visible", children: [
94
+ /* @__PURE__ */ jsx("path", { d: GAUGE_PATH, fill: "none", stroke: "currentColor", strokeWidth: 9, strokeLinecap: "round", className: "text-muted" }),
95
+ /* @__PURE__ */ jsx(
96
+ "path",
97
+ {
98
+ d: GAUGE_PATH,
99
+ fill: "none",
100
+ stroke: "currentColor",
101
+ strokeWidth: 9,
102
+ strokeLinecap: "round",
103
+ className: tone,
104
+ strokeDasharray: GAUGE_ARC,
105
+ strokeDashoffset: GAUGE_ARC * (1 - pct / 100)
106
+ }
107
+ )
108
+ ] }),
109
+ /* @__PURE__ */ jsx("span", { className: "absolute inset-x-0 bottom-0 text-center text-sm font-semibold text-foreground", children: centerDisplay === "percent" ? bounded ? `${pct}%` : hasValue ? "n/a" : "—" : hasValue ? formatUnit(usage, unit) : "—" }),
110
+ expandable && /* @__PURE__ */ jsx(
111
+ "button",
112
+ {
113
+ type: "button",
114
+ "aria-label": "Expand chart",
115
+ onClick: () => setExpanded(true),
116
+ className: "absolute right-0 top-0 text-muted-foreground hover:text-foreground",
117
+ children: /* @__PURE__ */ jsx(Icon, { icon: UiFullscreen, width: 12, height: 12 })
118
+ }
119
+ )
120
+ ] }),
121
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
122
+ icon ? /* @__PURE__ */ jsx(GaugeIcon, { icon }) : null,
123
+ /* @__PURE__ */ jsx("span", { children: title })
124
+ ] }),
125
+ expandable && /* @__PURE__ */ jsx(Modal, { open: expanded, onClose: () => setExpanded(false), title, size: "xl", children: /* @__PURE__ */ jsx(
126
+ TimeseriesPanel,
127
+ {
128
+ title,
129
+ ...icon ? { icon } : {},
130
+ ...unit ? { unit } : {},
131
+ baseUrl,
132
+ series: chartSeries,
133
+ range,
134
+ refreshMs,
135
+ expandable: false,
136
+ fetcher
137
+ }
138
+ ) })
139
+ ] });
140
+ }
141
+ export {
142
+ TimeseriesGauge
143
+ };
144
+ //# sourceMappingURL=TimeseriesGauge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimeseriesGauge.js","sources":["../../src/data/TimeseriesGauge.tsx"],"sourcesContent":["import { useQueries } from \"@tanstack/react-query\";\nimport { useMemo, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { formatUnit } from \"../lib/format\";\nimport { Modal } from \"../overlay/Modal\";\nimport { UiFullscreen } from \"../icons\";\nimport { Icon, type StaticIconComponent } from \"./Icon\";\nimport {\n TimeseriesPanel,\n type TimeseriesResponse,\n type TimeseriesSeries,\n} from \"./TimeseriesPanel\";\n\n/** A metric series whose latest value drives the gauge fill or its maximum. */\nexport interface GaugeSeries {\n /** Metric id appended to the gauge's baseUrl, e.g. \"k8s.statefulset.cycle.cpu.usage\". */\n id: string;\n /** Maps the latest value before display (e.g. unit scaling). Identity when omitted. */\n transform?: (value: number) => number;\n}\n\nexport interface TimeseriesGaugeProps {\n /** Common prefix; the value/max requests are `baseUrl + id`. */\n baseUrl?: string;\n /** The metric whose latest value fills the gauge. */\n value: GaugeSeries;\n /**\n * The gauge maximum. Either a metric series (its latest value, e.g. a `.limit`\n * metric) or a fixed number. When omitted, or when the resolved max is 0, the\n * gauge shows the raw value with no fill (an unbounded reading).\n */\n max?: GaugeSeries | number;\n title: string;\n /** Iconify name or static icon component shown beside the label. */\n icon?: string | StaticIconComponent;\n /** Grafana-style unit key for the readout (e.g. \"bytes\", \"percent\", \"short\", \"ms\"). */\n unit?: string;\n /** Look-back window for the expand chart, 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 /** Show an expand button that opens the value/max time-series chart in a modal. Default true. */\n expandable?: boolean;\n /**\n * Utilisation thresholds (percent of max) at which the arc turns amber then\n * red. Defaults to [75, 90].\n */\n thresholds?: [warning: number, danger: number];\n /**\n * What to print in the centre: the formatted value (default) or the\n * utilisation percentage of max (e.g. CPU usage out of its millicore limit).\n */\n centerDisplay?: \"value\" | \"percent\";\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 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\n// Half-gauge SVG geometry: a 100×50 viewBox semicircle (radius 40, centre 50,50)\n// swept 180°. The arc length is π·r; stroke-dashoffset reveals value/max of it.\nconst GAUGE_RADIUS = 40;\nconst GAUGE_ARC = Math.PI * GAUGE_RADIUS;\nconst GAUGE_PATH = `M 10 50 A ${GAUGE_RADIUS} ${GAUGE_RADIUS} 0 0 1 90 50`;\n\nfunction toneClass(pct: number, [warn, danger]: [number, number]): string {\n if (pct >= danger) return \"text-red-500\";\n if (pct >= warn) return \"text-amber-500\";\n return \"text-emerald-500\";\n}\n\nfunction latestValue(resp: TimeseriesResponse | undefined): number | undefined {\n const points = resp?.points;\n if (!points || points.length === 0) return undefined;\n return points[points.length - 1]?.value;\n}\n\nfunction GaugeIcon({ icon }: { icon: string | StaticIconComponent }) {\n if (typeof icon === \"string\") {\n return <Icon name={icon} width={14} height={14} className=\"text-muted-foreground\" />;\n }\n return <Icon icon={icon} width={14} height={14} className=\"text-muted-foreground\" />;\n}\n\n/**\n * TimeseriesGauge renders a half (180°) radial gauge whose fill is the latest\n * value of a metric over its maximum (a `.limit`-style metric or a fixed number),\n * both read live from the timeseries store. The centre shows the utilisation\n * percentage, with the value/max caption below; the arc colour crosses warning\n * and danger thresholds. An expand button opens the full value/max time-series\n * chart in a modal (a `TimeseriesPanel`), so the gauge gives the at-a-glance\n * reading and the chart the trend.\n */\nexport function TimeseriesGauge({\n baseUrl = \"\",\n value,\n max,\n title,\n icon,\n unit,\n range = \"1h\",\n refreshMs = 5000,\n expandable = true,\n thresholds = [75, 90],\n centerDisplay = \"value\",\n fetcher = defaultFetcher,\n className,\n}: TimeseriesGaugeProps) {\n const [expanded, setExpanded] = useState(false);\n\n const maxIsSeries = typeof max === \"object\";\n const maxSeries = maxIsSeries ? max : undefined;\n const ids = useMemo(() => {\n const list = [value.id];\n if (maxSeries) list.push(maxSeries.id);\n return list;\n }, [value.id, maxSeries]);\n\n const results = useQueries({\n queries: ids.map((id) => {\n const u = new URL(baseUrl + id, window.location.origin);\n if (range) u.searchParams.set(\"since\", range);\n const requestUrl = u.pathname + u.search;\n return {\n queryKey: [\"timeseries\", requestUrl],\n queryFn: () => fetcher(requestUrl),\n refetchInterval: refreshMs > 0 ? refreshMs : false,\n staleTime: 0,\n retry: 0,\n };\n }),\n });\n\n const rawValue = latestValue(results[0]?.data);\n const hasValue = rawValue !== undefined;\n const usage = value.transform ? value.transform(rawValue ?? 0) : (rawValue ?? 0);\n\n let limit: number | undefined;\n if (typeof max === \"number\") {\n limit = max;\n } else if (maxSeries) {\n const rawMax = latestValue(results[1]?.data);\n limit = rawMax === undefined ? undefined : maxSeries.transform ? maxSeries.transform(rawMax) : rawMax;\n }\n\n const bounded = hasValue && limit !== undefined && limit > 0;\n const pct = bounded ? Math.min(100, Math.round((usage / (limit as number)) * 100)) : 0;\n const tone = toneClass(pct, thresholds);\n\n const chartSeries: TimeseriesSeries[] = useMemo(() => {\n const s: TimeseriesSeries[] = [{ id: value.id, label: \"value\", ...(value.transform ? { transform: value.transform } : {}) }];\n if (maxSeries) s.push({ id: maxSeries.id, label: \"max\", ...(maxSeries.transform ? { transform: maxSeries.transform } : {}) });\n return s;\n }, [value.id, value.transform, maxSeries]);\n\n return (\n <div className={cn(\"flex flex-col items-center gap-1\", className)}>\n <div className=\"relative h-10 w-20\">\n <svg viewBox=\"0 0 100 50\" className=\"h-full w-full overflow-visible\">\n <path d={GAUGE_PATH} fill=\"none\" stroke=\"currentColor\" strokeWidth={9} strokeLinecap=\"round\" className=\"text-muted\" />\n <path\n d={GAUGE_PATH}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={9}\n strokeLinecap=\"round\"\n className={tone}\n strokeDasharray={GAUGE_ARC}\n strokeDashoffset={GAUGE_ARC * (1 - pct / 100)}\n />\n </svg>\n <span className=\"absolute inset-x-0 bottom-0 text-center text-sm font-semibold text-foreground\">\n {centerDisplay === \"percent\"\n ? bounded\n ? `${pct}%`\n : hasValue\n ? \"n/a\"\n : \"—\"\n : hasValue\n ? formatUnit(usage, unit)\n : \"—\"}\n </span>\n {expandable && (\n <button\n type=\"button\"\n aria-label=\"Expand chart\"\n onClick={() => setExpanded(true)}\n className=\"absolute right-0 top-0 text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={UiFullscreen} width={12} height={12} />\n </button>\n )}\n </div>\n <div className=\"flex items-center gap-1 text-xs text-muted-foreground\">\n {icon ? <GaugeIcon icon={icon} /> : null}\n <span>{title}</span>\n </div>\n\n {expandable && (\n <Modal open={expanded} onClose={() => setExpanded(false)} title={title} size=\"xl\">\n <TimeseriesPanel\n title={title}\n {...(icon ? { icon } : {})}\n {...(unit ? { unit } : {})}\n baseUrl={baseUrl}\n series={chartSeries}\n range={range}\n refreshMs={refreshMs}\n expandable={false}\n fetcher={fetcher}\n />\n </Modal>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AA0DA,MAAM,iBAAiB,OAAO,QAA6C;AACzE,QAAM,MAAM,MAAM,MAAM,GAAG;AAC3B,MAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,2BAA2B,IAAI,MAAM,EAAE;AACpE,SAAO,IAAI,KAAA;AACb;AAIA,MAAM,eAAe;AACrB,MAAM,YAAY,KAAK,KAAK;AAC5B,MAAM,aAAa,aAAa,YAAY,IAAI,YAAY;AAE5D,SAAS,UAAU,KAAa,CAAC,MAAM,MAAM,GAA6B;AACxE,MAAI,OAAO,OAAQ,QAAO;AAC1B,MAAI,OAAO,KAAM,QAAO;AACxB,SAAO;AACT;AAEA,SAAS,YAAY,MAA0D;;AAC7E,QAAM,SAAS,6BAAM;AACrB,MAAI,CAAC,UAAU,OAAO,WAAW,EAAG,QAAO;AAC3C,UAAO,YAAO,OAAO,SAAS,CAAC,MAAxB,mBAA2B;AACpC;AAEA,SAAS,UAAU,EAAE,QAAgD;AACnE,MAAI,OAAO,SAAS,UAAU;AAC5B,WAAO,oBAAC,QAAK,MAAM,MAAM,OAAO,IAAI,QAAQ,IAAI,WAAU,wBAAA,CAAwB;AAAA,EACpF;AACA,SAAO,oBAAC,QAAK,MAAY,OAAO,IAAI,QAAQ,IAAI,WAAU,yBAAwB;AACpF;AAWO,SAAS,gBAAgB;AAAA,EAC9B,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,aAAa,CAAC,IAAI,EAAE;AAAA,EACpB,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV;AACF,GAAyB;;AACvB,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAE9C,QAAM,cAAc,OAAO,QAAQ;AACnC,QAAM,YAAY,cAAc,MAAM;AACtC,QAAM,MAAM,QAAQ,MAAM;AACxB,UAAM,OAAO,CAAC,MAAM,EAAE;AACtB,QAAI,UAAW,MAAK,KAAK,UAAU,EAAE;AACrC,WAAO;AAAA,EACT,GAAG,CAAC,MAAM,IAAI,SAAS,CAAC;AAExB,QAAM,UAAU,WAAW;AAAA,IACzB,SAAS,IAAI,IAAI,CAAC,OAAO;AACvB,YAAM,IAAI,IAAI,IAAI,UAAU,IAAI,OAAO,SAAS,MAAM;AACtD,UAAI,MAAO,GAAE,aAAa,IAAI,SAAS,KAAK;AAC5C,YAAM,aAAa,EAAE,WAAW,EAAE;AAClC,aAAO;AAAA,QACL,UAAU,CAAC,cAAc,UAAU;AAAA,QACnC,SAAS,MAAM,QAAQ,UAAU;AAAA,QACjC,iBAAiB,YAAY,IAAI,YAAY;AAAA,QAC7C,WAAW;AAAA,QACX,OAAO;AAAA,MAAA;AAAA,IAEX,CAAC;AAAA,EAAA,CACF;AAED,QAAM,WAAW,aAAY,aAAQ,CAAC,MAAT,mBAAY,IAAI;AAC7C,QAAM,WAAW,aAAa;AAC9B,QAAM,QAAQ,MAAM,YAAY,MAAM,UAAU,YAAY,CAAC,IAAK,YAAY;AAE9E,MAAI;AACJ,MAAI,OAAO,QAAQ,UAAU;AAC3B,YAAQ;AAAA,EACV,WAAW,WAAW;AACpB,UAAM,SAAS,aAAY,aAAQ,CAAC,MAAT,mBAAY,IAAI;AAC3C,YAAQ,WAAW,SAAY,SAAY,UAAU,YAAY,UAAU,UAAU,MAAM,IAAI;AAAA,EACjG;AAEA,QAAM,UAAU,YAAY,UAAU,UAAa,QAAQ;AAC3D,QAAM,MAAM,UAAU,KAAK,IAAI,KAAK,KAAK,MAAO,QAAS,QAAoB,GAAG,CAAC,IAAI;AACrF,QAAM,OAAO,UAAU,KAAK,UAAU;AAEtC,QAAM,cAAkC,QAAQ,MAAM;AACpD,UAAM,IAAwB,CAAC,EAAE,IAAI,MAAM,IAAI,OAAO,SAAS,GAAI,MAAM,YAAY,EAAE,WAAW,MAAM,cAAc,CAAA,GAAK;AAC3H,QAAI,UAAW,GAAE,KAAK,EAAE,IAAI,UAAU,IAAI,OAAO,OAAO,GAAI,UAAU,YAAY,EAAE,WAAW,UAAU,cAAc,CAAA,GAAK;AAC5H,WAAO;AAAA,EACT,GAAG,CAAC,MAAM,IAAI,MAAM,WAAW,SAAS,CAAC;AAEzC,8BACG,OAAA,EAAI,WAAW,GAAG,oCAAoC,SAAS,GAC9D,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,sBACb,UAAA;AAAA,MAAA,qBAAC,OAAA,EAAI,SAAQ,cAAa,WAAU,kCAClC,UAAA;AAAA,QAAA,oBAAC,QAAA,EAAK,GAAG,YAAY,MAAK,QAAO,QAAO,gBAAe,aAAa,GAAG,eAAc,SAAQ,WAAU,cAAa;AAAA,QACpH;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,GAAG;AAAA,YACH,MAAK;AAAA,YACL,QAAO;AAAA,YACP,aAAa;AAAA,YACb,eAAc;AAAA,YACd,WAAW;AAAA,YACX,iBAAiB;AAAA,YACjB,kBAAkB,aAAa,IAAI,MAAM;AAAA,UAAA;AAAA,QAAA;AAAA,MAC3C,GACF;AAAA,0BACC,QAAA,EAAK,WAAU,iFACb,UAAA,kBAAkB,YACf,UACE,GAAG,GAAG,MACN,WACE,QACA,MACJ,WACE,WAAW,OAAO,IAAI,IACtB,KACR;AAAA,MACC,cACC;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,cAAW;AAAA,UACX,SAAS,MAAM,YAAY,IAAI;AAAA,UAC/B,WAAU;AAAA,UAEV,8BAAC,MAAA,EAAK,MAAM,cAAc,OAAO,IAAI,QAAQ,GAAA,CAAI;AAAA,QAAA;AAAA,MAAA;AAAA,IACnD,GAEJ;AAAA,IACA,qBAAC,OAAA,EAAI,WAAU,yDACZ,UAAA;AAAA,MAAA,OAAO,oBAAC,WAAA,EAAU,KAAA,CAAY,IAAK;AAAA,MACpC,oBAAC,UAAM,UAAA,MAAA,CAAM;AAAA,IAAA,GACf;AAAA,IAEC,cACC,oBAAC,OAAA,EAAM,MAAM,UAAU,SAAS,MAAM,YAAY,KAAK,GAAG,OAAc,MAAK,MAC3E,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACC,GAAI,OAAO,EAAE,KAAA,IAAS,CAAA;AAAA,QACtB,GAAI,OAAO,EAAE,KAAA,IAAS,CAAA;AAAA,QACvB;AAAA,QACA,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,QACA,YAAY;AAAA,QACZ;AAAA,MAAA;AAAA,IAAA,EACF,CACF;AAAA,EAAA,GAEJ;AAEJ;"}
package/dist/data.cjs CHANGED
@@ -27,6 +27,9 @@ const SignedDeltaBar = require("./data/SignedDeltaBar.cjs");
27
27
  const SortableHeader = require("./data/SortableHeader.cjs");
28
28
  const TabButton = require("./data/TabButton.cjs");
29
29
  const TimeseriesPanel = require("./data/TimeseriesPanel.cjs");
30
+ const TimeseriesGauge = require("./data/TimeseriesGauge.cjs");
31
+ const TimeseriesCoreBars = require("./data/TimeseriesCoreBars.cjs");
32
+ const StatusBreakdown = require("./data/StatusBreakdown.cjs");
30
33
  const Tree = require("./data/Tree.cjs");
31
34
  const TreeNode = require("./data/TreeNode.cjs");
32
35
  const TreeGroupHeader = require("./data/TreeGroupHeader.cjs");
@@ -353,6 +356,12 @@ exports.TabButton = TabButton.TabButton;
353
356
  exports.TimeseriesPanel = TimeseriesPanel.TimeseriesPanel;
354
357
  exports.resolveBreakdown = TimeseriesPanel.resolveBreakdown;
355
358
  exports.resolveSeries = TimeseriesPanel.resolveSeries;
359
+ exports.TimeseriesGauge = TimeseriesGauge.TimeseriesGauge;
360
+ exports.TimeseriesCoreBars = TimeseriesCoreBars.TimeseriesCoreBars;
361
+ exports.deriveCoreBars = TimeseriesCoreBars.deriveCoreBars;
362
+ exports.StackedStatusBar = StatusBreakdown.StackedStatusBar;
363
+ exports.StatusRows = StatusBreakdown.StatusRows;
364
+ exports.segment = StatusBreakdown.segment;
356
365
  exports.Tree = Tree.Tree;
357
366
  exports.TreeNode = TreeNode.TreeNode;
358
367
  exports.TreeGroupHeader = TreeGroupHeader.TreeGroupHeader;
package/dist/data.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"data.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"data.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/data.d.ts CHANGED
@@ -26,6 +26,9 @@ export { SignedDeltaBar, type SignedDeltaBarProps } from './data/SignedDeltaBar'
26
26
  export { SortableHeader, type SortableHeaderProps } from './data/SortableHeader';
27
27
  export { TabButton, type TabButtonProps } from './data/TabButton';
28
28
  export { TimeseriesPanel, resolveSeries, resolveBreakdown, type BreakdownItem, type ResolvedSeries, type TimeseriesPanelProps, type TimeseriesPoint, type TimeseriesResponse, type TimeseriesSeries, } from './data/TimeseriesPanel';
29
+ export { TimeseriesGauge, type GaugeSeries, type TimeseriesGaugeProps, } from './data/TimeseriesGauge';
30
+ export { TimeseriesCoreBars, deriveCoreBars, type TimeseriesCoreBarsProps, type CoreBar, type CoreBarsModel, } from './data/TimeseriesCoreBars';
31
+ export { StatusRows, StackedStatusBar, segment, type StatusSegment, type StatusRenderLink, } from './data/StatusBreakdown';
29
32
  export { Tree, type TreeProps } from './data/Tree';
30
33
  export { TreeNode, type TreeNodeProps, type TreeRowContext } from './data/TreeNode';
31
34
  export { TreeGroupHeader, type TreeGroupHeaderProps } from './data/TreeGroupHeader';
@@ -1 +1 @@
1
- {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../src/data.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../src/data.ts"],"names":[],"mappings":"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"}
package/dist/data.js CHANGED
@@ -25,6 +25,9 @@ import { SignedDeltaBar } from "./data/SignedDeltaBar.js";
25
25
  import { SortableHeader } from "./data/SortableHeader.js";
26
26
  import { TabButton } from "./data/TabButton.js";
27
27
  import { TimeseriesPanel, resolveBreakdown, resolveSeries } from "./data/TimeseriesPanel.js";
28
+ import { TimeseriesGauge } from "./data/TimeseriesGauge.js";
29
+ import { TimeseriesCoreBars, deriveCoreBars } from "./data/TimeseriesCoreBars.js";
30
+ import { StackedStatusBar, StatusRows, segment } from "./data/StatusBreakdown.js";
28
31
  import { Tree } from "./data/Tree.js";
29
32
  import { TreeNode } from "./data/TreeNode.js";
30
33
  import { TreeGroupHeader } from "./data/TreeGroupHeader.js";
@@ -338,12 +341,16 @@ export {
338
341
  SignedDeltaBar,
339
342
  SortableHeader,
340
343
  StackTrace,
344
+ StackedStatusBar,
341
345
  StatusDot,
346
+ StatusRows,
342
347
  TabButton,
343
348
  TagActionsProvider,
344
349
  TagList,
345
350
  TaskManager,
346
351
  TaskProgress,
352
+ TimeseriesCoreBars,
353
+ TimeseriesGauge,
347
354
  TimeseriesPanel,
348
355
  Timestamp,
349
356
  Tree,
@@ -848,6 +855,7 @@ export {
848
855
  countProcesses,
849
856
  countStackByState,
850
857
  countThreadsByState,
858
+ deriveCoreBars,
851
859
  detectDumpFormat,
852
860
  findProcessByPID,
853
861
  formatPropertyLabel,
@@ -875,6 +883,7 @@ export {
875
883
  processStateIcon,
876
884
  resolveBreakdown,
877
885
  resolveSeries,
886
+ segment,
878
887
  setFallbackIconProvider,
879
888
  splitTagToken,
880
889
  tagActionsFromRecord,
package/dist/data.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"data.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"data.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.cjs CHANGED
@@ -56,6 +56,9 @@ const SignedDeltaBar = require("./data/SignedDeltaBar.cjs");
56
56
  const SortableHeader = require("./data/SortableHeader.cjs");
57
57
  const TabButton = require("./data/TabButton.cjs");
58
58
  const TimeseriesPanel = require("./data/TimeseriesPanel.cjs");
59
+ const TimeseriesGauge = require("./data/TimeseriesGauge.cjs");
60
+ const TimeseriesCoreBars = require("./data/TimeseriesCoreBars.cjs");
61
+ const StatusBreakdown = require("./data/StatusBreakdown.cjs");
59
62
  const Tree = require("./data/Tree.cjs");
60
63
  const TreeNode = require("./data/TreeNode.cjs");
61
64
  const TreeGroupHeader = require("./data/TreeGroupHeader.cjs");
@@ -442,6 +445,12 @@ exports.TabButton = TabButton.TabButton;
442
445
  exports.TimeseriesPanel = TimeseriesPanel.TimeseriesPanel;
443
446
  exports.resolveBreakdown = TimeseriesPanel.resolveBreakdown;
444
447
  exports.resolveSeries = TimeseriesPanel.resolveSeries;
448
+ exports.TimeseriesGauge = TimeseriesGauge.TimeseriesGauge;
449
+ exports.TimeseriesCoreBars = TimeseriesCoreBars.TimeseriesCoreBars;
450
+ exports.deriveCoreBars = TimeseriesCoreBars.deriveCoreBars;
451
+ exports.StackedStatusBar = StatusBreakdown.StackedStatusBar;
452
+ exports.StatusRows = StatusBreakdown.StatusRows;
453
+ exports.segment = StatusBreakdown.segment;
445
454
  exports.Tree = Tree.Tree;
446
455
  exports.TreeNode = TreeNode.TreeNode;
447
456
  exports.TreeGroupHeader = TreeGroupHeader.TreeGroupHeader;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.js CHANGED
@@ -54,6 +54,9 @@ import { SignedDeltaBar } from "./data/SignedDeltaBar.js";
54
54
  import { SortableHeader } from "./data/SortableHeader.js";
55
55
  import { TabButton } from "./data/TabButton.js";
56
56
  import { TimeseriesPanel, resolveBreakdown, resolveSeries } from "./data/TimeseriesPanel.js";
57
+ import { TimeseriesGauge } from "./data/TimeseriesGauge.js";
58
+ import { TimeseriesCoreBars, deriveCoreBars } from "./data/TimeseriesCoreBars.js";
59
+ import { StackedStatusBar, StatusRows, segment } from "./data/StatusBreakdown.js";
57
60
  import { Tree } from "./data/Tree.js";
58
61
  import { TreeNode } from "./data/TreeNode.js";
59
62
  import { TreeGroupHeader } from "./data/TreeGroupHeader.js";
@@ -420,7 +423,9 @@ export {
420
423
  SplitButton,
421
424
  SplitPane,
422
425
  StackTrace,
426
+ StackedStatusBar,
423
427
  StatusDot,
428
+ StatusRows,
424
429
  TabButton,
425
430
  TagActionsProvider,
426
431
  TagList,
@@ -429,6 +434,8 @@ export {
429
434
  ThemeProvider,
430
435
  ThemeSwitcher,
431
436
  TimeRange,
437
+ TimeseriesCoreBars,
438
+ TimeseriesGauge,
432
439
  TimeseriesPanel,
433
440
  Timestamp,
434
441
  Tree,
@@ -936,6 +943,7 @@ export {
936
943
  countProcesses,
937
944
  countStackByState,
938
945
  countThreadsByState,
946
+ deriveCoreBars,
939
947
  detectDumpFormat,
940
948
  filterOperationsByDomain,
941
949
  findDetailEndpoint,
@@ -977,6 +985,7 @@ export {
977
985
  resolveBreakdown,
978
986
  resolveSeries,
979
987
  resolveSize,
988
+ segment,
980
989
  setFallbackIconProvider,
981
990
  splitTagToken,
982
991
  tagActionsFromRecord,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}