feat(widgets): scale chart axes/tooltips with unit + scale options

Consistent graph scaling across every line-chart widget. A new shared
frontend/src/lib/metricFormat.ts picks a decimal prefix (kB/MB/GB, kbps/Mbps,
Gbps, …) from the series magnitude and formats values; LineSeriesChart accepts
unit + scale and formats both the Y-axis ticks and the tooltip with the SAME
prefix (one consistent unit per axis). MetricChartWidget (Prometheus) and
QbittorrentSpeedWidget pass the widget config through; qBit speed defaults to
bytes/sec → MB/s.

WidgetConfigDialog now renders `enum` schema fields as a <Select> dropdown, so
the backend's unit/scale Literal enums become consistent pickers in every graph
widget's config (and any future enum option).

Decimal (x1000) prefixes by default (matches Mbps/MB/s/Grafana).

Tests: 13 new metricFormat tests (auto/fixed scaling, percent, seconds,
nulls, trailing-zero trimming). 179/179 frontend tests pass; tsc + ESLint clean.
This commit is contained in:
Developer
2026-07-12 11:46:07 +00:00
parent 05a9faca3e
commit b7019b33ac
12 changed files with 333 additions and 26 deletions
@@ -2,6 +2,7 @@ import { Alert, AlertDescription } from "@/components/ui/alert";
import { Skeleton } from "@/components/ui/skeleton";
import { LineSeriesChart } from "../components/LineSeriesChart";
import type { ChartSeries } from "../components/LineSeriesChart";
import type { MetricScale, MetricUnit } from "../lib/metricFormat";
import { SectionCard } from "../components/SectionCard";
import { useWidgetData } from "../hooks/useWidgets";
import type { WidgetInstance } from "../types";
@@ -19,6 +20,9 @@ export function QbittorrentSpeedWidget({
}: Props) {
const { data, isLoading } = useWidgetData(widget.id, refreshIntervalMs);
const series = data?.data?.series as ChartSeries[] | undefined;
// Source returns raw bytes/sec; default to bytes/sec + auto scale (MB/s, …).
const unit = (widget.config.unit as MetricUnit) || "bytes_per_sec";
const scale = (widget.config.scale as MetricScale) || "auto";
return (
<SectionCard title={widget.title} description={description}>
@@ -29,7 +33,12 @@ export function QbittorrentSpeedWidget({
<AlertDescription>{data.error}</AlertDescription>
</Alert>
) : series && series.length > 0 ? (
<LineSeriesChart series={series} height={220} />
<LineSeriesChart
series={series}
unit={unit}
scale={scale}
height={220}
/>
) : (
<Alert>
<AlertDescription>No speed data yet</AlertDescription>