diff --git a/frontend/src/components/.pi-map.index.md b/frontend/src/components/.pi-map.index.md index ed3ef40..36deff6 100644 --- a/frontend/src/components/.pi-map.index.md +++ b/frontend/src/components/.pi-map.index.md @@ -2,7 +2,7 @@ dir: frontend/src/components ## role -Reusable UI component library providing presentation-layer building blocks (tables, cards, charts, dialogs, widgets) for the frontend application. +Shared UI component library providing reusable React display, table, dialog, card, chart, and widget components for the frontend application. ## parent index: frontend/src/.pi-map.index.md map: frontend/src/.pi-map.md @@ -28,6 +28,7 @@ map: frontend/src/.pi-map.md - PinnedServiceLink.tsx - SectionCard.tsx - SelectionRailCard.tsx +- ServiceTestPanel.tsx - SessionActivityPanel.tsx - TabbedCard.tsx - WidgetConfigDialog.tsx diff --git a/frontend/src/components/.pi-map.md b/frontend/src/components/.pi-map.md index 8582260..d9cb66d 100644 --- a/frontend/src/components/.pi-map.md +++ b/frontend/src/components/.pi-map.md @@ -4,7 +4,7 @@ dir: frontend/src/components index: frontend/src/components/.pi-map.index.md ## role -Reusable UI component library providing presentation-layer building blocks (tables, cards, charts, dialogs, widgets) for the frontend application. +Shared UI component library providing reusable React display, table, dialog, card, chart, and widget components for the frontend application. ## files - BackupAlertsTable.tsx | Renders a responsive table of backup alerts with severity badges and acknowledge actions, switching between desktop table and mobile card layouts. | exp: func:BackupAlertsTable({ alerts, onAcknowledge }: Props), call:useIsMobile, call:onAcknowledge, call:alerts.map, call:severityVariant, call:formatTimestamp | dep: @/components/ui/badge, @/components/ui/button, @/components/ui/table, @/components/ui/mobile-card, ../hooks/useIsMobile, ../types/backups, useIsMobile hook, BackupAlert type - BackupDashboardWidget.tsx | Displays a dashboard widget summarizing backup job statistics including total jobs, 24-hour success rate, active alerts, and last failure timestamp. | exp: func:BackupDashboardWidget(), call:useBackupDashboard, call:new Date(data.last_failed_at * 1000).toLocaleString | dep: @/components/ui/badge, @/components/ui/card, ../hooks/useBackups @@ -14,18 +14,19 @@ Reusable UI component library providing presentation-layer building blocks (tabl - DialogFooter.tsx | Renders a dialog footer component with cancel, optional secondary action, and confirm buttons, mapping legacy MUI color/variant props to shadcn Button variants. | exp: func:DialogFooter({ onCancel, cancelLabel = "Cancel", onConfirm, confirmLabel, confirmBusyLabel, confirmDisabled, confirmColor = "primary", confirmVariant = "contained", confirmStartIcon, secondaryAction, }: DialogFooterProps), call:resolveConfirmVariant | dep: react, @/components/ui/button - HoverEditButton.tsx | Renders a hover-reveal edit button for desktop and always-visible edit button for mobile, preserving legacy CSS class hooks. | exp: func:HoverEditButton({ onClick, label = "Edit", mobile = "always", }: HoverEditButtonProps), call:e.stopPropagation, call:onClick | dep: lucide-react, @/components/ui/button - LibraryOverview.tsx | Renders a two-column responsive grid displaying movie and TV library counts using shadcn/ui Card components | exp: func:LibraryOverview({ libraries }: Props), call:libraries.filter, call:movieLibs.map, call:lib.total.toLocaleString, call:lib.movies.toLocaleString, call:tvLibs.map, call:lib.series.toLocaleString | dep: @/components/ui/card, ../types -- LineSeriesChart.tsx | Renders multiple time-series as a shared recharts line chart with merged data and formatted timestamps. | exp: SeriesPoint, ChartSeries, func:LineSeriesChart({ series, height = 300, }: LineSeriesChartProps), call:mergeSeries, call:formatTime, call:Number, call:series.map | dep: recharts +- LineSeriesChart.tsx | Renders multiple time-series as a shared recharts line-chart with merged timestamps. | exp: SeriesPoint, ChartSeries, func:LineSeriesChart({ series, height = 300, }: LineSeriesChartProps), call:mergeSeries, call:formatTime, call:Number, call:series.map | dep: recharts - MetricCard.tsx | Renders a compact metric display card with label, value, and optional subtext using Tailwind CSS styling. | exp: func:MetricCard({ label, value, subtext }: Props) | dep: @/components/ui/card - NowPlaying.tsx | Renders a now-playing panel by wrapping SessionActivityPanel with a specific empty message for user activity sessions. | exp: func:NowPlaying({ sessions, onSelectSession }: Props) | dep: ../types, ./SessionActivityPanel - PinnedServiceLink.tsx | Renders a navigable card-shaped button for pinned service shortcuts on dashboards and provides a helper to construct service target paths. | exp: PinnedServiceLinkProps, func:PinnedServiceLink({ label, target, icon: Icon = Boxes, className, }: PinnedServiceLinkProps), call:useNavigate, call:navigate, call:cn, func:serviceLinkTarget(serviceType: string, serviceId: string, tab: string) → string | dep: react-router-dom, lucide-react, @/lib/utils - SectionCard.tsx | A reusable card component that renders a titled section with optional description and action, built on shadcn/ui Card primitives for comfortable density layout. | exp: func:SectionCard({ title, description, action, children, }: SectionCardProps) | dep: react, @/components/ui/card - SelectionRailCard.tsx | A reusable card component for a selection rail UI with titled header, scrollable body, and optional footer, preserving legacy API compatibility during a migration from MUI. | exp: func:SelectionRailCard({ title, description, children, footer, minHeight = 420, }: SelectionRailCardProps) | dep: react, @/components/ui/card +- ServiceTestPanel.tsx | Presentational component rendering a "Test credentials" panel with test button, result display, and a "Save anyway" checkbox. | exp: func:ServiceTestPanel({ result, isPending, saveAnyway, onTest, onSaveAnywayChange, disabled, }: Props), call:onSaveAnywayChange | dep: @/components/ui/alert, @/components/ui/button, ../types - SessionActivityPanel.tsx | Renders a scrollable table displaying live media session activity details with status badges and optional session selection callbacks. | exp: func:SessionActivityPanel({ sessions, emptyMessage = "No live sessions matched to this user.", selectedUserLabel, onSelectSession, }: Props), call:buildStatusSummary, call:sessions.map, call:formatStateLabel, call:onSelectSession, call:sessionStateVariant, call:event.stopPropagation | dep: @/components/ui/badge, @/components/ui/button, @/components/ui/table, ../types - TabbedCard.tsx | Renders a card with a line-style tab bar header and content area, acting as a controlled wrapper around shadcn/ui Tabs for backward-compatible API migration from MUI. | exp: func:TabbedCard({ value, onChange, tabs, children, }: TabbedCardProps), call:onChange, call:String | dep: react, @/components/ui/card, @/components/ui/tabs -- WidgetConfigDialog.tsx | Provides a user interface dialog and form for creating, configuring, editing, and managing the sort order of dashboard widgets and widget references. | exp: func:WidgetConfigDialog({ open, onClose, serviceId, dashboardScope, editWidgetId, }: Props), call:useWidgetInstances, call:useServiceInstances, call:useTasks, call:useSaveWidgetInstance, call:useDeleteWidgetInstance, call:useWidgetReferences, call:useCreateWidgetReference, call:useDeleteWidgetReference, call:useDetachWidgetReference, call:useUpdateWidgetReference, call:useState, call:Boolean, call:useEffect, call:instances.find, call:references.find, call:startEdit, call:setDraft, call:SERVICE_REGISTRY[ services.find((s) => s.id === serviceId)?.service_type ?? "" ]?.widgets.find, call:services.find, call:setDraftBaseline, call:onClose, call:saveWidget.mutateAsync, call:reset, call:updateRef.mutateAsync, call:deleteWidget.mutateAsync, call:[...instances].sort, call:references.map, call:[...owned, ...refs].sort, call:useMemo, call:instances.map, call:existingSearch.toLowerCase().trim, call:allWidgets .filter((w) => !onDashboard.has(w.id)) .filter, call:onDashboard.has, call:w.title.toLowerCase().includes, call:w.widget_kind.toLowerCase().includes, call:createRef.mutateAsync, call:deleteRef.mutateAsync, call:detachRef.mutateAsync, call:SERVICE_REGISTRY[ services.find((s) => s.id === draft.serviceId)?.service_type ?? "" ]?.widgets.find, call:useIsMobile, call:String, call:Number, call:combinedWidgets.map, call:bindingLabel, call:moveInstance, call:toggleEnabled, call:handleDetach, call:handleRemoveReference, call:removeInstance, call:setShowExisting, call:setExistingSearch, call:availableWidgets.map, call:handleAddReference, call:Object.values(BUILTIN_WIDGETS).map, call:startAddBuiltIn, call:services .filter((s) => s.enabled) // When scoped to a service Overview, only show widgets for THAT // service instance's type (not all services' widgets). .filter((s) => !serviceId || s.id === serviceId) .flatMap, call:(SERVICE_REGISTRY[s.service_type]?.widgets ?? []).map, call:startAddService, call:handleClose, call:JSON.stringify | dep: react, @/components/ui/dialog, @/components/ui/button, @/components/ui/input, @/components/ui/textarea, @/components/ui/label, @/components/ui/switch, @/components/ui/select, @/components/ui/badge, @/components/ui/alert, lucide-react, ../hooks/useWidgets, ../hooks/useServices, ../hooks/useSettings, ../hooks/useIsMobile, @/components/ui/sheet-form, ../types, ../integrations/registry, @/components/ui/* +- WidgetConfigDialog.tsx | Provides a dialog and sheet interface for creating, configuring, reordering, and managing the lifecycle of UI widgets and their dashboard references. | exp: func:WidgetConfigDialog({ open, onClose, serviceId, dashboardScope, editWidgetId, }: Props), call:useWidgetInstances, call:useMemo, call:useServiceInstances, call:useTasks, call:useSaveWidgetInstance, call:useDeleteWidgetInstance, call:useWidgetReferences, call:useCreateWidgetReference, call:useDeleteWidgetReference, call:useDetachWidgetReference, call:useUpdateWidgetReference, call:useState, call:Boolean, call:useEffect, call:instances.find, call:references.find, call:startEdit, call:setDraft, call:SERVICE_REGISTRY[ services.find((s) => s.id === serviceId)?.service_type ?? "" ]?.widgets.find, call:services.find, call:setDraftBaseline, call:onClose, call:saveWidget.mutateAsync, call:reset, call:updateRef.mutateAsync, call:deleteWidget.mutateAsync, call:[...instances].sort, call:references.map, call:[...owned, ...refs].sort, call:instances.map, call:existingSearch.toLowerCase().trim, call:allWidgets .filter((w) => !onDashboard.has(w.id)) .filter, call:onDashboard.has, call:w.title.toLowerCase().includes, call:w.widget_kind.toLowerCase().includes, call:createRef.mutateAsync, call:deleteRef.mutateAsync, call:detachRef.mutateAsync, call:SERVICE_REGISTRY[ services.find((s) => s.id === draft.serviceId)?.service_type ?? "" ]?.widgets.find, call:useIsMobile, call:String, call:Number, call:combinedWidgets.map, call:bindingLabel, call:moveInstance, call:toggleEnabled, call:handleDetach, call:handleRemoveReference, call:removeInstance, call:setShowExisting, call:setExistingSearch, call:availableWidgets.map, call:handleAddReference, call:Object.values(BUILTIN_WIDGETS).map, call:startAddBuiltIn, call:services .filter((s) => s.enabled) // When scoped to a service Overview, only show widgets for THAT // service instance's type (not all services' widgets). .filter((s) => !serviceId || s.id === serviceId) .flatMap, call:(SERVICE_REGISTRY[s.service_type]?.widgets ?? []).map, call:startAddService, call:handleClose, call:JSON.stringify | dep: react, @/components/ui/dialog, @/components/ui/button, @/components/ui/input, @/components/ui/textarea, @/components/ui/label, @/components/ui/switch, @/components/ui/select, @/components/ui/badge, @/components/ui/alert, lucide-react, ../hooks/useWidgets, ../hooks/useServices, ../hooks/useSettings, ../hooks/useIsMobile, @/components/ui/sheet-form, ../types, ../integrations/registry, @/components/ui (dialog, button, input, textarea, label, switch, select, badge, alert, sheet-form) - WidgetInstance.tsx | Renders a widget instance card that dynamically resolves and displays a widget component, with optional edit and copy actions. | exp: func:WidgetInstanceCard({ widget, onEdit, onCopy }: Props), call:useServiceInstances, call:resolveWidget, call:onCopy, call:onEdit | dep: @/components/ui/alert, @/components/ui/button, lucide-react, ../hooks/useServices, ../integrations/registry, ../types, ./SectionCard ## arch -React functional components with responsive design patterns, built on shadcn/ui and Tailwind CSS with adapter layers for backward-compatible MUI-to-shadcn migration. +Presentational React/TypeScript components built on shadcn/ui primitives with responsive (desktop/mobile) layouts, wrapping recharts for visualization and maintaining backward-compatible APIs during migration from MUI. ## tags call:use, components, ui, card, widget, table, backup, call:on ## symbols diff --git a/frontend/src/components/LineSeriesChart.tsx b/frontend/src/components/LineSeriesChart.tsx index 7720db7..beaf39b 100644 --- a/frontend/src/components/LineSeriesChart.tsx +++ b/frontend/src/components/LineSeriesChart.tsx @@ -72,10 +72,10 @@ export function LineSeriesChart({ formatTime(Number(label))} contentStyle={{ - backgroundColor: "hsl(var(--popover))", - border: "1px solid hsl(var(--border))", + backgroundColor: "var(--color-popover)", + border: "1px solid var(--color-border)", borderRadius: "0.5rem", - color: "hsl(var(--popover-foreground))", + color: "var(--color-popover-foreground)", }} /> {series.map((s, i) => ( diff --git a/frontend/src/widgets/.pi-map.index.md b/frontend/src/widgets/.pi-map.index.md index 6957a99..9687fb4 100644 --- a/frontend/src/widgets/.pi-map.index.md +++ b/frontend/src/widgets/.pi-map.index.md @@ -2,7 +2,7 @@ dir: frontend/src/widgets ## role -Dashboard widget components that render visual data for various integrations (Alertmanager, Backups, Jellyfin, Prometheus, qBittorrent, SSH tasks, static content) within the monitoring application. +Provides self-contained dashboard widget components for rendering various data sources (Alertmanager, backups, Jellyfin, Prometheus metrics, qBittorrent, SSH tasks, and static content) in a unified card layout. ## parent index: frontend/src/.pi-map.index.md map: frontend/src/.pi-map.md @@ -15,9 +15,9 @@ map: frontend/src/.pi-map.md - BackupsWidget.tsx - JellyfinNowPlayingWidget.tsx - JellyfinWidget.tsx -- PrometheusChartWidget.tsx -- PrometheusGaugeWidget.tsx -- PrometheusMeanWidget.tsx +- MetricChartWidget.tsx +- MetricGaugeWidget.tsx +- MetricMeanWidget.tsx - PrometheusMetricWidget.tsx - QbittorrentActiveTorrentsWidget.tsx - QbittorrentSpeedWidget.tsx diff --git a/frontend/src/widgets/.pi-map.md b/frontend/src/widgets/.pi-map.md index dbca410..c5e40d7 100644 --- a/frontend/src/widgets/.pi-map.md +++ b/frontend/src/widgets/.pi-map.md @@ -4,34 +4,34 @@ dir: frontend/src/widgets index: frontend/src/widgets/.pi-map.index.md ## role -Dashboard widget components that render visual data for various integrations (Alertmanager, Backups, Jellyfin, Prometheus, qBittorrent, SSH tasks, static content) within the monitoring application. +Provides self-contained dashboard widget components for rendering various data sources (Alertmanager, backups, Jellyfin, Prometheus metrics, qBittorrent, SSH tasks, and static content) in a unified card layout. ## files - AlertmanagerAlertsWidget.tsx | Renders an Alertmanager alerts dashboard widget displaying alert summaries, severity badges, and individual alert details. | exp: func:AlertmanagerAlertsWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:Object.entries(summary.by_severity).map, call:severityVariant, call:alerts.slice(0, 5).map | dep: @/components/ui/alert, @/components/ui/badge, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types - BackupsWidget.tsx | Displays a dashboard widget showing backup job metrics including total jobs, 24-hour success rate, active alerts, and last failure timestamp. | exp: func:BackupsWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:new Date(summary.last_failed_at * 1000).toLocaleString | dep: @/components/ui/alert, @/components/ui/badge, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types/backups, ../types - JellyfinNowPlayingWidget.tsx | Displays a Jellyfin now-playing widget that fetches and renders active media sessions using a custom data hook and session activity panel. | exp: func:JellyfinNowPlayingWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:Array.isArray | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/SessionActivityPanel, ../components/SectionCard, ../hooks/useWidgets, ../types, SessionActivityPanel, SectionCard, useWidgetData, types - JellyfinWidget.tsx | Displays Jellyfin media server activity sessions in a widget with loading, error, and empty states. | exp: func:JellyfinWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:Array.isArray | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/SessionActivityPanel, ../components/SectionCard, ../hooks/useWidgets, ../types -- PrometheusChartWidget.tsx | Renders a Prometheus query widget that fetches time-series data and displays it as a line chart, with loading, error, and empty states. | exp: func:PrometheusChartWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/LineSeriesChart, ../components/SectionCard, ../hooks/useWidgets, ../types, LineSeriesChart, SectionCard, useWidgetData hook, WidgetInstance type -- PrometheusGaugeWidget.tsx | Renders a Prometheus metric as a radial gauge widget with configurable warning/critical threshold bands. | exp: func:PrometheusGaugeWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:Math.max, call:Math.round, call:Math.min, call:toPercent, call:trackCells.map, call:formatValue | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types, recharts, SectionCard, useWidgetData -- PrometheusMeanWidget.tsx | Displays a single Prometheus metric value (mean) with adaptive formatting and unit support in a dashboard widget. | exp: func:PrometheusMeanWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:formatMean | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types, SectionCard, useWidgetData, WidgetInstance -- PrometheusMetricWidget.tsx | A React component that fetches and displays Prometheus metric data from a widget, handling loading, error, and data states with formatted output for scalar and vector result types. | exp: func:PrometheusMetricWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:formatPrometheusValue | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types +- MetricChartWidget.tsx | Renders a metric chart widget that fetches time-series data and displays it in a line chart with loading, error, and empty states. | exp: func:MetricChartWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/LineSeriesChart, ../components/SectionCard, ../hooks/useWidgets, ../types, LineSeriesChart, SectionCard, useWidgetData, WidgetInstance type +- MetricGaugeWidget.tsx | Renders a metric gauge widget using a radial bar chart with threshold-based color bands for visualizing a single numeric value. | exp: func:MetricGaugeWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:Math.max, call:Math.round, call:Math.min, call:toPercent, call:formatValue | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types, recharts, SectionCard, useWidgetData, WidgetInstance types +- MetricMeanWidget.tsx | Displays a metric mean value from Prometheus/PromQL query results with conditional loading, error, and empty states. | exp: func:MetricMeanWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:formatMean | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types, SectionCard, useWidgetData, WidgetInstance +- PrometheusMetricWidget.tsx | Displays a Prometheus metric widget that fetches and formats time-series data with loading and error states. | exp: func:PrometheusMetricWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:formatMetricResult | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types, SectionCard, useWidgetData, WidgetInstance - QbittorrentActiveTorrentsWidget.tsx | Displays a list of active torrents from a qBittorrent instance with download/upload speeds and status badges in a dashboard widget. | exp: func:QbittorrentActiveTorrentsWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:torrents.map, call:formatSpeed | dep: @/components/ui/alert, @/components/ui/badge, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types, SectionCard, useWidgetData hook, WidgetInstance type - QbittorrentSpeedWidget.tsx | Renders a qBittorrent download/upload speed widget with a line series chart, handling loading, error, and empty states. | exp: func:QbittorrentSpeedWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/LineSeriesChart, ../components/SectionCard, ../hooks/useWidgets, ../types, LineSeriesChart, SectionCard, useWidgetData hook - QbittorrentTotalsWidget.tsx | Displays qBittorrent torrent totals and per-state breakdown using a widget with loading, error, and data states. | exp: func:QbittorrentTotalsWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:Object.keys, call:Object.entries(payload.by_state).map | dep: @/components/ui/alert, @/components/ui/badge, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types - SshTaskWidget.tsx | Displays SSH task execution results with exit status, stdout, and stderr in a polling widget card | exp: func:SshTaskWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types - StaticWidget.tsx | Renders a static text widget that displays fetched text content or a fallback message within a section card. | exp: func:StaticWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData | dep: ../components/SectionCard, ../hooks/useWidgets, ../types, SectionCard, useWidgetData, WidgetInstance -- index.ts | Barrel file that re-exports all dashboard widget components from a widgets directory. | dep: AlertmanagerAlertsWidget, BackupsWidget, PrometheusChartWidget, PrometheusGaugeWidget, PrometheusMeanWidget, JellyfinWidget, JellyfinNowPlayingWidget, PrometheusMetricWidget, QbittorrentActiveTorrentsWidget, QbittorrentSpeedWidget, QbittorrentTotalsWidget, SshTaskWidget, StaticWidget +- index.ts | Barrel file that re-exports all widget components from a dashboard/widget module. | dep: AlertmanagerAlertsWidget, BackupsWidget, MetricChartWidget, MetricGaugeWidget, MetricMeanWidget, JellyfinWidget, JellyfinNowPlayingWidget, PrometheusMetricWidget, QbittorrentActiveTorrentsWidget, QbittorrentSpeedWidget, QbittorrentTotalsWidget, SshTaskWidget, StaticWidget ## arch -React functional components using a per-widget pattern with consistent loading/error/empty state handling, custom data hooks, and barrel-file aggregation for modular dashboard composition. +Composable React functional components using custom data-fetching hooks, each implementing independent loading/error/empty state handling with chart visualizations, exported via a barrel file for modular dashboard composition. ## tags -widget, components, data, ui, call:use, prometheus, sectioncard, types +widget, components, data, ui, call:use, metric, sectioncard, types ## symbols - AlertmanagerAlertsWidget - BackupsWidget - JellyfinNowPlayingWidget - JellyfinWidget -- PrometheusChartWidget -- PrometheusGaugeWidget -- PrometheusMeanWidget +- MetricChartWidget +- MetricGaugeWidget +- MetricMeanWidget - PrometheusMetricWidget ## workflows - change widgets behavior diff --git a/frontend/src/widgets/MetricGaugeWidget.tsx b/frontend/src/widgets/MetricGaugeWidget.tsx index 2130942..3168b8e 100644 --- a/frontend/src/widgets/MetricGaugeWidget.tsx +++ b/frontend/src/widgets/MetricGaugeWidget.tsx @@ -60,22 +60,18 @@ export function MetricGaugeWidget({ const toPercent = (v: number) => Math.round(((v - min) / range) * 100); const valuePct = Math.max(0, Math.min(100, toPercent(value))); - // Build track cells: green / amber / red when bands are set, else neutral. - const trackCells = hasBands - ? [ - { pct: toPercent(warnAt!), fill: "hsl(var(--chart-1))" }, // green - { pct: toPercent(critAt!), fill: "hsl(var(--chart-4))" }, // amber - { pct: 100, fill: "hsl(var(--destructive))" }, // red - ] - : [{ pct: 100, fill: "hsl(var(--muted))" }]; - + // The value arc is colored by status (green < warn, amber < crit, red >= crit) + // and drawn over a neutral full-track background. We intentionally do NOT + // render separate "track band" rings: recharts draws each data entry as a + // concentric ring (not an arc segment), so multiple entries produced nested + // rings that looked broken. const valueColor = hasBands ? value >= critAt! - ? "hsl(var(--destructive))" + ? "var(--color-chart-4)" // red (destructive) : value >= warnAt! - ? "hsl(var(--chart-4))" - : "hsl(var(--chart-1))" - : "hsl(var(--primary))"; + ? "var(--color-chart-3)" // amber (warning) + : "var(--color-chart-2)" // green (success) + : "var(--color-primary)"; return ( @@ -86,19 +82,12 @@ export function MetricGaugeWidget({ {data.error} ) : gauge ? ( -
- +
+ ({ - name: "track", - pct: c.pct, - fill: c.fill, - })), - { name: "value", pct: valuePct, fill: valueColor }, - ]} + data={[{ name: "value", pct: valuePct, fill: valueColor }]} startAngle={90} endAngle={-270} > @@ -109,18 +98,18 @@ export function MetricGaugeWidget({ tick={false} /> -
- +
+ {formatValue(value, gauge.unit)} {hasBands ? ( - + warn {formatValue(warnAt!, gauge.unit)} · crit{" "} {formatValue(critAt!, gauge.unit)}