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 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 iconContent = ` `; return `
${iconContent}
${esc(agent.hostname)}
${esc(agent.ip) || '—'}
${offline ? '—' : fmtPct(cpu)}
${offline ? '—' : (metrics?.memory_used && metrics?.memory_total ? fmt(metrics.memory_used) + '/' + fmt(metrics.memory_total) : '—')}
${offline ? '—' : (metrics?.hdd_used && metrics?.hdd_total ? fmt(metrics.hdd_used) + '/' + fmt(metrics.hdd_total) : '—')}
${offline ? 'Hors ligne' : `${uptimeStr || '—'}`}
`; } function update(agentId, metrics) { const entry = _agents.get(agentId); if (!entry) return; // 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 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, getAgent, fmt, fmtPct }; })();