Files
nano_metrics/dashboard/js/grid.js
T
Gilles Soulier 775d54f07c feat: suppression agent, RAM en Go, métriques par défaut (cpu/mem/disk/smart)
- API DELETE /api/agents/{id} — supprime agent + métriques + config + icône
- Bouton poubelle sur chaque tuile + dialog de confirmation
- RAM : affichage "utilisé/total" en Go (ex: 6.2Go/8.0Go) au lieu du %
- Config agent par défaut : cpu, memory, disk, smart activés (UDP)
- DefaultAgentConfig() dans models pour les nouveaux agents

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 19:54:10 +02:00

166 lines
6.8 KiB
JavaScript

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 = `<img src="${API.iconUrl(id)}" alt=""
style="width:100%;height:100%;object-fit:cover;border-radius:7px"
onerror="this.style.display='none';this.nextSibling.style.display='flex'">
<span style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:var(--accent)">
<i class="fa-solid fa-server"></i></span>`;
return `<div class="tile ${sc}" id="tile-${id}" onclick="Popups.showDetail('${esc(id)}')">
<div class="tile-head">
<div class="t-icon">${iconContent}</div>
<div class="t-names">
<div class="t-host">${esc(agent.hostname)}</div>
<div class="t-ip">${esc(agent.ip) || '—'}</div>
</div>
<div class="t-led ${ledClass(agent.status)}"></div>
</div>
<div class="tile-gauges">
<div class="g-row">
<div class="g-ico" data-tip="CPU"><i class="fa-solid fa-microchip"></i></div>
<div class="g-bar"><div class="g-fill ${offline ? '' : gFill(cpu ?? 0)}"
style="width:${offline ? 0 : (cpu ?? 0).toFixed(0)}%"></div></div>
<span class="g-val">${offline ? '—' : fmtPct(cpu)}</span>
</div>
<div class="g-row">
<div class="g-ico" data-tip="RAM"><i class="fa-solid fa-memory"></i></div>
<div class="g-bar"><div class="g-fill ${offline ? '' : gFill(memPct ?? 0)}"
style="width:${offline ? 0 : (memPct ?? 0).toFixed(0)}%"></div></div>
<span class="g-val">${offline ? '—' : (metrics?.memory_used && metrics?.memory_total ? fmt(metrics.memory_used) + '/' + fmt(metrics.memory_total) : '—')}</span>
</div>
<div class="g-row">
<div class="g-ico" data-tip="Disque"><i class="fa-solid fa-hard-drive"></i></div>
<div class="g-bar"><div class="g-fill ${offline ? '' : (diskPct >= (App.serverConfig?.warn_disk ?? 75) ? 'w' : '')}"
style="width:${offline ? 0 : (diskPct ?? 0).toFixed(0)}%"></div></div>
<span class="g-val">${offline ? '—' : fmtPct(diskPct)}</span>
</div>
</div>
<div class="tile-foot">
${offline
? '<i class="fa-solid fa-circle-xmark" style="color:var(--err)"></i><span style="color:var(--err)">Hors ligne</span>'
: `<i class="fa-solid fa-clock"></i><span>${uptimeStr}</span>`}
<button class="btn-del-agent" title="Supprimer cet agent"
onclick="event.stopPropagation();Popups.confirmDeleteAgent('${esc(id)}','${esc(agent.hostname)}')">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</div>`;
}
function update(agentId, metrics) {
const entry = _agents.get(agentId);
if (!entry) return;
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: 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 };
})();