import { useEffect, useState } from 'react' interface ContainerInfo { name: string image: string tag: string upToDate: boolean | null registry: string endpoint: string } export function DockerUpdatesWidget() { const [containers, setContainers] = useState([]) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) useEffect(() => { fetch('/api/updates/docker') .then(r => r.json()) .then(d => { if (d.error) setError(d.error) else setContainers(d.containers) }) .catch(() => setError('Failed to connect')) .finally(() => setLoading(false)) }, []) const outdated = containers.filter(c => c.upToDate === false).length const unknown = containers.filter(c => c.upToDate === null && c.registry !== 'docker.io' && c.registry !== 'ghcr.io').length return (
Docker
{!loading && !error && ( 0 ? { background: 'rgba(248,113,113,0.15)', color: 'var(--red)' } : {}}> {outdated > 0 ? `${outdated} outdated` : 'all current'} )}
{loading &&
Checking images…
} {error &&
⚠ {error}
} {!loading && !error && (
{[...containers] .sort((a, b) => { const rank = (c: ContainerInfo) => c.upToDate === false ? 0 : c.upToDate === null ? 1 : 2 return rank(a) - rank(b) }) .map(c => (
{c.name} {c.image}:{c.tag.startsWith('sha256:') ? c.tag.slice(0, 15) + '…' : c.tag} · {c.endpoint}
{c.upToDate === null && c.registry !== 'docker.io' && c.registry !== 'ghcr.io' ? ( ext ) : c.upToDate === true ? ( ) : c.upToDate === false ? ( ↑ update ) : ( ? )}
))} {unknown > 0 && (
{unknown} non-Docker Hub image{unknown > 1 ? 's' : ''} not checked
)}
)}
) }