diff --git a/llm_team_ui.py b/llm_team_ui.py index 117bf93..82f7f20 100644 --- a/llm_team_ui.py +++ b/llm_team_ui.py @@ -775,13 +775,21 @@ async function loadThreats() { sentinelCard.appendChild(sHeader); - // Start countdown + // Countdown synced to server's next_scan_ts + // Store the absolute target time so refresh doesn't reset if (window._sentinelTimer) clearInterval(window._sentinelTimer); - window._sentinelCountdown = nextIn; + var serverNow = sentinel.server_time || (Date.now()/1000); + var nextScanTs = serverNow + nextIn; + window._sentinelTargetTs = nextScanTs; + window._sentinelServerOffset = serverNow - (Date.now()/1000); // clock difference window._sentinelTimer = setInterval(function(){ - window._sentinelCountdown = Math.max(0, window._sentinelCountdown - 1); + var localNow = (Date.now()/1000) + (window._sentinelServerOffset||0); + var remaining = Math.max(0, (window._sentinelTargetTs||0) - localNow); var el = document.getElementById('sentinel-countdown'); - if (el) { el.textContent = Math.ceil(window._sentinelCountdown) || '...'; if (window._sentinelCountdown <= 0) { el.textContent = '✓'; el.style.color = '#4ade80'; clearInterval(window._sentinelTimer); } } + if (el) { + if (remaining > 0) { el.textContent = Math.ceil(remaining); el.style.color = '#d946ef'; } + else { el.textContent = '✓'; el.style.color = '#4ade80'; } + } }, 1000); if (ss.last_error) { @@ -6390,7 +6398,7 @@ SENTINEL_MODEL = "qwen2.5:latest" SENTINEL_INTERVAL = 300 # 5 minutes _sentinel_last_pos = 0 _sentinel_results = [] # last 50 analyses -_sentinel_stats = {"scans": 0, "bans": 0, "last_run": None, "last_error": None} +_sentinel_stats = {"scans": 0, "bans": 0, "last_run": None, "last_error": None, "next_scan_ts": 0} def _sentinel_log_entry(msg): """Write to sentinel log file.""" @@ -6573,6 +6581,7 @@ def _sentinel_loop(): _sentinel_log_entry("SENTINEL_START model=" + SENTINEL_MODEL + " interval=" + str(SENTINEL_INTERVAL) + "s") while True: + _sentinel_stats["next_scan_ts"] = time.time() + SENTINEL_INTERVAL time.sleep(SENTINEL_INTERVAL) try: _sentinel_scan() @@ -6585,18 +6594,16 @@ def _sentinel_loop(): @app.route("/api/admin/sentinel") @admin_required def admin_sentinel_status(): - last_ts = _sentinel_stats.get("last_run_ts", 0) now = time.time() - elapsed = now - last_ts if last_ts else 0 - next_in = max(0, SENTINEL_INTERVAL - elapsed) + next_ts = _sentinel_stats.get("next_scan_ts", 0) + next_in = max(0, next_ts - now) return jsonify({ "stats": _sentinel_stats, "recent_verdicts": list(reversed(_sentinel_results[-20:])), "model": SENTINEL_MODEL, "interval": SENTINEL_INTERVAL, - "elapsed_since_scan": round(elapsed, 1), "next_scan_in": round(next_in, 1), - "server_time": now + "server_time": round(now, 1) })