- 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.
192 lines
5.4 KiB
HTML
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 %}
|