refactor(widgets): let the UniFi OS chart fill the tile
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fa945e947e
commit
35095276fa
2 files changed
+62
-26
No files matched your search
@@ -11,7 +11,7 @@ import { useIntegrationConnected } from "@homarr/common";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
import { ThroughputChart } from "./throughput-chart";
|
||||
import { ThroughputChart, ThroughputLegend } from "./throughput-chart";
|
||||
import type { ThroughputPoint } from "./throughput-history";
|
||||
import { appendThroughputPoint } from "./throughput-history";
|
||||
|
||||
@@ -94,8 +94,11 @@ export default function UnifiOsWidget({ integrationIds, options }: WidgetCompone
|
||||
const wanColor = summary.wan.state === "online" ? "green" : summary.wan.state === "offline" ? "red" : "gray";
|
||||
const isp = summary.wan.isp;
|
||||
|
||||
// No `justify="space-between"` on the outer stack: the Internet card flexes to
|
||||
// take the leftover height, so there is no slack left to distribute —
|
||||
// distributing it is exactly what piled empty space between the bottom rows.
|
||||
return (
|
||||
<Stack h="100%" p="sm" gap="xs" justify="space-between">
|
||||
<Stack h="100%" p="sm" gap="xs">
|
||||
{/* The site name is not decorative: a console can host several sites and
|
||||
the widget only shows one. Naming it makes that choice visible. */}
|
||||
<Group gap={6} wrap="nowrap">
|
||||
@@ -116,11 +119,23 @@ export default function UnifiOsWidget({ integrationIds, options }: WidgetCompone
|
||||
)}
|
||||
</Group>
|
||||
|
||||
{/* The one block that grows; the header, the stats rows and the footer keep
|
||||
their natural size. No `minHeight: 0` here on purpose: a flex item's
|
||||
automatic minimum is its content, so on a tile too short for everything
|
||||
the card stops shrinking at its own content and the tile scrolls, as it
|
||||
already did, rather than the card being squeezed and spilling the graph
|
||||
over the rows below it. */}
|
||||
<Stack
|
||||
gap={4}
|
||||
p="xs"
|
||||
style={{ borderRadius: "var(--mantine-radius-md)", background: "var(--mantine-color-default-hover)" }}
|
||||
style={{
|
||||
flex: 1,
|
||||
borderRadius: "var(--mantine-radius-md)",
|
||||
background: "var(--mantine-color-default-hover)",
|
||||
}}
|
||||
>
|
||||
{/* The rates ride the header row, right-aligned opposite the title, so
|
||||
they read as a heading and detach from the area below them. */}
|
||||
<Group justify="space-between" wrap="nowrap" gap="xs">
|
||||
<Group gap={6} wrap="nowrap">
|
||||
<IconCircleFilled size={10} color={`var(--mantine-color-${wanColor}-6)`} />
|
||||
@@ -128,10 +143,15 @@ export default function UnifiOsWidget({ integrationIds, options }: WidgetCompone
|
||||
{t("widget.unifiOs.card.internet")}
|
||||
</Text>
|
||||
</Group>
|
||||
{summary.wan.throughput === null && (
|
||||
{summary.wan.throughput === null ? (
|
||||
<Text size="sm" ff="monospace">
|
||||
{EMPTY_VALUE}
|
||||
</Text>
|
||||
) : (
|
||||
<ThroughputLegend
|
||||
upLabel={formatBitsPerSecond(summary.wan.throughput.txBps)}
|
||||
downLabel={formatBitsPerSecond(summary.wan.throughput.rxBps)}
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
@@ -141,8 +161,6 @@ export default function UnifiOsWidget({ integrationIds, options }: WidgetCompone
|
||||
{summary.wan.throughput !== null && (
|
||||
<ThroughputChart
|
||||
history={throughputHistory}
|
||||
upLabel={formatBitsPerSecond(summary.wan.throughput.txBps)}
|
||||
downLabel={formatBitsPerSecond(summary.wan.throughput.rxBps)}
|
||||
formatRate={formatBitsPerSecond}
|
||||
upName={t("widget.unifiOs.card.up")}
|
||||
downName={t("widget.unifiOs.card.down")}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { AreaChart } from "@mantine/charts";
|
||||
import { Group, Paper, Stack, Text } from "@mantine/core";
|
||||
import { Box, Group, Paper, Stack, Text } from "@mantine/core";
|
||||
|
||||
import type { ThroughputPoint } from "./throughput-history";
|
||||
|
||||
@@ -19,7 +19,13 @@ import type { ThroughputPoint } from "./throughput-history";
|
||||
const DOWN_COLOR = "blue.7";
|
||||
const UP_COLOR = "orange.8";
|
||||
|
||||
const CHART_HEIGHT = 44;
|
||||
/**
|
||||
* A floor, not a height: the chart now takes the height the card has left over.
|
||||
* `ResponsiveContainer` measures its parent and renders nothing at all when that
|
||||
* parent collapses to zero, so the wrapper keeps a small non-zero minimum — on a
|
||||
* very short tile the area stays legible instead of disappearing.
|
||||
*/
|
||||
const CHART_MIN_HEIGHT = 32;
|
||||
|
||||
/**
|
||||
* A dot carries the series identity so the figure beside it can stay in muted
|
||||
@@ -69,34 +75,46 @@ const ThroughputTooltip = ({
|
||||
);
|
||||
};
|
||||
|
||||
export interface ThroughputChartProps {
|
||||
history: ThroughputPoint[];
|
||||
export interface ThroughputLegendProps {
|
||||
/** Rendered beside the up marker, already formatted. */
|
||||
upLabel: string;
|
||||
/** Rendered beside the down marker, already formatted. */
|
||||
downLabel: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sits on the card's "Internet" header row rather than above the graph, so the
|
||||
* figures read as a heading and detach from the area they summarise. Exported
|
||||
* from here instead of being rewritten there: the dots take the very constants
|
||||
* the chart's series use, so the two can never drift apart.
|
||||
*/
|
||||
export const ThroughputLegend = ({ upLabel, downLabel }: ThroughputLegendProps) => (
|
||||
<Group gap="sm" wrap="nowrap" justify="flex-end">
|
||||
<SeriesKey color={DOWN_COLOR} label={`↓ ${downLabel}`} />
|
||||
<SeriesKey color={UP_COLOR} label={`↑ ${upLabel}`} />
|
||||
</Group>
|
||||
);
|
||||
|
||||
export interface ThroughputChartProps {
|
||||
history: ThroughputPoint[];
|
||||
/** Formats a raw bits-per-second value for the tooltip. */
|
||||
formatRate: (bitsPerSecond: number) => string;
|
||||
upName: string;
|
||||
downName: string;
|
||||
}
|
||||
|
||||
export const ThroughputChart = ({
|
||||
history,
|
||||
upLabel,
|
||||
downLabel,
|
||||
formatRate,
|
||||
upName,
|
||||
downName,
|
||||
}: ThroughputChartProps) => (
|
||||
<Stack gap={2}>
|
||||
<Group gap="sm" wrap="nowrap" justify="flex-end">
|
||||
<SeriesKey color={DOWN_COLOR} label={`↓ ${downLabel}`} />
|
||||
<SeriesKey color={UP_COLOR} label={`↑ ${upLabel}`} />
|
||||
</Group>
|
||||
|
||||
/**
|
||||
* The wrapper is the flex child that claims the card's leftover height, and the
|
||||
* chart fills it with `h="100%"`. Mantine turns that into an inline
|
||||
* `height: 100%` on the chart root, which recharts' `ResponsiveContainer` then
|
||||
* measures; a percentage resolves here because flex layout gives this wrapper a
|
||||
* definite used height, the card above it being a flex item of a `h="100%"`
|
||||
* stack inside a tile of fixed height.
|
||||
*/
|
||||
export const ThroughputChart = ({ history, formatRate, upName, downName }: ThroughputChartProps) => (
|
||||
<Box style={{ flex: 1, minHeight: CHART_MIN_HEIGHT }}>
|
||||
<AreaChart
|
||||
h={CHART_HEIGHT}
|
||||
h="100%"
|
||||
data={history}
|
||||
dataKey="index"
|
||||
series={[
|
||||
@@ -115,5 +133,5 @@ export const ThroughputChart = ({
|
||||
content: <ThroughputTooltip formatRate={formatRate} upName={upName} downName={downName} />,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
Reference in new issue
Block a user