Files
intrusion-detection/app/templates/analysis.html
T
zwitschi 6036fa8b1e feat: Implement SQLite schema and migrations for intrusion detection system
- Added schema definition and migration logic in app/db/schema.py.
- Created tables for log sources, requests, IP stats, scans, blocklist, whitelist, and firewall rules.
- Introduced functions to apply migrations and ensure database integrity.

feat: Enhance scan functionality with database integration

- Added _sync_blocklist_to_db function to synchronize blocklist with SQLite.
- Updated run_scan function to handle database connections and scan records.
- Implemented error handling for database operations during scans.

feat: Introduce log analysis page and API endpoints

- Created analysis.html template for displaying log analysis results.
- Added routes for fetching top IPs, IP request timelines, and cross-source IPs.
- Integrated analysis data into the dashboard and status endpoints.

fix: Ensure whitelists are synchronized with the database

- Implemented sync_whitelists_to_db function to persist in-memory whitelists.
- Updated constants.py to define the path for the SQLite database.

refactor: Modularize analysis helpers for better maintainability

- Created _analysis_helpers function to encapsulate SQLite query logic.
- Updated routes.py to utilize new analysis helper functions for API responses.
2026-10-24 19:48:20 +02:00

192 lines
5.4 KiB
HTML

{% extends "base.html" %} {% block title %}Log Analysis — Intrusion Detection{%
endblock %} {% block content %}
<h1>Log Analysis</h1>
<p class="muted">
Detailed analysis of persisted log data from the SQLite store. Select an IP
to see its full request history across all log sources.
</p>
<div class="panel">
<div class="panel-head">
<h2>Top IPs</h2>
<span class="muted" id="top-disclaimer"></span>
</div>
<table class="table">
<thead>
<tr>
<th>IP</th>
<th class="num">Requests</th>
<th class="num">Errors</th>
<th class="num">Malicious</th>
<th></th>
</tr>
</thead>
<tbody id="top-ips"></tbody>
</table>
</div>
<div class="panel">
<div class="panel-head">
<h2>Cross-source IPs</h2>
<span class="muted">Seen in more than one log source</span>
</div>
<table class="table">
<thead>
<tr>
<th>IP</th>
<th class="num">Sources</th>
<th>Source list</th>
<th class="num">Total</th>
</tr>
</thead>
<tbody id="cross-source"></tbody>
</table>
</div>
<div class="panel" id="ip-panel" hidden>
<div class="panel-head">
<h2 id="ip-title">Request history</h2>
<a class="btn ghost" href="#" id="ip-close">✕ Close</a>
</div>
<table class="table">
<thead>
<tr>
<th>Time</th>
<th>Source</th>
<th>Method</th>
<th>Path</th>
<th class="num">Status</th>
<th>UA</th>
</tr>
</thead>
<tbody id="ip-timeline"></tbody>
</table>
</div>
{% endblock %} {% block scripts %}
<script>
function showIp(ip) {
const panel = document.getElementById("ip-panel");
const body = document.getElementById("ip-timeline");
document.getElementById("ip-title").textContent = `Request history — ${ip}`;
panel.hidden = false;
body.innerHTML =
'<tr><td colspan="6" class="muted">Loading…</td></tr>';
api(`/api/analysis/ip/${encodeURIComponent(ip)}`)
.then((data) => {
body.innerHTML = "";
const tl = data.timeline || [];
if (!tl.length) {
body.innerHTML =
'<tr><td colspan="6" class="muted">No persisted requests for this IP.</td></tr>';
return;
}
for (const r of tl) {
const status = r.status ?? "—";
const row = el(
"tr",
el("td", {
class: "mono",
text: r.ts || "—",
}),
el("td", { text: r.source }),
el("td", { text: r.method || "—" }),
el("td", { class: "mono", text: r.path || "—" }),
el(
"td",
{
class: "num",
text: status,
style: status >= 400 ? "color:var(--danger)" : "",
},
),
el("td", { class: "muted", text: r.ua || "—" }),
);
if (r.is_error) row.classList.add("row-error");
body.append(row);
}
})
.catch((e) => toast(e.message, "error"));
panel.scrollIntoView({ behavior: "smooth", block: "start" });
}
async function loadTop() {
const body = document.getElementById("top-ips");
body.innerHTML = "";
try {
const data = await api("/api/analysis/top-ips?limit=50");
body.innerHTML = "";
const ips = data.top_ips || [];
document.getElementById("top-disclaimer").textContent =
`showing top ${ips.length} IPs`;
if (!ips.length) {
body.innerHTML =
'<tr><td colspan="5" class="muted">No persisted requests yet — run a scan to populate the SQLite store.</td></tr>';
return;
}
for (const r of ips) {
body.append(
el(
"tr",
el("td", { class: "mono", text: r.ip }),
el("td", { class: "num", text: r.total }),
el("td", { class: "num", text: r.errors }),
el("td", { class: "num", text: r.malicious_hits }),
el(
"td",
el(
"button",
{ class: "btn ghost", onclick: () => showIp(r.ip) },
"Detail",
),
),
),
);
}
} catch (e) {
body.innerHTML =
'<tr><td colspan="5" class="muted b">SQLite analysis unavailable.</td></tr>';
toast(e.message, "error");
}
}
async function loadCrossSource() {
const body = document.getElementById("cross-source");
body.innerHTML = "";
try {
const data = await api("/api/analysis/cross-source?limit=100");
const ips = data.ips || [];
if (!ips.length) {
body.innerHTML =
'<tr><td colspan="4" class="muted">No IPs seen across multiple sources yet.</td></tr>';
return;
}
for (const r of ips) {
body.append(
el(
"tr",
el("td", { class: "mono", text: r.ip }),
el("td", { class: "num", text: r.sources }),
el("td", { text: r.source_list }),
el("td", { class: "num", text: r.total }),
),
);
}
} catch (e) {
body.innerHTML =
'<tr><td colspan="4" class="muted b">SQLite analysis unavailable.</td></tr>';
toast(e.message, "error");
}
}
document
.getElementById("ip-close")
.addEventListener("click", (e) => {
e.preventDefault();
document.getElementById("ip-panel").hidden = true;
});
loadTop();
loadCrossSource();
</script>
{% endblock %}