(function () { 'use strict'; const $ = (selector, scope = document) => scope.querySelector(selector); const $$ = (selector, scope = document) => [...scope.querySelectorAll(selector)]; const renderCache = new WeakMap(); const pages = { overview: { label: 'Overview', title: 'Management', subtitle: 'Overview', icon: 'grid' }, vms: { label: 'Machines', title: 'Machine', subtitle: 'Collection', icon: 'server' }, network: { label: 'Network & ports', title: 'Private', subtitle: 'Connections', icon: 'network' }, system: { label: 'Host health', title: 'Host', subtitle: 'Wellbeing', icon: 'heart' }, create: { label: 'New machine', title: 'Something', subtitle: 'New', icon: 'plus' }, 'vm-detail': { label: 'Machine details', title: 'Machine', subtitle: 'Details', icon: 'server' }, }; const state = { token: '', authenticated: false, session: 0, vms: [], loaded: false, health: null, checks: null, vmError: '', healthError: '', checksError: '', page: 'overview', detailId: null, detail: null, portEditing: false, resizeEditing: false, selected: new Set(), filter: 'all', query: '', sort: 'recent', busy: new Set(), controllers: new Set(), requests: { vms: 0, health: 0, detail: 0, logs: 0 }, timer: null, refreshPromise: null, checksPromise: null, lastUpdate: 0, lastChecks: 0, lastChecksAttempt: 0, searchIndex: -1, menuTrigger: null, }; function esc(value) { return String(value ?? '').replace(/[&<>"']/g, (character) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', })[character]); } function icon(name, className = '') { return ``; } function text(selector, value) { const element = $(selector); const content = String(value ?? '—'); if (element.textContent !== content) { element.textContent = content; renderCache.delete(element); } } // Unchanged polling responses must not replace focused controls or selections. function html(selector, markup) { const element = typeof selector === 'string' ? $(selector) : selector; if (renderCache.get(element) === markup) return; element.innerHTML = markup; renderCache.set(element, markup); } function label(value) { const valueText = String(value || 'unknown').replace(/[-_]/g, ' '); return valueText.charAt(0).toUpperCase() + valueText.slice(1); } function number(value) { return new Intl.NumberFormat(undefined, { maximumFractionDigits: 2 }).format(value); } function formatRam(value) { const mib = Number(value); if (!Number.isFinite(mib)) return '—'; return mib >= 1024 ? `${number(mib / 1024)} GiB` : `${number(mib)} MiB`; } function formatStorage(value) { return value == null ? 'Not recorded' : formatRam(value); } function timestamp(value) { if (value == null || value === '') return NaN; if (typeof value === 'number' || /^\d+(\.\d+)?$/.test(value)) { const numeric = Number(value); return numeric < 1e12 ? numeric * 1000 : numeric; } return Date.parse(value); } function formatDate(value) { const time = timestamp(value); if (!Number.isFinite(time)) return '—'; return new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit', }).format(new Date(time)); } function runtimeStatus(vm) { return String(vm.observed_status || vm.status || 'unknown').toLowerCase(); } function tone(status) { if (status === 'running') return 'running'; if (status === 'stopped' || status === 'inactive') return 'stopped'; if (['ok', 'enforced', 'ready'].includes(status)) return 'good'; if (['failed', 'dead', 'error', 'unhealthy', 'missing', 'drift', 'quarantined'].includes(status)) return 'attention'; return 'pending'; } function badge(status) { return `${esc(label(status))}`; } function vcpu(vm) { const cpu = Number(vm.cpu); return Number.isFinite(cpu) && cpu > 0 ? Math.ceil(cpu) : 0; } function percent(part, total) { return total > 0 ? Math.min(100, Math.max(0, part / total * 100)) : 0; } function vmPath(identifier, suffix = '') { return `/vms/${encodeURIComponent(identifier)}${suffix}`; } function vmHref(identifier) { return `#vm/${encodeURIComponent(identifier)}`; } function machineName(vm) { return vm.name || vm.id; } function isAborted(error) { return error.name === 'AbortError'; } function apiError(data, status) { if (data?.error?.message) return data.error.message; if (typeof data?.detail === 'string') return data.detail; if (Array.isArray(data?.detail)) { return data.detail.map((item) => { const field = (item.loc || []).filter((part) => part !== 'body').join('.'); return `${field ? `${field}: ` : ''}${item.msg || item.message || 'Invalid value'}`; }).join('; '); } return `The server returned HTTP ${status}.`; } async function api(path, { method = 'GET', body } = {}) { const session = state.session; const controller = new AbortController(); state.controllers.add(controller); let timedOut = false; // Installation and lifecycle calls may legitimately take longer than reads. const timer = method === 'GET' ? window.setTimeout(() => { timedOut = true; controller.abort(); }, 20000) : null; try { const response = await fetch(path, { method, headers: { Accept: 'application/json', 'X-UVM-Token': state.token, ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}), }, ...(body !== undefined ? { body: JSON.stringify(body) } : {}), cache: 'no-store', signal: controller.signal, }); const raw = await response.text(); if (session !== state.session) throw new DOMException('Session changed', 'AbortError'); let data = null; try { data = raw ? JSON.parse(raw) : null; } catch { if (response.ok) throw new Error('The server returned an unreadable response.'); } if (!response.ok) { const error = new Error(apiError(data, response.status)); error.status = response.status; if (response.status === 401 && state.authenticated) { logout('Your token was not accepted. Connect again to continue.'); } throw error; } return data; } catch (error) { if (timedOut) throw new Error('The request timed out. Try again in a moment.'); if (error instanceof TypeError) throw new Error('Unable to reach the control plane. Check your connection.'); throw error; } finally { window.clearTimeout(timer); state.controllers.delete(controller); } } function requireList(value) { if (!Array.isArray(value)) throw new Error('The server returned an unexpected response.'); return value; } function toast(message, error = false) { const item = document.createElement('div'); item.className = `toast${error ? ' toast-error' : ''}`; item.innerHTML = icon(error ? 'info' : 'check'); const copy = document.createElement('span'); copy.textContent = message; item.append(copy); const container = $('#toast-container'); while (container.children.length >= 3) container.firstElementChild.remove(); container.append(item); window.setTimeout(() => item.remove(), error ? 6500 : 3800); } function lockForm(form) { const controls = $$('input, button, select', form).map((element) => [element, element.disabled]); controls.forEach(([element]) => { element.disabled = true; }); return () => controls.forEach(([element, disabled]) => { element.disabled = disabled; }); } async function withButton(button, pendingLabel, operation) { if (button.disabled) return; const previous = button.innerHTML; button.disabled = true; if (pendingLabel) button.textContent = pendingLabel; try { return await operation(); } finally { button.disabled = false; button.innerHTML = previous; } } function fleetStats() { const running = state.vms.filter((vm) => runtimeStatus(vm) === 'running'); const stopped = state.vms.filter((vm) => runtimeStatus(vm) === 'stopped'); const sum = (vms, value) => vms.reduce((total, vm) => total + value(vm), 0); return { total: state.vms.length, running: running.length, stopped: stopped.length, other: state.vms.length - running.length - stopped.length, cpu: sum(state.vms, vcpu), activeCpu: sum(running, vcpu), ram: sum(state.vms, (vm) => Number(vm.ram_mib) || 0), activeRam: sum(running, (vm) => Number(vm.ram_mib) || 0), gateways: [...new Set(state.vms.map((vm) => vm.gateway).filter(Boolean))], managed: state.vms.filter((vm) => Array.isArray(vm.ports)).length, enforced: state.vms.filter((vm) => vm.policy_status === 'enforced').length, ports: sum(state.vms, (vm) => (vm.ports || []).length), }; } function compactMetric(symbol, value, caption, word = false) { return `
${icon(symbol)}
${esc(value)}${esc(caption)}
`; } function emptyState(title, description, buttonLabel, target, isAction = false) { const control = isAction ? `` : `${icon('plus')}${esc(buttonLabel)}`; return `
${icon('server')}

${esc(title)}

${esc(description)}

${control}
`; } function portsMarkup(ports, limit = Infinity) { if (ports === null) return 'Legacy policy'; if (!ports?.length) return 'No open ports'; return ports.slice(0, limit).map((rule) => `${esc(rule.port)}${esc(rule.protocol)}`).join('') + (ports.length > limit ? `+${ports.length - limit}` : ''); } function renderConnection() { const connected = state.loaded && !state.vmError; $('#connection-dot').className = `dot ${connected ? 'dot-sage' : state.vmError ? 'dot-error' : ''}`; $('#footer-dot').className = `dot ${connected ? 'dot-sage' : 'dot-error'}`; text('#connection-label', connected ? 'Connected' : state.vmError ? 'Reconnecting' : 'Connecting'); $('#connection-banner').hidden = !state.vmError; text('#connection-message', state.vmError ? `${state.vmError}${state.loaded ? ' Showing the last available snapshot.' : ''}` : ''); text('#last-updated', state.lastUpdate ? `Updated ${new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit' }).format(state.lastUpdate)}` : 'Waiting for an update'); } function renderChrome() { const stats = fleetStats(); text('#nav-vm-count', state.loaded ? stats.total : '—'); text('#rail-machines', state.loaded ? `${stats.total} ${stats.total === 1 ? 'machine' : 'machines'} in your fleet` : 'Your private fleet'); text('#rail-network', state.loaded ? `${stats.ports} private port ${stats.ports === 1 ? 'rule' : 'rules'}` : 'Connections, considered'); const failed = (state.checks || []).filter((check) => !check.ok).length; const checksKnown = state.checks !== null; text('#rail-health', checksKnown ? `${state.checks.length - failed} of ${state.checks.length} checks ready` : state.checksError ? 'Checks unavailable' : 'Checking the essentials'); $('#nav-health-dot').style.background = checksKnown ? (failed ? 'var(--peach)' : 'var(--sage)') : 'transparent'; if (state.checksError) { text('#insight-copy', 'Host insights are taking a little longer. Open host health to try again.'); } else if (checksKnown && failed) { html('#insight-copy', `A little care goes a long way. ${failed} host ${failed === 1 ? 'check needs' : 'checks need'} your attention.`); } else if (checksKnown && state.checks.length) { html('#insight-copy', `The essentials are in place. All ${state.checks.length} host checks are ready.`); } else if (checksKnown) { text('#insight-copy', 'Your host returned no readiness checks. Open host health for the details.'); } else { text('#insight-copy', 'A fresh perspective on your local compute. Checking the little details.'); } text('#insight-footer', checksKnown ? 'From your latest host checks' : 'A little clarity, on its way'); renderConnection(); } function renderOverview() { const stats = fleetStats(); text('#metric-total', state.loaded ? number(stats.total).padStart(2, '0') : '—'); text('#metric-running', state.loaded ? `${stats.running} running now` : 'Waiting for the fleet'); text('#metric-stopped', state.loaded ? `${stats.stopped} at rest` : '—'); text('#metric-cpu', state.loaded ? `${number(stats.cpu)} vCPU` : '— vCPU'); text('#metric-ram', state.loaded ? formatRam(stats.ram) : '— MiB'); text('#cpu-active-label', state.loaded ? `${number(stats.activeCpu)} active / ${number(stats.cpu)}` : 'Allocated'); text('#ram-active-label', state.loaded ? `${Math.round(percent(stats.activeRam, stats.ram))}% active` : 'Allocated'); $('#cpu-allocation-bar').style.width = `${percent(stats.activeCpu, stats.cpu)}%`; $('#ram-allocation-bar').style.width = `${percent(stats.activeRam, stats.ram)}%`; if (!state.loaded) { html('#overview-machines', state.vmError ? emptyState('A little trouble connecting.', 'Your machines will appear as soon as the registry is reachable.', 'Try again', 'refresh', true) : '
Bringing your machines into view…
'); } else if (!stats.total) { html('#overview-machines', emptyState('Your first machine starts here.', 'A private little space for whatever you have in mind.', 'Create a machine', '#create')); } else { const recent = [...state.vms].sort((a, b) => (timestamp(b.created_at) || 0) - (timestamp(a.created_at) || 0)).slice(0, 5); html('#overview-machines', `
${recent.map((vm) => ` `).join('')}
MachineRuntime stateGuest addressAllocationPort policyDetails
${icon('server')}${esc(machineName(vm))}
${badge(runtimeStatus(vm))}${esc(vm.guest_ip || '—')}${vcpu(vm)} vCPU·${esc(formatRam(vm.ram_mib))}${badge(vm.policy_status || 'unknown')}${icon('chevron')}
`); } const runningPercent = Math.round(percent(stats.running, stats.total)); html('#ring-percent', `${state.loaded ? runningPercent : '—'}%`); text('#ring-total', state.loaded ? `${stats.running} of ${stats.total} machines` : 'Your fleet, in balance'); text('#legend-running', state.loaded ? stats.running : '—'); text('#legend-stopped', state.loaded ? stats.stopped : '—'); text('#legend-other', state.loaded ? stats.other : '—'); $('#fleet-ring').setAttribute('aria-label', state.loaded ? `${stats.running} running, ${stats.stopped} stopped, and ${stats.other} other machines. ${runningPercent}% running.` : 'Waiting for machine activity'); let offset = 0; [['running', stats.running], ['stopped', stats.stopped], ['other', stats.other]].forEach(([name, count]) => { const share = percent(count, stats.total); const arc = share === 100 ? 100 : Math.max(0, share - 1.5); const element = $(`#ring-${name}`); element.style.strokeDasharray = `${arc} ${100 - arc}`; element.style.strokeDashoffset = String(-offset); element.style.opacity = count && arc ? '1' : '0'; offset += share; }); renderNotes(stats); } function renderNotes(stats = fleetStats()) { const checks = state.checks; const failed = (checks || []).filter((check) => !check.ok).length; const rows = [ ['system', 'heart', 'Host essentials', checks ? failed ? `${failed} ${failed === 1 ? 'check could' : 'checks could'} use a look` : checks.length ? 'Everything is in its place' : 'No checks returned' : state.checksError ? 'Host checks unavailable' : 'Checking the foundations', checks ? `${checks.length - failed}/${checks.length}` : '—'], ['network', 'network', 'Your private network', stats.gateways.length ? stats.gateways.join(' · ') : 'A gateway appears with your first machine', stats.gateways.length ? `${stats.gateways.length} ${stats.gateways.length === 1 ? 'gateway' : 'gateways'}` : '—'], ['network', 'shield', 'Considered connections', stats.managed ? `${stats.managed} managed ${stats.managed === 1 ? 'policy' : 'policies'} in your fleet` : 'A private policy for each new machine', state.loaded ? `${stats.ports} ${stats.ports === 1 ? 'rule' : 'rules'}` : '—'], ]; html('#workspace-notes', rows.map(([route, symbol, title, detail, value]) => `${icon(symbol)}
${esc(title)}${esc(detail)}
${esc(value)}${icon('chevron')}
`).join('')); } function visibleVms() { const query = state.query.trim().toLowerCase(); return state.vms.filter((vm) => { const status = runtimeStatus(vm); const matchesStatus = state.filter === 'all' || (state.filter === 'attention' ? !['running', 'stopped'].includes(status) || tone(vm.policy_status) === 'attention' : status === state.filter); const matchesQuery = !query || [vm.name, vm.id, vm.guest_ip].some((value) => String(value || '').toLowerCase().includes(query)); return matchesStatus && matchesQuery; }).sort((a, b) => { if (state.sort === 'name') return machineName(a).localeCompare(machineName(b)); if (state.sort === 'status') return runtimeStatus(a).localeCompare(runtimeStatus(b)); return (timestamp(b.created_at) || 0) - (timestamp(a.created_at) || 0); }); } function renderMachines() { const stats = fleetStats(); html('#fleet-summary', compactMetric('server', state.loaded ? stats.total : '—', 'Machines in your collection') + compactMetric('play', state.loaded ? stats.running : '—', 'Running, right now') + compactMetric('stop', state.loaded ? stats.stopped : '—', 'At rest, disks retained')); const machines = visibleVms(); html('#machine-rows', machines.map((vm) => { const id = esc(vm.id); const running = runtimeStatus(vm) === 'running'; const busy = state.busy.has(vm.id) || ['starting', 'stopping', 'terminating'].includes(vm.status); return `
${icon('server')}
${esc(machineName(vm))}${esc(vm.guest_ip || vm.id)}
${badge(runtimeStatus(vm))}${vcpu(vm)} vCPU${esc(formatRam(vm.ram_mib))} RAM${esc(formatStorage(vm.storage_mib))} storage
${portsMarkup(vm.ports, 2)}
${badge(vm.policy_status || 'unknown')}
`; }).join('')); $('#machines-empty').hidden = machines.length > 0; if (!machines.length) { html('#machines-empty', !state.loaded ? state.vmError ? emptyState('The registry is out of reach.', state.vmError, 'Try again', 'refresh', true) : '
Bringing your machines into view…
' : stats.total ? emptyState('Nothing here by that name.', 'Try another search or give your filters a fresh start.', 'Clear filters', 'clear-filters', true) : emptyState('A little quiet. A lot of potential.', 'Create your first private machine and give your next idea a home.', 'New machine', '#create')); } text('#visible-count', state.loaded ? `${machines.length} of ${stats.total}` : 'Loading'); text('#fleet-list-caption', state.loaded ? `${stats.total} ${stats.total === 1 ? 'machine' : 'machines'} · ${stats.running} running · ${stats.stopped} stopped` : 'Your private machine registry.'); const selectedVisible = machines.filter((vm) => state.selected.has(vm.id)).length; const selectAll = $('#vm-select-all'); selectAll.disabled = !machines.length; selectAll.checked = machines.length > 0 && selectedVisible === machines.length; selectAll.indeterminate = selectedVisible > 0 && selectedVisible < machines.length; $('#bulk-controls').hidden = !state.selected.size; text('#bulk-count', `${state.selected.size} selected`); $$('.filter-tab').forEach((button) => { const active = button.dataset.filter === state.filter; button.classList.toggle('active', active); button.setAttribute('aria-pressed', String(active)); }); } function renderNetwork() { const stats = fleetStats(); html('#network-summary', compactMetric('network', state.loaded ? stats.gateways.length : '—', 'Private gateways') + compactMetric('shield', state.loaded ? stats.managed : '—', 'Managed port policies') + compactMetric('grid', state.loaded ? stats.ports : '—', 'Configured TCP rules')); text('#network-gateway', stats.gateways.join(' · ') || (state.loaded ? 'No gateway yet' : 'Loading')); text('#network-machine-count', state.loaded ? `${stats.total} private ${stats.total === 1 ? 'machine' : 'machines'}` : 'Waiting for your fleet'); html('#network-policies', state.vms.length ? state.vms.map((vm) => `
${esc(machineName(vm))}${icon('chevron')}
${esc(vm.guest_ip || 'Address pending')}${badge(vm.policy_status || 'unknown')}
${portsMarkup(vm.ports)}
`).join('') : !state.loaded ? state.vmError ? emptyState('Connections are out of view.', state.vmError, 'Try again', 'refresh', true) : '
Looking at your connections…
' : emptyState('Good connections take a first step.', 'Each new machine comes with a private address and an explicit port policy.', 'Create a machine', '#create')); } function renderHost() { const checks = state.checks; const ready = (checks || []).filter((check) => check.ok).length; const failed = (checks || []).length - ready; text('#checks-ready', checks ? ready : '—'); text('#checks-attention', checks ? failed : '—'); text('#checks-total', checks ? checks.length : '—'); $('#checks-ready-bar').style.width = `${percent(ready, checks?.length)}%`; $('#checks-attention-bar').style.width = `${percent(failed, checks?.length)}%`; text('#checks-summary', checks ? `${ready} / ${checks.length} ready` : state.checksError ? 'Unavailable' : 'Taking a look'); const healthy = state.health?.status === 'ok'; html('#health-summary', compactMetric('heart', state.health ? healthy ? 'Available' : label(state.health.status) : state.healthError ? 'Unavailable' : 'Checking', 'Management API', true) + compactMetric('check', checks ? `${ready} / ${checks.length}` : '—', 'Host checks ready') + compactMetric('info', checks ? failed : '—', 'Checks needing attention')); text('#diagnostics-count', checks ? `${ready} / ${checks.length} ready` : state.checksError ? 'Unavailable' : 'Checking'); text('#info-api-url', location.origin); text('#info-observed', state.lastChecks ? formatDate(state.lastChecks) : 'Not checked yet'); const names = { root: 'Host permissions', kvm: 'KVM virtualization', Firecracker: 'Firecracker runtime', 'guest assets': 'Guest image assets', 'private firewall': 'Private port filtering', ip: 'IP networking', iptables: 'Egress routing', nft: 'nftables', conntrack: 'Connection tracking', debugfs: 'Guest disk tools', openssl: 'OpenSSL', 'ssh-keygen': 'SSH key tools', ssh: 'SSH client', }; html('#diagnostics-list', state.checksError ? emptyState('A check-in is taking a little longer.', state.checksError, 'Try again', 'diagnostics', true) : checks ? checks.length ? checks.map((check) => `
${icon(check.ok ? 'check' : 'info')}

${esc(names[check.name] || label(check.name))}${check.ok ? 'Ready' : 'Attention'}

${esc(check.message)}

`).join('') : '

The host returned no diagnostic checks.

' : '
Checking in with your host…
'); $('#diagnostics-refresh-btn').disabled = Boolean(state.checksPromise); renderChrome(); renderNotes(); } function renderFleet() { renderChrome(); renderOverview(); renderMachines(); renderNetwork(); if (!$('#search-results').hidden) renderSearch(); } async function loadVms() { const request = ++state.requests.vms; const session = state.session; try { const vms = requireList(await api('/vms')); if (request !== state.requests.vms || session !== state.session) return false; state.vms = vms; state.loaded = true; state.vmError = ''; state.lastUpdate = Date.now(); const existing = new Set(vms.map((vm) => vm.id)); state.selected = new Set([...state.selected].filter((id) => existing.has(id))); renderFleet(); if (state.page === 'vm-detail' && state.detail) { const vm = vms.find((item) => item.id === state.detail.id); if (vm) { state.detail = vm; renderDetail(); } else { await loadDetail(state.detail.id); } } return true; } catch (error) { if (isAborted(error) || request !== state.requests.vms || session !== state.session) return false; state.vmError = error.message; renderConnection(); if (!state.loaded) renderFleet(); return false; } } async function loadHealth() { const request = ++state.requests.health; const session = state.session; try { const health = await api('/health'); if (request !== state.requests.health || session !== state.session) return false; if (!health || typeof health.status !== 'string') throw new Error('The health response was not recognized.'); state.health = health; state.healthError = ''; renderHost(); return true; } catch (error) { if (isAborted(error) || request !== state.requests.health || session !== state.session) return false; state.health = null; state.healthError = error.message; renderHost(); return false; } } function loadDiagnostics() { if (state.checksPromise) return state.checksPromise; const session = state.session; state.lastChecksAttempt = Date.now(); const operation = (async () => { try { const checks = requireList(await api('/diagnostics')); if (session !== state.session) return false; state.checks = checks; state.checksError = ''; state.lastChecks = Date.now(); return true; } catch (error) { if (isAborted(error) || session !== state.session) return false; state.checks = null; state.checksError = error.message; return false; } finally { if (state.checksPromise === operation) state.checksPromise = null; if (session === state.session) renderHost(); } })(); state.checksPromise = operation; $('#diagnostics-refresh-btn').disabled = true; return operation; } function refreshData(forceChecks = false) { if (!state.authenticated) return Promise.resolve(false); if (state.refreshPromise) return state.refreshPromise; const session = state.session; $('#refresh-btn').classList.add('is-loading'); const loads = [loadVms(), loadHealth()]; if (forceChecks || Date.now() - state.lastChecksAttempt > 30000) loads.push(loadDiagnostics()); const operation = Promise.all(loads).then((results) => results.every(Boolean)).finally(() => { if (state.refreshPromise === operation) state.refreshPromise = null; if (session === state.session) $('#refresh-btn').classList.remove('is-loading'); }); state.refreshPromise = operation; return operation; } async function manualRefresh() { const session = state.session; const success = await refreshData(true); if (session === state.session && state.authenticated) toast(success ? 'A fresh look at your workspace.' : 'Some workspace data could not be refreshed.', !success); } function detailRows(rows) { return rows.map(([key, value, mono]) => `
${esc(key)}
${esc(value ?? '—')}
`).join(''); } function renderDetail() { const vm = state.detail; if (!vm || state.page !== 'vm-detail') return; $('#detail-loading').hidden = true; $('#detail-content').hidden = false; text('#detail-heading', machineName(vm)); text('#detail-id', vm.id); html('#detail-status', badge(runtimeStatus(vm))); document.title = `${machineName(vm)} · UVM Control`; const busy = state.busy.has(vm.id); const running = runtimeStatus(vm) === 'running'; const actionButton = (action, caption, symbol, primary = false) => ``; html('#detail-actions', running ? actionButton('restart', 'Restart', 'refresh') + actionButton('stop', 'Stop machine', 'stop') : actionButton('start', 'Start machine', 'play', true)); html('#detail-config', detailRows([ ['Recorded state', label(vm.status)], ['Observed state', label(runtimeStatus(vm))], ['vCPUs', vcpu(vm)], ['Memory', formatRam(vm.ram_mib)], ['Storage', formatStorage(vm.storage_mib)], ['Guest user', vm.username], ['Created', formatDate(vm.created_at)], ['Last updated', formatDate(vm.updated_at)], ])); html('#detail-network', detailRows([ ['Guest address', vm.guest_ip, true], ['Gateway', vm.gateway, true], ['MAC address', vm.mac, true], ['Policy state', label(vm.policy_status)], ['Policy type', vm.ports === null ? 'Legacy / unmanaged' : 'Managed TCP'], ['Process ID', vm.pid ?? 'Not running'], ['Current operation', vm.operation ? label(vm.operation) : 'None'], ])); text('#detail-error', vm.last_error || ''); $('#detail-error').hidden = !vm.last_error; text('#ssh-command', `ssh ${vm.username || 'root'}@${vm.guest_ip || '—'}`); text('#ssh-hint', vm.ports === null ? 'SSH reachability is not managed for this legacy policy.' : !(vm.ports || []).some((rule) => Number(rule.port) === 22) ? 'TCP port 22 is not allowed by this policy.' : !running ? 'Start this machine before connecting.' : 'Ready to copy into your local terminal.'); if (!state.portEditing) { html('#detail-ports', vm.ports === null ? `

${vm.status === 'stopped' ? 'This legacy machine is ready for a managed policy. Set its first rules to continue.' : 'This machine has a legacy policy. Stop it before defining its first managed rules.'}

` : portsMarkup(vm.ports)); $('#port-input').value = (vm.ports || []).map((rule) => `${rule.port}:${rule.protocol}`).join(', '); } $('#detail-ports').hidden = state.portEditing; $('#port-editor').hidden = !state.portEditing; $('#edit-ports-btn').hidden = state.portEditing || (vm.ports === null && vm.status !== 'stopped'); $('#sync-ports-btn').hidden = vm.ports === null; $('#sync-ports-btn').disabled = busy; $('#open-ports-btn').hidden = vm.ports === null; $('#close-ports-btn').hidden = vm.ports === null; if (!state.resizeEditing) { const resources = vm.pending_resize || vm; $('#resize-cpu').value = resources.cpu; $('#resize-ram').value = `${resources.ram_mib}M`; $('#resize-storage').value = resources.storage_mib == null ? '' : `${resources.storage_mib / 1024}G`; } $('#resize-form').hidden = !state.resizeEditing; $('#edit-resources-btn').hidden = state.resizeEditing; $('#edit-resources-btn').disabled = busy || runtimeStatus(vm) === 'unknown' || vm.ports === null || vm.operation === 'delete' || vm.status === 'terminating'; $('#resize-submit').disabled = busy; text('#resize-note', vm.pending_resize ? `Pending resize: ${number(vm.pending_resize.cpu)} CPU, ${formatRam(vm.pending_resize.ram_mib)} memory, ${formatStorage(vm.pending_resize.storage_mib)} storage. Retry the resize or start the stopped machine to finish it.` : 'Resource changes stop the machine, apply the new size, and start it again with its existing IP and guest credentials.'); } async function loadDetail(identifier) { const request = ++state.requests.detail; const session = state.session; try { const vm = await api(vmPath(identifier)); if (request !== state.requests.detail || session !== state.session || state.page !== 'vm-detail') return; if (!vm?.id) throw new Error('The machine response was not recognized.'); state.detail = vm; state.detailId = vm.id; renderDetail(); await loadLogs(); } catch (error) { if (isAborted(error) || request !== state.requests.detail || session !== state.session || state.page !== 'vm-detail') return; if (state.detail && error.status !== 404) { text('#detail-error', error.message); $('#detail-error').hidden = false; } else { state.detail = null; $('#detail-content').hidden = true; $('#detail-loading').hidden = false; html('#detail-loading', emptyState('This machine is out of view.', error.message, 'Back to machines', '#vms')); } } } async function loadLogs() { if (!state.detail) return; const id = state.detail.id; const request = ++state.requests.logs; const session = state.session; const tail = Number($('#log-tail').value); $('#log-refresh-btn').disabled = true; text('#log-meta', 'Bringing the latest entries into view…'); try { const logs = await api(vmPath(id, `/logs?tail=${tail}`)); if (request !== state.requests.logs || session !== state.session || state.page !== 'vm-detail' || state.detailId !== id) return; text('#log-output', logs?.content || 'A quiet log. No entries yet.'); text('#log-meta', logs?.truncated ? 'The available tail is shown. A size limit or oversized line truncated the log.' : `Up to ${number(tail)} recent lines. Refresh to see new entries.`); } catch (error) { if (isAborted(error) || request !== state.requests.logs || session !== state.session || state.page !== 'vm-detail' || state.detailId !== id) return; text('#log-output', 'The runtime log is unavailable.'); text('#log-meta', error.message); } finally { if (request === state.requests.logs && session === state.session) $('#log-refresh-btn').disabled = false; } } function confirmAction(title, description, actionLabel, destructive = false) { const dialog = $('#confirm-dialog'); if (dialog.open) return Promise.resolve(false); text('#confirm-title', title); text('#confirm-body', description); text('#confirm-ok', actionLabel); $('#confirm-ok').className = `button ${destructive ? 'button-danger' : 'button-primary'}`; dialog.returnValue = 'cancel'; return new Promise((resolve) => { dialog.addEventListener('close', () => resolve(dialog.returnValue === 'confirm'), { once: true }); dialog.showModal(); }); } async function refreshAfterMutation(id) { await loadVms(); if (state.page === 'vm-detail' && state.detailId === id) await loadDetail(id); } async function machineAction(action, id) { if (!state.authenticated || state.busy.has(id)) return; const session = state.session; const vm = state.vms.find((item) => item.id === id) || (state.detail?.id === id ? state.detail : null); const name = vm ? machineName(vm) : id; const confirmations = { stop: [`Let ${name} take a breather?`, 'The runtime will stop. Its private disk, guest credentials, and network identity are kept for the next start.', 'Stop machine'], restart: [`A fresh start for ${name}?`, 'The runtime will stop and boot again from its existing private disk. Changes on the disk are retained.', 'Restart machine'], delete: [`Say goodbye to ${name}?`, 'This permanently removes the machine and its private disk, and releases its network resources.', 'Delete machine'], }; if (confirmations[action] && !await confirmAction(...confirmations[action], action === 'delete')) return; if (session !== state.session || !state.authenticated) return; state.busy.add(id); renderMachines(); renderDetail(); try { const suffix = action === 'ports-sync' ? '/ports/sync' : action === 'delete' ? '' : `/${action}`; await api(vmPath(id, suffix), { method: action === 'delete' ? 'DELETE' : 'POST' }); if (session !== state.session) return; if (action === 'delete' && state.detailId === id) { state.detail = null; state.detailId = null; location.hash = '#vms'; } const messages = { start: 'Machine started.', stop: 'Machine stopped. Its disk is tucked away.', restart: 'Machine restarted.', delete: 'Machine deleted.', 'ports-sync': 'Port policy synced.' }; toast(messages[action]); await refreshAfterMutation(id); } catch (error) { if (!isAborted(error) && session === state.session) toast(error.message, true); } finally { if (session === state.session) { state.busy.delete(id); renderMachines(); renderDetail(); } } } function parsePorts(value) { if (!value.trim()) return []; const rules = new Map(); value.split(',').forEach((part) => { const match = part.trim().match(/^(\d+)(?:\s*:\s*(TCP|HTTP|HTTPS))?$/i); const port = match ? Number(match[1]) : 0; if (!match || !Number.isInteger(port) || port < 1 || port > 65535) { throw new Error(`“${part.trim()}” is not a valid port rule. Use a port from 1 to 65535, followed by TCP, HTTP, or HTTPS.`); } const protocol = (match[2] || 'TCP').toUpperCase(); if (rules.has(port) && rules.get(port).protocol !== protocol) throw new Error(`Port ${port} has conflicting protocol labels.`); rules.set(port, { port, protocol }); }); return [...rules.values()].sort((a, b) => a.port - b.port); } function parseRam(value) { const match = value.trim().match(/^(\d+(?:\.\d+)?)\s*([BKMG]i?B?)?$/i); if (!match) throw new Error('Use a memory amount such as 512M, 1G, or 2G.'); const factors = { b: 1 / (1024 * 1024), k: 1 / 1024, kb: 1 / 1024, ki: 1 / 1024, kib: 1 / 1024, m: 1, mb: 1, mi: 1, mib: 1, g: 1024, gb: 1024, gi: 1024, gib: 1024 }; const mib = Math.floor(Number(match[1]) * factors[(match[2] || 'm').toLowerCase()]); if (!Number.isFinite(mib) || mib < 128) throw new Error('Give this machine at least 128 MiB of memory.'); return mib; } function parseStorage(value) { const match = value.trim().match(/^(\d+(?:\.\d+)?)\s*([BKMGT]i?B?)?$/i); if (!match) throw new Error('Use a storage amount such as 30G, 50G, or 1T.'); const factors = { b: 1 / (1024 * 1024), k: 1 / 1024, kb: 1 / 1024, ki: 1 / 1024, kib: 1 / 1024, m: 1, mb: 1, mi: 1, mib: 1, g: 1024, gb: 1024, gi: 1024, gib: 1024, t: 1024 * 1024, tb: 1024 * 1024, ti: 1024 * 1024, tib: 1024 * 1024 }; const amount = Number(match[1]) * factors[(match[2] || 'g').toLowerCase()]; if (!Number.isFinite(amount) || amount < 30 * 1024) throw new Error('Give this machine at least 30 GiB of storage.'); if (amount > 8796093022207) throw new Error('That storage capacity exceeds the supported limit.'); return Math.ceil(amount); } async function submitResize(event) { event.preventDefault(); if (!state.detail || state.busy.has(state.detail.id)) return; const id = state.detail.id; const session = state.session; const payload = {}; try { const cpu = $('#resize-cpu').value.trim(); const ram = $('#resize-ram').value.trim(); const storage = $('#resize-storage').value.trim(); if (cpu) { payload.cpu = Number(cpu); if (!Number.isFinite(payload.cpu) || payload.cpu <= 0) throw new Error('CPU capacity must be a positive number.'); } if (ram) { parseRam(ram); payload.ram = ram; } if (storage) { parseStorage(storage); payload.storage = storage; } if (!Object.keys(payload).length) throw new Error('Enter at least one resource to resize.'); } catch (error) { text('#resize-error', error.message); return; } text('#resize-error', ''); const button = $('#resize-submit'); const previous = button.textContent; const unlock = lockForm($('#resize-form')); state.busy.add(id); button.textContent = 'Resizing and restarting…'; renderDetail(); try { await api(vmPath(id, '/resize'), { method: 'POST', body: payload }); if (session !== state.session) return; if (state.detailId === id) state.resizeEditing = false; toast('Machine resized and started.'); await refreshAfterMutation(id); } catch (error) { if (!isAborted(error) && session === state.session) { if (state.detailId === id) text('#resize-error', error.message); else toast(error.message, true); await refreshAfterMutation(id); } } finally { unlock(); button.textContent = previous; if (session === state.session) { state.busy.delete(id); renderMachines(); renderDetail(); } } } async function submitPorts(event) { event.preventDefault(); if (!state.detail || state.busy.has(state.detail.id)) return; const id = state.detail.id; const session = state.session; const mode = event.submitter?.value || 'set'; let ports; try { ports = parsePorts($('#port-input').value); } catch (error) { text('#port-error', error.message); return; } text('#port-error', ''); const unlock = lockForm($('#port-editor')); state.busy.add(id); try { await api(vmPath(id, '/ports'), { method: 'POST', body: { mode, ports } }); if (session !== state.session) return; state.portEditing = false; toast({ set: 'Port policy updated.', open: 'Port rules opened.', close: 'Port rules closed.' }[mode]); await refreshAfterMutation(id); } catch (error) { if (!isAborted(error) && session === state.session) text('#port-error', error.message); } finally { unlock(); if (session === state.session) { state.busy.delete(id); renderDetail(); } } } async function copyText(value) { try { try { if (!navigator.clipboard) throw new Error('Clipboard unavailable'); await navigator.clipboard.writeText(value); } catch { const previous = document.activeElement; const field = document.createElement('textarea'); field.value = value; field.style.cssText = 'position:fixed;left:-9999px;top:0;opacity:0'; document.body.append(field); field.select(); const copied = document.execCommand('copy'); field.remove(); previous?.focus({ preventScroll: true }); if (!copied) throw new Error('Copying is unavailable. Select the command and copy it manually.'); } toast('SSH command copied.'); } catch (error) { toast(error.message, true); } } function updateCreateSummary() { let memory = '—'; let storage = '—'; let ports = 'Check port rules'; try { memory = formatRam(parseRam($('#create-ram').value)); } catch { /* The form reports validation on submit. */ } try { storage = formatStorage(parseStorage($('#create-storage').value)); } catch { /* Report invalid capacity on submit. */ } try { const rules = parsePorts($('#create-ports').value); if ($('#create-default-ssh').checked && !rules.some((rule) => rule.port === 22)) rules.unshift({ port: 22, protocol: 'TCP' }); ports = rules.length ? rules.map((rule) => `${rule.port} / ${rule.protocol}`).join(', ') : 'No inbound TCP ports'; } catch { /* Keep the summary useful while a rule is being typed. */ } const cpu = Number($('#create-cpu').value); text('#create-summary-name', $('#create-name').value.trim() || 'Room for an idea.'); html('#create-summary-details', detailRows([ ['Compute', Number.isFinite(cpu) && cpu > 0 ? `${Math.ceil(cpu)} vCPU` : '—'], ['Memory', memory], ['Storage', storage], ['Guest IP', $('#create-guest-ip').value.trim() || 'Automatic'], ['Guest user', $('#create-username').value.trim() || '—'], ['Allowed ports', ports], ])); const presets = { small: [1, 512], balanced: [2, 1024], roomy: [4, 2048] }; let mib = null; try { mib = parseRam($('#create-ram').value); } catch { /* No preset matches an incomplete value. */ } $$('[data-preset]').forEach((button) => { const [presetCpu, presetRam] = presets[button.dataset.preset]; const active = cpu === presetCpu && mib === presetRam; button.classList.toggle('active', active); button.setAttribute('aria-pressed', String(active)); }); } function resetPasswordVisibility() { $('#create-password').type = 'password'; const toggle = $('[data-action="toggle-password"]'); toggle.setAttribute('aria-pressed', 'false'); toggle.setAttribute('aria-label', 'Show password'); } async function submitCreate(event) { event.preventDefault(); if (state.busy.has('create')) return; const session = state.session; let payload; try { parseRam($('#create-ram').value); parseStorage($('#create-storage').value); const cpu = Number($('#create-cpu').value); if (!Number.isFinite(cpu) || cpu <= 0) throw new Error('CPU capacity must be a positive number.'); payload = { name: $('#create-name').value.trim() || null, cpu, ram: $('#create-ram').value.trim(), storage: $('#create-storage').value.trim(), guest_ip: $('#create-guest-ip').value.trim() || null, username: $('#create-username').value.trim(), password: $('#create-password').value, ports: parsePorts($('#create-ports').value), no_default_ports: !$('#create-default-ssh').checked, }; if (!payload.password) throw new Error('Add a password for the guest account.'); } catch (error) { text('#create-error', error.message); return; } text('#create-error', ''); const button = $('#create-submit'); const previous = button.innerHTML; const unlock = lockForm($('#create-form')); state.busy.add('create'); button.textContent = 'Creating your machine…'; try { const vm = await api('/vms', { method: 'POST', body: payload }); if (session !== state.session) return; if (!vm?.id) throw new Error('The machine response was not recognized. Refresh the registry to check its state.'); $('#create-form').reset(); resetPasswordVisibility(); updateCreateSummary(); toast('A new little machine, ready for you.'); location.hash = vmHref(vm.id); await loadVms(); } catch (error) { if (!isAborted(error) && session === state.session) text('#create-error', error.message); } finally { unlock(); button.innerHTML = previous; if (session === state.session) state.busy.delete('create'); } } async function runBulkAction() { if (!state.selected.size || state.busy.has('bulk')) return; const ids = [...state.selected]; const action = $('#bulk-action').value; const session = state.session; const labels = { start: 'Start', stop: 'Stop', restart: 'Restart', delete: 'Delete', 'ports-sync': 'Sync policies for' }; const title = `${labels[action]} ${ids.length} ${ids.length === 1 ? 'machine' : 'machines'}?`; const description = action === 'delete' ? 'Every selected machine and its private disk will be permanently removed. Network resources will be released.' : `This applies to the ${ids.length} selected ${ids.length === 1 ? 'machine' : 'machines'}. Each result will be reported separately.`; if (!await confirmAction(title, description, action === 'ports-sync' ? 'Sync policies' : `${labels[action]} selected`, action === 'delete')) return; if (session !== state.session) return; state.busy.add('bulk'); ids.forEach((id) => state.busy.add(id)); await withButton($('#bulk-run-btn'), 'Applying…', async () => { try { const results = requireList(await api('/vms/actions', { method: 'POST', body: { action, identifiers: ids } })); if (session !== state.session) return; const failures = results.filter((result) => result.outcome !== 'succeeded'); state.selected = new Set(failures.map((result) => result.id)); $('#bulk-results').hidden = !failures.length; if (failures.length) { html('#bulk-results', `

${results.length - failures.length} succeeded · ${failures.length} need attention

`); } toast(failures.length ? `${results.length - failures.length} succeeded. See the details for the remaining machines.` : `${results.length} ${results.length === 1 ? 'machine' : 'machines'} updated.`, Boolean(failures.length)); await loadVms(); } catch (error) { if (!isAborted(error) && session === state.session) toast(error.message, true); } finally { if (session === state.session) { state.busy.delete('bulk'); ids.forEach((id) => state.busy.delete(id)); renderMachines(); } } }); } async function installAssets() { const session = state.session; const force = $('#install-force').checked; await withButton($('#install-btn'), 'Preparing your host…', async () => { $('#install-force').disabled = true; $('#install-result').hidden = false; text('#install-result', 'Installing the host and guest assets. This can take a few minutes.'); try { const result = await api('/install', { method: 'POST', body: { force } }); if (session !== state.session) return; text('#install-result', `Installation complete.\n\nFirecracker\n${result.firecracker}\n\nGuest kernel\n${result.kernel}\n\nRoot filesystem\n${result.rootfs}`); toast('A fresh foundation for your machines.'); await Promise.all([loadDiagnostics(), loadHealth()]); } catch (error) { if (!isAborted(error) && session === state.session) { text('#install-result', error.message); toast('Installation could not finish. See the host details.', true); } } finally { $('#install-force').disabled = false; } }); } function closeMenu(restoreFocus = false) { $('#action-menu').hidden = true; if (state.menuTrigger?.isConnected) { state.menuTrigger.setAttribute('aria-expanded', 'false'); if (restoreFocus) state.menuTrigger.focus(); } state.menuTrigger = null; } function openMachineMenu(trigger) { if (state.menuTrigger === trigger && !$('#action-menu').hidden) return closeMenu(true); closeMenu(); const id = trigger.dataset.id; const vm = state.vms.find((item) => item.id === id); if (!vm) return; const running = runtimeStatus(vm) === 'running'; const items = [['inspect', 'eye', 'Inspect machine']]; if (running) items.push(['restart', 'refresh', 'Restart'], ['stop', 'stop', 'Stop machine']); else items.push(['start', 'play', 'Start machine']); if (vm.ports !== null) items.push(['ports-sync', 'shield', 'Sync port policy']); items.push(['delete', 'trash', 'Delete machine']); const menu = $('#action-menu'); html(menu, items.map(([action, symbol, caption]) => ``).join('')); state.menuTrigger = trigger; trigger.setAttribute('aria-expanded', 'true'); menu.hidden = false; const rect = trigger.getBoundingClientRect(); const height = menu.getBoundingClientRect().height; menu.style.left = `${Math.max(12, Math.min(rect.right - 181, window.innerWidth - 193))}px`; menu.style.top = `${Math.max(12, rect.bottom + height + 8 > window.innerHeight ? rect.top - height - 6 : rect.bottom + 6)}px`; $('button', menu)?.focus(); } function closeSearch() { $('#search-results').hidden = true; $('#global-search').setAttribute('aria-expanded', 'false'); $('#global-search').removeAttribute('aria-activedescendant'); state.searchIndex = -1; } function renderSearch() { if (!state.authenticated) return; const query = $('#global-search').value.trim().toLowerCase(); const candidates = Object.entries(pages).filter(([key]) => key !== 'vm-detail').map(([key, page]) => ({ title: page.label, description: 'Workspace page', href: `#${key}`, icon: page.icon, search: page.label, })).concat(state.vms.map((vm) => ({ title: machineName(vm), description: `${vm.guest_ip || vm.id} · ${label(runtimeStatus(vm))}`, href: vmHref(vm.id), icon: 'server', search: [vm.name, vm.id, vm.guest_ip].join(' ') }))); const results = candidates.filter((item) => !query || item.search.toLowerCase().includes(query)).slice(0, 8); state.searchIndex = Math.min(state.searchIndex, results.length - 1); html('#search-results', results.length ? results.map((item, index) => `${icon(item.icon)}
${esc(item.title)}${esc(item.description)}
${icon('arrow')}
`).join('') : '

No machines or pages found. Try another name.

'); $('#search-results').hidden = false; $('#global-search').setAttribute('aria-expanded', 'true'); if (state.searchIndex >= 0) $('#global-search').setAttribute('aria-activedescendant', `search-option-${state.searchIndex}`); else $('#global-search').removeAttribute('aria-activedescendant'); } function focusSearch() { setMobileNav(false); $('#global-search').focus(); $('#global-search').select(); renderSearch(); } function setMobileNav(open, restoreFocus = false) { const mobile = window.matchMedia('(max-width: 700px)').matches; const shown = open && mobile; $('#main-app').classList.toggle('nav-open', shown); $('#nav-toggle').setAttribute('aria-expanded', String(shown)); $('#nav-toggle').setAttribute('aria-label', shown ? 'Close navigation' : 'Open navigation'); $('#nav-scrim').hidden = !shown; $('.workspace').inert = shown; if (shown) { $('#workspace-nav').setAttribute('role', 'dialog'); $('#workspace-nav').setAttribute('aria-modal', 'true'); $('.brand', $('#workspace-nav')).focus(); } else { $('#workspace-nav').removeAttribute('role'); $('#workspace-nav').removeAttribute('aria-modal'); if (restoreFocus && mobile) $('#nav-toggle').focus(); } } function route(initial = false) { if (!state.authenticated) return; let value = location.hash.slice(1) || 'overview'; let identifier = null; if (value.startsWith('vm/')) { try { identifier = decodeURIComponent(value.slice(3)); } catch { identifier = null; } value = identifier ? 'vm-detail' : 'overview'; } if (!Object.prototype.hasOwnProperty.call(pages, value)) value = 'overview'; const previous = state.page; state.page = value; state.requests.detail += 1; state.requests.logs += 1; state.detailId = identifier; state.detail = null; state.portEditing = false; state.resizeEditing = false; text('#resize-error', ''); closeMenu(); closeSearch(); setMobileNav(false); $$('.page').forEach((page) => { page.hidden = page.id !== `page-${value}`; }); $$('.nav-item[data-nav]').forEach((item) => { const active = item.dataset.nav === (value === 'vm-detail' ? 'vms' : value); item.classList.toggle('active', active); if (active) item.setAttribute('aria-current', 'page'); else item.removeAttribute('aria-current'); }); text('#sidebar-title', pages[value].title); text('#sidebar-subtitle', pages[value].subtitle); text('#breadcrumb', pages[value].label); document.title = `${pages[value].label} · UVM Control`; $('#main-content').scrollTop = 0; if (!initial && previous !== value && window.matchMedia('(max-width: 700px)').matches) window.scrollTo({ top: 0, behavior: 'instant' }); if (value === 'vm-detail') { $('#detail-content').hidden = true; $('#detail-loading').hidden = false; html('#detail-loading', 'Getting to know this machine…'); text('#log-output', 'Bringing the runtime log into view…'); loadDetail(identifier); } if (value === 'system') loadDiagnostics(); if (value === 'create') updateCreateSummary(); if (!initial && value !== 'vm-detail') $(`#page-${value} h1`)?.focus({ preventScroll: true }); } function storeToken(token) { try { if (token) sessionStorage.setItem('uvm_token', token); else sessionStorage.removeItem('uvm_token'); } catch { /* A restricted browser can still use an in-memory session. */ } } async function connect(token) { if (!token || !/^[!-~]+$/.test(token)) { text('#auth-error', 'Enter a management token without spaces or non-ASCII characters.'); return; } const session = ++state.session; state.token = token; text('#auth-error', ''); const button = $('#auth-btn'); const previous = button.innerHTML; button.disabled = true; button.textContent = 'Opening your workspace…'; $('#token-input').disabled = true; try { const vms = requireList(await api('/vms')); if (session !== state.session) return; state.authenticated = true; state.vms = vms; state.loaded = true; state.lastUpdate = Date.now(); storeToken(token); $('#token-input').value = ''; $('#auth-screen').hidden = true; $('#main-app').hidden = false; renderFleet(); renderHost(); route(true); Promise.all([loadHealth(), loadDiagnostics()]); window.clearInterval(state.timer); state.timer = window.setInterval(() => { if (!document.hidden) refreshData(); }, 5000); } catch (error) { if (session !== state.session || isAborted(error)) return; state.token = ''; storeToken(''); text('#auth-error', error.status === 401 ? 'That token wasn’t accepted. Give it another look.' : error.message); $('#token-input').focus(); } finally { if (session === state.session) { button.disabled = false; button.innerHTML = previous; $('#token-input').disabled = false; if (!state.authenticated) $('#token-input').focus(); } } } function logout(message = '') { state.session += 1; state.controllers.forEach((controller) => controller.abort()); state.controllers.clear(); window.clearInterval(state.timer); state.timer = null; state.token = ''; state.authenticated = false; state.vms = []; state.loaded = false; state.health = null; state.checks = null; state.vmError = ''; state.healthError = ''; state.checksError = ''; state.lastUpdate = 0; state.lastChecks = 0; state.lastChecksAttempt = 0; state.refreshPromise = null; state.checksPromise = null; state.selected.clear(); state.busy.clear(); state.detail = null; state.detailId = null; state.portEditing = false; state.resizeEditing = false; state.query = ''; state.filter = 'all'; state.sort = 'recent'; storeToken(''); closeMenu(); closeSearch(); setMobileNav(false); if ($('#confirm-dialog').open) $('#confirm-dialog').close('cancel'); $('#create-form').reset(); resetPasswordVisibility(); $('#install-force').checked = false; $('#install-result').hidden = true; $('#bulk-results').hidden = true; $('#vm-search').value = ''; $('#vm-sort').value = 'recent'; $('#global-search').value = ''; $('#token-input').value = ''; $('#token-input').disabled = false; text('#create-error', ''); text('#port-error', ''); text('#resize-error', ''); text('#auth-error', message); $('#main-app').hidden = true; $('#auth-screen').hidden = false; $('#refresh-btn').classList.remove('is-loading'); $('#toast-container').replaceChildren(); document.title = 'UVM Control'; $('#token-input').focus(); } function handleAction(target) { const action = target.dataset.action; const id = target.dataset.id; if (action === 'search') focusSearch(); if (action === 'refresh') manualRefresh(); if (action === 'diagnostics') loadDiagnostics(); if (action === 'machine-menu') openMachineMenu(target); if (action === 'inspect') { closeMenu(); location.hash = vmHref(id); } if (action === 'vm-action') { closeMenu(true); machineAction(target.dataset.operation, id); } if (action === 'clear-selection') { state.selected.clear(); renderMachines(); } if (action === 'clear-filters') { state.query = ''; state.filter = 'all'; $('#vm-search').value = ''; renderMachines(); } if (action === 'dismiss-results') $('#bulk-results').hidden = true; if (action === 'install') installAssets(); if (action === 'refresh-logs') loadLogs(); if (action === 'copy-ssh') copyText($('#ssh-command').textContent); if (action === 'delete-current' && state.detail) machineAction('delete', state.detail.id); if (action === 'sync-ports' && state.detail) machineAction('ports-sync', state.detail.id); if (action === 'edit-ports') { state.portEditing = true; text('#port-error', ''); renderDetail(); $('#port-input').focus(); } if (action === 'cancel-ports') { state.portEditing = false; text('#port-error', ''); renderDetail(); } if (action === 'edit-resources') { state.resizeEditing = true; text('#resize-error', ''); renderDetail(); $('#resize-cpu').focus(); } if (action === 'cancel-resize') { state.resizeEditing = false; text('#resize-error', ''); renderDetail(); } if (action === 'toggle-password') { const visible = $('#create-password').type === 'password'; $('#create-password').type = visible ? 'text' : 'password'; target.setAttribute('aria-pressed', String(visible)); target.setAttribute('aria-label', visible ? 'Hide password' : 'Show password'); } } function bindEvents() { $('#auth-form').addEventListener('submit', (event) => { event.preventDefault(); connect($('#token-input').value.trim()); }); $('#logout-btn').addEventListener('click', () => logout()); $('#refresh-btn').addEventListener('click', manualRefresh); $('#create-form').addEventListener('submit', submitCreate); $('#create-form').addEventListener('input', updateCreateSummary); $('#port-editor').addEventListener('submit', submitPorts); $('#resize-form').addEventListener('submit', submitResize); $('#bulk-run-btn').addEventListener('click', runBulkAction); $('#log-tail').addEventListener('change', loadLogs); $('#vm-search').addEventListener('input', (event) => { state.query = event.target.value; renderMachines(); }); $('#vm-sort').addEventListener('change', (event) => { state.sort = event.target.value; renderMachines(); }); $('#vm-select-all').addEventListener('change', (event) => { visibleVms().forEach((vm) => { if (event.target.checked) state.selected.add(vm.id); else state.selected.delete(vm.id); }); renderMachines(); }); $('#machine-rows').addEventListener('change', (event) => { const checkbox = event.target.closest('[data-select]'); if (!checkbox) return; if (checkbox.checked) state.selected.add(checkbox.dataset.select); else state.selected.delete(checkbox.dataset.select); const id = checkbox.dataset.select; renderMachines(); $$('[data-select]').find((item) => item.dataset.select === id)?.focus({ preventScroll: true }); }); $('#global-search').addEventListener('input', () => { state.searchIndex = -1; renderSearch(); }); $('#global-search').addEventListener('focus', renderSearch); $('#global-search').addEventListener('keydown', (event) => { if (event.key === 'Escape') { closeSearch(); event.stopPropagation(); return; } if (!['ArrowDown', 'ArrowUp', 'Enter'].includes(event.key)) return; const results = $$('.search-result'); if (!results.length) return; event.preventDefault(); if (event.key === 'Enter') { const selected = results[Math.max(0, state.searchIndex)]; location.hash = selected.getAttribute('href'); $('#global-search').value = ''; closeSearch(); $('#global-search').blur(); } else { state.searchIndex = (state.searchIndex + (event.key === 'ArrowDown' ? 1 : -1) + results.length) % results.length; renderSearch(); $(`#search-option-${state.searchIndex}`)?.scrollIntoView({ block: 'nearest' }); } }); $('#nav-toggle').addEventListener('click', () => setMobileNav(!$('#main-app').classList.contains('nav-open'))); $('#nav-scrim').addEventListener('click', () => setMobileNav(false, true)); $('#confirm-dialog').addEventListener('click', (event) => { if (event.target !== $('#confirm-dialog')) return; const rect = event.target.getBoundingClientRect(); if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) event.target.close('cancel'); }); document.addEventListener('click', (event) => { const target = event.target.closest('[data-action]'); if (!event.target.closest('.global-search')) closeSearch(); if (!event.target.closest('#action-menu, [data-action="machine-menu"]')) closeMenu(); if (event.target.closest('.search-result')) { $('#global-search').value = ''; closeSearch(); } if (event.target.closest('a[href^="#"]')) setMobileNav(false); const filter = event.target.closest('[data-filter]'); if (filter) { state.filter = filter.dataset.filter; renderMachines(); } const preset = event.target.closest('[data-preset]'); if (preset) { const sizes = { small: [1, '512M'], balanced: [2, '1G'], roomy: [4, '2G'] }; const [cpu, ram] = sizes[preset.dataset.preset]; $('#create-cpu').value = cpu; $('#create-ram').value = ram; updateCreateSummary(); } if (target && !target.disabled) handleAction(target); }); document.addEventListener('keydown', (event) => { const editing = event.target.closest('input, textarea, select, [contenteditable="true"]'); if (state.authenticated && ((event.key.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) || (event.key === '/' && !editing))) { if ($('#confirm-dialog').open) return; event.preventDefault(); focusSearch(); } if (event.key === 'Escape') { closeSearch(); closeMenu(true); if ($('#main-app').classList.contains('nav-open')) setMobileNav(false, true); } if (!$('#action-menu').hidden && ['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) { event.preventDefault(); const items = $$('button:not(:disabled)', $('#action-menu')); let index = items.indexOf(document.activeElement); index = event.key === 'Home' ? 0 : event.key === 'End' ? items.length - 1 : (index + (event.key === 'ArrowDown' ? 1 : -1) + items.length) % items.length; items[index]?.focus(); } if (event.key === 'Tab' && $('#main-app').classList.contains('nav-open')) { const items = $$('a, button:not(:disabled)', $('#workspace-nav')); if (event.shiftKey && document.activeElement === items[0]) { event.preventDefault(); items.at(-1).focus(); } if (!event.shiftKey && document.activeElement === items.at(-1)) { event.preventDefault(); items[0].focus(); } } }); document.addEventListener('focusin', (event) => { if (!event.target.closest('.global-search')) closeSearch(); if (!$('#action-menu').hidden && !event.target.closest('#action-menu, [data-action="machine-menu"]')) closeMenu(); }); $('#main-content').addEventListener('scroll', () => { closeMenu(); closeSearch(); }, { passive: true }); window.addEventListener('resize', () => { closeMenu(); if (!window.matchMedia('(max-width: 700px)').matches) setMobileNav(false); }); window.addEventListener('hashchange', () => route()); document.addEventListener('visibilitychange', () => { if (!document.hidden && state.authenticated) refreshData(); }); } function boot() { bindEvents(); text('#today-label', new Intl.DateTimeFormat(undefined, { weekday: 'short', month: 'short', day: 'numeric' }).format(new Date())); updateCreateSummary(); let stored = ''; try { stored = sessionStorage.getItem('uvm_token') || ''; } catch { /* Session storage is optional. */ } if (stored) connect(stored); } boot(); })();