fix(infrastructure): wrap gauge row so every disk stays reachable

The gauge row on the infrastructure details page laid out memory, CPU
and one gauge per disk in a single flex row with no wrapping and no
overflow handling. Each gauge is capped at 260px, so on a host with
enough disks the row ran past the content column.

RootLayout sets overflow: "hidden" on that column, so the excess was
clipped outright rather than becoming scrollable - the later disks
rendered but could not be reached by any means.

Three changes, all scoped to this row:

flexWrap moves the overflow onto additional rows.

useFlexGap is required alongside it. MUI's Stack implements `spacing`
as `& > :not(style) ~ :not(style) { marginLeft }`, and that selector is
DOM-order based rather than line-aware, so the first card of each
wrapped row would carry a spurious 16px left margin and misalign
against the row above. useFlexGap switches to a real flex gap.

flexBasis on the children is what actually makes the line break.
BaseChart sets `flex: 1`, which expands to `flex: 1 1 0%`; wrapping is
decided by the sum of items' hypothetical main sizes, so with a zero
basis that sum is 0 and the row never overflows. The cards would
instead shrink to min-content (roughly the 70px gauge plus the widest
label) and stay on one cramped line. Giving each child a 260px basis
makes the row wrap at the intended card width.

The basis is applied through the row's own `& > *` selector rather than
by changing BaseChart, which has eight other consumers that default to
maxWidth: "100%" and would have had their layout altered.

The repeated 260 literals are now a named constant.

Only affects viewports at or above the md breakpoint; below it the
stack is already a column, where flexWrap is inert.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
This commit is contained in:
gorkem-bwl committed 2026-08-28 10:36:50 -04:00
1 parent 80073af80a
commit 96b2266a9d
1 file changed
+15 -3
@@ -8,6 +8,9 @@ import { useTheme } from "@mui/material";
import useMediaQuery from "@mui/material/useMediaQuery";
import type { CheckSnapshot } from "@/Types/Check";
// Width each gauge card aims for, and the basis the wrapping row breaks on.
const GAUGE_MAX_WIDTH = 260;
export const InfraDetailsGauges = ({
snapshot,
}: {
@@ -26,10 +29,19 @@ export const InfraDetailsGauges = ({
direction={isSmall ? "column" : "row"}
spacing={theme.spacing(8)}
alignItems={"stretch"}
flexWrap={"wrap"}
useFlexGap
sx={{
// The gauge cards are flex: 1 1 0, so a wrapping row would never
// overflow and they would squash to min-content instead of breaking
// the line. Give each one its own width as the basis so the row wraps
// at the intended card size.
"& > *": { flexBasis: GAUGE_MAX_WIDTH },
}}
>
<DetailGauge
title={t("pages.infrastructure.gauges.memory.title")}
maxWidth={260}
maxWidth={GAUGE_MAX_WIDTH}
progress={(snapshot?.memory?.usage_percent || 0) * 100}
upperLabel={t("pages.infrastructure.gauges.memory.upperLabel")}
upperValue={prettyBytes(snapshot?.memory?.used_bytes || 0)}
@@ -38,7 +50,7 @@ export const InfraDetailsGauges = ({
/>
<DetailGauge
title={t("pages.infrastructure.gauges.cpu.title")}
maxWidth={260}
maxWidth={GAUGE_MAX_WIDTH}
progress={(snapshot?.cpu?.usage_percent || 0) * 100}
upperLabel={t("pages.infrastructure.gauges.cpu.upperLabel")}
upperValue={getFrequency(snapshot?.cpu?.current_frequency || 0)}
@@ -51,7 +63,7 @@ export const InfraDetailsGauges = ({
key={disk?.device || 0 + idx}
// title={`Disk ${idx} usage`}
title={t("pages.infrastructure.gauges.disk.title", { idx })}
maxWidth={260}
maxWidth={GAUGE_MAX_WIDTH}
progress={(disk.usage_percent || 0) * 100}
upperLabel={t("pages.infrastructure.gauges.disk.upperLabel")}
upperValue={prettyBytes(disk?.used_bytes || 0)}