Merge branch 'dev' into sfle_enhancement

This commit is contained in:
HB9HIL
2024-05-03 11:19:43 +02:00
committed by GitHub
19 changed files with 1501 additions and 29 deletions

View File

@@ -22,7 +22,7 @@ $config['migration_enabled'] = TRUE;
|
*/
$config['migration_version'] = 195;
$config['migration_version'] = 196;
/*
|--------------------------------------------------------------------------

View File

@@ -25,6 +25,10 @@ class Clublog extends CI_Controller {
public function upload() {
$this->load->model('clublog_model');
// set the last run in cron table for the correct cron id
$this->load->model('cron_model');
$this->cron_model->set_last_run($this->router->class.'_'.$this->router->method);
$users = $this->clublog_model->get_clublog_users();
foreach ($users as $user) {

View File

@@ -0,0 +1,262 @@
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class cron extends CI_Controller {
function __construct() {
parent::__construct();
if (ENVIRONMENT == 'maintenance' && $this->session->userdata('user_id') == '') {
echo "Maintenance Mode is active. Try again later.\n";
redirect('user/login');
}
$this->load->model('cron_model');
}
public function index() {
$this->load->model('user_model');
if (!$this->user_model->authorize(99)) {
$this->session->set_flashdata('notice', 'You\'re not allowed to do that!');
redirect('dashboard');
}
$this->load->helper('file');
$footerData = [];
$footerData['scripts'] = [
'assets/js/cronstrue.min.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/cronstrue.min.js")),
'assets/js/sections/cron.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/sections/cron.js"))
];
$data['page_title'] = "Cron Manager";
$data['crons'] = $this->cron_model->get_crons();
$mastercron = array();
$mastercron = $this->get_mastercron_status();
$data['mastercron'] = $mastercron;
$this->load->view('interface_assets/header', $data);
$this->load->view('cron/index');
$this->load->view('interface_assets/footer', $footerData);
}
public function run() {
// This is the main function, which handles all crons, runs them if enabled and writes the 'next run' timestamp to the database
// TODO Add an API Key to the cronjob to improve security?
$crons = $this->cron_model->get_crons();
$status = 'pending';
foreach ($crons as $cron) {
if ($cron->enabled == 1) {
// calculate the crons expression
$data = array(
'expression' => $cron->expression,
'timeZone' => null
);
$this->load->library('CronExpression', $data);
$cronjob = $this->cronexpression;
$dt = new DateTime();
$isdue = $cronjob->isMatching($dt);
$next_run = $cronjob->getNext();
$next_run_date = date('Y-m-d H:i:s', $next_run);
$this->cron_model->set_next_run($cron->id, $next_run_date);
if ($isdue == true) {
$isdue_result = 'true';
// TODO Add log_message level debug here to have logging for the cron manager
echo "CRON: " . $cron->id . " -> is due: " . $isdue_result . "\n";
echo "CRON: " . $cron->id . " -> RUNNING...\n";
$url = base_url() . $cron->function;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_USERAGENT, 'Wavelog Updater');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$crun = curl_exec($ch);
curl_close($ch);
if ($crun !== false) {
echo "CRON: " . $cron->id . " -> CURL Result: " . $crun . "\n";
$status = 'healthy';
} else {
echo "ERROR: Something went wrong with " . $cron->id . "\n";
$status = 'failed';
}
} else {
$isdue_result = 'false';
echo "CRON: " . $cron->id . " -> is due: " . $isdue_result . " -> Next Run: " . $next_run_date . "\n";
$status = 'healthy';
}
} else {
echo 'CRON: ' . $cron->id . " is disabled. skipped..\n";
$status = 'disabled';
// Set the next_run timestamp to null to indicate in the view/database that this cron is disabled
$this->cron_model->set_next_run($cron->id, null);
}
$this->cron_model->set_status($cron->id, $status);
$this->cronexpression = null;
}
$datetime = new DateTime("now", new DateTimeZone('UTC'));
$datetime = $datetime->format('Ymd H:i:s');
$this->optionslib->update('mastercron_last_run', $datetime , 'no');
}
public function editDialog() {
$cron_query = $this->cron_model->cron(xss_clean($this->input->post('id', true)));
$data['cron'] = $cron_query->row();
$data['page_title'] = "Edit Cronjob";
$this->load->view('cron/edit', $data);
}
public function edit() {
$this->load->model('user_model');
if (!$this->user_model->authorize(99)) {
$this->session->set_flashdata('notice', 'You\'re not allowed to do that!');
redirect('dashboard');
}
$id = xss_clean($this->input->post('cron_id', true));
$description = xss_clean($this->input->post('cron_description', true));
$expression = xss_clean($this->input->post('cron_expression', true));
$enabled = xss_clean($this->input->post('cron_enabled', true));
$data = array(
'expression' => $expression,
'timeZone' => null
);
$this->load->library('CronExpression', $data);
$cron = $this->cronexpression;
if ($cron->isValid()) {
$this->cron_model->edit_cron($id, $description, $expression, $enabled);
$this->cronexpression = null;
header("Content-type: application/json");
echo json_encode(['success' => true, 'messagecategory' => 'success', 'message' => 'Changes saved for Cronjob "' . $id . '"']);
} else {
$this->session->set_flashdata('error', 'The Cron Expression you entered is not valid');
$this->cronexpression = null;
header("Content-type: application/json");
echo json_encode(['success' => false, 'messagecategory' => 'error', 'message' => 'The expression "' . $expression . '" is not valid. Please try again.']);
}
}
public function toogleEnableCronSwitch() {
$id = xss_clean($this->input->post('id', true));
$cron_enabled = xss_clean($this->input->post('checked', true));
if ($id ?? '' != '') {
$this->cron_model->set_cron_enabled($id, $cron_enabled);
$data['success'] = 1;
} else {
$data['success'] = 0;
$data['flashdata'] = 'Not allowed';
}
echo json_encode($data);
}
public function fetchCrons() {
$hres = [];
$result = $this->cron_model->get_crons();
foreach ($result as $cron) {
$single = (object) [];
$single->cron_id = $cron->id;
$single->cron_description = $cron->description;
$single->cron_status = $this->cronStatus2html($cron->enabled, $cron->status);
$single->cron_expression = $this->cronExpression2html($cron->expression);
$single->cron_last_run = $cron->last_run ?? 'never';
$single->cron_next_run = ($cron->enabled == '1') ? ($cron->next_run ?? 'calculating..') : 'never';
$single->cron_edit = $this->cronEdit2html($cron->id);
$single->cron_enabled = $this->cronEnabled2html($cron->id, $cron->enabled);
array_push($hres, $single);
}
echo json_encode($hres);
}
private function cronStatus2html($enabled, $status) {
if ($enabled == '1') {
if ($status == 'healthy') {
$htmlret = '<span class="badge text-bg-success">healthy</span>';
} else {
$htmlret = '<span class="badge text-bg-warning">' . $status . '</span>';
}
} else {
$htmlret = '<span class="badge text-bg-secondary">disabled</span>';
}
return $htmlret;
}
private function cronExpression2html($expression) {
$htmlret = '<code id="humanreadable_tooltip" data-bs-toggle="tooltip">' . $expression . '</code>';
return $htmlret;
}
private function cronEdit2html($id) {
$htmlret = '<button id="' . $id . '" class="editCron btn btn-outline-primary btn-sm"><i class="fas fa-edit"></i></button>';
return $htmlret;
}
private function cronEnabled2html($id, $enabled) {
if ($enabled == '1') {
$checked = 'checked';
} else {
$checked = '';
}
$htmlret = '<div class="form-check form-switch"><input name="cron_enable_switch" class="form-check-input enableCronSwitch" type="checkbox" role="switch" id="' . $id . '" ' . $checked . '></div>';
return $htmlret;
}
private function get_mastercron_status() {
$warning_timelimit_seconds = 120; // yellow - warning please check
$error_timelimit_seconds = 600; // red - "not running"
$result = array();
$last_run = $this->optionslib->get_option('mastercron_last_run') ?? null;
if ($last_run != null) {
$timestamp_last_run = DateTime::createFromFormat('Ymd H:i:s', $last_run, new DateTimeZone('UTC'));
$now = new DateTime();
$diff = $now->getTimestamp() - $timestamp_last_run->getTimestamp();
if ($diff >= 0 && $diff <= $warning_timelimit_seconds) {
$result['status'] = 'OK';
$result['status_class'] = 'success';
} else {
if ($diff <= $error_timelimit_seconds) {
$result['status'] = 'Last run occurred more than ' . $warning_timelimit_seconds . ' seconds ago.<br>Please check your master cron! It should run every minute (* * * * *).';
$result['status_class'] = 'warning';
} else {
$result['status'] = 'Last run occurred more than ' . ($error_timelimit_seconds / 60) . ' minutes ago.<br>Seems like your Mastercron isn\'t running!<br>It should run every minute (* * * * *).';
$result['status_class'] = 'danger';
}
}
} else {
$result['status'] = 'Not running';
$result['status_class'] = 'danger';
}
return $result;
}
}

View File

@@ -21,6 +21,7 @@ class Debug extends CI_Controller
$this->load->model('Logbook_model');
$this->load->model('Debug_model');
$this->load->model('Stations');
$this->load->model('cron_model');
$footerData = [];
$footerData['scripts'] = ['assets/js/sections/debug.js'];
@@ -62,6 +63,14 @@ class Debug extends CI_Controller
$data['userdata_status'] = $userdata_status;
}
$data['dxcc_update'] = $this->cron_model->cron('update_dxcc')->row();
$data['dok_update'] = $this->cron_model->cron('update_update_dok')->row();
$data['lotw_user_update'] = $this->cron_model->cron('update_lotw_users')->row();
$data['pota_update'] = $this->cron_model->cron('update_update_pota')->row();
$data['scp_update'] = $this->cron_model->cron('update_update_clublog_scp')->row();
$data['sota_update'] = $this->cron_model->cron('update_update_sota')->row();
$data['wwff_update'] = $this->cron_model->cron('update_update_wwff')->row();
$data['page_title'] = "Debug";
$this->load->view('interface_assets/header', $data);

View File

@@ -713,6 +713,11 @@ class eqsl extends CI_Controller {
* Used for CRON job
*/
public function sync() {
// set the last run in cron table for the correct cron id
$this->load->model('cron_model');
$this->cron_model->set_last_run($this->router->class.'_'.$this->router->method);
ini_set('memory_limit', '-1');
set_time_limit(0);
$this->load->model('eqslmethods_model');

View File

@@ -25,6 +25,10 @@ class Hrdlog extends CI_Controller {
public function upload() {
$this->setOptions();
// set the last run in cron table for the correct cron id
$this->load->model('cron_model');
$this->cron_model->set_last_run($this->router->class.'_'.$this->router->method);
$this->load->model('logbook_model');
$station_ids = $this->logbook_model->get_station_id_with_hrdlog_code();

View File

@@ -198,6 +198,10 @@ class Lotw extends CI_Controller {
echo "You must install php OpenSSL for LoTW functions to work";
}
// set the last run in cron table for the correct cron id
$this->load->model('cron_model');
$this->cron_model->set_last_run($this->router->class.'_'.$this->router->method);
// Get Station Profile Data
$this->load->model('Stations');

View File

@@ -69,6 +69,10 @@ class Qrz extends CI_Controller {
public function upload() {
$this->setOptions();
// set the last run in cron table for the correct cron id
$this->load->model('cron_model');
$this->cron_model->set_last_run($this->router->class.'_'.$this->router->method);
$this->load->model('logbook_model');
$station_ids = $this->logbook_model->get_station_id_with_qrz_api();
@@ -260,6 +264,8 @@ class Qrz extends CI_Controller {
$this->load->model('user_model');
$this->load->model('logbook_model');
$this->load->model('cron_model');
$this->cron_model->set_last_run($this->router->class.'_'.$this->router->method);
$api_keys = $this->logbook_model->get_qrz_apikeys();

View File

@@ -20,6 +20,9 @@ class Update extends CI_Controller {
public function index()
{
$this->load->model('user_model');
if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('notice', 'You\'re not allowed to do that!'); redirect('dashboard'); }
$data['page_title'] = "Updates";
$this->load->view('interface_assets/header', $data);
$this->load->view('update/index');
@@ -176,6 +179,11 @@ class Update extends CI_Controller {
// Updates the DXCC & Exceptions from the Club Log Cty.xml file.
public function dxcc() {
// set the last run in cron table for the correct cron id
$this->load->model('cron_model');
$this->cron_model->set_last_run($this->router->class.'_'.$this->router->method);
$this->update_status("Downloading file");
// give it 10 minutes...
@@ -236,9 +244,6 @@ class Update extends CI_Controller {
$html .= "Dxcc Prefixes: ".$this->db->count_all('dxcc_prefixes')."<br/>";
} else {
$html = $done."....<br/>";
$datetime = new DateTime("now", new DateTimeZone('UTC'));
$datetime = $datetime->format('Ymd h:i');
$this->optionslib->update('dxcc_clublog_update', $datetime , 'no');
}
file_put_contents($this->make_update_path("status.html"), $html);
@@ -302,6 +307,11 @@ class Update extends CI_Controller {
}
public function update_clublog_scp() {
// set the last run in cron table for the correct cron id
$this->load->model('cron_model');
$this->cron_model->set_last_run($this->router->class.'_'.$this->router->method);
$strFile = $this->make_update_path("clublog_scp.txt");
$url = "https://cdn.clublog.org/clublog.scp.gz";
set_time_limit(300);
@@ -320,9 +330,6 @@ class Update extends CI_Controller {
if ($nCount > 0)
{
echo "DONE: " . number_format($nCount) . " callsigns loaded";
$datetime = new DateTime("now", new DateTimeZone('UTC'));
$datetime = $datetime->format('Ymd h:i');
$this->optionslib->update('scp_update', $datetime , 'no');
} else {
echo "FAILED: Empty file";
}
@@ -352,6 +359,11 @@ class Update extends CI_Controller {
}
public function lotw_users() {
// set the last run in cron table for the correct cron id
$this->load->model('cron_model');
$this->cron_model->set_last_run($this->router->class.'_'.$this->router->method);
$mtime = microtime();
$mtime = explode(" ",$mtime);
$mtime = $mtime[1] + $mtime[0];
@@ -390,9 +402,6 @@ class Update extends CI_Controller {
$totaltime = ($endtime - $starttime);
echo "This page was created in ".$totaltime." seconds <br />";
echo "Records inserted: " . $i . " <br/>";
$datetime = new DateTime("now", new DateTimeZone('UTC'));
$datetime = $datetime->format('Ymd h:i');
$this->optionslib->update('lotw_users_update', $datetime , 'no');
}
public function lotw_check() {
@@ -412,6 +421,11 @@ class Update extends CI_Controller {
* Used for autoupdating the DOK file which is used in the QSO entry dialog for autocompletion.
*/
public function update_dok() {
// set the last run in cron table for the correct cron id
$this->load->model('cron_model');
$this->cron_model->set_last_run($this->router->class.'_'.$this->router->method);
$contents = file_get_contents('https://www.df2et.de/cqrlog/dok_and_sdok.txt', true);
if($contents === FALSE) {
@@ -424,9 +438,6 @@ class Update extends CI_Controller {
if ($nCount > 0)
{
echo "DONE: " . number_format($nCount) . " DOKs and SDOKs saved";
$datetime = new DateTime("now", new DateTimeZone('UTC'));
$datetime = $datetime->format('Ymd h:i');
$this->optionslib->update('dok_file_update', $datetime , 'no');
} else {
echo"FAILED: Empty file";
}
@@ -440,6 +451,11 @@ class Update extends CI_Controller {
* Used for autoupdating the SOTA file which is used in the QSO entry dialog for autocompletion.
*/
public function update_sota() {
// set the last run in cron table for the correct cron id
$this->load->model('cron_model');
$this->cron_model->set_last_run($this->router->class.'_'.$this->router->method);
$csvfile = 'https://www.sotadata.org.uk/summitslist.csv';
$sotafile = './assets/json/sota.txt';
@@ -474,9 +490,6 @@ class Update extends CI_Controller {
if ($nCount > 0)
{
echo "DONE: " . number_format($nCount) . " SOTA's saved";
$datetime = new DateTime("now", new DateTimeZone('UTC'));
$datetime = $datetime->format('Ymd h:i');
$this->optionslib->update('sota_file_update', $datetime , 'no');
} else {
echo"FAILED: Empty file";
}
@@ -486,6 +499,11 @@ class Update extends CI_Controller {
* Pulls the WWFF directory for autocompletion in QSO dialogs
*/
public function update_wwff() {
// set the last run in cron table for the correct cron id
$this->load->model('cron_model');
$this->cron_model->set_last_run($this->router->class.'_'.$this->router->method);
$csvfile = 'https://wwff.co/wwff-data/wwff_directory.csv';
$wwfffile = './assets/json/wwff.txt';
@@ -524,15 +542,17 @@ class Update extends CI_Controller {
if ($nCount > 0)
{
echo "DONE: " . number_format($nCount) . " WWFF's saved";
$datetime = new DateTime("now", new DateTimeZone('UTC'));
$datetime = $datetime->format('Ymd h:i');
$this->optionslib->update('wwff_file_update', $datetime , 'no');
} else {
echo"FAILED: Empty file";
}
}
public function update_pota() {
// set the last run in cron table for the correct cron id
$this->load->model('cron_model');
$this->cron_model->set_last_run($this->router->class.'_'.$this->router->method);
$csvfile = 'https://pota.app/all_parks.csv';
$potafile = './assets/json/pota.txt';
@@ -570,9 +590,6 @@ class Update extends CI_Controller {
if ($nCount > 0)
{
echo "DONE: " . number_format($nCount) . " POTA's saved";
$datetime = new DateTime("now", new DateTimeZone('UTC'));
$datetime = $datetime->format('Ymd h:i');
$this->optionslib->update('pota_file_update', $datetime , 'no');
} else {
echo"FAILED: Empty file";
}

View File

@@ -0,0 +1,424 @@
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
/**
* Cron expression parser and validator
*
* @author René Pollesch
* edited by HB9HIL 04/2024
*/
class CronExpression {
/**
* Weekday name look-up table
*/
private const WEEKDAY_NAMES = [
'sun' => 0,
'mon' => 1,
'tue' => 2,
'wed' => 3,
'thu' => 4,
'fri' => 5,
'sat' => 6
];
/**
* Month name look-up table
*/
private const MONTH_NAMES = [
'jan' => 1,
'feb' => 2,
'mar' => 3,
'apr' => 4,
'may' => 5,
'jun' => 6,
'jul' => 7,
'aug' => 8,
'sep' => 9,
'oct' => 10,
'nov' => 11,
'dec' => 12
];
/**
* Value boundaries
*/
private const VALUE_BOUNDARIES = [
0 => [
'min' => 0,
'max' => 59,
'mod' => 1
],
1 => [
'min' => 0,
'max' => 23,
'mod' => 1
],
2 => [
'min' => 1,
'max' => 31,
'mod' => 1
],
3 => [
'min' => 1,
'max' => 12,
'mod' => 1
],
4 => [
'min' => 0,
'max' => 7,
'mod' => 0
]
];
/**
* @var DateTimeZone|null
*/
protected readonly ?DateTimeZone $timeZone;
/**
* @var array|null
*/
protected readonly ?array $registers;
/**
* @var string
*/
protected readonly string $expression;
/**
* @param string $expression a cron expression, e.g. "* * * * *"
* @param DateTimeZone|null $timeZone time zone objectstring $expression, DateTimeZone $timeZone = null
*/
public function __construct($data) {
$this->timeZone = $data['timeZone'];
$this->expression = $data['expression'];
try {
$this->registers = $this->parse($data['expression']);
} catch (Exception $e) {
$this->registers = null;
}
}
/**
* Whether current cron expression has been parsed successfully
*
* @return bool
*/
public function isValid(): bool {
return null !== $this->registers;
}
/**
* Match either "now", a given date/time object or a timestamp against current cron expression
*
* @param mixed $when a DateTime object, a timestamp (int), or "now" if not set
* @return bool
* @throws Exception
*/
public function isMatching($when = null): bool {
if (false === ($when instanceof DateTimeInterface)) {
$when = (new DateTime())->setTimestamp($when === null ? time() : $when);
}
if ($this->timeZone !== null) {
$when->setTimezone($this->timeZone);
}
return $this->isValid() && $this->match(sscanf($when->format('i G j n w'), '%d %d %d %d %d'));
}
/**
* Calculate next matching timestamp
*
* @param mixed $start a DateTime object, a timestamp (int) or "now" if not set
* @return int|bool next matching timestamp, or false on error
* @throws Exception
*/
public function getNext($start = null) {
if ($this->isValid()) {
$next = $this->toDateTime($start);
do {
$pos = sscanf($next->format('i G j n Y w'), '%d %d %d %d %d %d');
} while ($this->increase($next, $pos));
return $next->getTimestamp();
}
return false;
}
/**
* @param mixed $start a DateTime object, a timestamp (int) or "now" if not set
* @return DateTime
*/
private function toDateTime($start): DateTime {
if ($start instanceof DateTimeInterface) {
$next = $start;
} elseif ((int)$start > 0) {
$next = new DateTime('@' . $start);
} else {
$next = new DateTime('@' . time());
}
$next->setTimestamp($next->getTimeStamp() - $next->getTimeStamp() % 60);
$next->setTimezone($this->timeZone ?: new DateTimeZone(date_default_timezone_get()));
if ($this->isMatching($next)) {
$next->modify('+1 minute');
}
return $next;
}
/**
* Increases the timestamp in step sizes depending on which segment(s) of the cron pattern are matching.
* Returns FALSE if the cron pattern is matching and thus no further cycle is required.
*
* @param DateTimeInterface $next
* @param array $pos
* @return bool
*/
private function increase(DateTimeInterface $next, array $pos): bool {
switch (true) {
case false === isset($this->registers[3][$pos[3]]):
// next month, reset day/hour/minute
$next->setTime(0, 0);
$next->setDate($pos[4], $pos[3], 1);
$next->modify('+1 month');
return true;
case false === (isset($this->registers[2][$pos[2]]) && isset($this->registers[4][$pos[5]])):
// next day, reset hour/minute
$next->setTime(0, 0);
$next->modify('+1 day');
return true;
case false === isset($this->registers[1][$pos[1]]):
// next hour, reset minute
$next->setTime($pos[1], 0);
$next->modify('+1 hour');
return true;
case false === isset($this->registers[0][$pos[0]]):
// next minute
$next->modify('+1 minute');
return true;
default:
// all segments are matching
return false;
}
}
/**
* @param array $segments
* @return bool
*/
private function match(array $segments): bool {
foreach ($this->registers as $i => $item) {
if (isset($item[(int)$segments[$i]]) === false) {
return false;
}
}
return true;
}
/**
* Parse whole cron expression
*
* @param string $expression
* @return array
* @throws Exception
*/
private function parse(string $expression): array {
$segments = preg_split('/\s+/', trim($expression));
if (is_array($segments) && sizeof($segments) === 5) {
$registers = array_fill(0, 5, []);
foreach ($segments as $index => $segment) {
$this->parseSegment($registers[$index], $index, $segment);
}
$this->validateDate($registers);
if (isset($registers[4][7])) {
$registers[4][0] = true;
}
return $registers;
}
throw new Exception('invalid number of segments');
}
/**
* Parse one segment of a cron expression
*
* @param array $register
* @param int $index
* @param string $segment
* @throws Exception
*/
private function parseSegment(array &$register, int $index, string $segment): void {
$allowed = [false, false, false, self::MONTH_NAMES, self::WEEKDAY_NAMES];
// month names, weekdays
if ($allowed[$index] !== false && isset($allowed[$index][strtolower($segment)])) {
// cannot be used together with lists or ranges
$register[$allowed[$index][strtolower($segment)]] = true;
} else {
// split up current segment into single elements, e.g. "1,5-7,*/2" => [ "1", "5-7", "*/2" ]
foreach (explode(',', $segment) as $element) {
$this->parseElement($register, $index, $element);
}
}
}
/**
* @param array $register
* @param int $index
* @param string $element
* @throws Exception
*/
private function parseElement(array &$register, int $index, string $element): void {
$step = 1;
$segments = explode('/', $element);
if (sizeof($segments) > 1) {
$this->validateStepping($segments, $index);
$element = (string)$segments[0];
$step = (int)$segments[1];
}
if (is_numeric($element)) {
$this->validateValue($element, $index, $step);
$register[intval($element)] = true;
} else {
$this->parseRange($register, $index, $element, $step);
}
}
/**
* Parse range of values, e.g. "5-10"
*
* @param array $register
* @param int $index
* @param string $range
* @param int $stepping
* @throws Exception
*/
private function parseRange(array &$register, int $index, string $range, int $stepping): void {
if ($range === '*') {
$rangeArr = [self::VALUE_BOUNDARIES[$index]['min'], self::VALUE_BOUNDARIES[$index]['max']];
} else {
$rangeArr = explode('-', $range);
}
$this->validateRange($rangeArr, $index);
$this->fillRange($register, $index, $rangeArr, $stepping);
}
/**
* @param array $register
* @param int $index
* @param array $range
* @param int $stepping
*/
private function fillRange(array &$register, int $index, array $range, int $stepping): void {
$boundary = self::VALUE_BOUNDARIES[$index]['max'] + self::VALUE_BOUNDARIES[$index]['mod'];
$length = $range[1] - $range[0];
for ($i = 0; $i <= $length; $i += $stepping) {
$register[($range[0] + $i) % $boundary] = true;
}
}
/**
* Validate whether a given range of values exceeds allowed value boundaries
*
* @param array $range
* @param int $index
* @throws Exception
*/
private function validateRange(array $range, int $index): void {
if (sizeof($range) !== 2) {
throw new Exception('invalid range notation');
}
foreach ($range as $value) {
$this->validateValue($value, $index);
}
if ($range[0] > $range[1]) {
throw new Exception('lower value in range is larger than upper value');
}
}
/**
* @param string $value
* @param int $index
* @param int $step
* @throws Exception
*/
private function validateValue(string $value, int $index, int $step = 1): void {
if (false === ctype_digit($value)) {
throw new Exception('non-integer value');
}
if (
intval($value) < self::VALUE_BOUNDARIES[$index]['min'] ||
intval($value) > self::VALUE_BOUNDARIES[$index]['max']
) {
throw new Exception('value out of boundary');
}
if ($step !== 1) {
throw new Exception('invalid combination of value and stepping notation');
}
}
/**
* @param array $segments
* @param int $index
* @throws Exception
*/
private function validateStepping(array $segments, int $index): void {
if (sizeof($segments) !== 2) {
throw new Exception('invalid stepping notation');
}
if ((int)$segments[1] < 1 || (int)$segments[1] > self::VALUE_BOUNDARIES[$index]['max']) {
throw new Exception('stepping out of allowed range');
}
}
/**
* @param array $segments
* @throws Exception
*/
private function validateDate(array $segments): void {
$year = date('Y');
for ($y = 0; $y < 27; $y++) {
foreach (array_keys($segments[3]) as $month) {
foreach (array_keys($segments[2]) as $day) {
if (false === checkdate($month, $day, $year + $y)) {
continue;
}
if (false === isset($segments[date('w', strtotime(sprintf('%d-%d-%d', $year + $y, $month, $day)))])) {
continue;
}
return;
}
}
}
throw new Exception('no date ever can match the given combination of day/month/weekday');
}
}

View File

@@ -0,0 +1,216 @@
<?php
defined('BASEPATH') or exit('No direct script access allowed');
class Migration_cron_table extends CI_Migration {
public function up() {
if (!$this->db->table_exists('cron')) {
// define the structure of the new cron table
$this->dbforge->add_field(array(
'id' => array(
'type' => 'VARCHAR',
'constraint' => '255',
'null' => FALSE,
),
'enabled' => array(
'type' => 'TINYINT',
'constraint' => '1',
'null' => FALSE,
),
'status' => array(
'type' => 'VARCHAR',
'constraint' => '255',
'null' => TRUE,
),
'description' => array(
'type' => 'VARCHAR',
'constraint' => '255',
'null' => TRUE,
),
'function' => array(
'type' => 'VARCHAR',
'constraint' => '255',
'null' => FALSE,
),
'expression' => array(
'type' => 'VARCHAR',
'constraint' => '100',
'null' => TRUE,
),
'last_run' => array(
'type' => 'TIMESTAMP',
'null' => TRUE,
),
'next_run' => array(
'type' => 'TIMESTAMP',
'null' => TRUE,
),
'modified' => array(
'type' => 'TIMESTAMP',
'null' => TRUE,
),
));
// we set the key for the id, in this case the id is not numerical
$this->dbforge->add_key('id', TRUE);
// now we can create the new table
$this->dbforge->create_table('cron');
// to transfer data for the file updates we load the optionslib library
$this->load->library('OptionsLib');
// and we fill the table with the cronjobs
$data = array(
array(
'id' => 'clublog_upload',
'enabled' => '0',
'status' => 'pending',
'description' => 'Upload QSOs to Clublog',
'function' => 'index.php/clublog/upload',
'expression' => '3 */6 * * *',
'last_run' => null,
'next_run' => null
),
array(
'id' => 'lotw_lotw_upload',
'enabled' => '0',
'status' => 'pending',
'description' => 'Upload QSOs to LoTW',
'function' => 'index.php/lotw/lotw_upload',
'expression' => '0 */1 * * *',
'last_run' => null,
'next_run' => null
),
array(
'id' => 'qrz_upload',
'enabled' => '0',
'status' => 'pending',
'description' => 'Upload QSOs to QRZ',
'function' => 'index.php/qrz/upload',
'expression' => '6 */6 * * *',
'last_run' => null,
'next_run' => null
),
array(
'id' => 'qrz_download',
'enabled' => '0',
'status' => 'pending',
'description' => 'Download QSOs from QRZ',
'function' => 'index.php/qrz/download',
'expression' => '18 */6 * * *',
'last_run' => null,
'next_run' => null
),
array(
'id' => 'hrdlog_upload',
'enabled' => '0',
'status' => 'pending',
'description' => 'Upload QSOs to HRD',
'function' => 'index.php/hrdlog/upload',
'expression' => '12 */6 * * *',
'last_run' => null,
'next_run' => null
),
array(
'id' => 'eqsl_sync',
'enabled' => '0',
'status' => 'pending',
'description' => 'Upload/download QSOs to/from Eqsl',
'function' => 'index.php/eqsl/sync',
'expression' => '9 */6 * * *',
'last_run' => null,
'next_run' => null
),
array(
'id' => 'update_lotw_users',
'enabled' => '1',
'status' => 'pending',
'description' => 'Update LOTW Users Activity',
'function' => 'index.php/update/lotw_users',
'expression' => '10 1 * * 1',
'last_run' => ($this->optionslib->get_option('lotw_users_update') ? date("Y-m-d H:i", strtotime($this->optionslib->get_option('lotw_users_update'))) : null),
'next_run' => null
),
array(
'id' => 'update_update_clublog_scp',
'enabled' => '1',
'status' => 'pending',
'description' => 'Update Clublog SCP Database File',
'function' => 'index.php/update/update_clublog_scp',
'expression' => '0 0 * * 0',
'last_run' => ($this->optionslib->get_option('scp_update') ? date("Y-m-d H:i", strtotime($this->optionslib->get_option('scp_update'))) : null),
'next_run' => null
),
array(
'id' => 'update_update_dok',
'enabled' => '1',
'status' => 'pending',
'description' => 'Update DOK File',
'function' => 'index.php/update/update_dok',
'expression' => '0 0 1 * *',
'last_run' => ($this->optionslib->get_option('dok_file_update') ? date("Y-m-d H:i", strtotime($this->optionslib->get_option('dok_file_update'))) : null),
'next_run' => null
),
array(
'id' => 'update_update_sota',
'enabled' => '1',
'status' => 'pending',
'description' => 'Update SOTA File',
'function' => 'index.php/update/update_sota',
'expression' => '5 0 1 * *',
'last_run' => ($this->optionslib->get_option('sota_file_update') ? date("Y-m-d H:i", strtotime($this->optionslib->get_option('sota_file_update'))) : null),
'next_run' => null
),
array(
'id' => 'update_update_wwff',
'enabled' => '1',
'status' => 'pending',
'description' => 'Update WWFF File',
'function' => 'index.php/update/update_wwff',
'expression' => '10 0 1 * *',
'last_run' => ($this->optionslib->get_option('wwff_file_update') ? date("Y-m-d H:i", strtotime($this->optionslib->get_option('wwff_file_update'))) : null),
'next_run' => null
),
array(
'id' => 'update_update_pota',
'enabled' => '1',
'status' => 'pending',
'description' => 'Update POTA File',
'function' => 'index.php/update/update_pota',
'expression' => '15 0 1 * *',
'last_run' => ($this->optionslib->get_option('pota_file_update') ? date("Y-m-d H:i", strtotime($this->optionslib->get_option('pota_file_update'))) : null),
'next_run' => null
),
array(
'id' => 'update_dxcc',
'enabled' => '1',
'status' => 'pending',
'description' => 'Update DXCC data',
'function' => 'index.php/update/dxcc',
'expression' => '20 0 1 */2 *',
'last_run' => ($this->optionslib->get_option('dxcc_clublog_update') ? date("Y-m-d H:i", strtotime($this->optionslib->get_option('dxcc_clublog_update'))) : null),
'next_run' => null
),
);
$this->db->insert_batch('cron', $data);
// since we transfered the source for the file update timestamps we don't need this options anymore
$this->db->delete('options', array('option_name' => 'lotw_users_update'));
$this->db->delete('options', array('option_name' => 'scp_update'));
$this->db->delete('options', array('option_name' => 'dok_file_update'));
$this->db->delete('options', array('option_name' => 'sota_file_update'));
$this->db->delete('options', array('option_name' => 'wwff_file_update'));
$this->db->delete('options', array('option_name' => 'pota_file_update'));
$this->db->delete('options', array('option_name' => 'dxcc_clublog_update'));
}
}
public function down() {
$this->dbforge->drop_table('cron');
}
}

View File

@@ -0,0 +1,93 @@
<?php
class Cron_model extends CI_Model
{
// get all crons from the database
function get_crons() {
$this->db->from('cron');
$results = array();
$results = $this->db->get()->result();
return $results;
}
// get details for a specific cron
function cron($id) {
$clean_id = $this->security->xss_clean($id);
$this->db->where('id', $clean_id);
return $this->db->get('cron');
}
// set the modified timestamp
function set_modified($cron) {
$data = array(
'modified' => date('Y-m-d H:i:s')
);
$this->db->where('id', $cron);
$this->db->update('cron', $data);
}
// set a new status for the cron
function set_status($cron, $status) {
$data = array(
'status' => $status
);
$this->db->where('id', $cron);
$this->db->update('cron', $data);
}
// set the last run
function set_last_run($cron) {
$data = array(
'last_run' => date('Y-m-d H:i:s')
);
$this->db->where('id', $cron);
$this->db->update('cron', $data);
}
// set the calculated next run
function set_next_run($cron,$timestamp) {
$data = array(
'next_run' => $timestamp
);
$this->db->where('id', $cron);
$this->db->update('cron', $data);
}
// set the cron enabled flag
function set_cron_enabled($cron, $cron_enabled) {
$data = array (
'enabled' => ($cron_enabled === 'true' ? 1 : 0),
'status' => ($cron_enabled === 'true' ? 'pending' : 'disabled'),
);
$this->db->where('id', $cron);
$this->db->update('cron', $data);
$this->set_modified($cron);
}
// set the edited details for a cron
function edit_cron($id, $description, $expression, $enabled) {
$data = array (
'description' => $description,
'expression' => $expression,
'enabled' => ($enabled === 'true' ? 1 : 0)
);
$this->db->where('id', $id);
$this->db->update('cron', $data);
$this->set_modified($id);
}
}

View File

@@ -0,0 +1,78 @@
<div class="modal fade" id="editCronModal" tabindex="-1" aria-labelledby="editCronModal" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="cronEditLabel">Edit Cronjob</h5>
</div>
<div class="modal-body">
<table class="table table-sm">
<tbody>
<tr>
<th scope="row">Identifier</th>
<td>
<div class="input-group">
<input type="text" class="form-control" disabled style="font-family: Courier New;" name="edit_cron_id" id="edit_cron_id" value="<?php echo $cron->id; ?>">
<span class="input-group-text" data-bs-toggle="tooltip" data-bs-placement="right" title="ID's can't be changed">
<i class="fas fa-info"></i>
</span>
</div>
</td>
</tr>
<tr>
<th scope="row">Enabled</th>
<td>
<div class="form-check form-switch">
<input name="edit_cron_enable_switch" class="form-check-input" type="checkbox" role="switch" id="edit_<?php echo $cron->id; ?>" <?php if ($cron->enabled ?? '0') { echo 'checked'; } ?>>
</div>
</td>
</tr>
<tr>
<th scope="row">Description</th>
<td>
<textarea class="form-control" name="edit_cron_description" id="edit_cron_description" maxlength="240" rows="2" style="width:100%;"><?php echo $cron->description; ?></textarea>
</td>
</tr>
<tr>
<th scope="row">Intervall</th>
<td>
<p>Choose a preset from the dropdown</p>
<?php
// List of available Presets
$presets = array(
'' => 'Custom',
'*/5 * * * *' => 'Every 5 Minutes',
'*/15 * * * *' => 'Every 15 Minutes',
'0 * * * *' => 'Every Hour',
'0 */2 * * *' => 'Every 2 Hours',
'0 0 * * *' => 'Every Day at Midnight',
'0 3 * * 1' => 'Every Monday at 03:00',
'0 0 1 * *' => 'First Day of Every Month at midnight',
'0 2 1 */2 *' => 'Every 2 Months at 02:00'
);
?>
<select class="form-select mb-4" id="edit_cron_expression_dropdown" name="edit_cron_expression_dropdown">
<?php foreach ($presets as $cron_preset => $label) : ?>
<option value="<?php echo $cron_preset; ?>" <?php if ($cron->expression == $cron_preset) {
echo " selected=\"selected\"";
} ?>><?php echo $label; ?></option>
<?php endforeach; ?>
</select>
<p class="text-center"> - OR -</p>
<p>Enter your own Cron Expression</p>
<input type="text" class="form-control mb-1" style="font-family: Courier New;" name="edit_cron_expression_custom" id="edit_cron_expression_custom" value="<?php echo htmlspecialchars($cron->expression); ?>">
<em id="exp_humanreadable" style="display: none;"></em>
</td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" onclick="editCron()" ><?php echo lang('admin_save'); ?></button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo lang('general_word_cancel'); ?></button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,97 @@
<div class="container mb-4 mt-2">
<br>
<h2><?php echo $page_title; ?></h2>
<div style="display: none;" id="cron_message_area" role="alert"></div>
<div class="card">
<div class="card-header">
How it works
</div>
<div class="card-body">
<div class="row">
<div class="col-auto">
<p class="card-text">
The Cron Manager assists the administrator in managing cron jobs without requiring CLI access.
</p>
<p class="card-text">
To execute cron jobs based on the data below, remove all old cron jobs and create a new one:
</p>
<div class="main_cronjob">
<pre><code id="main_cronjob">* * * * * curl --silent <?php echo base_url(); ?>index.php/cron/run &>/dev/null</code><span data-bs-toggle="tooltip" title="<?php echo lang('copy_to_clipboard'); ?>" onclick='copyCron("main_cronjob")'><i class="copy-icon fas fa-copy"></i></span></pre>
</div>
</div>
<div class="col text-end" id="alert_status">
<div class="alert alert-<?php echo $mastercron['status_class'] ?? 'danger'; ?> d-inline-block">
Status Master-Cron: <?php echo $mastercron['status'] ?? 'Not running'; ?>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
Cron List
</div>
<div class="card-body">
<?php if ($mastercron['status_class'] != 'danger') { ?>
<div class="table-responsive">
<table id="cron_table" style="width:100%" class="crontable table table-sm table-striped">
<thead>
<tr>
<th>ID</th>
<th>Description</th>
<th>Status</th>
<th>Intervall</th>
<th>Last Run</th>
<th>Next Run</th>
<th>Edit</th>
<th>I/O</th>
</tr>
</thead>
<tbody>
<?php foreach ($crons as $cron) { ?>
<tr>
<td style="vertical-align: middle;" class='cron_<?php echo $cron->id; ?>'><?php echo $cron->id; ?></td>
<td style="vertical-align: middle;"><?php echo $cron->description; ?></td>
<td style="vertical-align: middle;"><?php
if ($cron->enabled == '1') {
if ($cron->status == 'healthy') { ?>
<span class="badge text-bg-success">healthy</span>
<?php } else if ($cron->status == 'failed') { ?>
<span class="badge text-bg-danger">failed</span>
<?php } else { ?>
<span class="badge text-bg-warning"><?php echo $cron->status; ?></span>
<?php } ?>
<?php } else { ?>
<span class="badge text-bg-secondary">disabled</span>
<?php } ?>
</td>
<td style="vertical-align: middle;"><?php echo '<code id="humanreadable_tooltip" data-bs-toggle="tooltip">' . $cron->expression . '</code>'; ?></td>
<td style="vertical-align: middle;"><?php echo $cron->last_run ?? 'never'; ?></td>
<td style="vertical-align: middle;"><?php if ($cron->enabled == '1') {
echo $cron->next_run ?? 'never';
} else {
echo 'never';
} ?></td>
<td style="vertical-align: middle;"><button id="<?php echo $cron->id; ?>" class="editCron btn btn-outline-primary btn-sm"><i class="fas fa-edit"></i></button></td>
<td style="vertical-align: middle;">
<div class="form-check form-switch"><input name="cron_enable_switch" class="form-check-input enableCronSwitch" type="checkbox" role="switch" id="<?php echo $cron->id; ?>" <?php if ($cron->enabled ?? '0') {
echo 'checked';
} ?>></div>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
<?php } else { ?>
<div class="text-center">
<h4>Your Mastercron isn't running.<br>Copy the cron above to a external cron service or into your server's cron to use this cron manager.</h4>
<p>On a basic linux server with shell access use this command to edit your crons:<pre><code>crontab -e</code></pre></p>
</div>
<?php } ?>
</div>
</div>
</div>

View File

@@ -462,38 +462,38 @@
</thead>
<tr>
<td>DXCC update from Club Log</td>
<td><?php echo (($this->optionslib->get_option('dxcc_clublog_update') ?? '') == '' ? '' : date($custom_date_format, strtotime($this->optionslib->get_option('dxcc_clublog_update') ?? '')) . ' ' . date("h:i", strtotime($this->optionslib->get_option('dxcc_clublog_update') ?? ''))) ?></td>
<td><?php echo $dxcc_update->last_run ?? 'never'; ?></td>
<td><a class="btn btn-sm btn-primary" href="<?php echo site_url('update'); ?>">Update</a></td>
</tr>
<tr>
<td>DOK file download</td>
<td><?php echo (($this->optionslib->get_option('dok_file_update') ?? '') == '' ? '' : date($custom_date_format, strtotime($this->optionslib->get_option('dok_file_update') ?? '')) . ' ' . date("h:i", strtotime($this->optionslib->get_option('dok_file_update') ?? ''))) ?></td>
<td><?php echo $dok_update->last_run ?? 'never'; ?></td>
<td><a class="btn btn-sm btn-primary" href="<?php echo site_url('update/update_dok'); ?>">Update</a></td>
</tr>
<tr>
<td>LoTW users download</td>
<td><?php echo (($this->optionslib->get_option('lotw_users_update') ?? '') == '' ? '' : date($custom_date_format, strtotime($this->optionslib->get_option('lotw_users_update') ?? '')) . ' ' . date("h:i", strtotime($this->optionslib->get_option('lotw_users_update') ?? ''))) ?></td>
<td><?php echo $lotw_user_update->last_run ?? 'never'; ?></td>
<td><a class="btn btn-sm btn-primary" href="<?php echo site_url('update/lotw_users'); ?>">Update</a></td>
</tr>
<tr>
<td>POTA file download</td>
<td><?php echo (($this->optionslib->get_option('pota_file_update') ?? '') == '' ? '' : date($custom_date_format, strtotime($this->optionslib->get_option('pota_file_update') ?? '')) . ' ' . date("h:i", strtotime($this->optionslib->get_option('pota_file_update') ?? ''))) ?></td>
<td><?php echo $pota_update->last_run ?? 'never'; ?></td>
<td><a class="btn btn-sm btn-primary" href="<?php echo site_url('update/update_pota'); ?>">Update</a></td>
</tr>
<tr>
<td>SCP file download</td>
<td><?php echo (($this->optionslib->get_option('scp_update') ?? '') == '' ? '' : date($custom_date_format, strtotime($this->optionslib->get_option('scp_update') ?? '')) . ' ' . date("h:i", strtotime($this->optionslib->get_option('scp_update') ?? ''))) ?></td>
<td><?php echo $scp_update->last_run ?? 'never'; ?></td>
<td><a class="btn btn-sm btn-primary" href="<?php echo site_url('update/update_clublog_scp'); ?>">Update</a></td>
</tr>
<tr>
<td>SOTA file download</td>
<td><?php echo (($this->optionslib->get_option('sota_file_update') ?? '') == '' ? '' : date($custom_date_format, strtotime($this->optionslib->get_option('sota_file_update') ?? '')) . ' ' . date("h:i", strtotime($this->optionslib->get_option('sota_file_update') ?? ''))) ?></td>
<td><?php echo $sota_update->last_run ?? 'never'; ?></td>
<td><a class="btn btn-sm btn-primary" href="<?php echo site_url('update/update_sota'); ?>">Update</a></td>
</tr>
<tr>
<td>WWFF file download</td>
<td><?php echo (($this->optionslib->get_option('wwff_file_update') ?? '') == '' ? '' : date($custom_date_format, strtotime($this->optionslib->get_option('wwff_file_update') ?? '')) . ' ' . date("h:i", strtotime($this->optionslib->get_option('wwff_file_update') ?? ''))) ?></td>
<td><?php echo $wwff_update->last_run ?? 'never'; ?></td>
<td><a class="btn btn-sm btn-primary" href="<?php echo site_url('update/update_wwff'); ?>">Update</a></td>
</tr>
</table>

View File

@@ -106,6 +106,11 @@ if($this->session->userdata('user_id') != null) {
<script src="<?php echo base_url() ;?>assets/js/sections/oqrs.js"></script>
<?php } ?>
<!-- JS library to convert cron format to human readable -->
<?php if ($this->uri->segment(1) == "cron") { ?>
<script src="<?php echo base_url() ;?>assets/js/cronstrue.min.js"async></script>
<?php } ?>
<?php if ($this->uri->segment(1) == "options") { ?>
<script>
$('#sendTestMailButton').click(function() {

View File

@@ -236,6 +236,8 @@
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="<?php echo site_url('update'); ?>" title="Update Country Files"><i class="fas fa-sync"></i> <?php echo lang('menu_update_country_files'); ?></a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="<?php echo site_url('cron'); ?>" title="Cron Manager"><i class="fas fa-clock"></i> Cron Manager</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="<?php echo site_url('debug'); ?>" title="Debug Information"><i class="fas fa-tools"></i> <?php echo lang('menu_debug_information'); ?></a>
</div>
</li>

1
assets/js/cronstrue.min.js vendored Normal file

File diff suppressed because one or more lines are too long

245
assets/js/sections/cron.js Normal file
View File

@@ -0,0 +1,245 @@
$(document).ready(function () {
init_datatable();
$(document).on('click', '.editCron', async function (e) { // Dynamic binding, since element doesn't exists when loading this JS
editCronDialog(e);
});
$(document).on('click', '.enableCronSwitch', async function (e) { // Dynamic binding, since element doesn't exists when loading this JS
toggleEnableCronSwitch(e.currentTarget.id, this);
});
});
function copyCron(id) {
var content = $('#' + id).text();
navigator.clipboard.writeText(content).then(function () { });
$('#' + id).addClass('flash-copy').delay('1000').queue(function () {
$('#' + id).removeClass('flash-copy').dequeue();
});
}
function init_expression_tooltips() {
$('.crontable tbody tr').each(function () {
var expression = $(this).find('td:eq(3)').text().trim();
var humanReadable = cronstrue.toString(expression);
$(this).find('#humanreadable_tooltip').attr('data-bs-original-title', humanReadable).tooltip();
});
}
function init_datatable() {
$('.crontable').DataTable({
"pageLength": 25,
responsive: true,
ordering: true,
"scrollY": "600px",
"scrollCollapse": true,
"paging": false,
"scrollX": true,
"autoWidth": false,
"language": {
url: getDataTablesLanguageUrl(),
},
dom: 'Bfrtip',
buttons: [
{
text: 'Refresh',
action: function (e, dt, node, config) {
reloadCrons();
}
}
]
});
init_expression_tooltips();
}
function modalEventListener() {
$('#edit_cron_expression_custom').on('input change', function (e) {
humanReadableInEditDialog()
});
$('#edit_cron_expression_dropdown').change(function () {
humanReadableInEditDialog()
});
}
function displayMessages(category, message) {
var html_class;
var message_area = $('#cron_message_area');
if (category == 'success') {
html_class = 'alert alert-success';
} else if (category == 'warning') {
html_class = 'alert alert-warning';
} else if (category == 'error') {
html_class = 'alert alert-danger';
} else {
html_class = 'alert alert-info';
}
message_area.show();
message_area.addClass(html_class);
message_area.text(message);
setTimeout(function () {
message_area.fadeOut();
}, 7000);
}
function editCronDialog(e) {
$('#editCronModal').remove();
$.ajax({
url: base_url + 'index.php/cron/editDialog',
type: 'post',
data: {
id: e.currentTarget.id,
},
success: function (data) {
$('body').append(data);
var editCronModal = new bootstrap.Modal(document.getElementById('editCronModal'));
editCronModal.show();
modalEventListener();
$('[data-bs-toggle="tooltip"]').tooltip();
},
error: function (data) {
},
});
return false;
}
function editCron() {
var $cron_id = $('#edit_cron_id').val();
var $cron_description = $('#edit_cron_description').val();
var $cron_expression = $('#edit_cron_expression_custom').val();
var $cron_enabled = $('#edit_' + $cron_id).is(':checked') ? 'true' : 'false';
$.ajax({
url: base_url + 'index.php/cron/edit',
method: 'POST',
data: {
cron_id: $cron_id,
cron_description: $cron_description,
cron_expression: $cron_expression,
cron_enabled: $cron_enabled
},
success: function (response) {
if (response.success) {
reloadCrons();
displayMessages(response.messagecategory, response.message);
} else {
displayMessages(response.messagecategory, response.message);
}
},
error: function (response) {
displayMessages('error', 'The query failed for a unknown reason');
}
});
}
function humanReadableInEditDialog() {
var exp_inputID = $('#edit_cron_expression_custom');
var exp_dropdownID = $('#edit_cron_expression_dropdown');
var exp_humanreadableID = $('#exp_humanreadable');
var humanReadable = '';
exp_inputID.on('input', function () {
exp_dropdownID.val('');
});
if (exp_dropdownID.val() == '') {
exp_humanreadableID.show();
try {
humanReadable = cronstrue.toString(exp_inputID.val());
} catch (error) {
humanReadable = 'waiting for complete expression...';
}
exp_humanreadableID.text(humanReadable);
} else {
exp_humanreadableID.hide();
exp_inputID.val(exp_dropdownID.val());
}
}
function toggleEnableCronSwitch(id, thisvar) {
$.ajax({
url: base_url + 'index.php/cron/toogleEnableCronSwitch',
type: 'post',
data: {
id: id,
checked: $(thisvar).is(':checked')
},
success: function (data) {
reloadCrons();
},
error: function (data) {
},
});
return false;
}
function reloadCrons() {
$.ajax({
url: base_url + 'index.php/cron/fetchCrons',
type: 'post',
dataType: 'json',
success: function (data) {
loadCronTable(data);
},
error: function (data) {
BootstrapDialog.alert({
title: 'ERROR',
message: 'An error ocurred while making the request',
type: BootstrapDialog.TYPE_DANGER,
closable: false,
draggable: false,
callback: function (result) {
}
});
},
});
return false;
}
function loadCronTable(rows) {
var uninitialized = $('.crontable').filter(function () {
return !$.fn.DataTable.fnIsDataTable(this);
});
uninitialized.each(function () {
init_datatable();
});
var table = $('.crontable').DataTable();
table.clear();
for (i = 0; i < rows.length; i++) {
let cron = rows[i];
var data = [];
data.push(cron.cron_id);
data.push(cron.cron_description);
data.push(cron.cron_status);
data.push(cron.cron_expression);
data.push(cron.cron_last_run);
data.push(cron.cron_next_run);
data.push(cron.cron_edit);
data.push(cron.cron_enabled);
let createdRow = table.row.add(data).index();
}
table.draw();
init_expression_tooltips();
}