"""Router Historique patching — vue de patch_history""" from fastapi import APIRouter, Request, Depends, Query from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.templating import Jinja2Templates from sqlalchemy import text from ..dependencies import get_db, get_current_user from ..config import APP_NAME router = APIRouter() templates = Jinja2Templates(directory="app/templates") @router.get("/patching/historique", response_class=HTMLResponse) async def patch_history_page(request: Request, db=Depends(get_db), year: int = Query(None), week: int = Query(None), hostname: str = Query(None), page: int = Query(1)): user = get_current_user(request) if not user: return RedirectResponse(url="/login") from datetime import datetime if not year: year = datetime.now().year per_page = 100 offset = (page - 1) * per_page # KPIs kpis = {} kpis["total"] = db.execute(text( "SELECT COUNT(*) FROM patch_history WHERE EXTRACT(YEAR FROM date_patch)=:y" ), {"y": year}).scalar() kpis["servers"] = db.execute(text( "SELECT COUNT(DISTINCT server_id) FROM patch_history WHERE EXTRACT(YEAR FROM date_patch)=:y" ), {"y": year}).scalar() kpis["patchables"] = db.execute(text( "SELECT COUNT(*) FROM servers WHERE etat='Production' AND patch_os_owner='secops'" )).scalar() kpis["never"] = db.execute(text(""" SELECT COUNT(*) FROM servers s WHERE s.etat='Production' AND s.patch_os_owner='secops' AND NOT EXISTS (SELECT 1 FROM patch_history ph WHERE ph.server_id=s.id AND EXTRACT(YEAR FROM ph.date_patch)=:y) """), {"y": year}).scalar() kpis["coverage_pct"] = round((kpis["servers"] / kpis["patchables"] * 100), 1) if kpis["patchables"] else 0 # Par semaine by_week = db.execute(text(""" SELECT TO_CHAR(date_patch, 'IW') as week_num, COUNT(DISTINCT server_id) as servers FROM patch_history WHERE EXTRACT(YEAR FROM date_patch)=:y GROUP BY TO_CHAR(date_patch, 'IW') ORDER BY week_num """), {"y": year}).fetchall() # Filtres where = ["EXTRACT(YEAR FROM ph.date_patch)=:y"] params = {"y": year, "limit": per_page, "offset": offset} if week: where.append("EXTRACT(WEEK FROM ph.date_patch)=:wk") params["wk"] = week if hostname: where.append("s.hostname ILIKE :h") params["h"] = f"%{hostname}%" wc = " AND ".join(where) total_filtered = db.execute(text( f"SELECT COUNT(*) FROM patch_history ph JOIN servers s ON ph.server_id=s.id WHERE {wc}" ), params).scalar() rows = db.execute(text(f""" SELECT s.id as sid, s.hostname, s.os_family, s.etat, ph.date_patch, ph.status, ph.notes, z.name as zone FROM patch_history ph JOIN servers s ON ph.server_id = s.id LEFT JOIN zones z ON s.zone_id = z.id WHERE {wc} ORDER BY ph.date_patch DESC LIMIT :limit OFFSET :offset """), params).fetchall() # Années dispo years = db.execute(text(""" SELECT DISTINCT EXTRACT(YEAR FROM date_patch)::int as y FROM patch_history ORDER BY y DESC """)).fetchall() return templates.TemplateResponse("patch_history.html", { "request": request, "user": user, "app_name": APP_NAME, "kpis": kpis, "by_week": by_week, "rows": rows, "year": year, "week": week, "hostname": hostname, "page": page, "per_page": per_page, "total_filtered": total_filtered, "years": [y.y for y in years], })