// Health monitoring UI functions let healthData = null; let healthUpdateInterval = null; const SERVICE_DEFS = [ { key: 'dns', name: 'DNS Service', icon: 'fa-globe' }, { key: 'proxy', name: 'Proxy Service', icon: 'fa-shield-halved' }, { key: 'swarm', name: 'Swarm', icon: 'fa-diagram-project' }, { key: 'corestore', name: 'Corestore', icon: 'fa-hard-drive' } ]; // Fetch health data async function fetchHealth() { try { const response = await fetch('/api/health'); if (!response.ok) { throw new Error('Failed to fetch health data'); } healthData = await response.json(); return healthData; } catch (err) { console.error('Failed to fetch health:', err); if (window.showNotification) window.showNotification('Failed to load health data', 'error'); return null; } } // Render health dashboard (HTTP only when no WS data yet) async function renderHealth() { if (healthData) { applyHealthPayload(healthData); return; } const data = await fetchHealth(); if (!data) return; applyHealthPayload(data); } // Apply health payload (HTTP initial or WebSocket) function applyHealthPayload(data) { const incoming = normalizeHealthPayload(data); if (!incoming) return; healthData = mergeHealthPayload(healthData, incoming); updateHealthStatus(healthData); renderServiceCards(healthData); updateHealthHistory(healthData); } // Update health status display function updateHealthStatus(data) { const statusEl = document.getElementById('health-status'); if (statusEl) { statusEl.textContent = data.status === 'healthy' ? 'Healthy' : 'Degraded'; statusEl.className = data.status === 'healthy' ? 'stats-metric-value stats-health-status stats-health-status--ok' : 'stats-metric-value stats-health-status stats-health-status--warn'; } const uptimeEl = document.getElementById('health-uptime'); if (uptimeEl && data.uptime) { uptimeEl.textContent = window.formatUptime ? window.formatUptime(data.uptime) : `${Math.floor(data.uptime / 1000)}s`; } } function getServiceData(data, key) { return data.services?.[key] || data.dependencies?.[key] || null; } function normalizeHealthPayload(data) { if (!data) return null; return { status: data.status, timestamp: data.timestamp, uptime: data.uptime, services: data.services, dependencies: data.dependencies }; } function mergeServiceData(existing, incoming) { if (!incoming) return existing || null; if (!existing) return incoming; const merged = { ...existing, ...incoming }; if (incoming.details != null) { merged.details = { ...(existing.details || {}), ...incoming.details }; } else if (existing.details) { merged.details = existing.details; } return merged; } function mergeHealthPayload(prev, next) { if (!next) return prev; if (!prev) return next; const merged = { ...prev, ...next, services: { ...(prev.services || {}) }, dependencies: { ...(prev.dependencies || {}) } }; for (const [key, val] of Object.entries(next.services || {})) { merged.services[key] = mergeServiceData(prev.services?.[key], val); } for (const [key, val] of Object.entries(next.dependencies || {})) { merged.dependencies[key] = mergeServiceData(prev.dependencies?.[key], val); } return merged; } function formatDetailEntries(details) { if (!details || typeof details !== 'object') return []; return Object.entries(details).map(([key, value]) => ({ key, value: String(value) })); } function ensureServiceCards(container) { if (container.dataset.initialized === '3') return; container.dataset.initialized = '3'; container.innerHTML = SERVICE_DEFS.map((service) => `

${service.name}

`).join(''); } function updateServiceCardElement(card, serviceData) { if (!serviceData) return; const healthy = serviceData.healthy !== false; const enabled = serviceData.enabled !== false; const badge = card.querySelector('.health-service-badge'); if (badge) { badge.textContent = healthy ? 'Healthy' : 'Unhealthy'; badge.className = `health-service-badge health-service-badge--${healthy ? 'ok' : 'error'}`; } const enabledEl = card.querySelector('.health-service-enabled'); if (enabledEl) { enabledEl.textContent = enabled ? 'Enabled' : 'Disabled'; } const detailsEl = card.querySelector('.health-service-details'); if (!detailsEl || serviceData.details == null) return; const entries = formatDetailEntries(serviceData.details); const existingRows = new Map(); detailsEl.querySelectorAll('[data-detail-key]').forEach((row) => { existingRows.set(row.dataset.detailKey, row); }); for (const { key, value } of entries) { let row = existingRows.get(key); if (!row) { row = document.createElement('div'); row.className = 'health-service-detail-row'; row.dataset.detailKey = key; const label = document.createElement('span'); label.className = 'health-service-detail-key'; label.textContent = key; const valueEl = document.createElement('span'); valueEl.className = 'health-detail-value'; valueEl.textContent = value; row.appendChild(label); row.appendChild(valueEl); detailsEl.appendChild(row); } else { const valueEl = row.querySelector('.health-detail-value'); if (valueEl && valueEl.textContent !== value) { valueEl.textContent = value; } } existingRows.delete(key); } for (const row of existingRows.values()) { row.remove(); } } // Render or update service status cards in place (avoids layout shift on refresh) function renderServiceCards(data) { const container = document.getElementById('health-services'); if (!container) return; ensureServiceCards(container); for (const service of SERVICE_DEFS) { const card = container.querySelector(`[data-service="${service.key}"]`); if (!card) continue; updateServiceCardElement(card, getServiceData(data, service.key)); } } // Update health history function updateHealthHistory(data) { // Health history tracking removed - chart no longer displayed } // Render health history chart - removed, chart no longer displayed function renderHealthHistoryChart() { // Health history chart removed from stats page } // Start health updates (data via stats-snapshot + update-health WebSocket) function startHealthUpdates() { if (healthData) { applyHealthPayload(healthData); return; } renderHealth(); } // Stop health updates function stopHealthUpdates() { if (healthUpdateInterval) { clearInterval(healthUpdateInterval); healthUpdateInterval = null; } } // Make functions globally accessible window.applyHealthPayload = applyHealthPayload; window.renderHealth = renderHealth; window.startHealthUpdates = startHealthUpdates; window.stopHealthUpdates = stopHealthUpdates;