feat(observability): service discovery, health cards, alertmanager widget

Slice 3 of observability-service-registry (frontend). The Observability
page discovers Grafana from the service registry instead of env vars,
adds Grafana + Prometheus health cards, and ships an alertmanager
active_alerts dashboard widget.

- types: added GrafanaStatus + PrometheusStatus; added optional
  service_id/error to AlertmanagerStatus.
- api/client.ts + hooks/useObservability.ts: fetchGrafanaStatus,
  fetchPrometheusStatus, useGrafanaStatus, usePrometheusStatus.
- widgets/AlertmanagerAlertsWidget.tsx (new): presentational widget
  consuming the active_alerts summary shape (total/by_severity/alerts);
  exported from widgets/index.ts.
- integrations/registry.ts: alertmanager binding (active_alerts kind,
  30s refresh, optional severity_filter); registry.test.ts updated to
  6 service types incl alertmanager + a resolve test.
- components/ObservabilityPage.tsx: removed
  import.meta.env.VITE_GRAFANA_URL; derive GRAFANA_BASE_URL from the
  first enabled grafana service via useServiceInstances("grafana");
  added Grafana + Prometheus HealthCards (up/not-configured/unreachable)
  with QueryError retry blocks; machine dashboard shows a "No Grafana
  service configured" empty-state linking to /services when none is set.

npm run build (tsc -b + vite) clean; 0 lint errors; 72 frontend tests
pass. Reviewed fresh-context (read-only): no blockers.
This commit is contained in:
Developer
2026-06-24 08:23:23 +00:00
parent 14771ae990
commit 0c5698c903
10 changed files with 314 additions and 19 deletions
+23
View File
@@ -1,4 +1,5 @@
import type { ComponentType } from "react";
import { AlertmanagerAlertsWidget } from "../widgets/AlertmanagerAlertsWidget";
import { BackupsWidget } from "../widgets/BackupsWidget";
import { GrafanaLinkWidget } from "../widgets/GrafanaLinkWidget";
import { JellyfinWidget } from "../widgets/JellyfinWidget";
@@ -42,6 +43,28 @@ export interface ServiceBinding {
}
export const SERVICE_REGISTRY: Record<string, ServiceBinding> = {
alertmanager: {
serviceType: "alertmanager",
name: "Alertmanager",
description: "Alertmanager alerts and status.",
widgets: [
{
kind: "active_alerts",
name: "Active alerts",
description: "Firing alerts summary from Alertmanager.",
refreshIntervalMs: 30_000,
defaultConfig: {},
configSchema: {
type: "object",
properties: {
severity_filter: { type: "string" },
},
required: [],
},
component: AlertmanagerAlertsWidget,
},
],
},
grafana: {
serviceType: "grafana",
name: "Grafana",