diff --git a/client/src/Pages/Dashboard/Components/CardPicker.tsx b/client/src/Pages/Dashboard/Components/CardPicker.tsx index ac5508135..e60653cac 100644 --- a/client/src/Pages/Dashboard/Components/CardPicker.tsx +++ b/client/src/Pages/Dashboard/Components/CardPicker.tsx @@ -79,7 +79,7 @@ export const CardPicker = ({ > {t(`components.sidebar.menu.${group}`, { defaultValue: group })} diff --git a/client/src/Pages/Dashboard/Components/CardPrimitives.tsx b/client/src/Pages/Dashboard/Components/CardPrimitives.tsx index 952cd875c..f12952a7c 100644 --- a/client/src/Pages/Dashboard/Components/CardPrimitives.tsx +++ b/client/src/Pages/Dashboard/Components/CardPrimitives.tsx @@ -97,7 +97,7 @@ export const CardRowLabel = ({ {secondary && ( @@ -132,7 +132,7 @@ export const CardFigure = ({ {caption && ( {caption} diff --git a/client/src/Pages/Dashboard/Components/Cards/AdminCards.tsx b/client/src/Pages/Dashboard/Components/Cards/AdminCards.tsx index 632a0b612..da00bad05 100644 --- a/client/src/Pages/Dashboard/Components/Cards/AdminCards.tsx +++ b/client/src/Pages/Dashboard/Components/Cards/AdminCards.tsx @@ -37,7 +37,7 @@ const StatLine = ({ gap={theme.spacing(LAYOUT.SM)} > {label} @@ -96,7 +96,7 @@ export const ChecksOnScheduleCard = () => { secondary={job.failReason} /> @@ -159,7 +159,7 @@ export const RecentErrorsCard = () => { action={ errors > 0 ? ( {t("pages.dashboard.cards.recentErrors.count", { count: errors })} @@ -183,7 +183,7 @@ export const RecentErrorsCard = () => { secondary={[entry.service, entry.method].filter(Boolean).join(" ยท ")} /> { gap={theme.spacing(LAYOUT.XS)} > {label} {/* A down server reports nothing โ€” show a dash, never 0%. */} diff --git a/client/src/Pages/Dashboard/Components/Cards/CheckmateServerCard.tsx b/client/src/Pages/Dashboard/Components/Cards/CheckmateServerCard.tsx index b7716f7c9..cdc7b8b2d 100644 --- a/client/src/Pages/Dashboard/Components/Cards/CheckmateServerCard.tsx +++ b/client/src/Pages/Dashboard/Components/Cards/CheckmateServerCard.tsx @@ -40,7 +40,7 @@ const MetricLine = ({ gap={theme.spacing(LAYOUT.SM)} > {label} diff --git a/client/src/Pages/Dashboard/Components/Cards/CurrentlyDownCard.tsx b/client/src/Pages/Dashboard/Components/Cards/CurrentlyDownCard.tsx index 21dca5153..5075e1c2e 100644 --- a/client/src/Pages/Dashboard/Components/Cards/CurrentlyDownCard.tsx +++ b/client/src/Pages/Dashboard/Components/Cards/CurrentlyDownCard.tsx @@ -92,7 +92,7 @@ export const CurrentlyDownCard = () => { action={ problems.length > 0 ? ( {t("pages.dashboard.cards.currentlyDown.count", { @@ -120,7 +120,7 @@ export const CurrentlyDownCard = () => { secondary={monitor.url} /> @@ -128,7 +128,7 @@ export const CurrentlyDownCard = () => { {since && ( { * the link and activating it navigates away. */} { size="md" /> {t(`pages.common.monitors.status.${key}`)} diff --git a/client/src/Pages/Dashboard/Components/Cards/MonitorsByGroupCard.tsx b/client/src/Pages/Dashboard/Components/Cards/MonitorsByGroupCard.tsx index 841b90c47..215771cf3 100644 --- a/client/src/Pages/Dashboard/Components/Cards/MonitorsByGroupCard.tsx +++ b/client/src/Pages/Dashboard/Components/Cards/MonitorsByGroupCard.tsx @@ -85,7 +85,7 @@ export const MonitorsByGroupCard = () => { > {bucket.down > 0 && ( {t("pages.dashboard.cards.monitorsByType.down", { diff --git a/client/src/Pages/Dashboard/Components/Cards/MonitorsByTypeCard.tsx b/client/src/Pages/Dashboard/Components/Cards/MonitorsByTypeCard.tsx index 1893edcb0..a36c87d0b 100644 --- a/client/src/Pages/Dashboard/Components/Cards/MonitorsByTypeCard.tsx +++ b/client/src/Pages/Dashboard/Components/Cards/MonitorsByTypeCard.tsx @@ -85,7 +85,7 @@ export const MonitorsByTypeCard = () => { > {bucket.down > 0 && ( {t("pages.dashboard.cards.monitorsByType.down", { diff --git a/client/src/Pages/Dashboard/Components/Cards/RankingCards.tsx b/client/src/Pages/Dashboard/Components/Cards/RankingCards.tsx index e76171287..d405c5392 100644 --- a/client/src/Pages/Dashboard/Components/Cards/RankingCards.tsx +++ b/client/src/Pages/Dashboard/Components/Cards/RankingCards.tsx @@ -33,7 +33,7 @@ const RankingCount = ({ shown, total }: { shown: number; total: number }) => { const { t } = useTranslation(); return ( {t("pages.dashboard.cards.ranking.topOf", { shown, total })} diff --git a/client/src/Pages/Dashboard/Components/Cards/RecentIncidentsCard.tsx b/client/src/Pages/Dashboard/Components/Cards/RecentIncidentsCard.tsx index c1ebf1b2a..448e02bfd 100644 --- a/client/src/Pages/Dashboard/Components/Cards/RecentIncidentsCard.tsx +++ b/client/src/Pages/Dashboard/Components/Cards/RecentIncidentsCard.tsx @@ -44,7 +44,7 @@ export const RecentIncidentsCard = () => { action={ data && data.totalActive > 0 ? ( {t("pages.dashboard.cards.recentIncidents.active", { @@ -78,7 +78,7 @@ export const RecentIncidentsCard = () => { secondary={formatTimestamp(incident.startTime)} /> @@ -88,7 +88,7 @@ export const RecentIncidentsCard = () => { {incident.resolutionType === "manual" && ( diff --git a/client/src/Pages/Dashboard/Components/Cards/SummaryCards.tsx b/client/src/Pages/Dashboard/Components/Cards/SummaryCards.tsx index c3e763ea2..8d5b49fad 100644 --- a/client/src/Pages/Dashboard/Components/Cards/SummaryCards.tsx +++ b/client/src/Pages/Dashboard/Components/Cards/SummaryCards.tsx @@ -27,7 +27,7 @@ const StatLine = ({ label, value }: { label: string; value: string }) => { gap={theme.spacing(LAYOUT.SM)} > {label} @@ -218,7 +218,7 @@ export const StatusPagesCard = () => { })} /> diff --git a/client/src/Pages/Dashboard/Components/DashboardCard.tsx b/client/src/Pages/Dashboard/Components/DashboardCard.tsx index 6b9af8455..9a1ad6407 100644 --- a/client/src/Pages/Dashboard/Components/DashboardCard.tsx +++ b/client/src/Pages/Dashboard/Components/DashboardCard.tsx @@ -52,9 +52,15 @@ export const DashboardCard = ({ flexDirection: "column", height: "100%", overflow: "hidden", - // The header controls stay invisible until the card is hovered or - // something inside it takes focus, so a settled dashboard is quiet. - "&:hover .dashboard-card-slot, &:focus-within .dashboard-card-slot": { + // The header controls stay invisible until the card is hovered, so a + // settled dashboard is quiet. + // + // `:has(:focus-visible)` rather than `:focus-within`: a mouse click + // leaves the button focused, and :focus-within would then pin that + // card's controls open long after the pointer had left. focus-visible + // only matches keyboard focus, which is the case that actually needs + // the controls revealed. + "&:hover .dashboard-card-slot, &:has(:focus-visible) .dashboard-card-slot": { opacity: 1, pointerEvents: "auto", }, @@ -158,7 +164,7 @@ export const CardMessage = ({ text, action }: { text: string; action?: ReactNode gap={theme.spacing(LAYOUT.SM)} > {text} diff --git a/client/src/Pages/Dashboard/cards.ts b/client/src/Pages/Dashboard/cards.ts index 33177b9b5..f16a61407 100644 --- a/client/src/Pages/Dashboard/cards.ts +++ b/client/src/Pages/Dashboard/cards.ts @@ -61,12 +61,14 @@ export interface CardDefinition { // The cards a fresh dashboard starts with. Chosen so a brand new install still // shows something real: Checkmate server works with zero monitors. +// Ordered so the declared widths pair into full rows: a card left alone in a +// row is stretched to fill it, so 8- and 4-wide cards are kept adjacent. export const DEFAULT_CARD_IDS: CardId[] = [ "monitorStatus", "currentlyDown", "failedChecks", - "recentIncidents", "monitorsByType", + "recentIncidents", "checkmateServer", ]; diff --git a/client/src/Pages/Dashboard/registry.ts b/client/src/Pages/Dashboard/registry.ts index 893f6b0be..b8a87ea27 100644 --- a/client/src/Pages/Dashboard/registry.ts +++ b/client/src/Pages/Dashboard/registry.ts @@ -24,7 +24,10 @@ import type { CardDefinition, CardId } from "./cards"; // Declared width is the minimum number of columns a card needs. The grid may // stretch the last card of a short row; nothing here is a user setting. // -// Order is the order the picker lists them in, grouped by sidebar section. +// Order is the render order, and the order the picker lists them in. Cards are +// sequenced so their declared widths pair into full rows: a card left alone in +// a row gets stretched to fill it, so an 8-wide card is kept next to a 4-wide +// one rather than being widened back to 12. export const CARD_REGISTRY: CardDefinition[] = [ { id: "monitorStatus", @@ -37,9 +40,18 @@ export const CARD_REGISTRY: CardDefinition[] = [ id: "currentlyDown", key: "currentlyDown", group: "uptime", - width: 12, + // Six columns per row โ€” the densest card โ€” but it does not need the + // full width, and 8 leaves room for a 4-wide card beside it. + width: 8, component: CurrentlyDownCard, }, + { + id: "failedChecks", + key: "failedChecks", + group: "checks", + width: 4, + component: FailedChecksCard, + }, { id: "slowestMonitors", key: "slowestMonitors", @@ -79,7 +91,7 @@ export const CARD_REGISTRY: CardDefinition[] = [ id: "recentIncidents", key: "recentIncidents", group: "incidents", - width: 8, + width: 4, component: RecentIncidentsCard, }, { @@ -110,13 +122,6 @@ export const CARD_REGISTRY: CardDefinition[] = [ width: 4, component: StatusPagesCard, }, - { - id: "failedChecks", - key: "failedChecks", - group: "checks", - width: 4, - component: FailedChecksCard, - }, { id: "checkmateServer", key: "checkmateServer", @@ -137,7 +142,7 @@ export const CARD_REGISTRY: CardDefinition[] = [ id: "recentErrors", key: "recentErrors", group: "logs", - width: 6, + width: 4, adminOnly: true, component: RecentErrorsCard, },