const deviceListEl = document.getElementById('deviceList'); const machineInfoEl = document.getElementById('machineInfo'); const portsListEl = document.getElementById('portsList'); const statusText = document.getElementById('statusText'); const scanBtn = document.getElementById('scanBtn'); const subnetInput = document.getElementById('subnetInput'); let devices = []; let selectedDeviceId = null; function setStatus(msg) { statusText.textContent = msg; } async function api(path, options = {}) { const res = await fetch(path, options); if (!res.ok) { let detail = `${res.status}`; try { const data = await res.json(); if (data.detail) detail = data.detail; } catch (_) {} throw new Error(detail); } return res.json(); } function formatDate(iso) { if (!iso) return '-'; return new Date(iso).toLocaleString(); } function deviceTitle(d) { return d.hostname ? `${d.hostname} (${d.ip})` : d.ip; } function renderDeviceList() { deviceListEl.innerHTML = ''; if (!devices.length) { deviceListEl.innerHTML = '
  • No devices discovered yet.
  • '; return; } devices.forEach((d) => { const li = document.createElement('li'); li.className = `device-item ${selectedDeviceId === d.id ? 'active' : ''}`; li.innerHTML = `
    ${deviceTitle(d)}
    ${d.os_name || 'OS unknown'} | ${d.is_active ? 'Active' : 'Missing'}
    `; li.addEventListener('click', () => { selectedDeviceId = d.id; renderDeviceList(); loadDevice(d.id); }); deviceListEl.appendChild(li); }); } function renderMachineInfo(d) { machineInfoEl.classList.remove('empty'); machineInfoEl.innerHTML = `
    Hostname
    ${d.hostname || '-'}
    IP Address
    ${d.ip}
    MAC Address
    ${d.mac || '-'}
    Vendor
    ${d.vendor || '-'}
    Operating System
    ${d.os_name || '-'}
    Status
    ${d.is_active ? 'Active' : 'Not Seen in Last Scan'}
    First Seen
    ${formatDate(d.first_seen)}
    Last Seen
    ${formatDate(d.last_seen)}
    `; } function renderPorts(ports) { portsListEl.innerHTML = ''; portsListEl.classList.remove('empty'); if (!ports.length) { portsListEl.classList.add('empty'); portsListEl.textContent = 'No ports recorded for this machine.'; return; } ports.forEach((p) => { const details = document.createElement('details'); details.className = 'port'; const svc = [p.service, p.product, p.version].filter(Boolean).join(' '); const headers = Object.entries(p.headers || {}) .map(([k, v]) => `${k}: ${v}`) .join('\n') || 'No headers captured'; details.innerHTML = ` ${p.port}/${p.protocol} - ${p.state}${svc ? ` - ${svc}` : ''}
    Service: ${svc || 'Unknown'} Extra: ${p.extra_info || '-'} Banner: ${p.banner || '-'} First Seen: ${formatDate(p.first_seen)} Last Seen: ${formatDate(p.last_seen)} Headers:\n${headers}
    `; portsListEl.appendChild(details); }); } async function loadDevices() { devices = await api('/api/devices'); renderDeviceList(); if (!selectedDeviceId && devices.length) { selectedDeviceId = devices[0].id; renderDeviceList(); } if (selectedDeviceId) { await loadDevice(selectedDeviceId); } } async function loadDevice(deviceId) { const d = await api(`/api/devices/${deviceId}`); renderMachineInfo(d); renderPorts(d.ports || []); } async function runScan() { const subnet = subnetInput.value.trim(); if (!subnet) return; scanBtn.disabled = true; setStatus(`Starting scan on ${subnet}...`); try { const result = await api(`/api/scans/run?subnet=${encodeURIComponent(subnet)}`, { method: 'POST' }); setStatus(`Scan #${result.scan_id} running on ${result.subnet}. Refreshing automatically...`); await pollUntilComplete(result.scan_id); } catch (err) { setStatus(`Scan failed to start: ${err.message}`); } finally { scanBtn.disabled = false; } } async function pollUntilComplete(scanId) { for (let i = 0; i < 240; i += 1) { await new Promise((r) => setTimeout(r, 3000)); const scans = await api('/api/scans?limit=1'); const latest = scans[0]; if (!latest || latest.id !== scanId) continue; if (latest.status === 'running') { setStatus(`Scan #${scanId} is running...`); continue; } setStatus(`Scan #${scanId} ${latest.status} (${latest.host_count} hosts).`); await loadDevices(); return; } setStatus(`Scan #${scanId} is taking longer than expected.`); } scanBtn.addEventListener('click', runScan); (async function init() { setStatus('Loading inventory...'); try { await loadDevices(); setStatus('Ready.'); } catch (err) { setStatus(`Failed loading data: ${err.message}`); } })();