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 = `
${safe(d.node)}
${sB}
CPU: ${safe(((st.cpu??null)!==null)?(st.cpu*100).toFixed(1)+'%':'—')}
Load: ${safe(load)}
Uptime: ${fmtSeconds(st.uptime)}
`; const memCard = `
Memory
${(mem.used != null && mem.total != null) ? `${humanBytes(mem.used)} / ${humanBytes(mem.total)} (${((mem.used/mem.total)*100).toFixed(1)}%)` : '—'}
RootFS
${(root.used != null && root.total != null) ? `${humanBytes(root.used)} / ${humanBytes(root.total)} (${((root.used/root.total)*100).toFixed(1)}%)` : '—'}
Kernel / QEMU
${safe(tech['Kernel'])} / ${safe(pick(ver.qemu, ver['running-qemu']))}
Time
${safe(tm.localtime)} ${tm.timezone ? `(${tm.timezone})` : ''}
`; 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 = `
${netRows.length ? netRows.join('') : rowHTML(['—','—','—','—','—','—'])}
IFTypeMethodPorts/AddressNetmask/CIDRComment
`; 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 = `
${diskRows.length ? diskRows.join('') : rowHTML(['—','—','—','—','—'])}
DeviceModelSizeHealthSerial
`; // 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 ? '' : `
Subscription
Status: ${badge(safe(subscription.status||'unknown'), /active|valid/i.test(subscription.status||'') ? 'ok':'warn')}
${subscription.productname ? `
Product: ${safe(subscription.productname)}
` : ''} ${subscription.message ? `
${safe(subscription.message)}
` : ''}
`; const rawId = `raw-node-${safe(d.node)}`; const rawBtn = ``; const rawBox = `
${JSON.stringify(d, null, 2)}
`; return `${top}${memCard}
System details
${sysDetails}
Network (config)
${netCfgTable}
Disks
${diskTable}
${subBox} ${rawBtn}${rawBox}`; }