Across en + 15 locales:
- pages.<X>.fallback.title / .description for every page that has an
EmptyState (uptime, infrastructure, pageSpeed, notifications,
maintenance, statusPages, incidents, logs, logs.queueFallback,
checks, account.team).
- pages.<X>.header.title / .description for every page that uses the
new PageHeader (same list, plus settings).
- pages.logs.diagnostics.sections.runtimeStats / .memoryCpu titles
and descriptions for the diagnostics section explainers.
- components.pageHeader.links.support / docs / changelog tooltip
labels.
en.json also gains pages.logs.jobQueueExplainer and
.failedJobsExplainer (English only — non-English locales fall back
to English until a translator sweep).
- MuiOutlinedInput hover override: was inheriting MUI's default
white outline in dark mode (too bright). Now uses an alpha-23%
white in dark and text.primary in light.
- TextInput: -webkit-autofill now repaints the input bg with
background.paper (instead of Chrome's harsh blue/yellow autofill
highlight), text in text.primary, with the standard 5000s-transition
trick to suppress the brief flash. Works in both modes.
TableJobs:
- Cut from 11 to 7 columns, dropping active / runCount / failCount /
lastFinishedAt (covered by Failed jobs table or by lastRun + lastRunTook).
- All cells get whiteSpace:nowrap + textOverflow:ellipsis + maxWidth so
long timestamps and IDs no longer wrap and inflate row height; full
value visible via title tooltip.
- Running-row tint: was theme.palette.success.light (too saturated in
light, drowns white text in dark). Now #ECF7F2 in light, an 18%-alpha
green in dark — readable in both modes.
TabQueue:
- One-line explainer above the Job queue heading, with the words
"Highlighted rows" wrapped in the same green tint so the meaning
is immediately associated.
- One-line explainer above Failed jobs heading too.
- Stats and StatGauges use CSS grid (5 + 4 columns) so cards in each
row are equal width; widths overridden against the legacy 25%-flex
GradientBox so they fill their grid cell.
- DetailGauge gains a maxWidth prop (default 225) so consumers in
wide grids can opt out of the cap.
- Section explainers above each row: "Runtime stats" + description,
"Memory & CPU" + description (i18n driven, propagated to all 16
locales via pages.logs.diagnostics.sections).
- Both grids render immediately with placeholder "—" values, so the
initial paint shows the layout and values fill in as data loads
(eliminates the layout shake when switching to the tab).
- New PageHeader component (variant D — grouped icon-pill toolbar):
big title + muted description on the left, Support / Docs /
Changelog as a 3-icon segmented pill on the right with tooltips.
External links open in a new tab.
- BasePage / BasePageWithStates / MonitorBasePageWithStates accept
a headerKey prop; when set, render PageHeader pulling
pages.<headerKey>.header.title and .description from i18n.
When unset, falls back to the existing breadcrumb (no behavior
change for not-yet-migrated pages).
- All top-level pages (uptime, infrastructure, pageSpeed,
notifications, maintenance, statusPages, incidents, logs, checks,
account, settings) now pass their headerKey.
- Sidebar: removed the bottom menu (Support / Discussions / Docs /
Changelog) since those links now live in the page header.
- New EmptyState + EmptyStateIllustration components: stacked-cards SVG
with grid background, theme-aware light/dark palette.
- Variants: default (centered with title + description), compact
(horizontal, in-section), fullscreen (vertically centers in viewport).
- Wired into Checks, Incidents, Logs (Logs+Queue tabs), Notifications,
Maintenance, Status pages, and Team tab — each with i18n title +
description and a fullscreen layout when truly empty.
- Table.EmptyView now wraps the EmptyState in a divider-bordered card
so short messages no longer look orphaned.
- Incidents Option-A short-circuit: when summary.total === 0, replace
the whole page chrome with EmptyState; otherwise keep filters + cards.
- Fallback.tsx (BaseFallback / EmptyFallback / EmptyMonitorFallback /
ErrorFallback) refactored to delegate to EmptyState.
Replaces the cards-with-halo SkeletonCard fallback (which produced a
washed-out, hard-to-read placeholder) with a single canonical empty
state used everywhere in the app.
What's new:
- EmptyStateIllustration.tsx — custom inline SVG: two stacked monitor
cards (front blue, back gray, both with hairline strokes and a soft
fade toward the bottom), set against a faint grid of solid + dashed
background lines. Uses only lightened palette tokens (#7DA8E8 blue,
#B6C2CF gray, #CBD5E1 grid).
- EmptyState.tsx — the layout: illustration + title + description +
optional CTA + optional secondary link + optional inline alert slot.
Supports a compact horizontal variant for in-panel use.
Migrations:
- Fallback.tsx: BaseFallback / EmptyFallback / EmptyMonitorFallback /
ErrorFallback now delegate to EmptyState. Public APIs unchanged so
every existing consumer (BasePageWithStates, MonitorBasePageWithStates,
Pages/Uptime, Pages/Notifications, Pages/Infrastructure, etc.) keeps
working without edits.
- BasePage.tsx: PageSpeedKeyPriorityFallback rebuilt on top of EmptyState
with the warning slot. Fixes the previous reports of "white cloudy
area" + half-faded text — message and link now render in consistent
dark text with green Settings link.
No prop changes for any page-level consumer. The illustration is a
single SVG with no external assets.
The Open site action in monitor row menus called `window.open(monitor.url)`
unconditionally, which produced unresolvable navigation for monitors
targeting internal services:
- Hardware monitors always point at a Capture agent (e.g.
http://capture:59232/api/v1/metrics) that the user's browser can't
reach.
- Port, ping, docker, game, grpc, and websocket monitors store hostnames
or non-HTTP endpoints, which browsers can't load anyway.
This change hides the action where it can't possibly work:
- Removed entirely from the Infrastructure (hardware) monitors table.
- In the Uptime monitors table, only shown for http and pagespeed
monitor types whose URL starts with http:// or https://.
A more comprehensive fix that adds a separate user-facing siteUrl
field on the Monitor schema is left for a follow-up.
Closes#3434
The OpenAPI spec only listed `limit` for GET /monitors/team/with-checks,
but the route's Zod schema (getMonitorsWithChecksQueryValidation) also
accepts page, rowsPerPage, filter, field, order, type, and explain.
Users hitting the endpoint via Swagger UI would either get rejected
with "page is not allowed" (when guessing param names) or miss
pagination entirely.
Adds the missing parameters with constraints mirrored from the
validation schema. Also bumps `limit` max from 50 to 100 to match
the schema (was 100 in code, 50 in spec).
Closes#3354
The client version shown on the Settings page was hardcoded as a
literal in vite.config.ts, while client/package.json sat at the
placeholder "0.0.0". Bumping the displayed version required editing
two unrelated places, and stale Docker client images would keep
showing the old number after upgrades.
This change makes client/package.json the single source of truth
and reads it at build time. Bumps client/package.json from 0.0.0
to 3.6.1 to preserve current behaviour.
Closes#3271
Mongoose 8 logs a reserved-key warning at boot because the Check schema
defines an `errors` field, which collides with Mongoose's internal
document `errors` accessor. The field is intentional and safe to keep,
so opt out of the warning via `suppressReservedKeysWarning: true`.
Closes#1539
Vite >=4.3 rejects requests with a Host header not in the allowlist,
which broke status-page submissions and dev-server access for users
serving Checkmate behind a reverse proxy after upgrading.
Adds VITE_ALLOWED_HOSTS env var (comma-separated list). When unset,
falls back to allowing all hosts so existing self-hosted deployments
keep working without changes.
Closes#3397Closes#2951
Adds four fully-designed visual themes for the public status page
(refined | modern | bold | editorial) with a light/dark mode selector,
admin preview in a mock browser frame, and a shared theme provider
that keeps the component tree unified while letting each theme own
its visual identity.
## Selectable themes
- Refined — clean, flat cards, soft shadows, colored status dot.
- Modern — flat surfaces with a pulsing status dot and staggered
fade-in cards.
- Bold — conic-gradient logo, 34px hero title with a check disc,
subtle dual-radial glow on dark mode, accent-green hover border.
- Editorial — serif headings, narrow newspaper column, 'Service
Report' eyebrow + title, flat rectangular badges and dateline.
## Architecture
- Single `StatusPageThemeProvider` hosts CSS custom-property tokens
per theme and resolves light/dark based on admin-forced mode,
falling back to `prefers-color-scheme` with a matchMedia listener.
- Each theme owns only its `*StatusPage.tsx` + scoped CSS file.
The heatmap, histogram, and infrastructure renderers are shared
across all four themes (parameterized by a `classPrefix`) to avoid
the 12-file duplication the initial draft had.
- Renderer lookup table in `Status/index.tsx` replaces a 4-way
ternary chain; unknown themes fall through to the legacy renderer.
- `resolveOverallStatus`, `statusBadgeKey`, `monoFirstChar`, and
`monitorBadgeTone` are extracted to `themes/shared/overallStatus.tsx`.
## Admin UX
- `ThemePicker` uses the project's `RadioWithDescription` and
`Select` components — one radio card per theme plus an Auto/
Light-only/Dark-only mode dropdown.
- `BrowserFrame` wraps the preview in the admin panel with a mock
chrome (traffic lights + URL bar) that adopts the status page's
theme tokens. Edge-to-edge fill with no gray bleed.
- Public route renders the theme directly on the viewport with the
document body painted to the theme background, reverted on unmount.
## Performance & polish
- Histogram's derived stats (padded array, max, avg, peak) are
memoized on `checks`; `max` is reused for `peak` instead of
computing twice.
- Editorial's dateline is wrapped in `useMemo` so the 10s poll
doesn't rerender "April 23, 2026" as a changing string.
- Provider's `setSystemMode` reconciliation uses the updater form
to avoid an extra render when the value is unchanged.
- `monoFirstChar` no longer uses a non-null assertion.
## Form plumbing
- `useStatusPageForm` defaults include `theme` + `themeMode`.
- `Validation/statusPage.ts` accepts both as optional zod enums.
- `Pages/StatusPage/Create/index.tsx` appends both to FormData on
create and update.
## CLAUDE.md
- Dev port note: local client runs on 10001 (5173 is used by a
different local project); server `CLIENT_HOST` is set to match.
## Changes
- Add `theme` (refined | modern | bold | editorial, default "refined") and
`themeMode` (auto | light | dark, default "auto") fields to the StatusPage
Mongoose model and the shared `StatusPage` type.
- Extend the create/update Zod validator to accept both fields optionally.
- Map both fields in the repository's `toEntity` so GET responses include them.
- Document both fields in the create and update endpoints in `openapi.json`.
- Add `STATUS_PAGE_THEMES_ENABLED` env var (default `true`) wired through
`SettingsService` for ops-level rollback. When disabled, the controller
strips theme fields on write and forces defaults on read.
## Notes
- No migration needed; Mongoose applies defaults for legacy documents.
- Controller short-circuits to zero-cost when the feature flag is on.
- `DEFAULT_STATUS_PAGE_THEME` / `DEFAULT_STATUS_PAGE_THEME_MODE` constants
exported so the frontend PR can consume the same values.
## Changes
- Removed the "Register here" link from the login page — it misled users into thinking public registration was open.
- Added redirect from /login to /register when no super admin exists (initial setup).
- Added an info alert on the register page clarifying the page is only for initial setup; additional users are invited from team settings. Hidden for the invite-token flow.
- Extracted shared useSuperAdminRedirect hook to dedupe identical fetch/redirect logic across Login and Register.
## Benefits
- Clearer UX: login page no longer suggests self-registration is available post-setup.
- Correct routing on fresh installs: users land on the registration page instead of a login page they can't use.