From dad9afe8380ab96cf037fdfcde7921c450f6ec13 Mon Sep 17 00:00:00 2001 From: Maximilian Kaske <56969857+mxkaske@users.noreply.github.com> Date: Fri, 16 Oct 2026 21:46:46 +0200 Subject: [PATCH] feat(dashboard): warn when page components link inactive monitors (#2923) * feat(dashboard): warn when page components link inactive monitors Inactive monitors don't collect data, so their status page bars stay empty. The components form now warns about it and shows an Active/Inactive tooltip per row; the docs explain the behavior and point to static components or Reports only. * chore: point ship command at the existing format script * fix: make monitor status dot keyboard accessible and clarify inactive monitor docs --- .claude/commands/ship.md | 2 +- .../(dashboard)/monitors/[id]/nav-actions.tsx | 33 +----------- .../components/common/monitor-status-dot.tsx | 51 +++++++++++++++++++ .../forms/components/form-components.tsx | 38 +++++++++----- .../pages/docs/reference/page-components.mdx | 11 ++-- 5 files changed, 87 insertions(+), 48 deletions(-) create mode 100644 apps/dashboard/src/components/common/monitor-status-dot.tsx diff --git a/.claude/commands/ship.md b/.claude/commands/ship.md index e063ec98e..5dee41b02 100644 --- a/.claude/commands/ship.md +++ b/.claude/commands/ship.md @@ -20,7 +20,7 @@ $ARGUMENTS - optional branch name - Confirm there are uncommitted changes (from status above). If none, stop. - Confirm current branch. If not on `main`, ask user before proceeding. - Warn if any .env or credential files would be staged. -- Run `pnpm format:fix` in the root directory to fix formatting issues +- Run `pnpm format` in the root directory to fix formatting issues ## 2. Create Branch diff --git a/apps/dashboard/src/app/(dashboard)/monitors/[id]/nav-actions.tsx b/apps/dashboard/src/app/(dashboard)/monitors/[id]/nav-actions.tsx index 84ac57a45..cfcdca253 100644 --- a/apps/dashboard/src/app/(dashboard)/monitors/[id]/nav-actions.tsx +++ b/apps/dashboard/src/app/(dashboard)/monitors/[id]/nav-actions.tsx @@ -17,6 +17,7 @@ import { useParams, usePathname, useRouter } from "next/navigation"; import { useState } from "react"; import { toast } from "sonner"; +import { MonitorStatusDot } from "@/components/common/monitor-status-dot"; import { DataTableSheetTest } from "@/components/data-table/response-logs/data-table-sheet-test"; import { QuickActions } from "@/components/dropdowns/quick-actions"; import { NavFeedback } from "@/components/nav/nav-feedback"; @@ -212,37 +213,7 @@ export function NavActions() {
- - - {!monitor.active ? ( - - - - ) : monitor.status === "active" ? ( - - - - - ) : monitor.status === "error" ? ( - - - - ) : ( - - - - )} - - - {!monitor.active - ? "Inactive" - : monitor.status === "active" - ? "Normal" - : monitor.status === "error" - ? "Failing" - : "Degraded"} - - +
diff --git a/apps/dashboard/src/components/common/monitor-status-dot.tsx b/apps/dashboard/src/components/common/monitor-status-dot.tsx new file mode 100644 index 000000000..9f8c03099 --- /dev/null +++ b/apps/dashboard/src/components/common/monitor-status-dot.tsx @@ -0,0 +1,51 @@ +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@openstatus/ui/components/ui/tooltip"; +import { cn } from "@openstatus/ui/lib/utils"; + +export function MonitorStatusDot({ + monitor, + label, + className, +}: { + monitor: { active: boolean | null; status: string }; + /** Overrides the default tooltip text */ + label?: React.ReactNode; + className?: string; +}) { + const text = + label ?? + (!monitor.active + ? "Inactive" + : monitor.status === "active" + ? "Normal" + : monitor.status === "error" + ? "Failing" + : "Degraded"); + + return ( + + + {/* biome-ignore lint/a11y/noNoninteractiveTabindex: focusable so keyboard users can open the tooltip */} + + {!monitor.active ? ( + + ) : monitor.status === "active" ? ( + <> + + + + ) : monitor.status === "error" ? ( + + ) : ( + + )} + {text} + + + {text} + + ); +} diff --git a/apps/dashboard/src/components/forms/components/form-components.tsx b/apps/dashboard/src/components/forms/components/form-components.tsx index 50a09d4f4..1246f95ff 100644 --- a/apps/dashboard/src/components/forms/components/form-components.tsx +++ b/apps/dashboard/src/components/forms/components/form-components.tsx @@ -12,6 +12,7 @@ import { Unlinked, Plug, Delete, + Info, } from "@openstatus/icons"; import { AlertDialog, @@ -66,6 +67,8 @@ import { toast } from "sonner"; import { z } from "zod"; import { Link } from "@/components/common/link"; +import { MonitorStatusDot } from "@/components/common/monitor-status-dot"; +import { Note } from "@/components/common/note"; import { EmptyStateContainer, EmptyStateTitle, @@ -81,7 +84,6 @@ import { FormCardSeparator, FormCardTitle, } from "@/components/forms/form-card"; -import { STATUS } from "@/components/nav/nav-monitors"; import { Sortable, SortableContent, @@ -289,6 +291,14 @@ export function FormComponents({ const watchComponents = form.watch("components"); const watchGroups = form.watch("groups"); const [openUpgradeDialog, setOpenUpgradeDialog] = useState(false); + const linkedMonitorIds = new Set( + [...watchComponents, ...watchGroups.flatMap((g) => g.components)].map( + (c) => c.monitorId, + ), + ); + const hasInactiveMonitors = monitors.some( + (m) => !m.active && linkedMonitorIds.has(m.id), + ); const [data, setData] = useState<(PageComponent | ComponentGroup)[]>( getSortedItems( allPageComponents, @@ -629,7 +639,15 @@ export function FormComponents({ /> - + + {hasInactiveMonitors ? ( + + + You have inactive monitors linked. They don't collect data, so + activate them, use static components, or set the status source + to Reports only. + + ) : null} ) : null} {component.monitor && component.type === "monitor" ? ( -
+ + + ) : null}
diff --git a/apps/web/src/content/pages/docs/reference/page-components.mdx b/apps/web/src/content/pages/docs/reference/page-components.mdx index 9ad99ecb3..fedf2cd52 100644 --- a/apps/web/src/content/pages/docs/reference/page-components.mdx +++ b/apps/web/src/content/pages/docs/reference/page-components.mdx @@ -23,9 +23,9 @@ Page components come in two distinct types, each serving different purposes on y **Type:** `monitor` -Monitor components are linked to an active monitor in your workspace. They automatically inherit the monitor's status and display real-time health information. +Monitor components are linked to a monitor in your workspace. While the monitor is active, they automatically inherit its status and display real-time health information. -**Characteristics:** +**Characteristics (while the linked monitor is active):** - Display live monitor status (up, degraded, down). - Show active incidents from the linked monitor. - Include historical uptime data. @@ -38,6 +38,10 @@ Monitor components are linked to an active monitor in your workspace. They autom - Tracking critical service dependencies. - Monitoring infrastructure components. + + #### Configuring monitor components When you create a monitor component, you link it to an existing monitor in your workspace. This connection provides several benefits: @@ -63,7 +67,7 @@ Monitor components calculate uptime percentages based on: **Monitor selection:** When adding a monitor component, the dashboard shows you available monitors with indicators for: - **Public/Private status**: Whether the monitor is already public. -- **Active status**: Only active monitors can be linked. +- **Active status**: Inactive monitors can be linked, but don't share data until they're activated again. The dashboard warns you when a linked monitor is inactive. - **Already linked**: Monitors already used on this status page are unavailable.