const Grid = (() => { const _agents = new Map(); function statusClass(agent) { if (agent.status === 'offline') return 't-off'; const m = _agents.get(agent.id)?.metrics; if (!m) return ''; const cfg = App.serverConfig; const errThreshold = cfg?.err_cpu ?? 85; const warnThreshold = cfg?.warn_cpu ?? 70; if ((m.cpu_percent ?? 0) >= errThreshold) return 't-err'; if ((m.cpu_percent ?? 0) >= warnThreshold || (m.hdd_used && m.hdd_total && (m.hdd_used / m.hdd_total * 100) >= (cfg?.warn_disk ?? 75))) return 't-warn'; return ''; } function ledClass(status) { return { online: 's-ok', warn: 's-warn', err: 's-err', offline: 's-off' }[status] ?? 's-off'; } function fmt(bytes) { if (!bytes) return '—'; if (bytes < 1024) return bytes + 'o'; if (bytes < 1024 ** 2) return (bytes / 1024).toFixed(1) + 'Ko'; if (bytes < 1024 ** 3) return (bytes / 1024 ** 2).toFixed(1) + 'Mo'; return (bytes / 1024 ** 3).toFixed(1) + 'Go'; } function fmtPct(val) { return val != null ? val.toFixed(0) + '%' : '—'; } function gFill(pct) { const cfg = App.serverConfig; if (pct >= (cfg?.err_cpu ?? 85)) return 'e'; if (pct >= (cfg?.warn_cpu ?? 70)) return 'w'; return ''; } function renderGaugeRow(faIcon, tip, label, pct, fillClass, valStr, extra) { const standard = (App.serverConfig?.gauge_type ?? 'compact') === 'standard'; if (standard) { return `
${label} ${valStr}${extra || ''}
`; } return `
${valStr}${extra || ''}
`; } function renderTile(agent, metrics) { const id = agent.id; const sc = statusClass(agent); const offline = agent.status === 'offline'; const cpu = metrics?.cpu_percent ?? null; const memPct = (metrics?.memory_used && metrics?.memory_total) ? metrics.memory_used / metrics.memory_total * 100 : null; const diskPct = (metrics?.hdd_used && metrics?.hdd_total) ? metrics.hdd_used / metrics.hdd_total * 100 : null; const uptimeSec = metrics?.uptime; let uptimeStr = ''; if (uptimeSec) { const d = Math.floor(uptimeSec / 86400); const h = Math.floor((uptimeSec % 86400) / 3600); uptimeStr = d > 0 ? `${d}j ${h}h` : `${h}h`; } const smartIco = !offline && metrics?.smart != null ? (metrics.smart.passed ? `` : ``) : ''; const iconContent = ` `; return `
${iconContent}
${esc(agent.hostname)}
${esc(agent.ip) || '—'}
${renderGaugeRow('microchip', 'CPU', 'CPU', offline ? 0 : (cpu ?? 0), offline ? '' : gFill(cpu ?? 0), offline ? '—' : fmtPct(cpu))} ${renderGaugeRow('memory', 'RAM', 'MÉMOIRE', offline ? 0 : (memPct ?? 0), offline ? '' : gFill(memPct ?? 0), offline ? '—' : (metrics?.memory_used && metrics?.memory_total ? fmt(metrics.memory_used) + '/' + fmt(metrics.memory_total) : '—'))} ${renderGaugeRow('hard-drive', 'Disque', 'DISQUE', offline ? 0 : (diskPct ?? 0), offline ? '' : (diskPct >= (App.serverConfig?.warn_disk ?? 75) ? 'w' : ''), offline ? '—' : (metrics?.hdd_used && metrics?.hdd_total ? fmt(metrics.hdd_used) + '/' + fmt(metrics.hdd_total) : '—'), smartIco)}
${offline ? 'Hors ligne' : `${uptimeStr || '—'}`}
`; } function update(agentId, metrics) { let entry = _agents.get(agentId); if (!entry) { // Nouvel agent découvert via WebSocket — on crée la tuile à la volée const agent = { id: agentId, hostname: metrics.hostname || agentId, ip: metrics.ip || '', status: metrics.status || 'online', }; _agents.set(agentId, { agent, metrics }); const grid = document.getElementById('agents-grid'); if (grid) grid.insertAdjacentHTML('beforeend', renderTile(agent, metrics)); updateStats(); return; } // Mettre à jour ip/status depuis les métriques fraîches if (metrics.ip) entry.agent.ip = metrics.ip; if (metrics.status) entry.agent.status = metrics.status; // Conserver les valeurs lentes (disque, smart) quand le paquet ne les contient pas if (entry.metrics) { for (const k of Object.keys(entry.metrics)) { if (metrics[k] == null && entry.metrics[k] != null) metrics[k] = entry.metrics[k]; } } entry.metrics = metrics; const el = document.getElementById('tile-' + agentId); if (el) { el.outerHTML = renderTile(entry.agent, metrics); } updateStats(); } function refresh(agents) { agents.forEach(a => { if (!_agents.has(a.id)) { _agents.set(a.id, { agent: a, metrics: a.last_metrics || null }); } else { _agents.get(a.id).agent = a; } }); const grid = document.getElementById('agents-grid'); if (!grid) return; grid.innerHTML = agents.map(a => renderTile(a, _agents.get(a.id)?.metrics)).join(''); updateStats(); } function updateStats() { let total = 0, ok = 0, warn = 0, err = 0; _agents.forEach(({ agent }) => { total++; const sc = statusClass(agent); if (agent.status === 'offline') {} else if (sc === 't-err') err++; else if (sc === 't-warn') warn++; else ok++; }); document.getElementById('stat-total').textContent = total; document.getElementById('stat-ok').textContent = ok; document.getElementById('stat-warn').textContent = warn; document.getElementById('stat-err').textContent = err; } function rerenderAll() { const grid = document.getElementById('agents-grid'); if (!grid) return; grid.innerHTML = [..._agents.values()].map(({ agent, metrics }) => renderTile(agent, metrics)).join(''); } function removeAgent(id) { _agents.delete(id); const el = document.getElementById('tile-' + id); if (el) el.remove(); updateStats(); } function getAgent(id) { return _agents.get(id); } function updateStatus(agentId, status) { const entry = _agents.get(agentId); if (!entry) return; entry.agent.status = status; const el = document.getElementById('tile-' + agentId); if (el) el.outerHTML = renderTile(entry.agent, entry.metrics); updateStats(); } return { refresh, update, updateStatus, removeAgent, rerenderAll, getAgent, fmt, fmtPct }; })();