Mahakii's picture
Swagger ui done
177c3f8
Raw
History Blame Contribute Delete
7.68 kB
"use client";
import * as React from "react";
import { api, MonitoringAlert, MonitoringHealth } from "@/lib/api";
import { useEnv } from "@/components/env-provider";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
function formatMaybeNumber(value: unknown, digits = 2): string {
const n = typeof value === "number" ? value : Number(value);
if (Number.isFinite(n)) return n.toFixed(digits);
return "—";
}
export default function MonitoringPage() {
const { connectionState, connectionText } = useEnv();
const [health, setHealth] = React.useState<MonitoringHealth | null>(null);
const [alerts, setAlerts] = React.useState<MonitoringAlert[] | null>(null);
const [metricsText, setMetricsText] = React.useState<string | null>(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState<string>("");
const refresh = React.useCallback(async () => {
setError("");
setLoading(true);
try {
const [h, a, m] = await Promise.all([
api.getMonitoringHealth(),
api.getMonitoringAlerts(),
api.getPrometheusMetrics(),
]);
setHealth(h);
setAlerts(a);
setMetricsText(m);
} catch (e) {
setError(e instanceof Error ? e.message : "Failed to fetch monitoring data.");
} finally {
setLoading(false);
}
}, []);
React.useEffect(() => {
const timeout = setTimeout(() => {
void refresh();
}, 0);
return () => clearTimeout(timeout);
}, [refresh]);
const monitoringEnabled = health !== null || alerts !== null || metricsText !== null;
return (
<div className="flex-1 space-y-6 p-8 max-w-5xl">
<div className="flex items-start justify-between gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight">Monitoring</h1>
<p className="text-muted-foreground mt-1">
Backend monitoring endpoints and Prometheus metrics.
</p>
</div>
<div className="flex items-center gap-3">
<Badge variant="outline">
{connectionState === "connected" ? "Connected" : connectionState === "checking" ? "Checking" : "Disconnected"}
</Badge>
<Badge variant="outline" className="max-w-[28rem] truncate">
{connectionText}
</Badge>
<Button variant="outline" onClick={refresh} disabled={loading}>
Refresh
</Button>
</div>
</div>
{error ? (
<div className="rounded-lg border border-border/50 bg-card/50 px-4 py-3 text-sm text-destructive">
{error}
</div>
) : null}
<Card className="border-border/50 bg-card/50">
<CardHeader>
<CardTitle className="flex items-center justify-between">
Status
<Badge variant={monitoringEnabled ? "default" : "secondary"}>
{monitoringEnabled ? "Enabled" : "Disabled"}
</Badge>
</CardTitle>
<CardDescription>
If monitoring is disabled, the backend returns 403 for these endpoints.
</CardDescription>
</CardHeader>
<CardContent className="text-sm">
{health ? (
<div className="grid gap-2 md:grid-cols-2">
<div className="flex items-center justify-between">
<span className="text-muted-foreground">Status</span>
<span className="font-mono">{health.status}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-muted-foreground">Uptime (s)</span>
<span className="font-mono">{health.uptime_seconds}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-muted-foreground">Alerts enabled</span>
<span className="font-mono">{String(health.alerts_enabled)}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-muted-foreground">Prometheus enabled</span>
<span className="font-mono">{String(health.prometheus_enabled)}</span>
</div>
</div>
) : (
<div className="text-muted-foreground">
{loading ? "Loading…" : "Monitoring health endpoint is unavailable (likely disabled)."}
</div>
)}
</CardContent>
</Card>
<Card className="border-border/50 bg-card/50">
<CardHeader>
<CardTitle>Active alerts</CardTitle>
<CardDescription>From `GET /monitoring/alerts`.</CardDescription>
</CardHeader>
<CardContent>
{alerts === null ? (
<div className="text-sm text-muted-foreground">
{loading ? "Loading…" : "Alerts endpoint is unavailable (likely disabled)."}
</div>
) : alerts.length === 0 ? (
<div className="text-sm text-muted-foreground">No active alerts.</div>
) : (
<div className="space-y-2">
{alerts.map((a, i) => (
<div key={i} className="rounded-lg border border-border/50 bg-background px-3 py-2 text-sm">
<div className="flex items-center justify-between gap-3">
<div className="font-medium truncate">
{String(a.message ?? a.type ?? "Alert")}
</div>
<Badge variant="outline" className="shrink-0">
{String(a.severity ?? "unknown")}
</Badge>
</div>
{a.details ? (
<pre className="mt-2 whitespace-pre-wrap break-words text-[11px] text-muted-foreground">
{JSON.stringify(a.details, null, 2)}
</pre>
) : null}
</div>
))}
</div>
)}
</CardContent>
</Card>
<Card className="border-border/50 bg-card/50">
<CardHeader>
<CardTitle>Prometheus metrics</CardTitle>
<CardDescription>From `GET /metrics` (raw text).</CardDescription>
</CardHeader>
<CardContent>
{metricsText === null ? (
<div className="text-sm text-muted-foreground">
{loading ? "Loading…" : "Metrics endpoint is unavailable (likely disabled)."}
</div>
) : (
<div className="space-y-3">
{health?.current_metrics ? (
<div className="grid gap-2 md:grid-cols-2 text-sm">
<div className="flex items-center justify-between">
<span className="text-muted-foreground">Error rate</span>
<span className="font-mono">{formatMaybeNumber(health.current_metrics.error_rate, 4)}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-muted-foreground">P99 latency (ms)</span>
<span className="font-mono">{formatMaybeNumber(health.current_metrics.latency_p99_ms, 1)}</span>
</div>
</div>
) : null}
<pre className="max-h-[420px] overflow-auto rounded-lg border border-border/50 bg-background p-3 text-[11px] leading-relaxed">
{metricsText}
</pre>
</div>
)}
</CardContent>
</Card>
</div>
);
}