Proxmox VMs: full resources from cluster API (both nodes), CPU/RAM/disk/uptime/network per VM

This commit is contained in:
2026-05-30 04:40:34 +00:00
parent 0ac03a6bfe
commit cbd63f1a1e
2 changed files with 119 additions and 53 deletions
+57 -23
View File
@@ -285,10 +285,9 @@ if ($action) {
// ── PROXMOX VMs ───────────────────────────────────────────────────────
case 'vms_list':
$raw = JarvisDB::single("SELECT data,UNIX_TIMESTAMP(updated_at) ts FROM api_cache WHERE cache_key='proxmox'");
if (!$raw) j(['vms'=>[],'ts'=>null]);
if (!$raw) j(['vms'=>[],'containers'=>[],'node_info'=>[],'ts'=>null]);
$pve = json_decode($raw['data'],true) ?? [];
$vms = array_merge($pve['vms']??[], $pve['containers']??[]);
j(['vms'=>$vms,'node_status'=>$pve['node_status']??null,'ts'=>$raw['ts']]);
j(['vms'=>$pve['vms']??[],'containers'=>$pve['containers']??[],'node_info'=>$pve['node_info']??[],'node_status'=>$pve['node_status']??null,'ts'=>$raw['ts']]);
// ── USERS ────────────────────────────────────────────────────────────
case 'users_list':
@@ -1207,26 +1206,61 @@ function newsCustomModal(id=0, title='', url='') {
async function loadVMs() {
document.getElementById('vms-tbl').innerHTML='<div class="loading">LOADING...</div>';
const data = await api('vms_list');
const vms = data.vms||[];
if (!vms.length) { document.getElementById('vms-tbl').innerHTML='<div class="empty">NO VM DATA (Proxmox cache may be empty)</div>'; return; }
const ns = data.node_status||{};
let rows = vms.map(v => {
const run = v.status==='running';
const cpu = v.cpu_pct!=null?Math.round(v.cpu_pct)+'%':'—';
const mem = v.mem_pct!=null?Math.round(v.mem_pct)+'%':'—';
return `<tr>
<td>${v.vmid||'—'}</td>
<td><strong>${esc(v.name||'—')}</strong></td>
<td><span class="badge badge-dim">${esc(v.type||'vm').toUpperCase()}</span></td>
<td>${run?'<span class="badge badge-green">RUNNING</span>':'<span class="badge badge-red">'+esc(v.status).toUpperCase()+'</span>'}</td>
<td>${cpu}</td><td>${mem}</td>
<td class="ts">${v.uptime_human||'—'}</td>
</tr>`;
}).join('');
document.getElementById('vms-tbl').innerHTML = `
${ns.cpu_pct!=null?`<div style="font-size:0.65rem;color:var(--dim);padding:8px 12px">PVE NODE — CPU: ${Math.round(ns.cpu_pct)}% · RAM: ${Math.round(ns.mem_pct||0)}% · UPTIME: ${ns.uptime||'—'}</div>`:''}
<table><thead><tr><th>VMID</th><th>NAME</th><th>TYPE</th><th>STATUS</th><th>CPU</th><th>MEM</th><th>UPTIME</th></tr></thead>
<tbody>${rows}</tbody></table>`;
const vms = [...(data.vms||[]), ...(data.containers||[])];
if (!vms.length) { document.getElementById('vms-tbl').innerHTML='<div class="empty">NO VM DATA Proxmox cache empty, refreshes every 5 min</div>'; return; }
const ni = data.node_info||{};
function nodeBar(info) {
if (!info) return '';
const cc = info.cpu_pct>80?'var(--red)':info.cpu_pct>60?'var(--yellow)':'var(--green)';
const mc = info.mem_pct>80?'var(--red)':info.mem_pct>60?'var(--yellow)':'var(--cyan)';
return `CPU <span style="color:${cc}">${info.cpu_pct}%</span> · `+
`RAM <span style="color:${mc}">${info.mem_used_gb}/${info.mem_total_gb}GB (${info.mem_pct}%)</span> · `+
`Disk ${info.disk_used_gb}/${info.disk_total_gb}GB · Up ${info.uptime}`;
}
function meter(pct, warn=70, crit=85) {
if (pct == null) return '<span style="color:var(--dim)">—</span>';
const c = pct>=crit?'var(--red)':pct>=warn?'var(--yellow)':'var(--green)';
return `<div style="display:flex;align-items:center;gap:5px">
<div style="width:44px;height:4px;background:var(--border);flex-shrink:0">
<div style="width:${Math.min(pct,100)}%;height:100%;background:${c}"></div>
</div>
<span style="color:${c};font-size:0.65rem">${pct}%</span>
</div>`;
}
// Group by node
const nodes = [...new Set(vms.map(v=>v.node||'pve'))].sort();
let html = '';
for (const node of nodes) {
const nodeVMs = vms.filter(v=>(v.node||'pve')===node);
const info = ni[node];
html += `<div style="font-family:var(--font-mono);font-size:0.6rem;color:var(--cyan);letter-spacing:2px;padding:10px 12px 4px;border-top:1px solid var(--border2)">`+
`${node.toUpperCase()} NODE${info?` — ${nodeBar(info)}`:''}</div>`;
html += nodeVMs.map(v => {
const run = v.status==='running';
const typeColor = v.type==='lxc'?'var(--orange)':'var(--cyan)';
const memLabel = v.mem_used_mb && v.mem_total_mb
? `${Math.round(v.mem_used_mb/1024*10)/10}/${Math.round(v.mem_total_mb/1024*10)/10}GB`
: '—';
return `<tr>
<td style="color:var(--dim)">${v.vmid}</td>
<td><strong>${esc(v.name)}</strong></td>
<td><span style="color:${typeColor};font-size:0.6rem">${(v.type||'qemu').toUpperCase()}</span></td>
<td>${run?'<span class="badge badge-green">RUNNING</span>':'<span class="badge badge-red">'+esc(v.status||'stopped').toUpperCase()+'</span>'}</td>
<td>${meter(v.cpu_pct,50,80)} <span style="font-size:0.6rem;color:var(--dim)">${v.cpus||1}vCPU</span></td>
<td>${meter(v.mem_pct)} <span style="font-size:0.6rem;color:var(--dim)">${memLabel}</span></td>
<td style="font-size:0.65rem;color:var(--dim)">${v.disk_gb||'—'}GB</td>
<td class="ts">${run?(v.uptime_human||'—'):'—'}</td>
<td style="font-size:0.6rem;color:var(--dim)">↓${v.netin_fmt||'—'} ↑${v.netout_fmt||'—'}</td>
</tr>`;
}).join('');
}
document.getElementById('vms-tbl').innerHTML =
`<table><thead><tr><th>VMID</th><th>NAME</th><th>TYPE</th><th>STATUS</th><th>CPU</th><th>RAM</th><th>DISK</th><th>UPTIME</th><th>NETWORK</th></tr></thead>
<tbody>${html}</tbody></table>`;
}
// ── AUTO-LOGIN CHECK (PHP session) ───────────────────────────────────────────