diff --git a/app/routers/qualys.py b/app/routers/qualys.py index c00380f..63c9ef5 100644 --- a/app/routers/qualys.py +++ b/app/routers/qualys.py @@ -479,6 +479,58 @@ async def qualys_agents_page(request: Request, db=Depends(get_db)): return templates.TemplateResponse("qualys_agents.html", ctx) +@router.get("/qualys/agents/export-no-agent") +async def export_no_agent_csv(request: Request, db=Depends(get_db)): + user = get_current_user(request) + if not user: + return RedirectResponse(url="/login") + import io, csv as _csv + rows = db.execute(text(""" + SELECT s.hostname, s.os_family, s.etat, d.name as domain, e.name as env, z.name as zone + FROM servers s + LEFT JOIN domain_environments de ON s.domain_env_id = de.id + LEFT JOIN domains d ON de.domain_id = d.id + LEFT JOIN environments e ON de.environment_id = e.id + LEFT JOIN zones z ON s.zone_id = z.id + WHERE NOT EXISTS (SELECT 1 FROM qualys_assets qa WHERE LOWER(qa.hostname) = LOWER(s.hostname)) + ORDER BY s.hostname + """)).fetchall() + output = io.StringIO() + w = _csv.writer(output, delimiter=";") + w.writerow(["Hostname", "OS", "Domaine", "Environnement", "Zone", "Etat"]) + for r in rows: + w.writerow([r.hostname, r.os_family or "", r.domain or "", r.env or "", r.zone or "", r.etat or ""]) + output.seek(0) + return StreamingResponse( + iter(["\ufeff" + output.getvalue()]), + media_type="text/csv", + headers={"Content-Disposition": "attachment; filename=serveurs_sans_agent.csv"}) + + +@router.get("/qualys/agents/export-inactive") +async def export_inactive_csv(request: Request, db=Depends(get_db)): + user = get_current_user(request) + if not user: + return RedirectResponse(url="/login") + import io, csv as _csv + rows = db.execute(text(""" + SELECT qa.hostname, qa.os, qa.agent_version, qa.last_checkin, s.etat + FROM qualys_assets qa LEFT JOIN servers s ON qa.server_id = s.id + WHERE qa.agent_status ILIKE '%inactive%' ORDER BY qa.hostname + """)).fetchall() + output = io.StringIO() + w = _csv.writer(output, delimiter=";") + w.writerow(["Hostname", "OS", "Version agent", "Dernier check-in", "Etat"]) + for r in rows: + lc = str(r.last_checkin)[:10] if r.last_checkin else "" + w.writerow([r.hostname, r.os or "", r.agent_version or "", lc, r.etat or ""]) + output.seek(0) + return StreamingResponse( + iter(["\ufeff" + output.getvalue()]), + media_type="text/csv", + headers={"Content-Disposition": "attachment; filename=agents_inactifs.csv"}) + + @router.get("/qualys/vulns/{ip}", response_class=HTMLResponse) async def qualys_vulns_detail(request: Request, ip: str, db=Depends(get_db)): """Retourne le detail des vulns severity 3,4,5 pour une IP (fragment HTMX)""" diff --git a/app/templates/qualys_agents.html b/app/templates/qualys_agents.html index 224aa19..ab33da4 100644 --- a/app/templates/qualys_agents.html +++ b/app/templates/qualys_agents.html @@ -87,7 +87,8 @@ {% if no_agent_servers %}
-

Serveurs sans agent Qualys ({{ no_agent_servers|length }})

+

Serveurs sans agent Qualys ({{ no_agent_servers|length }})

+ Exporter CSV
@@ -147,7 +148,10 @@ {% if inactive_agents %}
-

* Agents inactifs ({{ inactive_agents|length }})

+
+

* Agents inactifs ({{ inactive_agents|length }})

+ Exporter CSV +
* Légende : Ces serveurs ont un agent Qualys installé mais qui ne communique plus avec le cloud Qualys. Causes possibles : serveur éteint, flux réseau bloqué (port 443 vers qualysagent.qualys.eu), agent crashé, ou OS non supporté (RHEL 5 EOL).