import { useEffect, useState } from 'react' interface ContainerInfo { name: string image: string tag: string upToDate: boolean | null registry: string endpoint: string } const KNOWN = ['docker.io', 'ghcr.io', 'lscr.io'] export function DockerUpdatesWidget() { const [containers, setContainers] = useState([]) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) const [refreshing, setRefreshing] = useState(false) const load = (bust = false) => { if (bust) setRefreshing(true) else setLoading(true) setError(null) fetch(`/api/updates/docker${bust ? '?refresh=1' : ''}`) .then(r => r.json()) .then(d => { if (d.error) setError(d.error); else setContainers(d.containers) }) .catch(() => setError('Failed to connect')) .finally(() => { setLoading(false); setRefreshing(false) }) } useEffect(() => { load() }, []) const outdated = containers.filter(c => c.upToDate === false) const upToDate = containers.filter(c => c.upToDate === true) const unverified = containers.filter(c => c.upToDate === null && KNOWN.includes(c.registry)) const external = containers.filter(c => !KNOWN.includes(c.registry)) // Show: outdated first, then up to date. Unverified and ext only as a footer count. const visible = [...outdated, ...upToDate] return (
Docker
{!loading && !error && ( 0 ? { background: 'rgba(248,113,113,0.15)', color: 'var(--red)' } : {}}> {outdated.length > 0 ? `${outdated.length} outdated` : 'all current'} )}
{loading &&
Checking images…
} {error &&
⚠ {error}
} {!loading && !error && (
{visible.map(c => (
{c.name} {c.image}:{c.tag.startsWith('sha256:') ? c.tag.slice(0, 15) + '…' : c.tag} · {c.endpoint}
{c.upToDate === true ? : ↑ update }
))} {(unverified.length > 0 || external.length > 0) && (
{unverified.length > 0 &&
{unverified.length} image{unverified.length > 1 ? 's' : ''} could not be verified
} {external.length > 0 &&
{external.length} image{external.length > 1 ? 's' : ''} on unsupported registry
}
)}
)}
) }