From cdcb85917dc41f8b0b60bf1048327f0ed5e28728 Mon Sep 17 00:00:00 2001 From: Admin MPCZ Date: Mon, 27 Apr 2026 23:25:50 +0200 Subject: [PATCH] feat(qualys/agents): audit en background thread + page d'attente auto-refresh (fix ERR_CONNECTION_RESET sur audits longs) --- app/routers/qualys.py | 23 +++++++++--- app/services/realtime_audit_service.py | 52 ++++++++++++++++++++++++++ app/templates/qualys_agent_audit.html | 34 ++++++++++++++++- 3 files changed, 102 insertions(+), 7 deletions(-) diff --git a/app/routers/qualys.py b/app/routers/qualys.py index 723847e..ced0e8a 100644 --- a/app/routers/qualys.py +++ b/app/routers/qualys.py @@ -1354,16 +1354,29 @@ async def qualys_asset_delete(request: Request, asset_id: int, db=Depends(get_db @router.get("/qualys/agents/{hostname}/audit-qualys", response_class=HTMLResponse) -def qualys_agent_audit_page(hostname: str, request: Request, db=Depends(get_db)): - """Audit cible Qualys Agent : status service + version + logs (agent + systeme).""" +def qualys_agent_audit_page(hostname: str, request: Request, db=Depends(get_db), + refresh: int = 0): + """Audit cible Qualys Agent (async). Background thread + page auto-refresh.""" user = get_current_user(request) if not user: return RedirectResponse(url="/login") perms = get_user_perms(db, user) if not can_view(perms, "qualys"): return RedirectResponse(url="/dashboard") - from app.services.realtime_audit_service import audit_qualys_agent_only - audit = audit_qualys_agent_only(hostname) + from app.services.realtime_audit_service import ( + start_qualys_audit_async, get_qualys_audit_state + ) + state = get_qualys_audit_state(hostname) + if refresh or not state: + start_qualys_audit_async(hostname, force=bool(refresh)) + state = get_qualys_audit_state(hostname) ctx = base_context(request, db, user) - ctx.update({"audit": audit, "hostname": hostname}) + ctx.update({ + "hostname": hostname, + "audit_status": (state or {}).get("status", "pending"), + "audit_started_at": (state or {}).get("started_at"), + "audit_finished_at": (state or {}).get("finished_at"), + "audit": (state or {}).get("result"), + "audit_error": (state or {}).get("error"), + }) return templates.TemplateResponse("qualys_agent_audit.html", ctx) diff --git a/app/services/realtime_audit_service.py b/app/services/realtime_audit_service.py index cc00a82..c339b82 100644 --- a/app/services/realtime_audit_service.py +++ b/app/services/realtime_audit_service.py @@ -606,6 +606,58 @@ QUALYS_AGENT_CMDS = { } +import threading as _threading +_qualys_audit_cache = {} # hostname -> {status, result, started_at, finished_at, error} +_qualys_audit_lock = _threading.Lock() + + +def start_qualys_audit_async(hostname, force=False): + """Lance audit_qualys_agent_only en background. Reuse run pending récent (<2min).""" + with _qualys_audit_lock: + existing = _qualys_audit_cache.get(hostname) + if existing and existing.get("status") == "pending" and not force: + age = (datetime.now() - existing["started_at"]).total_seconds() + if age < 120: + return False + _qualys_audit_cache[hostname] = { + "status": "pending", + "result": None, + "started_at": datetime.now(), + "finished_at": None, + "error": None, + } + + def _runner(): + try: + res = audit_qualys_agent_only(hostname) + with _qualys_audit_lock: + state = _qualys_audit_cache.get(hostname, {}) + state.update({ + "status": "ok", + "result": res, + "finished_at": datetime.now(), + }) + _qualys_audit_cache[hostname] = state + except Exception as ex: + with _qualys_audit_lock: + state = _qualys_audit_cache.get(hostname, {}) + state.update({ + "status": "error", + "error": str(ex), + "finished_at": datetime.now(), + }) + _qualys_audit_cache[hostname] = state + + t = _threading.Thread(target=_runner, daemon=True) + t.start() + return True + + +def get_qualys_audit_state(hostname): + with _qualys_audit_lock: + return dict(_qualys_audit_cache.get(hostname, {})) or None + + def audit_qualys_agent_only(hostname): """Audit cible Qualys Agent uniquement: status service + version + logs. Utilise _resolve + _connect + _run comme audit_single_server. diff --git a/app/templates/qualys_agent_audit.html b/app/templates/qualys_agent_audit.html index c3303c1..bdace56 100644 --- a/app/templates/qualys_agent_audit.html +++ b/app/templates/qualys_agent_audit.html @@ -2,17 +2,45 @@ {% block title %}Audit Qualys Agent — {{ hostname }}{% endblock %} {% block content %} +{% if audit_status == 'pending' %} + +{% endif %} +

Audit Qualys Agent

-

{{ hostname }}{% if audit.resolved_fqdn %} → {{ audit.resolved_fqdn }}{% endif %}

+

{{ hostname }}{% if audit and audit.resolved_fqdn %} → {{ audit.resolved_fqdn }}{% endif %}

- Relancer + Relancer ← Retour
+{% if audit_status == 'pending' %} + +
+
+ + + +
+ ⏳ Audit en cours… + Connexion SSH + collecte status, version, logs (10–30s typique) +
Démarré : {{ audit_started_at.strftime('%H:%M:%S') if audit_started_at else '?' }} — page rafraichie auto toutes les 3s
+
+
+
+ + +{% elif audit_status == 'error' %} +
+ ✗ Erreur audit +
{{ audit_error or '(pas de détail)' }}
+
+ +{% else %} +