From 4f97f7d4136b9891ac420504c0a2dfb85e4518ad Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Tue, 3 Mar 2026 16:42:26 -0800 Subject: [PATCH] plugin: add scheduler iteration status card to UI Add a Scheduler Iteration card showing the current phase, active job type, idle sleep duration, and last iteration result. Update the Scheduler State table to replace In Flight / Next Detection / Interval columns with Status (Processing/Idle) and Max Duration columns to match the new iteration model. Add renderSchedulerIterationStatus JS function and update refreshJobsAndActivities to fetch and render iteration status. Co-Authored-By: Claude Opus 4.6 --- weed/admin/view/app/plugin.templ | 86 +++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 12 deletions(-) diff --git a/weed/admin/view/app/plugin.templ b/weed/admin/view/app/plugin.templ index 63dc16f05..492e494dc 100644 --- a/weed/admin/view/app/plugin.templ +++ b/weed/admin/view/app/plugin.templ @@ -115,12 +115,42 @@ templ Plugin(page string) { +
+
+
+
+
Scheduler Iteration
+ Current iteration phase and timing +
+
+
+
+
Phase
+
-
+
+
+
Current Job Type
+
-
+
+
+
Idle Sleep
+
-
+
+
+
Last Iteration
+
-
+
+
+
+
+
+
Scheduler State
- Per job type detection schedule and execution limits + Per job type execution limits and status
@@ -130,9 +160,8 @@ templ Plugin(page string) { Job Type Enabled Detector - In Flight - Next Detection - Interval + Status + Max Duration Exec Global Exec/Worker Executor Workers @@ -140,7 +169,7 @@ templ Plugin(page string) { - Loading... + Loading...
@@ -1432,7 +1461,7 @@ templ Plugin(page string) { var states = Array.isArray(state.schedulerStates) ? state.schedulerStates : []; if (!states.length) { - tbody.innerHTML = 'No scheduler state available'; + tbody.innerHTML = 'No scheduler state available'; return; } @@ -1442,8 +1471,8 @@ templ Plugin(page string) { var enabled = !!item.enabled; var inFlight = !!item.detection_in_flight; var detector = item.detector_available ? textOrDash(item.detector_worker_id) : 'No detector'; - var intervalSeconds = Number(item.detection_interval_seconds || 0); - var intervalText = intervalSeconds > 0 ? (String(intervalSeconds) + 's') : '-'; + var maxDurationSeconds = Number(item.max_job_type_duration_seconds || 0); + var maxDurationText = maxDurationSeconds > 0 ? (String(Math.round(maxDurationSeconds / 60)) + 'm') : '-'; var globalExec = Number(item.global_execution_concurrency || 0); var perWorkerExec = Number(item.per_worker_execution_concurrency || 0); var executorWorkers = Number(item.executor_worker_count || 0); @@ -1454,7 +1483,7 @@ templ Plugin(page string) { var effectiveExecText = enabled ? String(effectiveExec) : '-'; var enabledBadge = enabled ? 'Enabled' : 'Disabled'; - var inFlightBadge = inFlight ? 'Yes' : 'No'; + var statusBadge = inFlight ? 'Processing' : 'Idle'; var policyErrorHtml = ''; if (item.policy_error) { policyErrorHtml = '
' + escapeHtml(String(item.policy_error)) + '
'; @@ -1464,9 +1493,8 @@ templ Plugin(page string) { '' + escapeHtml(textOrDash(item.job_type)) + policyErrorHtml + '' + '' + enabledBadge + '' + '' + escapeHtml(detector) + '' + - '' + inFlightBadge + '' + - '' + escapeHtml(parseTime(item.next_detection_at) || '-') + '' + - '' + escapeHtml(intervalText) + '' + + '' + statusBadge + '' + + '' + escapeHtml(maxDurationText) + '' + '' + escapeHtml(globalExecText) + '' + '' + escapeHtml(perWorkerExecText) + '' + '' + escapeHtml(executorWorkersText) + '' + @@ -2780,6 +2808,37 @@ templ Plugin(page string) { } } + function renderSchedulerIterationStatus(schedulerStatus) { + var phaseEl = document.getElementById('plugin-scheduler-phase'); + var currentJtEl = document.getElementById('plugin-scheduler-current-jt'); + var idleSleepEl = document.getElementById('plugin-scheduler-idle-sleep'); + var lastIterEl = document.getElementById('plugin-scheduler-last-iteration'); + if (!phaseEl) { + return; + } + + var sched = (schedulerStatus && schedulerStatus.scheduler) ? schedulerStatus.scheduler : {}; + var phase = String(sched.phase || 'unknown'); + var phaseColors = { idle: 'bg-secondary', acquiring_lock: 'bg-info', processing: 'bg-primary' }; + var phaseColor = phaseColors[phase] || 'bg-dark'; + phaseEl.innerHTML = '' + escapeHtml(phase) + ''; + currentJtEl.textContent = sched.current_job_type || '-'; + + var idleSleep = Number(sched.idle_sleep_seconds || 0); + idleSleepEl.textContent = idleSleep > 0 ? (String(Math.round(idleSleep / 60)) + ' min') : '-'; + + var lastEnded = parseTime(sched.last_iteration_ended_at); + var workDetected = !!sched.last_iteration_work_detected; + if (lastEnded) { + var workBadge = workDetected + ? 'work found' + : 'no work'; + lastIterEl.innerHTML = '' + escapeHtml(lastEnded) + '' + workBadge; + } else { + lastIterEl.textContent = '-'; + } + } + async function refreshJobsAndActivities() { var executionStateFilter = document.getElementById('plugin-monitor-job-state-filter'); @@ -2788,10 +2847,12 @@ templ Plugin(page string) { var allJobsPromise = pluginRequest('GET', '/api/plugin/jobs?limit=500'); var allActivitiesPromise = pluginRequest('GET', '/api/plugin/activities?limit=500'); var schedulerPromise = pluginRequest('GET', '/api/plugin/scheduler-states'); + var schedulerStatusPromise = pluginRequest('GET', '/api/plugin/scheduler-status'); var allJobs = await allJobsPromise; var allActivities = await allActivitiesPromise; var schedulerStates = await schedulerPromise; + var schedulerStatus = await schedulerStatusPromise; state.jobs = Array.isArray(allJobs) ? allJobs : []; state.activities = Array.isArray(allActivities) ? allActivities : []; @@ -2803,6 +2864,7 @@ templ Plugin(page string) { renderExecutionJobs(); renderExecutionActivities(); renderSchedulerStates(); + renderSchedulerIterationStatus(schedulerStatus); renderStatus(); renderJobTypeSummary(); }