ai_api / src /app /(dashboard) /dashboard /runtime /RuntimePageClient.tsx
Yogesh
initial deploy
cd8bd0a
Raw
History Blame Contribute Delete
44.6 kB
"use client";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslations } from "next-intl";
import Link from "next/link";
import Card from "@/shared/components/Card";
import ProviderIcon from "@/shared/components/ProviderIcon";
import ModelCooldownsCard from "./components/ModelCooldownsCard";
import { useProviderNodeMap, resolveProviderName } from "@/lib/display/useProviderNodeMap";
type KnownBreakerState = "CLOSED" | "OPEN" | "HALF_OPEN" | "DEGRADED";
type BreakerState = KnownBreakerState | (string & {});
type ProviderBreaker = {
provider: string;
state: BreakerState;
failureCount: number;
lastFailure: string | null;
retryAfterMs: number;
};
type LockoutEntry = {
reason?: string;
until?: number | string | null;
remainingMs?: number;
model?: string;
accountId?: string;
};
type SessionTop = {
sessionId: string;
requestCount: number;
connectionId?: string | null;
ageMs: number;
idleMs: number;
createdAt?: string;
lastActiveAt?: string;
};
type QuotaMonitor = {
sessionId?: string;
accountId?: string;
provider?: string;
window?: string;
status?: "ok" | "alerting" | "exhausted" | "error" | string;
remainingPercent?: number;
};
type HealthPayload = {
timestamp?: string;
providerBreakers?: ProviderBreaker[];
lockouts?: Record<string, LockoutEntry>;
quotaMonitor?: {
active?: number;
alerting?: number;
exhausted?: number;
errors?: number;
monitors?: QuotaMonitor[];
};
sessions?: {
activeCount?: number;
stickyBoundCount?: number;
byApiKey?: Record<string, number>;
top?: SessionTop[];
};
};
type Connection = {
id: string;
provider: string;
name?: string;
displayName?: string;
email?: string;
authType?: string;
rateLimitedUntil?: string | null;
testStatus?: string;
lastError?: string;
lastErrorType?: string;
errorCode?: string | number;
backoffLevel?: number;
};
type FeedEventKind =
| "circuit-opened"
| "circuit-degraded"
| "circuit-recovered"
| "circuit-closed"
| "cooldown-added"
| "cooldown-cleared"
| "lockout-added"
| "lockout-cleared"
| "session-new"
| "quota-alert"
| "quota-exhausted"
| "quota-recovered";
type FeedEvent = {
id: string;
ts: number;
kind: FeedEventKind;
title: string;
detail: string;
};
type FeedFilter = "all" | "circuits" | "cooldowns" | "lockouts" | "sessions" | "quotas";
// ─────────────────────────────────────────────────────────────────────────────
// Constants & helpers
// ─────────────────────────────────────────────────────────────────────────────
const REFRESH_INTERVAL_MS = 5000;
const FEED_MAX_EVENTS = 50;
const EMPTY_PROVIDER_BREAKERS: ProviderBreaker[] = [];
type BreakerTone = { dot: string; bg: string; ring: string; label: string; icon: string };
const BREAKER_TONE: Record<string, BreakerTone> = {
CLOSED: {
dot: "#22c55e",
bg: "rgba(34,197,94,0.10)",
ring: "rgba(34,197,94,0.30)",
label: "OK",
icon: "check_circle",
},
HALF_OPEN: {
dot: "#eab308",
bg: "rgba(234,179,8,0.10)",
ring: "rgba(234,179,8,0.30)",
label: "RECOV",
icon: "sync",
},
DEGRADED: {
dot: "#f97316",
bg: "rgba(249,115,22,0.10)",
ring: "rgba(249,115,22,0.30)",
label: "DEG",
icon: "warning",
},
OPEN: {
dot: "#ef4444",
bg: "rgba(239,68,68,0.10)",
ring: "rgba(239,68,68,0.30)",
label: "OPEN",
icon: "block",
},
};
const FALLBACK_BREAKER_TONE: BreakerTone = {
dot: "#64748b",
bg: "rgba(100,116,139,0.10)",
ring: "rgba(100,116,139,0.30)",
label: "UNK",
icon: "help",
};
const FEED_KIND_META: Record<FeedEventKind, { icon: string; color: string; group: FeedFilter }> = {
"circuit-opened": { icon: "block", color: "#ef4444", group: "circuits" },
"circuit-degraded": { icon: "warning", color: "#f97316", group: "circuits" },
"circuit-recovered": { icon: "sync", color: "#eab308", group: "circuits" },
"circuit-closed": { icon: "check_circle", color: "#22c55e", group: "circuits" },
"cooldown-added": { icon: "ac_unit", color: "#3b82f6", group: "cooldowns" },
"cooldown-cleared": { icon: "lock_open", color: "#22c55e", group: "cooldowns" },
"lockout-added": { icon: "lock", color: "#f97316", group: "lockouts" },
"lockout-cleared": { icon: "lock_open", color: "#22c55e", group: "lockouts" },
"session-new": { icon: "fingerprint", color: "#06b6d4", group: "sessions" },
"quota-alert": { icon: "warning", color: "#eab308", group: "quotas" },
"quota-exhausted": { icon: "error", color: "#ef4444", group: "quotas" },
"quota-recovered": { icon: "check_circle", color: "#22c55e", group: "quotas" },
};
function fmtMs(ms: number | undefined | null): string {
if (!ms || ms <= 0) return "0s";
if (ms < 1000) return `${ms}ms`;
const s = Math.ceil(ms / 1000);
if (s < 60) return `${s}s`;
const m = Math.floor(s / 60);
const rs = s % 60;
if (m < 60) return rs > 0 ? `${m}m ${rs}s` : `${m}m`;
const h = Math.floor(m / 60);
const rm = m % 60;
return `${h}h ${rm}m`;
}
function fmtClock(ts: number): string {
return new Date(ts).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
});
}
function shortId(value: string | null | undefined, max = 12): string {
if (!value) return "—";
return value.length > max ? `${value.slice(0, max)}…` : value;
}
function untilMs(value: number | string | null | undefined): number {
if (typeof value === "number") return value - Date.now();
if (typeof value === "string") {
const ts = new Date(value).getTime();
if (Number.isFinite(ts)) return ts - Date.now();
}
return 0;
}
function normalizeBreakerState(state: string | null | undefined): string {
return String(state || "")
.trim()
.toUpperCase();
}
function getBreakerTone(normalizedState: string): BreakerTone {
return BREAKER_TONE[normalizedState] || FALLBACK_BREAKER_TONE;
}
function pushFeed(prev: FeedEvent[], events: FeedEvent[]): FeedEvent[] {
if (events.length === 0) return prev;
const merged = [...events, ...prev];
return merged.slice(0, FEED_MAX_EVENTS);
}
// Compute new feed events by diffing previous vs next snapshot.
// Conservative: only emit transitions (added/cleared/state-change), never
// emit for items that simply persist between polls.
function diffSnapshots(
prev: { health: HealthPayload | null; conns: Connection[] },
next: { health: HealthPayload | null; conns: Connection[] },
nowTs: number
): FeedEvent[] {
const out: FeedEvent[] = [];
// Circuit breakers
const prevBreakers = new Map((prev.health?.providerBreakers ?? []).map((b) => [b.provider, b]));
const nextBreakers = new Map((next.health?.providerBreakers ?? []).map((b) => [b.provider, b]));
for (const [provider, nextB] of nextBreakers) {
const prevB = prevBreakers.get(provider);
if (!prevB) continue;
const prevState = normalizeBreakerState(prevB.state);
const nextState = normalizeBreakerState(nextB.state);
if (prevState === nextState) continue;
if (nextState === "OPEN") {
out.push({
id: `cb-open-${provider}-${nowTs}`,
ts: nowTs,
kind: "circuit-opened",
title: `${provider} circuit OPEN`,
detail: `threshold hit · retry in ${fmtMs(nextB.retryAfterMs)}`,
});
} else if (nextState === "HALF_OPEN") {
out.push({
id: `cb-half-${provider}-${nowTs}`,
ts: nowTs,
kind: "circuit-recovered",
title: `${provider} HALF_OPEN`,
detail: `probing recovery`,
});
} else if (nextState === "DEGRADED") {
out.push({
id: `cb-deg-${provider}-${nowTs}`,
ts: nowTs,
kind: "circuit-degraded",
title: `${provider} DEGRADED`,
detail: `${nextB.failureCount} failures · degraded but serving`,
});
} else if (nextState === "CLOSED" && prevState !== "CLOSED") {
out.push({
id: `cb-close-${provider}-${nowTs}`,
ts: nowTs,
kind: "circuit-closed",
title: `${provider} circuit CLOSED`,
detail: `recovered to healthy`,
});
}
}
// Cooldowns (per-connection rateLimitedUntil)
const prevCooldowns = new Map(
prev.conns
.filter((c) => c.rateLimitedUntil && untilMs(c.rateLimitedUntil) > 0)
.map((c) => [c.id, c])
);
const nextCooldowns = new Map(
next.conns
.filter((c) => c.rateLimitedUntil && untilMs(c.rateLimitedUntil) > 0)
.map((c) => [c.id, c])
);
for (const [id, conn] of nextCooldowns) {
if (!prevCooldowns.has(id)) {
const label = conn.name || conn.email || conn.displayName || shortId(id);
out.push({
id: `cd-add-${id}-${nowTs}`,
ts: nowTs,
kind: "cooldown-added",
title: `${conn.provider}/${label} cooling`,
detail: `${fmtMs(untilMs(conn.rateLimitedUntil))} · ${conn.lastError || "unavailable"}`,
});
}
}
for (const [id, conn] of prevCooldowns) {
if (!nextCooldowns.has(id)) {
const label = conn.name || conn.email || conn.displayName || shortId(id);
out.push({
id: `cd-clr-${id}-${nowTs}`,
ts: nowTs,
kind: "cooldown-cleared",
title: `${conn.provider}/${label} resumed`,
detail: `cooldown cleared`,
});
}
}
// Lockouts
const prevLockKeys = new Set(Object.keys(prev.health?.lockouts ?? {}));
const nextLockKeys = new Set(Object.keys(next.health?.lockouts ?? {}));
for (const key of nextLockKeys) {
if (!prevLockKeys.has(key)) {
const entry = next.health?.lockouts?.[key];
out.push({
id: `lk-add-${key}-${nowTs}`,
ts: nowTs,
kind: "lockout-added",
title: `${key} locked`,
detail: entry?.reason ? `reason: ${entry.reason}` : "rate-limit lockout",
});
}
}
for (const key of prevLockKeys) {
if (!nextLockKeys.has(key)) {
out.push({
id: `lk-clr-${key}-${nowTs}`,
ts: nowTs,
kind: "lockout-cleared",
title: `${key} unlocked`,
detail: `lockout expired`,
});
}
}
// Sessions (additions only — sessions are short-lived, deletions are noise)
const prevSessions = new Set((prev.health?.sessions?.top ?? []).map((s) => s.sessionId));
for (const s of next.health?.sessions?.top ?? []) {
if (prevSessions.has(s.sessionId)) continue;
out.push({
id: `ss-new-${s.sessionId}-${nowTs}`,
ts: nowTs,
kind: "session-new",
title: `new session ${shortId(s.sessionId, 8)}`,
detail: s.connectionId ? `bound to ${shortId(s.connectionId, 10)}` : "no binding yet",
});
}
// Quota monitors (alerting / exhausted transitions)
const prevQuota = new Map(
(prev.health?.quotaMonitor?.monitors ?? []).map((m) => [
`${m.accountId ?? ""}:${m.provider ?? ""}:${m.window ?? ""}`,
m,
])
);
for (const m of next.health?.quotaMonitor?.monitors ?? []) {
const key = `${m.accountId ?? ""}:${m.provider ?? ""}:${m.window ?? ""}`;
const prevM = prevQuota.get(key);
const prevStatus = prevM?.status;
if (m.status === prevStatus) continue;
if (m.status === "exhausted") {
out.push({
id: `qe-${key}-${nowTs}`,
ts: nowTs,
kind: "quota-exhausted",
title: `${m.accountId ?? "?"} EXHAUSTED`,
detail: `${m.window ?? ""}${m.provider ? ` · ${m.provider}` : ""}`,
});
} else if (m.status === "alerting") {
out.push({
id: `qa-${key}-${nowTs}`,
ts: nowTs,
kind: "quota-alert",
title: `${m.accountId ?? "?"} ALERTING`,
detail: `${m.window ?? ""}${
typeof m.remainingPercent === "number" ? ` · ${Math.round(m.remainingPercent)}% left` : ""
}`,
});
} else if (prevStatus === "exhausted" || prevStatus === "alerting") {
out.push({
id: `qr-${key}-${nowTs}`,
ts: nowTs,
kind: "quota-recovered",
title: `${m.accountId ?? "?"} recovered`,
detail: `${m.window ?? ""} back to OK`,
});
}
}
return out;
}
// ─────────────────────────────────────────────────────────────────────────────
// Component
// ─────────────────────────────────────────────────────────────────────────────
export default function RuntimePageClient() {
const t = useTranslations("runtime");
const nodeMap = useProviderNodeMap();
const [health, setHealth] = useState<HealthPayload | null>(null);
const [connections, setConnections] = useState<Connection[]>([]);
const [loading, setLoading] = useState(true);
const [paused, setPaused] = useState(false);
const [lastUpdated, setLastUpdated] = useState<number | null>(null);
const [feed, setFeed] = useState<FeedEvent[]>([]);
const [feedFilter, setFeedFilter] = useState<FeedFilter>("all");
const prevSnapshotRef = useRef<{ health: HealthPayload | null; conns: Connection[] }>({
health: null,
conns: [],
});
const initialLoadRef = useRef(true);
const fetchAll = useCallback(async () => {
try {
const [healthRes, connsRes] = await Promise.all([
fetch("/api/monitoring/health"),
fetch("/api/providers/client"),
]);
const healthData: HealthPayload | null = healthRes.ok ? await healthRes.json() : null;
const connsData = connsRes.ok ? await connsRes.json() : null;
const conns: Connection[] = Array.isArray(connsData?.connections)
? connsData.connections
: [];
const nowTs = Date.now();
const prev = prevSnapshotRef.current;
// Skip diff on first load — everything is "new" but we don't want a
// burst of fake events on mount.
if (!initialLoadRef.current) {
const events = diffSnapshots(prev, { health: healthData, conns }, nowTs);
if (events.length > 0) setFeed((cur) => pushFeed(cur, events));
}
initialLoadRef.current = false;
prevSnapshotRef.current = { health: healthData, conns };
setHealth(healthData);
setConnections(conns);
setLastUpdated(nowTs);
} catch (err) {
console.error("[Runtime] fetch failed", err);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchAll();
if (paused) return;
const id = setInterval(fetchAll, REFRESH_INTERVAL_MS);
return () => clearInterval(id);
}, [fetchAll, paused]);
// ── Derived data ────────────────────────────────────────────────────────────
const cooldowns = useMemo(() => {
return connections.filter((c) => c.rateLimitedUntil && untilMs(c.rateLimitedUntil) > 0);
}, [connections]);
const breakers = health?.providerBreakers ?? EMPTY_PROVIDER_BREAKERS;
const lockoutEntries = useMemo<Array<[string, LockoutEntry]>>(
() => Object.entries(health?.lockouts ?? {}),
[health]
);
const counts = useMemo(() => {
let openCircuits = 0;
let halfCircuits = 0;
let degradedCircuits = 0;
let unknownCircuits = 0;
for (const breaker of breakers) {
const state = normalizeBreakerState(breaker.state);
if (state === "OPEN") {
openCircuits++;
} else if (state === "HALF_OPEN") {
halfCircuits++;
} else if (state === "DEGRADED") {
degradedCircuits++;
} else if (state !== "CLOSED") {
unknownCircuits++;
}
}
const totalBreakers = breakers.length;
const affectedCircuits = openCircuits + halfCircuits + degradedCircuits + unknownCircuits;
const sessions = health?.sessions?.activeCount ?? 0;
const lockouts = lockoutEntries.length;
const quota = health?.quotaMonitor;
const quotaAlertingTotal =
(quota?.alerting ?? 0) + (quota?.exhausted ?? 0) + (quota?.errors ?? 0);
return {
sessions,
stickyBound: health?.sessions?.stickyBoundCount ?? 0,
openCircuits,
halfCircuits,
degradedCircuits,
unknownCircuits,
affectedCircuits,
totalBreakers,
cooldowns: cooldowns.length,
lockouts,
quotaAlerting: quotaAlertingTotal,
quotaExhausted: quota?.exhausted ?? 0,
};
}, [breakers, cooldowns, health, lockoutEntries]);
const filteredFeed = useMemo(() => {
if (feedFilter === "all") return feed;
return feed.filter((ev) => FEED_KIND_META[ev.kind].group === feedFilter);
}, [feed, feedFilter]);
const overallHealthy = counts.totalBreakers - counts.affectedCircuits;
const overallPercent =
counts.totalBreakers > 0 ? Math.round((overallHealthy / counts.totalBreakers) * 100) : 100;
// ── Render ──────────────────────────────────────────────────────────────────
return (
<div className="flex flex-col gap-4">
{/* Header */}
<div className="flex items-start justify-between flex-wrap gap-3">
<div>
<h1 className="text-xl font-bold text-text-main flex items-center gap-2">
<span className="material-symbols-outlined text-[24px] text-primary">bolt</span>
{t("title")}
</h1>
<p className="text-sm text-text-muted mt-0.5">{t("description")}</p>
</div>
<div className="flex items-center gap-2">
<span className="text-[11px] text-text-muted tabular-nums">
{lastUpdated ? `↻ ${fmtClock(lastUpdated)}` : "—"}
</span>
<button
type="button"
onClick={() => setPaused((p) => !p)}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-bg-subtle border border-border text-text-main text-[12px] cursor-pointer hover:bg-black/[0.04] dark:hover:bg-white/[0.04]"
>
<span className="material-symbols-outlined text-[16px]">
{paused ? "play_arrow" : "pause"}
</span>
{paused ? t("resume") : t("pause")}
</button>
<button
type="button"
onClick={() => fetchAll()}
disabled={loading}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-bg-subtle border border-border text-text-main text-[12px] cursor-pointer hover:bg-black/[0.04] dark:hover:bg-white/[0.04] disabled:opacity-50"
title={t("refreshNow")}
>
<span
className={`material-symbols-outlined text-[16px] ${loading ? "animate-spin" : ""}`}
>
refresh
</span>
</button>
</div>
</div>
{/* Row 1 — KPIs */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
<KpiCard
icon="fingerprint"
label={t("kpiSessions")}
value={counts.sessions}
hint={t("hintStickyBound", { count: counts.stickyBound })}
tone="#06b6d4"
onClick={() => setFeedFilter("sessions")}
active={feedFilter === "sessions"}
/>
<KpiCard
icon="bolt"
label={t("kpiCircuits")}
value={`${counts.openCircuits} / ${counts.totalBreakers}`}
hint={
counts.halfCircuits + counts.degradedCircuits + counts.unknownCircuits > 0
? t("hintRecovering", {
count: counts.halfCircuits + counts.degradedCircuits + counts.unknownCircuits,
})
: counts.openCircuits === 0
? t("hintAllHealthy")
: t("hintOpen")
}
tone={
counts.openCircuits > 0
? "#ef4444"
: counts.halfCircuits + counts.degradedCircuits + counts.unknownCircuits > 0
? "#eab308"
: "#22c55e"
}
onClick={() => setFeedFilter("circuits")}
active={feedFilter === "circuits"}
/>
<KpiCard
icon="ac_unit"
label={t("kpiCooldowns")}
value={counts.cooldowns}
hint={t("hintConnsCooling")}
tone={counts.cooldowns > 0 ? "#3b82f6" : "#22c55e"}
onClick={() => setFeedFilter("cooldowns")}
active={feedFilter === "cooldowns"}
/>
<KpiCard
icon="lock"
label={t("kpiLockouts")}
value={counts.lockouts}
hint={t("hintModelsBlocked")}
tone={counts.lockouts > 0 ? "#f97316" : "#22c55e"}
onClick={() => setFeedFilter("lockouts")}
active={feedFilter === "lockouts"}
/>
</div>
<ModelCooldownsCard />
{/* Row 2 — Resilience layers (left, 2/3) + Live Feed (right, 1/3) */}
<div className="grid grid-cols-1 xl:grid-cols-[2fr_1fr] gap-3">
<Card padding="md">
<SectionHeader
icon="shield"
title={t("resilienceTitle")}
subtitle={t("resilienceSubtitle")}
trailing={
<div className="flex items-center gap-3 text-[11px]">
<span className="text-green-500">✓ {overallHealthy}</span>
<span className="text-amber-500">
⚠ {counts.halfCircuits + counts.degradedCircuits + counts.unknownCircuits}
</span>
<span className="text-red-500">⛔ {counts.openCircuits}</span>
</div>
}
/>
<div className="mb-4 mt-2">
<div className="h-2 rounded-sm bg-black/[0.06] dark:bg-white/[0.06] overflow-hidden">
<div
className="h-full rounded-sm bg-gradient-to-r from-green-500 via-amber-500 to-red-500/40"
style={{ width: `${overallPercent}%` }}
/>
</div>
<div className="mt-1 text-[11px] text-text-muted tabular-nums">
{t("providersHealthy", { percent: overallPercent })}
</div>
</div>
{/* Layer 1 */}
<LayerSection
id={1}
title={t("layer1Title")}
description={t("layer1Desc")}
badge={t("badgeAffectedOf", {
affected: counts.affectedCircuits,
total: counts.totalBreakers,
})}
badgeTone={
counts.openCircuits > 0
? "red"
: counts.halfCircuits + counts.degradedCircuits + counts.unknownCircuits > 0
? "amber"
: "green"
}
>
{breakers.length === 0 ? (
<EmptyHint text={t("emptyCircuits")} />
) : (
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-2">
{breakers.map((b) => {
const state = normalizeBreakerState(b.state);
const tone = getBreakerTone(state);
return (
<div
key={b.provider}
className="rounded-md border px-2.5 py-2 flex flex-col gap-0.5"
style={{ borderColor: tone.ring, background: tone.bg }}
title={`${resolveProviderName(b.provider, nodeMap)} · ${state || "UNKNOWN"} · failures ${b.failureCount}`}
>
<div className="flex items-center gap-1.5 text-[11px] font-semibold text-text-main">
<ProviderIcon providerId={b.provider} size={14} />
<span className="truncate flex-1">
{resolveProviderName(b.provider, nodeMap)}
</span>
<span style={{ color: tone.dot }} className="text-[10px] font-bold">
{tone.label}
</span>
</div>
<div className="text-[10px] text-text-muted tabular-nums">
{state === "OPEN"
? `retry ${fmtMs(b.retryAfterMs)}`
: `${b.failureCount} failures`}
</div>
</div>
);
})}
</div>
)}
</LayerSection>
{/* Layer 2 */}
<LayerSection
id={2}
title={t("layer2Title")}
description={t("layer2Desc")}
badge={t("badgeCooling", { count: counts.cooldowns })}
badgeTone={counts.cooldowns > 0 ? "blue" : "green"}
>
{cooldowns.length === 0 ? (
<EmptyHint text={t("emptyCooldowns")} />
) : (
<div className="flex flex-col divide-y divide-border/60">
{cooldowns.slice(0, 8).map((c) => {
const remaining = untilMs(c.rateLimitedUntil);
const label = c.name || c.email || c.displayName || shortId(c.id);
return (
<div
key={c.id}
className="py-2 grid items-center gap-2"
style={{
gridTemplateColumns: "minmax(0,1.5fr) 70px minmax(0,1fr) 60px 60px",
}}
>
<div className="flex items-center gap-2 min-w-0">
<ProviderIcon providerId={c.provider} size={18} />
<div className="min-w-0">
<div className="text-[12px] text-text-main truncate font-medium">
{resolveProviderName(c.provider, nodeMap)}/{label}
</div>
{c.lastErrorType && (
<div className="text-[10px] text-text-muted truncate">
{c.lastErrorType}
</div>
)}
</div>
</div>
<div className="text-[11px] tabular-nums text-blue-400">
{fmtMs(remaining)}
</div>
<div className="text-[10px] text-text-muted truncate">
{c.lastError || "—"}
</div>
<div className="text-[10px] text-text-muted tabular-nums">
L{c.backoffLevel ?? 0}
</div>
<div className="text-[10px] text-text-muted tabular-nums text-right">
{c.errorCode ?? ""}
</div>
</div>
);
})}
{cooldowns.length > 8 && (
<div className="pt-2 text-[10px] text-text-muted">
{t("moreCooldowns", { count: cooldowns.length - 8 })}
</div>
)}
</div>
)}
</LayerSection>
{/* Layer 3 */}
<LayerSection
id={3}
title={t("layer3Title")}
description={t("layer3Desc")}
badge={t("badgeLocked", { count: lockoutEntries.length })}
badgeTone={lockoutEntries.length > 0 ? "orange" : "green"}
>
{lockoutEntries.length === 0 ? (
<EmptyHint text={t("emptyLockouts")} />
) : (
<div className="flex flex-col divide-y divide-border/60">
{lockoutEntries.slice(0, 8).map(([key, lk]) => {
const remaining =
typeof lk.remainingMs === "number" ? lk.remainingMs : untilMs(lk.until);
return (
<div
key={key}
className="py-2 grid items-center gap-2"
style={{ gridTemplateColumns: "minmax(0,1.5fr) minmax(0,1fr) 70px" }}
>
<div className="flex items-center gap-2 min-w-0">
<span className="material-symbols-outlined text-[14px] text-orange-400 shrink-0">
lock
</span>
<span className="text-[12px] text-text-main truncate font-medium">
{key}
</span>
</div>
<div className="text-[10px] text-text-muted truncate">
{lk.reason || "rate limit"}
</div>
<div className="text-[11px] tabular-nums text-orange-400 text-right">
{remaining > 0 ? fmtMs(remaining) : "—"}
</div>
</div>
);
})}
{lockoutEntries.length > 8 && (
<div className="pt-2 text-[10px] text-text-muted">
{t("moreLockouts", { count: lockoutEntries.length - 8 })}
</div>
)}
</div>
)}
</LayerSection>
</Card>
{/* Live Feed */}
<Card padding="md">
<SectionHeader
icon="rss_feed"
title={t("feedTitle")}
subtitle={t("feedSubtitle", { count: filteredFeed.length })}
trailing={
<div className="flex items-center gap-1.5">
<select
value={feedFilter}
onChange={(e) => setFeedFilter(e.target.value as FeedFilter)}
className="text-[10px] bg-bg-subtle border border-border rounded px-1.5 py-1 cursor-pointer text-text-main"
>
<option value="all">{t("feedFilterAll")}</option>
<option value="circuits">{t("feedFilterCircuits")}</option>
<option value="cooldowns">{t("feedFilterCooldowns")}</option>
<option value="lockouts">{t("feedFilterLockouts")}</option>
<option value="sessions">{t("feedFilterSessions")}</option>
<option value="quotas">{t("feedFilterQuotas")}</option>
</select>
<button
type="button"
onClick={() => setFeed([])}
className="text-[10px] px-1.5 py-1 rounded border border-border text-text-muted hover:text-text-main hover:bg-black/[0.04] dark:hover:bg-white/[0.04] cursor-pointer"
title={t("feedClear")}
>
{t("feedClear")}
</button>
</div>
}
/>
<div className="mt-2 flex flex-col gap-1.5 max-h-[640px] overflow-auto pr-1">
{filteredFeed.length === 0 ? (
<div className="text-center py-12 text-text-muted">
<span className="material-symbols-outlined text-[40px] opacity-30 block mb-2">
hourglass_empty
</span>
<p className="text-xs">
{feed.length === 0 ? t("feedEmptyWaiting") : t("feedEmptyFiltered")}
</p>
</div>
) : (
filteredFeed.map((ev) => {
const meta = FEED_KIND_META[ev.kind];
return (
<div
key={ev.id}
className="flex items-start gap-2 px-2 py-1.5 rounded hover:bg-black/[0.03] dark:hover:bg-white/[0.03]"
>
<span
className="material-symbols-outlined text-[14px] mt-0.5"
style={{ color: meta.color }}
>
{meta.icon}
</span>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between gap-2">
<div className="text-[11px] font-medium text-text-main truncate">
{ev.title}
</div>
<span className="text-[9px] text-text-muted tabular-nums shrink-0">
{fmtClock(ev.ts)}
</span>
</div>
<div className="text-[10px] text-text-muted truncate">{ev.detail}</div>
</div>
</div>
);
})
)}
</div>
</Card>
</div>
{/* Row 3 — Sessions table + Quota Monitors */}
<div className="grid grid-cols-1 xl:grid-cols-[3fr_2fr] gap-3">
<Card padding="md">
<SectionHeader
icon="fingerprint"
title={t("sessionsTitle")}
subtitle={t("sessionsSubtitle")}
trailing={
<span className="text-[11px] text-text-muted">
{t("sessionsActive", { count: counts.sessions })}
</span>
}
/>
{(health?.sessions?.top ?? []).length === 0 ? (
<div className="text-center py-8 text-text-muted">
<span className="material-symbols-outlined text-[40px] opacity-30 block mb-2">
fingerprint
</span>
<p className="text-sm">{t("sessionsEmptyTitle")}</p>
<p className="text-xs mt-1">{t("sessionsEmptyHint")}</p>
</div>
) : (
<div className="overflow-x-auto mt-2">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-border/40">
<th className="text-left py-2 px-2 text-[10px] font-semibold text-text-muted uppercase tracking-wider">
{t("tblSession")}
</th>
<th className="text-right py-2 px-2 text-[10px] font-semibold text-text-muted uppercase tracking-wider">
{t("tblAge")}
</th>
<th className="text-right py-2 px-2 text-[10px] font-semibold text-text-muted uppercase tracking-wider">
{t("tblIdle")}
</th>
<th className="text-right py-2 px-2 text-[10px] font-semibold text-text-muted uppercase tracking-wider">
{t("tblReqs")}
</th>
<th className="text-left py-2 px-2 text-[10px] font-semibold text-text-muted uppercase tracking-wider">
{t("tblBoundTo")}
</th>
</tr>
</thead>
<tbody>
{(health?.sessions?.top ?? []).map((s) => (
<tr key={s.sessionId} className="border-b border-border/10 hover:bg-surface/20">
<td className="py-2 px-2">
<span className="font-mono text-[11px] text-text-muted">
{shortId(s.sessionId, 14)}
</span>
</td>
<td className="py-2 px-2 text-right text-[11px] text-text-muted tabular-nums">
{fmtMs(s.ageMs)}
</td>
<td className="py-2 px-2 text-right text-[11px] text-text-muted tabular-nums">
{fmtMs(s.idleMs)}
</td>
<td className="py-2 px-2 text-right">
<span className="text-[11px] font-semibold tabular-nums">
{s.requestCount}
</span>
</td>
<td className="py-2 px-2">
{s.connectionId ? (
<span className="font-mono text-[10px] text-cyan-400">
{shortId(s.connectionId, 12)}
</span>
) : (
<span className="text-text-muted/40"></span>
)}
</td>
</tr>
))}
</tbody>
</table>
{Object.keys(health?.sessions?.byApiKey ?? {}).length > 0 && (
<div className="mt-2 pt-2 border-t border-border/40 text-[10px] text-text-muted">
{t("topApiKeys")}:{" "}
{Object.entries(health?.sessions?.byApiKey ?? {})
.sort((a, b) => b[1] - a[1])
.slice(0, 3)
.map(([k, n]) => `${shortId(k, 8)}(${n})`)
.join(" · ")}
</div>
)}
</div>
)}
</Card>
<Card padding="md">
<SectionHeader
icon="radar"
title={t("quotaMonitorsTitle")}
subtitle={t("quotaMonitorsSubtitle")}
trailing={
<Link href="/dashboard/quota" className="text-[11px] text-primary hover:underline">
{t("openQuota")} →
</Link>
}
/>
{(() => {
const monitors = health?.quotaMonitor?.monitors ?? [];
const exhausted = monitors.filter((m) => m.status === "exhausted");
const alerting = monitors.filter((m) => m.status === "alerting");
const errors = monitors.filter((m) => m.status === "error");
const total = exhausted.length + alerting.length + errors.length;
if (total === 0) {
return (
<div className="text-center py-8 text-text-muted">
<span className="material-symbols-outlined text-[40px] opacity-30 block mb-2">
radar
</span>
<p className="text-sm">{t("allQuotasHealthy")}</p>
</div>
);
}
return (
<div className="mt-2 flex flex-col gap-3">
{exhausted.length > 0 && (
<QuotaGroup tone="red" label={t("statusExhausted")} items={exhausted} />
)}
{alerting.length > 0 && (
<QuotaGroup tone="amber" label={t("statusAlerting")} items={alerting} />
)}
{errors.length > 0 && (
<QuotaGroup tone="orange" label={t("statusError")} items={errors} />
)}
</div>
);
})()}
</Card>
</div>
</div>
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Sub-components
// ─────────────────────────────────────────────────────────────────────────────
function KpiCard({
icon,
label,
value,
hint,
tone,
onClick,
active,
}: {
icon: string;
label: string;
value: string | number;
hint: string;
tone: string;
onClick: () => void;
active: boolean;
}) {
return (
<button
type="button"
onClick={onClick}
className="text-left rounded-xl border px-4 py-3 transition-colors cursor-pointer bg-surface"
style={{
borderColor: active ? tone : "var(--color-border)",
boxShadow: active ? `0 0 0 2px ${tone}22` : undefined,
}}
>
<div className="flex items-center justify-between">
<span className="text-[11px] uppercase tracking-wider font-semibold text-text-muted">
{label}
</span>
<span className="material-symbols-outlined text-[16px]" style={{ color: tone }}>
{icon}
</span>
</div>
<div className="mt-1 text-2xl font-bold tabular-nums" style={{ color: tone }}>
{value}
</div>
<div className="text-[10px] text-text-muted truncate">{hint}</div>
</button>
);
}
function SectionHeader({
icon,
title,
subtitle,
trailing,
}: {
icon: string;
title: string;
subtitle?: string;
trailing?: React.ReactNode;
}) {
return (
<div className="flex items-start justify-between gap-3">
<div className="flex items-start gap-2">
<span className="material-symbols-outlined text-[20px] text-primary mt-0.5">{icon}</span>
<div>
<h2 className="text-base font-semibold text-text-main leading-tight">{title}</h2>
{subtitle && <p className="text-[11px] text-text-muted mt-0.5">{subtitle}</p>}
</div>
</div>
{trailing && <div className="shrink-0">{trailing}</div>}
</div>
);
}
function LayerSection({
id,
title,
description,
badge,
badgeTone,
children,
}: {
id: 1 | 2 | 3;
title: string;
description: string;
badge: string;
badgeTone: "red" | "amber" | "green" | "blue" | "orange";
children: React.ReactNode;
}) {
const toneMap = {
red: { bg: "rgba(239,68,68,0.10)", text: "#ef4444" },
amber: { bg: "rgba(234,179,8,0.10)", text: "#eab308" },
green: { bg: "rgba(34,197,94,0.10)", text: "#22c55e" },
blue: { bg: "rgba(59,130,246,0.10)", text: "#3b82f6" },
orange: { bg: "rgba(249,115,22,0.10)", text: "#f97316" },
} as const;
const tone = toneMap[badgeTone];
return (
<div className="mt-4 border-t border-border/40 pt-3 first-of-type:border-t-0 first-of-type:pt-0">
<div className="flex items-center justify-between gap-2 mb-2">
<div className="flex items-center gap-2">
<span className="text-[10px] uppercase tracking-wider font-bold text-text-muted">
Layer {id}
</span>
<h3 className="text-[13px] font-semibold text-text-main">{title}</h3>
</div>
<span
className="text-[10px] font-semibold px-2 py-0.5 rounded-full tabular-nums"
style={{ background: tone.bg, color: tone.text }}
>
{badge}
</span>
</div>
<p className="text-[11px] text-text-muted mb-2">{description}</p>
{children}
</div>
);
}
function EmptyHint({ text }: { text: string }) {
return (
<div className="text-[11px] text-text-muted italic py-3 text-center bg-bg-subtle/40 rounded-md">
{text}
</div>
);
}
function QuotaGroup({
tone,
label,
items,
}: {
tone: "red" | "amber" | "orange";
label: string;
items: QuotaMonitor[];
}) {
const t = useTranslations("runtime");
const toneMap = {
red: { text: "#ef4444", bg: "rgba(239,68,68,0.08)", border: "rgba(239,68,68,0.20)" },
amber: { text: "#eab308", bg: "rgba(234,179,8,0.08)", border: "rgba(234,179,8,0.20)" },
orange: { text: "#f97316", bg: "rgba(249,115,22,0.08)", border: "rgba(249,115,22,0.20)" },
} as const;
const tc = toneMap[tone];
return (
<div>
<div
className="text-[10px] font-bold uppercase tracking-wider mb-1.5"
style={{ color: tc.text }}
>
{label}
</div>
<div className="flex flex-col gap-1">
{items.slice(0, 6).map((m, i) => (
<div
key={`${m.accountId ?? ""}:${m.window ?? ""}:${i}`}
className="rounded-md border px-2.5 py-1.5 flex items-center justify-between gap-2"
style={{ background: tc.bg, borderColor: tc.border }}
>
<div className="min-w-0">
<div className="text-[11px] font-medium text-text-main truncate">
{m.accountId ?? "—"}
{m.provider ? ` / ${resolveProviderName(m.provider, nodeMap)}` : ""}
</div>
<div className="text-[10px] text-text-muted">{m.window ?? ""}</div>
</div>
{typeof m.remainingPercent === "number" && (
<span className="text-[11px] font-bold tabular-nums" style={{ color: tc.text }}>
{Math.round(m.remainingPercent)}%
</span>
)}
</div>
))}
{items.length > 6 && (
<div className="text-[10px] text-text-muted text-center pt-1">
{t("moreSuffix", { count: items.length - 6 })}
</div>
)}
</div>
</div>
);
}