import { safe, ensureArr, badge, rowHTML, humanBytes, kvGrid, fmtSeconds, parseVmNetworks } from './helpers.js';
export function renderVmDetailCard(d) {
// --- dokładnie ten sam kod, który miałeś — przeniesiony bez zmian ---
// (skrócone dla czytelności — wklejam pełną wersję z Twojego pliku)
const meta = d.meta || {};
const cur = d.current || {};
const cfg = d.config || {};
const ag = d.agent || {};
const agInfo = ag.info || null;
const agOS = ag.osinfo && ag.osinfo.result ? ag.osinfo.result : null;
const agIfs = ag.ifaces && ag.ifaces.result ? ag.ifaces.result : null;
const statusBadge = /running|online|started/i.test(meta.status || cur.status || '')
? badge(meta.status || cur.status || 'running', 'ok')
: badge(meta.status || cur.status || 'stopped', 'err');
const maxmem = cur.maxmem ?? (cfg.memory ? Number(cfg.memory) * 1024 * 1024 : null);
const used = cur.mem ?? null;
const free = (maxmem != null && used != null) ? Math.max(0, maxmem - used) : null;
const balloonEnabled = (cfg.balloon !== undefined) ? (Number(cfg.balloon) !== 0) : (cur.balloon !== undefined && Number(cur.balloon) !== 0);
const binfo = cur.ballooninfo || null;
let guestName = agOS && (agOS.name || agOS.pretty_name) || (agInfo && agInfo.version) || '';
let guestIPs = [];
if (Array.isArray(agIfs)) {
agIfs.forEach(i => {
(i['ip-addresses'] || []).forEach(ip => { const a = ip['ip-address']; if (a && !a.startsWith('fe80')) guestIPs.push(a); });
});
}
const bstat = cur.blockstat || {};
const bRows = Object.keys(bstat).sort().map(dev => {
const s = bstat[dev] || {};
return rowHTML([dev, humanBytes(s.rd_bytes||0), String(s.rd_operations||0),
humanBytes(s.wr_bytes||0), String(s.wr_operations||0), String(s.flush_operations||0), humanBytes(s.wr_highest_offset||0)]);
});
const ha = cur.ha || {};
const haBadge = ha.state ? (/started/i.test(ha.state) ? badge(ha.state,'ok') : badge(ha.state,'warn')) : badge('—','dark');
const sysCards = {
'QMP status': cur.qmpstatus, 'QEMU': cur['running-qemu'], 'Machine': cur['running-machine'], 'PID': cur.pid,
'Pressure CPU (some/full)': `${String(cur.pressurecpusome||'—')}/${String(cur.pressurecpufull||'—')}`,
'Pressure IO (some/full)': `${String(cur.pressureiosome||'—')}/${String(cur.pressureiofull||'—')}`,
'Pressure MEM (some/full)': `${String(cur.pressurememorysome||'—')}/${String(cur.pressurememoryfull||'—')}`
};
const nets = parseVmNetworks(cfg);
const netRows = nets.map(n => {
const br = n.bridge || n.br || '—';
const mdl = n.model || n.type || (n.raw?.split(',')[0]?.split('=')[0]) || 'virtio';
const mac = n.hwaddr || n.mac || (n.raw?.match(/([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}/)?.[0] || '—');
const vlan = n.tag || n.vlan || '—';
const fw = (n.firewall === '1') ? badge('on','warn') : badge('off','dark');
return rowHTML([`net${n.idx}`, mdl, br, vlan, mac, fw]);
});
const netTable = `
IF | Model | Bridge | VLAN | MAC | FW |
${netRows.length ? netRows.join('') : rowHTML(['—','—','—','—','—','—'])}
`;
const agentSummary = (agInfo || agOS || guestIPs.length)
? `
${agOS ? `
Guest OS: ${safe(guestName)}
` : ''}
${guestIPs.length ? `
Guest IPs: ${guestIPs.map(ip => badge(ip,'info')).join(' ')}
` : ''}
${agInfo ? `
Agent: ${badge('present','ok')}
` : `
Agent: ${badge('not available','err')}
`}
`
: 'No guest agent data
';
const cfgFacts = {
'BIOS': cfg.bios, 'UEFI/EFI disk': cfg.efidisk0 ? 'yes' : 'no',
'CPU type': cfg.cpu, 'Sockets': cfg.sockets, 'Cores': cfg.cores, 'NUMA': cfg.numa,
'On boot': cfg.onboot ? 'yes' : 'no', 'OS type': cfg.ostype, 'SCSI hw': cfg.scsihw
};
const rawId = `raw-${d.type}-${d.vmid}`;
const rawBtn = ``;
const rawBox = `
${JSON.stringify(cur,null,2)}
${JSON.stringify(cfg,null,2)}
${JSON.stringify(d.agent||{},null,2)}
`;
return `
${safe(meta.name || cfg.name || d.sid)}
${(d.type || '').toUpperCase()} / VMID ${safe(d.vmid)} @ ${safe(d.node)}
${statusBadge}
${meta.hastate ? `
HA: ${badge(meta.hastate, /started/i.test(meta.hastate) ? 'ok' : 'warn')}
` : ''}
${ha.state ? `
HA runtime: ${haBadge}
` : ''}
CPU
${cur.cpu !== undefined ? (cur.cpu * 100).toFixed(1) + '%' : '—'}
vCPUs: ${safe(cur.cpus)}
Memory (used/free/total)
${(used != null && maxmem != null) ? `${humanBytes(used)} / ${humanBytes(free)} / ${humanBytes(maxmem)}` : '—'}
Disk (used/total)
${(cur.disk != null && cur.maxdisk != null) ? `${humanBytes(cur.disk)} / ${humanBytes(cur.maxdisk)}` : '—'}
R: ${humanBytes(cur.diskread||0)} | W: ${humanBytes(cur.diskwrite||0)}
Uptime
${fmtSeconds(cur.uptime)}
Network (config)
${netTable}
Disks (block statistics)
Device | Read bytes | Read ops | Write bytes | Write ops | Flush ops | Highest offset |
${Object.keys(bstat).length ? bRows.join('') : rowHTML(['—','—','—','—','—','—','—'])}
System / QEMU
${kvGrid(sysCards, Object.keys(sysCards))}
Config facts
${kvGrid(cfgFacts, Object.keys(cfgFacts))}
${agentSummary}
${rawBtn}${rawBox}
`;
}