Upload via GUI (258 Dateien)

This commit is contained in:
2026-10-16 19:12:18 +00:00
parent 56deade877
commit 6cced3beda
50 changed files with 11148 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
<?php
/**
* Systemüberwachung: erkennt, wenn der Mail-Abruf oder die Hintergrundjobs nicht mehr laufen,
* benachrichtigt Administratoren per E-Mail und liefert eine Liste von Problemen für die
* Weboberfläche und die Apps (Banner).
*/
/** Ab so vielen Fehlversuchen in Folge gilt ein Postfach als ausgefallen (bei 5-Minuten-Takt ≈ 15 Min.). */
const HEALTH_FAIL_THRESHOLD = 3;
/** Wiederholte Alarm-Mail frühestens nach so vielen Sekunden, solange der Fehler anhält. */
const HEALTH_ALERT_REPEAT = 86400;
/** E-Mail-Adressen aller aktiven Administratoren. */
function health_admin_emails(PDO $pdo): array
{
return $pdo->query("SELECT email FROM staff WHERE is_active = 1 AND role = 'admin'")->fetchAll(PDO::FETCH_COLUMN) ?: [];
}
/**
* Hält das Ergebnis eines Abrufs für ein Postfach fest. $error = null bedeutet Erfolg.
* Verschickt eine Alarm-Mail an die Administratoren, wenn der Fehler anhält, und eine
* Entwarnung, wenn das Postfach wieder erreichbar ist.
*/
function health_record_mail_result(PDO $pdo, array $account, ?string $error): void
{
$now = db_now();
$label = $account['label'] ?: $account['username'];
if ($error === null) {
$wasAlerted = !empty($account['alert_sent_at']);
$pdo->prepare('UPDATE imap_accounts SET last_success_at = ?, last_error = NULL, fail_count = 0, alert_sent_at = NULL WHERE id = ?')->execute([$now, $account['id']]);
if ($wasAlerted) {
foreach (health_admin_emails($pdo) as $to) {
send_mail($to, "DeskHub: Postfach „{$label}“ ist wieder erreichbar",
'<p>Der Mail-Abruf für das Postfach <strong>' . e($label) . '</strong> funktioniert wieder.</p>');
}
}
return;
}
$fails = (int) ($account['fail_count'] ?? 0) + 1;
$pdo->prepare('UPDATE imap_accounts SET last_error = ?, fail_count = ? WHERE id = ?')->execute([mb_substr($error, 0, 500), $fails, $account['id']]);
$lastAlert = !empty($account['alert_sent_at']) ? strtotime($account['alert_sent_at']) : 0;
if ($fails >= HEALTH_FAIL_THRESHOLD && (time() - $lastAlert) >= HEALTH_ALERT_REPEAT) {
$since = $account['last_success_at'] ? date('d.m.Y H:i', strtotime($account['last_success_at'])) : 'noch nie';
$sent = false;
foreach (health_admin_emails($pdo) as $to) {
$sent = send_mail($to, "DeskHub: Mail-Abruf für „{$label}“ fehlgeschlagen",
'<p>Der Mail-Abruf für das Postfach <strong>' . e($label) . '</strong> (' . e($account['host']) . ') schlägt seit '
. $fails . ' Versuchen fehl. Eingehende Mails werden in dieser Zeit <strong>nicht</strong> als Tickets angelegt.</p>'
. '<p>Letzter Fehler: <code>' . e($error) . '</code><br>Letzter erfolgreicher Abruf: ' . e($since) . '</p>'
. '<p>Bitte Zugangsdaten und Server unter E-Mail-Adressen → Entferntes Postfach prüfen.</p>') || $sent;
}
// Auch ohne versendete Mail als „gemeldet“ markieren, damit nicht bei jedem Lauf neu versucht wird
$pdo->prepare('UPDATE imap_accounts SET alert_sent_at = ? WHERE id = ?')->execute([$now, $account['id']]);
}
}
/** Merkt sich den Erfolg bzw. Misserfolg des letzten Mailversands (für die Statusanzeige). */
function health_record_send_result(bool $ok, string $error = ''): void
{
try {
if ($ok) { set_setting('mail_send_last_ok', db_now()); return; }
set_setting('mail_send_last_error', db_now() . '|' . mb_substr($error, 0, 300));
} catch (Throwable $e) {
// Überwachung darf den Versand nie beeinträchtigen
}
}
/**
* Liefert die aktuellen Probleme als Liste von ['level' => 'error'|'warning', 'code' => ..., 'message' => ...].
* Leer = alles in Ordnung. Rein lesend, daher auch bei jedem Seitenaufruf aufrufbar.
*/
function system_health(PDO $pdo): array
{
$issues = [];
// Das zuletzt geprüfte Backup lässt sich nicht wiederherstellen
$bk = json_decode((string) get_setting('backup_last_check', ''), true);
if (is_array($bk) && array_key_exists('ok', $bk) && !$bk['ok']) {
$issues[] = ['level' => 'error', 'code' => 'backup_broken', 'message' => 'Das Backup ' . ($bk['file'] ?? '') . ' ist nicht wiederherstellbar: ' . implode(' ', (array) ($bk['errors'] ?? [])) . ' Bitte unter Backup ein neues erstellen.'];
}
$accounts = $pdo->query('SELECT * FROM imap_accounts WHERE is_active = 1')->fetchAll();
if ($accounts) {
if (!function_exists('imap_open')) {
$issues[] = ['level' => 'error', 'code' => 'imap_missing', 'message' => 'Die PHP-IMAP-Erweiterung fehlt – eingehende Mails können nicht abgerufen werden.'];
}
foreach ($accounts as $a) {
$label = $a['label'] ?: $a['username'];
$fails = (int) ($a['fail_count'] ?? 0);
if ($fails >= HEALTH_FAIL_THRESHOLD) {
$since = !empty($a['last_success_at']) ? ' Letzter Erfolg: ' . date('d.m.Y H:i', strtotime($a['last_success_at'])) . '.' : '';
$issues[] = ['level' => 'error', 'code' => 'mailbox_down', 'message' => "Postfach „{$label}“ ist nicht erreichbar ({$fails} Fehlversuche in Folge).{$since} " . ($a['last_error'] ?? '')];
} elseif ($fails > 0) {
$issues[] = ['level' => 'warning', 'code' => 'mailbox_flaky', 'message' => "Postfach „{$label}“: letzter Abruf fehlgeschlagen. " . ($a['last_error'] ?? '')];
}
}
// Der Abruf selbst läuft nicht mehr (Cronjob/Seitenaufrufe bleiben aus)
$last = get_setting('cron_fetch_emails_last_run', '');
if (get_setting('pseudo_cron_enabled', '1') === '1' || $last !== '') {
if ($last !== '' && (time() - strtotime($last)) > 1200) {
$issues[] = ['level' => 'warning', 'code' => 'fetch_stale', 'message' => 'Der Mail-Abruf wurde seit ' . cron_last_run_label($last) . ' nicht ausgeführt. Läuft der Cronjob (cron/fetch_emails.php)?'];
}
}
}
$lastErr = (string) get_setting('mail_send_last_error', '');
if ($lastErr !== '') {
[$at, $msg] = array_pad(explode('|', $lastErr, 2), 2, '');
$ok = (string) get_setting('mail_send_last_ok', '');
if ($at !== '' && strtotime($at) > time() - 86400 && ($ok === '' || strtotime($ok) < strtotime($at))) {
$issues[] = ['level' => 'warning', 'code' => 'send_failed', 'message' => 'Der letzte E-Mail-Versand ist fehlgeschlagen (' . date('d.m.Y H:i', strtotime($at)) . '): ' . $msg];
}
}
return $issues;
}