Initial commit

This commit is contained in:
Keith Solomon
2026-03-08 15:06:50 -05:00
commit 9fb58a9677
16 changed files with 1194 additions and 0 deletions

177
app/static/app.js Normal file
View File

@@ -0,0 +1,177 @@
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 = '<li class="device-item">No devices discovered yet.</li>';
return;
}
devices.forEach((d) => {
const li = document.createElement('li');
li.className = `device-item ${selectedDeviceId === d.id ? 'active' : ''}`;
li.innerHTML = `
<div>${deviceTitle(d)}</div>
<div class="meta">${d.os_name || 'OS unknown'} | ${d.is_active ? 'Active' : 'Missing'}</div>
`;
li.addEventListener('click', () => {
selectedDeviceId = d.id;
renderDeviceList();
loadDevice(d.id);
});
deviceListEl.appendChild(li);
});
}
function renderMachineInfo(d) {
machineInfoEl.classList.remove('empty');
machineInfoEl.innerHTML = `
<div class="info-grid">
<div class="info-card"><div class="label">Hostname</div><div class="value">${d.hostname || '-'}</div></div>
<div class="info-card"><div class="label">IP Address</div><div class="value">${d.ip}</div></div>
<div class="info-card"><div class="label">MAC Address</div><div class="value">${d.mac || '-'}</div></div>
<div class="info-card"><div class="label">Vendor</div><div class="value">${d.vendor || '-'}</div></div>
<div class="info-card"><div class="label">Operating System</div><div class="value">${d.os_name || '-'}</div></div>
<div class="info-card"><div class="label">Status</div><div class="value">${d.is_active ? 'Active' : 'Not Seen in Last Scan'}</div></div>
<div class="info-card"><div class="label">First Seen</div><div class="value">${formatDate(d.first_seen)}</div></div>
<div class="info-card"><div class="label">Last Seen</div><div class="value">${formatDate(d.last_seen)}</div></div>
</div>
`;
}
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 = `
<summary>${p.port}/${p.protocol} - ${p.state}${svc ? ` - ${svc}` : ''}</summary>
<div class="port-body">
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}
</div>
`;
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}`);
}
})();