feat(qualys/agents): audit en background thread + page d'attente auto-refresh (fix ERR_CONNECTION_RESET sur audits longs)
This commit is contained in:
parent
26e05d63ac
commit
cdcb85917d
@ -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)
|
@router.get("/qualys/agents/{hostname}/audit-qualys", response_class=HTMLResponse)
|
||||||
def qualys_agent_audit_page(hostname: str, request: Request, db=Depends(get_db)):
|
def qualys_agent_audit_page(hostname: str, request: Request, db=Depends(get_db),
|
||||||
"""Audit cible Qualys Agent : status service + version + logs (agent + systeme)."""
|
refresh: int = 0):
|
||||||
|
"""Audit cible Qualys Agent (async). Background thread + page auto-refresh."""
|
||||||
user = get_current_user(request)
|
user = get_current_user(request)
|
||||||
if not user:
|
if not user:
|
||||||
return RedirectResponse(url="/login")
|
return RedirectResponse(url="/login")
|
||||||
perms = get_user_perms(db, user)
|
perms = get_user_perms(db, user)
|
||||||
if not can_view(perms, "qualys"):
|
if not can_view(perms, "qualys"):
|
||||||
return RedirectResponse(url="/dashboard")
|
return RedirectResponse(url="/dashboard")
|
||||||
from app.services.realtime_audit_service import audit_qualys_agent_only
|
from app.services.realtime_audit_service import (
|
||||||
audit = audit_qualys_agent_only(hostname)
|
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 = 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)
|
return templates.TemplateResponse("qualys_agent_audit.html", ctx)
|
||||||
|
|||||||
@ -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):
|
def audit_qualys_agent_only(hostname):
|
||||||
"""Audit cible Qualys Agent uniquement: status service + version + logs.
|
"""Audit cible Qualys Agent uniquement: status service + version + logs.
|
||||||
Utilise _resolve + _connect + _run comme audit_single_server.
|
Utilise _resolve + _connect + _run comme audit_single_server.
|
||||||
|
|||||||
@ -2,17 +2,45 @@
|
|||||||
{% block title %}Audit Qualys Agent — {{ hostname }}{% endblock %}
|
{% block title %}Audit Qualys Agent — {{ hostname }}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
{% if audit_status == 'pending' %}
|
||||||
|
<meta http-equiv="refresh" content="3">
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-xl font-bold text-cyber-accent">Audit Qualys Agent</h2>
|
<h2 class="text-xl font-bold text-cyber-accent">Audit Qualys Agent</h2>
|
||||||
<p class="text-xs text-gray-500 mt-1 font-mono">{{ hostname }}{% if audit.resolved_fqdn %} → {{ audit.resolved_fqdn }}{% endif %}</p>
|
<p class="text-xs text-gray-500 mt-1 font-mono">{{ hostname }}{% if audit and audit.resolved_fqdn %} → {{ audit.resolved_fqdn }}{% endif %}</p>
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex;gap:8px">
|
<div style="display:flex;gap:8px">
|
||||||
<a href="/qualys/agents/{{ hostname }}/audit-qualys" class="btn-sm bg-cyber-border text-gray-300 px-3 py-2 text-xs">Relancer</a>
|
<a href="/qualys/agents/{{ hostname }}/audit-qualys?refresh=1" class="btn-sm bg-cyber-border text-gray-300 px-3 py-2 text-xs">Relancer</a>
|
||||||
<a href="/qualys/agents#inactive-list" class="btn-sm bg-cyber-border text-gray-300 px-3 py-2 text-xs">← Retour</a>
|
<a href="/qualys/agents#inactive-list" class="btn-sm bg-cyber-border text-gray-300 px-3 py-2 text-xs">← Retour</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if audit_status == 'pending' %}
|
||||||
|
<!-- Bandeau "audit en cours" -->
|
||||||
|
<div class="card p-4 mb-4" style="border:1px solid #f59e0b;background:rgba(245,158,11,0.08)">
|
||||||
|
<div class="flex items-center gap-3 text-sm">
|
||||||
|
<svg style="width:24px;height:24px;animation:spin 1s linear infinite" viewBox="0 0 24 24" fill="none" stroke="#f59e0b" stroke-width="2">
|
||||||
|
<circle cx="12" cy="12" r="10" stroke-opacity="0.3"/><path d="M12 2a10 10 0 0 1 10 10" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
<div>
|
||||||
|
<span class="font-bold text-cyber-yellow">⏳ Audit en cours…</span>
|
||||||
|
<span class="text-xs text-gray-400 ml-2">Connexion SSH + collecte status, version, logs (10–30s typique)</span>
|
||||||
|
<div class="text-xs text-gray-500 mt-1">Démarré : {{ audit_started_at.strftime('%H:%M:%S') if audit_started_at else '?' }} — page rafraichie auto toutes les 3s</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<style>@keyframes spin{to{transform:rotate(360deg)}}</style>
|
||||||
|
|
||||||
|
{% elif audit_status == 'error' %}
|
||||||
|
<div class="card p-4 mb-4" style="border:1px solid #ef4444;background:rgba(239,68,68,0.08)">
|
||||||
|
<span class="font-bold text-cyber-red">✗ Erreur audit</span>
|
||||||
|
<pre style="background:#0b0f1a;color:#e5e7eb;padding:10px;border-radius:4px;font-size:11px;margin-top:8px;white-space:pre-wrap">{{ audit_error or '(pas de détail)' }}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
|
||||||
<!-- Bandeau statut connexion -->
|
<!-- Bandeau statut connexion -->
|
||||||
<div class="card p-3 mb-4" style="
|
<div class="card p-3 mb-4" style="
|
||||||
{% if audit.status == 'OK' %}border:1px solid #22c55e;background:rgba(34,197,94,0.08);
|
{% if audit.status == 'OK' %}border:1px solid #22c55e;background:rgba(34,197,94,0.08);
|
||||||
@ -70,4 +98,6 @@
|
|||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user