OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

settings.php

25 KB
<?php $this->layout('layouts/admin', [
    'page_title'         => 'Statusseite – Einstellungen',
    'current_admin_page' => 'statuspage-settings',
]); ?>
<?php $v = $values ?? []; $val = fn($k) => $this->e($v[$k] ?? ''); ?>

<div class="settings-page">
    <form method="post" action="/admin/statuspage/settings">
        <input type="hidden" name="_csrf" value="<?= $this->e($this->csrf()) ?>">

        <!-- Card 1: Darstellung -->
        <section class="settings-card">
            <div class="settings-card-header">
                <span class="settings-card-icon">&#127760;</span>
                <div>
                    <h2><?= $this->te('Darstellung der öffentlichen Statusseite') ?></h2>
                    <p class="settings-card-desc"><?= $this->t('Untertitel, Akzentfarbe, Support-Link und Uptime-Anzeige der Seite unter <code>/status</code>.') ?></p>
                </div>
            </div>
            <div class="settings-card-body">
                <div class="form-group">
                    <label for="sp-subtitle"><?= $this->te('Untertitel') ?></label>
                    <input type="text" id="sp-subtitle" name="statuspage_subtitle" class="form-control"
                           value="<?= $val('statuspage_subtitle') ?>" placeholder="<?= $this->te('z. B. Aktueller Betriebsstatus unserer IT-Dienste') ?>">
                    <small class="form-hint"><?= $this->te('Wird unter der Überschrift „Systemstatus" angezeigt. Leer = kein Untertitel.') ?></small>
                </div>

                <div class="form-group">
                    <label for="sp-accent"><?= $this->te('Akzentfarbe') ?></label>
                    <input type="color" id="sp-accent" name="statuspage_accent" class="form-control" style="max-width:80px;padding:.2rem;"
                           value="<?= $v['statuspage_accent'] !== '' ? $val('statuspage_accent') : '#2563eb' ?>">
                    <small class="form-hint"><?= $this->te('Färbt Links, den Support-Button und die Websuche auf der öffentlichen Seite.') ?></small>
                </div>

                <div class="form-group">
                    <label for="sp-support-label"><?= $this->te('Support-Link – Beschriftung') ?></label>
                    <input type="text" id="sp-support-label" name="statuspage_support_label" class="form-control"
                           value="<?= $val('statuspage_support_label') ?>" placeholder="<?= $this->te('z. B. Störung melden') ?>">
                </div>
                <div class="form-group">
                    <label for="sp-support-url"><?= $this->te('Support-Link – Adresse') ?></label>
                    <input type="text" id="sp-support-url" name="statuspage_support_url" class="form-control"
                           value="<?= $val('statuspage_support_url') ?>" placeholder="<?= $this->te('https://… oder mailto:support@…') ?>">
                    <small class="form-hint"><?= $this->te('Beide Felder ausfüllen, damit der Button erscheint. Leer = kein Button.') ?></small>
                </div>

                <div class="form-group">
                    <label class="settings-toggle" style="display:inline-flex;align-items:center;gap:.5rem;">
                        <input type="checkbox" name="statuspage_show_uptime" value="1" <?= ($v['statuspage_show_uptime'] ?? '0') === '1' ? 'checked' : '' ?>>
                        <span><strong><?= $this->te('90-Tage-Uptime-Verlauf anzeigen') ?></strong></span>
                    </label>
                    <small class="form-hint"><?= $this->te('Zeigt je Dienst eine Balkenreihe der letzten 90 Tage. Die Tageswerte schreibt der Hintergrund-Task „Statusseite – Dienst-Snapshot" fort (Administration → Cronjobs).') ?></small>
                </div>

                <div class="form-group">
                    <label class="settings-toggle" style="display:inline-flex;align-items:center;gap:.5rem;">
                        <input type="checkbox" name="statuspage_maintenance_feed" value="1" <?= ($v['statuspage_maintenance_feed'] ?? '0') === '1' ? 'checked' : '' ?>>
                        <span><strong><?= $this->te('Wartungen aus dem Wartungskalender übernehmen') ?></strong></span>
                    </label>
                    <small class="form-hint"><?= $this->te('Termine vom Typ „Wartung" aus dem Wartungskalender erscheinen automatisch als geplante Wartung auf der öffentlichen Seite – maximal 10 Tage im Voraus. Setzt das aktive Modul „Wartungskalender" voraus.') ?></small>
                </div>

                <div class="form-group" style="margin-bottom:0;">
                    <label class="settings-toggle" style="display:inline-flex;align-items:center;gap:.5rem;">
                        <input type="checkbox" name="statuspage_configlog_feed" value="1" <?= ($v['statuspage_configlog_feed'] ?? '0') === '1' ? 'checked' : '' ?>>
                        <span><strong><?= $this->te('Geplante Änderungen aus dem Konfigurationslog übernehmen') ?></strong></span>
                    </label>
                    <small class="form-hint"><?= $this->te('Geplante Konfigurationslog-Einträge erscheinen als geplante Wartung – aber nur, wenn der jeweilige Eintrag ausdrücklich „Auf öffentlicher Statusseite anzeigen" gesetzt hat (max. 10 Tage im Voraus). Setzt das aktive Modul „Konfigurationslog" voraus.') ?></small>
                </div>
            </div>
        </section>

        <!-- Card 2: Websuche -->
        <section class="settings-card">
            <div class="settings-card-header">
                <span class="settings-card-icon">&#128269;</span>
                <div>
                    <h2><?= $this->te('Websuche') ?></h2>
                    <p class="settings-card-desc"><?= $this->te('Optionales Suchfeld auf der Statusseite – als kompakter Block über dem Status oder im Startseiten-Modus (Suche im Mittelpunkt, z. B. als Browser-Startseite für Mitarbeitende). Besucher suchen mit dem hier festgelegten Anbieter (auf der Seite nicht änderbar).') ?></p>
                </div>
            </div>
            <div class="settings-card-body">
                <div class="form-group">
                    <label class="settings-toggle" style="display:inline-flex;align-items:center;gap:.5rem;">
                        <input type="checkbox" name="statuspage_search_enabled" value="1" <?= ($v['statuspage_search_enabled'] ?? '0') === '1' ? 'checked' : '' ?>>
                        <span><strong><?= $this->te('Websuche einblenden') ?></strong></span>
                    </label>
                    <small class="form-hint"><?= $this->te('Zeigt ein Suchfeld. Der eingegebene Begriff öffnet die Suche beim gewählten Anbieter in einem neuen Tab.') ?></small>
                </div>
                <div class="form-group">
                    <label><?= $this->te('Einbindung') ?></label>
                    <?php $curLayout = ($v['statuspage_search_layout'] ?? '') === 'start' ? 'start' : 'box'; ?>
                    <label class="settings-toggle" style="display:flex;align-items:flex-start;gap:.5rem;margin-bottom:.4rem;">
                        <input type="radio" name="statuspage_search_layout" value="box" <?= $curLayout === 'box' ? 'checked' : '' ?> style="margin-top:.2rem;">
                        <span><strong><?= $this->te('Kompakter Block über dem Status') ?></strong><br>
                        <small class="form-hint" style="margin-top:0;"><?= $this->te('Klassische Statusseite mit Suchfeld darüber. Die Suche öffnet in einem neuen Tab.') ?></small></span>
                    </label>
                    <label class="settings-toggle" style="display:flex;align-items:flex-start;gap:.5rem;">
                        <input type="radio" name="statuspage_search_layout" value="start" <?= $curLayout === 'start' ? 'checked' : '' ?> style="margin-top:.2rem;">
                        <span><strong><?= $this->te('Startseiten-Modus') ?></strong><br>
                        <small class="form-hint" style="margin-top:0;"><?= $this->te('Logo, Begrüßung und große Suche stehen im Mittelpunkt, dazu eine Status-Kurzinfo; die Status-Details folgen darunter. Ideal, wenn die Seite als Browser-Startseite für Mitarbeitende dient. Die Suche öffnet im selben Tab, das Suchfeld ist beim Laden fokussiert.') ?></small></span>
                    </label>
                </div>
                <div class="form-group" style="margin-bottom:0;">
                    <label for="sp-search-provider"><?= $this->te('Suchanbieter') ?></label>
                    <?php $curProv = ($v['statuspage_search_provider'] ?? '') ?: 'google'; ?>
                    <select id="sp-search-provider" name="statuspage_search_provider" class="form-control" style="max-width:220px;">
                        <?php foreach (\OpenNIT\Controllers\Admin\StatusAdminController::SEARCH_PROVIDERS as $pk => $pinfo): ?>
                        <option value="<?= $this->e($pk) ?>" <?= $pk === $curProv ? 'selected' : '' ?>><?= $this->e($pinfo[0]) ?></option>
                        <?php endforeach; ?>
                    </select>
                    <small class="form-hint"><?= $this->te('Google, Bing, DuckDuckGo, Startpage oder Ecosia. Dieser Anbieter wird auf der Statusseite fest verwendet – Besucher können ihn nicht ändern.') ?></small>
                </div>
            </div>
        </section>

        <!-- Card 3: Eigener HTML-Block -->
        <section class="settings-card">
            <div class="settings-card-header">
                <span class="settings-card-icon">&#128187;</span>
                <div>
                    <h2><?= $this->te('Eigener HTML-Block') ?></h2>
                    <p class="settings-card-desc"><?= $this->te('Optionaler, frei befüllbarer Inhalt ganz oben auf der Statusseite (z. B. Hinweis, Kontaktbox, Bild).') ?></p>
                </div>
            </div>
            <div class="settings-card-body">
                <div class="form-group">
                    <label class="settings-toggle" style="display:inline-flex;align-items:center;gap:.5rem;">
                        <input type="checkbox" name="statuspage_html_enabled" value="1" <?= ($v['statuspage_html_enabled'] ?? '0') === '1' ? 'checked' : '' ?>>
                        <span><strong><?= $this->te('HTML-Block einblenden') ?></strong></span>
                    </label>
                    <small class="form-hint"><?= $this->te('Blendet den unten hinterlegten Inhalt oben auf der Seite ein.') ?></small>
                </div>
                <div class="form-group" style="margin-bottom:0;">
                    <label for="sp-html"><?= $this->te('HTML-Inhalt') ?></label>
                    <textarea id="sp-html" name="statuspage_html_content" class="form-control" rows="8" style="font-family:monospace;font-size:.85rem;" placeholder="<?= $this->te('&lt;p&gt;Willkommen auf unserer Statusseite …&lt;/p&gt;') ?>"><?= $this->e($v['statuspage_html_content'] ?? '') ?></textarea>
                    <small class="form-hint"><?= $this->t('<strong>Wird nicht gefiltert</strong> – bitte nur vertrauenswürdiges HTML einfügen. Das HTML wird genau so dargestellt, wie es hier steht.') ?></small>
                </div>
            </div>
        </section>

        <!-- Card: Zugriff -->
        <section class="settings-card">
            <div class="settings-card-header">
                <span class="settings-card-icon">&#128274;</span>
                <div>
                    <h2><?= $this->te('Zugriff auf die Statusseite') ?></h2>
                    <p class="settings-card-desc">
                        <?= $this->t('Standardmäßig ist <code>/status</code> für jeden erreichbar. Optional lässt sich der Zugriff an einen Token in der Adresse binden – dann öffnet sich die Seite nur noch über einen Link, der ihn enthält.') ?>
                    </p>
                </div>
            </div>
            <div class="settings-card-body">
                <div class="form-group" style="margin-bottom:0;">
                    <label class="settings-toggle" style="display:inline-flex;align-items:center;gap:.5rem;">
                        <input type="checkbox" name="statuspage_token_enabled" value="1" <?= ($v['statuspage_token_enabled'] ?? '0') === '1' ? 'checked' : '' ?>>
                        <span><strong><?= $this->te('Nur mit Token in der Adresse sichtbar') ?></strong></span>
                    </label>
                    <small class="form-hint">
                        <?= $this->te('Beim Einschalten wird ein Token erzeugt, falls noch keiner vorhanden ist. Ein Token in der Adresse steht in Browserverlauf, Lesezeichen und Server-Protokollen – er ersetzt keine Anmeldung, hält die Seite aber aus Suchmaschinen und vom zufälligen Aufruf fern.') ?>
                    </small>
                </div>
            </div>
        </section>

        <!-- Card: Benachrichtigungen für Besucher -->
        <section class="settings-card">
            <div class="settings-card-header">
                <span class="settings-card-icon">&#128233;</span>
                <div>
                    <h2><?= $this->te('Besucher benachrichtigen') ?></h2>
                    <p class="settings-card-desc">
                        <?= $this->te('Besucher können ihre E-Mail-Adresse auf der Statusseite eintragen und werden dann benachrichtigt, sobald eine Störung gemeldet oder behoben wird. Zwischenstände einer laufenden Störung gehen nicht hinaus.') ?>
                    </p>
                </div>
            </div>
            <div class="settings-card-body">
                <div class="form-group" style="margin-bottom:0;">
                    <label class="settings-toggle" style="display:inline-flex;align-items:center;gap:.5rem;">
                        <input type="checkbox" name="statuspage_subscribe_enabled" value="1" <?= ($v['statuspage_subscribe_enabled'] ?? '0') === '1' ? 'checked' : '' ?>>
                        <span><strong><?= $this->te('Eintragen auf der Statusseite anbieten') ?></strong></span>
                    </label>
                    <small class="form-hint">
                        <?= $this->t('Eine Adresse wird erst nach dem Klick im Bestätigungsschreiben eingetragen; unbestätigte Eintragungen werden nach sieben Tagen verworfen. Jede Nachricht enthält einen Abmeldelink. Der Versand läuft über die Zeitsteuerung (<a href="/admin/cronjobs">Cronjobs</a>, Aufgabe <em>Statusseite: Benachrichtigungen versenden</em>) und setzt eine eingerichtete E-Mail-Verbindung voraus.') ?>
                    </small>
                </div>
            </div>
        </section>

        <!-- Card: Vorschläge aus dem Monitoring -->
        <?php if (!empty($modules['monitoring'])): ?>
        <section class="settings-card">
            <div class="settings-card-header">
                <span class="settings-card-icon">&#128225;</span>
                <div>
                    <h2><?= $this->te('Störungen aus dem Monitoring vorschlagen') ?></h2>
                    <p class="settings-card-desc">
                        <?= $this->te('Offene Alarme des Monitorings erscheinen in der Statusseiten-Verwaltung als Vorschlag. Übernommen wird ein Vorschlag von Hand &ndash; damit nur das öffentlich wird, was auch öffentlich werden soll.') ?>
                    </p>
                </div>
            </div>
            <div class="settings-card-body">
                <div class="form-group">
                    <label class="settings-toggle" style="display:inline-flex;align-items:center;gap:.5rem;">
                        <input type="checkbox" name="statuspage_suggest_enabled" value="1" <?= ($v['statuspage_suggest_enabled'] ?? '0') === '1' ? 'checked' : '' ?>>
                        <span><strong><?= $this->te('Vorschläge in der Verwaltung anzeigen') ?></strong></span>
                    </label>
                    <small class="form-hint">
                        <?= $this->te('Ein verworfener Vorschlag kehrt nicht zurück. Meldet das Monitoring einen übernommenen Alarm später als behoben, weist die Verwaltung darauf hin &ndash; die Entwarnung selbst bleibt eine bewusste Handlung.') ?>
                    </small>
                </div>
                <div class="form-group" style="margin-bottom:0;max-width:320px;">
                    <label for="sp-suggest-sev"><?= $this->te('Ab welchem Schweregrad') ?></label>
                    <select id="sp-suggest-sev" name="statuspage_suggest_severity" class="form-control">
                        <?php foreach (OpenNIT\Status\StatusManager::MONITORING_SEVERITY as $key => $label): ?>
                            <option value="<?= $this->e($key) ?>" <?= ($v['statuspage_suggest_severity'] ?? 'high') === $key ? 'selected' : '' ?>>
                                <?= $this->te('{stufe} und höher', ['stufe' => $label]) ?>
                            </option>
                        <?php endforeach; ?>
                    </select>
                    <small class="form-hint"><?= $this->te('Niedrigere Stufen erzeugen viele Vorschläge, die kaum je eine öffentliche Störung sind.') ?></small>
                </div>
            </div>
        </section>
        <?php endif; ?>

        <div class="form-actions" style="display:flex;gap:.75rem;flex-wrap:wrap;">
            <button type="submit" class="btn btn-primary"><?= $this->te('Speichern') ?></button>
            <a href="/status" target="_blank" rel="noopener" class="btn btn-default"><?= $this->te('Öffentliche Seite ansehen') ?></a>
        </div>
    </form>

    <?php if (($v['statuspage_subscribe_enabled'] ?? '0') === '1'): ?>
    <?php $sc = $subscriber_counts ?? ['bestaetigt' => 0, 'offen' => 0, 'wartend' => 0]; ?>
    <!-- Eigenes Formular je Zeile: das Entfernen gehört nicht zum Speichern der Einstellungen -->
    <section class="settings-card">
        <div class="settings-card-header">
            <span class="settings-card-icon">&#128203;</span>
            <div>
                <h2><?= $this->te('Eingetragene Adressen') ?></h2>
                <p class="settings-card-desc">
                    <?= $this->t('{bestaetigt} bestätigt, {offen} unbestätigt, {wartend} Nachricht(en) warten auf den Versand.', ['bestaetigt' => (int)$sc['bestaetigt'], 'offen' => (int)$sc['offen'], 'wartend' => (int)$sc['wartend']]) ?>
                </p>
            </div>
        </div>
        <div class="settings-card-body">
            <?php if (empty($subscribers)): ?>
                <div class="empty-state">
                    <p><?= $this->te('Noch hat sich niemand eingetragen.') ?></p>
                </div>
            <?php else: ?>
            <div class="table-wrapper">
                <table class="data-table">
                    <thead>
                        <tr>
                            <th scope="col"><?= $this->te('Adresse') ?></th>
                            <th scope="col"><?= $this->te('Zustand') ?></th>
                            <th scope="col"><?= $this->te('Eingetragen') ?></th>
                            <th scope="col"><?= $this->te('Zuletzt benachrichtigt') ?></th>
                            <th scope="col"></th>
                        </tr>
                    </thead>
                    <tbody>
                    <?php foreach ($subscribers as $abo): ?>
                        <tr>
                            <td><?= $this->e((string)$abo['email']) ?></td>
                            <td>
                                <?php if (!empty($abo['confirmed_at'])): ?>
                                    <span class="status-pill status-pill--success"><?= $this->te('Bestätigt') ?></span>
                                <?php else: ?>
                                    <span class="status-pill status-pill--warning"><?= $this->te('Unbestätigt') ?></span>
                                <?php endif; ?>
                            </td>
                            <td class="text-muted"><?= date('d.m.Y', (int)strtotime((string)$abo['created_at'])) ?></td>
                            <td class="text-muted">
                                <?= !empty($abo['last_sent_at'])
                                        ? date('d.m.Y H:i', (int)strtotime((string)$abo['last_sent_at']))
                                        : '—' ?>
                            </td>
                            <td style="text-align:right;">
                                <form method="post" action="/admin/statuspage/subscribers/<?= (int)$abo['id'] ?>/delete"
                                      onsubmit="return confirm('Diese Eintragung entfernen?');">
                                    <input type="hidden" name="_csrf" value="<?= $this->e($this->csrf()) ?>">
                                    <button type="submit" class="btn btn-outline-danger btn-sm"><?= $this->te('Entfernen') ?></button>
                                </form>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
            <?php endif; ?>
        </div>
    </section>
    <?php endif; ?>

    <?php if (($v['statuspage_token_enabled'] ?? '0') === '1' && ($status_token ?? '') !== ''): ?>
    <!-- Eigenes Formular: das Neu-Erzeugen gehört nicht zum Speichern der Einstellungen -->
    <section class="settings-card">
        <div class="settings-card-header">
            <span class="settings-card-icon">&#128273;</span>
            <div>
                <h2><?= $this->te('Zugriffslinks') ?></h2>
                <p class="settings-card-desc"><?= $this->te('Diese Adressen öffnen die Seite; ohne den Token bleibt sie verborgen.') ?></p>
            </div>
        </div>
        <div class="settings-card-body">
            <div class="form-group">
                <label for="sp-link-public"><?= $this->te('Öffentliche Seite') ?></label>
                <input type="text" id="sp-link-public" class="form-control" readonly onclick="this.select()"
                       value="/status?token=<?= $this->e($status_token) ?>">
            </div>
            <div class="form-group">
                <label for="sp-link-embed"><?= $this->te('iframe-Variante') ?></label>
                <input type="text" id="sp-link-embed" class="form-control" readonly onclick="this.select()"
                       value="/status/embed?token=<?= $this->e($status_token) ?>">
                <small class="form-hint"><?= $this->te('Den fertigen Einbettungscode gibt es in der Statusseiten-Verwaltung unter „Einbetten &amp; teilen" – er enthält den Token automatisch.') ?></small>
            </div>
            <form method="post" action="/admin/statuspage/token/regenerate"
                  onsubmit="return confirm('Neuen Token erzeugen? Bestehende Links und iframes funktionieren danach nicht mehr.');">
                <input type="hidden" name="_csrf" value="<?= $this->e($this->csrf()) ?>">
                <div class="form-group" style="margin-bottom:0;">
                    <button type="submit" class="btn btn-default">
                        <i class="fas fa-sync-alt"></i> <?= $this->te('Neuen Token erzeugen') ?>
                    </button>
                    <small class="form-hint"><?= $this->te('Der bisherige Token verliert sofort seine Gültigkeit.') ?></small>
                </div>
            </form>
        </div>
    </section>
    <?php endif; ?>

    <!-- Eigenes Formular: die Aktion gehört nicht zum Speichern der Einstellungen -->
    <section class="settings-card">
        <div class="settings-card-header">
            <span class="settings-card-icon">&#128200;</span>
            <div>
                <h2><?= $this->te('Verlauf aufbauen') ?></h2>
                <p class="settings-card-desc">
                    <?= $this->t('Die Balken unter jedem Dienst zeigen die letzten 90&nbsp;Tage. Solange nichts erfasst wurde, bleiben sie leer. Diese Aktion trägt für jeden angelegten Dienst die vergangenen 90&nbsp;Tage bis heute als <strong>betriebsbereit</strong> nach – sinnvoll beim Start der Statusseite.') ?>
                </p>
            </div>
        </div>
        <div class="settings-card-body">
            <form method="post" action="/admin/statuspage/history/backfill"
                  onsubmit="return confirm('Die vergangenen 90 Tage für alle Dienste als betriebsbereit eintragen?');">
                <input type="hidden" name="_csrf" value="<?= $this->e($this->csrf()) ?>">
                <div class="form-group" style="margin-bottom:0;">
                    <button type="submit" class="btn btn-default">
                        <i class="fas fa-history"></i> <?= $this->te('Letzte 90 Tage als betriebsbereit eintragen') ?>
                    </button>
                    <small class="form-hint">
                        <?= $this->te('Bereits erfasste Tage bleiben unverändert – eine protokollierte Störung wird dadurch nicht nachträglich grün. Die Aktion lässt sich gefahrlos wiederholen und wird im Audit-Log festgehalten.') ?>
                    </small>
                </div>
            </form>
        </div>
    </section>
</div>