feat: implement collection runs tracking and error acknowledgment features

This commit is contained in:
ludal committed 2026-09-21 13:45:58 +02:00
1 parent c39364eda4
commit 34903492f6
14 files changed
+484 -21

No files matched your search

+44
View File
@@ -57,6 +57,11 @@ def services_page():
return _read_page("services.html")
@app.get("/collecte", response_class=HTMLResponse)
def collecte_page():
return _read_page("collecte.html")
@app.get("/health")
def health():
try:
@@ -150,3 +155,42 @@ def api_resource_detail(provider: str, name: str, service_name: str):
return queries.resource_detail(conn, provider, name, service_name)
finally:
conn.close()
@app.get("/api/collection-runs")
def api_collection_runs(limit: int = Query(50, ge=1, le=200)):
conn = queries.get_connection()
try:
return {"runs": queries.list_collection_runs(conn, limit=limit)}
finally:
conn.close()
@app.get("/api/collection-runs/alert-status")
def api_collection_runs_alert_status():
conn = queries.get_connection()
try:
return {"has_unacknowledged_errors": queries.has_unacknowledged_errors(conn)}
finally:
conn.close()
@app.get("/api/collection-runs/{run_id}/errors")
def api_collection_run_errors(run_id: int):
conn = queries.get_connection()
try:
return {"errors": queries.collection_run_errors(conn, run_id)}
finally:
conn.close()
@app.post("/api/collection-runs/{run_id}/acknowledge")
def api_acknowledge_collection_run(run_id: int):
conn = queries.get_connection()
try:
acknowledged = queries.acknowledge_collection_run(conn, run_id)
if not acknowledged:
raise HTTPException(status_code=404, detail="Exécution introuvable ou déjà acquittée")
return {"status": "acknowledged"}
finally:
conn.close()
+59 -1
View File
@@ -10,7 +10,7 @@ from __future__ import annotations
import os
import sqlite3
from datetime import date
from datetime import date, datetime, timezone
from pathlib import Path
DEFAULT_DB_PATH = "cost_dashboard.db"
@@ -656,3 +656,61 @@ def resource_detail(conn: sqlite3.Connection, provider: str, name: str, service_
"period_end": period_end,
"resources": resources,
}
# ----------------------------------------------------------------------
# Exécutions de collecte (page "Collecte")
# ----------------------------------------------------------------------
def list_collection_runs(conn: sqlite3.Connection, limit: int = 50) -> list[dict]:
rows = conn.execute(
"""
SELECT id, run_type, started_at, finished_at, tenants_ok, tenants_error, acknowledged_at
FROM collection_runs
ORDER BY started_at DESC
LIMIT ?
""",
(limit,),
).fetchall()
return [dict(r) for r in rows]
def collection_run_errors(conn: sqlite3.Connection, run_id: int) -> list[dict]:
"""Détail des relevés en erreur d'une exécution — relu depuis
`collections` sur la fenêtre [started_at, finished_at] du run, plutôt que
dupliqué dans collection_runs."""
run = conn.execute(
"SELECT started_at, finished_at FROM collection_runs WHERE id = ?", (run_id,)
).fetchone()
if run is None:
return []
rows = conn.execute(
"""
SELECT provider, tenant_name, granularity, period_start, error_message, collected_at
FROM collections
WHERE status = 'error' AND collected_at BETWEEN ? AND ?
ORDER BY collected_at
""",
(run["started_at"], run["finished_at"] or run["started_at"]),
).fetchall()
return [dict(r) for r in rows]
def has_unacknowledged_errors(conn: sqlite3.Connection) -> bool:
row = conn.execute(
"SELECT 1 FROM collection_runs WHERE tenants_error > 0 AND acknowledged_at IS NULL LIMIT 1"
).fetchone()
return row is not None
def acknowledge_collection_run(conn: sqlite3.Connection, run_id: int) -> bool:
"""Seule écriture de ce module (le reste est lecture seule) : marque une
exécution en erreur comme acquittée manuellement depuis le dashboard.
Retourne False si l'exécution n'existe pas ou était déjà acquittée."""
cur = conn.execute(
"UPDATE collection_runs SET acknowledged_at = ? WHERE id = ? AND acknowledged_at IS NULL",
(datetime.now(timezone.utc).isoformat(), run_id),
)
conn.commit()
return cur.rowcount > 0