// ------ helpers ------ export const $ = (q) => document.querySelector(q); export function safe(v) { return (v === undefined || v === null || v === '') ? '—' : String(v); } export function ensureArr(a) { return Array.isArray(a) ? a : []; } export function pct(p) { if (p == null) return '—'; return (p * 100).toFixed(1) + '%'; } export function humanBytes(n) { if (n == null) return '—'; const u = ['B','KiB','MiB','GiB','TiB','PiB']; let i=0,x=+n; while (x>=1024 && i${safe(txt)}`; } export function rowHTML(cols, attrs='') { return `${cols.map(c => `${c ?? '—'}`).join('')}`; } export function setRows(tbody, rows) { tbody.innerHTML = rows.length ? rows.join('') : rowHTML(['—']); } export function fmtSeconds(s) { if (s == null) return '—'; s = Math.floor(s); const d = Math.floor(s/86400); s%=86400; const h = Math.floor(s/3600); s%=3600; const m = Math.floor(s/60); s%=60; const parts = [h.toString().padStart(2,'0'), m.toString().padStart(2,'0'), s.toString().padStart(2,'0')].join(':'); return d>0 ? `${d}d ${parts}` : parts; } export function parseNetConf(val) { const out = {}; if (!val) return out; val.split(',').forEach(kv => { const [k,v] = kv.split('='); if (k && v !== undefined) out[k.trim()] = v.trim(); }); return out; } export function parseVmNetworks(config) { const nets = []; for (const [k, v] of Object.entries(config || {})) { const m = k.match(/^net(\d+)$/); if (m) nets.push({ idx:+m[1], raw:v, ...parseNetConf(v) }); } nets.sort((a,b)=>a.idx-b.idx); return nets; } export function kvGrid(obj, keys, titleMap={}) { return `
${ keys.map(k => `
${titleMap[k] || k}
${safe(obj[k])}
`).join('')}
`; } // prefer first non-empty export function pick(...vals) { for (const v of vals) { if (v !== undefined && v !== null && v !== '') return v; } return ''; } // toast export function showToast(title, body, variant) { const cont = document.getElementById('toast-container'); if (!cont) return; const vcls = { success:'text-bg-success', info:'text-bg-info', warning:'text-bg-warning', danger:'text-bg-danger', secondary:'text-bg-secondary' }[variant||'secondary']; const el = document.createElement('div'); el.className = 'toast align-items-center ' + vcls; el.setAttribute('role','alert'); el.setAttribute('aria-live','assertive'); el.setAttribute('aria-atomic','true'); el.innerHTML = `
${title||''}${title?': ':''}${body||''}
`; cont.appendChild(el); const t = new bootstrap.Toast(el, { delay: 5000 }); t.show(); el.addEventListener('hidden.bs.toast', () => el.remove()); }