Files
deskhub/Plugins/plugins/ueberwachung/pages/ueberwachung.php
T
2026-10-24 20:15:16 +00:00

183 lines
19 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// Seite des Überwachungs-Plugins. Verfügbar: $staff, $pdo. Nur für Administratoren.
if (($staff['role'] ?? '') !== 'admin') { echo '<h1>Überwachung</h1><div class="flash error">Diese Seite ist nur für Administratoren zugänglich.</div>'; return; }
$message = null;
$error = null;
$edit = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
verify_csrf();
$action = $_POST['action'] ?? '';
if ($action === 'save') {
$id = (int) ($_POST['id'] ?? 0);
$r = ueberwachung_save($pdo, $_POST + ['is_active' => !empty($_POST['is_active']) ? 1 : 0, 'verify_tls' => !empty($_POST['verify_tls']) ? 1 : 0], $id);
if ($r['ok']) { $message = $id ? 'Gespeichert.' : 'Das Ziel wird jetzt überwacht.'; if (!$id) ueberwachung_check($pdo, $r['id']); }
else { $error = $r['error']; $edit = $_POST; }
} elseif ($action === 'self') {
$r = ueberwachung_save($pdo, ['name' => 'Diese DeskHub-Seite', 'type' => 'http', 'target' => rtrim(base_url(), '/') . '/', 'interval_min' => 5]);
if ($r['ok']) { ueberwachung_check($pdo, $r['id']); $message = 'Diese Seite wird jetzt überwacht.'; } else $error = $r['error'];
} elseif ($action === 'test_ticket') {
$tid = ueberwachung_open_ticket($pdo, 'Test: Überwachung', 'Dies ist ein Testticket der Überwachung. Es kann gelöscht oder geschlossen werden.');
if ($tid) { $tn = $pdo->query('SELECT ticket_number FROM tickets WHERE id = ' . (int) $tid)->fetchColumn(); $message = 'Testticket #' . $tn . ' wurde angelegt.'; }
else $error = 'Das Testticket konnte nicht angelegt werden: ' . (plugin_setting(UEBERWACHUNG, 'ticket_error', '') ?: 'Tickets sind ausgeschaltet.');
} elseif ($action === 'check') {
$message = ueberwachung_check($pdo, (int) ($_POST['id'] ?? 0)) ? 'Geprüft.' : null;
if ($message === null) $error = 'Ziel nicht gefunden.';
} elseif ($action === 'toggle') {
$pdo->prepare('UPDATE plugin_ueberwachung_monitors SET is_active = 1 - is_active WHERE id = ?')->execute([(int) ($_POST['id'] ?? 0)]);
$message = 'Gespeichert.';
} elseif ($action === 'delete') {
ueberwachung_delete($pdo, (int) ($_POST['id'] ?? 0));
$message = 'Das Ziel wurde gelöscht (bereits angelegte Tickets bleiben).';
} elseif ($action === 'settings') {
plugin_set_setting(UEBERWACHUNG, 'threshold', (string) max(1, min(10, (int) ($_POST['threshold'] ?? 2))));
plugin_set_setting(UEBERWACHUNG, 'department_id', (string) (int) ($_POST['department_id'] ?? 0));
plugin_set_setting(UEBERWACHUNG, 'priority_id', (string) (int) ($_POST['priority_id'] ?? 0));
foreach (['create_tickets', 'auto_resolve', 'mail'] as $k) plugin_set_setting(UEBERWACHUNG, $k, !empty($_POST[$k]) ? '1' : '0');
$mailTo = trim((string) ($_POST['alert_email'] ?? ''));
$ok = true;
foreach (preg_split('/[,;\s]+/', $mailTo, -1, PREG_SPLIT_NO_EMPTY) ?: [] as $a) if (!filter_var($a, FILTER_VALIDATE_EMAIL)) { $ok = false; $error = 'Ungültige Adresse: ' . $a; }
if ($ok) { plugin_set_setting(UEBERWACHUNG, 'alert_email', $mailTo); $message = 'Einstellungen gespeichert.'; }
}
}
if (isset($_GET['edit'])) {
$s = $pdo->prepare('SELECT * FROM plugin_ueberwachung_monitors WHERE id = ?');
$s->execute([(int) $_GET['edit']]);
$edit = $s->fetch() ?: null;
}
$cfg = ueberwachung_config();
$monitors = $pdo->query('SELECT * FROM plugin_ueberwachung_monitors ORDER BY name')->fetchAll();
$events = $pdo->query('SELECT e.*, m.name FROM plugin_ueberwachung_events e LEFT JOIN plugin_ueberwachung_monitors m ON m.id = e.monitor_id ORDER BY e.id DESC LIMIT 40')->fetchAll();
$depts = $pdo->query('SELECT id, name FROM departments ORDER BY name')->fetchAll();
$prios = $pdo->query('SELECT id, name FROM priorities ORDER BY sort_order')->fetchAll();
$types = ueberwachung_types();
$down = count(array_filter($monitors, fn($m) => $m['state'] === 'down'));
$badge = ['up' => ['In Ordnung', '#157a45', '#e6f7ee'], 'down' => ['Ausfall', '#b42318', '#fde8e8'], 'unknown' => ['Noch nicht geprüft', '#5b6b77', '#eef1f4']];
$f = fn(string $k, $d = '') => $edit[$k] ?? $d;
?>
<h1>Website- und Server-Überwachung</h1>
<?php if ($message): ?><div class="flash success"><?= e($message) ?></div><?php endif; ?>
<?php if ($error): ?><div class="flash error"><?= e($error) ?></div><?php endif; ?>
<?php if (ueberwachung_ca_warning($monitors, ueberwachung_ca_bundle_missing())): ?>
<div class="flash error" style="max-width:860px"><div><strong>Achtung:</strong> Die Zertifikatsprüfung scheitert bei allen HTTPS-Zielen, und auf diesem Server findet PHP keine Liste vertrauenswürdiger Zertifizierungsstellen (CA-Bundle). Vermutlich meldet die Prüfung deshalb bei <em>jeder</em> HTTPS-Adresse „Zertifikat ungültig“, auch wenn die Seite in Ordnung ist. Stellen Sie in der PHP-Konfiguration <code>curl.cainfo</code> und <code>openssl.cafile</code> auf eine CA-Datei (zum Beispiel <code>/etc/ssl/certs/ca-certificates.crt</code>) oder schalten Sie bei den Zielen „Zertifikat prüfen“ aus.</div></div>
<?php endif; ?>
<?php $ticketError = (string) plugin_setting(UEBERWACHUNG, 'ticket_error', ''); ?>
<?php if ($ticketError !== ''): ?>
<div class="flash error" style="max-width:860px"><div>
<strong>Hinweis zum Anlegen von Tickets:</strong> <?= e($ticketError) ?>
<form method="post" style="margin-top:6px"><?= csrf_field() ?><input type="hidden" name="action" value="test_ticket"><button class="btn secondary" type="submit">Testticket anlegen</button></form>
</div></div>
<?php endif; ?>
<div class="card" style="margin-bottom:16px">
<h3>Zustand <?= $monitors ? '' : '' ?></h3>
<?php if (!$monitors): ?>
<p class="muted">Noch nichts eingetragen. Unten ein Ziel hinzufügen oder gleich diese Seite überwachen:</p>
<form method="post"><?= csrf_field() ?><input type="hidden" name="action" value="self"><button class="btn secondary" type="submit">Diese DeskHub-Seite überwachen</button></form>
<?php else: ?>
<p style="font-size:.95rem"><?= $down ? '<strong style="color:#b42318">' . $down . ' Ausfall' . ($down > 1 ? 'e' : '') . '</strong> · ' : '<strong style="color:#157a45">Alles in Ordnung</strong> · ' ?><?= count($monitors) ?> Ziele, geprüft vom Hintergrunddienst von DeskHub (im jeweils eingestellten Abstand).</p>
<table style="width:100%">
<thead><tr><th>Name</th><th>Ziel</th><th>Zustand</th><th>Zertifikat</th><th>Zuletzt geprüft</th><th></th></tr></thead>
<tbody>
<?php foreach ($monitors as $m): $b = $badge[$m['state']] ?? $badge['unknown']; $days = $m['cert_expires'] ? (int) floor((strtotime((string) $m['cert_expires']) - time()) / 86400) : null; ?>
<tr style="<?= $m['is_active'] ? '' : 'opacity:.55' ?>">
<td><strong><?= e($m['name']) ?></strong><div class="muted" style="font-size:.74rem"><?= e($types[$m['type']] ?? $m['type']) ?><?= $m['is_active'] ? '' : ' · pausiert' ?></div></td>
<td style="font-size:.84rem;word-break:break-all"><?= e($m['target']) ?><?php if (!empty($m['last_status']) || !empty($m['last_title'])): ?><div class="muted" style="font-size:.74rem">Antwort: <?= $m['last_status'] ? 'HTTP ' . (int) $m['last_status'] : '' ?><?= !empty($m['last_title']) ? ' · Titel „' . e(mb_strimwidth((string) $m['last_title'], 0, 60, '…')) . '“' : '' ?></div><?php endif; ?></td>
<td><span style="background:<?= $b[2] ?>;color:<?= $b[1] ?>;border-radius:20px;padding:3px 11px;font-size:.78rem;font-weight:700"><?= e($b[0]) ?></span>
<?php if ($m['state'] === 'down' && $m['last_error']): ?><div style="font-size:.74rem;color:#b42318"><?= e((string) $m['last_error']) ?></div>
<?php elseif ((int) $m['fails'] > 0): ?><div style="font-size:.74rem;color:#9a6200">Fehlschlag <?= (int) $m['fails'] ?> von <?= (int) $cfg['threshold'] ?> (Ausfall und Ticket ab Fehlschlag <?= (int) $cfg['threshold'] ?>): <?= e((string) $m['last_error']) ?></div><?php endif; ?>
<?php if ($m['is_active'] && $m['last_check_at']): $next = (int) strtotime((string) $m['last_check_at']) + ueberwachung_next_wait($m); ?><div class="muted" style="font-size:.72rem">nächste Prüfung ca. <?= e(date('H:i', max($next, time()))) ?></div><?php endif; ?></td>
<td style="font-size:.84rem"><?= $days === null ? '–' : ($days < 0 ? '<strong style="color:#b42318">abgelaufen</strong>' : ($days <= (int) $m['cert_warn_days'] ? '<strong style="color:#9a6200">' . $days . ' Tage</strong>' : $days . ' Tage')) ?></td>
<td class="muted" style="font-size:.8rem"><?= $m['last_check_at'] ? e(format_dt((string) $m['last_check_at'])) . ($m['last_ms'] !== null ? ' · ' . (int) $m['last_ms'] . ' ms' : '') : '–' ?></td>
<td style="white-space:nowrap">
<form method="post" style="display:inline"><?= csrf_field() ?><input type="hidden" name="id" value="<?= (int) $m['id'] ?>"><button class="btn secondary" name="action" value="check" type="submit" style="padding:2px 9px;font-size:.78rem">Jetzt prüfen</button>
<button class="btn secondary" name="action" value="toggle" type="submit" style="padding:2px 9px;font-size:.78rem"><?= $m['is_active'] ? 'Pausieren' : 'Fortsetzen' ?></button>
<a class="btn secondary" href="/staff/plugin.php?p=ueberwachung&amp;page=ueberwachung&amp;edit=<?= (int) $m['id'] ?>" style="padding:2px 9px;font-size:.78rem">Ändern</a>
<button class="btn danger" name="action" value="delete" type="submit" style="padding:2px 9px;font-size:.78rem" onclick="return confirm('Ziel löschen?')">Löschen</button></form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<?php
$lastTick = (int) plugin_setting(UEBERWACHUNG, 'last_tick', '0');
$stale = $monitors && (!$lastTick || time() - $lastTick > 900);
?>
<div class="card" style="max-width:860px;margin-bottom:16px;<?= $stale ? 'border-left:4px solid #b45309' : '' ?>">
<h3>Hintergrunddienst</h3>
<p style="font-size:.9rem">
<?php if (!$lastTick): ?><strong style="color:#b45309">Noch kein Lauf.</strong> Die Prüfungen starten, sobald DeskHub das nächste Mal aufgerufen wird.
<?php elseif ($stale): ?><strong style="color:#b45309">Letzter Lauf vor <?= e(ueberwachung_duration(time() - $lastTick)) ?>.</strong> Es hat länger niemand DeskHub aufgerufen, oder der automatische Hintergrund-Betrieb ist ausgeschaltet (Einstellungen → Cron-Jobs).
<?php else: ?><strong style="color:#157a45">✓ Läuft.</strong> Letzter Lauf: <?= e(format_dt(date('Y-m-d H:i:s', $lastTick))) ?>.<?php endif; ?>
</p>
<?php
$cliLast = (string) get_setting('cron_run_plugins_last_cli', '');
$cliAge = $cliLast !== '' ? time() - (int) strtotime($cliLast) : null;
?>
<p style="font-size:.88rem">
Echter Server-Cronjob:
<?php if ($cliAge === null): ?><strong style="color:#b45309">bisher kein Lauf</strong> <span class="muted">(Einstellungen → Cron-Jobs → „Neu installieren / aktualisieren“, mit den aktuellen DeskHub-Dateien; bei älteren Dateien siehe unten)</span>
<?php elseif ($cliAge > 900): ?><strong style="color:#b45309">letzter Lauf vor <?= e(ueberwachung_duration($cliAge)) ?></strong> <span class="muted">(<?= e(format_dt($cliLast)) ?>). Er sollte jede Minute laufen: Crontab und <code>cron/run_plugins.log</code> prüfen.</span>
<?php else: ?><strong style="color:#157a45">✓ läuft</strong> <span class="muted">(letzter Lauf <?= e(format_dt($cliLast)) ?>)</span><?php endif; ?>
</p>
<p class="muted" style="font-size:.84rem">Die Prüfungen laufen mit den Hintergrundaufgaben von DeskHub. <strong>Mit dem aktuellen DeskHub-Update</strong> gehören sie zum echten Server-Cronjob: Einstellungen → Cron-Jobs → „Neu installieren / aktualisieren“ trägt die Zeile ein (jede Minute), dann wird auch nachts ohne Besucher in dem Abstand geprüft, den Sie je Ziel einstellen (mindestens 1 Minute). Bei älteren Dateien oder ohne Server-Cronjob läuft die Prüfung nur, solange jemand die Seite oder das Backend aufruft. Dann hilft ein eigener Cronjob für dieses Plugin (zum Beispiel Plesk → Geplante Aufgaben, alle 5 Minuten):</p>
<pre style="background:var(--bg-soft,#f3f6f8);border:1px solid var(--border,#e3e9ee);border-radius:6px;padding:8px 10px;font-size:.78rem;overflow:auto;margin:0">php <?= e(str_replace('\\', '/', plugins_dir()) . '/' . UEBERWACHUNG . '/cron.php') ?></pre>
<p class="muted" style="font-size:.8rem;margin-top:8px"><strong>Wichtig:</strong> Fällt der Server aus, auf dem DeskHub selbst läuft, kann DeskHub das nicht melden. Die eigene Erreichbarkeit überwacht am besten ein Dienst auf einem anderen Server.</p>
</div>
<form method="post" class="card" style="max-width:860px;margin-bottom:16px">
<?= csrf_field() ?>
<input type="hidden" name="action" value="save">
<input type="hidden" name="id" value="<?= (int) $f('id', 0) ?>">
<h3><?= $edit && !empty($edit['id']) ? 'Ziel ändern' : 'Neues Ziel' ?></h3>
<div style="display:flex;gap:12px;flex-wrap:wrap">
<div style="flex:1;min-width:200px"><label>Name</label><input type="text" name="name" value="<?= e((string) $f('name')) ?>" placeholder="Support-Seite" required></div>
<div><label>Art</label><select name="type"><?php foreach ($types as $k => $l): ?><option value="<?= e($k) ?>" <?= $f('type', 'http') === $k ? 'selected' : '' ?>><?= e($l) ?></option><?php endforeach; ?></select></div>
</div>
<label style="margin-top:8px">Adresse bzw. Host:Port</label>
<input type="text" name="target" value="<?= e((string) $f('target')) ?>" placeholder="https://support.example.de/ oder mail.example.de:993" required>
<label style="margin-top:8px">Erwarteter Text auf der Seite (optional, nur bei Webseiten)</label>
<p class="muted" style="font-size:.78rem;margin:0 0 4px">Tipp: Mit einem Text, der nur auf der echten Seite steht (zum Beispiel der Name Ihrer Anwendung), erkennt die Überwachung auch Fälle, in denen ein Proxy oder Webserver eine andere Seite mit „200 OK“ ausliefert. Ohne Text werden bekannte Standard- und Fehlerseiten am Titel erkannt.</p>
<input type="text" name="expect_text" value="<?= e((string) $f('expect_text')) ?>" maxlength="200">
<div style="display:flex;gap:12px;flex-wrap:wrap;margin-top:8px">
<div><label>Prüfen alle (Minuten)</label><input type="number" name="interval_min" min="1" max="1440" value="<?= (int) $f('interval_min', 5) ?>" style="max-width:110px"></div>
<div><label>Zeitlimit (Sekunden)</label><input type="number" name="timeout_sec" min="2" max="30" value="<?= (int) $f('timeout_sec', 8) ?>" style="max-width:110px"></div>
<div><label>Zertifikat-Warnung (Tage vorher)</label><input type="number" name="cert_warn_days" min="1" max="90" value="<?= (int) $f('cert_warn_days', 14) ?>" style="max-width:110px"></div>
</div>
<p class="muted" style="font-size:.78rem;margin:6px 0 0">Der eingestellte Abstand gilt, sobald der Hintergrunddienst von DeskHub jede Minute läuft (aktuelle Dateien: ein Cronjob mit „Neu installieren / aktualisieren“, oder bei Seitenaufrufen). Bei älteren Dateien läuft er nur alle 5 Minuten, kürzere Werte greifen dort erst mit dem eigenen Cronjob des Plugins (Karte „Hintergrunddienst“, Zeitplan <code>* * * * *</code>).</p>
<label style="display:block;margin-top:8px;font-weight:400"><input type="checkbox" name="verify_tls" value="1" style="width:auto" <?= !$edit || !array_key_exists('verify_tls', $edit) || !empty($edit['verify_tls']) ? 'checked' : '' ?>> Zertifikat prüfen (bei selbstsignierten oder internen Zertifikaten abschalten)</label>
<label style="display:block;margin-top:8px;font-weight:400"><input type="checkbox" name="is_active" value="1" style="width:auto" <?= !$edit || !empty($edit['is_active']) ? 'checked' : '' ?>> Aktiv</label>
<div class="btn-row"><button type="submit" class="btn">Speichern</button><?php if ($edit && !empty($edit['id'])): ?> <a class="btn secondary" href="/staff/plugin.php?p=ueberwachung&amp;page=ueberwachung">Abbrechen</a><?php endif; ?></div>
</form>
<form method="post" class="card" style="max-width:860px;margin-bottom:16px">
<?= csrf_field() ?>
<input type="hidden" name="action" value="settings">
<h3>Einstellungen</h3>
<label style="display:block;font-weight:400"><input type="checkbox" name="create_tickets" value="1" style="width:auto" <?= $cfg['create_tickets'] ? 'checked' : '' ?>> Bei Ausfall und ablaufendem Zertifikat ein Ticket anlegen</label>
<label style="display:block;font-weight:400"><input type="checkbox" name="auto_resolve" value="1" style="width:auto" <?= $cfg['auto_resolve'] ? 'checked' : '' ?>> Ticket nach der Behebung automatisch auf „Gelöst“ setzen (Entwarnung)</label>
<label style="display:block;font-weight:400"><input type="checkbox" name="mail" value="1" style="width:auto" <?= $cfg['mail'] ? 'checked' : '' ?>> E-Mail bei Ausfall, Entwarnung und Zertifikat</label>
<div style="display:flex;gap:12px;flex-wrap:wrap;margin-top:8px">
<div><label>Ausfall melden nach … Fehlschlägen in Folge</label><input type="number" name="threshold" min="1" max="10" value="<?= (int) $cfg['threshold'] ?>" style="max-width:110px"></div>
<div><label>Abteilung der Tickets</label><select name="department_id"><option value="0">Standard</option><?php foreach ($depts as $d): ?><option value="<?= (int) $d['id'] ?>" <?= (int) $d['id'] === $cfg['department_id'] ? 'selected' : '' ?>><?= e($d['name']) ?></option><?php endforeach; ?></select></div>
<div><label>Priorität der Tickets</label><select name="priority_id"><option value="0">Standard</option><?php foreach ($prios as $p): ?><option value="<?= (int) $p['id'] ?>" <?= (int) $p['id'] === $cfg['priority_id'] ? 'selected' : '' ?>><?= e($p['name']) ?></option><?php endforeach; ?></select></div>
</div>
<label style="margin-top:8px">Mail an (leer = Administrator-Adresse der Installation, mehrere mit Komma)</label>
<input type="text" name="alert_email" value="<?= e((string) plugin_setting(UEBERWACHUNG, 'alert_email', '')) ?>" placeholder="<?= e((string) $cfg['alert_email']) ?>">
<div class="btn-row"><button type="submit" class="btn">Speichern</button> <button type="submit" class="btn secondary" name="action" value="test_ticket" formnovalidate>Testticket anlegen</button></div>
</form>
<div class="card">
<h3>Ereignisse</h3>
<?php if (!$events): ?><p class="muted">Noch keine Ereignisse.</p><?php else: ?>
<table style="width:100%"><thead><tr><th>Wann</th><th>Ziel</th><th>Ereignis</th></tr></thead><tbody>
<?php foreach ($events as $ev): ?>
<tr><td class="muted" style="white-space:nowrap"><?= e(format_dt((string) $ev['created_at'])) ?></td><td><?= e((string) $ev['name']) ?></td><td><?= e(['down' => 'Ausfall', 'up' => 'Entwarnung', 'cert' => 'Zertifikat', 'cert_ok' => 'Zertifikat erneuert'][$ev['kind']] ?? $ev['kind']) ?>: <?= e((string) $ev['message']) ?></td></tr>
<?php endforeach; ?>
</tbody></table>
<?php endif; ?>
</div>