diff --git a/app/routers/patch_history.py b/app/routers/patch_history.py new file mode 100644 index 0000000..f995f22 --- /dev/null +++ b/app/routers/patch_history.py @@ -0,0 +1,96 @@ +"""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], + }) diff --git a/app/routers/servers.py b/app/routers/servers.py index adcd907..97adacf 100644 --- a/app/routers/servers.py +++ b/app/routers/servers.py @@ -20,7 +20,7 @@ async def servers_list(request: Request, db=Depends(get_db), domain: str = Query(None), env: str = Query(None), tier: str = Query(None), etat: str = Query(None), os: str = Query(None), owner: str = Query(None), - zone: str = Query(None), + zone: str = Query(None), licence: str = Query(None), application: str = Query(None), application_id: int = Query(None), search: str = Query(None), page: int = Query(1), sort: str = Query("hostname"), sort_dir: str = Query("asc")): @@ -29,7 +29,7 @@ async def servers_list(request: Request, db=Depends(get_db), return RedirectResponse(url="/login") filters = {"domain": domain, "env": env, "tier": tier, "etat": etat, "os": os, - "owner": owner, "zone": zone, + "owner": owner, "zone": zone, "licence": licence, "application": application, "application_id": application_id, "search": search} servers, total = list_servers(db, filters, page, sort=sort, sort_dir=sort_dir) diff --git a/app/services/server_service.py b/app/services/server_service.py index d4967b0..eb11af3 100644 --- a/app/services/server_service.py +++ b/app/services/server_service.py @@ -132,6 +132,11 @@ def list_servers(db, filters, page=1, per_page=50, sort="hostname", sort_dir="as params["zone"] = filters["zone"] if filters.get("owner"): where.append("s.patch_os_owner = :owner"); params["owner"] = filters["owner"] + if filters.get("licence"): + if filters["licence"] == "__null__": + where.append("s.licence_support IS NULL") + else: + where.append("s.licence_support = :licence"); params["licence"] = filters["licence"] if filters.get("application_id"): where.append("s.application_id = :app_id"); params["app_id"] = filters["application_id"] elif filters.get("application"): diff --git a/app/templates/servers.html b/app/templates/servers.html index 61a7888..6bfba3b 100644 --- a/app/templates/servers.html +++ b/app/templates/servers.html @@ -53,6 +53,12 @@ +