ngxstat/scripts/generate_reports.py

93 lines
2.7 KiB
Python

import json
import sqlite3
from pathlib import Path
from typing import List, Dict
import yaml
import typer
from jinja2 import Environment, FileSystemLoader
DB_PATH = Path("database/ngxstat.db")
OUTPUT_DIR = Path("output")
TEMPLATE_DIR = Path("templates")
REPORT_CONFIG = Path("reports.yml")
app = typer.Typer(help="Generate aggregated log reports")
def _load_config() -> List[Dict]:
if not REPORT_CONFIG.exists():
typer.echo(f"Config file not found: {REPORT_CONFIG}")
raise typer.Exit(1)
with REPORT_CONFIG.open("r") as fh:
data = yaml.safe_load(fh) or []
if not isinstance(data, list):
typer.echo("reports.yml must contain a list of report definitions")
raise typer.Exit(1)
return data
def _save_json(path: Path, data: List[Dict]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, indent=2))
def _render_html(interval: str, reports: List[Dict], out_path: Path) -> None:
env = Environment(loader=FileSystemLoader(TEMPLATE_DIR))
template = env.get_template("report.html")
out_path.write_text(template.render(interval=interval, reports=reports))
def _generate_interval(interval: str) -> None:
cfg = _load_config()
defs = [d for d in cfg if d.get("interval") == interval]
if not defs:
typer.echo(f"No reports defined for {interval}")
return
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
out_dir = OUTPUT_DIR / interval
out_dir.mkdir(parents=True, exist_ok=True)
report_list = []
for definition in defs:
name = definition["name"]
query = definition["query"]
cur.execute(query)
rows = cur.fetchall()
headers = [c[0] for c in cur.description]
data = [dict(zip(headers, row)) for row in rows]
json_path = out_dir / f"{name}.json"
_save_json(json_path, data)
report_list.append({
"name": name,
"label": definition.get("label", name.title()),
"chart": definition.get("chart", "line"),
"json": f"{name}.json",
})
_save_json(out_dir / "reports.json", report_list)
_render_html(interval, report_list, out_dir / "index.html")
typer.echo(f"Generated {interval} reports")
@app.command()
def hourly() -> None:
"""Generate hourly reports."""
_generate_interval("hourly")
@app.command()
def daily() -> None:
"""Generate daily reports."""
_generate_interval("daily")
@app.command()
def weekly() -> None:
"""Generate weekly reports."""
_generate_interval("weekly")
@app.command()
def monthly() -> None:
"""Generate monthly reports."""
_generate_interval("monthly")
if __name__ == "__main__":
app()