diff --git a/app/routers/qualys.py b/app/routers/qualys.py index 448d57f..8c519ec 100644 --- a/app/routers/qualys.py +++ b/app/routers/qualys.py @@ -518,7 +518,8 @@ def qualys_agents_page(request: Request, db=Depends(get_db)): @router.post("/qualys/agents/refresh") -def qualys_agents_refresh(request: Request, db=Depends(get_db)): +def qualys_agents_refresh(request: Request, db=Depends(get_db), mode: str = "diff"): + """Sync Qualys. mode=diff (rapide, depuis dernier sync) ou full (complet).""" from fastapi.responses import JSONResponse user = get_current_user(request) if not user: @@ -528,7 +529,7 @@ def qualys_agents_refresh(request: Request, db=Depends(get_db)): return JSONResponse({"ok": False, "msg": "Permission refusée"}, status_code=403) from ..services.qualys_service import refresh_all_agents try: - stats = refresh_all_agents(db) + stats = refresh_all_agents(db, mode=mode) if stats.get("busy"): return JSONResponse(stats, status_code=409) if not stats.get("ok"): diff --git a/app/services/qualys_service.py b/app/services/qualys_service.py index 872b5a6..a44d05a 100644 --- a/app/services/qualys_service.py +++ b/app/services/qualys_service.py @@ -544,28 +544,46 @@ def get_cache_stats(): return _cache.stats() -def refresh_all_agents(db): - """Rafraichit tous les agents depuis l'API Qualys QPS (bulk, paginé)""" +def refresh_all_agents(db, mode="diff"): + """Rafraichit les agents depuis l'API Qualys QPS. + + mode='diff' (defaut) : ne pull que les assets dont lastCheckedIn > dernier sync diff + Court (~30s), pour cron frequent. + mode='full' : pull tous les assets matchant le filtre tag. + Long (5-10 min), pour ménage hebdo. + """ global _refresh_running if not _refresh_lock.acquire(blocking=False): return {"ok": False, "msg": "Une synchronisation Qualys est déjà en cours", "busy": True} _refresh_running = True _refresh_cancel.clear() try: - return _refresh_all_agents_impl(db) + return _refresh_all_agents_impl(db, mode=mode) finally: _refresh_running = False _refresh_lock.release() -def _refresh_all_agents_impl(db): +def _refresh_all_agents_impl(db, mode="diff"): """Implémentation réelle du refresh (appelée sous verrou)""" - # Early exit si tous les assets ont moins de 40 min (pas besoin d'appeler Qualys) - total = db.execute(text("SELECT COUNT(*) FROM qualys_assets")).scalar() or 0 - if total > 0: - stale = db.execute(text("SELECT COUNT(*) FROM qualys_assets WHERE updated_at < now() - interval '40 minutes'")).scalar() or 0 - if stale == 0: - return {"ok": True, "msg": f"Tous les {total} assets sont récents (< 40 min), rien à faire", "skipped_all": True} + from .secrets_service import get_secret, set_secret + from datetime import datetime, timezone + + # En mode diff : recupere le timestamp du dernier diff sync + last_diff_iso = None + if mode == "diff": + last_diff_iso = get_secret(db, "qualys_last_diff_sync") + # Early exit seulement en diff : si tous recents ET dernier diff < 30 min + total = db.execute(text("SELECT COUNT(*) FROM qualys_assets")).scalar() or 0 + if total > 0 and last_diff_iso: + try: + last_dt = datetime.fromisoformat(last_diff_iso.replace("Z", "+00:00")) + age_min = (datetime.now(timezone.utc) - last_dt).total_seconds() / 60 + if age_min < 5: + return {"ok": True, "msg": f"Diff sync deja effectue il y a {int(age_min)} min, rien a faire", + "skipped_all": True, "mode": mode} + except Exception: + pass qualys_url, qualys_user, qualys_pass, qualys_proxy = _get_qualys_creds(db) if not qualys_user: @@ -600,6 +618,9 @@ def _refresh_all_agents_impl(db): criteria = [{"field": "tagName", "operator": "CONTAINS", "value": tag_filter}] if last_id: criteria.append({"field": "id", "operator": "GREATER", "value": str(last_id)}) + # Mode diff : ajoute filtre lastCheckedIn > timestamp dernier diff sync + if mode == "diff" and last_diff_iso: + criteria.append({"field": "lastCheckedIn", "operator": "GREATER", "value": last_diff_iso}) payload = {"ServiceRequest": { "preferences": {"limitResults": 100}, "filters": {"Criteria": criteria} @@ -733,7 +754,16 @@ def _refresh_all_agents_impl(db): last_id = new_last_id stats["ok"] = True - stats["msg"] = f"{stats['created']} créés, {stats['updated']} mis à jour ({stats['pages']} pages, {stats['errors']} erreurs, {len(tag_filters)} filtres)" + stats["mode"] = mode + stats["msg"] = f"[{mode}] {stats['created']} créés, {stats['updated']} mis à jour ({stats['pages']} pages, {stats['errors']} erreurs, {len(tag_filters)} filtres)" + # Memorise le timestamp pour le prochain diff sync + if mode == "diff": + try: + now_iso = datetime.now(timezone.utc).isoformat() + set_secret(db, "qualys_last_diff_sync", now_iso, "Timestamp dernier sync Qualys diff") + db.commit() + except Exception: + pass return stats diff --git a/app/templates/qualys_agents.html b/app/templates/qualys_agents.html index b526b03..fbdacc7 100644 --- a/app/templates/qualys_agents.html +++ b/app/templates/qualys_agents.html @@ -7,8 +7,11 @@
Activation keys et versions des agents déployés