import { useCallback, useEffect, useRef, useState } from "react"; import { RefreshCw, ShieldAlert, ShieldCheck } from "lucide-react"; import { useClient } from "hooks/useClient"; import { readableError } from "./errors"; /** * Certificates, ordered by how soon they expire. * * That ordering is the whole design. A certificate list sorted by name is a reference; sorted by expiry * it answers the only question anybody has, which is whether something is about to break. The server * computes `daysUntilExpiry` so the browser is not parsing dates and getting the timezone wrong. */ type Certificate = { id: number; provider: string; nice_name: string; domain_names: string[]; expires_on: string | null; daysUntilExpiry: number | null; }; /** Let's Encrypt renews at 30 days, so inside 14 something has actually gone wrong. */ const WARN_DAYS = 30; const URGENT_DAYS = 14; export const CertificatesPanel = () => { const client = useClient(); const clientRef = useRef(client); clientRef.current = client; const [certificates, setCertificates] = useState(null); const [error, setError] = useState(null); const load = useCallback(async () => { try { setCertificates(await clientRef.current.get("/proxy-manager/certificates")); setError(null); } catch (caught) { // Not `.body` — `useClient` throws `ApiError(status, message)` and has no body. See `asFailure` in // HostsPanel.tsx for the full account; this panel had the same dead read. setError(readableError(caught, "Could not load certificates.")); setCertificates(null); } }, []); useEffect(() => { void load(); }, [load]); if (error) return {error}; if (!certificates) return Loading certificates…; if (certificates.length === 0) return No certificates.; const expiring = certificates.filter((c) => c.daysUntilExpiry !== null && c.daysUntilExpiry < WARN_DAYS).length; return (
{certificates.length} certificate{certificates.length === 1 ? "" : "s"} {expiring > 0 ? {expiring} expiring : null}
    {certificates.map((certificate) => { const days = certificate.daysUntilExpiry; const urgent = days !== null && days < URGENT_DAYS; const warn = days !== null && days < WARN_DAYS; return (
  • {warn ? ( ) : ( )} {certificate.nice_name} {certificate.domain_names.join(", ")} {days === null ? certificate.provider : days < 0 ? `expired ${-days}d ago` : `${days}d`}
  • ); })}
); }; const Centred = ({ children }: { children: React.ReactNode }) => (
{children}
);