asdasd
All checks were successful
Deploy / deploy-staging (push) Successful in 6s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-04-30 02:39:50 +02:00
parent 9c9ec477d0
commit 0c5c89acfa
11 changed files with 961 additions and 65 deletions

View File

@@ -43,6 +43,8 @@ foreach ($fields as $field) {
$isFxRatesSetup = $moduleName === 'fx-rates';
$current = modules()->settings($moduleName);
$intervalTaskStatuses = modules()->intervalTaskStatuses($moduleName);
$cronTaskDefinitions = modules()->cronTasks($moduleName);
$cronTaskStatuses = modules()->cronTaskStatuses($moduleName);
$setupActions = modules()->hasFunction($moduleName, 'setup_actions')
? (array) module_fn($moduleName, 'setup_actions')
: [];
@@ -285,6 +287,16 @@ $formatRunTimestamp = static function (?string $value): string {
return date('Y-m-d H:i:s', $ts);
};
$cronWeekdays = [
'0' => 'Sonntag',
'1' => 'Montag',
'2' => 'Dienstag',
'3' => 'Mittwoch',
'4' => 'Donnerstag',
'5' => 'Freitag',
'6' => 'Samstag',
];
$renderField = function (array $field) use (&$current, $getNested, $driverOptions): void {
$name = (string)($field['name'] ?? '');
if ($name === '') {
@@ -371,6 +383,39 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$current = array_replace_recursive($current, $payload);
if ($cronTaskDefinitions !== []) {
$cronJobs = is_array($current['cron_jobs'] ?? null) ? $current['cron_jobs'] : [];
foreach ($cronTaskDefinitions as $cronTask) {
if (!is_array($cronTask)) {
continue;
}
$cronName = trim((string) ($cronTask['name'] ?? ''));
if ($cronName === '') {
continue;
}
$prefix = 'cron_job_' . preg_replace('/[^a-zA-Z0-9_]/', '_', $cronName);
$cronJobs[$cronName] = array_merge(
is_array($cronJobs[$cronName] ?? null) ? $cronJobs[$cronName] : [],
[
'enabled' => isset($_POST[$prefix . '_enabled']),
'cron_expression' => trim((string) ($_POST[$prefix . '_cron_expression'] ?? ($cronTask['default_cron'] ?? ''))),
'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;
}
$postedTestGroup = (string)($_POST['test_db'] ?? '');
$postedResetGroup = (string)($_POST['reset_db'] ?? '');
$postedSetupAction = trim((string)($_POST['module_setup_action'] ?? ''));
@@ -611,21 +656,14 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
<div class="setup-panel__head">
<div>
<span class="pill">Scheduler</span>
<h2>Taeglicher Abruf</h2>
<h2>Cron-Zeitzone</h2>
</div>
</div>
<div class="setup-grid">
<label class="setup-field muted">
<span>Taeglichen Abruf aktivieren</span>
<input type="checkbox" name="daily_refresh_enabled" value="1" <?= !empty($current['daily_refresh_enabled']) ? 'checked' : '' ?>>
</label>
<label class="setup-field muted">
<span>Taegliche Abrufstunde</span>
<input type="number" name="daily_refresh_hour" min="0" max="23" value="<?= e((string) ($current['daily_refresh_hour'] ?? 18)) ?>">
</label>
<label class="setup-field muted">
<span>Scheduler-Zeitzone</span>
<input type="text" name="schedule_timezone" value="<?= e((string) ($current['schedule_timezone'] ?? 'Europe/Berlin')) ?>">
<small class="muted">Diese Zeitzone wird fuer Cron-Jobs und die lokale Zeitberechnung verwendet.</small>
</label>
</div>
</section>
@@ -736,6 +774,68 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
</section>
<?php endif; ?>
<?php if ($cronTaskStatuses !== []): ?>
<section class="setup-panel">
<div class="setup-panel__head">
<div>
<span class="pill">Automationen</span>
<h2>Cron-Jobs</h2>
<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 class="setup-grid">
<?php foreach ($cronTaskStatuses as $task): ?>
<?php
$cronName = trim((string) ($task['name'] ?? ''));
$cronConfig = is_array($task['config'] ?? null) ? $task['config'] : [];
$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>
<span><?= e((string) ($task['label'] ?? $cronName)) ?></span>
<?php if (trim((string) ($task['help'] ?? '')) !== ''): ?>
<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>
<input type="text" name="<?= e($prefix) ?>_timezone" value="<?= e((string) ($cronConfig['timezone'] ?? 'UTC')) ?>" data-cron-timezone>
<select name="<?= e($prefix) ?>_builder_mode" data-cron-builder-mode>
<option value="builder" <?= (string) ($builder['mode'] ?? 'builder') === 'builder' ? 'selected' : '' ?>>Builder</option>
<option value="manual" <?= (string) ($builder['mode'] ?? 'builder') === 'manual' ? 'selected' : '' ?>>Cron-Syntax</option>
</select>
<div data-cron-builder-fields>
<select name="<?= e($prefix) ?>_builder_kind" data-cron-builder-kind>
<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="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="every_x_hours" <?= (string) ($builder['kind'] ?? '') === 'every_x_hours' ? 'selected' : '' ?>>Alle x Stunden</option>
</select>
<input type="time" name="<?= e($prefix) ?>_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>
<select name="<?= e($prefix) ?>_builder_weekday" data-cron-builder-weekday>
<?php foreach ($cronWeekdays as $weekdayValue => $weekdayLabel): ?>
<option value="<?= e($weekdayValue) ?>" <?= (string) ($builder['weekday'] ?? '1') === $weekdayValue ? 'selected' : '' ?>><?= e($weekdayLabel) ?></option>
<?php endforeach; ?>
</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="23" name="<?= e($prefix) ?>_builder_interval_hours" value="<?= e((string) ($builder['interval_hours'] ?? 6)) ?>" data-cron-builder-interval-hours>
</div>
<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">Naechster Lauf UTC: <?= e($formatRunTimestamp((string) ($task['next_due_at'] ?? ''))) ?></small>
<small class="muted">Naechster Lauf lokal: <?= e((string) (($task['next_due_at_local'] ?? '') !== '' ? $task['next_due_at_local'] : '-')) ?></small>
<small class="muted">Status: <?= e((string) (($task['state']['last_status'] ?? '') !== '' ? $task['state']['last_status'] : '-')) ?></small>
<?php if (trim((string) ($task['parse_error'] ?? '')) !== ''): ?>
<small class="muted" style="color:#b42318;">Cron-Fehler: <?= e((string) $task['parse_error']) ?></small>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<div class="setup-actions">
<button class="cta-button" type="submit">Speichern</button>
<a class="nav-link" href="/modules/access/<?= e($moduleName) ?>">Zugriff verwalten</a>
@@ -1034,6 +1134,68 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
</section>
<?php endif; ?>
<?php if ($cronTaskStatuses !== []): ?>
<section class="setup-panel">
<div class="setup-panel__head">
<div>
<span class="pill">Automationen</span>
<h2>Cron-Jobs</h2>
<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 class="setup-grid">
<?php foreach ($cronTaskStatuses as $task): ?>
<?php
$cronName = trim((string) ($task['name'] ?? ''));
$cronConfig = is_array($task['config'] ?? null) ? $task['config'] : [];
$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>
<span><?= e((string) ($task['label'] ?? $cronName)) ?></span>
<?php if (trim((string) ($task['help'] ?? '')) !== ''): ?>
<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>
<input type="text" name="<?= e($prefix) ?>_timezone" value="<?= e((string) ($cronConfig['timezone'] ?? 'UTC')) ?>" data-cron-timezone>
<select name="<?= e($prefix) ?>_builder_mode" data-cron-builder-mode>
<option value="builder" <?= (string) ($builder['mode'] ?? 'builder') === 'builder' ? 'selected' : '' ?>>Builder</option>
<option value="manual" <?= (string) ($builder['mode'] ?? 'builder') === 'manual' ? 'selected' : '' ?>>Cron-Syntax</option>
</select>
<div data-cron-builder-fields>
<select name="<?= e($prefix) ?>_builder_kind" data-cron-builder-kind>
<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="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="every_x_hours" <?= (string) ($builder['kind'] ?? '') === 'every_x_hours' ? 'selected' : '' ?>>Alle x Stunden</option>
</select>
<input type="time" name="<?= e($prefix) ?>_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>
<select name="<?= e($prefix) ?>_builder_weekday" data-cron-builder-weekday>
<?php foreach ($cronWeekdays as $weekdayValue => $weekdayLabel): ?>
<option value="<?= e($weekdayValue) ?>" <?= (string) ($builder['weekday'] ?? '1') === $weekdayValue ? 'selected' : '' ?>><?= e($weekdayLabel) ?></option>
<?php endforeach; ?>
</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="23" name="<?= e($prefix) ?>_builder_interval_hours" value="<?= e((string) ($builder['interval_hours'] ?? 6)) ?>" data-cron-builder-interval-hours>
</div>
<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">Naechster Lauf UTC: <?= e($formatRunTimestamp((string) ($task['next_due_at'] ?? ''))) ?></small>
<small class="muted">Naechster Lauf lokal: <?= e((string) (($task['next_due_at_local'] ?? '') !== '' ? $task['next_due_at_local'] : '-')) ?></small>
<small class="muted">Status: <?= e((string) (($task['state']['last_status'] ?? '') !== '' ? $task['state']['last_status'] : '-')) ?></small>
<?php if (trim((string) ($task['parse_error'] ?? '')) !== ''): ?>
<small class="muted" style="color:#b42318;">Cron-Fehler: <?= e((string) $task['parse_error']) ?></small>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<?php if ($setupActions !== []): ?>
<section class="setup-panel">
<div class="setup-panel__head">
@@ -1172,5 +1334,79 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
<a class="nav-link" href="/modules">Zurück</a>
</div>
<?php endif; ?>
<script>
(() => {
const jobs = document.querySelectorAll('[data-cron-job]');
if (!jobs.length) return;
const buildExpression = (kind, values) => {
const [hourRaw, minuteRaw] = String(values.time || '18:00').split(':');
const minute = Math.max(0, Math.min(59, Number(minuteRaw || 0)));
const hour = Math.max(0, Math.min(23, Number(hourRaw || 0)));
const everyDays = Math.max(1, Number(values.intervalDays || 2));
const weekday = String(values.weekday || '1');
const monthDay = Math.max(1, Math.min(31, Number(values.monthDay || 1)));
const everyHours = Math.max(1, Math.min(23, Number(values.intervalHours || 6)));
switch (kind) {
case 'every_x_days':
return `${minute} ${hour} */${everyDays} * *`;
case 'weekly':
return `${minute} ${hour} * * ${weekday}`;
case 'monthly_day':
return `${minute} ${hour} ${monthDay} * *`;
case 'every_x_hours':
return `${minute} */${everyHours} * * *`;
case 'daily':
default:
return `${minute} ${hour} * * *`;
}
};
jobs.forEach((job) => {
const expression = job.querySelector('[data-cron-expression]');
const mode = job.querySelector('[data-cron-builder-mode]');
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]');
if (!expression || !mode || !fields || !kind || !time || !intervalDays || !weekday || !monthDay || !intervalHours) {
return;
}
const sync = () => {
const builderMode = mode.value === 'manual' ? 'manual' : 'builder';
fields.hidden = builderMode === 'manual';
expression.readOnly = builderMode !== 'manual';
if (builderMode === 'builder') {
expression.value = buildExpression(kind.value, {
time: time.value,
intervalDays: intervalDays.value,
weekday: weekday.value,
monthDay: monthDay.value,
intervalHours: intervalHours.value,
});
}
};
[mode, kind, time, intervalDays, weekday, monthDay, intervalHours].forEach((node) => {
node.addEventListener('change', sync);
node.addEventListener('input', sync);
});
sync();
});
})();
</script>
<style>
[data-cron-builder-fields] {
display: grid;
gap: 10px;
}
</style>
</form>
</div>