Skip to content

Multi-host monitoring & New exceptions-first dashboard

Choose a tag to compare

@btouchard btouchard released this 15 Jun 23:09

Two headline themes in this release:

  • Multi-host monitoring :material-crown: (Pro) ? a single maintenant binary now runs in three modes (embedded, server, agent). Lightweight agents deployed on remote hosts discover their runtime (Docker, Swarm or Kubernetes) and push their observations to a central server over gRPC. One server, one UI, one MCP endpoint ? the whole fleet as a single source of truth.
  • The new dashboard ? a ground-up, exceptions-first redesign: a verdict banner that answers "is everything OK?" above the fold, an actionable "Needs attention" panel, and the ~70 healthy monitors collapsed into a dense grid instead of an endless wall. The work was extracted into a site-wide design system.

Community Edition is unchanged: still a single self-sufficient binary reading the local runtime directly. Multi-host is Pro-only.


Multi-host monitoring :material-crown: (Pro)

Until now maintenant was a single process reading the local Docker/Swarm/Kubernetes runtime through its SDK. v1.3.0 introduces a distributed architecture, Pro only, so you can watch an entire fleet of hosts from one central instance.

Three modes, one binary

maintenant --mode=embedded   # default ? unchanged single-process behaviour
maintenant --mode=server     # serves the UI, REST API and MCP; aggregates the fleet
maintenant --mode=agent --server=grpcs://<server>:<port> --enrollment-token=mnt_enr_xxx
  • server keeps the entire user surface ? embedded Vue UI, REST API, MCP server ? and becomes the single aggregation point for every host.
  • agent is silent: it collects and pushes, serves no interface, and accepts no inbound commands (push-only by design). To add an HTTP or certificate monitor you do it from the server UI, which runs it itself or delegates to the relevant agent via its config.
  • One agent = one runtime. The agent auto-detects Docker, Swarm or Kubernetes by priority (in-cluster/kubeconfig ? Kubernetes, otherwise Docker); force it with --runtime=docker|kubernetes or MAINTENANT_RUNTIME.

Secure enrollment

From the server UI, Agents ? Generate enrollment token issues a single-use token (mnt_enr_<random>, 24h TTL) together with ready-to-run install commands (multiple templates for different install methods).

On first boot the agent generates an Ed25519 key pair, persists it to /var/lib/maintenant/identity.json (0600), and registers with the token. On every reconnect it proves possession of its private key by signing a server nonce ? no token reuse, no shared secret on the wire.

  • Transport is gRPC over standard TLS (not mTLS ? no internal PKI to operate). A self-signed dev certificate is used as a fallback; for dev/test you can skip verification with --grpc-insecure-skip-tls-verify.
  • A crashed agent that already has an identity does not re-enroll ? it re-authenticates with its existing key and resumes the stream.
  • Reconnects use exponential backoff (1s base, 60s max).

Real-time collection

A registered agent streams its runtime events as they happen ? container start/stop, endpoint probe results, heartbeat pings, resource metrics, detected TLS certificates ? to the server, which dispatches them to the existing services (container, endpoint, heartbeat, resource, certificate), persists to SQLite, and broadcasts over the SSE bus. The server UI sees remote hosts in near real time, exactly as if they were local. Full inventory is re-seeded on every (re)connect, so the dashboard reflects reality even after a restart.

Every monitored entity is attributed to the host (agent) that reported it, so you can filter the dashboard by host and see per-host samples. The MCP server stays centralized ? one endpoint sees the whole fleet as a single unified source.

Management & revocation

The Agents page lists every enrolled host with its hostname/label, detected runtime, binary version, last activity, persisted status (active/revoked) and a computed connection state (connected/disconnected, derived from the live stream and last_seen_at ? never persisted). Revoking an agent in one click flips it to revoked, closes its live stream within seconds, and refuses any further authentication. Deleting an agent now also resolves its lingering disconnect alert, so a removed host never leaves a ghost behind.

Edition gating

Multi-host is Pro. In Community only embedded is available and the experience is byte-for-byte identical (single binary, local runtime, no gRPC). The server/agent modes refuse to start without a valid Pro license, and the gRPC endpoints reject every connection with PRO_REQUIRED. SQLite remains the only store ? the server is a central point by design.

Migration

The data model moved to UUID identifiers for primary/foreign keys, applied automatically and in-place at startup (guarded so it runs exactly once). Multi-host adds the 22_agents migration plus per-host attribution tables (resource_snapshots, cert_monitors, enrollment_tokens). Existing single-host data is preserved. Agent ingest can be rate-limited with MAINTENANT_AGENT_RATE_LIMIT_PER_SECOND.


The new dashboard

The dashboard was rebuilt around a single principle: exceptions first. The abnormal is big and at the top; the healthy majority is dense and quiet. Where ~70 healthy monitors used to occupy as much space as the handful of incidents, the question "is everything OK?" now has an answer above the fold.

Verdict banner

A banner at the very top answers "is everything OK?" in three seconds: either "X incidents need your attention" with a one-line breakdown (e.g. "1 agent disconnected � 7 critical updates � 1 security risk"), or "All systems operational". The Incident / Warning / OK counters ? previously tiny in the header ? are promoted, large and readable, into the banner (with reduced-motion and keyboard support).

"Needs attention" ? actionable

A pinned panel under the verdict lists everything that needs action, sorted by severity: incidents first, then warnings. Each item is a single dense row ? severity dot (colour and shape), name, type badge, short description, relative age ("27 min"), chevron ? and clicking it opens the entity's detail (slide-over for containers/heartbeats/certificates, route otherwise). When nothing is wrong, a positive empty state rather than a blank panel.

Dense, collapsible monitor grid

The monitors are no longer a wall of tall rows. They render as a dense grid of tiles coloured by severity, grouped (by type by default), each group collapsible with a severity count ("8 OK"). Fully-healthy groups are collapsed by default ? the "wall of green" stays out of the way. A toggle switches to a sortable list; grouping can be switched to by severity. Your grid/list choice and density are persisted. The 90-day history and detailed sparkline left the scan view and moved to the detail (hover tooltip / click panel); large sets are virtualized for smooth scrolling. A compact KPI strip carries the at-a-glance overview (uptime, response time, host CPU, certificates, security posture).

Trustworthy numbers

The redesign also unified how those numbers are computed, so the dashboard can never contradict itself or the Alerts page:

  • One source of truth. The verdict banner, the header counters and the "Needs attention" panel all derive from a single unified model ? they can't disagree.
  • No invented severities. An item's severity comes straight from the alert engine (critical ? incident, warning ? warning) or, for a monitor without an alert, from its own status. The dashboard never overrides it, so it always matches the Alerts page.
  • Critical updates are rolled up into one "N critical updates" entry that links to the Updates page, instead of scattering individual rows.
  • Engine alerts are visible. Security findings and other engine alerts now appear in "Needs attention" ? nothing is counted that you can't see and click.

Site-wide design system

The dashboard was the pilot; the real deliverable is consistency. The work was extracted into a reusable design system ? mnt-* design tokens (dual light + dark, no hardcoded colours), shared ui/ components, and a four-severity visual taxonomy (ok / warning / incident / unknown, plus a neutral paused). The underlying data model is unchanged ? this is a presentation layer.


Fixes & internals

  • Incident counts reconciled ? a down monitor now correctly counts toward the header counters (a latent bug dropped them entirely), and disconnected agents are reflected consistently across the dashboard and the Alerts page.
  • Update alerts are per-container ? update alerts now carry the container's entity id on both detection and resolution. Previously every update shared one blank dedup key, so a higher-severity update could overwrite a shared record and produce mismatched alerts (one container's message on another's entity). Fixed end to end.
  • Dedup-key collision guard ? the alert engine now logs a loud error if two distinct entities ever land on the same dedup key (a missing entity id in some source), and an escalation refreshes the whole record ? so this class of corruption can never happen silently again.
  • Ghost agent cleanup ? a disconnect alert for an agent that was deleted while already offline is now resolved (on delete/revoke even with no live session, and via a startup reconciliation sweep), so removed hosts don't linger in attention lists.

Upgrade notes

docker compose pull && docker compose up -d --force-recreate
  1. Database migrations run automatically on startup, including the one-time conversion to UUID identifiers and the 22_agents migration. Existing data is preserved; back up your SQLite file before upgrading a production instance, as the UUID conversion is in-place.
  2. Community Edition: nothing changes ? single binary, local runtime, no new configuration. The new dashboard applies immediately.
  3. Multi-host (Pro): run one instance with --mode=server (it keeps serving the UI/API/MCP), then enroll hosts from Agents ? Generate enrollment token and run the printed command on each remote host. Route the server's gRPC endpoint over TLS. Optional: MAINTENANT_AGENT_RATE_LIMIT_PER_SECOND to cap agent ingest.
  4. Lingering alerts (a removed agent, or a pre-1.3.0 blank-id update alert) are pruned automatically on the first restart after upgrade.

Documentation