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,
},