@flanksource/clicky-ui 0.3.37 → 0.3.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +36 -0
  2. package/dist/data/TimeseriesCoreBars.cjs +10 -36
  3. package/dist/data/TimeseriesCoreBars.cjs.map +1 -1
  4. package/dist/data/TimeseriesCoreBars.d.ts +1 -1
  5. package/dist/data/TimeseriesCoreBars.d.ts.map +1 -1
  6. package/dist/data/TimeseriesCoreBars.js +8 -34
  7. package/dist/data/TimeseriesCoreBars.js.map +1 -1
  8. package/dist/data/TimeseriesGauge.cjs +12 -36
  9. package/dist/data/TimeseriesGauge.cjs.map +1 -1
  10. package/dist/data/TimeseriesGauge.d.ts +14 -4
  11. package/dist/data/TimeseriesGauge.d.ts.map +1 -1
  12. package/dist/data/TimeseriesGauge.js +10 -34
  13. package/dist/data/TimeseriesGauge.js.map +1 -1
  14. package/dist/data/TimeseriesPanel.cjs +82 -39
  15. package/dist/data/TimeseriesPanel.cjs.map +1 -1
  16. package/dist/data/TimeseriesPanel.d.ts +1 -1
  17. package/dist/data/TimeseriesPanel.d.ts.map +1 -1
  18. package/dist/data/TimeseriesPanel.js +82 -39
  19. package/dist/data/TimeseriesPanel.js.map +1 -1
  20. package/dist/data/TimeseriesPanel.model.cjs +1 -0
  21. package/dist/data/TimeseriesPanel.model.cjs.map +1 -1
  22. package/dist/data/TimeseriesPanel.model.d.ts +38 -2
  23. package/dist/data/TimeseriesPanel.model.d.ts.map +1 -1
  24. package/dist/data/TimeseriesPanel.model.js +1 -0
  25. package/dist/data/TimeseriesPanel.model.js.map +1 -1
  26. package/dist/data/WorkloadCard.cjs +67 -31
  27. package/dist/data/WorkloadCard.cjs.map +1 -1
  28. package/dist/data/WorkloadCard.d.ts +19 -6
  29. package/dist/data/WorkloadCard.d.ts.map +1 -1
  30. package/dist/data/WorkloadCard.js +69 -33
  31. package/dist/data/WorkloadCard.js.map +1 -1
  32. package/dist/data/WorkloadCard.model.cjs +16 -10
  33. package/dist/data/WorkloadCard.model.cjs.map +1 -1
  34. package/dist/data/WorkloadCard.model.d.ts +29 -1
  35. package/dist/data/WorkloadCard.model.d.ts.map +1 -1
  36. package/dist/data/WorkloadCard.model.js +17 -11
  37. package/dist/data/WorkloadCard.model.js.map +1 -1
  38. package/dist/data/timeseries-query.cjs +51 -0
  39. package/dist/data/timeseries-query.cjs.map +1 -0
  40. package/dist/data/timeseries-query.d.ts +31 -0
  41. package/dist/data/timeseries-query.d.ts.map +1 -0
  42. package/dist/data/timeseries-query.js +51 -0
  43. package/dist/data/timeseries-query.js.map +1 -0
  44. package/dist/data/version-info.cjs +3 -3
  45. package/dist/data/version-info.js +3 -3
  46. package/dist/data.cjs +1 -0
  47. package/dist/data.cjs.map +1 -1
  48. package/dist/data.d.ts +2 -2
  49. package/dist/data.d.ts.map +1 -1
  50. package/dist/data.js +3 -2
  51. package/dist/index.cjs +1 -0
  52. package/dist/index.cjs.map +1 -1
  53. package/dist/index.js +2 -1
  54. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"TimeseriesPanel.js","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\";\nimport {\n mergeSeries,\n resolveBreakdown,\n resolveSeries,\n type BreakdownItem,\n type MergedRow,\n type ResolvedSeries,\n type TimeseriesPanelProps,\n type TimeseriesResponse,\n} from \"./TimeseriesPanel.model\";\n\nexport type { AxisAssignment, AxisOrientation, AxisSpec } from \"./TimeseriesPanel.axes\";\nexport type {\n BreakdownItem,\n MergedRow,\n ResolvedSeries,\n TimeseriesPanelProps,\n TimeseriesPoint,\n TimeseriesResponse,\n TimeseriesSeries,\n} from \"./TimeseriesPanel.model\";\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\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":["_a"],"mappings":"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"}
1
+ {"version":3,"file":"TimeseriesPanel.js","sources":["../../src/data/TimeseriesPanel.tsx"],"sourcesContent":["import { 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\";\nimport {\n mergeSeries,\n resolveBreakdown,\n resolveSeries,\n type BreakdownItem,\n type MergedRow,\n type ResolvedSeries,\n type TimeseriesPanelProps,\n type TimeseriesReferenceLine,\n} from \"./TimeseriesPanel.model\";\nimport { defaultTimeseriesFetcher, useTimeseriesQueries } from \"./timeseries-query\";\n\nexport type { AxisAssignment, AxisOrientation, AxisSpec } from \"./TimeseriesPanel.axes\";\nexport type {\n BreakdownItem,\n MergedRow,\n ResolvedSeries,\n SeriesLoadContext,\n SeriesLoader,\n TimeseriesPanelProps,\n TimeseriesPoint,\n TimeseriesReferenceLine,\n TimeseriesResponse,\n TimeseriesSeries,\n} from \"./TimeseriesPanel.model\";\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\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 referenceLines,\n fetcher = defaultTimeseriesFetcher,\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 results = useTimeseriesQueries(\n series.map((s) => ({ id: s.key, load: s.load })),\n { baseUrl: props.baseUrl ?? \"\", range, refreshMs, fetcher },\n );\n const refs = referenceLines ?? [];\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\n rows={rows}\n series={series}\n variant={chartVariant}\n unit={unit}\n showZeroLine={hasMirrored}\n referenceLines={refs}\n />\n )}\n </div>\n {!breakdown && !isError && !isLoading && rows.length >= 2 && series.length + refs.length > 1 ? (\n <ChartLegend series={series} rows={rows} referenceLines={refs} unit={unit} />\n ) : null}\n {canExpand ? (\n <Modal open={expanded} onClose={() => setExpanded(false)} title={title} size=\"xl\">\n <div style={{ height: 360 }}>\n <Chart\n rows={rows}\n series={series}\n variant={expandVariant}\n unit={unit}\n showZeroLine={hasMirrored}\n referenceLines={refs}\n />\n </div>\n {series.length + refs.length > 1 ? (\n <ChartLegend series={series} rows={rows} referenceLines={refs} unit={unit} />\n ) : 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\nconst REFERENCE_LINE_COLOR = \"var(--muted-foreground, #64748b)\";\n\nfunction referenceColor(ref: TimeseriesReferenceLine): string {\n return resolveCssColor(ref.color ?? REFERENCE_LINE_COLOR);\n}\n\n/**\n * Wrapping color-keyed legend for the time-series variants: dot + label + latest\n * value, then each reference line with its fixed value.\n */\nfunction ChartLegend({\n series,\n rows,\n referenceLines,\n unit,\n}: {\n series: ResolvedSeries[];\n rows: MergedRow[];\n referenceLines: TimeseriesReferenceLine[];\n unit: string | undefined;\n}) {\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 {referenceLines.map((ref, i) => (\n <li key={`ref-${i}`} className=\"inline-flex min-w-0 items-center gap-1.5\">\n <span\n className=\"h-0.5 w-2.5 shrink-0\"\n style={{ backgroundColor: referenceColor(ref) }}\n aria-hidden\n />\n <span className=\"truncate text-foreground\">{ref.label}</span>\n <span className=\"tabular-nums\">{formatUnit(ref.value, ref.unit ?? unit)}</span>\n </li>\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 referenceLines,\n}: {\n rows: MergedRow[];\n series: ResolvedSeries[];\n variant: ChartVariant;\n unit: string | undefined;\n showZeroLine: boolean;\n referenceLines: TimeseriesReferenceLine[];\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 // A reference line draws against the axis formatting its unit, else the left.\n const axisOfUnit = (u: string | undefined): \"left\" | \"right\" =>\n yAxes.find((ax) => ax.unit === u)?.id ?? \"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 {referenceLines.map((ref, i) => (\n <ReferenceLine\n key={`ref-${i}`}\n yAxisId={axisOfUnit(ref.unit ?? unit)}\n y={ref.value}\n stroke={referenceColor(ref)}\n strokeDasharray=\"4 4\"\n ifOverflow=\"extendDomain\"\n />\n ))}\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":["_a"],"mappings":"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"}
@@ -20,6 +20,7 @@ function resolveSeries(props) {
20
20
  color: s.color ?? paletteColor(i),
21
21
  ...s.unit ?? props.unit ? { unit: s.unit ?? props.unit } : {},
22
22
  ...s.transform ? { transform: s.transform } : {},
23
+ ...s.load ? { load: s.load } : {},
23
24
  ...s.current !== void 0 ? { current: s.current } : {}
24
25
  }));
25
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TimeseriesPanel.model.cjs","sources":["../../src/data/TimeseriesPanel.model.ts"],"sourcesContent":["import type { StaticIconComponent } from \"./Icon\";\nimport { latestOf } 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 or a Tailwind color class.\n * Defaults cycle a small palette by index.\n */\n color?: string;\n /**\n * Grafana-style unit key for this series' readout/tooltip. Falls back to the\n * panel-level `unit`.\n */\n unit?: string;\n /**\n * Instantaneous value for the `breakdown` variant. When omitted, the\n * breakdown falls back to the latest polled point.\n */\n current?: number;\n}\n\nexport interface TimeseriesPanelProps {\n url?: string;\n baseUrl?: string;\n series?: TimeseriesSeries[];\n title: string;\n icon?: string | StaticIconComponent;\n unit?: string;\n range?: string;\n refreshMs?: number;\n height?: number;\n variant?: \"area\" | \"line\" | \"stacked\" | \"breakdown\";\n expandVariant?: \"area\" | \"line\" | \"stacked\";\n total?: number;\n expandable?: boolean;\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. */\nexport interface ResolvedSeries {\n key: string;\n url: string;\n label: string;\n color: string;\n unit?: string;\n transform?: (value: number) => number;\n current?: number;\n}\n\n/** Normalises the single-url and baseUrl+series forms to one list. */\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. */\nexport interface BreakdownItem {\n key: string;\n label: string;\n color: string;\n unit?: string;\n value: number;\n percent: number;\n}\n\n/**\n * One row of the merged chart data: the timestamp in `at` plus each series'\n * value under its key.\n */\nexport interface MergedRow {\n at: string;\n [seriesKey: string]: string | number | undefined;\n}\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\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"],"names":["latestOf"],"mappings":";;;AA2DA,MAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,aAAa,OAAuB;AAC3C,SAAO,eAAe,QAAQ,eAAe,MAAM,KAAK,eAAe,CAAC;AAC1E;AAcO,SAAS,cAAc,OAA+C;AAC3E,QAAM,OAAO,MAAM,WAAW;AAC9B,QAAM,OAAO,MAAM,WAAW,MAAM,MAAM,CAAC,EAAE,IAAI,MAAM,IAAA,CAAK,IAAI,CAAA;AAChE,SAAO,KAAK,IAAI,CAAC,GAAG,OAAO;AAAA,IACzB,KAAK,EAAE;AAAA,IACP,KAAK,OAAO,EAAE;AAAA,IACd,OAAO,EAAE,SAAS,EAAE;AAAA,IACpB,OAAO,EAAE,SAAS,aAAa,CAAC;AAAA,IAChC,GAAI,EAAE,QAAQ,MAAM,OAAO,EAAE,MAAM,EAAE,QAAQ,MAAM,KAAA,IAAS,CAAA;AAAA,IAC5D,GAAI,EAAE,YAAY,EAAE,WAAW,EAAE,UAAA,IAAc,CAAA;AAAA,IAC/C,GAAI,EAAE,YAAY,SAAY,EAAE,SAAS,EAAE,YAAY,CAAA;AAAA,EAAC,EACxD;AACJ;AAqBO,SAAS,iBACd,QACA,MACA,OAC2C;AAC3C,QAAM,YAAY,OAAO,IAAI,CAAC,OAAO,EAAE,GAAG,OAAO,EAAE,WAAWA,qBAAAA,SAAS,MAAM,EAAE,GAAG,KAAK,IAAI;AAC3F,QAAM,QAAQ,SAAS,UAAU,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,OAAO,CAAC;AACpE,QAAM,QAAQ,UAAU,IAAI,CAAC,EAAE,GAAG,aAAa;AAAA,IAC7C,KAAK,EAAE;AAAA,IACP,OAAO,EAAE;AAAA,IACT,OAAO,EAAE;AAAA,IACT,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAA,IAAS,CAAA;AAAA,IAChC;AAAA,IACA,SAAS,QAAQ,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,GAAI,QAAQ,QAAS,GAAG,CAAC,IAAI;AAAA,EAAA,EACzE;AACF,SAAO,EAAE,OAAO,OAAO,MAAA;AACzB;AAEO,SAAS,YACd,QACA,gBACa;AACb,QAAM,2BAAW,IAAA;AACjB,SAAO,QAAQ,CAAC,GAAG,MAAM;AACvB,eAAW,KAAK,eAAe,CAAC,KAAK,CAAA,GAAI;AACvC,YAAM,MAAM,KAAK,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,GAAA;AACtC,UAAI,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,KAAK,IAAI,EAAE;AACpD,WAAK,IAAI,EAAE,IAAI,GAAG;AAAA,IACpB;AAAA,EACF,CAAC;AACD,SAAO,CAAC,GAAG,KAAK,OAAA,CAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,GAAG,cAAc,EAAE,EAAE,CAAC;AACnE;;;;"}
1
+ {"version":3,"file":"TimeseriesPanel.model.cjs","sources":["../../src/data/TimeseriesPanel.model.ts"],"sourcesContent":["import type { StaticIconComponent } from \"./Icon\";\nimport { latestOf } 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/** What a {@link SeriesLoader} receives from the widget polling it. */\nexport interface SeriesLoadContext {\n /** The widget's look-back window, e.g. \"1h\". */\n range: string;\n /** Aborted when the query is cancelled, e.g. the widget unmounts mid-request. */\n signal: AbortSignal;\n}\n\n/**\n * Loads a series directly, instead of the widget requesting `baseUrl + id`\n * through its `fetcher`. Called again whenever the range changes and on every\n * poll.\n */\nexport type SeriesLoader = (ctx: SeriesLoadContext) => Promise<TimeseriesResponse>;\n\n/**\n * One metric plotted on a panel. Its request URL is `baseUrl + id`, unless a\n * `load` function supplies the data.\n */\nexport interface TimeseriesSeries {\n /**\n * Metric id appended to the panel's baseUrl, e.g. \"sqlserver.iops.read\".\n * With `load`, the id is the series' cache identity instead: loaded series\n * are cached under `[\"timeseries\", \"load\", id, range]`, so it must be unique\n * per data source (two series sharing an id share one cached response).\n */\n id: string;\n /** Loads the series' points; when set, `baseUrl` and `fetcher` are not used. */\n load?: SeriesLoader;\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 or a Tailwind color class.\n * Defaults cycle a small palette by index.\n */\n color?: string;\n /**\n * Grafana-style unit key for this series' readout/tooltip. Falls back to the\n * panel-level `unit`.\n */\n unit?: string;\n /**\n * Instantaneous value for the `breakdown` variant. When omitted, the\n * breakdown falls back to the latest polled point.\n */\n current?: number;\n}\n\n/** A horizontal marker line at a fixed value, e.g. a static capacity. */\nexport interface TimeseriesReferenceLine {\n value: number;\n /** Legend label, e.g. \"capacity\". */\n label: string;\n /** CSS color value or Tailwind color class; defaults to the muted foreground. */\n color?: string;\n /** Grafana-style unit key for the legend value; falls back to the panel `unit`. */\n unit?: string;\n}\n\nexport interface TimeseriesPanelProps {\n url?: string;\n baseUrl?: string;\n series?: TimeseriesSeries[];\n title: string;\n icon?: string | StaticIconComponent;\n unit?: string;\n range?: string;\n refreshMs?: number;\n height?: number;\n variant?: \"area\" | \"line\" | \"stacked\" | \"breakdown\";\n expandVariant?: \"area\" | \"line\" | \"stacked\";\n total?: number;\n /** Flat lines drawn across the time-series chart, e.g. a fixed capacity. */\n referenceLines?: TimeseriesReferenceLine[];\n expandable?: boolean;\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. */\nexport interface ResolvedSeries {\n key: string;\n url: string;\n label: string;\n color: string;\n unit?: string;\n transform?: (value: number) => number;\n load?: SeriesLoader;\n current?: number;\n}\n\n/** Normalises the single-url and baseUrl+series forms to one list. */\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.load ? { load: s.load } : {}),\n ...(s.current !== undefined ? { current: s.current } : {}),\n }));\n}\n\n/** One legend/bar row of the breakdown variant. */\nexport interface BreakdownItem {\n key: string;\n label: string;\n color: string;\n unit?: string;\n value: number;\n percent: number;\n}\n\n/**\n * One row of the merged chart data: the timestamp in `at` plus each series'\n * value under its key.\n */\nexport interface MergedRow {\n at: string;\n [seriesKey: string]: string | number | undefined;\n}\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\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"],"names":["latestOf"],"mappings":";;;AAiGA,MAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,aAAa,OAAuB;AAC3C,SAAO,eAAe,QAAQ,eAAe,MAAM,KAAK,eAAe,CAAC;AAC1E;AAeO,SAAS,cAAc,OAA+C;AAC3E,QAAM,OAAO,MAAM,WAAW;AAC9B,QAAM,OAAO,MAAM,WAAW,MAAM,MAAM,CAAC,EAAE,IAAI,MAAM,IAAA,CAAK,IAAI,CAAA;AAChE,SAAO,KAAK,IAAI,CAAC,GAAG,OAAO;AAAA,IACzB,KAAK,EAAE;AAAA,IACP,KAAK,OAAO,EAAE;AAAA,IACd,OAAO,EAAE,SAAS,EAAE;AAAA,IACpB,OAAO,EAAE,SAAS,aAAa,CAAC;AAAA,IAChC,GAAI,EAAE,QAAQ,MAAM,OAAO,EAAE,MAAM,EAAE,QAAQ,MAAM,KAAA,IAAS,CAAA;AAAA,IAC5D,GAAI,EAAE,YAAY,EAAE,WAAW,EAAE,UAAA,IAAc,CAAA;AAAA,IAC/C,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAA,IAAS,CAAA;AAAA,IAChC,GAAI,EAAE,YAAY,SAAY,EAAE,SAAS,EAAE,YAAY,CAAA;AAAA,EAAC,EACxD;AACJ;AAqBO,SAAS,iBACd,QACA,MACA,OAC2C;AAC3C,QAAM,YAAY,OAAO,IAAI,CAAC,OAAO,EAAE,GAAG,OAAO,EAAE,WAAWA,qBAAAA,SAAS,MAAM,EAAE,GAAG,KAAK,IAAI;AAC3F,QAAM,QAAQ,SAAS,UAAU,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,OAAO,CAAC;AACpE,QAAM,QAAQ,UAAU,IAAI,CAAC,EAAE,GAAG,aAAa;AAAA,IAC7C,KAAK,EAAE;AAAA,IACP,OAAO,EAAE;AAAA,IACT,OAAO,EAAE;AAAA,IACT,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAA,IAAS,CAAA;AAAA,IAChC;AAAA,IACA,SAAS,QAAQ,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,GAAI,QAAQ,QAAS,GAAG,CAAC,IAAI;AAAA,EAAA,EACzE;AACF,SAAO,EAAE,OAAO,OAAO,MAAA;AACzB;AAEO,SAAS,YACd,QACA,gBACa;AACb,QAAM,2BAAW,IAAA;AACjB,SAAO,QAAQ,CAAC,GAAG,MAAM;AACvB,eAAW,KAAK,eAAe,CAAC,KAAK,CAAA,GAAI;AACvC,YAAM,MAAM,KAAK,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,GAAA;AACtC,UAAI,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,KAAK,IAAI,EAAE;AACpD,WAAK,IAAI,EAAE,IAAI,GAAG;AAAA,IACpB;AAAA,EACF,CAAC;AACD,SAAO,CAAC,GAAG,KAAK,OAAA,CAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,GAAG,cAAc,EAAE,EAAE,CAAC;AACnE;;;;"}
@@ -7,10 +7,33 @@ export interface TimeseriesResponse {
7
7
  id: string;
8
8
  points: TimeseriesPoint[];
9
9
  }
10
- /** One metric plotted on a panel. Its request URL is `baseUrl + id`. */
10
+ /** What a {@link SeriesLoader} receives from the widget polling it. */
11
+ export interface SeriesLoadContext {
12
+ /** The widget's look-back window, e.g. "1h". */
13
+ range: string;
14
+ /** Aborted when the query is cancelled, e.g. the widget unmounts mid-request. */
15
+ signal: AbortSignal;
16
+ }
17
+ /**
18
+ * Loads a series directly, instead of the widget requesting `baseUrl + id`
19
+ * through its `fetcher`. Called again whenever the range changes and on every
20
+ * poll.
21
+ */
22
+ export type SeriesLoader = (ctx: SeriesLoadContext) => Promise<TimeseriesResponse>;
23
+ /**
24
+ * One metric plotted on a panel. Its request URL is `baseUrl + id`, unless a
25
+ * `load` function supplies the data.
26
+ */
11
27
  export interface TimeseriesSeries {
12
- /** Metric id appended to the panel's baseUrl, e.g. "sqlserver.iops.read". */
28
+ /**
29
+ * Metric id appended to the panel's baseUrl, e.g. "sqlserver.iops.read".
30
+ * With `load`, the id is the series' cache identity instead: loaded series
31
+ * are cached under `["timeseries", "load", id, range]`, so it must be unique
32
+ * per data source (two series sharing an id share one cached response).
33
+ */
13
34
  id: string;
35
+ /** Loads the series' points; when set, `baseUrl` and `fetcher` are not used. */
36
+ load?: SeriesLoader;
14
37
  /** Tooltip/legend label; defaults to the id. */
15
38
  label?: string;
16
39
  /**
@@ -34,6 +57,16 @@ export interface TimeseriesSeries {
34
57
  */
35
58
  current?: number;
36
59
  }
60
+ /** A horizontal marker line at a fixed value, e.g. a static capacity. */
61
+ export interface TimeseriesReferenceLine {
62
+ value: number;
63
+ /** Legend label, e.g. "capacity". */
64
+ label: string;
65
+ /** CSS color value or Tailwind color class; defaults to the muted foreground. */
66
+ color?: string;
67
+ /** Grafana-style unit key for the legend value; falls back to the panel `unit`. */
68
+ unit?: string;
69
+ }
37
70
  export interface TimeseriesPanelProps {
38
71
  url?: string;
39
72
  baseUrl?: string;
@@ -47,6 +80,8 @@ export interface TimeseriesPanelProps {
47
80
  variant?: "area" | "line" | "stacked" | "breakdown";
48
81
  expandVariant?: "area" | "line" | "stacked";
49
82
  total?: number;
83
+ /** Flat lines drawn across the time-series chart, e.g. a fixed capacity. */
84
+ referenceLines?: TimeseriesReferenceLine[];
50
85
  expandable?: boolean;
51
86
  fetcher?: (url: string) => Promise<TimeseriesResponse>;
52
87
  className?: string;
@@ -59,6 +94,7 @@ export interface ResolvedSeries {
59
94
  color: string;
60
95
  unit?: string;
61
96
  transform?: (value: number) => number;
97
+ load?: SeriesLoader;
62
98
  current?: number;
63
99
  }
64
100
  /** Normalises the single-url and baseUrl+series forms to one list. */
@@ -1 +1 @@
1
- {"version":3,"file":"TimeseriesPanel.model.d.ts","sourceRoot":"","sources":["../../src/data/TimeseriesPanel.model.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TimeseriesPanel.model.d.ts","sourceRoot":"","sources":["../../src/data/TimeseriesPanel.model.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,7 @@ function resolveSeries(props) {
18
18
  color: s.color ?? paletteColor(i),
19
19
  ...s.unit ?? props.unit ? { unit: s.unit ?? props.unit } : {},
20
20
  ...s.transform ? { transform: s.transform } : {},
21
+ ...s.load ? { load: s.load } : {},
21
22
  ...s.current !== void 0 ? { current: s.current } : {}
22
23
  }));
23
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TimeseriesPanel.model.js","sources":["../../src/data/TimeseriesPanel.model.ts"],"sourcesContent":["import type { StaticIconComponent } from \"./Icon\";\nimport { latestOf } 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 or a Tailwind color class.\n * Defaults cycle a small palette by index.\n */\n color?: string;\n /**\n * Grafana-style unit key for this series' readout/tooltip. Falls back to the\n * panel-level `unit`.\n */\n unit?: string;\n /**\n * Instantaneous value for the `breakdown` variant. When omitted, the\n * breakdown falls back to the latest polled point.\n */\n current?: number;\n}\n\nexport interface TimeseriesPanelProps {\n url?: string;\n baseUrl?: string;\n series?: TimeseriesSeries[];\n title: string;\n icon?: string | StaticIconComponent;\n unit?: string;\n range?: string;\n refreshMs?: number;\n height?: number;\n variant?: \"area\" | \"line\" | \"stacked\" | \"breakdown\";\n expandVariant?: \"area\" | \"line\" | \"stacked\";\n total?: number;\n expandable?: boolean;\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. */\nexport interface ResolvedSeries {\n key: string;\n url: string;\n label: string;\n color: string;\n unit?: string;\n transform?: (value: number) => number;\n current?: number;\n}\n\n/** Normalises the single-url and baseUrl+series forms to one list. */\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. */\nexport interface BreakdownItem {\n key: string;\n label: string;\n color: string;\n unit?: string;\n value: number;\n percent: number;\n}\n\n/**\n * One row of the merged chart data: the timestamp in `at` plus each series'\n * value under its key.\n */\nexport interface MergedRow {\n at: string;\n [seriesKey: string]: string | number | undefined;\n}\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\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"],"names":[],"mappings":";AA2DA,MAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,aAAa,OAAuB;AAC3C,SAAO,eAAe,QAAQ,eAAe,MAAM,KAAK,eAAe,CAAC;AAC1E;AAcO,SAAS,cAAc,OAA+C;AAC3E,QAAM,OAAO,MAAM,WAAW;AAC9B,QAAM,OAAO,MAAM,WAAW,MAAM,MAAM,CAAC,EAAE,IAAI,MAAM,IAAA,CAAK,IAAI,CAAA;AAChE,SAAO,KAAK,IAAI,CAAC,GAAG,OAAO;AAAA,IACzB,KAAK,EAAE;AAAA,IACP,KAAK,OAAO,EAAE;AAAA,IACd,OAAO,EAAE,SAAS,EAAE;AAAA,IACpB,OAAO,EAAE,SAAS,aAAa,CAAC;AAAA,IAChC,GAAI,EAAE,QAAQ,MAAM,OAAO,EAAE,MAAM,EAAE,QAAQ,MAAM,KAAA,IAAS,CAAA;AAAA,IAC5D,GAAI,EAAE,YAAY,EAAE,WAAW,EAAE,UAAA,IAAc,CAAA;AAAA,IAC/C,GAAI,EAAE,YAAY,SAAY,EAAE,SAAS,EAAE,YAAY,CAAA;AAAA,EAAC,EACxD;AACJ;AAqBO,SAAS,iBACd,QACA,MACA,OAC2C;AAC3C,QAAM,YAAY,OAAO,IAAI,CAAC,OAAO,EAAE,GAAG,OAAO,EAAE,WAAW,SAAS,MAAM,EAAE,GAAG,KAAK,IAAI;AAC3F,QAAM,QAAQ,SAAS,UAAU,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,OAAO,CAAC;AACpE,QAAM,QAAQ,UAAU,IAAI,CAAC,EAAE,GAAG,aAAa;AAAA,IAC7C,KAAK,EAAE;AAAA,IACP,OAAO,EAAE;AAAA,IACT,OAAO,EAAE;AAAA,IACT,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAA,IAAS,CAAA;AAAA,IAChC;AAAA,IACA,SAAS,QAAQ,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,GAAI,QAAQ,QAAS,GAAG,CAAC,IAAI;AAAA,EAAA,EACzE;AACF,SAAO,EAAE,OAAO,OAAO,MAAA;AACzB;AAEO,SAAS,YACd,QACA,gBACa;AACb,QAAM,2BAAW,IAAA;AACjB,SAAO,QAAQ,CAAC,GAAG,MAAM;AACvB,eAAW,KAAK,eAAe,CAAC,KAAK,CAAA,GAAI;AACvC,YAAM,MAAM,KAAK,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,GAAA;AACtC,UAAI,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,KAAK,IAAI,EAAE;AACpD,WAAK,IAAI,EAAE,IAAI,GAAG;AAAA,IACpB;AAAA,EACF,CAAC;AACD,SAAO,CAAC,GAAG,KAAK,OAAA,CAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,GAAG,cAAc,EAAE,EAAE,CAAC;AACnE;"}
1
+ {"version":3,"file":"TimeseriesPanel.model.js","sources":["../../src/data/TimeseriesPanel.model.ts"],"sourcesContent":["import type { StaticIconComponent } from \"./Icon\";\nimport { latestOf } 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/** What a {@link SeriesLoader} receives from the widget polling it. */\nexport interface SeriesLoadContext {\n /** The widget's look-back window, e.g. \"1h\". */\n range: string;\n /** Aborted when the query is cancelled, e.g. the widget unmounts mid-request. */\n signal: AbortSignal;\n}\n\n/**\n * Loads a series directly, instead of the widget requesting `baseUrl + id`\n * through its `fetcher`. Called again whenever the range changes and on every\n * poll.\n */\nexport type SeriesLoader = (ctx: SeriesLoadContext) => Promise<TimeseriesResponse>;\n\n/**\n * One metric plotted on a panel. Its request URL is `baseUrl + id`, unless a\n * `load` function supplies the data.\n */\nexport interface TimeseriesSeries {\n /**\n * Metric id appended to the panel's baseUrl, e.g. \"sqlserver.iops.read\".\n * With `load`, the id is the series' cache identity instead: loaded series\n * are cached under `[\"timeseries\", \"load\", id, range]`, so it must be unique\n * per data source (two series sharing an id share one cached response).\n */\n id: string;\n /** Loads the series' points; when set, `baseUrl` and `fetcher` are not used. */\n load?: SeriesLoader;\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 or a Tailwind color class.\n * Defaults cycle a small palette by index.\n */\n color?: string;\n /**\n * Grafana-style unit key for this series' readout/tooltip. Falls back to the\n * panel-level `unit`.\n */\n unit?: string;\n /**\n * Instantaneous value for the `breakdown` variant. When omitted, the\n * breakdown falls back to the latest polled point.\n */\n current?: number;\n}\n\n/** A horizontal marker line at a fixed value, e.g. a static capacity. */\nexport interface TimeseriesReferenceLine {\n value: number;\n /** Legend label, e.g. \"capacity\". */\n label: string;\n /** CSS color value or Tailwind color class; defaults to the muted foreground. */\n color?: string;\n /** Grafana-style unit key for the legend value; falls back to the panel `unit`. */\n unit?: string;\n}\n\nexport interface TimeseriesPanelProps {\n url?: string;\n baseUrl?: string;\n series?: TimeseriesSeries[];\n title: string;\n icon?: string | StaticIconComponent;\n unit?: string;\n range?: string;\n refreshMs?: number;\n height?: number;\n variant?: \"area\" | \"line\" | \"stacked\" | \"breakdown\";\n expandVariant?: \"area\" | \"line\" | \"stacked\";\n total?: number;\n /** Flat lines drawn across the time-series chart, e.g. a fixed capacity. */\n referenceLines?: TimeseriesReferenceLine[];\n expandable?: boolean;\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. */\nexport interface ResolvedSeries {\n key: string;\n url: string;\n label: string;\n color: string;\n unit?: string;\n transform?: (value: number) => number;\n load?: SeriesLoader;\n current?: number;\n}\n\n/** Normalises the single-url and baseUrl+series forms to one list. */\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.load ? { load: s.load } : {}),\n ...(s.current !== undefined ? { current: s.current } : {}),\n }));\n}\n\n/** One legend/bar row of the breakdown variant. */\nexport interface BreakdownItem {\n key: string;\n label: string;\n color: string;\n unit?: string;\n value: number;\n percent: number;\n}\n\n/**\n * One row of the merged chart data: the timestamp in `at` plus each series'\n * value under its key.\n */\nexport interface MergedRow {\n at: string;\n [seriesKey: string]: string | number | undefined;\n}\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\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"],"names":[],"mappings":";AAiGA,MAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,aAAa,OAAuB;AAC3C,SAAO,eAAe,QAAQ,eAAe,MAAM,KAAK,eAAe,CAAC;AAC1E;AAeO,SAAS,cAAc,OAA+C;AAC3E,QAAM,OAAO,MAAM,WAAW;AAC9B,QAAM,OAAO,MAAM,WAAW,MAAM,MAAM,CAAC,EAAE,IAAI,MAAM,IAAA,CAAK,IAAI,CAAA;AAChE,SAAO,KAAK,IAAI,CAAC,GAAG,OAAO;AAAA,IACzB,KAAK,EAAE;AAAA,IACP,KAAK,OAAO,EAAE;AAAA,IACd,OAAO,EAAE,SAAS,EAAE;AAAA,IACpB,OAAO,EAAE,SAAS,aAAa,CAAC;AAAA,IAChC,GAAI,EAAE,QAAQ,MAAM,OAAO,EAAE,MAAM,EAAE,QAAQ,MAAM,KAAA,IAAS,CAAA;AAAA,IAC5D,GAAI,EAAE,YAAY,EAAE,WAAW,EAAE,UAAA,IAAc,CAAA;AAAA,IAC/C,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAA,IAAS,CAAA;AAAA,IAChC,GAAI,EAAE,YAAY,SAAY,EAAE,SAAS,EAAE,YAAY,CAAA;AAAA,EAAC,EACxD;AACJ;AAqBO,SAAS,iBACd,QACA,MACA,OAC2C;AAC3C,QAAM,YAAY,OAAO,IAAI,CAAC,OAAO,EAAE,GAAG,OAAO,EAAE,WAAW,SAAS,MAAM,EAAE,GAAG,KAAK,IAAI;AAC3F,QAAM,QAAQ,SAAS,UAAU,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,OAAO,CAAC;AACpE,QAAM,QAAQ,UAAU,IAAI,CAAC,EAAE,GAAG,aAAa;AAAA,IAC7C,KAAK,EAAE;AAAA,IACP,OAAO,EAAE;AAAA,IACT,OAAO,EAAE;AAAA,IACT,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAA,IAAS,CAAA;AAAA,IAChC;AAAA,IACA,SAAS,QAAQ,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,GAAI,QAAQ,QAAS,GAAG,CAAC,IAAI;AAAA,EAAA,EACzE;AACF,SAAO,EAAE,OAAO,OAAO,MAAA;AACzB;AAEO,SAAS,YACd,QACA,gBACa;AACb,QAAM,2BAAW,IAAA;AACjB,SAAO,QAAQ,CAAC,GAAG,MAAM;AACvB,eAAW,KAAK,eAAe,CAAC,KAAK,CAAA,GAAI;AACvC,YAAM,MAAM,KAAK,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,GAAA;AACtC,UAAI,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,KAAK,IAAI,EAAE;AACpD,WAAK,IAAI,EAAE,IAAI,GAAG;AAAA,IACpB;AAAA,EACF,CAAC;AACD,SAAO,CAAC,GAAG,KAAK,OAAA,CAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,GAAG,cAAc,EAAE,EAAE,CAAC;AACnE;"}
@@ -12,6 +12,7 @@ const TimeseriesCoreBars = require("./TimeseriesCoreBars.cjs");
12
12
  const TimeseriesGauge = require("./TimeseriesGauge.cjs");
13
13
  const TimeseriesPanel = require("./TimeseriesPanel.cjs");
14
14
  const WorkloadCard_model = require("./WorkloadCard.model.cjs");
15
+ const UiBox = require("../icons/components/UiBox.cjs");
15
16
  const UiChip = require("../icons/components/UiChip.cjs");
16
17
  const UiMemoryStick = require("../icons/components/UiMemoryStick.cjs");
17
18
  const UiHardDrive = require("../icons/components/UiHardDrive.cjs");
@@ -79,7 +80,7 @@ function metricGridClass(count) {
79
80
  if (count === 2) return "grid-cols-2";
80
81
  return "grid-cols-3";
81
82
  }
82
- const MEMORY_BAR_UNIT = { perBar: 1024 ** 3, label: "GB", barLabel: "GB" };
83
+ const MEMORY_BAR_UNIT = { perBar: 1024 ** 3, label: "GiB", barLabel: "GiB" };
83
84
  function WorkloadIcon({
84
85
  icon,
85
86
  title,
@@ -92,36 +93,69 @@ function WorkloadIcon({
92
93
  }
93
94
  function metricSeries(metric) {
94
95
  const series = [
95
- {
96
- id: metric.value.id,
97
- label: metric.valueLabel ?? "usage",
98
- ...metric.value.transform ? { transform: metric.value.transform } : {}
99
- }
96
+ { ...metric.value, label: metric.valueLabel ?? "usage" }
100
97
  ];
101
98
  if (typeof metric.max === "object") {
102
- series.push({
103
- id: metric.max.id,
104
- label: metric.maxLabel ?? "limit",
105
- ...metric.max.transform ? { transform: metric.max.transform } : {}
106
- });
99
+ series.push({ ...metric.max, label: metric.maxLabel ?? "limit" });
107
100
  }
108
101
  return series;
109
102
  }
103
+ function metricReferenceLines(metric) {
104
+ if (typeof metric.max !== "number") return [];
105
+ return [{ value: metric.max, label: metric.maxLabel ?? "capacity" }];
106
+ }
110
107
  function hasMetric(metrics) {
111
108
  return Boolean(metrics.cpu || metrics.memory || metrics.disk);
112
109
  }
113
- function HeaderMeta({ children }) {
114
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex min-w-0 items-center gap-1", children: [
115
- /* @__PURE__ */ jsxRuntime.jsx(
110
+ function HeaderMeta({
111
+ separator,
112
+ title,
113
+ children
114
+ }) {
115
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex min-w-0 items-center gap-1", title, children: [
116
+ separator ? /* @__PURE__ */ jsxRuntime.jsx(
116
117
  "span",
117
118
  {
118
119
  className: "h-1 w-1 shrink-0 rounded-full bg-muted-foreground/40",
119
120
  "aria-hidden": true
120
121
  }
121
- ),
122
+ ) : null,
122
123
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children })
123
124
  ] });
124
125
  }
126
+ function headerMetaItems(workload, typeLabel) {
127
+ var _a;
128
+ const items = [];
129
+ if (typeLabel) items.push({ key: "type", content: typeLabel });
130
+ if (workload.namespace) {
131
+ items.push({ key: "namespace", content: workload.namespace });
132
+ }
133
+ if (workload.role) items.push({ key: "role", content: workload.role });
134
+ const replicaText = WorkloadCard_model.formatReplicaCounts(workload.replicas);
135
+ if (replicaText) items.push({ key: "replicas", content: replicaText });
136
+ if (workload.createdAt !== void 0) {
137
+ items.push({
138
+ key: "age",
139
+ content: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
140
+ "age ",
141
+ /* @__PURE__ */ jsxRuntime.jsx(Timestamp.Timestamp, { value: workload.createdAt, format: "relative" })
142
+ ] })
143
+ });
144
+ }
145
+ (_a = workload.metadata) == null ? void 0 : _a.forEach((item, i) => {
146
+ const plain = typeof item.value === "string" || typeof item.value === "number";
147
+ items.push({
148
+ key: `meta-${i}`,
149
+ content: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
150
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground/70", children: item.label }),
151
+ " ",
152
+ item.value
153
+ ] }),
154
+ title: plain ? `${item.label}: ${item.value}` : item.label
155
+ });
156
+ });
157
+ return items;
158
+ }
125
159
  function WorkloadCard({
126
160
  workload,
127
161
  metrics,
@@ -129,6 +163,7 @@ function WorkloadCard({
129
163
  range = "1h",
130
164
  refreshMs = 5e3,
131
165
  expandable = true,
166
+ headerActions,
132
167
  size = "md",
133
168
  variant = "default",
134
169
  fetcher,
@@ -136,10 +171,10 @@ function WorkloadCard({
136
171
  }) {
137
172
  var _a;
138
173
  const [expanded, setExpanded] = react.useState(false);
139
- const kindLabel = WorkloadCard_model.workloadKindLabel(workload.kind);
174
+ const typeLabel = WorkloadCard_model.workloadTypeLabel(workload);
140
175
  const statusLabel = WorkloadCard_model.workloadStatusLabel(workload.status);
141
- const replicaText = WorkloadCard_model.formatReplicaCounts(workload.replicas);
142
- const workloadIcon = workload.icon ?? KIND_ICONS[workload.kind];
176
+ const metaItems = headerMetaItems(workload, typeLabel);
177
+ const workloadIcon = workload.icon ?? (workload.kind ? KIND_ICONS[workload.kind] : UiBox.UiBox);
143
178
  const sizeClasses = SIZE_CLASSES[size];
144
179
  const panelMetrics = react.useMemo(() => {
145
180
  const list = [];
@@ -206,7 +241,7 @@ function WorkloadCard({
206
241
  WorkloadIcon,
207
242
  {
208
243
  icon: workloadIcon,
209
- title: kindLabel,
244
+ title: typeLabel ?? workload.name,
210
245
  className: utils.cn("shrink-0 text-muted-foreground", sizeClasses.icon)
211
246
  }
212
247
  ),
@@ -221,25 +256,24 @@ function WorkloadCard({
221
256
  ]
222
257
  }
223
258
  ),
224
- /* @__PURE__ */ jsxRuntime.jsxs(
259
+ metaItems.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
225
260
  "div",
226
261
  {
227
262
  className: utils.cn(
228
263
  "flex flex-wrap items-center text-muted-foreground",
229
264
  sizeClasses.meta
230
265
  ),
231
- children: [
232
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: kindLabel }),
233
- workload.namespace ? /* @__PURE__ */ jsxRuntime.jsx(HeaderMeta, { children: workload.namespace }) : null,
234
- workload.role ? /* @__PURE__ */ jsxRuntime.jsx(HeaderMeta, { children: workload.role }) : null,
235
- replicaText ? /* @__PURE__ */ jsxRuntime.jsx(HeaderMeta, { children: replicaText }) : null,
236
- workload.createdAt !== void 0 ? /* @__PURE__ */ jsxRuntime.jsxs(HeaderMeta, { children: [
237
- "age ",
238
- /* @__PURE__ */ jsxRuntime.jsx(Timestamp.Timestamp, { value: workload.createdAt, format: "relative" })
239
- ] }) : null
240
- ]
266
+ children: metaItems.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(
267
+ HeaderMeta,
268
+ {
269
+ separator: i > 0,
270
+ title: item.title,
271
+ children: item.content
272
+ },
273
+ item.key
274
+ ))
241
275
  }
242
- )
276
+ ) : null
243
277
  ] }),
244
278
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
245
279
  statusLabel ? /* @__PURE__ */ jsxRuntime.jsx("span", { title: (_a = workload.status) == null ? void 0 : _a.message, children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -251,6 +285,7 @@ function WorkloadCard({
251
285
  children: statusLabel
252
286
  }
253
287
  ) }) : null,
288
+ headerActions,
254
289
  canExpand ? /* @__PURE__ */ jsxRuntime.jsx(
255
290
  "button",
256
291
  {
@@ -355,6 +390,7 @@ function WorkloadCard({
355
390
  title,
356
391
  icon,
357
392
  series: metricSeries(metric),
393
+ referenceLines: metricReferenceLines(metric),
358
394
  ...unit ? { unit } : {},
359
395
  ...metricProps,
360
396
  expandable: false
@@ -1 +1 @@
1
- {"version":3,"file":"WorkloadCard.cjs","sources":["../../src/data/WorkloadCard.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport {\n K8SDeployment,\n K8SIngress,\n K8SPod,\n K8SService,\n K8SStatefulset,\n} from \"@flanksource/icons/mi\";\nimport { cn } from \"../lib/utils\";\nimport { Modal } from \"../overlay/Modal\";\nimport { Badge } from \"./Badge\";\nimport { Timestamp } from \"./cells/Timestamp\";\nimport { Icon, type StaticIconComponent } from \"./Icon\";\nimport { TimeseriesCoreBars } from \"./TimeseriesCoreBars\";\nimport { TimeseriesGauge } from \"./TimeseriesGauge\";\nimport {\n TimeseriesPanel,\n type TimeseriesResponse,\n type TimeseriesSeries,\n} from \"./TimeseriesPanel\";\nimport { UiChip, UiFullscreen, UiHardDrive, UiMemoryStick } from \"../icons\";\nimport {\n formatReplicaCounts,\n workloadKindLabel,\n workloadStatusLabel,\n workloadStatusTone,\n type WorkloadCardIcon,\n type WorkloadCardKind,\n type WorkloadCardMetrics,\n type WorkloadCardResourceMetric,\n type WorkloadCardWorkload,\n} from \"./WorkloadCard.model\";\n\nexport interface WorkloadCardProps {\n workload: WorkloadCardWorkload;\n metrics: WorkloadCardMetrics;\n baseUrl?: string;\n range?: string;\n refreshMs?: number;\n expandable?: boolean;\n size?: WorkloadCardSize;\n variant?: WorkloadCardVariant;\n fetcher?: (url: string) => Promise<TimeseriesResponse>;\n className?: string;\n}\n\nexport type WorkloadCardSize = \"sm\" | \"md\" | \"lg\";\nexport type WorkloadCardVariant =\n | \"default\"\n | \"outline\"\n | \"subtle\"\n | \"elevated\"\n | \"ghost\";\n\ntype MetricKey = keyof WorkloadCardMetrics;\n\ntype PanelMetric = {\n key: MetricKey;\n title: string;\n unit?: string;\n icon: WorkloadCardIcon;\n metric: WorkloadCardResourceMetric;\n};\n\nconst KIND_ICONS: Record<WorkloadCardKind, StaticIconComponent> = {\n service: K8SService,\n ingress: K8SIngress,\n deployment: K8SDeployment,\n statefulset: K8SStatefulset,\n pod: K8SPod,\n};\n\ntype WorkloadCardSizeClasses = {\n root: string;\n header: string;\n titleGap: string;\n icon: string;\n name: string;\n meta: string;\n statusSize: \"xxs\" | \"xs\" | \"sm\";\n action: string;\n actionIcon: number;\n metricGrid: string;\n metricVariant: \"default\" | \"cell\";\n empty: string;\n};\n\nconst SIZE_CLASSES: Record<WorkloadCardSize, WorkloadCardSizeClasses> = {\n sm: {\n root: \"p-2\",\n header: \"mb-2 gap-2\",\n titleGap: \"gap-1\",\n icon: \"h-3.5 w-3.5\",\n name: \"text-xs\",\n meta: \"mt-0.5 gap-x-1.5 gap-y-0.5 text-[11px] leading-4\",\n statusSize: \"xxs\",\n action: \"h-5 w-5\",\n actionIcon: 12,\n metricGrid: \"grid-cols-1 gap-1.5\",\n metricVariant: \"cell\",\n empty: \"px-2 py-3 text-[11px]\",\n },\n md: {\n root: \"p-3\",\n header: \"mb-3 gap-2\",\n titleGap: \"gap-1.5\",\n icon: \"h-4 w-4\",\n name: \"text-sm\",\n meta: \"mt-0.5 gap-x-2 gap-y-0.5 text-xs\",\n statusSize: \"xs\",\n action: \"h-6 w-6\",\n actionIcon: 14,\n metricGrid: \"gap-3\",\n metricVariant: \"default\",\n empty: \"px-3 py-4 text-xs\",\n },\n lg: {\n root: \"p-4\",\n header: \"mb-4 gap-3\",\n titleGap: \"gap-2\",\n icon: \"h-5 w-5\",\n name: \"text-base\",\n meta: \"mt-1 gap-x-2 gap-y-1 text-xs\",\n statusSize: \"sm\",\n action: \"h-7 w-7\",\n actionIcon: 16,\n metricGrid: \"gap-4\",\n metricVariant: \"default\",\n empty: \"px-4 py-6 text-sm\",\n },\n};\n\nconst VARIANT_CLASSES: Record<WorkloadCardVariant, string> = {\n default: \"border-border bg-card text-card-foreground\",\n outline: \"border-border bg-background text-foreground\",\n subtle: \"border-transparent bg-muted/35 text-foreground\",\n elevated: \"border-border/70 bg-card text-card-foreground shadow-sm\",\n ghost: \"border-transparent bg-transparent text-foreground\",\n};\n\nfunction metricGridClass(count: number): string {\n if (count <= 1) return \"grid-cols-1\";\n if (count === 2) return \"grid-cols-2\";\n return \"grid-cols-3\";\n}\n\n// Memory renders as cores-style bars at one bar per gigabyte, matching CPU; bytes\n// are fed raw and TimeseriesCoreBars divides by perBar so captions read \"x GB\".\nconst MEMORY_BAR_UNIT = { perBar: 1024 ** 3, label: \"GB\", barLabel: \"GB\" };\n\nfunction WorkloadIcon({\n icon,\n title,\n className,\n}: {\n icon: WorkloadCardIcon;\n title: string;\n className?: string;\n}) {\n if (typeof icon === \"string\") {\n return (\n <Icon name={icon} title={title} {...(className ? { className } : {})} />\n );\n }\n return (\n <Icon icon={icon} title={title} {...(className ? { className } : {})} />\n );\n}\n\nfunction metricSeries(metric: WorkloadCardResourceMetric): TimeseriesSeries[] {\n const series: TimeseriesSeries[] = [\n {\n id: metric.value.id,\n label: metric.valueLabel ?? \"usage\",\n ...(metric.value.transform ? { transform: metric.value.transform } : {}),\n },\n ];\n if (typeof metric.max === \"object\") {\n series.push({\n id: metric.max.id,\n label: metric.maxLabel ?? \"limit\",\n ...(metric.max.transform ? { transform: metric.max.transform } : {}),\n });\n }\n return series;\n}\n\nfunction hasMetric(metrics: WorkloadCardMetrics): boolean {\n return Boolean(metrics.cpu || metrics.memory || metrics.disk);\n}\n\nfunction HeaderMeta({ children }: { children: ReactNode }) {\n return (\n <span className=\"inline-flex min-w-0 items-center gap-1\">\n <span\n className=\"h-1 w-1 shrink-0 rounded-full bg-muted-foreground/40\"\n aria-hidden\n />\n <span className=\"min-w-0 truncate\">{children}</span>\n </span>\n );\n}\n\n/**\n * WorkloadCard is a compact resource card for Kubernetes-style workloads. It\n * shows workload identity, status, replica/age metadata, and CPU/memory/disk\n * utilization backed by the existing time-series primitives: CPU and memory as\n * core-style bars (memory at one bar per GB) and disk as a linear progress bar.\n * Callers own the metric ids; the card only composes the supplied series.\n */\nexport function WorkloadCard({\n workload,\n metrics,\n baseUrl,\n range = \"1h\",\n refreshMs = 5000,\n expandable = true,\n size = \"md\",\n variant = \"default\",\n fetcher,\n className,\n}: WorkloadCardProps) {\n const [expanded, setExpanded] = useState(false);\n const kindLabel = workloadKindLabel(workload.kind);\n const statusLabel = workloadStatusLabel(workload.status);\n const replicaText = formatReplicaCounts(workload.replicas);\n const workloadIcon = workload.icon ?? KIND_ICONS[workload.kind];\n const sizeClasses = SIZE_CLASSES[size];\n\n const panelMetrics = useMemo(() => {\n const list: PanelMetric[] = [];\n if (metrics.cpu) {\n list.push({\n key: \"cpu\",\n title: metrics.cpu.title ?? \"CPU\",\n unit: metrics.cpu.unit ?? \"short\",\n icon: metrics.cpu.icon ?? UiChip,\n metric: metrics.cpu,\n });\n }\n if (metrics.memory) {\n list.push({\n key: \"memory\",\n title: metrics.memory.title ?? \"Memory\",\n unit: metrics.memory.unit ?? \"bytes\",\n icon: metrics.memory.icon ?? UiMemoryStick,\n metric: metrics.memory,\n });\n }\n if (metrics.disk) {\n list.push({\n key: \"disk\",\n title: metrics.disk.title ?? \"Disk\",\n unit: metrics.disk.unit ?? \"bytes\",\n icon: metrics.disk.icon ?? UiHardDrive,\n metric: metrics.disk,\n });\n }\n return list;\n }, [metrics]);\n\n const metricProps = {\n ...(baseUrl !== undefined ? { baseUrl } : {}),\n range,\n refreshMs,\n ...(fetcher !== undefined ? { fetcher } : {}),\n };\n const canExpand = expandable && hasMetric(metrics);\n\n return (\n <div\n className={cn(\n \"w-full rounded-md border\",\n VARIANT_CLASSES[variant],\n sizeClasses.root,\n className,\n )}\n data-size={size}\n data-variant={variant}\n >\n <div\n className={cn(\"flex items-start justify-between\", sizeClasses.header)}\n >\n <div className=\"min-w-0\">\n <div\n className={cn(\"flex min-w-0 items-center\", sizeClasses.titleGap)}\n >\n <WorkloadIcon\n icon={workloadIcon}\n title={kindLabel}\n className={cn(\"shrink-0 text-muted-foreground\", sizeClasses.icon)}\n />\n <span\n className={cn(\"min-w-0 truncate font-medium\", sizeClasses.name)}\n title={workload.name}\n >\n {workload.name}\n </span>\n </div>\n <div\n className={cn(\n \"flex flex-wrap items-center text-muted-foreground\",\n sizeClasses.meta,\n )}\n >\n <span>{kindLabel}</span>\n {workload.namespace ? (\n <HeaderMeta>{workload.namespace}</HeaderMeta>\n ) : null}\n {workload.role ? <HeaderMeta>{workload.role}</HeaderMeta> : null}\n {replicaText ? <HeaderMeta>{replicaText}</HeaderMeta> : null}\n {workload.createdAt !== undefined ? (\n <HeaderMeta>\n age <Timestamp value={workload.createdAt} format=\"relative\" />\n </HeaderMeta>\n ) : null}\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-1\">\n {statusLabel ? (\n <span title={workload.status?.message}>\n <Badge\n tone={workloadStatusTone(workload.status)}\n variant=\"soft\"\n size={sizeClasses.statusSize}\n >\n {statusLabel}\n </Badge>\n </span>\n ) : null}\n {canExpand ? (\n <button\n type=\"button\"\n aria-label={`Open ${workload.name} history`}\n title=\"History\"\n onClick={() => setExpanded(true)}\n className={cn(\n \"inline-flex items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\",\n sizeClasses.action,\n )}\n >\n <Icon\n icon={UiFullscreen}\n width={sizeClasses.actionIcon}\n height={sizeClasses.actionIcon}\n />\n </button>\n ) : null}\n </div>\n </div>\n\n {hasMetric(metrics) ? (\n <div\n className={cn(\n \"grid items-start gap-3\",\n sizeClasses.metricVariant === \"cell\"\n ? sizeClasses.metricGrid\n : cn(\n metricGridClass(panelMetrics.length),\n sizeClasses.metricGrid,\n ),\n )}\n >\n {metrics.cpu ? (\n <TimeseriesCoreBars\n title={metrics.cpu.title ?? \"CPU\"}\n icon={metrics.cpu.icon ?? UiChip}\n value={metrics.cpu.value}\n {...(metrics.cpu.max !== undefined\n ? { max: metrics.cpu.max }\n : {})}\n {...(metrics.cpu.thresholds\n ? { thresholds: metrics.cpu.thresholds }\n : {})}\n {...(metrics.cpu.orientation\n ? { orientation: metrics.cpu.orientation }\n : {})}\n {...(metrics.cpu.showValue !== undefined\n ? { showValue: metrics.cpu.showValue }\n : {})}\n variant={sizeClasses.metricVariant}\n {...metricProps}\n />\n ) : null}\n {metrics.memory ? (\n <TimeseriesCoreBars\n title={metrics.memory.title ?? \"Memory\"}\n icon={metrics.memory.icon ?? UiMemoryStick}\n value={metrics.memory.value}\n unit={MEMORY_BAR_UNIT}\n variant={sizeClasses.metricVariant}\n {...(metrics.memory.max !== undefined\n ? { max: metrics.memory.max }\n : {})}\n {...(metrics.memory.thresholds\n ? { thresholds: metrics.memory.thresholds }\n : {})}\n {...metricProps}\n />\n ) : null}\n {metrics.disk ? (\n <TimeseriesGauge\n title={metrics.disk.title ?? \"Disk\"}\n icon={metrics.disk.icon ?? UiHardDrive}\n value={metrics.disk.value}\n unit={metrics.disk.unit ?? \"bytes\"}\n shape=\"linear\"\n expandable={false}\n centerDisplay={metrics.disk.centerDisplay ?? \"value\"}\n variant={sizeClasses.metricVariant}\n {...(metrics.disk.max !== undefined\n ? { max: metrics.disk.max }\n : {})}\n {...(metrics.disk.thresholds\n ? { thresholds: metrics.disk.thresholds }\n : {})}\n {...metricProps}\n />\n ) : null}\n </div>\n ) : (\n <div\n className={cn(\n \"rounded-md border border-dashed border-border text-center text-muted-foreground\",\n sizeClasses.empty,\n )}\n >\n No metrics configured\n </div>\n )}\n\n <Modal\n open={expanded}\n onClose={() => setExpanded(false)}\n title={`${workload.name} history`}\n size=\"xl\"\n >\n <div className=\"flex flex-col gap-3\">\n {panelMetrics.map(({ key, title, unit, icon, metric }) => (\n <TimeseriesPanel\n key={key}\n title={title}\n icon={icon}\n series={metricSeries(metric)}\n {...(unit ? { unit } : {})}\n {...metricProps}\n expandable={false}\n />\n ))}\n </div>\n </Modal>\n </div>\n );\n}\n"],"names":["K8SService","K8SIngress","K8SDeployment","K8SStatefulset","K8SPod","jsx","Icon","jsxs","useState","workloadKindLabel","workloadStatusLabel","formatReplicaCounts","useMemo","UiChip","UiMemoryStick","UiHardDrive","cn","Timestamp","Badge","workloadStatusTone","UiFullscreen","TimeseriesCoreBars","TimeseriesGauge","Modal","TimeseriesPanel"],"mappings":";;;;;;;;;;;;;;;;;;AAgEA,MAAM,aAA4D;AAAA,EAChE,SAASA,GAAAA;AAAAA,EACT,SAASC,GAAAA;AAAAA,EACT,YAAYC,GAAAA;AAAAA,EACZ,aAAaC,GAAAA;AAAAA,EACb,KAAKC,GAAAA;AACP;AAiBA,MAAM,eAAkE;AAAA,EACtE,IAAI;AAAA,IACF,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,OAAO;AAAA,EAAA;AAAA,EAET,IAAI;AAAA,IACF,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,OAAO;AAAA,EAAA;AAAA,EAET,IAAI;AAAA,IACF,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,OAAO;AAAA,EAAA;AAEX;AAEA,MAAM,kBAAuD;AAAA,EAC3D,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,OAAO;AACT;AAEA,SAAS,gBAAgB,OAAuB;AAC9C,MAAI,SAAS,EAAG,QAAO;AACvB,MAAI,UAAU,EAAG,QAAO;AACxB,SAAO;AACT;AAIA,MAAM,kBAAkB,EAAE,QAAQ,QAAQ,GAAG,OAAO,MAAM,UAAU,KAAA;AAEpE,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,MAAI,OAAO,SAAS,UAAU;AAC5B,WACEC,+BAACC,KAAAA,MAAA,EAAK,MAAM,MAAM,OAAe,GAAI,YAAY,EAAE,UAAA,IAAc,CAAA,EAAC,CAAI;AAAA,EAE1E;AACA,SACED,2BAAAA,IAACC,WAAA,EAAK,MAAY,OAAe,GAAI,YAAY,EAAE,cAAc,CAAA,GAAK;AAE1E;AAEA,SAAS,aAAa,QAAwD;AAC5E,QAAM,SAA6B;AAAA,IACjC;AAAA,MACE,IAAI,OAAO,MAAM;AAAA,MACjB,OAAO,OAAO,cAAc;AAAA,MAC5B,GAAI,OAAO,MAAM,YAAY,EAAE,WAAW,OAAO,MAAM,cAAc,CAAA;AAAA,IAAC;AAAA,EACxE;AAEF,MAAI,OAAO,OAAO,QAAQ,UAAU;AAClC,WAAO,KAAK;AAAA,MACV,IAAI,OAAO,IAAI;AAAA,MACf,OAAO,OAAO,YAAY;AAAA,MAC1B,GAAI,OAAO,IAAI,YAAY,EAAE,WAAW,OAAO,IAAI,cAAc,CAAA;AAAA,IAAC,CACnE;AAAA,EACH;AACA,SAAO;AACT;AAEA,SAAS,UAAU,SAAuC;AACxD,SAAO,QAAQ,QAAQ,OAAO,QAAQ,UAAU,QAAQ,IAAI;AAC9D;AAEA,SAAS,WAAW,EAAE,YAAqC;AACzD,SACEC,2BAAAA,KAAC,QAAA,EAAK,WAAU,0CACd,UAAA;AAAA,IAAAF,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAU;AAAA,QACV,eAAW;AAAA,MAAA;AAAA,IAAA;AAAA,IAEbA,2BAAAA,IAAC,QAAA,EAAK,WAAU,oBAAoB,SAAA,CAAS;AAAA,EAAA,GAC/C;AAEJ;AASO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,OAAO;AAAA,EACP,UAAU;AAAA,EACV;AAAA,EACA;AACF,GAAsB;;AACpB,QAAM,CAAC,UAAU,WAAW,IAAIG,MAAAA,SAAS,KAAK;AAC9C,QAAM,YAAYC,mBAAAA,kBAAkB,SAAS,IAAI;AACjD,QAAM,cAAcC,mBAAAA,oBAAoB,SAAS,MAAM;AACvD,QAAM,cAAcC,mBAAAA,oBAAoB,SAAS,QAAQ;AACzD,QAAM,eAAe,SAAS,QAAQ,WAAW,SAAS,IAAI;AAC9D,QAAM,cAAc,aAAa,IAAI;AAErC,QAAM,eAAeC,MAAAA,QAAQ,MAAM;AACjC,UAAM,OAAsB,CAAA;AAC5B,QAAI,QAAQ,KAAK;AACf,WAAK,KAAK;AAAA,QACR,KAAK;AAAA,QACL,OAAO,QAAQ,IAAI,SAAS;AAAA,QAC5B,MAAM,QAAQ,IAAI,QAAQ;AAAA,QAC1B,MAAM,QAAQ,IAAI,QAAQC,OAAAA;AAAAA,QAC1B,QAAQ,QAAQ;AAAA,MAAA,CACjB;AAAA,IACH;AACA,QAAI,QAAQ,QAAQ;AAClB,WAAK,KAAK;AAAA,QACR,KAAK;AAAA,QACL,OAAO,QAAQ,OAAO,SAAS;AAAA,QAC/B,MAAM,QAAQ,OAAO,QAAQ;AAAA,QAC7B,MAAM,QAAQ,OAAO,QAAQC,cAAAA;AAAAA,QAC7B,QAAQ,QAAQ;AAAA,MAAA,CACjB;AAAA,IACH;AACA,QAAI,QAAQ,MAAM;AAChB,WAAK,KAAK;AAAA,QACR,KAAK;AAAA,QACL,OAAO,QAAQ,KAAK,SAAS;AAAA,QAC7B,MAAM,QAAQ,KAAK,QAAQ;AAAA,QAC3B,MAAM,QAAQ,KAAK,QAAQC,YAAAA;AAAAA,QAC3B,QAAQ,QAAQ;AAAA,MAAA,CACjB;AAAA,IACH;AACA,WAAO;AAAA,EACT,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,cAAc;AAAA,IAClB,GAAI,YAAY,SAAY,EAAE,QAAA,IAAY,CAAA;AAAA,IAC1C;AAAA,IACA;AAAA,IACA,GAAI,YAAY,SAAY,EAAE,YAAY,CAAA;AAAA,EAAC;AAE7C,QAAM,YAAY,cAAc,UAAU,OAAO;AAEjD,SACER,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWS,MAAAA;AAAAA,QACT;AAAA,QACA,gBAAgB,OAAO;AAAA,QACvB,YAAY;AAAA,QACZ;AAAA,MAAA;AAAA,MAEF,aAAW;AAAA,MACX,gBAAc;AAAA,MAEd,UAAA;AAAA,QAAAT,2BAAAA;AAAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAWS,MAAAA,GAAG,oCAAoC,YAAY,MAAM;AAAA,YAEpE,UAAA;AAAA,cAAAT,2BAAAA,KAAC,OAAA,EAAI,WAAU,WACb,UAAA;AAAA,gBAAAA,2BAAAA;AAAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC,WAAWS,MAAAA,GAAG,6BAA6B,YAAY,QAAQ;AAAA,oBAE/D,UAAA;AAAA,sBAAAX,2BAAAA;AAAAA,wBAAC;AAAA,wBAAA;AAAA,0BACC,MAAM;AAAA,0BACN,OAAO;AAAA,0BACP,WAAWW,MAAAA,GAAG,kCAAkC,YAAY,IAAI;AAAA,wBAAA;AAAA,sBAAA;AAAA,sBAElEX,2BAAAA;AAAAA,wBAAC;AAAA,wBAAA;AAAA,0BACC,WAAWW,MAAAA,GAAG,gCAAgC,YAAY,IAAI;AAAA,0BAC9D,OAAO,SAAS;AAAA,0BAEf,UAAA,SAAS;AAAA,wBAAA;AAAA,sBAAA;AAAA,oBACZ;AAAA,kBAAA;AAAA,gBAAA;AAAA,gBAEFT,2BAAAA;AAAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC,WAAWS,MAAAA;AAAAA,sBACT;AAAA,sBACA,YAAY;AAAA,oBAAA;AAAA,oBAGd,UAAA;AAAA,sBAAAX,2BAAAA,IAAC,UAAM,UAAA,UAAA,CAAU;AAAA,sBAChB,SAAS,YACRA,2BAAAA,IAAC,YAAA,EAAY,UAAA,SAAS,WAAU,IAC9B;AAAA,sBACH,SAAS,OAAOA,2BAAAA,IAAC,YAAA,EAAY,UAAA,SAAS,MAAK,IAAgB;AAAA,sBAC3D,cAAcA,2BAAAA,IAAC,YAAA,EAAY,UAAA,YAAA,CAAY,IAAgB;AAAA,sBACvD,SAAS,cAAc,SACtBE,2BAAAA,KAAC,YAAA,EAAW,UAAA;AAAA,wBAAA;AAAA,uDACLU,UAAAA,WAAA,EAAU,OAAO,SAAS,WAAW,QAAO,WAAA,CAAW;AAAA,sBAAA,EAAA,CAC9D,IACE;AAAA,oBAAA;AAAA,kBAAA;AAAA,gBAAA;AAAA,cACN,GACF;AAAA,cACAV,2BAAAA,KAAC,OAAA,EAAI,WAAU,oCACZ,UAAA;AAAA,gBAAA,cACCF,2BAAAA,IAAC,QAAA,EAAK,QAAO,cAAS,WAAT,mBAAiB,SAC5B,UAAAA,2BAAAA;AAAAA,kBAACa,MAAAA;AAAAA,kBAAA;AAAA,oBACC,MAAMC,mBAAAA,mBAAmB,SAAS,MAAM;AAAA,oBACxC,SAAQ;AAAA,oBACR,MAAM,YAAY;AAAA,oBAEjB,UAAA;AAAA,kBAAA;AAAA,gBAAA,GAEL,IACE;AAAA,gBACH,YACCd,2BAAAA;AAAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC,MAAK;AAAA,oBACL,cAAY,QAAQ,SAAS,IAAI;AAAA,oBACjC,OAAM;AAAA,oBACN,SAAS,MAAM,YAAY,IAAI;AAAA,oBAC/B,WAAWW,MAAAA;AAAAA,sBACT;AAAA,sBACA,YAAY;AAAA,oBAAA;AAAA,oBAGd,UAAAX,2BAAAA;AAAAA,sBAACC,KAAAA;AAAAA,sBAAA;AAAA,wBACC,MAAMc,aAAAA;AAAAA,wBACN,OAAO,YAAY;AAAA,wBACnB,QAAQ,YAAY;AAAA,sBAAA;AAAA,oBAAA;AAAA,kBACtB;AAAA,gBAAA,IAEA;AAAA,cAAA,EAAA,CACN;AAAA,YAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAGD,UAAU,OAAO,IAChBb,2BAAAA;AAAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAWS,MAAAA;AAAAA,cACT;AAAA,cACA,YAAY,kBAAkB,SAC1B,YAAY,aACZA,MAAAA;AAAAA,gBACE,gBAAgB,aAAa,MAAM;AAAA,gBACnC,YAAY;AAAA,cAAA;AAAA,YACd;AAAA,YAGL,UAAA;AAAA,cAAA,QAAQ,MACPX,2BAAAA;AAAAA,gBAACgB,mBAAAA;AAAAA,gBAAA;AAAA,kBACC,OAAO,QAAQ,IAAI,SAAS;AAAA,kBAC5B,MAAM,QAAQ,IAAI,QAAQR,OAAAA;AAAAA,kBAC1B,OAAO,QAAQ,IAAI;AAAA,kBAClB,GAAI,QAAQ,IAAI,QAAQ,SACrB,EAAE,KAAK,QAAQ,IAAI,IAAA,IACnB,CAAA;AAAA,kBACH,GAAI,QAAQ,IAAI,aACb,EAAE,YAAY,QAAQ,IAAI,WAAA,IAC1B,CAAA;AAAA,kBACH,GAAI,QAAQ,IAAI,cACb,EAAE,aAAa,QAAQ,IAAI,YAAA,IAC3B,CAAA;AAAA,kBACH,GAAI,QAAQ,IAAI,cAAc,SAC3B,EAAE,WAAW,QAAQ,IAAI,UAAA,IACzB,CAAA;AAAA,kBACJ,SAAS,YAAY;AAAA,kBACpB,GAAG;AAAA,gBAAA;AAAA,cAAA,IAEJ;AAAA,cACH,QAAQ,SACPR,2BAAAA;AAAAA,gBAACgB,mBAAAA;AAAAA,gBAAA;AAAA,kBACC,OAAO,QAAQ,OAAO,SAAS;AAAA,kBAC/B,MAAM,QAAQ,OAAO,QAAQP,cAAAA;AAAAA,kBAC7B,OAAO,QAAQ,OAAO;AAAA,kBACtB,MAAM;AAAA,kBACN,SAAS,YAAY;AAAA,kBACpB,GAAI,QAAQ,OAAO,QAAQ,SACxB,EAAE,KAAK,QAAQ,OAAO,IAAA,IACtB,CAAA;AAAA,kBACH,GAAI,QAAQ,OAAO,aAChB,EAAE,YAAY,QAAQ,OAAO,WAAA,IAC7B,CAAA;AAAA,kBACH,GAAG;AAAA,gBAAA;AAAA,cAAA,IAEJ;AAAA,cACH,QAAQ,OACPT,2BAAAA;AAAAA,gBAACiB,gBAAAA;AAAAA,gBAAA;AAAA,kBACC,OAAO,QAAQ,KAAK,SAAS;AAAA,kBAC7B,MAAM,QAAQ,KAAK,QAAQP,YAAAA;AAAAA,kBAC3B,OAAO,QAAQ,KAAK;AAAA,kBACpB,MAAM,QAAQ,KAAK,QAAQ;AAAA,kBAC3B,OAAM;AAAA,kBACN,YAAY;AAAA,kBACZ,eAAe,QAAQ,KAAK,iBAAiB;AAAA,kBAC7C,SAAS,YAAY;AAAA,kBACpB,GAAI,QAAQ,KAAK,QAAQ,SACtB,EAAE,KAAK,QAAQ,KAAK,IAAA,IACpB,CAAA;AAAA,kBACH,GAAI,QAAQ,KAAK,aACd,EAAE,YAAY,QAAQ,KAAK,WAAA,IAC3B,CAAA;AAAA,kBACH,GAAG;AAAA,gBAAA;AAAA,cAAA,IAEJ;AAAA,YAAA;AAAA,UAAA;AAAA,QAAA,IAGNV,2BAAAA;AAAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAWW,MAAAA;AAAAA,cACT;AAAA,cACA,YAAY;AAAA,YAAA;AAAA,YAEf,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAKHX,2BAAAA;AAAAA,UAACkB,MAAAA;AAAAA,UAAA;AAAA,YACC,MAAM;AAAA,YACN,SAAS,MAAM,YAAY,KAAK;AAAA,YAChC,OAAO,GAAG,SAAS,IAAI;AAAA,YACvB,MAAK;AAAA,YAEL,UAAAlB,2BAAAA,IAAC,OAAA,EAAI,WAAU,uBACZ,UAAA,aAAa,IAAI,CAAC,EAAE,KAAK,OAAO,MAAM,MAAM,aAC3CA,2BAAAA;AAAAA,cAACmB,gBAAAA;AAAAA,cAAA;AAAA,gBAEC;AAAA,gBACA;AAAA,gBACA,QAAQ,aAAa,MAAM;AAAA,gBAC1B,GAAI,OAAO,EAAE,KAAA,IAAS,CAAA;AAAA,gBACtB,GAAG;AAAA,gBACJ,YAAY;AAAA,cAAA;AAAA,cANP;AAAA,YAAA,CAQR,EAAA,CACH;AAAA,UAAA;AAAA,QAAA;AAAA,MACF;AAAA,IAAA;AAAA,EAAA;AAGN;;"}
1
+ {"version":3,"file":"WorkloadCard.cjs","sources":["../../src/data/WorkloadCard.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport {\n K8SDeployment,\n K8SIngress,\n K8SPod,\n K8SService,\n K8SStatefulset,\n} from \"@flanksource/icons/mi\";\nimport { cn } from \"../lib/utils\";\nimport { Modal } from \"../overlay/Modal\";\nimport { Badge } from \"./Badge\";\nimport { Timestamp } from \"./cells/Timestamp\";\nimport { Icon, type StaticIconComponent } from \"./Icon\";\nimport { TimeseriesCoreBars } from \"./TimeseriesCoreBars\";\nimport { TimeseriesGauge } from \"./TimeseriesGauge\";\nimport {\n TimeseriesPanel,\n type TimeseriesReferenceLine,\n type TimeseriesResponse,\n type TimeseriesSeries,\n} from \"./TimeseriesPanel\";\nimport {\n UiBox,\n UiChip,\n UiFullscreen,\n UiHardDrive,\n UiMemoryStick,\n} from \"../icons\";\nimport {\n formatReplicaCounts,\n workloadStatusLabel,\n workloadStatusTone,\n workloadTypeLabel,\n type WorkloadCardIcon,\n type WorkloadCardKind,\n type WorkloadCardMetrics,\n type WorkloadCardResourceMetric,\n type WorkloadCardWorkload,\n} from \"./WorkloadCard.model\";\n\nexport interface WorkloadCardProps {\n workload: WorkloadCardWorkload;\n /**\n * CPU/memory/disk series. Each is requested as `baseUrl + id` via `fetcher`,\n * or loaded by its own `load` function (then `id` is the cache key and must\n * be unique per data source).\n */\n metrics: WorkloadCardMetrics;\n baseUrl?: string;\n range?: string;\n refreshMs?: number;\n expandable?: boolean;\n /** Controls rendered beside status and history, owned by the host domain. */\n headerActions?: ReactNode;\n size?: WorkloadCardSize;\n variant?: WorkloadCardVariant;\n fetcher?: (url: string) => Promise<TimeseriesResponse>;\n className?: string;\n}\n\nexport type WorkloadCardSize = \"sm\" | \"md\" | \"lg\";\nexport type WorkloadCardVariant =\n | \"default\"\n | \"outline\"\n | \"subtle\"\n | \"elevated\"\n | \"ghost\";\n\ntype MetricKey = keyof WorkloadCardMetrics;\n\ntype PanelMetric = {\n key: MetricKey;\n title: string;\n unit?: string;\n icon: WorkloadCardIcon;\n metric: WorkloadCardResourceMetric;\n};\n\nconst KIND_ICONS: Record<WorkloadCardKind, StaticIconComponent> = {\n service: K8SService,\n ingress: K8SIngress,\n deployment: K8SDeployment,\n statefulset: K8SStatefulset,\n pod: K8SPod,\n};\n\ntype WorkloadCardSizeClasses = {\n root: string;\n header: string;\n titleGap: string;\n icon: string;\n name: string;\n meta: string;\n statusSize: \"xxs\" | \"xs\" | \"sm\";\n action: string;\n actionIcon: number;\n metricGrid: string;\n metricVariant: \"default\" | \"cell\";\n empty: string;\n};\n\nconst SIZE_CLASSES: Record<WorkloadCardSize, WorkloadCardSizeClasses> = {\n sm: {\n root: \"p-2\",\n header: \"mb-2 gap-2\",\n titleGap: \"gap-1\",\n icon: \"h-3.5 w-3.5\",\n name: \"text-xs\",\n meta: \"mt-0.5 gap-x-1.5 gap-y-0.5 text-[11px] leading-4\",\n statusSize: \"xxs\",\n action: \"h-5 w-5\",\n actionIcon: 12,\n metricGrid: \"grid-cols-1 gap-1.5\",\n metricVariant: \"cell\",\n empty: \"px-2 py-3 text-[11px]\",\n },\n md: {\n root: \"p-3\",\n header: \"mb-3 gap-2\",\n titleGap: \"gap-1.5\",\n icon: \"h-4 w-4\",\n name: \"text-sm\",\n meta: \"mt-0.5 gap-x-2 gap-y-0.5 text-xs\",\n statusSize: \"xs\",\n action: \"h-6 w-6\",\n actionIcon: 14,\n metricGrid: \"gap-3\",\n metricVariant: \"default\",\n empty: \"px-3 py-4 text-xs\",\n },\n lg: {\n root: \"p-4\",\n header: \"mb-4 gap-3\",\n titleGap: \"gap-2\",\n icon: \"h-5 w-5\",\n name: \"text-base\",\n meta: \"mt-1 gap-x-2 gap-y-1 text-xs\",\n statusSize: \"sm\",\n action: \"h-7 w-7\",\n actionIcon: 16,\n metricGrid: \"gap-4\",\n metricVariant: \"default\",\n empty: \"px-4 py-6 text-sm\",\n },\n};\n\nconst VARIANT_CLASSES: Record<WorkloadCardVariant, string> = {\n default: \"border-border bg-card text-card-foreground\",\n outline: \"border-border bg-background text-foreground\",\n subtle: \"border-transparent bg-muted/35 text-foreground\",\n elevated: \"border-border/70 bg-card text-card-foreground shadow-sm\",\n ghost: \"border-transparent bg-transparent text-foreground\",\n};\n\nfunction metricGridClass(count: number): string {\n if (count <= 1) return \"grid-cols-1\";\n if (count === 2) return \"grid-cols-2\";\n return \"grid-cols-3\";\n}\n\n// Memory renders as cores-style bars at one bar per gibibyte, matching CPU; bytes\n// are fed raw and TimeseriesCoreBars divides by perBar so captions read \"x GiB\".\nconst MEMORY_BAR_UNIT = { perBar: 1024 ** 3, label: \"GiB\", barLabel: \"GiB\" };\n\nfunction WorkloadIcon({\n icon,\n title,\n className,\n}: {\n icon: WorkloadCardIcon;\n title: string;\n className?: string;\n}) {\n if (typeof icon === \"string\") {\n return (\n <Icon name={icon} title={title} {...(className ? { className } : {})} />\n );\n }\n return (\n <Icon icon={icon} title={title} {...(className ? { className } : {})} />\n );\n}\n\nfunction metricSeries(metric: WorkloadCardResourceMetric): TimeseriesSeries[] {\n const series: TimeseriesSeries[] = [\n { ...metric.value, label: metric.valueLabel ?? \"usage\" },\n ];\n if (typeof metric.max === \"object\") {\n series.push({ ...metric.max, label: metric.maxLabel ?? \"limit\" });\n }\n return series;\n}\n\n// A fixed numeric max has no series to plot, so the history chart draws it as\n// a flat capacity line instead.\nfunction metricReferenceLines(\n metric: WorkloadCardResourceMetric,\n): TimeseriesReferenceLine[] {\n if (typeof metric.max !== \"number\") return [];\n return [{ value: metric.max, label: metric.maxLabel ?? \"capacity\" }];\n}\n\nfunction hasMetric(metrics: WorkloadCardMetrics): boolean {\n return Boolean(metrics.cpu || metrics.memory || metrics.disk);\n}\n\ntype HeaderMetaItem = { key: string; content: ReactNode; title?: string };\n\nfunction HeaderMeta({\n separator,\n title,\n children,\n}: {\n separator: boolean;\n title?: string | undefined;\n children: ReactNode;\n}) {\n return (\n <span className=\"inline-flex min-w-0 items-center gap-1\" title={title}>\n {separator ? (\n <span\n className=\"h-1 w-1 shrink-0 rounded-full bg-muted-foreground/40\"\n aria-hidden\n />\n ) : null}\n <span className=\"min-w-0 truncate\">{children}</span>\n </span>\n );\n}\n\nfunction headerMetaItems(\n workload: WorkloadCardWorkload,\n typeLabel: string | undefined,\n): HeaderMetaItem[] {\n const items: HeaderMetaItem[] = [];\n if (typeLabel) items.push({ key: \"type\", content: typeLabel });\n if (workload.namespace) {\n items.push({ key: \"namespace\", content: workload.namespace });\n }\n if (workload.role) items.push({ key: \"role\", content: workload.role });\n const replicaText = formatReplicaCounts(workload.replicas);\n if (replicaText) items.push({ key: \"replicas\", content: replicaText });\n if (workload.createdAt !== undefined) {\n items.push({\n key: \"age\",\n content: (\n <>\n age <Timestamp value={workload.createdAt} format=\"relative\" />\n </>\n ),\n });\n }\n workload.metadata?.forEach((item, i) => {\n const plain =\n typeof item.value === \"string\" || typeof item.value === \"number\";\n items.push({\n key: `meta-${i}`,\n content: (\n <>\n <span className=\"text-muted-foreground/70\">{item.label}</span>{\" \"}\n {item.value}\n </>\n ),\n title: plain ? `${item.label}: ${item.value}` : item.label,\n });\n });\n return items;\n}\n\n/**\n * WorkloadCard is a compact resource card for a workload: a Kubernetes\n * resource (`kind`) or anything else (a free-form `type`, `icon` and\n * `metadata`). It shows identity, status, subtitle metadata, and\n * CPU/memory/disk utilization backed by the existing time-series primitives:\n * CPU and memory as core-style bars (memory at one bar per GiB) and disk as a\n * linear progress bar. Callers own the metric series (URL ids or `load`\n * functions); the card only composes them.\n *\n * Like the timeseries widgets it renders, it needs a react-query\n * `QueryClientProvider` above it.\n */\nexport function WorkloadCard({\n workload,\n metrics,\n baseUrl,\n range = \"1h\",\n refreshMs = 5000,\n expandable = true,\n headerActions,\n size = \"md\",\n variant = \"default\",\n fetcher,\n className,\n}: WorkloadCardProps) {\n const [expanded, setExpanded] = useState(false);\n const typeLabel = workloadTypeLabel(workload);\n const statusLabel = workloadStatusLabel(workload.status);\n const metaItems = headerMetaItems(workload, typeLabel);\n const workloadIcon =\n workload.icon ?? (workload.kind ? KIND_ICONS[workload.kind] : UiBox);\n const sizeClasses = SIZE_CLASSES[size];\n\n const panelMetrics = useMemo(() => {\n const list: PanelMetric[] = [];\n if (metrics.cpu) {\n list.push({\n key: \"cpu\",\n title: metrics.cpu.title ?? \"CPU\",\n unit: metrics.cpu.unit ?? \"short\",\n icon: metrics.cpu.icon ?? UiChip,\n metric: metrics.cpu,\n });\n }\n if (metrics.memory) {\n list.push({\n key: \"memory\",\n title: metrics.memory.title ?? \"Memory\",\n unit: metrics.memory.unit ?? \"bytes\",\n icon: metrics.memory.icon ?? UiMemoryStick,\n metric: metrics.memory,\n });\n }\n if (metrics.disk) {\n list.push({\n key: \"disk\",\n title: metrics.disk.title ?? \"Disk\",\n unit: metrics.disk.unit ?? \"bytes\",\n icon: metrics.disk.icon ?? UiHardDrive,\n metric: metrics.disk,\n });\n }\n return list;\n }, [metrics]);\n\n const metricProps = {\n ...(baseUrl !== undefined ? { baseUrl } : {}),\n range,\n refreshMs,\n ...(fetcher !== undefined ? { fetcher } : {}),\n };\n const canExpand = expandable && hasMetric(metrics);\n\n return (\n <div\n className={cn(\n \"w-full rounded-md border\",\n VARIANT_CLASSES[variant],\n sizeClasses.root,\n className,\n )}\n data-size={size}\n data-variant={variant}\n >\n <div\n className={cn(\"flex items-start justify-between\", sizeClasses.header)}\n >\n <div className=\"min-w-0\">\n <div\n className={cn(\"flex min-w-0 items-center\", sizeClasses.titleGap)}\n >\n <WorkloadIcon\n icon={workloadIcon}\n title={typeLabel ?? workload.name}\n className={cn(\"shrink-0 text-muted-foreground\", sizeClasses.icon)}\n />\n <span\n className={cn(\"min-w-0 truncate font-medium\", sizeClasses.name)}\n title={workload.name}\n >\n {workload.name}\n </span>\n </div>\n {metaItems.length > 0 ? (\n <div\n className={cn(\n \"flex flex-wrap items-center text-muted-foreground\",\n sizeClasses.meta,\n )}\n >\n {metaItems.map((item, i) => (\n <HeaderMeta\n key={item.key}\n separator={i > 0}\n title={item.title}\n >\n {item.content}\n </HeaderMeta>\n ))}\n </div>\n ) : null}\n </div>\n <div className=\"flex shrink-0 items-center gap-1\">\n {statusLabel ? (\n <span title={workload.status?.message}>\n <Badge\n tone={workloadStatusTone(workload.status)}\n variant=\"soft\"\n size={sizeClasses.statusSize}\n >\n {statusLabel}\n </Badge>\n </span>\n ) : null}\n {headerActions}\n {canExpand ? (\n <button\n type=\"button\"\n aria-label={`Open ${workload.name} history`}\n title=\"History\"\n onClick={() => setExpanded(true)}\n className={cn(\n \"inline-flex items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\",\n sizeClasses.action,\n )}\n >\n <Icon\n icon={UiFullscreen}\n width={sizeClasses.actionIcon}\n height={sizeClasses.actionIcon}\n />\n </button>\n ) : null}\n </div>\n </div>\n\n {hasMetric(metrics) ? (\n <div\n className={cn(\n \"grid items-start gap-3\",\n sizeClasses.metricVariant === \"cell\"\n ? sizeClasses.metricGrid\n : cn(\n metricGridClass(panelMetrics.length),\n sizeClasses.metricGrid,\n ),\n )}\n >\n {metrics.cpu ? (\n <TimeseriesCoreBars\n title={metrics.cpu.title ?? \"CPU\"}\n icon={metrics.cpu.icon ?? UiChip}\n value={metrics.cpu.value}\n {...(metrics.cpu.max !== undefined\n ? { max: metrics.cpu.max }\n : {})}\n {...(metrics.cpu.thresholds\n ? { thresholds: metrics.cpu.thresholds }\n : {})}\n {...(metrics.cpu.orientation\n ? { orientation: metrics.cpu.orientation }\n : {})}\n {...(metrics.cpu.showValue !== undefined\n ? { showValue: metrics.cpu.showValue }\n : {})}\n variant={sizeClasses.metricVariant}\n {...metricProps}\n />\n ) : null}\n {metrics.memory ? (\n <TimeseriesCoreBars\n title={metrics.memory.title ?? \"Memory\"}\n icon={metrics.memory.icon ?? UiMemoryStick}\n value={metrics.memory.value}\n unit={MEMORY_BAR_UNIT}\n variant={sizeClasses.metricVariant}\n {...(metrics.memory.max !== undefined\n ? { max: metrics.memory.max }\n : {})}\n {...(metrics.memory.thresholds\n ? { thresholds: metrics.memory.thresholds }\n : {})}\n {...metricProps}\n />\n ) : null}\n {metrics.disk ? (\n <TimeseriesGauge\n title={metrics.disk.title ?? \"Disk\"}\n icon={metrics.disk.icon ?? UiHardDrive}\n value={metrics.disk.value}\n unit={metrics.disk.unit ?? \"bytes\"}\n shape=\"linear\"\n expandable={false}\n centerDisplay={metrics.disk.centerDisplay ?? \"value\"}\n variant={sizeClasses.metricVariant}\n {...(metrics.disk.max !== undefined\n ? { max: metrics.disk.max }\n : {})}\n {...(metrics.disk.thresholds\n ? { thresholds: metrics.disk.thresholds }\n : {})}\n {...metricProps}\n />\n ) : null}\n </div>\n ) : (\n <div\n className={cn(\n \"rounded-md border border-dashed border-border text-center text-muted-foreground\",\n sizeClasses.empty,\n )}\n >\n No metrics configured\n </div>\n )}\n\n <Modal\n open={expanded}\n onClose={() => setExpanded(false)}\n title={`${workload.name} history`}\n size=\"xl\"\n >\n <div className=\"flex flex-col gap-3\">\n {panelMetrics.map(({ key, title, unit, icon, metric }) => (\n <TimeseriesPanel\n key={key}\n title={title}\n icon={icon}\n series={metricSeries(metric)}\n referenceLines={metricReferenceLines(metric)}\n {...(unit ? { unit } : {})}\n {...metricProps}\n expandable={false}\n />\n ))}\n </div>\n </Modal>\n </div>\n );\n}\n"],"names":["K8SService","K8SIngress","K8SDeployment","K8SStatefulset","K8SPod","jsx","Icon","jsxs","formatReplicaCounts","Fragment","Timestamp","useState","workloadTypeLabel","workloadStatusLabel","UiBox","useMemo","UiChip","UiMemoryStick","UiHardDrive","cn","Badge","workloadStatusTone","UiFullscreen","TimeseriesCoreBars","TimeseriesGauge","Modal","TimeseriesPanel"],"mappings":"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"}