Files
itflow/admin/post/cron.php
johnnyq dfb0298245 Rebuild Maintenance > Cron on the Update and Backup pages' pattern: a status card at the top says cron is running, when it last ran, how many jobs are on and which runs next, or instead that cron has never run or has stopped, with the crontab line and a copy button, that it is switched off, with Turn cron on, that a managed instance's jobs are paused by its manager, or that a job failed, by name; the jobs are listed with an icon, what each does, its schedule and next run, its last run and how long it took, and a status of Queued, Running, Completed, Failed, Skipped, Paused, Not run yet or Didn't finish for one still running six hours on, with the reason a run was skipped under the job and a kept error shown until dismissed, marked as having run since once it has; the list sorts in PHP on the standard sort and order by job, schedule, last run or status, starting by schedule, with no pager or search as it is a fixed list of at most fifteen; a job's menu has Run now, Change the schedule, Copy the command to run it and Turn off, which asks first; the page reloads itself every 10 seconds while a job is queued or running and every 15 while cron has not been heard from; the side column has At a glance, the cron line with a copy button and Turn cron off, which asks first; includes/cron_jobs.php gives each job an icon and drops cronJobTimeAgo, which nothing uses now; the schedule window opens with what the job does, uses the select picker and names the time zone; admin/post/cron.php words its messages as the buttons are worded, with the audit log unchanged; js/auto_reload.js holds a reload while a window, menu or confirmation is open, a field has focus or text is selected, which also stops the Backup page reloading away its Change the time window or what is typed into its retention boxes; CONTRIBUTING says to give a new job its icon; no database change and no new CSS; changelog entries added to 26.11.8
2026-11-13 01:51:34 -05:00

161 lines
5.7 KiB
PHP

<?php
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
/*
* Maintenance > Cron. Apart from the master switch, everything here identifies a job by its
* row, and every row is checked against the registry in includes/cron_jobs.php before
* anything is written - the database decides when and whether a job runs, never which file
* the dispatcher executes.
*/
if (isset($_GET['enable_cron']) || isset($_GET['disable_cron'])) {
validateCSRFToken();
// The master switch, config_enable_cron. Most jobs check it themselves and stop; it is
// not a dispatcher-level gate, so the two jobs that do not check it keep running. It
// lived in Settings > Notifications until 26.08, which is nowhere near anything else
// about cron.
$enabled = isset($_GET['enable_cron']) ? 1 : 0;
mysqli_query($mysqli, "UPDATE settings SET config_enable_cron = $enabled WHERE company_id = 1");
logAudit("Cron", "Edit", "$session_name " . ($enabled ? 'enabled' : 'disabled') . " the master cron switch");
flashAlert("Cron turned " . ($enabled ? 'on' : 'off') . ".", $enabled ? 'success' : 'error');
redirect();
}
if (isset($_POST['edit_cron_job'])) {
validateCSRFToken();
require_once "../includes/cron_jobs.php";
$cron_job_id = intval($_POST['cron_job_id']);
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT cron_job_name FROM cron_jobs WHERE cron_job_id = $cron_job_id LIMIT 1"));
$registry = cronJobRegistryByName();
if (!$row || !isset($registry[$row['cron_job_name']])) {
flashAlert("That cron job is not part of this version of ITFlow.", 'error');
redirect();
}
$cron_job_name = escapeSql($row['cron_job_name']);
$enabled = isset($_POST['enabled']) ? 1 : 0;
$schedule = $_POST['schedule'] === 'Daily' ? 'Daily' : 'Interval';
// A job the registry marks interval-unsafe only accepts the daily schedule. The form
// does not offer anything else, so anything else arriving here is a crafted request
if (($registry[$row['cron_job_name']]['interval_safe'] ?? true) === false) {
$schedule = 'Daily';
}
// A job cannot be asked to run more than once a minute (the dispatcher only wakes that
// often) and anything over a day belongs on the daily schedule instead.
$interval_minutes = min(1440, max(1, intval($_POST['interval_minutes'])));
// Time inputs hand back HH:MM; anything else is discarded rather than stored half-parsed
$daily_at = 'NULL';
if ($schedule === 'Daily') {
$submitted = trim($_POST['daily_at']);
if (!preg_match('/^([01][0-9]|2[0-3]):[0-5][0-9]$/', $submitted)) {
flashAlert("Enter the daily run time as a 24-hour time, for example 03:00.", 'error');
redirect();
}
$daily_at = "'" . escapeSql($submitted) . ":00'";
}
mysqli_query($mysqli, "UPDATE cron_jobs SET
cron_job_enabled = $enabled,
cron_job_schedule = '$schedule',
cron_job_interval_minutes = $interval_minutes,
cron_job_daily_at = $daily_at
WHERE cron_job_id = $cron_job_id");
logAudit("Cron", "Edit", "$session_name edited the schedule for cron job $cron_job_name", 0, $cron_job_id);
flashAlert("Cron job schedule updated.");
redirect();
}
if (isset($_GET['run_cron_job'])) {
validateCSRFToken();
require_once "../includes/cron_jobs.php";
$cron_job_id = intval($_GET['run_cron_job']);
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT cron_job_name FROM cron_jobs WHERE cron_job_id = $cron_job_id LIMIT 1"));
$registry = cronJobRegistryByName();
if (!$row || !isset($registry[$row['cron_job_name']])) {
flashAlert("That cron job is not part of this version of ITFlow.", 'error');
redirect();
}
$cron_job_name = escapeSql($row['cron_job_name']);
$cron_job_label = $registry[$row['cron_job_name']]['label'];
// The job is not run here. These scripts are written for the command line and some of
// them take minutes, so the request is left for the dispatcher to pick up on its next
// pass, which also means it goes through the same lock and claim as a scheduled run.
mysqli_query($mysqli, "UPDATE cron_jobs SET cron_job_run_now = 1 WHERE cron_job_id = $cron_job_id");
logAudit("Cron", "Run", "$session_name requested an immediate run of cron job $cron_job_name", 0, $cron_job_id);
flashAlert("$cron_job_label is queued and will start within a minute.");
redirect();
}
if (isset($_GET['enable_cron_job']) || isset($_GET['disable_cron_job'])) {
validateCSRFToken();
$enabled = isset($_GET['enable_cron_job']) ? 1 : 0;
$cron_job_id = intval($_GET[$enabled ? 'enable_cron_job' : 'disable_cron_job']);
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT cron_job_name FROM cron_jobs WHERE cron_job_id = $cron_job_id LIMIT 1"));
if (!$row) {
redirect();
}
require_once "../includes/cron_jobs.php";
$cron_job_name = escapeSql($row['cron_job_name']);
$cron_job_label = cronJobRegistryByName()[$row['cron_job_name']]['label'] ?? 'Cron job';
mysqli_query($mysqli, "UPDATE cron_jobs SET cron_job_enabled = $enabled WHERE cron_job_id = $cron_job_id");
logAudit("Cron", "Edit", "$session_name " . ($enabled ? 'enabled' : 'disabled') . " cron job $cron_job_name", 0, $cron_job_id);
flashAlert("$cron_job_label turned " . ($enabled ? 'on' : 'off') . ".", $enabled ? 'success' : 'error');
redirect();
}
if (isset($_GET['clear_cron_error'])) {
validateCSRFToken();
$cron_job_id = intval($_GET['clear_cron_error']);
mysqli_query($mysqli, "UPDATE cron_jobs SET cron_job_last_error = NULL, cron_job_last_error_at = NULL WHERE cron_job_id = $cron_job_id");
flashAlert("Error cleared.");
redirect();
}