Fix sentinel countdown: sync to actual scan schedule, not page load
- Sentinel thread sets next_scan_ts = time.time() + interval BEFORE sleeping - API returns next_scan_in derived from real next_scan_ts, not estimated - Frontend calculates server clock offset and counts down to the actual target timestamp — refresh shows the same remaining time, not a reset - Shows ✓ in green when scan fires, resumes countdown on next poll Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
357918013d
commit
7948089f04
@ -775,13 +775,21 @@ async function loadThreats() {
|
|||||||
|
|
||||||
sentinelCard.appendChild(sHeader);
|
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);
|
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._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');
|
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);
|
}, 1000);
|
||||||
|
|
||||||
if (ss.last_error) {
|
if (ss.last_error) {
|
||||||
@ -6390,7 +6398,7 @@ SENTINEL_MODEL = "qwen2.5:latest"
|
|||||||
SENTINEL_INTERVAL = 300 # 5 minutes
|
SENTINEL_INTERVAL = 300 # 5 minutes
|
||||||
_sentinel_last_pos = 0
|
_sentinel_last_pos = 0
|
||||||
_sentinel_results = [] # last 50 analyses
|
_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):
|
def _sentinel_log_entry(msg):
|
||||||
"""Write to sentinel log file."""
|
"""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")
|
_sentinel_log_entry("SENTINEL_START model=" + SENTINEL_MODEL + " interval=" + str(SENTINEL_INTERVAL) + "s")
|
||||||
while True:
|
while True:
|
||||||
|
_sentinel_stats["next_scan_ts"] = time.time() + SENTINEL_INTERVAL
|
||||||
time.sleep(SENTINEL_INTERVAL)
|
time.sleep(SENTINEL_INTERVAL)
|
||||||
try:
|
try:
|
||||||
_sentinel_scan()
|
_sentinel_scan()
|
||||||
@ -6585,18 +6594,16 @@ def _sentinel_loop():
|
|||||||
@app.route("/api/admin/sentinel")
|
@app.route("/api/admin/sentinel")
|
||||||
@admin_required
|
@admin_required
|
||||||
def admin_sentinel_status():
|
def admin_sentinel_status():
|
||||||
last_ts = _sentinel_stats.get("last_run_ts", 0)
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
elapsed = now - last_ts if last_ts else 0
|
next_ts = _sentinel_stats.get("next_scan_ts", 0)
|
||||||
next_in = max(0, SENTINEL_INTERVAL - elapsed)
|
next_in = max(0, next_ts - now)
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"stats": _sentinel_stats,
|
"stats": _sentinel_stats,
|
||||||
"recent_verdicts": list(reversed(_sentinel_results[-20:])),
|
"recent_verdicts": list(reversed(_sentinel_results[-20:])),
|
||||||
"model": SENTINEL_MODEL,
|
"model": SENTINEL_MODEL,
|
||||||
"interval": SENTINEL_INTERVAL,
|
"interval": SENTINEL_INTERVAL,
|
||||||
"elapsed_since_scan": round(elapsed, 1),
|
|
||||||
"next_scan_in": round(next_in, 1),
|
"next_scan_in": round(next_in, 1),
|
||||||
"server_time": now
|
"server_time": round(now, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user