Add scan creation and completion watch in UI.

Allow creating scans by CIDR from web UI and polling scan status until terminal state before refreshing topology.

Made-with: Cursor
This commit is contained in:
Andrey Lutsenko
2026-04-09 22:17:51 +10:00
parent 396d139482
commit 231c9045df
3 changed files with 84 additions and 0 deletions
+82
View File
@@ -16,6 +16,11 @@
<body>
<h2>NetTopo: просмотр топологии</h2>
<div class="hint">Можно выбрать последний scan автоматически или сравнить два скана через встроенный diff.</div>
<div class="row">
<input id="cidrInput" placeholder="CIDR, например 192.168.1.0/24" style="min-width: 320px;" />
<button id="createScanBtn">Создать scan</button>
<button id="watchScanBtn">Ждать завершения</button>
</div>
<div class="row">
<input id="scanId" placeholder="scan_id" style="min-width: 380px;" />
<select id="scanSelect" style="min-width: 360px;"></select>
@@ -50,12 +55,15 @@
<script>
const scanIdInput = document.getElementById("scanId");
const cidrInput = document.getElementById("cidrInput");
const scanSelect = document.getElementById("scanSelect");
const fromScanIdInput = document.getElementById("fromScanId");
const toScanIdInput = document.getElementById("toScanId");
const fromScanSelect = document.getElementById("fromScanSelect");
const toScanSelect = document.getElementById("toScanSelect");
const loadBtn = document.getElementById("loadBtn");
const createScanBtn = document.getElementById("createScanBtn");
const watchScanBtn = document.getElementById("watchScanBtn");
const latestBtn = document.getElementById("latestBtn");
const diffBtn = document.getElementById("diffBtn");
const exportTopologyBtn = document.getElementById("exportTopologyBtn");
@@ -216,6 +224,78 @@
}
}
async function createScan() {
const cidr = cidrInput.value.trim();
if (!cidr) {
setStatus("Введите CIDR для создания scan");
return;
}
setStatus("Создаю scan...");
try {
const payload = {
name: `manual-${new Date().toISOString()}`,
cidrs: [cidr],
exclude_ips: [],
options: {
ping_timeout_ms: 700,
ping_retries: 1,
max_parallel_hosts: 128,
port_scan_enabled: true,
ports: [22, 80, 443, 161]
}
};
const res = await fetch("/api/scans", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
if (!res.ok) {
const t = await res.text();
setStatus(`Ошибка создания scan: ${res.status} ${t}`);
return;
}
const d = await res.json();
scanIdInput.value = d.scan_id;
toScanIdInput.value = d.scan_id;
setStatus(`Scan создан: ${d.scan_id}`);
await loadScanOptions();
} catch (e) {
setStatus(`Ошибка создания scan: ${e.message}`);
}
}
async function watchScan() {
const id = scanIdInput.value.trim();
if (!id) {
setStatus("Введите scan_id");
return;
}
setStatus(`Ожидание завершения scan ${id}...`);
const startedAt = Date.now();
const maxWaitMs = 20 * 60 * 1000;
while (Date.now() - startedAt < maxWaitMs) {
try {
const res = await fetch(`/api/scans/${encodeURIComponent(id)}`);
if (!res.ok) {
setStatus(`Ошибка чтения scan: ${res.status}`);
return;
}
const s = await res.json();
setStatus(`Scan ${id}: ${s.status}, progress ${s.progress || 0}%`);
if (s.status === "done" || s.status === "failed" || s.status === "canceled") {
await loadTopology();
await loadScanOptions();
return;
}
} catch (e) {
setStatus(`Ошибка ожидания scan: ${e.message}`);
return;
}
await new Promise(resolve => setTimeout(resolve, 2000));
}
setStatus("Таймаут ожидания scan");
}
async function loadLatestScan() {
setStatus("Ищу последний scan...");
try {
@@ -278,6 +358,8 @@
}
loadBtn.addEventListener("click", loadTopology);
createScanBtn.addEventListener("click", createScan);
watchScanBtn.addEventListener("click", watchScan);
scanSelect.addEventListener("change", () => {
if (scanSelect.value) scanIdInput.value = scanSelect.value;
});