- relativeTime(date: Date) helper toegevoegd aan lib/utils.ts - AuditWidget gebruikt nu gedeelde relativeTime in plaats van inline functie - CaddyWidget toont rode badge als soonest cert-expiry <30 dagen - app/page.tsx berekent expiringWarning voor CaddyInitial Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
69 lines
2.5 KiB
TypeScript
69 lines
2.5 KiB
TypeScript
'use client'
|
|
|
|
import { useCallback, useEffect, useState } from 'react'
|
|
import Link from 'next/link'
|
|
import { apiFetch } from '@/lib/csrf'
|
|
import { relativeTime } from '@/lib/utils'
|
|
|
|
type LatestRun = { id: string; flow_key: string; status: string; started_at: string }
|
|
export type AuditInitial =
|
|
| { data: LatestRun | null; error: null }
|
|
| { data: null; error: string }
|
|
|
|
const STATUS_STYLES: Record<string, string> = {
|
|
pending: 'bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400',
|
|
running: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400',
|
|
success: 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400',
|
|
failed: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400',
|
|
cancelled: 'bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400',
|
|
}
|
|
|
|
async function fetchLatestRun(): Promise<LatestRun | null> {
|
|
const res = await apiFetch('/api/audit/latest')
|
|
if (!res.ok) throw new Error(`${res.status}`)
|
|
const json = (await res.json()) as { run: LatestRun | null }
|
|
return json.run
|
|
}
|
|
|
|
export default function AuditWidget({ initial }: { initial: AuditInitial }) {
|
|
const [data, setData] = useState<LatestRun | null>(initial.data)
|
|
const [error, setError] = useState<string | null>(initial.error)
|
|
|
|
const refresh = useCallback(async () => {
|
|
try {
|
|
const run = await fetchLatestRun()
|
|
setData(run)
|
|
setError(null)
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'refresh failed')
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
const id = setInterval(refresh, 30_000)
|
|
return () => clearInterval(id)
|
|
}, [refresh])
|
|
|
|
return (
|
|
<Link href="/audit" className="block rounded-lg border bg-card p-5 transition-colors hover:bg-accent">
|
|
<h2 className="text-sm font-medium text-muted-foreground">Audit</h2>
|
|
{error ? (
|
|
<p className="mt-2 text-sm text-destructive truncate">{error}</p>
|
|
) : data ? (
|
|
<div className="mt-2 space-y-1">
|
|
<div className="flex items-center gap-2">
|
|
<span
|
|
className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${STATUS_STYLES[data.status] ?? ''}`}
|
|
>
|
|
{data.status}
|
|
</span>
|
|
<span className="text-xs text-muted-foreground">{relativeTime(new Date(data.started_at))}</span>
|
|
</div>
|
|
<p className="font-mono text-xs text-muted-foreground truncate">{data.flow_key}</p>
|
|
</div>
|
|
) : (
|
|
<p className="mt-2 text-sm text-muted-foreground">geen runs</p>
|
|
)}
|
|
</Link>
|
|
)
|
|
}
|