'use client' import { useCallback, useEffect, useState } from 'react' import Link from 'next/link' import { parseCertList } from '@/lib/parse-caddy' import { fetchAgentOutput } from '@/lib/agent-fetch' type CaddyData = { soonestExpiryMs: number | null; count: number } export type CaddyInitial = { data: CaddyData; error: null } | { data: null; error: string } async function refreshCaddy(): Promise { const output = await fetchAgentOutput('caddy_list_certs') const certs = parseCertList(output) const expiryTimes = certs .filter((c) => c.notAfter) .map((c) => new Date(c.notAfter).getTime()) const soonestExpiryMs = expiryTimes.length > 0 ? Math.min(...expiryTimes) : null return { soonestExpiryMs, count: certs.length } } function daysUntil(ms: number): number { return Math.floor((ms - Date.now()) / (1000 * 60 * 60 * 24)) } export default function CaddyWidget({ initial }: { initial: CaddyInitial }) { const [data, setData] = useState(initial.data) const [error, setError] = useState(initial.error) const refresh = useCallback(async () => { try { const d = await refreshCaddy() setData(d) setError(null) } catch (err) { setError(err instanceof Error ? err.message : 'refresh failed') } }, []) useEffect(() => { const id = setInterval(refresh, 30_000) return () => clearInterval(id) }, [refresh]) return (

Caddy / TLS

{error ? (

{error}

) : data ? (
{data.soonestExpiryMs !== null ? (

{daysUntil(data.soonestExpiryMs)} days to expiry

) : (

no certs

)}

{data.count} cert{data.count !== 1 ? 's' : ''}

) : (

)} ) }