/** * System Stats Loader — inclut MDADM */ (function() { 'use strict'; const API_STATS = '/data/api/system-stats.php'; const API_MDADM = '/data/api/mdadm-status.php'; const INTERVAL = 5000; let timer = null; let updating = false; let loadedOnce = false; const el = { uptime: document.getElementById('uptime'), cpu: document.getElementById('cpu'), ram: document.getElementById('ram'), ramSub: document.getElementById('ram-subtitle'), diskRoot: document.getElementById('disk-root'), diskRootSub: document.getElementById('disk-root-subtitle'), diskVar: document.getElementById('disk-var'), diskVarSub: document.getElementById('disk-var-subtitle'), diskSrv: document.getElementById('disk-srv'), diskSrvSub: document.getElementById('disk-srv-subtitle'), load: document.getElementById('load'), processes: document.getElementById('processes'), mdadmCard: document.getElementById('mdadm-card'), mdadmStatus: document.getElementById('mdadm-status'), mdadmSub: document.getElementById('mdadm-subtitle'), }; function set(element, value) { if (!element || value === undefined) return; const v = String(value); if (element.textContent !== v) element.textContent = v; } function color(element, percent, warn, danger) { if (!element) return; const c = percent > danger ? '#ff5555' : percent > warn ? '#ffb86c' : '#50fa7b'; if (element.style.color !== c) element.style.color = c; } async function fetchStats() { if (updating) return; updating = true; try { const r = await fetch(API_STATS, { cache: 'no-cache' }); if (!r.ok) throw new Error(`HTTP ${r.status}`); const { success, data } = await r.json(); if (success && data) renderStats(data); else if (loadedOnce) showError(); } catch(e) { console.error('system-stats:', e); if (loadedOnce) showError(); } finally { updating = false; } } function renderStats(s) { loadedOnce = true; set(el.uptime, s.uptime); if (s.cpu_usage !== undefined) { set(el.cpu, s.cpu_usage + '%'); color(el.cpu, s.cpu_usage, 50, 80); } if (s.ram_used !== undefined) { set(el.ram, s.ram_used + ' Gio'); set(el.ramSub, `sur ${s.ram_total} Gio (${s.ram_percent}%)`); color(el.ram, s.ram_percent, 60, 80); } if (s.disk_root_free !== undefined) { set(el.diskRoot, s.disk_root_free + ' Gio'); set(el.diskRootSub, `sur ${s.disk_root_total} Gio (${s.disk_root_percent}% utilisé)`); color(el.diskRoot, s.disk_root_percent, 70, 90); } if (s.disk_var_free !== undefined) { set(el.diskVar, s.disk_var_free + ' Gio'); set(el.diskVarSub, `sur ${s.disk_var_total} Gio (${s.disk_var_percent}% utilisé)`); color(el.diskVar, s.disk_var_percent, 70, 90); } if (s.disk_srv_free !== undefined) { set(el.diskSrv, s.disk_srv_free + ' Gio'); set(el.diskSrvSub, `sur ${s.disk_srv_total} Gio (${s.disk_srv_percent}% utilisé)`); color(el.diskSrv, s.disk_srv_percent, 70, 90); } if (s.load_1 !== undefined) { set(el.load, s.load_1); color(el.load, (s.load_1 / 4) * 100, 50, 80); } set(el.processes, s.processes); } async function fetchMdadm() { try { const r = await fetch(API_MDADM, { cache: 'no-cache' }); if (!r.ok) throw new Error(`HTTP ${r.status}`); const d = await r.json(); if (d.healthy) { set(el.mdadmStatus, '✓ Sain'); if (el.mdadmStatus) el.mdadmStatus.style.color = '#50fa7b'; set(el.mdadmSub, d.arrays.map(a => `${a.name} [${a.bitmap}]`).join(' · ')); if (el.mdadmCard) el.mdadmCard.style.borderTop = ''; } else { set(el.mdadmStatus, '⚠ Dégradé'); if (el.mdadmStatus) el.mdadmStatus.style.color = '#ff5555'; set(el.mdadmSub, d.arrays .filter(a => a.health !== 'ok') .map(a => `${a.name}: ${a.detail || a.health}`) .join(' · ')); if (el.mdadmCard) el.mdadmCard.style.borderTop = '2px solid #ff5555'; } } catch(e) { set(el.mdadmStatus, '?'); if (el.mdadmStatus) el.mdadmStatus.style.color = '#ffb86c'; set(el.mdadmSub, 'erreur fetch'); } } function showError() { ['uptime','cpu','ram','diskRoot','diskVar','diskSrv','load','processes'].forEach(k => { if (el[k]) { el[k].textContent = 'Erreur'; el[k].style.color = '#ff5555'; } }); } async function refresh() { await fetchStats(); await fetchMdadm(); } function stop() { if (timer) { clearInterval(timer); timer = null; } } function start() { stop(); timer = setInterval(refresh, INTERVAL); } function init() { if (!document.querySelector('.stats-section')) return; refresh(); start(); window.addEventListener('beforeunload', stop); document.addEventListener('visibilitychange', () => { if (document.hidden) stop(); else { refresh(); start(); } }); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init(); })();