feat: next run countdown in auto-cycle config

- auto_cycle.py: next_run_at now set to future timestamp (now + interval)
  instead of current time — was always showing wrong value
- Config.tsx: NextRunCountdown component shows live countdown (updates
  every second) + absolute local time, only visible when auto-cycle enabled

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-17 12:02:50 +02:00
parent 3818544832
commit 27d6b598e8
2 changed files with 35 additions and 3 deletions

View File

@@ -744,9 +744,10 @@ def _scheduler_loop(stop_event: threading.Event):
interval_hours = 3.0
_current_status["interval_hours"] = interval_hours
next_run = datetime.utcnow().isoformat()
from datetime import timedelta
next_run = (datetime.utcnow() + timedelta(hours=interval_hours)).isoformat()
_current_status["next_run_at"] = next_run
logger.info(f"[Scheduler] Next cycle in {interval_hours}h")
logger.info(f"[Scheduler] Next cycle in {interval_hours}h (at {next_run[:16]} UTC)")
# Wait for the interval (or until stop is signalled)
stop_event.wait(timeout=interval_hours * 3600)

View File

@@ -39,6 +39,34 @@ const PROFILE_COLORS = [
{ value: '#eab308', label: 'Jaune' },
]
function NextRunCountdown({ nextRunAt }: { nextRunAt: string }) {
const [remaining, setRemaining] = useState('')
useEffect(() => {
function update() {
const diff = new Date(nextRunAt + 'Z').getTime() - Date.now()
if (diff <= 0) { setRemaining('Imminent…'); return }
const h = Math.floor(diff / 3_600_000)
const m = Math.floor((diff % 3_600_000) / 60_000)
const s = Math.floor((diff % 60_000) / 1_000)
setRemaining(h > 0 ? `${h}h ${m.toString().padStart(2,'0')}min` : `${m}min ${s.toString().padStart(2,'0')}s`)
}
update()
const id = setInterval(update, 1_000)
return () => clearInterval(id)
}, [nextRunAt])
const absTime = new Date(nextRunAt + 'Z').toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
return (
<div className="flex items-center gap-2 text-emerald-400/80">
<span className="inline-block w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse" />
<span>Prochain cycle dans <span className="font-mono font-semibold text-emerald-300">{remaining}</span></span>
<span className="text-slate-600">({absTime} heure locale)</span>
</div>
)
}
function evNetLabel(evNet: number): { text: string; cls: string } {
if (evNet > 0.05) return { text: `EV nette +${(evNet * 100).toFixed(0)}%`, cls: 'text-emerald-400' }
if (evNet >= -0.01) return { text: 'EV nette ≈ 0', cls: 'text-yellow-400' }
@@ -692,7 +720,7 @@ export default function Config() {
<RiskProfilesCard />
{cs?.last_cycle && (
<div className="card bg-dark-700/50 mb-4 text-xs">
<div className="card bg-dark-700/50 mb-4 text-xs space-y-2">
<div className="flex items-center gap-4 flex-wrap">
<span className="text-slate-500">Dernier cycle :</span>
<span className="text-slate-300">{cs.last_cycle.started_at?.slice(0, 16)} UTC</span>
@@ -704,6 +732,9 @@ export default function Config() {
<span className="text-slate-500">Géo: {cs.last_cycle.geo_score}</span>
<span className="text-slate-500 capitalize">{cs.last_cycle.dominant_regime}</span>
</div>
{cs.enabled && cs.next_run_at && !cs.running && (
<NextRunCountdown nextRunAt={cs.next_run_at} />
)}
</div>
)}