Export CSV: serveurs sans agent + agents inactifs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Khalid MOUTAOUAKIL 2026-04-07 01:24:49 +02:00
parent 5db47c497f
commit 7f5e5c83eb
2 changed files with 58 additions and 2 deletions

View File

@ -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)"""

View File

@ -87,7 +87,8 @@
{% if no_agent_servers %}
<div class="card p-4 mb-4" x-data="{fHost:'', fOs:'', fDom:'', fEnv:'', fEtat:''}">
<div class="flex justify-between items-center mb-3">
<h3 class="text-sm font-bold text-cyber-red">Serveurs sans agent Qualys (<span x-text="document.querySelectorAll('#noagent-body tr:not([style*=none])').length">{{ no_agent_servers|length }}</span>)</h3>
<h3 class="text-sm font-bold text-cyber-red">Serveurs sans agent Qualys ({{ no_agent_servers|length }})</h3>
<a href="/qualys/agents/export-no-agent" class="btn-sm bg-cyber-green text-black px-3 py-1 text-xs">Exporter CSV</a>
</div>
<div class="flex gap-2 mb-3">
<input type="text" x-model="fHost" placeholder="Hostname..." class="text-xs py-1 px-2 flex-1 font-mono">
@ -147,7 +148,10 @@
<!-- Agents inactifs -->
{% if inactive_agents %}
<div id="inactive-list" class="card p-4 mb-4">
<h3 class="text-sm font-bold text-cyber-red mb-3">* Agents inactifs ({{ inactive_agents|length }})</h3>
<div class="flex justify-between items-center mb-3">
<h3 class="text-sm font-bold text-cyber-red">* Agents inactifs ({{ inactive_agents|length }})</h3>
<a href="/qualys/agents/export-inactive" class="btn-sm bg-cyber-green text-black px-3 py-1 text-xs">Exporter CSV</a>
</div>
<div class="card p-3 mb-3 text-xs text-gray-400" style="background:#111827;">
<b>* Légende :</b> 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).