775d54f07c
- 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>
166 lines
6.8 KiB
JavaScript
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 };
|
|
})();
|