diff --git a/app/database.py b/app/database.py index dc51e88..7e989f3 100644 --- a/app/database.py +++ b/app/database.py @@ -23,6 +23,13 @@ def init_db(): processed_at INTEGER ) """) + conn.execute(""" + CREATE TABLE IF NOT EXISTS whitelist ( + plate TEXT PRIMARY KEY, + label TEXT, + added_at INTEGER + ) + """) # Migrate existing DBs that lack clip_path try: conn.execute("ALTER TABLE events ADD COLUMN clip_path TEXT") @@ -126,3 +133,57 @@ def get_cameras(): rows = conn.execute("SELECT DISTINCT camera FROM events ORDER BY camera").fetchall() conn.close() return [r[0] for r in rows] + + +# ── Whitelist ───────────────────────────────────────────────────────────────── + +def get_whitelist() -> list[dict]: + conn = get_db() + rows = conn.execute("SELECT plate, label, added_at FROM whitelist ORDER BY added_at DESC").fetchall() + conn.close() + return [dict(r) for r in rows] + + +def is_whitelisted(plate: str) -> bool: + if not plate: + return False + conn = get_db() + row = conn.execute("SELECT plate FROM whitelist WHERE plate = ?", (plate.upper().strip(),)).fetchone() + conn.close() + return row is not None + + +def add_to_whitelist(plate: str, label: str = ""): + import time + conn = get_db() + conn.execute( + "INSERT OR REPLACE INTO whitelist (plate, label, added_at) VALUES (?, ?, ?)", + (plate.upper().strip(), label.strip(), int(time.time())), + ) + conn.commit() + conn.close() + + +def remove_from_whitelist(plate: str): + conn = get_db() + conn.execute("DELETE FROM whitelist WHERE plate = ?", (plate.upper().strip(),)) + conn.commit() + conn.close() + + +# ── Stats ───────────────────────────────────────────────────────────────────── + +def get_plate_stats() -> list[dict]: + conn = get_db() + rows = conn.execute(""" + SELECT plate, + COUNT(*) AS count, + MIN(start_time) AS first_seen, + MAX(start_time) AS last_seen + FROM events + WHERE plate IS NOT NULL AND plate != '' + GROUP BY plate + ORDER BY count DESC + """).fetchall() + conn.close() + return [dict(r) for r in rows] diff --git a/app/main.py b/app/main.py index 27a4c74..e2b1e5c 100644 --- a/app/main.py +++ b/app/main.py @@ -109,6 +109,7 @@ async def event_detail(request: Request, event_id: str): plate_ocr_abs = os.path.join(event_dir, "plate_ocr.jpg") plate_ocr = f"events/{event_id}/plate_ocr.jpg" if os.path.exists(plate_ocr_abs) else None + is_wl = database.is_whitelisted(ev.get("plate") or "") return templates.TemplateResponse("event_detail.html", { "request": request, "ev": ev, @@ -120,6 +121,7 @@ async def event_detail(request: Request, event_id: str): "capture_duration": CAPTURE_DURATION, "capture_fps": CAPTURE_FPS, "capture_total": CAPTURE_DURATION * CAPTURE_FPS, + "is_whitelisted": is_wl, }) @@ -186,6 +188,39 @@ async def upload_clip(file: UploadFile = File(...)): return RedirectResponse(f"/event/{event_id}", status_code=303) +@app.get("/stats", response_class=HTMLResponse) +async def stats_page(request: Request): + rows = database.get_plate_stats() + whitelist_plates = {w["plate"] for w in database.get_whitelist()} + for r in rows: + r["first_str"] = ts_to_str(r["first_seen"]) + r["last_str"] = ts_to_str(r["last_seen"]) + r["whitelisted"] = r["plate"] in whitelist_plates + return templates.TemplateResponse("stats.html", {"request": request, "stats": rows}) + + +@app.get("/whitelist", response_class=HTMLResponse) +async def whitelist_page(request: Request): + entries = database.get_whitelist() + for e in entries: + e["added_str"] = ts_to_str(e["added_at"]) + return templates.TemplateResponse("whitelist.html", {"request": request, "entries": entries}) + + +@app.post("/whitelist/add") +async def whitelist_add(plate: str = Form(""), label: str = Form(""), back: str = Form("")): + plate = plate.strip().upper() + if plate: + database.add_to_whitelist(plate, label) + return RedirectResponse(back or "/whitelist", status_code=303) + + +@app.post("/whitelist/remove/{plate}") +async def whitelist_remove(plate: str, back: str = Form("")): + database.remove_from_whitelist(plate) + return RedirectResponse(back or "/whitelist", status_code=303) + + @app.get("/health") async def health(): return {"status": "ok"} diff --git a/app/templates/event_detail.html b/app/templates/event_detail.html index e44b3af..4ccd593 100644 --- a/app/templates/event_detail.html +++ b/app/templates/event_detail.html @@ -35,6 +35,20 @@ ← Retour 🚗 {{ ev.time_str }} + {% if ev.plate %} + {% if is_whitelisted %} +
+ {% else %} + + {% endif %} + {% endif %}