Fix agents render: move miniBar outside setTimeout scope, add Array.isArray guard

This commit is contained in:
2026-05-30 05:02:03 +00:00
parent d38d66d147
commit 20c91671da
+26 -31
View File
@@ -827,53 +827,49 @@ function scanShell(tblWrapId, headers, titleEl, scanLabel) {
} }
// ── AGENTS ──────────────────────────────────────────────────────────────────── // ── AGENTS ────────────────────────────────────────────────────────────────────
const miniBar = (pct, warn=70, crit=85) => {
if (pct == null) return '—';
const c = pct>=crit?'var(--red)':pct>=warn?'var(--yellow)':'var(--green)';
return `<span style="color:${c}">${Math.round(pct)}%</span>`;
};
async function loadAgents() { async function loadAgents() {
const tbl = document.getElementById('agents-tbl'); const tbl = document.getElementById('agents-tbl');
const title = document.getElementById('agents-title'); const title = document.getElementById('agents-title');
// Build empty table shell immediately — no waiting tbl.innerHTML = `<table><thead><tr>
tbl.innerHTML = `<table id="agents-table"> <th>HOSTNAME</th><th>STATUS</th><th>TYPE</th><th>IP</th><th>METRICS</th><th>LAST SEEN</th><th>REGISTERED</th><th></th>
<thead><tr><th>HOSTNAME</th><th>STATUS</th><th>TYPE</th><th>IP</th><th>METRICS</th><th>LAST SEEN</th><th>REGISTERED</th><th></th></tr></thead> </tr></thead><tbody id="agents-tbody"></tbody></table>`;
<tbody id="agents-tbody"></tbody></table>`;
title.innerHTML = 'AGENTS <span style="color:var(--dim);font-size:0.6rem;letter-spacing:2px">SCANNING...</span>'; title.innerHTML = 'AGENTS <span style="color:var(--dim);font-size:0.6rem;letter-spacing:2px">SCANNING...</span>';
const agents = await api('agents_list'); let agents;
const tbody = document.getElementById('agents-tbody'); try { agents = await api('agents_list'); }
catch(e) { tbl.innerHTML='<div class="empty">ERROR LOADING AGENTS</div>'; title.textContent='AGENTS'; return; }
if (!agents.length) { if (!Array.isArray(agents) || !agents.length) {
tbl.innerHTML = '<div class="empty">NO AGENTS REGISTERED</div>'; tbl.innerHTML = '<div class="empty">NO AGENTS REGISTERED</div>';
title.textContent = 'AGENTS'; title.textContent = 'AGENTS';
return; return;
} }
// Reveal each agent row with a staggered delay
agents.forEach((a, i) => { agents.forEach((a, i) => {
setTimeout(() => { setTimeout(() => {
const tbody = document.getElementById('agents-tbody');
if (!tbody) return;
const m = a.metrics; const m = a.metrics;
const online = a.status === 'online'; const online = a.status === 'online';
const lastSeen = a.last_seen ? (Date.now() - new Date(a.last_seen)) / 1000 : null; const lastSeen = a.last_seen ? (Date.now() - new Date(a.last_seen)) / 1000 : null;
const fresh = lastSeen !== null && lastSeen < 30; const fresh = lastSeen !== null && lastSeen < 30;
const meterCell = m
// CPU/RAM mini bars ? `<span style="font-size:0.65rem">CPU ${miniBar(m.cpu_pct)} · RAM ${miniBar(m.mem_pct)} · DISK ${miniBar(m.disk_pct,80,90)}</span>`
let meterCell = `<span style="color:var(--dim);font-size:0.65rem">no metrics</span>`; : `<span style="color:var(--dim);font-size:0.65rem">no metrics</span>`;
if (m) {
function miniBar(pct, warn=70, crit=85) {
if (pct == null) return '—';
const c = pct>=crit?'var(--red)':pct>=warn?'var(--yellow)':'var(--green)';
return `<span style="color:${c}">${Math.round(pct)}%</span>`;
}
meterCell = `<span style="font-size:0.65rem">CPU ${miniBar(m.cpu_pct)} · RAM ${miniBar(m.mem_pct)} · DISK ${miniBar(m.disk_pct,80,90)}</span>`;
}
const row = document.createElement('tr'); const row = document.createElement('tr');
row.className = 'agent-row'; row.className = 'agent-row';
row.style.animationDelay = '0s';
row.innerHTML = ` row.innerHTML = `
<td> <td><span class="dot ${online?'dot-green':'dot-red'}"></span>
<span class="dot ${online ? 'dot-green' : 'dot-red'}"></span>
<strong>${esc(a.hostname)}</strong> <strong>${esc(a.hostname)}</strong>
${fresh && online ? '<span style="font-size:0.55rem;color:var(--green);margin-left:4px">● LIVE</span>' : ''} ${fresh&&online?'<span style="font-size:0.55rem;color:var(--green);margin-left:4px">● LIVE</span>':''}
</td> </td>
<td>${statusBadge(a.status)}</td> <td>${statusBadge(a.status)}</td>
<td><span class="badge badge-cyan">${esc(a.agent_type||'linux').toUpperCase()}</span></td> <td><span class="badge badge-cyan">${esc(a.agent_type||'linux').toUpperCase()}</span></td>
@@ -882,15 +878,14 @@ async function loadAgents() {
<td class="ts">${ago(a.last_seen)}</td> <td class="ts">${ago(a.last_seen)}</td>
<td class="ts">${ts(a.created_at)}</td> <td class="ts">${ts(a.created_at)}</td>
<td><button class="btn btn-xs btn-red" onclick="delAgent('${esc(a.agent_id)}','${esc(a.hostname)}')">DEL</button></td>`; <td><button class="btn btn-xs btn-red" onclick="delAgent('${esc(a.agent_id)}','${esc(a.hostname)}')">DEL</button></td>`;
tbody?.appendChild(row); tbody.appendChild(row);
// Update title counter as agents appear
const found = i + 1; const found = i + 1;
const online_count = agents.slice(0, found).filter(x => x.status === 'online').length; const onlineCt = agents.slice(0, found).filter(x => x.status === 'online').length;
if (title) title.innerHTML = found < agents.length title.innerHTML = found < agents.length
? `AGENTS <span style="color:var(--dim);font-size:0.6rem;letter-spacing:2px">SCANNING... ${found}/${agents.length}</span>` ? `AGENTS <span style="color:var(--dim);font-size:0.6rem;letter-spacing:2px">SCANNING... ${found}/${agents.length}</span>`
: `AGENTS <span style="color:var(--cyan);font-size:0.6rem;letter-spacing:2px">${online_count} ONLINE / ${agents.length} TOTAL</span>`; : `AGENTS <span style="color:var(--cyan);font-size:0.6rem;letter-spacing:2px">${onlineCt} ONLINE / ${agents.length} TOTAL</span>`;
}, i * 120); // 120ms stagger per agent }, i * 120);
}); });
} }