fix(scheduler): prevent auto-enable with wrong interval on page load

Root cause: GET /api/sync/schedule returned interval_minutes=null when
scheduler was stopped, causing dropdown to stay on first HTML option
(1 min). Setting .value programmatically could trigger onchange, sending
a second PUT with interval=1 right after the user's intended interval.

- GET schedule falls back to DB/default (10 min) when scheduler is off
- Add _schedulerLoading flag to block onchange during loadSchedulerStatus
- Default interval 10 min everywhere (was 5 in backend)
- Cache bust dashboard.js v=33

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-04-01 13:57:22 +00:00
parent 1a912b5fa4
commit aa9e580e1b
5 changed files with 15 additions and 8 deletions

View File

@@ -53,7 +53,7 @@ async def lifespan(app: FastAPI):
try: try:
config = await sqlite_service.get_scheduler_config() config = await sqlite_service.get_scheduler_config()
if config.get("enabled") == "True": if config.get("enabled") == "True":
interval = int(config.get("interval_minutes", "5")) interval = int(config.get("interval_minutes", "10"))
scheduler_service.start_scheduler(interval) scheduler_service.start_scheduler(interval)
except Exception: except Exception:
pass pass

View File

@@ -804,8 +804,12 @@ async def update_schedule(config: ScheduleConfig):
@router.get("/api/sync/schedule") @router.get("/api/sync/schedule")
async def get_schedule(): async def get_schedule():
"""Get current scheduler status.""" """Get current scheduler status (falls back to DB for interval)."""
return scheduler_service.get_scheduler_status() status = scheduler_service.get_scheduler_status()
if status["interval_minutes"] is None:
config = await sqlite_service.get_scheduler_config()
status["interval_minutes"] = int(config.get("interval_minutes", "10"))
return status
@router.get("/api/settings") @router.get("/api/settings")

View File

@@ -15,7 +15,7 @@ def init_scheduler():
logger.info("Scheduler initialized") logger.info("Scheduler initialized")
def start_scheduler(interval_minutes: int = 5): def start_scheduler(interval_minutes: int = 10):
"""Start the scheduler with the given interval.""" """Start the scheduler with the given interval."""
global _is_running global _is_running
if _scheduler is None: if _scheduler is None:

View File

@@ -11,6 +11,7 @@ let _lastRunId = null;
let _currentRunId = null; let _currentRunId = null;
let _pollIntervalMs = 5000; // default, overridden from settings let _pollIntervalMs = 5000; // default, overridden from settings
let _knownLastRunId = null; // track last_run.run_id to detect missed syncs let _knownLastRunId = null; // track last_run.run_id to detect missed syncs
let _schedulerLoading = false; // prevent onchange during programmatic load
// ── Init ────────────────────────────────────────── // ── Init ──────────────────────────────────────────
@@ -202,6 +203,7 @@ async function toggleScheduler() {
} }
async function updateSchedulerInterval() { async function updateSchedulerInterval() {
if (_schedulerLoading) return; // ignore programmatic changes during load
const enabled = document.getElementById('schedulerToggle').checked; const enabled = document.getElementById('schedulerToggle').checked;
if (enabled) { if (enabled) {
await toggleScheduler(); await toggleScheduler();
@@ -209,15 +211,16 @@ async function updateSchedulerInterval() {
} }
async function loadSchedulerStatus() { async function loadSchedulerStatus() {
_schedulerLoading = true;
try { try {
const res = await fetch('/api/sync/schedule'); const res = await fetch('/api/sync/schedule');
const data = await res.json(); const data = await res.json();
document.getElementById('schedulerToggle').checked = data.enabled || false; document.getElementById('schedulerToggle').checked = data.enabled || false;
if (data.interval_minutes) { document.getElementById('schedulerInterval').value = data.interval_minutes || 10;
document.getElementById('schedulerInterval').value = data.interval_minutes;
}
} catch (err) { } catch (err) {
console.error('loadSchedulerStatus error:', err); console.error('loadSchedulerStatus error:', err);
} finally {
_schedulerLoading = false;
} }
} }

View File

@@ -114,5 +114,5 @@
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script src="{{ request.scope.get('root_path', '') }}/static/js/dashboard.js?v=32"></script> <script src="{{ request.scope.get('root_path', '') }}/static/js/dashboard.js?v=33"></script>
{% endblock %} {% endblock %}