72 lines
2 KiB
TypeScript
72 lines
2 KiB
TypeScript
|
|
/* StatusIndicator — top-bar entry point to /status.
|
||
|
|
*
|
||
|
|
* Polls /api/dashboard/integration-health every 60 s, rolls up overall
|
||
|
|
* state into a single StatusLight, and links to /status. Title attribute
|
||
|
|
* gives a quick textual hint; click goes to the full page. */
|
||
|
|
|
||
|
|
'use client';
|
||
|
|
|
||
|
|
import { useEffect, useState } from 'react';
|
||
|
|
import Link from 'next/link';
|
||
|
|
import { StatusLight, type StatusLightState } from '@/components/ui/status-light';
|
||
|
|
|
||
|
|
interface HealthSummary {
|
||
|
|
failed: number;
|
||
|
|
expired: number;
|
||
|
|
expiringWithin14Days: number;
|
||
|
|
hasIssues: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
const POLL_MS = 60_000;
|
||
|
|
|
||
|
|
export function StatusIndicator() {
|
||
|
|
const [summary, setSummary] = useState<HealthSummary | null>(null);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
let cancelled = false;
|
||
|
|
async function load() {
|
||
|
|
try {
|
||
|
|
const res = await fetch('/api/dashboard/integration-health', { cache: 'no-store' });
|
||
|
|
if (!res.ok || cancelled) return;
|
||
|
|
const j = (await res.json()) as { summary: HealthSummary };
|
||
|
|
if (!cancelled) setSummary(j.summary);
|
||
|
|
} catch {
|
||
|
|
/* leave summary null — light renders idle */
|
||
|
|
}
|
||
|
|
}
|
||
|
|
void load();
|
||
|
|
const id = setInterval(() => void load(), POLL_MS);
|
||
|
|
return () => {
|
||
|
|
cancelled = true;
|
||
|
|
clearInterval(id);
|
||
|
|
};
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const state: StatusLightState = !summary
|
||
|
|
? 'idle'
|
||
|
|
: summary.failed > 0 || summary.expired > 0
|
||
|
|
? 'error'
|
||
|
|
: summary.expiringWithin14Days > 0
|
||
|
|
? 'warn'
|
||
|
|
: 'ok';
|
||
|
|
|
||
|
|
const title = !summary
|
||
|
|
? 'System status'
|
||
|
|
: state === 'error'
|
||
|
|
? `${summary.failed + summary.expired} integration issue(s)`
|
||
|
|
: state === 'warn'
|
||
|
|
? `${summary.expiringWithin14Days} token(s) expiring soon`
|
||
|
|
: 'All systems operational';
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Link
|
||
|
|
href="/status"
|
||
|
|
title={title}
|
||
|
|
aria-label={title}
|
||
|
|
className="inline-flex h-9 w-9 items-center justify-center rounded-md hover:bg-accent/40 transition-colors"
|
||
|
|
>
|
||
|
|
<StatusLight state={state} size="md" label={title} />
|
||
|
|
</Link>
|
||
|
|
);
|
||
|
|
}
|