dolibarr.bankimport/admin/cronmonitor.php
data f340ba2da5 feat: Robustes Cron-System mit Monitoring (v2.7)
- Dediziertes Cron-Logging unter /documents/bankimport/logs/
- Shutdown Handler für fatale PHP-Fehler
- Pause-Mechanismus nach 3 Fehlern (verhindert Bank-Sperrung)
- Auth-Fehler-Erkennung für Authentifizierungsprobleme
- Neue Admin-Seite: Cron-Monitor (Status, Logs, Pause/Resume)
- CHANGELOG aktualisiert

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-23 10:58:06 +01:00

347 lines
12 KiB
PHP

<?php
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*/
/**
* \file bankimport/admin/cronmonitor.php
* \ingroup bankimport
* \brief Cron job monitoring and log viewer
*/
// Load Dolibarr environment
$res = 0;
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
}
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
$tmp2 = realpath(__FILE__);
$i = strlen($tmp) - 1;
$j = strlen($tmp2) - 1;
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
$i--;
$j--;
}
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
$res = @include substr($tmp, 0, ($i + 1))."/main.inc.php";
}
if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) {
$res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
}
if (!$res && file_exists("../../main.inc.php")) {
$res = @include "../../main.inc.php";
}
if (!$res && file_exists("../../../main.inc.php")) {
$res = @include "../../../main.inc.php";
}
if (!$res) {
die("Include of main fails");
}
// Libraries
require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php";
require_once '../lib/bankimport.lib.php';
dol_include_once('/bankimport/class/bankimportcron.class.php');
/**
* @var Conf $conf
* @var DoliDB $db
* @var Translate $langs
* @var User $user
*/
// Translations
$langs->loadLangs(array("admin", "bankimport@bankimport", "cron"));
// Parameters
$action = GETPOST('action', 'aZ09');
$lines = GETPOSTINT('lines') ?: 100;
// Access control
if (!$user->admin) {
accessforbidden();
}
/*
* Actions
*/
if ($action == 'unpause') {
$cron = new BankImportCron($db);
$cron->unpauseCron();
setEventMessages("Cron-Pause aufgehoben", null, 'mesgs');
header("Location: ".$_SERVER["PHP_SELF"]);
exit;
}
if ($action == 'clearfailcount') {
dolibarr_del_const($db, 'BANKIMPORT_CRON_FAIL_COUNT', $conf->entity);
setEventMessages("Fehlerzähler zurückgesetzt", null, 'mesgs');
header("Location: ".$_SERVER["PHP_SELF"]);
exit;
}
if ($action == 'resetcronjob') {
// Reset the cron job in llx_cronjob table
$sql = "UPDATE ".MAIN_DB_PREFIX."cronjob SET processing = 0, datenextrun = NOW() WHERE label = 'BankImportAutoFetch' AND processing = 1";
$resql = $db->query($sql);
if ($resql && $db->affected_rows($resql) > 0) {
setEventMessages("Hängenden Cron-Job zurückgesetzt", null, 'mesgs');
} else {
setEventMessages("Kein hängender Job gefunden oder Fehler", null, 'warnings');
}
header("Location: ".$_SERVER["PHP_SELF"]);
exit;
}
/*
* View
*/
$page_name = "Cron Monitor - BankImport";
llxHeader('', $page_name, '', '', 0, 0, '', '', '', 'mod-bankimport page-admin-cronmonitor');
// Admin Tabs
$head = bankimportAdminPrepareHead();
print dol_get_fiche_head($head, 'cronmonitor', 'BankImport', -1, 'bank');
// Get cron status
$cronStatus = BankImportCron::getCronStatus();
// Get Dolibarr cron job info
$sql = "SELECT rowid, label, datenextrun, datelastrun, datelastresult, processing, lastoutput, lastresult, status
FROM ".MAIN_DB_PREFIX."cronjob
WHERE label = 'BankImportAutoFetch'";
$resql = $db->query($sql);
$cronJob = $resql ? $db->fetch_object($resql) : null;
print '<div class="div-table-responsive-no-min">';
// Status Overview
print '<table class="noborder centpercent">';
print '<tr class="liste_titre"><th colspan="2">Cron Status Übersicht</th></tr>';
// Enabled/Disabled
print '<tr class="oddeven"><td width="300">Auto-Import aktiviert</td><td>';
if ($cronStatus['enabled']) {
print '<span class="badge badge-status4">Aktiviert</span>';
} else {
print '<span class="badge badge-status8">Deaktiviert</span>';
}
print '</td></tr>';
// Paused status
print '<tr class="oddeven"><td>Pause-Status</td><td>';
if ($cronStatus['paused']) {
print '<span class="badge badge-status1" style="background-color: #ff9800;">PAUSIERT</span> ';
print 'bis '.dol_print_date($cronStatus['paused_until'], 'dayhour');
print '<br><strong>Grund:</strong> '.dol_escape_htmltag($cronStatus['pause_reason']);
print '<br><a class="butAction" href="'.$_SERVER["PHP_SELF"].'?action=unpause">Pause aufheben</a>';
} else {
print '<span class="badge badge-status4">Aktiv</span>';
}
print '</td></tr>';
// Failure count
print '<tr class="oddeven"><td>Fehlversuche in Folge</td><td>';
$failCount = $cronStatus['fail_count'];
if ($failCount >= 3) {
print '<span class="badge badge-status8">'.$failCount.'</span> ';
print '<span class="warning">(bei 3+ wird automatisch pausiert)</span> ';
print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?action=clearfailcount">Zurücksetzen</a>';
} elseif ($failCount > 0) {
print '<span class="badge badge-status1">'.$failCount.'</span>';
} else {
print '<span class="badge badge-status4">0</span>';
}
print '</td></tr>';
// Last run status
print '<tr class="oddeven"><td>Letzter Lauf</td><td>';
if (!empty($cronStatus['last_run'])) {
$lastRun = $cronStatus['last_run'];
$statusClass = 'badge-status4';
if ($lastRun['status'] == 'error') $statusClass = 'badge-status8';
elseif ($lastRun['status'] == 'paused') $statusClass = 'badge-status1';
elseif ($lastRun['status'] == 'running') $statusClass = 'badge-status6';
print '<span class="badge '.$statusClass.'">'.strtoupper($lastRun['status']).'</span> ';
print dol_print_date($lastRun['timestamp'], 'dayhour');
if (!empty($lastRun['duration'])) {
print ' (Dauer: '.$lastRun['duration'].'s)';
}
if (!empty($lastRun['message'])) {
print '<br><small>'.dol_escape_htmltag($lastRun['message']).'</small>';
}
} else {
print '<span class="opacitymedium">Keine Daten</span>';
}
print '</td></tr>';
// Notification
print '<tr class="oddeven"><td>Aktuelle Benachrichtigung</td><td>';
if (!empty($cronStatus['notification'])) {
$notif = $cronStatus['notification'];
$notifLabels = array(
'tan_required' => 'TAN erforderlich',
'login_error' => 'Login-Fehler',
'fetch_error' => 'Abruf-Fehler',
'config_error' => 'Konfigurationsfehler',
'session_expired' => 'Session abgelaufen',
'error' => 'Allgemeiner Fehler',
'paused' => 'Pausiert'
);
$label = $notifLabels[$notif['type']] ?? $notif['type'];
print '<span class="badge badge-status1" style="background-color: #ff9800;">'.$label.'</span>';
if (!empty($notif['date'])) {
print ' seit '.dol_print_date($notif['date'], 'dayhour');
}
} else {
print '<span class="badge badge-status4">Keine</span>';
}
print '</td></tr>';
print '</table>';
// Dolibarr Cron Job Status
print '<br>';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre"><th colspan="2">Dolibarr Cron-Job Status</th></tr>';
if ($cronJob) {
print '<tr class="oddeven"><td width="300">Job Status</td><td>';
if ($cronJob->processing) {
print '<span class="badge badge-status1" style="background-color: #ff9800;">LÄUFT / HÄNGT</span> ';
print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?action=resetcronjob">Job zurücksetzen</a>';
} elseif ($cronJob->status == 1) {
print '<span class="badge badge-status4">Bereit</span>';
} else {
print '<span class="badge badge-status5">Deaktiviert</span>';
}
print '</td></tr>';
print '<tr class="oddeven"><td>Nächster geplanter Lauf</td><td>'.dol_print_date($db->jdate($cronJob->datenextrun), 'dayhour').'</td></tr>';
print '<tr class="oddeven"><td>Letzter Lauf (Start)</td><td>'.dol_print_date($db->jdate($cronJob->datelastrun), 'dayhour').'</td></tr>';
print '<tr class="oddeven"><td>Letzter Lauf (Ende)</td><td>'.dol_print_date($db->jdate($cronJob->datelastresult), 'dayhour').'</td></tr>';
print '<tr class="oddeven"><td>Letztes Ergebnis</td><td>';
if ($cronJob->lastresult === '0' || $cronJob->lastresult === 0) {
print '<span class="badge badge-status4">OK</span>';
} elseif (!empty($cronJob->lastresult)) {
print '<span class="badge badge-status8">Fehler: '.$cronJob->lastresult.'</span>';
} else {
print '<span class="opacitymedium">-</span>';
}
print '</td></tr>';
print '<tr class="oddeven"><td>Letzte Ausgabe</td><td><pre style="margin:0; white-space: pre-wrap;">'.dol_escape_htmltag($cronJob->lastoutput ?: '-').'</pre></td></tr>';
} else {
print '<tr class="oddeven"><td colspan="2" class="center opacitymedium">Cron-Job nicht gefunden</td></tr>';
}
print '</table>';
// Log file viewer
print '<br>';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<th>Cron Log-Datei</th>';
print '<th class="right">';
print '<form method="get" style="display:inline">';
print 'Zeilen: <input type="number" name="lines" value="'.$lines.'" style="width:60px"> ';
print '<input type="submit" class="button small" value="Aktualisieren">';
print '</form>';
print '</th>';
print '</tr>';
$logFile = $conf->bankimport->dir_output.'/logs/cron_bankimport.log';
print '<tr class="oddeven"><td colspan="2">';
if (file_exists($logFile)) {
$fileSize = filesize($logFile);
print '<small>Datei: '.$logFile.' ('.dol_print_size($fileSize, 1).')</small>';
print '<pre style="background:#1e1e1e; color:#d4d4d4; padding:10px; max-height:500px; overflow:auto; font-size:12px; margin-top:5px;">';
// Read last N lines efficiently
$logContent = '';
$fp = fopen($logFile, 'r');
if ($fp) {
$buffer = array();
while (!feof($fp)) {
$line = fgets($fp);
if ($line !== false) {
$buffer[] = $line;
if (count($buffer) > $lines) {
array_shift($buffer);
}
}
}
fclose($fp);
$logContent = implode('', $buffer);
}
// Syntax highlighting for log
$logContent = htmlspecialchars($logContent);
$logContent = preg_replace('/\[ERROR\]/', '<span style="color:#f44336">[ERROR]</span>', $logContent);
$logContent = preg_replace('/\[WARNING\]/', '<span style="color:#ff9800">[WARNING]</span>', $logContent);
$logContent = preg_replace('/\[INFO\]/', '<span style="color:#4caf50">[INFO]</span>', $logContent);
$logContent = preg_replace('/\[DEBUG\]/', '<span style="color:#9e9e9e">[DEBUG]</span>', $logContent);
$logContent = preg_replace('/========== CRON (START|END.*) ==========/', '<span style="color:#2196f3;font-weight:bold">========== CRON $1 ==========</span>', $logContent);
print $logContent;
print '</pre>';
} else {
print '<span class="opacitymedium">Log-Datei existiert noch nicht: '.$logFile.'</span>';
}
print '</td></tr>';
print '</table>';
// All cron jobs overview
print '<br>';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<th>Alle Modul Cron-Jobs</th>';
print '<th>Status</th>';
print '<th>Nächster Lauf</th>';
print '<th>Letzter Lauf</th>';
print '<th>Processing</th>';
print '</tr>';
$sql = "SELECT rowid, label, datenextrun, datelastrun, processing, status
FROM ".MAIN_DB_PREFIX."cronjob
WHERE label LIKE '%Import%' OR label LIKE '%Bank%' OR label LIKE '%Zugferd%' OR label LIKE '%Dump%'
ORDER BY label";
$resql = $db->query($sql);
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
print '<tr class="oddeven">';
print '<td>'.$obj->label.'</td>';
print '<td>';
if ($obj->status == 1) {
print '<span class="badge badge-status4">Aktiv</span>';
} else {
print '<span class="badge badge-status5">Inaktiv</span>';
}
print '</td>';
print '<td>'.dol_print_date($db->jdate($obj->datenextrun), 'dayhour').'</td>';
print '<td>'.dol_print_date($db->jdate($obj->datelastrun), 'dayhour').'</td>';
print '<td>';
if ($obj->processing) {
print '<span class="badge badge-status1" style="background-color:#ff9800">HÄNGT</span>';
} else {
print '<span class="badge badge-status4">OK</span>';
}
print '</td>';
print '</tr>';
}
}
print '</table>';
print dol_get_fiche_end();
llxFooter();
$db->close();