""" Endpoints pour la heatmap temporelle (hits par heure / hôte) """ from collections import defaultdict from fastapi import APIRouter, HTTPException, Query from ..database import db router = APIRouter(prefix="/api/heatmap", tags=["heatmap"]) @router.get("/hourly") async def get_heatmap_hourly(): """Hits agrégés par heure sur les 72 dernières heures.""" try: sql = """ SELECT toHour(window_start) AS hour, sum(hits) AS hits, uniq(replaceRegexpAll(toString(src_ip), '^::ffff:', '')) AS unique_ips, max(max_requests_per_sec) AS max_rps FROM mabase_prod.agg_host_ip_ja4_1h WHERE window_start >= now() - INTERVAL 72 HOUR GROUP BY hour ORDER BY hour ASC """ result = db.query(sql) hours = [ { "hour": int(row[0]), "hits": int(row[1]), "unique_ips": int(row[2]), "max_rps": int(row[3]), } for row in result.result_rows ] return {"hours": hours} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/top-hosts") async def get_heatmap_top_hosts(limit: int = Query(20, ge=1, le=100)): """Hôtes les plus ciblés avec répartition horaire sur 24h.""" try: # Aggregate overall stats per host agg_sql = """ SELECT host, sum(hits) AS total_hits, uniq(replaceRegexpAll(toString(src_ip), '^::ffff:', '')) AS unique_ips, uniq(ja4) AS unique_ja4s FROM mabase_prod.agg_host_ip_ja4_1h WHERE window_start >= now() - INTERVAL 72 HOUR GROUP BY host ORDER BY total_hits DESC LIMIT %(limit)s """ agg_res = db.query(agg_sql, {"limit": limit}) top_hosts = [str(r[0]) for r in agg_res.result_rows] host_stats = { str(r[0]): { "host": str(r[0]), "total_hits": int(r[1]), "unique_ips": int(r[2]), "unique_ja4s":int(r[3]), } for r in agg_res.result_rows } if not top_hosts: return {"items": []} # Hourly breakdown per host hourly_sql = """ SELECT host, toHour(window_start) AS hour, sum(hits) AS hits FROM mabase_prod.agg_host_ip_ja4_1h WHERE window_start >= now() - INTERVAL 72 HOUR AND host IN %(hosts)s GROUP BY host, hour """ hourly_res = db.query(hourly_sql, {"hosts": top_hosts}) hourly_map: dict = defaultdict(lambda: [0] * 24) for row in hourly_res.result_rows: h = str(row[0]) hour = int(row[1]) hits = int(row[2]) hourly_map[h][hour] += hits items = [] for host in top_hosts: entry = dict(host_stats[host]) entry["hourly_hits"] = hourly_map[host] items.append(entry) return {"items": items} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/matrix") async def get_heatmap_matrix(): """Matrice top-15 hôtes × 24 heures (sum hits) sur les 72 dernières heures.""" try: top_sql = """ SELECT host, sum(hits) AS total_hits FROM mabase_prod.agg_host_ip_ja4_1h WHERE window_start >= now() - INTERVAL 72 HOUR GROUP BY host ORDER BY total_hits DESC """ top_res = db.query(top_sql) top_hosts = [str(r[0]) for r in top_res.result_rows] if not top_hosts: return {"hosts": [], "matrix": []} cell_sql = """ SELECT host, toHour(window_start) AS hour, sum(hits) AS hits FROM mabase_prod.agg_host_ip_ja4_1h WHERE window_start >= now() - INTERVAL 72 HOUR AND host IN %(hosts)s GROUP BY host, hour """ cell_res = db.query(cell_sql, {"hosts": top_hosts}) matrix_map: dict = defaultdict(lambda: [0] * 24) for row in cell_res.result_rows: h = str(row[0]) hour = int(row[1]) hits = int(row[2]) matrix_map[h][hour] += hits matrix = [matrix_map[h] for h in top_hosts] return {"hosts": top_hosts, "matrix": matrix} except Exception as e: raise HTTPException(status_code=500, detail=str(e))