Files
dashcaddy/status/js/card-badges.js
Sami f61e85d9a7 Initial commit: DashCaddy v1.0
Full codebase including API server (32 modules + routes), dashboard frontend,
DashCA certificate distribution, installer script, and deployment skills.
2026-03-05 02:26:12 -08:00

116 lines
3.9 KiB
JavaScript

// ========== 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();
})();