feat(db): backfill, DB-backed web scans, firewall snapshot view (R001/S001/F001)

- R001: app/db/backfill.py imports git-history blocklist -> ledger, firewall
  snapshots, and per-source requests.csv (idempotent; ran live migrating 705
  historical blocklist entries).
- S001: web_scan.latest_scans() now merges SQLite 'scans' records so the
  dashboard surfaces CLI/restart scans (_db_scans).
- F001: /api/firewall/snapshots + 'Snapshot history' panel; added
  recent_firewall_snapshots() query with entry counts.
This commit is contained in:
zwitschi committed 2026-10-24 19:56:20 +02:00
1 parent ba48054293
commit 2fb4032766
4 files changed
+308 -6

No files matched your search

+45
View File
@@ -23,6 +23,11 @@ endblock %} {% block content %}
<div class="panel-head"><h2>Firewall rules</h2></div>
<div id="firewall-rules"></div>
</div>
<div class="panel">
<div class="panel-head"><h2>Snapshot history (SQLite)</h2></div>
<div id="fw-snapshots"></div>
</div>
{% endblock %} {% block scripts %}
<script>
async function loadState() {
@@ -35,6 +40,45 @@ endblock %} {% block content %}
}
}
async function loadSnapshots() {
const box = document.getElementById("fw-snapshots");
try {
const d = await api("/api/firewall/snapshots?limit=10");
const snaps = d.snapshots || [];
if (!snaps.length) {
box.innerHTML =
'<p class="muted">No SQLite snapshots yet — they are recorded on each <code>--update-firewall</code> run.</p>';
return;
}
box.innerHTML = "";
const rows = snaps.map((s) =>
el(
"tr",
el("td", { class: "mono", text: `#${s.id}` }),
el("td", { text: fmtTime(s.captured_at) }),
el("td", { text: s.kind }),
el("td", { text: s.trigger }),
el("td", { class: "num", text: s.addr_entries }),
el("td", { class: "num", text: s.rule_entries }),
),
);
box.append(
el(
"table",
{ class: "table" },
el(
"thead",
el("tr", {}, el("th", { text: "ID" }), el("th", { text: "Captured" }), el("th", { text: "Kind" }), el("th", { text: "Trigger" }), el("th", { text: "Addr" }), el("th", { text: "Rules" })),
),
el("tbody", {}, ...rows),
),
);
} catch (e) {
box.innerHTML=
'<p class="muted">Snapshot history unavailable (SQLite).</p>';
}
}
function renderAddressLists(addr) {
const box = document.getElementById("address-lists");
box.innerHTML = "";
@@ -172,5 +216,6 @@ endblock %} {% block content %}
.getElementById("apply-btn")
.addEventListener("click", () => sync(false));
loadState();
loadSnapshots();
</script>
{% endblock %}