Add refresh button to Docker widget with server cache bust
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -123,15 +123,19 @@ async function getLscrLatestDigest(image: string, tag: string): Promise<string |
|
|||||||
return getGenericRegistryDigest('lscr.io', image, tag, process.env.GITHUB_TOKEN)
|
return getGenericRegistryDigest('lscr.io', image, tag, process.env.GITHUB_TOKEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get('/docker', async (_req, res) => {
|
router.get('/docker', async (req, res) => {
|
||||||
const host = process.env.PORTAINER_HOST
|
const host = process.env.PORTAINER_HOST
|
||||||
if (!host) {
|
if (!host) {
|
||||||
res.status(503).json({ error: 'PORTAINER_HOST not configured' })
|
res.status(503).json({ error: 'PORTAINER_HOST not configured' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const cached = fromCache<{ containers: ContainerInfo[] }>('docker:full', HUB_TTL)
|
if (!req.query.refresh) {
|
||||||
if (cached) { res.json(cached); return }
|
const cached = fromCache<{ containers: ContainerInfo[] }>('docker:full', HUB_TTL)
|
||||||
|
if (cached) { res.json(cached); return }
|
||||||
|
} else {
|
||||||
|
delete cache['docker:full']
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const headers = portainerHeaders()
|
const headers = portainerHeaders()
|
||||||
|
|||||||
@@ -15,14 +15,20 @@ export function DockerUpdatesWidget() {
|
|||||||
const [containers, setContainers] = useState<ContainerInfo[]>([])
|
const [containers, setContainers] = useState<ContainerInfo[]>([])
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [refreshing, setRefreshing] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
const load = (bust = false) => {
|
||||||
fetch('/api/updates/docker')
|
if (bust) setRefreshing(true)
|
||||||
|
else setLoading(true)
|
||||||
|
setError(null)
|
||||||
|
fetch(`/api/updates/docker${bust ? '?refresh=1' : ''}`)
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(d => { if (d.error) setError(d.error); else setContainers(d.containers) })
|
.then(d => { if (d.error) setError(d.error); else setContainers(d.containers) })
|
||||||
.catch(() => setError('Failed to connect'))
|
.catch(() => setError('Failed to connect'))
|
||||||
.finally(() => setLoading(false))
|
.finally(() => { setLoading(false); setRefreshing(false) })
|
||||||
}, [])
|
}
|
||||||
|
|
||||||
|
useEffect(() => { load() }, [])
|
||||||
|
|
||||||
const outdated = containers.filter(c => c.upToDate === false)
|
const outdated = containers.filter(c => c.upToDate === false)
|
||||||
const upToDate = containers.filter(c => c.upToDate === true)
|
const upToDate = containers.filter(c => c.upToDate === true)
|
||||||
@@ -36,11 +42,26 @@ export function DockerUpdatesWidget() {
|
|||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="widget-header">
|
<div className="widget-header">
|
||||||
<div className="widget-title"><span className="dot" />Docker</div>
|
<div className="widget-title"><span className="dot" />Docker</div>
|
||||||
{!loading && !error && (
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
<span className="widget-badge" style={outdated.length > 0 ? { background: 'rgba(248,113,113,0.15)', color: 'var(--red)' } : {}}>
|
{!loading && !error && (
|
||||||
{outdated.length > 0 ? `${outdated.length} outdated` : 'all current'}
|
<span className="widget-badge" style={outdated.length > 0 ? { background: 'rgba(248,113,113,0.15)', color: 'var(--red)' } : {}}>
|
||||||
</span>
|
{outdated.length > 0 ? `${outdated.length} outdated` : 'all current'}
|
||||||
)}
|
</span>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onClick={() => load(true)}
|
||||||
|
disabled={refreshing || loading}
|
||||||
|
title="Re-check all images"
|
||||||
|
style={{
|
||||||
|
background: 'none', border: '1px solid var(--border)', borderRadius: 6,
|
||||||
|
color: 'var(--muted)', cursor: 'pointer', padding: '2px 6px', fontSize: 11,
|
||||||
|
lineHeight: 1, transition: 'color 0.15s',
|
||||||
|
opacity: refreshing || loading ? 0.4 : 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{refreshing ? '…' : '↺'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{loading && <div className="widget-loading">Checking images…</div>}
|
{loading && <div className="widget-loading">Checking images…</div>}
|
||||||
|
|||||||
Reference in New Issue
Block a user