import Link from 'next/link'; import { FreshnessBadge } from '@/components/ui/badges'; import { fmtAgo, fmtDateTime, fmtInt } from '@/lib/format'; import type { ConnectorStatus } from '@/lib/types'; /** "in 2 h" / "overdue 5 min" for scheduled timestamps (fmtAgo only handles the past). */ export function fmtUntil(v: string | null | undefined, now: number = Date.now()): string { if (!v) return '—'; const t = new Date(v).getTime(); if (Number.isNaN(t)) return '—'; const s = Math.round((t - now) / 1000); if (s <= 0) return s > -60 ? 'due now' : `overdue ${fmtAgo(v, now).replace(' ago', '')}`; if (s < 60) return 'in < 1 min'; const m = Math.round(s / 60); if (m < 60) return `in ${m} min`; const h = Math.round(m / 60); if (h < 48) return `in ${h} h`; return `in ${Math.round(h / 24)} d`; } export function fmtDuration(ms: number | null | undefined): string { if (ms === null || ms === undefined) return '—'; if (ms < 1000) return `${fmtInt(ms)} ms`; if (ms < 120_000) return `${(ms / 1000).toFixed(1)} s`; return `${(ms / 60_000).toFixed(1)} min`; } export function fmtInterval(s: number | null | undefined): string { if (!s) return '—'; if (s % 86400 === 0) return `${s / 86400} d`; if (s % 3600 === 0) return `${s / 3600} h`; return `${Math.round(s / 60)} min`; } export function RunStatusPill({ status }: { status: string | null | undefined }) { const s = status ?? 'never run'; const color = s === 'success' ? 'var(--active)' : s === 'failed' ? 'var(--danger)' : s === 'running' ? 'var(--accent)' : s === 'unchanged' ? 'var(--ink-2)' : 'var(--inactive)'; return ( {s} ); } /** * Connector health table (public /status, /status/data, admin overview). Stacks into cards under 768 px. * `last_error` is the error of the most recent *failed* run, which may be older than the last success — labelled accordingly. */ export function ConnectorsTable({ connectors, now = Date.now(), linkAdmin = false }: { connectors: ConnectorStatus[]; now?: number; linkAdmin?: boolean }) { return (
| Connector | Freshness | Last success | Last status | Next run | Duration | Failures | Circuit | Last failure |
|---|---|---|---|---|---|---|---|---|
|
{linkAdmin ? (
{c.name}
) : (
{c.name}
)}
{c.source_name} · every {fmtInterval(c.interval_seconds)}
{!c.enabled && disabled}
|
|
{fmtAgo(c.last_success_at, now)} |
|
{c.enabled ? fmtUntil(c.next_run_at, now) : '—'} | {fmtDuration(c.last_duration_ms)} | 0 ? 'text-warn' : 'text-ink-2'}>{fmtInt(c.consecutive_failures)} | {c.circuit_open_until && new Date(c.circuit_open_until).getTime() > now ? ( open · resets {fmtUntil(c.circuit_open_until, now)} ) : ( closed )} | {c.last_error ? ( {c.last_error.length > 80 ? `${c.last_error.slice(0, 80)}…` : c.last_error} ) : ( none recorded )} |
Connector freshness is relative to each connector’s own schedule: