Browse Source

admin: expose per-job-type detection interval in plugin UI (#8552)

* admin: expose per-job-type detection interval in plugin UI

The detection_interval_seconds field was not editable in the admin UI.
collectAdminSettings() silently preserved the existing value, making it
impossible for users to change how often a job type checks for new work.
Users would change the global "Sleep Between Iterations" setting expecting
it to control job scheduling frequency, but that only controls the
scheduler loop's idle polling rate.

Add a "Detection Interval (s)" input to the per-job-type admin settings
form so users can actually configure it.

Fixes #8549

* admin: remove global Sleep Between Iterations setting

Now that per-job-type detection intervals are exposed in the UI, the
global IdleSleepSeconds setting is redundant and confusing. It only
controlled the scheduler loop's idle polling rate, which is always
overridden by earliestNextDetectionAt() when job types exist.

Replace the three usages with simpler alternatives:
- Scheduler loop sleep: use defaultSchedulerIdleSleep constant
- Initial delay for new job types: use policy.DetectionInterval/2
  (more logical since it's already per-job-type)
- Status fallback: use the constant

The API endpoints are kept for backward compatibility but the UI
no longer exposes or calls them.

* admin: restore configurable idle sleep in scheduler loop

The EC integration test sets idle_sleep_seconds=1 via the scheduler
config API so the scheduler wakes quickly after workers connect. The
previous commit replaced this with a hardcoded 613s constant, causing
the scheduler to sleep through the entire test window.

Restore GetSchedulerConfig().IdleSleepDuration() in the scheduler loop
and status reporting. The UI removal of the setting is still correct —
the API endpoint remains for programmatic use (e.g., tests).

* admin: cap first-run initial delay to 5s instead of DetectionInterval/2

The initial delay for first-run job types was set to
policy.DetectionInterval/2, which creates unbounded first-run latency
(e.g., 1 hour for vacuum with a 2-hour detection interval). A small
fixed 5-second delay provides sufficient stagger without penalizing
startup time.
pull/8558/head
Chris Lu 20 hours ago
committed by GitHub
parent
commit
961c270aba
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 3
      weed/admin/plugin/plugin_scheduler.go
  2. 114
      weed/admin/view/app/plugin.templ
  3. 4
      weed/admin/view/app/plugin_templ.go

3
weed/admin/plugin/plugin_scheduler.go

@ -112,7 +112,6 @@ func (r *Plugin) runSchedulerIteration() bool {
} }
active := make(map[string]struct{}, len(jobTypes)) active := make(map[string]struct{}, len(jobTypes))
schedulerIdleSleep := r.GetSchedulerConfig().IdleSleepDuration()
hadJobs := false hadJobs := false
for _, jobType := range jobTypes { for _, jobType := range jobTypes {
@ -129,7 +128,7 @@ func (r *Plugin) runSchedulerIteration() bool {
} }
initialDelay := time.Duration(0) initialDelay := time.Duration(0)
if runInfo := r.snapshotSchedulerRun(jobType); runInfo.lastRunStartedAt.IsZero() { if runInfo := r.snapshotSchedulerRun(jobType); runInfo.lastRunStartedAt.IsZero() {
initialDelay = schedulerIdleSleep / 2
initialDelay = 5 * time.Second
} }
if !r.markDetectionDue(jobType, policy.DetectionInterval, initialDelay) { if !r.markDetectionDue(jobType, policy.DetectionInterval, initialDelay) {
continue continue

114
weed/admin/view/app/plugin.templ

@ -156,14 +156,7 @@ templ Plugin(page string) {
<small class="text-muted">Global</small> <small class="text-muted">Global</small>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="mb-2">
<label class="form-label" for="plugin-scheduler-idle-sleep-overview">Sleep Between Iterations (s)</label>
<input type="number" class="form-control" id="plugin-scheduler-idle-sleep-overview" min="0"/>
<div class="form-text">Used when no jobs are detected.</div>
</div>
<button type="button" class="btn btn-outline-primary" id="plugin-save-scheduler-btn-overview">
<i class="fas fa-save me-1"></i>Save Scheduler Settings
</button>
<p class="text-muted mb-0">Detection intervals are configured per job type in the job type settings below.</p>
</div> </div>
</div> </div>
</div> </div>
@ -274,6 +267,11 @@ templ Plugin(page string) {
<input class="form-check-input" type="checkbox" id="plugin-admin-enabled"/> <input class="form-check-input" type="checkbox" id="plugin-admin-enabled"/>
</div> </div>
</div> </div>
<div class="col-12">
<label class="form-label" for="plugin-admin-detection-interval">Detection Interval (s)</label>
<input type="number" class="form-control" id="plugin-admin-detection-interval" min="0"/>
<div class="form-text">How often to check for new work.</div>
</div>
<div class="col-12"> <div class="col-12">
<label class="form-label" for="plugin-admin-detection-timeout">Detection Timeout (s)</label> <label class="form-label" for="plugin-admin-detection-timeout">Detection Timeout (s)</label>
<input type="number" class="form-control" id="plugin-admin-detection-timeout" min="0"/> <input type="number" class="form-control" id="plugin-admin-detection-timeout" min="0"/>
@ -311,14 +309,7 @@ templ Plugin(page string) {
<h5 class="mb-0"><i class="fas fa-clock me-2"></i>Scheduler Settings</h5> <h5 class="mb-0"><i class="fas fa-clock me-2"></i>Scheduler Settings</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="mb-3">
<label class="form-label" for="plugin-scheduler-idle-sleep">Sleep Between Iterations (s)</label>
<input type="number" class="form-control" id="plugin-scheduler-idle-sleep" min="0"/>
<div class="form-text">Used when no jobs are detected.</div>
</div>
<button type="button" class="btn btn-outline-primary" id="plugin-save-scheduler-btn">
<i class="fas fa-save me-1"></i>Save Scheduler Settings
</button>
<p class="text-muted mb-0">Detection intervals are configured per job type in the settings above.</p>
</div> </div>
</div> </div>
@ -632,8 +623,6 @@ templ Plugin(page string) {
activities: [], activities: [],
schedulerStates: [], schedulerStates: [],
schedulerStatus: null, schedulerStatus: null,
schedulerConfig: null,
schedulerConfigLoaded: false,
allJobs: [], allJobs: [],
allActivities: [], allActivities: [],
loadedJobType: '', loadedJobType: '',
@ -2461,6 +2450,7 @@ templ Plugin(page string) {
} }
document.getElementById('plugin-admin-enabled').checked = pickBool('enabled'); document.getElementById('plugin-admin-enabled').checked = pickBool('enabled');
document.getElementById('plugin-admin-detection-interval').value = String(pickNumber('detection_interval_seconds'));
document.getElementById('plugin-admin-detection-timeout').value = String(pickNumber('detection_timeout_seconds')); document.getElementById('plugin-admin-detection-timeout').value = String(pickNumber('detection_timeout_seconds'));
document.getElementById('plugin-admin-max-runtime').value = String(pickNumber('job_type_max_runtime_seconds')); document.getElementById('plugin-admin-max-runtime').value = String(pickNumber('job_type_max_runtime_seconds'));
document.getElementById('plugin-admin-max-results').value = String(pickNumber('max_jobs_per_detection')); document.getElementById('plugin-admin-max-results').value = String(pickNumber('max_jobs_per_detection'));
@ -2471,9 +2461,6 @@ templ Plugin(page string) {
} }
function collectAdminSettings() { function collectAdminSettings() {
var existingRuntime = (state.config && state.config.admin_runtime) ? state.config.admin_runtime : {};
var existingDetectionInterval = Number(existingRuntime.detection_interval_seconds || 0);
function getInt(id) { function getInt(id) {
var raw = String(document.getElementById(id).value || '').trim(); var raw = String(document.getElementById(id).value || '').trim();
if (!raw) { if (!raw) {
@ -2488,7 +2475,7 @@ templ Plugin(page string) {
return { return {
enabled: !!document.getElementById('plugin-admin-enabled').checked, enabled: !!document.getElementById('plugin-admin-enabled').checked,
detection_interval_seconds: existingDetectionInterval,
detection_interval_seconds: getInt('plugin-admin-detection-interval'),
detection_timeout_seconds: getInt('plugin-admin-detection-timeout'), detection_timeout_seconds: getInt('plugin-admin-detection-timeout'),
job_type_max_runtime_seconds: getInt('plugin-admin-max-runtime'), job_type_max_runtime_seconds: getInt('plugin-admin-max-runtime'),
max_jobs_per_detection: getInt('plugin-admin-max-results'), max_jobs_per_detection: getInt('plugin-admin-max-results'),
@ -2806,75 +2793,6 @@ templ Plugin(page string) {
} }
} }
async function loadSchedulerConfig(forceRefresh) {
if (state.schedulerConfigLoaded && !forceRefresh) {
return;
}
var idleInputs = [
document.getElementById('plugin-scheduler-idle-sleep'),
document.getElementById('plugin-scheduler-idle-sleep-overview'),
].filter(Boolean);
if (idleInputs.length === 0) {
return;
}
try {
var cfg = await pluginRequest('GET', '/api/plugin/scheduler-config');
state.schedulerConfig = cfg || {};
state.schedulerConfigLoaded = true;
var idleSeconds = Number((cfg && cfg.idle_sleep_seconds) || 0);
idleInputs.forEach(function(input) {
input.value = idleSeconds > 0 ? String(idleSeconds) : '';
});
} catch (e) {
notify('Failed to load scheduler config: ' + e.message, 'error');
}
}
async function saveSchedulerConfig(sourceInput) {
var idleInputs = [
document.getElementById('plugin-scheduler-idle-sleep'),
document.getElementById('plugin-scheduler-idle-sleep-overview'),
].filter(Boolean);
if (idleInputs.length === 0) {
return;
}
var raw = '';
if (sourceInput) {
raw = String(sourceInput.value || '').trim();
}
if (!raw) {
for (var i = 0; i < idleInputs.length; i++) {
if (idleInputs[i] === sourceInput) {
continue;
}
var candidate = String(idleInputs[i].value || '').trim();
if (candidate) {
raw = candidate;
break;
}
}
}
var parsed = raw ? parseInt(raw, 10) : 0;
if (Number.isNaN(parsed) || parsed < 0) {
notify('Invalid idle sleep value', 'error');
return;
}
try {
var updated = await pluginRequest('PUT', '/api/plugin/scheduler-config', {
idle_sleep_seconds: parsed,
});
state.schedulerConfig = updated || {};
state.schedulerConfigLoaded = true;
var idleSeconds = Number((updated && updated.idle_sleep_seconds) || 0);
idleInputs.forEach(function(input) {
input.value = idleSeconds > 0 ? String(idleSeconds) : '';
});
notify('Scheduler settings saved', 'success');
} catch (e) {
notify('Failed to save scheduler config: ' + e.message, 'error');
}
}
function getMaxResults() { function getMaxResults() {
var raw = String(document.getElementById('plugin-admin-max-results').value || '').trim(); var raw = String(document.getElementById('plugin-admin-max-results').value || '').trim();
if (!raw) { if (!raw) {
@ -3051,19 +2969,6 @@ templ Plugin(page string) {
saveConfig(); saveConfig();
}); });
var saveSchedulerBtn = document.getElementById('plugin-save-scheduler-btn');
if (saveSchedulerBtn) {
saveSchedulerBtn.addEventListener('click', function() {
saveSchedulerConfig(document.getElementById('plugin-scheduler-idle-sleep'));
});
}
var saveSchedulerBtnOverview = document.getElementById('plugin-save-scheduler-btn-overview');
if (saveSchedulerBtnOverview) {
saveSchedulerBtnOverview.addEventListener('click', function() {
saveSchedulerConfig(document.getElementById('plugin-scheduler-idle-sleep-overview'));
});
}
document.getElementById('plugin-trigger-detection-btn').addEventListener('click', function() { document.getElementById('plugin-trigger-detection-btn').addEventListener('click', function() {
runDetection(); runDetection();
}); });
@ -3148,7 +3053,6 @@ templ Plugin(page string) {
ensureActiveNavigation(); ensureActiveNavigation();
renderNavigationState(); renderNavigationState();
await refreshAll(); await refreshAll();
await loadSchedulerConfig(false);
state.refreshTimer = setInterval(function() { state.refreshTimer = setInterval(function() {
refreshAll(); refreshAll();

4
weed/admin/view/app/plugin_templ.go
File diff suppressed because it is too large
View File

Loading…
Cancel
Save