Files
pve-ha-web/static/js/nodeDetail.js
Mateusz Gruszczyński 2682a0ff4f refator_comm1
2025-10-18 20:58:51 +02:00

108 lines
6.3 KiB
JavaScript

import { safe, ensureArr, badge, rowHTML, humanBytes, kvGrid, fmtSeconds, pick } from './helpers.js';
export function renderNodeDetailCard(d) {
const st = d.status || {};
const ver = d.version || {};
const tm = d.time || {};
const netcfg = ensureArr(d.network_cfg);
const disks = ensureArr(d.disks);
const subscription = d.subscription || {};
// online detect
const isOn = /online|running/i.test(st.status || '') ||
/online/i.test(st.hastate || '') || (st.uptime > 0) ||
(st.cpu != null && st.maxcpu != null) || (st.memory && st.memory.total > 0);
const statusTxt = isOn ? 'online' : (st.status || 'offline');
const sB = isOn ? badge(statusTxt, 'ok') : badge(statusTxt, 'err');
const mem = st.memory || {};
const root = st.rootfs || {};
const load = Array.isArray(st.loadavg) ? st.loadavg.join(' ') : (st.loadavg || '');
const cpuinfo = st.cpuinfo || {};
const boot = st['boot-info'] || st.boot_info || {};
const curKernel = st['current-kernel'] || st.current_kernel || {};
const ramStr = (mem.used != null && mem.available != null && mem.total != null)
? `${humanBytes(mem.used)} used / ${humanBytes(mem.available)} free / ${humanBytes(mem.total)} total`
: (mem.total != null ? humanBytes(mem.total) : '—');
const tech = {
'PVE version': pick(st.pveversion, ver.pvemanager, ver['pve-manager']),
'Kernel': pick(st.kversion, curKernel.release, ver.kernel, ver.release),
'CPU model': pick(cpuinfo.model, st['cpu-model'], ver['cpu-model'], ver.cpu),
'Architecture': pick(curKernel.machine, ver.arch, st.architecture, st.arch),
'RAM': ramStr,
'Boot mode': pick(boot.mode) ? String(boot.mode).toUpperCase() : '—',
'Secure Boot': (boot.secureboot === 1 || boot.secureboot === '1') ? 'enabled' :
(boot.secureboot === 0 || boot.secureboot === '0') ? 'disabled' : '—'
};
const top = `
<div class="d-flex flex-wrap align-items-center gap-3 mb-2">
<div class="fw-bold">${safe(d.node)}</div>
<div class="vr"></div><div>${sB}</div>
<div class="vr"></div>
<div class="small text-muted">CPU: ${safe(((st.cpu??null)!==null)?(st.cpu*100).toFixed(1)+'%':'—')}</div>
<div class="small text-muted">Load: ${safe(load)}</div>
<div class="small text-muted">Uptime: ${fmtSeconds(st.uptime)}</div>
</div>`;
const memCard = `
<div class="row row-cols-2 row-cols-md-4 g-2">
<div class="col"><div class="card border-0"><div class="card-body p-2">
<div class="text-muted small">Memory</div>
<div class="fw-semibold">${(mem.used != null && mem.total != null) ? `${humanBytes(mem.used)} / ${humanBytes(mem.total)} (${((mem.used/mem.total)*100).toFixed(1)}%)` : '—'}</div>
</div></div></div>
<div class="col"><div class="card border-0"><div class="card-body p-2">
<div class="text-muted small">RootFS</div>
<div class="fw-semibold">${(root.used != null && root.total != null) ? `${humanBytes(root.used)} / ${humanBytes(root.total)} (${((root.used/root.total)*100).toFixed(1)}%)` : '—'}</div>
</div></div></div>
<div class="col"><div class="card border-0"><div class="card-body p-2">
<div class="text-muted small">Kernel / QEMU</div>
<div class="fw-semibold">${safe(tech['Kernel'])} / ${safe(pick(ver.qemu, ver['running-qemu']))}</div>
</div></div></div>
<div class="col"><div class="card border-0"><div class="card-body p-2">
<div class="text-muted small">Time</div>
<div class="fw-semibold">${safe(tm.localtime)} ${tm.timezone ? `(${tm.timezone})` : ''}</div>
</div></div></div>
</div>`;
const sysDetails = kvGrid(tech, Object.keys(tech), {
'PVE version': 'PVE version','Kernel':'Kernel version','CPU model':'CPU model',
'Architecture':'Arch','RAM':'RAM (used/free/total)','Boot mode':'Boot mode','Secure Boot':'Secure Boot'
});
const netRows = ensureArr(netcfg).map(n => rowHTML([safe(n.iface||n.ifname), safe(n.type), safe(n.method||n.autostart), safe(n.bridge_ports||n.address||'—'), safe(n.cidr||n.netmask||'—'), safe(n.comments||'')]));
const netCfgTable = `<div class="table-responsive"><table class="table table-sm table-striped align-middle table-nowrap">
<thead><tr><th>IF</th><th>Type</th><th>Method</th><th>Ports/Address</th><th>Netmask/CIDR</th><th>Comment</th></tr></thead>
<tbody>${netRows.length ? netRows.join('') : rowHTML(['—','—','—','—','—','—'])}</tbody></table></div>`;
const diskRows = ensureArr(disks).map(dv => rowHTML([safe(dv.devpath||dv.kname||dv.dev), safe(dv.model), safe(dv.size?humanBytes(dv.size):'—'), safe(dv.health||dv.wearout||'—'), safe(dv.serial||'—')]));
const diskTable = `<div class="table-responsive"><table class="table table-sm table-striped align-middle table-nowrap">
<thead><tr><th>Device</th><th>Model</th><th>Size</th><th>Health</th><th>Serial</th></tr></thead>
<tbody>${diskRows.length ? diskRows.join('') : rowHTML(['—','—','—','—','—'])}</tbody></table></div>`;
// Subscription: ukryj, gdy notfound/No subscription key
const subTxt = (subscription.message||'') + ' ' + (subscription.status||'');
const hideSub = /notfound/i.test(subTxt) || /no subscription key/i.test(subTxt);
const subBox = hideSub ? '' : `
<div class="mt-3"><div class="fw-semibold mb-1">Subscription</div>
<div class="small">
<div>Status: ${badge(safe(subscription.status||'unknown'), /active|valid/i.test(subscription.status||'') ? 'ok':'warn')}</div>
${subscription.productname ? `<div>Product: <strong>${safe(subscription.productname)}</strong></div>` : ''}
${subscription.message ? `<div class="text-muted">${safe(subscription.message)}</div>` : ''}
</div>
</div>`;
const rawId = `raw-node-${safe(d.node)}`;
const rawBtn = `<button class="btn btn-sm btn-outline-secondary mt-3" type="button" data-bs-toggle="collapse" data-bs-target="#${rawId}">Show raw JSON</button>`;
const rawBox = `<div id="${rawId}" class="collapse mt-2"><pre class="small mb-0">${JSON.stringify(d, null, 2)}</pre></div>`;
return `${top}${memCard}
<div class="mt-3"><div class="fw-semibold mb-1">System details</div>${sysDetails}</div>
<div class="mt-3"><div class="fw-semibold mb-1">Network (config)</div>${netCfgTable}</div>
<div class="mt-3"><div class="fw-semibold mb-1">Disks</div>${diskTable}</div>
${subBox}
${rawBtn}${rawBox}`;
}