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.
This commit is contained in:
1 parent
0137264fe4
commit
6036fa8b1e
17 files changed
+1292
-5
No files matched your search
@@ -0,0 +1,191 @@
|
||||
{% 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 %}
|
||||
Reference in new issue
Block a user