Merge pull request #25 from wagesj45/codex/implement-report-generation-and-rendering
Add global stats reporting
This commit is contained in:
commit
0bf67f6107
3 changed files with 80 additions and 0 deletions
|
@ -62,6 +62,35 @@ def _render_snippet(report: Dict, out_dir: Path) -> None:
|
||||||
snippet_path.write_text(template.render(report=report))
|
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:
|
def _bucket_expr(interval: str) -> str:
|
||||||
"""Return the SQLite strftime expression for the given interval."""
|
"""Return the SQLite strftime expression for the given interval."""
|
||||||
fmt = INTERVAL_FORMATS.get(interval)
|
fmt = INTERVAL_FORMATS.get(interval)
|
||||||
|
@ -199,6 +228,7 @@ def _generate_global() -> None:
|
||||||
report_list.append(entry)
|
report_list.append(entry)
|
||||||
|
|
||||||
_save_json(out_dir / "reports.json", report_list)
|
_save_json(out_dir / "reports.json", report_list)
|
||||||
|
_write_stats()
|
||||||
typer.echo("Generated global reports")
|
typer.echo("Generated global reports")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,12 @@
|
||||||
<span class="icon is-small is-left"><i data-feather="server"></i></span>
|
<span class="icon is-small is-left"><i data-feather="server"></i></span>
|
||||||
</div>
|
</div>
|
||||||
</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 id="reports-container"></div>
|
||||||
</div>
|
</div>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
<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 intervalSelect = document.getElementById('interval-select');
|
||||||
const domainSelect = document.getElementById('domain-select');
|
const domainSelect = document.getElementById('domain-select');
|
||||||
const container = document.getElementById('reports-container');
|
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 currentInterval = intervalSelect.value;
|
||||||
let currentDomain = domainSelect.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() {
|
function loadReports() {
|
||||||
let path = currentInterval;
|
let path = currentInterval;
|
||||||
if (currentDomain) {
|
if (currentDomain) {
|
||||||
|
@ -128,6 +149,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
loadReports();
|
loadReports();
|
||||||
|
loadStats();
|
||||||
feather.replace();
|
feather.replace();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -189,3 +189,31 @@ def test_global_reports_once(tmp_path, sample_reports, monkeypatch):
|
||||||
assert global_snippet.exists()
|
assert global_snippet.exists()
|
||||||
assert not (tmp_path / "output" / "hourly" / "domain_totals.html").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
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue