Qualys sync dual mode: diff (rapide, lastCheckedIn) + full (complet)
- refresh_all_agents accepte mode='diff'|'full' (defaut diff) - Mode diff: filtre Qualys lastCheckedIn > qualys_last_diff_sync (settings) - Mode full: pull tous les assets (comme avant) - Skip early-exit en diff si dernier diff < 5 min - 2 boutons UI: 'Sync rapide (diff)' et 'Sync complete' - JS refreshAgents(mode) passe le mode en query param
This commit is contained in:
parent
55f81de986
commit
a62f9a4146
@ -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"):
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -7,8 +7,11 @@
|
||||
<p class="text-xs text-gray-500 mt-1">Activation keys et versions des agents déployés</p>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px">
|
||||
<button id="btn-refresh" class="btn-primary px-4 py-2 text-sm" onclick="refreshAgents()">
|
||||
Rafraîchir depuis Qualys
|
||||
<button id="btn-refresh-diff" class="btn-primary px-4 py-2 text-sm" onclick="refreshAgents('diff')" title="Pull seulement les assets modifies depuis le dernier sync">
|
||||
Sync rapide (diff)
|
||||
</button>
|
||||
<button id="btn-refresh-full" class="btn-sm bg-cyber-yellow text-black px-4 py-2 text-sm" onclick="refreshAgents('full')" title="Pull complet (5-10 min). A faire 1x par jour">
|
||||
Sync complete
|
||||
</button>
|
||||
<a href="/qualys/deploy" class="btn-sm bg-cyber-border text-gray-300 px-4 py-2">Déployer</a>
|
||||
<a href="/qualys/search" class="btn-sm bg-cyber-border text-gray-300 px-4 py-2">Recherche</a>
|
||||
@ -43,22 +46,26 @@ function cancelRefresh() {
|
||||
});
|
||||
}
|
||||
|
||||
function refreshAgents() {
|
||||
var btn = document.getElementById('btn-refresh');
|
||||
function refreshAgents(mode) {
|
||||
mode = mode || 'diff';
|
||||
var btnDiff = document.getElementById('btn-refresh-diff');
|
||||
var btnFull = document.getElementById('btn-refresh-full');
|
||||
var overlay = document.getElementById('refresh-overlay');
|
||||
var timer = document.getElementById('refresh-timer');
|
||||
var msgDiv = document.getElementById('refresh-msg');
|
||||
btn.disabled = true;
|
||||
if (btnDiff) btnDiff.disabled = true;
|
||||
if (btnFull) btnFull.disabled = true;
|
||||
overlay.style.display = 'flex';
|
||||
msgDiv.style.display = 'none';
|
||||
var t0 = Date.now();
|
||||
var iv = setInterval(function(){ timer.textContent = Math.floor((Date.now()-t0)/1000) + 's'; }, 1000);
|
||||
fetch('/qualys/agents/refresh', {method:'POST', credentials:'same-origin'})
|
||||
fetch('/qualys/agents/refresh?mode=' + encodeURIComponent(mode), {method:'POST', credentials:'same-origin'})
|
||||
.then(function(r){ return r.json().then(function(d){ return {ok:r.ok, data:d}; }); })
|
||||
.then(function(res){
|
||||
clearInterval(iv);
|
||||
overlay.style.display = 'none';
|
||||
btn.disabled = false;
|
||||
if (btnDiff) btnDiff.disabled = false;
|
||||
if (btnFull) btnFull.disabled = false;
|
||||
if(res.ok && res.data.ok){
|
||||
msgDiv.style.background = '#1a5a2e';
|
||||
msgDiv.style.color = '#8f8';
|
||||
@ -75,7 +82,8 @@ function refreshAgents() {
|
||||
.catch(function(err){
|
||||
clearInterval(iv);
|
||||
overlay.style.display = 'none';
|
||||
btn.disabled = false;
|
||||
if (btnDiff) btnDiff.disabled = false;
|
||||
if (btnFull) btnFull.disabled = false;
|
||||
msgDiv.style.background = '#5a1a1a';
|
||||
msgDiv.style.color = '#ff3366';
|
||||
msgDiv.textContent = 'Erreur réseau : ' + err.message;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user