Refactor web_scan.py for improved readability and maintainability; add cron job script for npm log backups; update start.ps1 for consistent formatting.

This commit is contained in:
zwitschi committed 2026-09-08 19:49:11 +02:00
1 parent 0d1cd21468
commit c2eba88ac8
113 files changed
+772606 -772579

No files matched your search

+185 -185
View File
@@ -1,185 +1,185 @@
{% extends "base.html" %} {% block title %}Scan — Intrusion Detection{% endblock
%} {% block content %}
<h1>Run a scan</h1>
<p class="muted">
Scans run in the background; only one at a time. You can leave this page and
come back.
</p>
<div class="panel">
<form id="scan-form" class="form-grid">
<label
>Type
<select id="kind">
<option value="consolidated">Consolidated (copyparty + npm)</option>
<option value="copyparty">copyparty only</option>
<option value="npm">npm only</option>
</select>
</label>
<label>Top IPs <input id="top" type="number" value="30" min="1" /></label>
<label
>Min denied (copyparty)
<input id="min_denied" type="number" value="1" min="0"
/></label>
<label
>Min errors (npm) <input id="min_errors" type="number" value="5" min="0"
/></label>
<label
>Error ratio (npm)
<input
id="error_ratio"
type="number"
step="0.05"
value="0.5"
min="0"
max="1"
/></label>
<label class="check"
><input id="update_firewall" type="checkbox" /> Update firewall after
scan</label
>
<label class="check"
><input id="dry_run" type="checkbox" /> Dry-run (preview, no
changes)</label
>
<label class="check"
><input id="full" type="checkbox" /> Full scan (ignore incremental
state)</label
>
</form>
<div class="panel-actions">
<button class="btn primary" id="run-btn">Run scan</button>
<span class="muted" id="busy-hint"></span>
</div>
<div id="job-status" class="job-status"></div>
</div>
<div class="panel">
<div class="panel-head"><h2>Recent jobs</h2></div>
<table class="table">
<thead>
<tr>
<th>Job</th>
<th>Kind</th>
<th>State</th>
<th>Started</th>
<th>Finished</th>
<th class="num">Blocked</th>
</tr>
</thead>
<tbody id="jobs"></tbody>
</table>
</div>
{% endblock %} {% block scripts %}
<script>
let pollTimer = null;
const form = document.getElementById("scan-form");
const runBtn = document.getElementById("run-btn");
const hint = document.getElementById("busy-hint");
async function runScan() {
const kind = document.getElementById("kind").value;
const params = {
top: parseInt(document.getElementById("top").value, 10) || 30,
min_denied:
parseInt(document.getElementById("min_denied").value, 10) || 1,
min_errors:
parseInt(document.getElementById("min_errors").value, 10) || 5,
error_ratio:
parseFloat(document.getElementById("error_ratio").value) || 0.5,
update_firewall: document.getElementById("update_firewall").checked,
dry_run: document.getElementById("dry_run").checked,
full: document.getElementById("full").checked,
quiet: true,
};
runBtn.disabled = true;
try {
const job = await startScan(kind, params);
setStatus(`Job ${job.job_id} started (${job.state}).`, "ok");
poll(job.job_id);
} catch (e) {
setStatus(e.message, "error");
runBtn.disabled = false;
}
}
function setStatus(msg, type) {
const box = document.getElementById("job-status");
box.textContent = msg;
box.style.color =
type === "error"
? "var(--danger)"
: type === "ok"
? "var(--ok)"
: "inherit";
}
async function poll(jobId) {
clearTimeout(pollTimer);
let job;
try {
job = await api(`/api/scan/${jobId}`);
} catch (e) {
setStatus(e.message, "error");
runBtn.disabled = false;
return;
}
const r = job.result || {};
setStatus(
`[${job.state}] ${job.kind} — started ${fmtTime(job.started)}` +
(job.finished ? `, finished ${fmtTime(job.finished)}` : "") +
(r.blocked_targets != null
? `, blocked targets: ${r.blocked_targets}`
: "") +
(job.error ? `, error: ${job.error}` : ""),
job.state === "error" ? "error" : job.state === "done" ? "ok" : "",
);
if (job.state === "running" || job.state === "queued") {
pollTimer = setTimeout(() => poll(jobId), 2000);
} else {
runBtn.disabled = false;
refreshJobs();
const running = (await api("/api/scan/status")).running;
hint.textContent = running ? "" : "";
}
}
async function refreshJobs() {
try {
const s = await api("/api/scan/status");
hint.textContent = s.running ? "A scan is currently running…" : "";
const body = document.getElementById("jobs");
body.innerHTML = "";
const jobs = s.latest_scans || [];
if (!jobs.length) {
body.innerHTML =
'<tr><td colspan="6" class="muted">No jobs yet.</td></tr>';
return;
}
for (const j of jobs) {
const r = j.result || {};
body.append(
el(
"tr",
el("td", { class: "mono", text: j.job_id }),
el("td", { text: j.kind }),
el("td", {}, badge(j.state)),
el("td", { text: fmtTime(j.started) }),
el("td", { text: fmtTime(j.finished) }),
el("td", { class: "num", text: r.blocked_targets ?? "—" }),
),
);
}
} catch (e) {
toast(e.message, "error");
}
}
runBtn.addEventListener("click", runScan);
refreshJobs();
// /api/scan/status is only polled every 15 s; the active job's own
// 2 s poll keeps live progress without hammering the status endpoint.
setInterval(refreshJobs, 15000);
</script>
{% endblock %}
{% extends "base.html" %} {% block title %}Scan — Intrusion Detection{% endblock
%} {% block content %}
<h1>Run a scan</h1>
<p class="muted">
Scans run in the background; only one at a time. You can leave this page and
come back.
</p>
<div class="panel">
<form id="scan-form" class="form-grid">
<label
>Type
<select id="kind">
<option value="consolidated">Consolidated (copyparty + npm)</option>
<option value="copyparty">copyparty only</option>
<option value="npm">npm only</option>
</select>
</label>
<label>Top IPs <input id="top" type="number" value="30" min="1" /></label>
<label
>Min denied (copyparty)
<input id="min_denied" type="number" value="1" min="0"
/></label>
<label
>Min errors (npm) <input id="min_errors" type="number" value="5" min="0"
/></label>
<label
>Error ratio (npm)
<input
id="error_ratio"
type="number"
step="0.05"
value="0.5"
min="0"
max="1"
/></label>
<label class="check"
><input id="update_firewall" type="checkbox" /> Update firewall after
scan</label
>
<label class="check"
><input id="dry_run" type="checkbox" /> Dry-run (preview, no
changes)</label
>
<label class="check"
><input id="full" type="checkbox" /> Full scan (ignore incremental
state)</label
>
</form>
<div class="panel-actions">
<button class="btn primary" id="run-btn">Run scan</button>
<span class="muted" id="busy-hint"></span>
</div>
<div id="job-status" class="job-status"></div>
</div>
<div class="panel">
<div class="panel-head"><h2>Recent jobs</h2></div>
<table class="table">
<thead>
<tr>
<th>Job</th>
<th>Kind</th>
<th>State</th>
<th>Started</th>
<th>Finished</th>
<th class="num">Blocked</th>
</tr>
</thead>
<tbody id="jobs"></tbody>
</table>
</div>
{% endblock %} {% block scripts %}
<script>
let pollTimer = null;
const form = document.getElementById("scan-form");
const runBtn = document.getElementById("run-btn");
const hint = document.getElementById("busy-hint");
async function runScan() {
const kind = document.getElementById("kind").value;
const params = {
top: parseInt(document.getElementById("top").value, 10) || 30,
min_denied:
parseInt(document.getElementById("min_denied").value, 10) || 1,
min_errors:
parseInt(document.getElementById("min_errors").value, 10) || 5,
error_ratio:
parseFloat(document.getElementById("error_ratio").value) || 0.5,
update_firewall: document.getElementById("update_firewall").checked,
dry_run: document.getElementById("dry_run").checked,
full: document.getElementById("full").checked,
quiet: true,
};
runBtn.disabled = true;
try {
const job = await startScan(kind, params);
setStatus(`Job ${job.job_id} started (${job.state}).`, "ok");
poll(job.job_id);
} catch (e) {
setStatus(e.message, "error");
runBtn.disabled = false;
}
}
function setStatus(msg, type) {
const box = document.getElementById("job-status");
box.textContent = msg;
box.style.color =
type === "error"
? "var(--danger)"
: type === "ok"
? "var(--ok)"
: "inherit";
}
async function poll(jobId) {
clearTimeout(pollTimer);
let job;
try {
job = await api(`/api/scan/${jobId}`);
} catch (e) {
setStatus(e.message, "error");
runBtn.disabled = false;
return;
}
const r = job.result || {};
setStatus(
`[${job.state}] ${job.kind} — started ${fmtTime(job.started)}` +
(job.finished ? `, finished ${fmtTime(job.finished)}` : "") +
(r.blocked_targets != null
? `, blocked targets: ${r.blocked_targets}`
: "") +
(job.error ? `, error: ${job.error}` : ""),
job.state === "error" ? "error" : job.state === "done" ? "ok" : "",
);
if (job.state === "running" || job.state === "queued") {
pollTimer = setTimeout(() => poll(jobId), 2000);
} else {
runBtn.disabled = false;
refreshJobs();
const running = (await api("/api/scan/status")).running;
hint.textContent = running ? "" : "";
}
}
async function refreshJobs() {
try {
const s = await api("/api/scan/status");
hint.textContent = s.running ? "A scan is currently running…" : "";
const body = document.getElementById("jobs");
body.innerHTML = "";
const jobs = s.latest_scans || [];
if (!jobs.length) {
body.innerHTML =
'<tr><td colspan="6" class="muted">No jobs yet.</td></tr>';
return;
}
for (const j of jobs) {
const r = j.result || {};
body.append(
el(
"tr",
el("td", { class: "mono", text: j.job_id }),
el("td", { text: j.kind }),
el("td", {}, badge(j.state)),
el("td", { text: fmtTime(j.started) }),
el("td", { text: fmtTime(j.finished) }),
el("td", { class: "num", text: r.blocked_targets ?? "—" }),
),
);
}
} catch (e) {
toast(e.message, "error");
}
}
runBtn.addEventListener("click", runScan);
refreshJobs();
// /api/scan/status is only polled every 15 s; the active job's own
// 2 s poll keeps live progress without hammering the status endpoint.
setInterval(refreshJobs, 15000);
</script>
{% endblock %}