// ========== CARD HEALTH & UPDATE BADGES ========== (function() { // Fetch health data and update card UI async function refreshCardHealth() { try { const res = await fetch('/api/v1/health-checks/status'); const data = await res.json(); if (!data.success || !data.status) return; for (const [serviceId, svc] of Object.entries(data.status)) { const uptimeEl = document.getElementById('uptime-' + serviceId); const barEl = document.getElementById('uptime-bar-' + serviceId); if (!uptimeEl) continue; const uptime24 = svc.uptime?.['24h']; if (uptime24 !== undefined && uptime24 !== null) { const pct = uptime24.toFixed(1); uptimeEl.textContent = `${pct}% uptime`; // Color class uptimeEl.className = 'uptime-chip'; if (uptime24 >= 99.9) uptimeEl.classList.add('excellent'); else if (uptime24 >= 99) uptimeEl.classList.add('good'); else if (uptime24 >= 95) uptimeEl.classList.add('degraded'); else uptimeEl.classList.add('poor'); if (barEl) barEl.style.width = pct + '%'; } } } catch (_) { /* Health check API unavailable — uptime chips stay hidden */ console.warn('[Card Badges] Health check API unavailable'); } } // Track dismissed updates (per session) let dismissedUpdates; try { dismissedUpdates = new Set(JSON.parse(safeSessionGet('dismissed-updates') || '[]')); } catch (_) { /* Session storage unavailable */ dismissedUpdates = new Set(); } // Fetch update data and show badges async function refreshCardUpdates() { try { const res = await fetch('/api/v1/updates/available'); const data = await res.json(); if (!data.success) return; // Clear all update badges first document.querySelectorAll('.update-available-badge').forEach(el => el.classList.remove('visible')); if (!data.updates?.length) return; for (const upd of data.updates) { // Try to match by container name to service id const apps = window.APPS || []; for (const app of apps) { if (app.containerId === upd.containerId || app.id === upd.containerName || app.name === upd.containerName) { // Skip dismissed updates if (dismissedUpdates.has(app.id)) break; const badge = document.getElementById('update-badge-' + app.id); if (badge) { badge.classList.add('visible'); badge.title = `Image digest changed. Click to dismiss if already up to date.\n${upd.imageName || ''}`; badge.style.cursor = 'pointer'; badge.onclick = (e) => { e.stopPropagation(); badge.classList.remove('visible'); dismissedUpdates.add(app.id); safeSessionSet('dismissed-updates', JSON.stringify([...dismissedUpdates])); }; } break; } } } } catch (_) { /* Updates API unavailable — badges stay hidden */ console.warn('[Card Badges] Updates API unavailable'); } } // Run health and update checks after main refresh, and periodically function scheduleCardEnhancements() { // Initial load (delayed to not compete with main probe checks) setTimeout(() => { refreshCardHealth(); refreshCardUpdates(); }, 5000); // Periodic refresh every 60 seconds setInterval(() => { refreshCardHealth(); refreshCardUpdates(); }, 60000); } // Hook into main refresh cycle const origRefreshAll = window.refreshAll; if (origRefreshAll) { window.refreshAll = async function() { try { await origRefreshAll(); // Refresh health data after main status check setTimeout(refreshCardHealth, 1000); } catch (e) { console.warn('[Card Badges] Error in refreshAll hook:', e.message); } }; } scheduleCardEnhancements(); })();