cron
This commit is contained in:
@@ -25,11 +25,12 @@
|
|||||||
{ "name": "schedule_timezone", "label": "Scheduler-Zeitzone", "type": "text", "required": false, "help": "z.B. Europe/Berlin" }
|
{ "name": "schedule_timezone", "label": "Scheduler-Zeitzone", "type": "text", "required": false, "help": "z.B. Europe/Berlin" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"cron_tasks": [
|
"scheduler_jobs": [
|
||||||
{
|
{
|
||||||
"name": "rates_refresh",
|
"name": "rates_refresh",
|
||||||
"label": "Kursabruf",
|
"label": "Kursabruf",
|
||||||
"callback": "scheduled_refresh",
|
"callback": "scheduled_refresh",
|
||||||
|
"mode": "multi",
|
||||||
"default_enabled": true,
|
"default_enabled": true,
|
||||||
"default_cron": "0 18 * * *",
|
"default_cron": "0 18 * * *",
|
||||||
"default_timezone": "Europe/Berlin",
|
"default_timezone": "Europe/Berlin",
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ $current = modules()->settings($moduleName);
|
|||||||
$intervalTaskStatuses = modules()->intervalTaskStatuses($moduleName);
|
$intervalTaskStatuses = modules()->intervalTaskStatuses($moduleName);
|
||||||
$cronTaskDefinitions = modules()->cronTasks($moduleName);
|
$cronTaskDefinitions = modules()->cronTasks($moduleName);
|
||||||
$cronTaskStatuses = modules()->cronTaskStatuses($moduleName);
|
$cronTaskStatuses = modules()->cronTaskStatuses($moduleName);
|
||||||
|
$cronTaskStatusGroups = [];
|
||||||
|
foreach ($cronTaskStatuses as $cronTaskStatus) {
|
||||||
|
$cronGroupName = trim((string) ($cronTaskStatus['job_name'] ?? $cronTaskStatus['name'] ?? ''));
|
||||||
|
if ($cronGroupName === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$cronTaskStatusGroups[$cronGroupName][] = $cronTaskStatus;
|
||||||
|
}
|
||||||
$setupActions = modules()->hasFunction($moduleName, 'setup_actions')
|
$setupActions = modules()->hasFunction($moduleName, 'setup_actions')
|
||||||
? (array) module_fn($moduleName, 'setup_actions')
|
? (array) module_fn($moduleName, 'setup_actions')
|
||||||
: [];
|
: [];
|
||||||
@@ -384,7 +392,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$current = array_replace_recursive($current, $payload);
|
$current = array_replace_recursive($current, $payload);
|
||||||
|
|
||||||
if ($cronTaskDefinitions !== []) {
|
if ($cronTaskDefinitions !== []) {
|
||||||
$cronJobs = is_array($current['cron_jobs'] ?? null) ? $current['cron_jobs'] : [];
|
$postedSchedulerJobs = is_array($_POST['scheduler_jobs'] ?? null) ? $_POST['scheduler_jobs'] : [];
|
||||||
|
$schedulerJobs = [];
|
||||||
foreach ($cronTaskDefinitions as $cronTask) {
|
foreach ($cronTaskDefinitions as $cronTask) {
|
||||||
if (!is_array($cronTask)) {
|
if (!is_array($cronTask)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -394,26 +403,41 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$prefix = 'cron_job_' . preg_replace('/[^a-zA-Z0-9_]/', '_', $cronName);
|
$jobPayload = is_array($postedSchedulerJobs[$cronName] ?? null) ? $postedSchedulerJobs[$cronName] : [];
|
||||||
$cronJobs[$cronName] = array_merge(
|
$entriesPayload = is_array($jobPayload['entries'] ?? null) ? $jobPayload['entries'] : [];
|
||||||
is_array($cronJobs[$cronName] ?? null) ? $cronJobs[$cronName] : [],
|
$entries = [];
|
||||||
[
|
foreach (array_values($entriesPayload) as $entryPayload) {
|
||||||
'enabled' => isset($_POST[$prefix . '_enabled']),
|
if (!is_array($entryPayload)) {
|
||||||
'cron_expression' => trim((string) ($_POST[$prefix . '_cron_expression'] ?? ($cronTask['default_cron'] ?? ''))),
|
continue;
|
||||||
'timezone' => trim((string) ($_POST[$prefix . '_timezone'] ?? ($cronTask['default_timezone'] ?? 'UTC'))),
|
|
||||||
'builder' => [
|
|
||||||
'mode' => trim((string) ($_POST[$prefix . '_builder_mode'] ?? 'builder')),
|
|
||||||
'kind' => trim((string) ($_POST[$prefix . '_builder_kind'] ?? 'daily')),
|
|
||||||
'time' => trim((string) ($_POST[$prefix . '_builder_time'] ?? '18:00')),
|
|
||||||
'interval_days' => max(1, (int) ($_POST[$prefix . '_builder_interval_days'] ?? 2)),
|
|
||||||
'weekday' => trim((string) ($_POST[$prefix . '_builder_weekday'] ?? '1')),
|
|
||||||
'month_day' => max(1, min(31, (int) ($_POST[$prefix . '_builder_month_day'] ?? 1))),
|
|
||||||
'interval_hours' => max(1, (int) ($_POST[$prefix . '_builder_interval_hours'] ?? 6)),
|
|
||||||
],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
$current['cron_jobs'] = $cronJobs;
|
$cronExpression = trim((string) ($entryPayload['cron_expression'] ?? ''));
|
||||||
|
$timezone = trim((string) ($entryPayload['timezone'] ?? ($current['schedule_timezone'] ?? 'UTC')));
|
||||||
|
if ($cronExpression === '' && $timezone === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$entries[] = [
|
||||||
|
'enabled' => !empty($entryPayload['enabled']),
|
||||||
|
'cron_expression' => $cronExpression,
|
||||||
|
'timezone' => $timezone !== '' ? $timezone : 'UTC',
|
||||||
|
'builder' => [
|
||||||
|
'mode' => trim((string) ($entryPayload['builder']['mode'] ?? 'builder')),
|
||||||
|
'kind' => trim((string) ($entryPayload['builder']['kind'] ?? 'daily')),
|
||||||
|
'time' => trim((string) ($entryPayload['builder']['time'] ?? '18:00')),
|
||||||
|
'interval_days' => max(1, (int) ($entryPayload['builder']['interval_days'] ?? 2)),
|
||||||
|
'weekday' => trim((string) ($entryPayload['builder']['weekday'] ?? '1')),
|
||||||
|
'month_day' => max(1, min(31, (int) ($entryPayload['builder']['month_day'] ?? 1))),
|
||||||
|
'interval_hours' => max(1, min(23, (int) ($entryPayload['builder']['interval_hours'] ?? 6))),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((string) ($cronTask['mode'] ?? 'single') !== 'multi' && $entries !== []) {
|
||||||
|
$entries = [array_values($entries)[0]];
|
||||||
|
}
|
||||||
|
|
||||||
|
$schedulerJobs[$cronName] = ['entries' => $entries];
|
||||||
|
}
|
||||||
|
$current['scheduler_jobs'] = $schedulerJobs;
|
||||||
}
|
}
|
||||||
|
|
||||||
$postedTestGroup = (string)($_POST['test_db'] ?? '');
|
$postedTestGroup = (string)($_POST['test_db'] ?? '');
|
||||||
@@ -774,7 +798,7 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
|
|||||||
</section>
|
</section>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($cronTaskStatuses !== []): ?>
|
<?php if ($cronTaskDefinitions !== []): ?>
|
||||||
<section class="setup-panel">
|
<section class="setup-panel">
|
||||||
<div class="setup-panel__head">
|
<div class="setup-panel__head">
|
||||||
<div>
|
<div>
|
||||||
@@ -783,44 +807,50 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
|
|||||||
<p class="muted">Diese Jobs werden ueber den zentralen Nexus-Scheduler ausgefuehrt. Der System-Cron sollte den CLI-Runner jede Minute starten.</p>
|
<p class="muted">Diese Jobs werden ueber den zentralen Nexus-Scheduler ausgefuehrt. Der System-Cron sollte den CLI-Runner jede Minute starten.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="setup-grid">
|
<?php foreach ($cronTaskDefinitions as $cronDefinition): ?>
|
||||||
<?php foreach ($cronTaskStatuses as $task): ?>
|
<?php
|
||||||
|
$cronName = trim((string) ($cronDefinition['name'] ?? ''));
|
||||||
|
if ($cronName === '') { continue; }
|
||||||
|
$cronMode = (string) ($cronDefinition['mode'] ?? 'single');
|
||||||
|
$cronEntries = $cronTaskStatusGroups[$cronName] ?? [];
|
||||||
|
?>
|
||||||
|
<div class="setup-field muted" data-scheduler-job data-job-name="<?= e($cronName) ?>" data-job-mode="<?= e($cronMode) ?>">
|
||||||
|
<span><?= e((string) ($cronDefinition['label'] ?? $cronName)) ?></span>
|
||||||
|
<?php if (trim((string) ($cronDefinition['help'] ?? '')) !== ''): ?>
|
||||||
|
<small class="muted"><?= e((string) $cronDefinition['help']) ?></small>
|
||||||
|
<?php endif; ?>
|
||||||
|
<div class="scheduler-entries" data-scheduler-entries>
|
||||||
|
<?php foreach (array_values($cronEntries) as $entryIndex => $task): ?>
|
||||||
<?php
|
<?php
|
||||||
$cronName = trim((string) ($task['name'] ?? ''));
|
|
||||||
$cronConfig = is_array($task['config'] ?? null) ? $task['config'] : [];
|
$cronConfig = is_array($task['config'] ?? null) ? $task['config'] : [];
|
||||||
$builder = is_array($cronConfig['builder'] ?? null) ? $cronConfig['builder'] : [];
|
$builder = is_array($cronConfig['builder'] ?? null) ? $cronConfig['builder'] : [];
|
||||||
$prefix = 'cron_job_' . preg_replace('/[^a-zA-Z0-9_]/', '_', $cronName);
|
|
||||||
?>
|
?>
|
||||||
<div class="setup-field muted" data-cron-job>
|
<div class="scheduler-entry" data-scheduler-entry>
|
||||||
<span><?= e((string) ($task['label'] ?? $cronName)) ?></span>
|
<label><input type="checkbox" name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][enabled]" value="1" <?= !empty($cronConfig['enabled']) ? 'checked' : '' ?>> Aktiv</label>
|
||||||
<?php if (trim((string) ($task['help'] ?? '')) !== ''): ?>
|
<input type="text" name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][cron_expression]" value="<?= e((string) ($cronConfig['cron_expression'] ?? '')) ?>" data-cron-expression>
|
||||||
<small class="muted"><?= e((string) $task['help']) ?></small>
|
|
||||||
<?php endif; ?>
|
|
||||||
<label><input type="checkbox" name="<?= e($prefix) ?>_enabled" value="1" <?= !empty($cronConfig['enabled']) ? 'checked' : '' ?>> Aktiv</label>
|
|
||||||
<input type="text" name="<?= e($prefix) ?>_cron_expression" value="<?= e((string) ($cronConfig['cron_expression'] ?? '')) ?>" data-cron-expression>
|
|
||||||
<small class="muted">Cron-Syntax: Minute Stunde Tag Monat Wochentag</small>
|
<small class="muted">Cron-Syntax: Minute Stunde Tag Monat Wochentag</small>
|
||||||
<input type="text" name="<?= e($prefix) ?>_timezone" value="<?= e((string) ($cronConfig['timezone'] ?? 'UTC')) ?>" data-cron-timezone>
|
<input type="text" name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][timezone]" value="<?= e((string) ($cronConfig['timezone'] ?? 'UTC')) ?>" data-cron-timezone>
|
||||||
<select name="<?= e($prefix) ?>_builder_mode" data-cron-builder-mode>
|
<select name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][builder][mode]" data-cron-builder-mode>
|
||||||
<option value="builder" <?= (string) ($builder['mode'] ?? 'builder') === 'builder' ? 'selected' : '' ?>>Builder</option>
|
<option value="builder" <?= (string) ($builder['mode'] ?? 'builder') === 'builder' ? 'selected' : '' ?>>Builder</option>
|
||||||
<option value="manual" <?= (string) ($builder['mode'] ?? 'builder') === 'manual' ? 'selected' : '' ?>>Cron-Syntax</option>
|
<option value="manual" <?= (string) ($builder['mode'] ?? 'builder') === 'manual' ? 'selected' : '' ?>>Cron-Syntax</option>
|
||||||
</select>
|
</select>
|
||||||
<div data-cron-builder-fields>
|
<div data-cron-builder-fields>
|
||||||
<select name="<?= e($prefix) ?>_builder_kind" data-cron-builder-kind>
|
<select name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][builder][kind]" data-cron-builder-kind>
|
||||||
<option value="daily" <?= (string) ($builder['kind'] ?? 'daily') === 'daily' ? 'selected' : '' ?>>Taeglich</option>
|
<option value="daily" <?= (string) ($builder['kind'] ?? 'daily') === 'daily' ? 'selected' : '' ?>>Taeglich</option>
|
||||||
<option value="every_x_days" <?= (string) ($builder['kind'] ?? '') === 'every_x_days' ? 'selected' : '' ?>>Alle x Tage</option>
|
<option value="every_x_days" <?= (string) ($builder['kind'] ?? '') === 'every_x_days' ? 'selected' : '' ?>>Alle x Tage</option>
|
||||||
<option value="weekly" <?= (string) ($builder['kind'] ?? '') === 'weekly' ? 'selected' : '' ?>>Woechentlich</option>
|
<option value="weekly" <?= (string) ($builder['kind'] ?? '') === 'weekly' ? 'selected' : '' ?>>Woechentlich</option>
|
||||||
<option value="monthly_day" <?= (string) ($builder['kind'] ?? '') === 'monthly_day' ? 'selected' : '' ?>>X-Tag im Monat</option>
|
<option value="monthly_day" <?= (string) ($builder['kind'] ?? '') === 'monthly_day' ? 'selected' : '' ?>>X-Tag im Monat</option>
|
||||||
<option value="every_x_hours" <?= (string) ($builder['kind'] ?? '') === 'every_x_hours' ? 'selected' : '' ?>>Alle x Stunden</option>
|
<option value="every_x_hours" <?= (string) ($builder['kind'] ?? '') === 'every_x_hours' ? 'selected' : '' ?>>Alle x Stunden</option>
|
||||||
</select>
|
</select>
|
||||||
<input type="time" name="<?= e($prefix) ?>_builder_time" value="<?= e((string) ($builder['time'] ?? '18:00')) ?>" data-cron-builder-time>
|
<input type="time" name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][builder][time]" value="<?= e((string) ($builder['time'] ?? '18:00')) ?>" data-cron-builder-time>
|
||||||
<input type="number" min="1" name="<?= e($prefix) ?>_builder_interval_days" value="<?= e((string) ($builder['interval_days'] ?? 2)) ?>" data-cron-builder-interval-days>
|
<input type="number" min="1" name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][builder][interval_days]" value="<?= e((string) ($builder['interval_days'] ?? 2)) ?>" data-cron-builder-interval-days>
|
||||||
<select name="<?= e($prefix) ?>_builder_weekday" data-cron-builder-weekday>
|
<select name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][builder][weekday]" data-cron-builder-weekday>
|
||||||
<?php foreach ($cronWeekdays as $weekdayValue => $weekdayLabel): ?>
|
<?php foreach ($cronWeekdays as $weekdayValue => $weekdayLabel): ?>
|
||||||
<option value="<?= e($weekdayValue) ?>" <?= (string) ($builder['weekday'] ?? '1') === $weekdayValue ? 'selected' : '' ?>><?= e($weekdayLabel) ?></option>
|
<option value="<?= e($weekdayValue) ?>" <?= (string) ($builder['weekday'] ?? '1') === $weekdayValue ? 'selected' : '' ?>><?= e($weekdayLabel) ?></option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
<input type="number" min="1" max="31" name="<?= e($prefix) ?>_builder_month_day" value="<?= e((string) ($builder['month_day'] ?? 1)) ?>" data-cron-builder-month-day>
|
<input type="number" min="1" max="31" name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][builder][month_day]" value="<?= e((string) ($builder['month_day'] ?? 1)) ?>" data-cron-builder-month-day>
|
||||||
<input type="number" min="1" max="23" name="<?= e($prefix) ?>_builder_interval_hours" value="<?= e((string) ($builder['interval_hours'] ?? 6)) ?>" data-cron-builder-interval-hours>
|
<input type="number" min="1" max="23" name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][builder][interval_hours]" value="<?= e((string) ($builder['interval_hours'] ?? 6)) ?>" data-cron-builder-interval-hours>
|
||||||
</div>
|
</div>
|
||||||
<small class="muted">Letzter Start: <?= e($formatRunTimestamp((string) ($task['state']['last_started_at'] ?? ''))) ?></small>
|
<small class="muted">Letzter Start: <?= e($formatRunTimestamp((string) ($task['state']['last_started_at'] ?? ''))) ?></small>
|
||||||
<small class="muted">Letzter Erfolg: <?= e($formatRunTimestamp((string) ($task['state']['last_success_at'] ?? ''))) ?></small>
|
<small class="muted">Letzter Erfolg: <?= e($formatRunTimestamp((string) ($task['state']['last_success_at'] ?? ''))) ?></small>
|
||||||
@@ -830,9 +860,19 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
|
|||||||
<?php if (trim((string) ($task['parse_error'] ?? '')) !== ''): ?>
|
<?php if (trim((string) ($task['parse_error'] ?? '')) !== ''): ?>
|
||||||
<small class="muted" style="color:#b42318;">Cron-Fehler: <?= e((string) $task['parse_error']) ?></small>
|
<small class="muted" style="color:#b42318;">Cron-Fehler: <?= e((string) $task['parse_error']) ?></small>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php if ($cronMode === 'multi'): ?>
|
||||||
|
<button class="nav-link" type="button" data-remove-scheduler-entry>Eintrag entfernen</button>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
|
<?php if ($cronMode === 'multi'): ?>
|
||||||
|
<div class="setup-actions" style="justify-content:flex-start; margin-top:12px;">
|
||||||
|
<button class="nav-link" type="button" data-add-scheduler-entry>Weiteren Cron hinzufügen</button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
</section>
|
</section>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
@@ -1134,7 +1174,7 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
|
|||||||
</section>
|
</section>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($cronTaskStatuses !== []): ?>
|
<?php if ($cronTaskDefinitions !== []): ?>
|
||||||
<section class="setup-panel">
|
<section class="setup-panel">
|
||||||
<div class="setup-panel__head">
|
<div class="setup-panel__head">
|
||||||
<div>
|
<div>
|
||||||
@@ -1143,44 +1183,50 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
|
|||||||
<p class="muted">Diese Jobs werden ueber den zentralen Nexus-Scheduler ausgefuehrt. Der System-Cron sollte den CLI-Runner jede Minute starten.</p>
|
<p class="muted">Diese Jobs werden ueber den zentralen Nexus-Scheduler ausgefuehrt. Der System-Cron sollte den CLI-Runner jede Minute starten.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="setup-grid">
|
<?php foreach ($cronTaskDefinitions as $cronDefinition): ?>
|
||||||
<?php foreach ($cronTaskStatuses as $task): ?>
|
<?php
|
||||||
|
$cronName = trim((string) ($cronDefinition['name'] ?? ''));
|
||||||
|
if ($cronName === '') { continue; }
|
||||||
|
$cronMode = (string) ($cronDefinition['mode'] ?? 'single');
|
||||||
|
$cronEntries = $cronTaskStatusGroups[$cronName] ?? [];
|
||||||
|
?>
|
||||||
|
<div class="setup-field muted" data-scheduler-job data-job-name="<?= e($cronName) ?>" data-job-mode="<?= e($cronMode) ?>">
|
||||||
|
<span><?= e((string) ($cronDefinition['label'] ?? $cronName)) ?></span>
|
||||||
|
<?php if (trim((string) ($cronDefinition['help'] ?? '')) !== ''): ?>
|
||||||
|
<small class="muted"><?= e((string) $cronDefinition['help']) ?></small>
|
||||||
|
<?php endif; ?>
|
||||||
|
<div class="scheduler-entries" data-scheduler-entries>
|
||||||
|
<?php foreach (array_values($cronEntries) as $entryIndex => $task): ?>
|
||||||
<?php
|
<?php
|
||||||
$cronName = trim((string) ($task['name'] ?? ''));
|
|
||||||
$cronConfig = is_array($task['config'] ?? null) ? $task['config'] : [];
|
$cronConfig = is_array($task['config'] ?? null) ? $task['config'] : [];
|
||||||
$builder = is_array($cronConfig['builder'] ?? null) ? $cronConfig['builder'] : [];
|
$builder = is_array($cronConfig['builder'] ?? null) ? $cronConfig['builder'] : [];
|
||||||
$prefix = 'cron_job_' . preg_replace('/[^a-zA-Z0-9_]/', '_', $cronName);
|
|
||||||
?>
|
?>
|
||||||
<div class="setup-field muted" data-cron-job>
|
<div class="scheduler-entry" data-scheduler-entry>
|
||||||
<span><?= e((string) ($task['label'] ?? $cronName)) ?></span>
|
<label><input type="checkbox" name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][enabled]" value="1" <?= !empty($cronConfig['enabled']) ? 'checked' : '' ?>> Aktiv</label>
|
||||||
<?php if (trim((string) ($task['help'] ?? '')) !== ''): ?>
|
<input type="text" name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][cron_expression]" value="<?= e((string) ($cronConfig['cron_expression'] ?? '')) ?>" data-cron-expression>
|
||||||
<small class="muted"><?= e((string) $task['help']) ?></small>
|
|
||||||
<?php endif; ?>
|
|
||||||
<label><input type="checkbox" name="<?= e($prefix) ?>_enabled" value="1" <?= !empty($cronConfig['enabled']) ? 'checked' : '' ?>> Aktiv</label>
|
|
||||||
<input type="text" name="<?= e($prefix) ?>_cron_expression" value="<?= e((string) ($cronConfig['cron_expression'] ?? '')) ?>" data-cron-expression>
|
|
||||||
<small class="muted">Cron-Syntax: Minute Stunde Tag Monat Wochentag</small>
|
<small class="muted">Cron-Syntax: Minute Stunde Tag Monat Wochentag</small>
|
||||||
<input type="text" name="<?= e($prefix) ?>_timezone" value="<?= e((string) ($cronConfig['timezone'] ?? 'UTC')) ?>" data-cron-timezone>
|
<input type="text" name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][timezone]" value="<?= e((string) ($cronConfig['timezone'] ?? 'UTC')) ?>" data-cron-timezone>
|
||||||
<select name="<?= e($prefix) ?>_builder_mode" data-cron-builder-mode>
|
<select name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][builder][mode]" data-cron-builder-mode>
|
||||||
<option value="builder" <?= (string) ($builder['mode'] ?? 'builder') === 'builder' ? 'selected' : '' ?>>Builder</option>
|
<option value="builder" <?= (string) ($builder['mode'] ?? 'builder') === 'builder' ? 'selected' : '' ?>>Builder</option>
|
||||||
<option value="manual" <?= (string) ($builder['mode'] ?? 'builder') === 'manual' ? 'selected' : '' ?>>Cron-Syntax</option>
|
<option value="manual" <?= (string) ($builder['mode'] ?? 'builder') === 'manual' ? 'selected' : '' ?>>Cron-Syntax</option>
|
||||||
</select>
|
</select>
|
||||||
<div data-cron-builder-fields>
|
<div data-cron-builder-fields>
|
||||||
<select name="<?= e($prefix) ?>_builder_kind" data-cron-builder-kind>
|
<select name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][builder][kind]" data-cron-builder-kind>
|
||||||
<option value="daily" <?= (string) ($builder['kind'] ?? 'daily') === 'daily' ? 'selected' : '' ?>>Taeglich</option>
|
<option value="daily" <?= (string) ($builder['kind'] ?? 'daily') === 'daily' ? 'selected' : '' ?>>Taeglich</option>
|
||||||
<option value="every_x_days" <?= (string) ($builder['kind'] ?? '') === 'every_x_days' ? 'selected' : '' ?>>Alle x Tage</option>
|
<option value="every_x_days" <?= (string) ($builder['kind'] ?? '') === 'every_x_days' ? 'selected' : '' ?>>Alle x Tage</option>
|
||||||
<option value="weekly" <?= (string) ($builder['kind'] ?? '') === 'weekly' ? 'selected' : '' ?>>Woechentlich</option>
|
<option value="weekly" <?= (string) ($builder['kind'] ?? '') === 'weekly' ? 'selected' : '' ?>>Woechentlich</option>
|
||||||
<option value="monthly_day" <?= (string) ($builder['kind'] ?? '') === 'monthly_day' ? 'selected' : '' ?>>X-Tag im Monat</option>
|
<option value="monthly_day" <?= (string) ($builder['kind'] ?? '') === 'monthly_day' ? 'selected' : '' ?>>X-Tag im Monat</option>
|
||||||
<option value="every_x_hours" <?= (string) ($builder['kind'] ?? '') === 'every_x_hours' ? 'selected' : '' ?>>Alle x Stunden</option>
|
<option value="every_x_hours" <?= (string) ($builder['kind'] ?? '') === 'every_x_hours' ? 'selected' : '' ?>>Alle x Stunden</option>
|
||||||
</select>
|
</select>
|
||||||
<input type="time" name="<?= e($prefix) ?>_builder_time" value="<?= e((string) ($builder['time'] ?? '18:00')) ?>" data-cron-builder-time>
|
<input type="time" name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][builder][time]" value="<?= e((string) ($builder['time'] ?? '18:00')) ?>" data-cron-builder-time>
|
||||||
<input type="number" min="1" name="<?= e($prefix) ?>_builder_interval_days" value="<?= e((string) ($builder['interval_days'] ?? 2)) ?>" data-cron-builder-interval-days>
|
<input type="number" min="1" name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][builder][interval_days]" value="<?= e((string) ($builder['interval_days'] ?? 2)) ?>" data-cron-builder-interval-days>
|
||||||
<select name="<?= e($prefix) ?>_builder_weekday" data-cron-builder-weekday>
|
<select name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][builder][weekday]" data-cron-builder-weekday>
|
||||||
<?php foreach ($cronWeekdays as $weekdayValue => $weekdayLabel): ?>
|
<?php foreach ($cronWeekdays as $weekdayValue => $weekdayLabel): ?>
|
||||||
<option value="<?= e($weekdayValue) ?>" <?= (string) ($builder['weekday'] ?? '1') === $weekdayValue ? 'selected' : '' ?>><?= e($weekdayLabel) ?></option>
|
<option value="<?= e($weekdayValue) ?>" <?= (string) ($builder['weekday'] ?? '1') === $weekdayValue ? 'selected' : '' ?>><?= e($weekdayLabel) ?></option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
<input type="number" min="1" max="31" name="<?= e($prefix) ?>_builder_month_day" value="<?= e((string) ($builder['month_day'] ?? 1)) ?>" data-cron-builder-month-day>
|
<input type="number" min="1" max="31" name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][builder][month_day]" value="<?= e((string) ($builder['month_day'] ?? 1)) ?>" data-cron-builder-month-day>
|
||||||
<input type="number" min="1" max="23" name="<?= e($prefix) ?>_builder_interval_hours" value="<?= e((string) ($builder['interval_hours'] ?? 6)) ?>" data-cron-builder-interval-hours>
|
<input type="number" min="1" max="23" name="scheduler_jobs[<?= e($cronName) ?>][entries][<?= e((string) $entryIndex) ?>][builder][interval_hours]" value="<?= e((string) ($builder['interval_hours'] ?? 6)) ?>" data-cron-builder-interval-hours>
|
||||||
</div>
|
</div>
|
||||||
<small class="muted">Letzter Start: <?= e($formatRunTimestamp((string) ($task['state']['last_started_at'] ?? ''))) ?></small>
|
<small class="muted">Letzter Start: <?= e($formatRunTimestamp((string) ($task['state']['last_started_at'] ?? ''))) ?></small>
|
||||||
<small class="muted">Letzter Erfolg: <?= e($formatRunTimestamp((string) ($task['state']['last_success_at'] ?? ''))) ?></small>
|
<small class="muted">Letzter Erfolg: <?= e($formatRunTimestamp((string) ($task['state']['last_success_at'] ?? ''))) ?></small>
|
||||||
@@ -1190,9 +1236,19 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
|
|||||||
<?php if (trim((string) ($task['parse_error'] ?? '')) !== ''): ?>
|
<?php if (trim((string) ($task['parse_error'] ?? '')) !== ''): ?>
|
||||||
<small class="muted" style="color:#b42318;">Cron-Fehler: <?= e((string) $task['parse_error']) ?></small>
|
<small class="muted" style="color:#b42318;">Cron-Fehler: <?= e((string) $task['parse_error']) ?></small>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php if ($cronMode === 'multi'): ?>
|
||||||
|
<button class="nav-link" type="button" data-remove-scheduler-entry>Eintrag entfernen</button>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
|
<?php if ($cronMode === 'multi'): ?>
|
||||||
|
<div class="setup-actions" style="justify-content:flex-start; margin-top:12px;">
|
||||||
|
<button class="nav-link" type="button" data-add-scheduler-entry>Weiteren Cron hinzufügen</button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
</section>
|
</section>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
@@ -1336,9 +1392,19 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<script>
|
<script>
|
||||||
(() => {
|
(() => {
|
||||||
const jobs = document.querySelectorAll('[data-cron-job]');
|
const jobs = document.querySelectorAll('[data-scheduler-job]');
|
||||||
if (!jobs.length) return;
|
if (!jobs.length) return;
|
||||||
|
|
||||||
|
const weekdayOptions = [
|
||||||
|
['0', 'Sonntag'],
|
||||||
|
['1', 'Montag'],
|
||||||
|
['2', 'Dienstag'],
|
||||||
|
['3', 'Mittwoch'],
|
||||||
|
['4', 'Donnerstag'],
|
||||||
|
['5', 'Freitag'],
|
||||||
|
['6', 'Samstag'],
|
||||||
|
];
|
||||||
|
|
||||||
const buildExpression = (kind, values) => {
|
const buildExpression = (kind, values) => {
|
||||||
const [hourRaw, minuteRaw] = String(values.time || '18:00').split(':');
|
const [hourRaw, minuteRaw] = String(values.time || '18:00').split(':');
|
||||||
const minute = Math.max(0, Math.min(59, Number(minuteRaw || 0)));
|
const minute = Math.max(0, Math.min(59, Number(minuteRaw || 0)));
|
||||||
@@ -1363,17 +1429,92 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
jobs.forEach((job) => {
|
const weekdayMarkup = () => weekdayOptions.map(([value, label]) => (
|
||||||
const expression = job.querySelector('[data-cron-expression]');
|
`<option value="${value}">${label}</option>`
|
||||||
const mode = job.querySelector('[data-cron-builder-mode]');
|
)).join('');
|
||||||
const fields = job.querySelector('[data-cron-builder-fields]');
|
|
||||||
const kind = job.querySelector('[data-cron-builder-kind]');
|
|
||||||
const time = job.querySelector('[data-cron-builder-time]');
|
|
||||||
const intervalDays = job.querySelector('[data-cron-builder-interval-days]');
|
|
||||||
const weekday = job.querySelector('[data-cron-builder-weekday]');
|
|
||||||
const monthDay = job.querySelector('[data-cron-builder-month-day]');
|
|
||||||
const intervalHours = job.querySelector('[data-cron-builder-interval-hours]');
|
|
||||||
|
|
||||||
|
const createEntry = (job, values = {}) => {
|
||||||
|
const jobName = job.dataset.jobName || 'job';
|
||||||
|
const entry = document.createElement('div');
|
||||||
|
entry.className = 'scheduler-entry';
|
||||||
|
entry.dataset.schedulerEntry = '';
|
||||||
|
entry.innerHTML = `
|
||||||
|
<label><input type="checkbox" value="1" data-enabled> Aktiv</label>
|
||||||
|
<input type="text" value="" data-cron-expression>
|
||||||
|
<small class="muted">Cron-Syntax: Minute Stunde Tag Monat Wochentag</small>
|
||||||
|
<input type="text" value="" data-cron-timezone>
|
||||||
|
<select data-cron-builder-mode>
|
||||||
|
<option value="builder">Builder</option>
|
||||||
|
<option value="manual">Cron-Syntax</option>
|
||||||
|
</select>
|
||||||
|
<div data-cron-builder-fields>
|
||||||
|
<select data-cron-builder-kind>
|
||||||
|
<option value="daily">Taeglich</option>
|
||||||
|
<option value="every_x_days">Alle x Tage</option>
|
||||||
|
<option value="weekly">Woechentlich</option>
|
||||||
|
<option value="monthly_day">X-Tag im Monat</option>
|
||||||
|
<option value="every_x_hours">Alle x Stunden</option>
|
||||||
|
</select>
|
||||||
|
<input type="time" value="18:00" data-cron-builder-time>
|
||||||
|
<input type="number" min="1" value="2" data-cron-builder-interval-days>
|
||||||
|
<select data-cron-builder-weekday>${weekdayMarkup()}</select>
|
||||||
|
<input type="number" min="1" max="31" value="1" data-cron-builder-month-day>
|
||||||
|
<input type="number" min="1" max="23" value="6" data-cron-builder-interval-hours>
|
||||||
|
</div>
|
||||||
|
<small class="muted">Letzter Start: -</small>
|
||||||
|
<small class="muted">Letzter Erfolg: -</small>
|
||||||
|
<small class="muted">Naechster Lauf UTC: -</small>
|
||||||
|
<small class="muted">Naechster Lauf lokal: -</small>
|
||||||
|
<small class="muted">Status: -</small>
|
||||||
|
${job.dataset.jobMode === 'multi' ? '<button class="nav-link" type="button" data-remove-scheduler-entry>Eintrag entfernen</button>' : ''}
|
||||||
|
`;
|
||||||
|
|
||||||
|
entry.querySelector('[data-enabled]').checked = Boolean(values.enabled);
|
||||||
|
entry.querySelector('[data-cron-expression]').value = values.cron_expression || '0 18 * * *';
|
||||||
|
entry.querySelector('[data-cron-timezone]').value = values.timezone || 'UTC';
|
||||||
|
entry.querySelector('[data-cron-builder-mode]').value = values.builderMode || 'builder';
|
||||||
|
entry.querySelector('[data-cron-builder-kind]').value = values.builderKind || 'daily';
|
||||||
|
entry.querySelector('[data-cron-builder-time]').value = values.time || '18:00';
|
||||||
|
entry.querySelector('[data-cron-builder-interval-days]').value = values.intervalDays || '2';
|
||||||
|
entry.querySelector('[data-cron-builder-weekday]').value = values.weekday || '1';
|
||||||
|
entry.querySelector('[data-cron-builder-month-day]').value = values.monthDay || '1';
|
||||||
|
entry.querySelector('[data-cron-builder-interval-hours]').value = values.intervalHours || '6';
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
};
|
||||||
|
|
||||||
|
const reindexJob = (job) => {
|
||||||
|
const jobName = job.dataset.jobName || 'job';
|
||||||
|
job.querySelectorAll('[data-scheduler-entry]').forEach((entry, index) => {
|
||||||
|
const setName = (selector, suffix) => {
|
||||||
|
const node = entry.querySelector(selector);
|
||||||
|
if (node) {
|
||||||
|
node.name = `scheduler_jobs[${jobName}][entries][${index}]${suffix}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
setName('[data-enabled]', '[enabled]');
|
||||||
|
setName('[data-cron-expression]', '[cron_expression]');
|
||||||
|
setName('[data-cron-timezone]', '[timezone]');
|
||||||
|
setName('[data-cron-builder-mode]', '[builder][mode]');
|
||||||
|
setName('[data-cron-builder-kind]', '[builder][kind]');
|
||||||
|
setName('[data-cron-builder-time]', '[builder][time]');
|
||||||
|
setName('[data-cron-builder-interval-days]', '[builder][interval_days]');
|
||||||
|
setName('[data-cron-builder-weekday]', '[builder][weekday]');
|
||||||
|
setName('[data-cron-builder-month-day]', '[builder][month_day]');
|
||||||
|
setName('[data-cron-builder-interval-hours]', '[builder][interval_hours]');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const bindEntry = (entry) => {
|
||||||
|
const expression = entry.querySelector('[data-cron-expression]');
|
||||||
|
const mode = entry.querySelector('[data-cron-builder-mode]');
|
||||||
|
const fields = entry.querySelector('[data-cron-builder-fields]');
|
||||||
|
const kind = entry.querySelector('[data-cron-builder-kind]');
|
||||||
|
const time = entry.querySelector('[data-cron-builder-time]');
|
||||||
|
const intervalDays = entry.querySelector('[data-cron-builder-interval-days]');
|
||||||
|
const weekday = entry.querySelector('[data-cron-builder-weekday]');
|
||||||
|
const monthDay = entry.querySelector('[data-cron-builder-month-day]');
|
||||||
|
const intervalHours = entry.querySelector('[data-cron-builder-interval-hours]');
|
||||||
if (!expression || !mode || !fields || !kind || !time || !intervalDays || !weekday || !monthDay || !intervalHours) {
|
if (!expression || !mode || !fields || !kind || !time || !intervalDays || !weekday || !monthDay || !intervalHours) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1398,11 +1539,65 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
|
|||||||
node.addEventListener('input', sync);
|
node.addEventListener('input', sync);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const removeButton = entry.querySelector('[data-remove-scheduler-entry]');
|
||||||
|
if (removeButton) {
|
||||||
|
removeButton.addEventListener('click', () => {
|
||||||
|
const job = entry.closest('[data-scheduler-job]');
|
||||||
|
if (!job) return;
|
||||||
|
entry.remove();
|
||||||
|
reindexJob(job);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
sync();
|
sync();
|
||||||
|
};
|
||||||
|
|
||||||
|
jobs.forEach((job) => {
|
||||||
|
job.querySelectorAll('[data-scheduler-entry]').forEach((entry) => bindEntry(entry));
|
||||||
|
reindexJob(job);
|
||||||
|
|
||||||
|
const addButton = job.querySelector('[data-add-scheduler-entry]');
|
||||||
|
if (!addButton) return;
|
||||||
|
|
||||||
|
addButton.addEventListener('click', () => {
|
||||||
|
const container = job.querySelector('[data-scheduler-entries]');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const source = container.querySelector('[data-scheduler-entry]');
|
||||||
|
const values = source ? {
|
||||||
|
timezone: source.querySelector('[data-cron-timezone]')?.value || 'UTC',
|
||||||
|
builderMode: source.querySelector('[data-cron-builder-mode]')?.value || 'builder',
|
||||||
|
builderKind: source.querySelector('[data-cron-builder-kind]')?.value || 'daily',
|
||||||
|
time: source.querySelector('[data-cron-builder-time]')?.value || '18:00',
|
||||||
|
intervalDays: source.querySelector('[data-cron-builder-interval-days]')?.value || '2',
|
||||||
|
weekday: source.querySelector('[data-cron-builder-weekday]')?.value || '1',
|
||||||
|
monthDay: source.querySelector('[data-cron-builder-month-day]')?.value || '1',
|
||||||
|
intervalHours: source.querySelector('[data-cron-builder-interval-hours]')?.value || '6',
|
||||||
|
} : {};
|
||||||
|
|
||||||
|
const entry = createEntry(job, values);
|
||||||
|
container.appendChild(entry);
|
||||||
|
bindEntry(entry);
|
||||||
|
reindexJob(job);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
|
.scheduler-entries {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scheduler-entry {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: color-mix(in srgb, var(--surface) 88%, white);
|
||||||
|
}
|
||||||
|
|
||||||
[data-cron-builder-fields] {
|
[data-cron-builder-fields] {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ final class ModuleCronScheduler
|
|||||||
public function definitions(string $moduleName): array
|
public function definitions(string $moduleName): array
|
||||||
{
|
{
|
||||||
$module = $this->modules->get($moduleName);
|
$module = $this->modules->get($moduleName);
|
||||||
$tasks = is_array($module['cron_tasks'] ?? null) ? $module['cron_tasks'] : [];
|
$tasks = is_array($module['scheduler_jobs'] ?? null) ? $module['scheduler_jobs'] : (is_array($module['cron_tasks'] ?? null) ? $module['cron_tasks'] : []);
|
||||||
$definitions = [];
|
$definitions = [];
|
||||||
|
|
||||||
foreach ($tasks as $task) {
|
foreach ($tasks as $task) {
|
||||||
@@ -35,6 +35,7 @@ final class ModuleCronScheduler
|
|||||||
'name' => $name,
|
'name' => $name,
|
||||||
'label' => trim((string) ($task['label'] ?? $name)),
|
'label' => trim((string) ($task['label'] ?? $name)),
|
||||||
'callback' => $callback,
|
'callback' => $callback,
|
||||||
|
'mode' => in_array((string) ($task['mode'] ?? 'single'), ['single', 'multi'], true) ? (string) ($task['mode'] ?? 'single') : 'single',
|
||||||
'default_enabled' => array_key_exists('default_enabled', $task) ? (bool) $task['default_enabled'] : false,
|
'default_enabled' => array_key_exists('default_enabled', $task) ? (bool) $task['default_enabled'] : false,
|
||||||
'default_cron' => trim((string) ($task['default_cron'] ?? '0 * * * *')),
|
'default_cron' => trim((string) ($task['default_cron'] ?? '0 * * * *')),
|
||||||
'default_timezone' => trim((string) ($task['default_timezone'] ?? 'UTC')) ?: 'UTC',
|
'default_timezone' => trim((string) ($task['default_timezone'] ?? 'UTC')) ?: 'UTC',
|
||||||
@@ -61,8 +62,9 @@ final class ModuleCronScheduler
|
|||||||
$statuses = [];
|
$statuses = [];
|
||||||
|
|
||||||
foreach ($definitions as $name => $definition) {
|
foreach ($definitions as $name => $definition) {
|
||||||
$state = $states[$name] ?? [];
|
foreach ($this->jobConfigs($definition, $settings) as $entryIndex => $config) {
|
||||||
$config = $this->jobConfig($definition, $settings);
|
$stateKey = $this->stateKey($name, $entryIndex);
|
||||||
|
$state = $states[$stateKey] ?? [];
|
||||||
$timezone = $this->safeTimezone((string) ($config['timezone'] ?? 'UTC'));
|
$timezone = $this->safeTimezone((string) ($config['timezone'] ?? 'UTC'));
|
||||||
$expression = trim((string) ($config['cron_expression'] ?? ''));
|
$expression = trim((string) ($config['cron_expression'] ?? ''));
|
||||||
$isLocked = $this->isLockActive($state, $nowUtc->getTimestamp());
|
$isLocked = $this->isLockActive($state, $nowUtc->getTimestamp());
|
||||||
@@ -87,6 +89,9 @@ final class ModuleCronScheduler
|
|||||||
}
|
}
|
||||||
|
|
||||||
$statuses[] = $definition + [
|
$statuses[] = $definition + [
|
||||||
|
'job_name' => $name,
|
||||||
|
'entry_index' => $entryIndex,
|
||||||
|
'state_key' => $stateKey,
|
||||||
'config' => $config,
|
'config' => $config,
|
||||||
'state' => $state,
|
'state' => $state,
|
||||||
'enabled' => !empty($config['enabled']),
|
'enabled' => !empty($config['enabled']),
|
||||||
@@ -101,6 +106,7 @@ final class ModuleCronScheduler
|
|||||||
'next_due_at_local' => $nextDueUtc?->setTimezone($timezone)->format('Y-m-d H:i:s'),
|
'next_due_at_local' => $nextDueUtc?->setTimezone($timezone)->format('Y-m-d H:i:s'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $statuses;
|
return $statuses;
|
||||||
}
|
}
|
||||||
@@ -115,14 +121,15 @@ final class ModuleCronScheduler
|
|||||||
|
|
||||||
if (!$this->modules->hasFunction($moduleName, (string) $task['callback'])) {
|
if (!$this->modules->hasFunction($moduleName, (string) $task['callback'])) {
|
||||||
$results[] = [
|
$results[] = [
|
||||||
'task' => $task['name'],
|
'task' => $task['job_name'],
|
||||||
|
'entry_index' => $task['entry_index'],
|
||||||
'ok' => false,
|
'ok' => false,
|
||||||
'message' => 'Callback nicht registriert.',
|
'message' => 'Callback nicht registriert.',
|
||||||
];
|
];
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->acquireLock($moduleName, (string) $task['name'], (int) $task['lock_minutes'])) {
|
if (!$this->acquireLock($moduleName, (string) $task['state_key'], (int) $task['lock_minutes'])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +137,7 @@ final class ModuleCronScheduler
|
|||||||
$scheduledForUtc = trim((string) ($task['previous_due_at'] ?? ''));
|
$scheduledForUtc = trim((string) ($task['previous_due_at'] ?? ''));
|
||||||
$timezone = $this->safeTimezone((string) ($task['timezone'] ?? 'UTC'));
|
$timezone = $this->safeTimezone((string) ($task['timezone'] ?? 'UTC'));
|
||||||
|
|
||||||
$this->persistState($moduleName, (string) $task['name'], [
|
$this->persistState($moduleName, (string) $task['state_key'], [
|
||||||
'last_started_at' => $startedAt,
|
'last_started_at' => $startedAt,
|
||||||
'last_status' => 'running',
|
'last_status' => 'running',
|
||||||
'last_message' => 'Cron-Lauf gestartet.',
|
'last_message' => 'Cron-Lauf gestartet.',
|
||||||
@@ -163,16 +170,17 @@ final class ModuleCronScheduler
|
|||||||
if ($ok && !$skipped) {
|
if ($ok && !$skipped) {
|
||||||
$payload['last_success_at'] = $finishedAt;
|
$payload['last_success_at'] = $finishedAt;
|
||||||
}
|
}
|
||||||
$this->persistState($moduleName, (string) $task['name'], $payload);
|
$this->persistState($moduleName, (string) $task['state_key'], $payload);
|
||||||
|
|
||||||
$results[] = [
|
$results[] = [
|
||||||
'task' => $task['name'],
|
'task' => $task['job_name'],
|
||||||
|
'entry_index' => $task['entry_index'],
|
||||||
'ok' => $ok,
|
'ok' => $ok,
|
||||||
'message' => $message,
|
'message' => $message,
|
||||||
];
|
];
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$finishedAt = gmdate('Y-m-d H:i:s');
|
$finishedAt = gmdate('Y-m-d H:i:s');
|
||||||
$this->persistState($moduleName, (string) $task['name'], [
|
$this->persistState($moduleName, (string) $task['state_key'], [
|
||||||
'last_finished_at' => $finishedAt,
|
'last_finished_at' => $finishedAt,
|
||||||
'last_status' => 'error',
|
'last_status' => 'error',
|
||||||
'last_message' => $e->getMessage(),
|
'last_message' => $e->getMessage(),
|
||||||
@@ -180,7 +188,8 @@ final class ModuleCronScheduler
|
|||||||
'last_scheduled_for' => $scheduledForUtc !== '' ? $scheduledForUtc : null,
|
'last_scheduled_for' => $scheduledForUtc !== '' ? $scheduledForUtc : null,
|
||||||
]);
|
]);
|
||||||
$results[] = [
|
$results[] = [
|
||||||
'task' => $task['name'],
|
'task' => $task['job_name'],
|
||||||
|
'entry_index' => $task['entry_index'],
|
||||||
'ok' => false,
|
'ok' => false,
|
||||||
'message' => $e->getMessage(),
|
'message' => $e->getMessage(),
|
||||||
];
|
];
|
||||||
@@ -190,19 +199,64 @@ final class ModuleCronScheduler
|
|||||||
return $results;
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function jobConfig(array $definition, array $settings): array
|
private function jobConfigs(array $definition, array $settings): array
|
||||||
{
|
{
|
||||||
$jobs = is_array($settings['cron_jobs'] ?? null) ? $settings['cron_jobs'] : [];
|
$jobs = is_array($settings['scheduler_jobs'] ?? null)
|
||||||
$job = is_array($jobs[$definition['name']] ?? null) ? $jobs[$definition['name']] : [];
|
? $settings['scheduler_jobs']
|
||||||
|
: (is_array($settings['cron_jobs'] ?? null) ? $settings['cron_jobs'] : []);
|
||||||
|
$jobExists = array_key_exists($definition['name'], $jobs) && is_array($jobs[$definition['name']] ?? null);
|
||||||
|
$job = $jobExists ? $jobs[$definition['name']] : [];
|
||||||
$timezoneSetting = trim((string) ($definition['timezone_setting'] ?? ''));
|
$timezoneSetting = trim((string) ($definition['timezone_setting'] ?? ''));
|
||||||
$fallbackTimezone = $timezoneSetting !== '' ? trim((string) ($settings[$timezoneSetting] ?? '')) : '';
|
$fallbackTimezone = $timezoneSetting !== '' ? trim((string) ($settings[$timezoneSetting] ?? '')) : '';
|
||||||
|
$defaultEntry = [
|
||||||
return [
|
'enabled' => (bool) $definition['default_enabled'],
|
||||||
'enabled' => array_key_exists('enabled', $job) ? $this->settingBool($job['enabled'], (bool) $definition['default_enabled']) : (bool) $definition['default_enabled'],
|
'cron_expression' => trim((string) ($definition['default_cron'] ?? '0 * * * *')),
|
||||||
'cron_expression' => trim((string) ($job['cron_expression'] ?? $definition['default_cron'] ?? '')),
|
'timezone' => trim((string) ($fallbackTimezone !== '' ? $fallbackTimezone : ($definition['default_timezone'] ?? 'UTC'))),
|
||||||
'timezone' => trim((string) ($job['timezone'] ?? ($fallbackTimezone !== '' ? $fallbackTimezone : $definition['default_timezone']))),
|
'builder' => [],
|
||||||
'builder' => is_array($job['builder'] ?? null) ? $job['builder'] : [],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$entries = is_array($job['entries'] ?? null) ? $job['entries'] : [];
|
||||||
|
if (!$jobExists && $entries === []) {
|
||||||
|
$legacyEntry = [];
|
||||||
|
foreach (['enabled', 'cron_expression', 'timezone', 'builder'] as $field) {
|
||||||
|
if (array_key_exists($field, $job)) {
|
||||||
|
$legacyEntry[$field] = $job[$field];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$entries = [$legacyEntry !== [] ? $legacyEntry : $defaultEntry];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($jobExists && $entries === [] && ($definition['mode'] ?? 'single') === 'multi') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach (array_values($entries) as $entry) {
|
||||||
|
if (!is_array($entry)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$result[] = [
|
||||||
|
'enabled' => array_key_exists('enabled', $entry) ? $this->settingBool($entry['enabled'], (bool) $defaultEntry['enabled']) : (bool) $defaultEntry['enabled'],
|
||||||
|
'cron_expression' => trim((string) ($entry['cron_expression'] ?? $defaultEntry['cron_expression'])),
|
||||||
|
'timezone' => trim((string) ($entry['timezone'] ?? $defaultEntry['timezone'])),
|
||||||
|
'builder' => is_array($entry['builder'] ?? null) ? $entry['builder'] : [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result === []) {
|
||||||
|
$result[] = $defaultEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($definition['mode'] ?? 'single') !== 'multi') {
|
||||||
|
return [0 => $result[0]];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function stateKey(string $jobName, int $entryIndex): string
|
||||||
|
{
|
||||||
|
return $jobName . '#' . $entryIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function fetchStates(string $moduleName): array
|
private function fetchStates(string $moduleName): array
|
||||||
|
|||||||
@@ -307,6 +307,7 @@ final class ModuleManager
|
|||||||
'description' => $data['description'] ?? '',
|
'description' => $data['description'] ?? '',
|
||||||
'setup' => $data['setup'] ?? [],
|
'setup' => $data['setup'] ?? [],
|
||||||
'interval_tasks' => $data['interval_tasks'] ?? [],
|
'interval_tasks' => $data['interval_tasks'] ?? [],
|
||||||
|
'scheduler_jobs' => $data['scheduler_jobs'] ?? ($data['cron_tasks'] ?? []),
|
||||||
'cron_tasks' => $data['cron_tasks'] ?? [],
|
'cron_tasks' => $data['cron_tasks'] ?? [],
|
||||||
'menu' => $data['menu'] ?? [],
|
'menu' => $data['menu'] ?? [],
|
||||||
'sidebar' => $data['sidebar'] ?? [],
|
'sidebar' => $data['sidebar'] ?? [],
|
||||||
|
|||||||
@@ -4,7 +4,27 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
require_once dirname(__DIR__) . '/config/fileload.php';
|
require_once dirname(__DIR__) . '/config/fileload.php';
|
||||||
|
|
||||||
$targetModule = trim((string) ($argv[1] ?? ''));
|
$targetModule = '';
|
||||||
|
$statusOnly = false;
|
||||||
|
|
||||||
|
foreach (array_slice($argv, 1) as $arg) {
|
||||||
|
$arg = trim((string) $arg);
|
||||||
|
if ($arg === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($arg === '--status') {
|
||||||
|
$statusOnly = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (str_starts_with($arg, '--module=')) {
|
||||||
|
$targetModule = trim(substr($arg, 9));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($targetModule === '') {
|
||||||
|
$targetModule = $arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$modules = app()->modules()->all();
|
$modules = app()->modules()->all();
|
||||||
$results = [];
|
$results = [];
|
||||||
|
|
||||||
@@ -19,8 +39,17 @@ foreach ($modules as $name => $meta) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$taskDefs = app()->modules()->intervalTasks($name);
|
$intervalDefs = app()->modules()->intervalTasks($name);
|
||||||
if ($taskDefs === []) {
|
$cronDefs = app()->modules()->cronTasks($name);
|
||||||
|
if ($intervalDefs === [] && $cronDefs === []) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($statusOnly) {
|
||||||
|
$results[$name] = [
|
||||||
|
'interval' => app()->modules()->intervalTaskStatuses($name),
|
||||||
|
'cron' => app()->modules()->cronTaskStatuses($name),
|
||||||
|
];
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,5 +63,6 @@ echo json_encode([
|
|||||||
'ok' => true,
|
'ok' => true,
|
||||||
'ran_at_utc' => gmdate('Y-m-d H:i:s'),
|
'ran_at_utc' => gmdate('Y-m-d H:i:s'),
|
||||||
'module' => $targetModule !== '' ? $targetModule : null,
|
'module' => $targetModule !== '' ? $targetModule : null,
|
||||||
|
'mode' => $statusOnly ? 'status' : 'run',
|
||||||
'results' => $results,
|
'results' => $results,
|
||||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . PHP_EOL;
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . PHP_EOL;
|
||||||
|
|||||||
Reference in New Issue
Block a user