diff --git a/app/routers/audit_full.py b/app/routers/audit_full.py
index 83164fc..a71c262 100644
--- a/app/routers/audit_full.py
+++ b/app/routers/audit_full.py
@@ -1,7 +1,7 @@
"""Router Audit Complet — import JSON, liste, detail, carte flux, carte applicative"""
import json
from fastapi import APIRouter, Request, Depends, UploadFile, File
-from fastapi.responses import HTMLResponse, RedirectResponse
+from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy import text
from ..dependencies import get_db, get_current_user, get_user_perms, can_view, base_context
@@ -192,6 +192,80 @@ async def audit_full_import(request: Request, db=Depends(get_db),
)
+@router.get("/audit-full/export-csv")
+async def audit_full_export_csv(request: Request, db=Depends(get_db)):
+ user = get_current_user(request)
+ if not user:
+ return RedirectResponse(url="/login")
+
+ import io, csv
+ filtre = request.query_params.get("filter", "")
+ search = request.query_params.get("q", "").strip()
+ domain = request.query_params.get("domain", "")
+
+ audits = get_latest_audits(db, limit=9999)
+
+ # Memes filtres que la page liste
+ if filtre == "reboot":
+ audits = [a for a in audits if a.reboot_required]
+ elif filtre == "uptime":
+ audits = [a for a in audits if a.uptime and ("month" in a.uptime or "year" in a.uptime)]
+ elif filtre and filtre.startswith("app_"):
+ app_patterns = {
+ "app_postgres": "postgres", "app_mariadb": "mariadb|mysqld",
+ "app_hana": "hdb|sapstart|HANA", "app_oracle": "ora_pmon|oracle",
+ "app_httpd": "httpd", "app_nginx": "nginx", "app_haproxy": "haproxy",
+ "app_tomcat": "tomcat|catalina", "app_nodejs": "node",
+ "app_redis": "redis", "app_mongodb": "mongod",
+ "app_elastic": "elasticsearch|opensearch", "app_container": "docker|podman",
+ "app_java": "java|\\.jar",
+ }
+ pattern = app_patterns.get(filtre, "")
+ if pattern:
+ ids = {r.id for r in db.execute(text("""
+ SELECT id FROM server_audit_full
+ WHERE status = 'ok'
+ AND (services::text ~* :pat OR listen_ports::text ~* :pat OR processes::text ~* :pat)
+ AND id IN (SELECT DISTINCT ON (hostname) id FROM server_audit_full WHERE status = 'ok' ORDER BY hostname, audit_date DESC)
+ """), {"pat": pattern}).fetchall()}
+ audits = [a for a in audits if a.id in ids]
+ if domain:
+ zone_servers = {r.hostname for r in db.execute(text(
+ "SELECT s.hostname FROM servers s JOIN zones z ON s.zone_id = z.id WHERE z.name = :name"
+ ), {"name": domain}).fetchall()}
+ if zone_servers:
+ audits = [a for a in audits if a.hostname in zone_servers]
+ else:
+ domain_servers = {r.hostname for r in db.execute(text("""
+ SELECT s.hostname FROM servers s JOIN domain_environments de ON s.domain_env_id = de.id
+ JOIN domains d ON de.domain_id = d.id WHERE d.code = :dc
+ """), {"dc": domain}).fetchall()}
+ audits = [a for a in audits if a.hostname in domain_servers]
+ if search:
+ q = search.lower()
+ audits = [a for a in audits if q in a.hostname.lower()]
+
+ # Generer CSV
+ output = io.StringIO()
+ writer = csv.writer(output, delimiter=";")
+ writer.writerow(["Hostname", "OS", "Kernel", "Uptime", "Services", "Processus",
+ "Ports", "Connexions", "Reboot requis", "Date audit"])
+ for a in audits:
+ writer.writerow([
+ a.hostname, a.os_release or "", a.kernel or "", a.uptime or "",
+ a.svc_count, a.proc_count, a.port_count, a.conn_count,
+ "Oui" if a.reboot_required else "Non",
+ a.audit_date.strftime("%Y-%m-%d %H:%M") if a.audit_date else "",
+ ])
+
+ output.seek(0)
+ return StreamingResponse(
+ iter(["\ufeff" + output.getvalue()]),
+ media_type="text/csv",
+ headers={"Content-Disposition": "attachment; filename=audit_serveurs.csv"},
+ )
+
+
@router.get("/audit-full/flow-map", response_class=HTMLResponse)
async def audit_full_flow_map(request: Request, db=Depends(get_db)):
user = get_current_user(request)
diff --git a/app/templates/audit_full_list.html b/app/templates/audit_full_list.html
index d157ff7..ac2f476 100644
--- a/app/templates/audit_full_list.html
+++ b/app/templates/audit_full_list.html
@@ -11,6 +11,7 @@
+ Exporter CSV
Carte flux