112 lines
5.6 KiB
TypeScript
112 lines
5.6 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import { Activity, Server, AlertOctagon, Clock } from "lucide-react";
|
|
import { Card, CardBody, CardHeader, CardTitle } from "@/components/ui/Card";
|
|
import { Badge } from "@/components/ui/Badge";
|
|
import { EmptyState } from "@/components/ui/EmptyState";
|
|
import { MetricCard } from "@/components/ui/MetricCard";
|
|
import { fmtDate } from "@/lib/utils";
|
|
|
|
const SERVICES = [
|
|
{ name: "asf-core", status: "healthy", latency_ms: 42, error_rate: 0.1, rps: 184 },
|
|
{ name: "asf-worker", status: "healthy", latency_ms: 0, error_rate: 0.0, rps: 0, jobs: 6 },
|
|
{ name: "asf-context", status: "degraded", latency_ms: 310, error_rate: 2.3, rps: 42 },
|
|
{ name: "asf-ai", status: "healthy", latency_ms: 620, error_rate: 0.5, rps: 12 },
|
|
{ name: "mongodb", status: "healthy", latency_ms: 3, error_rate: 0.0, rps: 0 },
|
|
{ name: "rabbitmq", status: "healthy", latency_ms: 2, error_rate: 0.0, queue_depth: 14 },
|
|
];
|
|
|
|
const INCIDENTS = [
|
|
{ id: "INC-233", sev: "medium", title: "asf-context p99 latency spike", started: "2025-11-22T08:10:00Z", state: "open" },
|
|
{ id: "INC-232", sev: "low", title: "MinIO console certificate warn", started: "2025-11-21T14:40:00Z", state: "resolved" },
|
|
];
|
|
|
|
export default function ObservabilityPage() {
|
|
return (
|
|
<div className="p-6 lg:p-8 max-w-[1400px] mx-auto">
|
|
<header className="mb-6 flex flex-wrap items-start justify-between gap-3">
|
|
<div>
|
|
<h1 className="text-2xl font-semibold tracking-tight text-slate-900 flex items-center gap-2">
|
|
<Activity className="w-6 h-6 text-brand-600" />
|
|
Observability
|
|
</h1>
|
|
<p className="mt-1 text-sm text-slate-500">
|
|
Service health, latency, error rates and active incidents. Structured JSON logs + correlation_id propagate across every ASF hop.
|
|
</p>
|
|
</div>
|
|
<Badge tone="neutral">OpenTelemetry · prepared</Badge>
|
|
</header>
|
|
|
|
<section className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
|
<MetricCard label="Services healthy" value={5} tone="green" icon={<Server className="w-5 h-5" />} subtitle="of 6 total" />
|
|
<MetricCard label="Requests / sec" value={238} tone="brand" icon={<Activity className="w-5 h-5" />} />
|
|
<MetricCard label="Error rate" value={0.6} isPercentage tone="amber" icon={<AlertOctagon className="w-5 h-5" />} />
|
|
<MetricCard label="Open incidents" value={1} tone="red" icon={<Clock className="w-5 h-5" />} />
|
|
</section>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
|
<Card className="lg:col-span-2">
|
|
<CardHeader>
|
|
<CardTitle>Service inventory</CardTitle>
|
|
<Badge tone="brand">p50 latency</Badge>
|
|
</CardHeader>
|
|
<CardBody className="p-0">
|
|
<table className="w-full text-sm">
|
|
<thead className="bg-slate-50 text-xs uppercase tracking-wide text-slate-500 border-b border-slate-200">
|
|
<tr>
|
|
<th className="text-left px-4 py-2">Service</th>
|
|
<th className="text-left px-4 py-2">Status</th>
|
|
<th className="text-right px-4 py-2">Latency</th>
|
|
<th className="text-right px-4 py-2">Error %</th>
|
|
<th className="text-right px-4 py-2">Load</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-slate-100">
|
|
{SERVICES.map((s) => (
|
|
<tr key={s.name} className="hover:bg-slate-50">
|
|
<td className="px-4 py-2.5 font-mono text-xs">{s.name}</td>
|
|
<td className="px-4 py-2.5">
|
|
<Badge tone={s.status === "healthy" ? "green" : s.status === "degraded" ? "amber" : "red"}>{s.status}</Badge>
|
|
</td>
|
|
<td className="px-4 py-2.5 text-right tabular-nums">{s.latency_ms} ms</td>
|
|
<td className="px-4 py-2.5 text-right tabular-nums">{s.error_rate.toFixed(2)}%</td>
|
|
<td className="px-4 py-2.5 text-right text-xs text-slate-600 tabular-nums">
|
|
{s.rps ? `${s.rps} rps` : s.queue_depth !== undefined ? `q=${s.queue_depth}` : `${s.jobs} jobs`}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</CardBody>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Incidents</CardTitle>
|
|
<Badge tone="amber">correlation_id · audit</Badge>
|
|
</CardHeader>
|
|
<CardBody>
|
|
{INCIDENTS.length === 0 ? (
|
|
<EmptyState title="All quiet" description="No incidents in the last 24 hours." icon={Activity} />
|
|
) : (
|
|
<ul className="space-y-2 text-sm">
|
|
{INCIDENTS.map((i) => (
|
|
<li key={i.id} className="p-3 rounded-lg border border-slate-100 hover:border-slate-200">
|
|
<div className="flex items-center gap-2">
|
|
<Badge tone={i.sev === "high" || i.sev === "critical" ? "red" : i.sev === "medium" ? "amber" : "neutral"} className="uppercase">{i.sev}</Badge>
|
|
<span className="font-mono text-xs text-slate-500">{i.id}</span>
|
|
<Badge tone={i.state === "open" ? "amber" : "green"}>{i.state}</Badge>
|
|
</div>
|
|
<div className="mt-1.5 font-medium text-slate-800">{i.title}</div>
|
|
<div className="mt-1 text-xs text-slate-500">Started {fmtDate(i.started)}</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|