Add global stats generation

This commit is contained in:
Jordan Wages 2025-07-19 00:16:11 -05:00
commit a1102952e9
3 changed files with 80 additions and 0 deletions

View file

@ -62,6 +62,35 @@ def _render_snippet(report: Dict, out_dir: Path) -> None:
snippet_path.write_text(template.render(report=report))
def _write_stats() -> None:
"""Query basic dataset stats and write them to ``output/global/stats.json``."""
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("SELECT COUNT(*) FROM logs")
total_logs = cur.fetchone()[0] or 0
cur.execute("SELECT MIN(time), MAX(time) FROM logs")
row = cur.fetchone() or (None, None)
start_date = row[0] or ""
end_date = row[1] or ""
cur.execute("SELECT COUNT(DISTINCT host) FROM logs")
unique_domains = cur.fetchone()[0] or 0
conn.close()
stats = {
"total_logs": total_logs,
"start_date": start_date,
"end_date": end_date,
"unique_domains": unique_domains,
}
out_path = OUTPUT_DIR / "global" / "stats.json"
_save_json(out_path, stats)
def _bucket_expr(interval: str) -> str:
"""Return the SQLite strftime expression for the given interval."""
fmt = INTERVAL_FORMATS.get(interval)
@ -199,6 +228,7 @@ def _generate_global() -> None:
report_list.append(entry)
_save_json(out_dir / "reports.json", report_list)
_write_stats()
typer.echo("Generated global reports")

View file

@ -32,6 +32,12 @@
<span class="icon is-small is-left"><i data-feather="server"></i></span>
</div>
</div>
<div id="overview" class="box mb-5">
<h2 class="subtitle">Overview</h2>
<p>Total logs: <span id="stat-total">-</span></p>
<p>Date range: <span id="stat-start">-</span> to <span id="stat-end">-</span></p>
<p>Unique domains: <span id="stat-domains">-</span></p>
</div>
<div id="reports-container"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
@ -42,6 +48,10 @@
const intervalSelect = document.getElementById('interval-select');
const domainSelect = document.getElementById('domain-select');
const container = document.getElementById('reports-container');
const totalElem = document.getElementById('stat-total');
const startElem = document.getElementById('stat-start');
const endElem = document.getElementById('stat-end');
const domainsElem = document.getElementById('stat-domains');
let currentInterval = intervalSelect.value;
let currentDomain = domainSelect.value;
@ -97,6 +107,17 @@
});
}
function loadStats() {
fetch('global/stats.json')
.then(r => r.json())
.then(stats => {
totalElem.textContent = stats.total_logs;
startElem.textContent = stats.start_date;
endElem.textContent = stats.end_date;
domainsElem.textContent = stats.unique_domains;
});
}
function loadReports() {
let path = currentInterval;
if (currentDomain) {
@ -128,6 +149,7 @@
});
loadReports();
loadStats();
feather.replace();
</script>
</body>

View file

@ -189,3 +189,31 @@ def test_global_reports_once(tmp_path, sample_reports, monkeypatch):
assert global_snippet.exists()
assert not (tmp_path / "output" / "hourly" / "domain_totals.html").exists()
def test_global_stats_file(tmp_path, sample_reports, monkeypatch):
db_path = tmp_path / "database" / "ngxstat.db"
setup_db(db_path)
monkeypatch.setattr(gr, "DB_PATH", db_path)
monkeypatch.setattr(gr, "OUTPUT_DIR", tmp_path / "output")
monkeypatch.setattr(gr, "REPORT_CONFIG", sample_reports)
monkeypatch.setattr(
gr, "TEMPLATE_DIR", Path(__file__).resolve().parents[1] / "templates"
)
gr._generate_global()
stats_path = tmp_path / "output" / "global" / "stats.json"
assert stats_path.exists()
stats = json.loads(stats_path.read_text())
assert set(stats.keys()) == {
"total_logs",
"start_date",
"end_date",
"unique_domains",
}
assert stats["total_logs"] == 2
assert stats["start_date"] == "2024-01-01 10:00:00"
assert stats["end_date"] == "2024-01-01 10:05:00"
assert stats["unique_domains"] == 1