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

+176 -176
View File
@@ -1,176 +1,176 @@
{% extends "base.html" %} {% block title %}Firewall — Intrusion Detection{%
endblock %} {% block content %}
<h1>Firewall</h1>
<p class="muted">
Current MikroTik state (from the latest <code>data/mikrotik</code> snapshot).
Sync previews or applies the blocklist diff.
</p>
<div class="panel">
<div class="panel-actions" style="margin-top: 0">
<button class="btn" id="preview-btn">Preview sync (dry-run)</button>
<button class="btn danger" id="apply-btn">Apply sync</button>
<span class="muted" id="fw-result"></span>
</div>
</div>
<div class="panel">
<div class="panel-head"><h2>Address lists</h2></div>
<div id="address-lists"></div>
</div>
<div class="panel">
<div class="panel-head"><h2>Firewall rules</h2></div>
<div id="firewall-rules"></div>
</div>
{% endblock %} {% block scripts %}
<script>
async function loadState() {
try {
const s = await api("/api/firewall/state");
renderAddressLists(s.address_lists || {});
renderRules("firewall-rules", s.firewall_rules || {});
} catch (e) {
toast(e.message, "error");
}
}
function renderAddressLists(addr) {
const box = document.getElementById("address-lists");
box.innerHTML = "";
const byList = {};
for (const version of ["ipv4", "ipv6"]) {
for (const e of addr[version] || []) {
const name = e.list || "?";
(byList[name] = byList[name] || { ipv4: [], ipv6: [] })[version].push(
e.address,
);
}
}
const names = Object.keys(byList);
if (!names.length) {
box.innerHTML =
'<p class="muted">No address-list snapshot available. Run <code>app.py --update-firewall</code> first.</p>';
return;
}
for (const name of names) {
const rows = [];
for (const version of ["ipv4", "ipv6"]) {
const entries = (byList[name][version] || []).sort();
rows.push(
el(
"tr",
el("td", { text: version }),
el("td", { class: "mono", text: entries.join(", ") || "—" }),
),
);
}
box.append(
el(
"div",
{ style: "margin-bottom:1rem" },
el("div", {
style: "font-weight:600;margin-bottom:.4rem",
text: name,
}),
el(
"table",
{ class: "table" },
el(
"thead",
{},
el(
"tr",
{},
el("th", { text: "Version" }),
el("th", { text: "Addresses" }),
),
),
el("tbody", {}, ...rows),
),
),
);
}
}
function renderRules(boxId, rules) {
const box = document.getElementById(boxId);
box.innerHTML = "";
for (const version of ["ipv4", "ipv6"]) {
const list = rules[version] || [];
const rows = list.map((r) =>
el(
"tr",
el("td", {
class: "mono",
text: r.address ? r.address : r["src-address-list"] || "",
}),
el("td", { text: r.chain || "—" }),
el("td", { text: r.action || "—" }),
el("td", { text: r.comment || "" }),
),
);
box.append(
el(
"div",
{ style: "margin-bottom:1rem" },
el("div", {
style: "font-weight:600;margin-bottom:.4rem",
text: `IPv${version} (${list.length})`,
}),
rows.length
? el(
"table",
{ class: "table" },
el(
"thead",
{},
el(
"tr",
{},
el("th", { text: "Address / List" }),
el("th", { text: "Chain" }),
el("th", { text: "Action" }),
el("th", { text: "Comment" }),
),
),
el("tbody", {}, ...rows),
)
: el("p", { class: "muted", text: "No rules in snapshot." }),
),
);
}
}
async function sync(dryRun) {
const out = document.getElementById("fw-result");
out.textContent = "Working…";
try {
const changes = await api(`/api/firewall/${dryRun ? "sync" : "apply"}`, {
method: "POST",
body: {},
});
const verb = dryRun ? "Would" : "";
out.textContent =
`${verb} add ${(changes.added_ipv4 || []).length} IPv4 + ${(changes.added_ipv6 || []).length} IPv6; ` +
`remove ${(changes.removed_ipv4 || []).length} IPv4 + ${(changes.removed_ipv6 || []).length} IPv6.`;
toast(
(dryRun ? "Preview" : "Applied") + " firewall sync",
dryRun ? "warn" : "ok",
);
if (!dryRun) loadState();
} catch (e) {
out.textContent = "";
toast(e.message, "error");
}
}
document
.getElementById("preview-btn")
.addEventListener("click", () => sync(true));
document
.getElementById("apply-btn")
.addEventListener("click", () => sync(false));
loadState();
</script>
{% endblock %}
{% extends "base.html" %} {% block title %}Firewall — Intrusion Detection{%
endblock %} {% block content %}
<h1>Firewall</h1>
<p class="muted">
Current MikroTik state (from the latest <code>data/mikrotik</code> snapshot).
Sync previews or applies the blocklist diff.
</p>
<div class="panel">
<div class="panel-actions" style="margin-top: 0">
<button class="btn" id="preview-btn">Preview sync (dry-run)</button>
<button class="btn danger" id="apply-btn">Apply sync</button>
<span class="muted" id="fw-result"></span>
</div>
</div>
<div class="panel">
<div class="panel-head"><h2>Address lists</h2></div>
<div id="address-lists"></div>
</div>
<div class="panel">
<div class="panel-head"><h2>Firewall rules</h2></div>
<div id="firewall-rules"></div>
</div>
{% endblock %} {% block scripts %}
<script>
async function loadState() {
try {
const s = await api("/api/firewall/state");
renderAddressLists(s.address_lists || {});
renderRules("firewall-rules", s.firewall_rules || {});
} catch (e) {
toast(e.message, "error");
}
}
function renderAddressLists(addr) {
const box = document.getElementById("address-lists");
box.innerHTML = "";
const byList = {};
for (const version of ["ipv4", "ipv6"]) {
for (const e of addr[version] || []) {
const name = e.list || "?";
(byList[name] = byList[name] || { ipv4: [], ipv6: [] })[version].push(
e.address,
);
}
}
const names = Object.keys(byList);
if (!names.length) {
box.innerHTML =
'<p class="muted">No address-list snapshot available. Run <code>app.py --update-firewall</code> first.</p>';
return;
}
for (const name of names) {
const rows = [];
for (const version of ["ipv4", "ipv6"]) {
const entries = (byList[name][version] || []).sort();
rows.push(
el(
"tr",
el("td", { text: version }),
el("td", { class: "mono", text: entries.join(", ") || "—" }),
),
);
}
box.append(
el(
"div",
{ style: "margin-bottom:1rem" },
el("div", {
style: "font-weight:600;margin-bottom:.4rem",
text: name,
}),
el(
"table",
{ class: "table" },
el(
"thead",
{},
el(
"tr",
{},
el("th", { text: "Version" }),
el("th", { text: "Addresses" }),
),
),
el("tbody", {}, ...rows),
),
),
);
}
}
function renderRules(boxId, rules) {
const box = document.getElementById(boxId);
box.innerHTML = "";
for (const version of ["ipv4", "ipv6"]) {
const list = rules[version] || [];
const rows = list.map((r) =>
el(
"tr",
el("td", {
class: "mono",
text: r.address ? r.address : r["src-address-list"] || "",
}),
el("td", { text: r.chain || "—" }),
el("td", { text: r.action || "—" }),
el("td", { text: r.comment || "" }),
),
);
box.append(
el(
"div",
{ style: "margin-bottom:1rem" },
el("div", {
style: "font-weight:600;margin-bottom:.4rem",
text: `IPv${version} (${list.length})`,
}),
rows.length
? el(
"table",
{ class: "table" },
el(
"thead",
{},
el(
"tr",
{},
el("th", { text: "Address / List" }),
el("th", { text: "Chain" }),
el("th", { text: "Action" }),
el("th", { text: "Comment" }),
),
),
el("tbody", {}, ...rows),
)
: el("p", { class: "muted", text: "No rules in snapshot." }),
),
);
}
}
async function sync(dryRun) {
const out = document.getElementById("fw-result");
out.textContent = "Working…";
try {
const changes = await api(`/api/firewall/${dryRun ? "sync" : "apply"}`, {
method: "POST",
body: {},
});
const verb = dryRun ? "Would" : "";
out.textContent =
`${verb} add ${(changes.added_ipv4 || []).length} IPv4 + ${(changes.added_ipv6 || []).length} IPv6; ` +
`remove ${(changes.removed_ipv4 || []).length} IPv4 + ${(changes.removed_ipv6 || []).length} IPv6.`;
toast(
(dryRun ? "Preview" : "Applied") + " firewall sync",
dryRun ? "warn" : "ok",
);
if (!dryRun) loadState();
} catch (e) {
out.textContent = "";
toast(e.message, "error");
}
}
document
.getElementById("preview-btn")
.addEventListener("click", () => sync(true));
document
.getElementById("apply-btn")
.addEventListener("click", () => sync(false));
loadState();
</script>
{% endblock %}