*/
function ueberwachung_hints(array $m, string $error): array
{
$e = strtolower($error);
if (str_starts_with($error, 'Zertifikat')) return ['Zertifikat prüfen: abgelaufen, falscher Name oder fehlende Zwischenzertifikate?', 'Zertifikat erneuern (zum Beispiel Let’s Encrypt in Plesk) und die Seite im Browser öffnen.'];
if (preg_match('/http-status 5\d\d/', $e)) return ['Der Server antwortet, aber die Anwendung meldet einen Fehler: Fehlerprotokoll des Webservers und der Anwendung ansehen.', 'Datenbank, Speicherplatz und PHP-Prozess prüfen.', 'Kürzlich eingespielte Änderungen oder Updates zurücknehmen.'];
if (preg_match('/http-status 4\d\d/', $e)) return ['Die Adresse antwortet mit einem Fehler (zum Beispiel 404 oder 403): Adresse, Weiterleitung und Zugriffsschutz prüfen.'];
if (str_contains($e, 'standard- oder fehlerseite')) return ['Der Server liefert nur seine Standard- oder Fehlerseite aus: Ist der Eintrag im Reverse-Proxy (zum Beispiel Nginx Proxy Manager) aktiviert, und läuft der Dienst dahinter?', 'Adresse und Port des Ziels im Proxy prüfen und den Dienst dahinter ansehen.'];
if (str_contains($e, 'text fehlt')) return ['Die Seite antwortet, enthält aber nicht den erwarteten Text: Wartungsseite, Fehlerseite oder veränderter Inhalt?'];
if (str_contains($e, 'dns')) return ['DNS-Eintrag der Adresse prüfen (Domain abgelaufen? Eintrag geändert?).'];
$hints = ['Läuft der Server und der Dienst? Neustart des Webservers bzw. Dienstes versuchen.', 'Firewall und Port-Freigaben prüfen.'];
if ($m['type'] === 'http') $hints[] = 'Die Seite einmal im Browser und von einem anderen Netz aus öffnen, um ein lokales Problem auszuschließen.';
return $hints;
}
function ueberwachung_hint_list(array $hints): string
{
return 'Mögliche Ursachen und nächste Schritte
' . implode('', array_map(fn($h) => '- ' . e($h) . '
', $hints)) . '
';
}
/** Text des Ausfall-Tickets. */
function ueberwachung_down_body(array $m, array $res, int $fails, ?string $firstFail): string
{
$rows = [
['Dienst', (string) $m['name']],
['Art der Prüfung', ueberwachung_type_label((string) $m['type'])],
['Adresse', (string) $m['target']],
['Zustand', 'Ausfall'],
['Fehler', (string) $res['error']],
];
if (!empty($res['status'])) $rows[] = ['HTTP-Status', (string) $res['status']];
$rows[] = ['Seit', ueberwachung_dt($firstFail) . ($firstFail ? ' (vor ' . ueberwachung_duration(max(0, time() - (int) strtotime($firstFail))) . ')' : '')];
$rows[] = ['Fehlgeschlagene Prüfungen in Folge', $fails . ' (Prüfung alle ' . (int) $m['interval_min'] . ' Minuten, Ausfall ab ' . ueberwachung_config()['threshold'] . ' in Folge)'];
$rows[] = ['Letzter erfolgreicher Abruf', $m['last_ok_at'] ? ueberwachung_dt((string) $m['last_ok_at']) : 'noch nie'];
$rows[] = ['Dauer der letzten Prüfung', (int) $res['ms'] . ' ms (Zeitlimit ' . (int) $m['timeout_sec'] . ' Sekunden)'];
if (!empty($m['expect_text'])) $rows[] = ['Erwarteter Text', (string) $m['expect_text']];
foreach (ueberwachung_cert_rows($m, $res) as $r) $rows[] = $r;
$rows[] = ['Geprüft von', (string) (gethostname() ?: 'DeskHub-Server') . ', ' . date('d.m.Y H:i:s')];
return '' . e($m['name']) . ' ist nicht erreichbar bzw. fehlerhaft.
' . ueberwachung_table($rows)
. ueberwachung_hint_list(ueberwachung_hints($m, (string) $res['error']))
. 'Diese Meldung stammt von der automatischen Überwachung. Sobald der Dienst wieder erreichbar ist, wird das Ticket automatisch gelöst.
';
}
/** Text des Zertifikat-Tickets. */
function ueberwachung_cert_body(array $m, array $res, int $days): string
{
$headline = $days < 0 ? 'Das Zertifikat ist seit ' . abs($days) . ' Tagen abgelaufen.' : 'Das Zertifikat läuft in ' . $days . ' Tagen ab.';
$rows = array_merge([
['Dienst', (string) $m['name']],
['Art der Prüfung', ueberwachung_type_label((string) $m['type'])],
['Adresse', (string) $m['target']],
], ueberwachung_cert_rows($m, $res, $days), [
['Warnung ab', (int) $m['cert_warn_days'] . ' Tagen vor Ablauf'],
['Geprüft von', (string) (gethostname() ?: 'DeskHub-Server') . ', ' . date('d.m.Y H:i:s')],
]);
return '' . e($headline) . '
' . ueberwachung_table($rows)
. ueberwachung_hint_list(['Zertifikat erneuern (zum Beispiel Let’s Encrypt in Plesk oder beim Anbieter).', 'Nach der Erneuerung die Seite im Browser öffnen und die Kette prüfen.', 'Bei automatischer Erneuerung: prüfen, warum sie nicht gelaufen ist.'])
. 'Nach der Erneuerung wird dieses Ticket automatisch gelöst.
';
}
/** Interne Notiz bei Entwarnung (Ausfall). */
function ueberwachung_up_note(array $m, string $dur): string
{
$since = $m['first_fail_at'] ?? $m['down_since'] ?? null;
return 'Wieder erreichbar (' . $m['name'] . '). Der Ausfall dauerte ' . $dur . '.'
. ($since ? ' Von ' . ueberwachung_dt((string) $since) . ' bis ' . date('d.m.Y H:i:s') . '.' : '');
}
/** Titel der Seite (für Anzeige und die Erkennung von Standard- und Fehlerseiten). */
function ueberwachung_page_title(string $body): string
{
if (!preg_match('/]*>(.*?)<\/title>/is', $body, $m)) return '';
return mb_substr(trim((string) preg_replace('/\s+/u', ' ', html_entity_decode(strip_tags($m[1]), ENT_QUOTES | ENT_HTML5, 'UTF-8'))), 0, 150);
}
/**
* Antwortet ein Server mit 200, liefert aber nur seine Standard- oder Fehlerseite (zum Beispiel ein abgeschalteter Eintrag im Proxy), ist das
* Ziel trotzdem ausgefallen. Erkannt wird das am Seitentitel. Mit „erwartetem Text“ am Ziel entfällt diese Prüfung. @return string|null Fehlertext
*/
function ueberwachung_default_page(string $title, string $body = ''): ?string
{
// Titel bekannter Standard- und Fehlerseiten (Nginx Proxy Manager: „Default Site“)
if ($title !== '' && preg_match('/^default site$|congratulations|welcome to nginx|nginx.*default|default (web )?(server )?page|test page for|apache2? .*default page|^it works!?$|^index of \/|iis windows server|^welcome to openresty|bad gateway|service (temporarily )?unavailable|gateway time-?out|^50[234]\b|^404\b|not found$/i', $title)) {
return 'Der Server antwortet mit einer Standard- oder Fehlerseite statt mit der Anwendung (Seitentitel: „' . mb_substr($title, 0, 80) . '“)';
}
// Typische Sätze der Standardseiten, auch wenn der Titel nichtssagend ist
if ($body !== '' && preg_match('/successfully started the nginx proxy manager|trying to access a host that isn.?t set up yet|if you see this page, the nginx web server is successfully installed|apache2 (ubuntu|debian) default page|this is the default welcome page|web server\'s default page|domain (is )?(not configured|parked)/i', $body, $m)) {
return 'Der Server antwortet mit einer Standardseite statt mit der Anwendung (Text auf der Seite: „' . mb_substr($m[0], 0, 80) . '“)';
}
return null;
}
/**
* Soll die Warnung „PHP kennt keine vertrauenswürdigen Zertifizierungsstellen“ erscheinen? Nur wenn das System es annimmt UND es auch so aussieht:
* Mindestens ein HTTPS-Ziel mit Zertifikatsprüfung scheitert an der Zertifikatsprüfung und keines wurde erfolgreich geprüft. Wird irgendeine
* HTTPS-Seite mit Prüfung erreicht, funktioniert die Prüfung, egal was die Dateisuche meint.
* @param list> $monitors
*/
function ueberwachung_ca_warning(array $monitors, bool $systemSaysMissing): bool
{
if (!$systemSaysMissing) return false;
$bad = 0;
$good = 0;
foreach ($monitors as $m) {
if (($m['type'] ?? '') !== 'http' || !str_starts_with((string) $m['target'], 'https') || (int) ($m['verify_tls'] ?? 1) !== 1 || empty($m['last_check_at'])) continue;
if (str_starts_with((string) ($m['last_error'] ?? ''), 'Zertifikat/TLS-Fehler')) $bad++; else $good++;
}
return $bad > 0 && $good === 0;
}