// ========== HEALTH CHECK DASHBOARD ========== (function() { // Inject modal HTML injectModal('health-modal', `

๐Ÿฅ Health Check Dashboard

Loading health status...
๐Ÿšจ Loading incidents...
โš™๏ธ Loading configuration...

๐ŸŒ Global Settings

Applies to all health checks. Settings are stored locally in this browser.

Health history older than this is pruned.
How often each service is checked.
Warn when disk usage exceeds this level.
`); const modal = document.getElementById('health-modal'); const openBtn = document.getElementById('health-check-btn'); const cancelBtn = document.getElementById('health-cancel'); const refreshBtn = document.getElementById('health-refresh-btn'); const statusContainer = document.getElementById('health-status-container'); const incidentsContainer = document.getElementById('health-incidents-container'); const configContainer = document.getElementById('health-config-container'); const lastUpdateSpan = document.getElementById('health-last-update'); const addBtn = document.getElementById('health-add-btn'); const formEl = document.getElementById('health-config-form'); const formTitle = document.getElementById('health-form-title'); const formCancel = document.getElementById('health-form-cancel'); const formSave = document.getElementById('health-form-save'); // ---- Global health settings (retention, polling interval, disk threshold) ---- const HEALTH_SETTINGS_KEY = 'dashcaddy-health-settings'; const HEALTH_DEFAULTS = { retentionDays: 30, pollingInterval: 30, diskUsageThreshold: 80 }; const globalSaveBtn = document.getElementById('health-global-save'); const globalResetBtn = document.getElementById('health-global-reset'); const globalStatusSpan = document.getElementById('health-global-status'); const retentionInput = document.getElementById('health-setting-retention'); const intervalInput = document.getElementById('health-setting-interval'); const diskThresholdInput = document.getElementById('health-setting-disk-threshold'); function loadHealthSettings() { try { const raw = safeGet(HEALTH_SETTINGS_KEY); const saved = raw ? JSON.parse(raw) : {}; return Object.assign({}, HEALTH_DEFAULTS, saved); } catch (_) { return Object.assign({}, HEALTH_DEFAULTS); } } function applyHealthSettingsToUI() { const s = loadHealthSettings(); if (retentionInput) retentionInput.value = s.retentionDays; if (intervalInput) intervalInput.value = s.pollingInterval; if (diskThresholdInput) diskThresholdInput.value = s.diskUsageThreshold; } function saveHealthSettings() { const settings = { retentionDays: Math.max(1, Math.min(3650, parseInt(retentionInput?.value) || HEALTH_DEFAULTS.retentionDays)), pollingInterval: Math.max(5, Math.min(3600, parseInt(intervalInput?.value) || HEALTH_DEFAULTS.pollingInterval)), diskUsageThreshold: Math.max(50, Math.min(99, parseInt(diskThresholdInput?.value) || HEALTH_DEFAULTS.diskUsageThreshold)) }; try { safeSet(HEALTH_SETTINGS_KEY, JSON.stringify(settings)); applyHealthSettingsToUI(); if (globalStatusSpan) { globalStatusSpan.textContent = 'Saved โœ“'; globalStatusSpan.style.color = 'var(--ok-fg)'; setTimeout(() => { if (globalStatusSpan) globalStatusSpan.textContent = ''; }, 2500); } if (typeof showNotification === 'function') showNotification('Global health settings saved', 'success'); } catch (e) { if (globalStatusSpan) { globalStatusSpan.textContent = 'Save failed'; globalStatusSpan.style.color = 'var(--bad-fg)'; } if (typeof showNotification === 'function') showNotification('Failed to save settings: ' + e.message, 'error'); } } function resetHealthSettings() { try { safeSet(HEALTH_SETTINGS_KEY, JSON.stringify(HEALTH_DEFAULTS)); } catch (_) { /* ignore */ } applyHealthSettingsToUI(); if (globalStatusSpan) { globalStatusSpan.textContent = 'Reset to defaults โœ“'; globalStatusSpan.style.color = 'var(--ok-fg)'; setTimeout(() => { if (globalStatusSpan) globalStatusSpan.textContent = ''; }, 2500); } } applyHealthSettingsToUI(); globalSaveBtn?.addEventListener('click', saveHealthSettings); globalResetBtn?.addEventListener('click', resetHealthSettings); // ---- End global health settings ---- let editingId = null; function uptimeColor(pct) { if (pct >= 99.9) return 'var(--ok-fg)'; if (pct >= 95) return '#f39c12'; return 'var(--bad-fg)'; } function severityBadge(sev) { const colors = { critical: 'var(--bad-fg)', high: '#ff6b6b', medium: '#f39c12', low: 'var(--muted)' }; return `${sev}`; } async function loadStatus() { try { const res = await fetch('/api/v1/health-checks/status'); const data = await res.json(); if (!data.success || !data.status || Object.keys(data.status).length === 0) { statusContainer.innerHTML = '
๐ŸฅNo health checks configured. Go to the Configure tab to add services.
'; return; } const services = Object.values(data.status); let html = ''; html += ''; html += ''; html += ''; html += ''; for (const s of services) { const isUp = s.status === 'up'; const dotColor = isUp ? 'var(--dot-ok)' : 'var(--dot-bad)'; const u24 = s.uptime?.['24h'] ?? '-'; const u7d = s.uptime?.['7d'] ?? '-'; const avgRt = s.avgResponseTime != null ? Math.round(s.avgResponseTime) + 'ms' : '-'; const lastCheck = s.timestamp ? timeAgo(s.timestamp) : '-'; html += ``; html += ``; html += ``; html += ``; html += ``; html += ``; html += ``; html += ''; html += ``; } html += '
ServiceStatusUptime 24hUptime 7dAvg ResponseLast Check
${escapeHtml(s.name || s.serviceId)}${isUp ? 'Up' : 'Down'}${typeof u24 === 'number' ? u24.toFixed(1) + '%' : u24}${typeof u7d === 'number' ? u7d.toFixed(1) + '%' : u7d}${avgRt}${lastCheck}
'; statusContainer.innerHTML = html; lastUpdateSpan.textContent = 'Updated ' + new Date().toLocaleTimeString(); // Row click to expand details statusContainer.querySelectorAll('tr[data-health-id]').forEach(row => { row.addEventListener('click', async () => { const id = row.dataset.healthId; const detailRow = document.getElementById('health-detail-' + id); if (!detailRow) return; if (detailRow.style.display !== 'none') { detailRow.style.display = 'none'; return; } detailRow.style.display = ''; try { const r = await fetch(`/api/v1/health-checks/${id}/stats?hours=24`); const d = await r.json(); if (d.success && d.stats) { const st = d.stats; const rt = st.responseTime || {}; detailRow.querySelector('td').innerHTML = `
Total Checks
${st.totalChecks || 0}
Uptime
${(st.uptime || 0).toFixed(2)}%
Avg Response
${Math.round(rt.avg || 0)}ms
P95 / P99
${Math.round(rt.p95 || 0)}ms / ${Math.round(rt.p99 || 0)}ms
Min Response
${Math.round(rt.min || 0)}ms
Max Response
${Math.round(rt.max || 0)}ms
Up Checks
${st.upChecks || 0}
Down Checks
${st.downChecks || 0}
`; } else { detailRow.querySelector('td').innerHTML = '
No detailed stats available for this period.
'; } } catch (e) { detailRow.querySelector('td').innerHTML = `
Failed: ${escapeHtml(e.message)}
`; } }); }); } catch (e) { statusContainer.innerHTML = `
Failed to load health status: ${escapeHtml(e.message)}
`; } } async function loadIncidents() { try { const [openRes, histRes] = await Promise.all([ fetch('/api/v1/health-checks/incidents'), fetch('/api/v1/health-checks/incidents/history?limit=50') ]); const openData = await openRes.json(); const histData = await histRes.json(); let html = ''; // Open incidents const open = (openData.success && openData.incidents) ? openData.incidents : []; if (open.length > 0) { html += '

Open Incidents (' + open.length + ')

'; for (const inc of open) { html += `
${escapeHtml(inc.serviceId)} ${severityBadge(inc.severity)}
${escapeHtml(inc.message)}
Started ${timeAgo(inc.createdAt)} ยท ${inc.occurrences || 1} occurrence(s)
`; } html += '
'; } else { html += '
All services operational โ€” no open incidents
'; } // Incident history const history = (histData.success && histData.history) ? histData.history : []; if (history.length > 0) { html += '

Incident History

'; html += ''; html += ''; for (const inc of history) { const resolved = inc.status === 'resolved'; const dur = resolved && inc.duration ? (inc.duration < 60000 ? Math.round(inc.duration / 1000) + 's' : Math.round(inc.duration / 60000) + 'm') : '-'; html += ``; html += ``; html += ``; html += ``; html += ``; html += ``; html += ``; html += ''; } html += '
ServiceTypeSeverityStatusDurationWhen
${escapeHtml(inc.serviceId)}${escapeHtml(inc.type)}${severityBadge(inc.severity)}${inc.status}${dur}${timeAgo(inc.createdAt)}
'; } incidentsContainer.innerHTML = html || '
๐ŸšจNo incidents recorded yet.
'; } catch (e) { incidentsContainer.innerHTML = `
Failed: ${escapeHtml(e.message)}
`; } } async function loadConfig() { try { const res = await fetch('/api/v1/health-checks/status'); const data = await res.json(); const services = data.success && data.status ? Object.values(data.status) : []; if (services.length === 0) { configContainer.innerHTML = '
โš™๏ธNo health checks configured yet. Click "Add Health Check" below.
'; return; } let html = ''; html += ''; for (const s of services) { const isUp = s.status === 'up'; html += ``; html += ``; html += ``; html += ``; html += `'; } html += '
ServiceStatusSLA TargetActions
${escapeHtml(s.name || s.serviceId)}${isUp ? 'Up' : 'Down'}${s.sla?.target ? s.sla.target + '%' : '-'}`; html += ``; html += ``; html += '
'; configContainer.innerHTML = html; } catch (e) { configContainer.innerHTML = `
Failed: ${escapeHtml(e.message)}
`; } } function showForm(id, name, url, timeout, codes, sla, slow) { editingId = id || null; formTitle.textContent = id ? 'Edit Health Check' : 'Add Health Check'; document.getElementById('health-form-id').value = id || ''; document.getElementById('health-form-id').disabled = !!id; document.getElementById('health-form-name').value = name || ''; document.getElementById('health-form-url').value = url || ''; document.getElementById('health-form-timeout').value = timeout || 10000; document.getElementById('health-form-codes').value = codes || '200'; document.getElementById('health-form-sla').value = sla || 99.9; document.getElementById('health-form-slow').value = slow || 5000; formEl.style.display = ''; addBtn.style.display = 'none'; } function hideForm() { formEl.style.display = 'none'; addBtn.style.display = ''; editingId = null; } addBtn?.addEventListener('click', () => showForm('', '', '', 10000, '200', 99.9, 5000)); formCancel?.addEventListener('click', hideForm); formSave?.addEventListener('click', async () => { const id = editingId || document.getElementById('health-form-id').value.trim(); if (!id) return showNotification('Service ID is required', 'warning'); const url = document.getElementById('health-form-url').value.trim(); if (!url) return showNotification('URL is required', 'warning'); const codes = document.getElementById('health-form-codes').value.split(',').map(c => parseInt(c.trim())).filter(Boolean); const body = { name: document.getElementById('health-form-name').value.trim() || id, url, timeout: parseInt(document.getElementById('health-form-timeout').value) || 10000, expectedStatusCodes: codes.length ? codes : [200], sla: { target: parseFloat(document.getElementById('health-form-sla').value) || 99.9 }, slowResponseThreshold: parseInt(document.getElementById('health-form-slow').value) || 5000 }; try { formSave.textContent = 'Saving...'; formSave.disabled = true; const res = await secureFetch(`/api/v1/health-checks/${encodeURIComponent(id)}/configure`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const data = await res.json(); if (!data.success) throw new Error(data.error || 'Save failed'); hideForm(); loadConfig(); loadStatus(); } catch (e) { showNotification('Error: ' + e.message, 'error'); } finally { formSave.textContent = 'Save'; formSave.disabled = false; } }); document.addEventListener('health-edit', async (e) => { const id = e.detail; // Load existing config to populate form โ€” use current status data as fallback showForm(id, '', '', 10000, '200', 99.9, 5000); }); document.addEventListener('health-delete', async (e) => { const id = e.detail; if (!confirm(`Delete health check for "${id}"?`)) return; try { const res = await secureFetch(`/api/v1/health-checks/${encodeURIComponent(id)}/configure`, { method: 'DELETE' }); const data = await res.json(); if (!data.success) throw new Error(data.error); loadConfig(); loadStatus(); } catch (err) { showNotification('Error: ' + err.message, 'error'); } }); openBtn?.addEventListener('click', () => { modal?.classList.add('show'); loadStatus(); }); wireModal(modal, cancelBtn); refreshBtn?.addEventListener('click', loadStatus); // Lazy-load tabs document.querySelector('[data-panel="health-incidents"]')?.addEventListener('click', loadIncidents); document.querySelector('[data-panel="health-config"]')?.addEventListener('click', loadConfig); })();