OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

manage.php

47 KB
<?php
$this->layout('layouts/main', [
    'page_title'    => $page_title,
    'current_route' => $current_route,
    'full_width'    => true,
]);

use OpenNIT\Status\StatusManager;

$services        = $services ?? [];
$activeIncidents = $active_incidents ?? [];
$recentIncidents = $recent_incidents ?? [];
$maintenances    = $maintenances ?? [];
$groups          = $groups ?? [];
$overrides       = $service_overrides ?? [];
$view            = $this;
$csrf            = $this->csrf();

$svcPill = [
    'operational'    => 'status-pill--success',
    'degraded'       => 'status-pill--warning',
    'partial_outage' => 'status-pill--orange',
    'major_outage'   => 'status-pill--danger',
];
$svcColor = [
    'operational'    => '#16a34a',
    'degraded'       => '#d97706',
    'partial_outage' => '#ea580c',
    'major_outage'   => '#dc2626',
];

$suggestions     = $monitoring_suggestions ?? [];
$resolvedAlarms  = $monitoring_resolved ?? [];

$overall      = $overall ?? ['key' => 'operational', 'label' => 'Alle Systeme betriebsbereit', 'all_ok' => true];
$kpi          = $kpi ?? ['services_total' => 0, 'services_down' => 0, 'incidents' => 0, 'maintenances' => 0];
$overallShort = ['operational' => 'Alle OK', 'degraded' => 'Beeinträchtigt', 'partial_outage' => 'Teilausfall', 'major_outage' => 'Störung'];
$overallBg    = ['operational' => 'bg-success', 'degraded' => 'bg-warning', 'partial_outage' => 'bg-orange', 'major_outage' => 'bg-danger'];
?>

<style>
/* Statusseiten-Verwaltung – Tab-Layout (modul-spezifisch, sp-Prefix) */
.sp-tab-panel { display:none; }
.sp-tab-panel.active { display:block; }
.sp-panel-inner .data-table td { padding-top:.4rem; padding-bottom:.4rem; }
/* Tabs + Suche/Aktion in der Filter-Card (wie /orders) */
.sp-subnav { margin-bottom:0; flex:1 1 auto; }
.sp-search-wrap { flex:0 1 260px; margin-left:auto; }
/* Aktionszelle: Select + Buttons auf gleiche Höhe ziehen */
.sp-row-actions { display:inline-flex; align-items:stretch; gap:.35rem; flex-wrap:nowrap; }
.sp-row-actions > form { display:inline-flex; margin:0; }
.sp-row-actions .form-control-sm { width:auto; }
.sp-row-actions .btn { display:inline-flex; align-items:center; }
</style>

<div class="content-header">
    <div>
        <h1><?= $this->te('Status-Seite verwalten') ?></h1>
        <ol class="breadcrumb">
            <li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
            <li class="breadcrumb-item active"><?= $this->te('Statusseite') ?></li>
        </ol>
    </div>
    <div class="content-header-actions">
        <button type="button" class="btn btn-default btn-sm" onclick="document.getElementById('spEmbedModal').style.display='flex'"><i class="fas fa-code"></i> <?= $this->te('Einbetten &amp; teilen') ?></button>
        <a class="btn btn-default btn-sm" href="/status<?= ($status_token ?? '') !== '' ? '?token=' . $this->e($status_token) : '' ?>" target="_blank" rel="noopener"><i class="fas fa-external-link-alt"></i> <?= $this->te('Öffentliche Seite') ?></a>
    </div>
</div>

<div class="page-body">

    <!-- KPI-Übersicht -->
    <div class="kpi-row">
        <div class="kpi-col">
            <div class="small-box <?= $overallBg[$overall['key']] ?? 'bg-success' ?>">
                <div class="inner">
                    <h3 style="font-size:1.5rem;"><?= $view->e($overallShort[$overall['key']] ?? $overall['label']) ?></h3>
                    <p><?= $this->te('Gesamtstatus (öffentlich)') ?></p>
                </div>
                <div class="icon"><i class="fas fa-traffic-light"></i></div>
            </div>
        </div>
        <div class="kpi-col">
            <div class="small-box <?= $kpi['services_down'] > 0 ? 'bg-warning' : 'bg-success' ?>">
                <div class="inner">
                    <h3><?= (int)$kpi['services_down'] ?></h3>
                    <p><?= $this->t('Dienste beeinträchtigt (von') ?> <?= (int)$kpi['services_total'] ?>)</p>
                </div>
                <div class="icon"><i class="fas fa-server"></i></div>
            </div>
        </div>
        <div class="kpi-col">
            <div class="small-box <?= $kpi['incidents'] > 0 ? 'bg-danger' : 'bg-success' ?>">
                <div class="inner">
                    <h3><?= (int)$kpi['incidents'] ?></h3>
                    <p><?= $this->te('Aktive Störungen') ?></p>
                </div>
                <div class="icon"><i class="fas fa-exclamation-triangle"></i></div>
            </div>
        </div>
        <div class="kpi-col">
            <div class="small-box bg-info">
                <div class="inner">
                    <h3><?= (int)$kpi['maintenances'] ?></h3>
                    <p><?= $this->te('Geplante Wartungen') ?></p>
                </div>
                <div class="icon"><i class="fas fa-calendar-check"></i></div>
            </div>
        </div>
    </div>

    <!-- Filter-Card: Tabs + Suche/Aktion (wie /orders) -->
    <div class="card card-outline card-primary mb-3">
        <div class="filter-card-body">
            <div class="subnav sp-subnav" id="spTabs">
                <button type="button" class="subnav-tab active" data-sp-tab="dienste"><i class="fas fa-server"></i> <?= $this->te('Dienste') ?></button>
                <button type="button" class="subnav-tab" data-sp-tab="stoerungen"><i class="fas fa-exclamation-triangle"></i> <?= $this->te('Störungen') ?></button>
                <button type="button" class="subnav-tab" data-sp-tab="wartungen"><i class="fas fa-calendar-check"></i> <?= $this->te('Wartungen') ?></button>
            </div>
            <div class="filter-input-wrap sp-search-wrap" data-sp-show="dienste">
                <svg class="filter-search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
                <input type="search" class="form-control form-control-sm" placeholder="<?= $this->te('Dienste filtern …') ?>" oninput="spFilterServices(this.value)">
            </div>
            <button type="button" class="btn btn-primary btn-sm sp-action" data-sp-show="dienste" onclick="spNewService()"><i class="fas fa-plus"></i> <?= $this->te('Dienst') ?></button>
            <button type="button" class="btn btn-primary btn-sm sp-action" data-sp-show="stoerungen" style="display:none;" onclick="spNewIncident()"><i class="fas fa-plus"></i> <?= $this->te('Störung melden') ?></button>
            <button type="button" class="btn btn-primary btn-sm sp-action" data-sp-show="wartungen" style="display:none;" onclick="document.getElementById('spMaintModal').style.display='flex'"><i class="fas fa-plus"></i> <?= $this->te('Wartung planen') ?></button>
        </div>
    </div>

    <!-- Tab: Dienste -->
    <div class="sp-tab-panel active" id="sp-panel-dienste">
        <div class="sp-panel-inner">
            <?php if (empty($services)): ?>
                <div class="empty-state"><p><?= $this->te('Noch keine Dienste. Lege z. B. „Drucksystem", „Vivendi" oder „Sage" an.') ?></p></div>
            <?php else: ?>
            <div class="table-wrapper">
                <table class="data-table" id="spServiceTable">
                    <thead><tr><th scope="col" style="width:30px;text-align:center;" title="<?= $this->te('Zeilen am Griff ziehen, um die Reihenfolge zu ändern') ?>"><i class="fas fa-grip-vertical" style="color:var(--gray-300);"></i></th><th scope="col"><?= $this->te('Dienst') ?></th><th scope="col"><?= $this->te('Status') ?></th><th scope="col" style="width:240px;"></th></tr></thead>
                    <tbody>
                    <?php foreach ($services as $s): ?>
                        <tr data-id="<?= (int)$s['id'] ?>">
                            <td class="sp-drag-cell" style="text-align:center;color:var(--gray-400);cursor:grab;" draggable="true" title="<?= $this->te('Ziehen zum Sortieren') ?>"><i class="fas fa-grip-vertical"></i></td>
                            <td>
                                <strong><?= $view->e($s['name']) ?></strong>
                                <?php if (!empty($s['group_name'])): ?> <span class="badge"><?= $view->e($s['group_name']) ?></span><?php endif; ?>
                                <?php if (!empty($s['description'])): ?><div style="font-size:.8rem;color:var(--gray-500);"><?= $view->e($s['description']) ?></div><?php endif; ?>
                            </td>
                            <td>
                                <span class="status-pill <?= $svcPill[$s['status']] ?? 'status-pill--neutral' ?>"><?= $view->e(StatusManager::SERVICE_STATUS[$s['status']] ?? $s['status']) ?></span>
                                <?php if (isset($overrides[(int)$s['id']])): $ov = $overrides[(int)$s['id']]; ?>
                                    <div style="font-size:.75rem;color:var(--orange-800);margin-top:.25rem;">
                                        <i class="fas fa-clock"></i> <?= $this->t('öffentlich') ?>: <?= $view->e(StatusManager::SERVICE_STATUS[$ov['status']] ?? $ov['status']) ?>
                                        <a href="/configlog/<?= (int)$ov['id'] ?>" class="table-link" title="<?= $view->e($ov['title']) ?>"><?= $this->te('(geplante Änderung)') ?></a>
                                    </div>
                                <?php endif; ?>
                            </td>
                            <td>
                                <div class="sp-row-actions">
                                    <form method="post" action="/status/services/<?= (int)$s['id'] ?>/status">
                                        <input type="hidden" name="_csrf" value="<?= $view->e($csrf) ?>">
                                        <select name="status" class="form-control form-control-sm" style="font-weight:600;color:<?= $svcColor[$s['status']] ?? 'var(--gray-700)' ?>;" onchange="this.form.submit()">
                                            <?php foreach (StatusManager::SERVICE_STATUS as $k => $lbl): ?>
                                                <option value="<?= $view->e($k) ?>" <?= $s['status'] === $k ? 'selected' : '' ?>><?= $view->e($lbl) ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </form>
                                    <button type="button" class="btn btn-default btn-sm"
                                        onclick='spEditService(<?= json_encode(["id"=>(int)$s["id"],"name"=>$s["name"],"description"=>$s["description"]??"","group_name"=>$s["group_name"]??"","status"=>$s["status"],"sort_order"=>(int)$s["sort_order"]], JSON_HEX_APOS|JSON_HEX_QUOT) ?>)'><?= $this->te('Bearbeiten') ?></button>
                                    <form method="post" action="/status/services/<?= (int)$s['id'] ?>/delete" onsubmit="return confirm('Dienst löschen?');">
                                        <input type="hidden" name="_csrf" value="<?= $view->e($csrf) ?>">
                                        <button aria-label="<?= $this->te('Löschen') ?>" type="submit" class="btn btn-danger btn-sm">&times;</button>
                                    </form>
                                </div>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
            <?php endif; ?>
        </div>
    </div>

    <!-- Tab: Störungen -->
    <div class="sp-tab-panel" id="sp-panel-stoerungen">
        <div class="sp-panel-inner">

            <?php if (!empty($suggestions)): ?>
            <!-- Offene Monitoring-Alarme, über die noch nicht entschieden ist.
                 Die Übernahme füllt den Melden-Dialog vor; abgeschickt wird er
                 wie jede andere Störung auch. -->
            <div class="detail-card" style="margin-bottom:1rem;">
                <div class="detail-card-header">
                    <h2 class="detail-card-title">
                        <i class="fas fa-satellite-dish"></i> <?= $this->te('Aus dem Monitoring') ?>
                        <span class="badge"><?= count($suggestions) ?></span>
                    </h2>
                </div>
                <div class="detail-card-body">
                    <p style="font-size:.85rem;color:var(--gray-500);margin-top:0;">
                        <?= $this->te('Offene Alarme, die noch keine Störungsmeldung sind. Übernehmen füllt den Melden-Dialog vor &ndash; betroffene Dienste und Text bleiben Ihre Entscheidung.') ?>
                    </p>
                    <div class="table-wrapper">
                        <table class="data-table">
                            <thead><tr><th scope="col"><?= $this->te('Alarm') ?></th><th scope="col"><?= $this->te('Schweregrad') ?></th><th scope="col"><?= $this->te('Seit') ?></th><th scope="col"></th></tr></thead>
                            <tbody>
                            <?php foreach ($suggestions as $sug): ?>
                                <tr>
                                    <td>
                                        <strong><?= $view->e((string)$sug['trigger_name']) ?></strong>
                                        <?php $wer = trim((string)($sug['hostname'] ?? '')) ?: trim((string)($sug['host'] ?? '')); ?>
                                        <?php if ($wer !== ''): ?>
                                            <div style="font-size:.8rem;color:var(--gray-500);"><?= $view->e($wer) ?></div>
                                        <?php endif; ?>
                                    </td>
                                    <td><span class="badge"><?= $view->e(StatusManager::MONITORING_SEVERITY[$sug['severity']] ?? (string)$sug['severity']) ?></span></td>
                                    <td style="white-space:nowrap;"><?= $this->te('{zeitpunkt} Uhr', ['zeitpunkt' => date('d.m.Y H:i', strtotime((string)$sug['created_at']))]) ?></td>
                                    <td style="white-space:nowrap;text-align:right;">
                                        <button type="button" class="btn btn-primary btn-xs"
                                            onclick='spFromMonitoring(<?= json_encode([
                                                "id"     => (int)$sug["id"],
                                                "title"  => (string)$sug["vorschlag_titel"],
                                                "impact" => (string)$sug["vorschlag_impact"],
                                                "body"   => trim((string)($sug["message"] ?? "")) !== ""
                                                            ? (string)$sug["message"]
                                                            : (string)$sug["trigger_name"],
                                            ], JSON_HEX_APOS|JSON_HEX_QUOT) ?>)'><?= $this->te('Übernehmen') ?></button>
                                        <form method="post" action="/status/suggestions/<?= (int)$sug['id'] ?>/dismiss" style="display:inline;">
                                            <input type="hidden" name="_csrf" value="<?= $view->e($csrf) ?>">
                                            <button type="submit" class="btn btn-default btn-xs" title="<?= $this->te('Diesen Alarm nicht auf der Statusseite melden') ?>"><?= $this->te('Verwerfen') ?></button>
                                        </form>
                                    </td>
                                </tr>
                            <?php endforeach; ?>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
            <?php endif; ?>

            <?php if (empty($activeIncidents) && empty($recentIncidents)): ?>
                <div class="empty-state"><p><?= $this->te('Keine Störungen erfasst.') ?></p></div>
            <?php endif; ?>

            <?php foreach ($activeIncidents as $inc): ?>
            <div class="detail-card" style="margin-bottom:1rem;">
                <div class="detail-card-header">
                    <h2 class="detail-card-title"><?= $view->e($inc['title']) ?>
                        <span class="badge"><?= $view->e(StatusManager::INCIDENT_STATUS[$inc['status']] ?? $inc['status']) ?></span></h2>
                    <form method="post" action="/status/incidents/<?= (int)$inc['id'] ?>/delete" style="display:inline;" onsubmit="return confirm('Störung löschen?');">
                        <input type="hidden" name="_csrf" value="<?= $view->e($csrf) ?>">
                        <button aria-label="<?= $this->te('Löschen') ?>" type="submit" class="btn btn-danger btn-xs">&times;</button>
                    </form>
                </div>
                <div class="detail-card-body">
                    <?php if (isset($resolvedAlarms[(int)$inc['id']])): ?>
                    <p class="text-muted" style="font-size:.85rem;margin-top:0;">
                        <i class="fas fa-satellite-dish"></i>
                        <?= $this->t('Das Monitoring führt den zugehörigen Alarm seit {zeit} Uhr als behoben.', ['zeit' => date('d.m.Y H:i', strtotime((string)$resolvedAlarms[(int)$inc['id']]['resolved_at']))]) ?>
                    </p>
                    <?php endif; ?>
                    <?php if (!empty($inc['services'])): ?><p style="font-size:.85rem;color:var(--gray-500);"><?= $this->t('Betroffen') ?>: <?= $view->e(implode(', ', array_column($inc['services'], 'name'))) ?></p><?php endif; ?>
                    <ul style="list-style:none;padding-left:0;margin:0 0 1rem;">
                        <?php foreach ($inc['updates'] as $u): ?>
                        <li style="padding:.4rem 0;border-top:1px solid var(--gray-100);">
                            <span style="float:right;display:inline-flex;gap:.3rem;">
                                <button type="button" class="btn btn-default btn-xs" onclick="spToggle('spUpd<?= (int)$u['id'] ?>')"><?= $this->te('Bearbeiten') ?></button>
                                <form method="post" action="/status/incidents/<?= (int)$inc['id'] ?>/updates/<?= (int)$u['id'] ?>/delete" style="display:inline;" onsubmit="return confirm('Update löschen?');">
                                    <input type="hidden" name="_csrf" value="<?= $view->e($csrf) ?>">
                                    <button type="submit" class="btn btn-danger btn-xs" title="<?= $this->te('Update löschen') ?>">&times;</button>
                                </form>
                            </span>
                            <strong><?= $view->e(StatusManager::INCIDENT_STATUS[$u['status']] ?? $u['status']) ?></strong>
                            <span style="font-size:.78rem;color:var(--gray-400);"> · <?= $this->te('{zeitpunkt} Uhr', ['zeitpunkt' => date('d.m.Y H:i', strtotime($u['created_at']))]) ?><?= !empty($u['author_name']) ? ' · ' . $view->e($u['author_name']) : '' ?></span>
                            <div style="white-space:pre-wrap;"><?= $view->e($u['body']) ?></div>
                            <form id="spUpd<?= (int)$u['id'] ?>" method="post" action="/status/incidents/<?= (int)$inc['id'] ?>/updates/<?= (int)$u['id'] ?>/edit" style="display:none;margin-top:.4rem;">
                                <input type="hidden" name="_csrf" value="<?= $view->e($csrf) ?>">
                                <div style="display:flex;gap:.4rem;">
                                    <input type="text" name="body" class="form-control" value="<?= $view->e($u['body']) ?>" required>
                                    <button type="submit" class="btn btn-secondary btn-xs"><?= $this->te('Speichern') ?></button>
                                </div>
                            </form>
                        </li>
                        <?php endforeach; ?>
                    </ul>
                    <form method="post" action="/status/incidents/<?= (int)$inc['id'] ?>/update">
                        <input type="hidden" name="_csrf" value="<?= $view->e($csrf) ?>">
                        <div style="display:flex;gap:.5rem;">
                            <div class="form-group" style="flex:0 0 200px;">
                                <label><?= $this->te('Neuer Status') ?></label>
                                <select name="status" class="form-control">
                                    <?php foreach (StatusManager::INCIDENT_STATUS as $k => $lbl): ?>
                                        <option value="<?= $view->e($k) ?>" <?= $inc['status'] === $k ? 'selected' : '' ?>><?= $view->e($lbl) ?></option>
                                    <?php endforeach; ?>
                                </select>
                            </div>
                            <div class="form-group" style="flex:1;">
                                <label><?= $this->te('Update-Text') ?></label>
                                <input type="text" name="body" class="form-control" placeholder="<?= $this->te('Was ist der aktuelle Stand?') ?>" required>
                            </div>
                        </div>
                        <button type="submit" class="btn btn-secondary btn-sm"><?= $this->te('Update veröffentlichen') ?></button>
                    </form>
                </div>
            </div>
            <?php endforeach; ?>

            <?php if (!empty($recentIncidents)): ?>
            <h3 style="font-size:.9rem;color:var(--gray-500);margin:1rem 0 .5rem;"><?= $this->te('Behoben (letzte 30 Tage)') ?></h3>
            <div class="table-wrapper">
                <table class="data-table">
                    <thead><tr><th scope="col"><?= $this->te('Störung') ?></th><th scope="col"><?= $this->te('Behoben') ?></th><th scope="col"></th></tr></thead>
                    <tbody>
                    <?php foreach ($recentIncidents as $inc): ?>
                        <tr>
                            <td><?= $view->e($inc['title']) ?></td>
                            <td style="white-space:nowrap;"><?= $this->te('{zeitpunkt} Uhr', ['zeitpunkt' => date('d.m.Y H:i', strtotime($inc['resolved_at']))]) ?></td>
                            <td>
                                <form method="post" action="/status/incidents/<?= (int)$inc['id'] ?>/delete" style="display:inline;" onsubmit="return confirm('Störung löschen?');">
                                    <input type="hidden" name="_csrf" value="<?= $view->e($csrf) ?>">
                                    <button aria-label="<?= $this->te('Löschen') ?>" type="submit" class="btn btn-danger btn-xs">&times;</button>
                                </form>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
            <?php endif; ?>
        </div>
    </div>

    <!-- Tab: Wartungen -->
    <div class="sp-tab-panel" id="sp-panel-wartungen">
        <div class="sp-panel-inner">
            <?php if (empty($maintenances)): ?>
                <div class="empty-state"><p><?= $this->te('Keine Wartungen geplant.') ?></p></div>
            <?php else: ?>
            <div class="table-wrapper">
                <table class="data-table">
                    <thead><tr><th scope="col"><?= $this->te('Titel') ?></th><th scope="col"><?= $this->te('Zeitraum') ?></th><th scope="col"><?= $this->te('Status') ?></th><th scope="col" style="width:160px;"></th></tr></thead>
                    <tbody>
                    <?php foreach ($maintenances as $m): ?>
                        <tr>
                            <td>
                                <strong><?= $view->e($m['title']) ?></strong>
                                <?php if (!empty($m['services'])): ?><div style="font-size:.8rem;color:var(--gray-500);"><?= $view->e(implode(', ', array_column($m['services'], 'name'))) ?></div><?php endif; ?>
                            </td>
                            <td style="white-space:nowrap;"><?= date('d.m.Y H:i', strtotime($m['scheduled_start'])) ?><?= !empty($m['scheduled_end']) ? ' – ' . date('d.m.Y H:i', strtotime($m['scheduled_end'])) : '' ?></td>
                            <td>
                                <form method="post" action="/status/maintenances/<?= (int)$m['id'] ?>/update" style="display:inline;">
                                    <input type="hidden" name="_csrf" value="<?= $view->e($csrf) ?>">
                                    <select name="status" class="form-control form-control-sm" style="width:auto;" onchange="this.form.submit()">
                                        <?php foreach (StatusManager::MAINTENANCE_STATUS as $k => $lbl): ?>
                                            <option value="<?= $view->e($k) ?>" <?= $m['status'] === $k ? 'selected' : '' ?>><?= $view->e($lbl) ?></option>
                                        <?php endforeach; ?>
                                    </select>
                                </form>
                            </td>
                            <td>
                                <form method="post" action="/status/maintenances/<?= (int)$m['id'] ?>/delete" style="display:inline;" onsubmit="return confirm('Wartung löschen?');">
                                    <input type="hidden" name="_csrf" value="<?= $view->e($csrf) ?>">
                                    <button aria-label="<?= $this->te('Löschen') ?>" type="submit" class="btn btn-danger btn-sm">&times;</button>
                                </form>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
            <?php endif; ?>
        </div>
    </div>

</div>

<?php
// Wiederverwendbarer Service-Checkbox-Block für Modals
$serviceChecks = function (string $idPrefix) use ($services, $view): string {
    if (empty($services)) { return '<span style="color:var(--gray-400);">Erst Dienste anlegen.</span>'; }
    $html = '<div style="display:flex;flex-wrap:wrap;gap:.5rem;">';
    foreach ($services as $s) {
        $html .= '<label style="font-size:.85rem;display:inline-flex;align-items:center;gap:.3rem;">'
            . '<input type="checkbox" name="services[]" value="' . (int)$s['id'] . '"> ' . $view->e($s['name']) . '</label>';
    }
    return $html . '</div>';
};
?>

<!-- Modal: Dienst -->
<div id="spServiceModal" class="modal-overlay" role="dialog" aria-modal="true" style="display:none;" data-modal>
    <div class="modal-card">
        <div class="modal-header">
            <h2 id="spServiceTitle"><?= $this->te('Dienst anlegen') ?></h2>
            <button aria-label="<?= $this->te('Schließen') ?>" type="button" class="modal-close" onclick="document.getElementById('spServiceModal').style.display='none'">&times;</button>
        </div>
        <form method="post" id="spServiceForm" action="/status/services">
            <input type="hidden" name="_csrf" value="<?= $view->e($csrf) ?>">
            <div class="modal-body">
                <div class="form-group">
                    <label for="sp-svc-name"><?= $this->te('Name') ?> <span class="text-danger">*</span></label>
                    <input type="text" id="sp-svc-name" name="name" class="form-control" required placeholder="<?= $this->te('z. B. Drucksystem') ?>">
                </div>
                <div class="form-group">
                    <label for="sp-svc-desc"><?= $this->te('Beschreibung') ?></label>
                    <input type="text" id="sp-svc-desc" name="description" class="form-control">
                </div>
                <div class="form-group">
                    <label for="sp-svc-group"><?= $this->te('Gruppe') ?></label>
                    <input type="text" id="sp-svc-group" name="group_name" class="form-control" list="sp-groups" placeholder="<?= $this->te('z. B. Fachverfahren (optional)') ?>">
                    <datalist id="sp-groups"><?php foreach ($groups as $g): ?><option value="<?= $view->e($g) ?>"></option><?php endforeach; ?></datalist>
                </div>
                <div class="form-group">
                    <label for="sp-svc-status"><?= $this->te('Status') ?></label>
                    <select id="sp-svc-status" name="status" class="form-control">
                        <?php foreach (StatusManager::SERVICE_STATUS as $k => $lbl): ?>
                            <option value="<?= $view->e($k) ?>"><?= $view->e($lbl) ?></option>
                        <?php endforeach; ?>
                    </select>
                </div>
                <div class="form-group">
                    <label for="sp-svc-sort"><?= $this->te('Sortierung') ?></label>
                    <input type="number" id="sp-svc-sort" name="sort_order" class="form-control" value="0">
                </div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" onclick="document.getElementById('spServiceModal').style.display='none'"><?= $this->te('Abbrechen') ?></button>
                <button type="submit" class="btn btn-primary"><?= $this->te('Speichern') ?></button>
            </div>
        </form>
    </div>
</div>

<!-- Modal: Störung -->
<div id="spIncidentModal" class="modal-overlay" role="dialog" aria-modal="true" style="display:none;" data-modal>
    <div class="modal-card">
        <div class="modal-header">
            <h2><?= $this->te('Störung melden') ?></h2>
            <button aria-label="<?= $this->te('Schließen') ?>" type="button" class="modal-close" onclick="document.getElementById('spIncidentModal').style.display='none'">&times;</button>
        </div>
        <form method="post" action="/status/incidents">
            <input type="hidden" name="_csrf" value="<?= $view->e($csrf) ?>">
            <!-- Herkunft aus dem Monitoring; nur gesetzt, wenn der Dialog aus
                 einem Vorschlag heraus geöffnet wurde. -->
            <input type="hidden" name="monitoring_event_id" id="sp-inc-monitoring" value="">
            <div class="modal-body">
                <div class="form-group">
                    <label for="sp-inc-title"><?= $this->te('Titel') ?> <span class="text-danger">*</span></label>
                    <input type="text" id="sp-inc-title" name="title" class="form-control" required placeholder="<?= $this->te('z. B. Vivendi nicht erreichbar') ?>">
                </div>
                <div style="display:flex;gap:.5rem;">
                    <div class="form-group" style="flex:1;">
                        <label for="sp-inc-impact"><?= $this->te('Auswirkung') ?></label>
                        <select id="sp-inc-impact" name="impact" class="form-control">
                            <?php foreach (StatusManager::INCIDENT_IMPACT as $k => $lbl): ?><option value="<?= $view->e($k) ?>"><?= $view->e($lbl) ?></option><?php endforeach; ?>
                        </select>
                    </div>
                    <div class="form-group" style="flex:1;">
                        <label for="sp-inc-status"><?= $this->te('Status') ?></label>
                        <select id="sp-inc-status" name="status" class="form-control">
                            <?php foreach (StatusManager::INCIDENT_STATUS as $k => $lbl): ?><option value="<?= $view->e($k) ?>"><?= $view->e($lbl) ?></option><?php endforeach; ?>
                        </select>
                    </div>
                </div>
                <div class="form-group">
                    <label for="sp-inc-started"><?= $this->te('Beginn (optional)') ?></label>
                    <input type="datetime-local" id="sp-inc-started" name="started_at" class="form-control">
                    <small class="form-hint"><?= $this->te('Leer = jetzt. Für nachträglich dokumentierte Störungen den tatsächlichen Beginn eintragen.') ?></small>
                </div>
                <div class="form-group">
                    <label for="sp-inc-body"><?= $this->te('Erste Meldung') ?> <span class="text-danger">*</span></label>
                    <textarea id="sp-inc-body" name="body" class="form-control" rows="3" required placeholder="<?= $this->te('Was ist passiert / Stand der Dinge?') ?>"></textarea>
                </div>
                <div class="form-group">
                    <label><?= $this->te('Betroffene Dienste') ?></label>
                    <?= $serviceChecks('inc') ?>
                </div>
                <div class="form-group">
                    <label style="display:inline-flex;align-items:center;gap:.5rem;font-weight:normal;">
                        <input type="checkbox" name="apply_impact" value="1">
                        <?= $this->te('Betroffene Dienste automatisch entsprechend der Auswirkung herabstufen') ?>
                    </label>
                </div>
                <?php if (!empty($portal_enabled)): ?>
                <div class="form-group">
                    <label style="display:inline-flex;align-items:center;gap:.5rem;font-weight:normal;">
                        <input type="checkbox" name="portal_visible" value="1">
                        <?= $this->te('Im Self-Service-Portal als Hinweis anzeigen') ?>
                    </label>
                </div>
                <div class="form-group">
                    <label for="sp-inc-location"><?= $this->te('Standort (Portal-Anzeige)') ?></label>
                    <select id="sp-inc-location" name="location_id" class="form-control">
                        <option value=""><?= $this->te('Alle Standorte') ?></option>
                        <?php foreach (($locations ?? []) as $loc): ?>
                            <option value="<?= (int)$loc['id'] ?>"><?= str_repeat('&nbsp;&nbsp;', (int)($loc['_depth'] ?? 0)) . $this->e($loc['name']) ?></option>
                        <?php endforeach; ?>
                    </select>
                    <small class="form-hint"><?= $this->te('Leer = allen Portal-Nutzern anzeigen. Ein Oberstandort gilt auch f&uuml;r dessen Unterstandorte.') ?></small>
                </div>
                <div class="form-group">
                    <label for="sp-inc-ticket"><?= $this->te('Sammelticket (optional)') ?></label>
                    <input type="number" id="sp-inc-ticket" name="ticket_id" class="form-control" min="1"
                           placeholder="<?= $this->te('Ticket-Nummer, z. B. 1234') ?>">
                    <small class="text-muted"><?= $this->te('Betroffene Portal-Nutzer können sich per „Ich bin auch betroffen&ldquo; als Beobachter an dieses Ticket hängen, statt Duplikate zu melden.') ?></small>
                </div>
                <?php endif; ?>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" onclick="document.getElementById('spIncidentModal').style.display='none'"><?= $this->te('Abbrechen') ?></button>
                <button type="submit" class="btn btn-primary"><?= $this->te('Melden') ?></button>
            </div>
        </form>
    </div>
</div>

<!-- Modal: Wartung -->
<div id="spMaintModal" class="modal-overlay" role="dialog" aria-modal="true" style="display:none;" data-modal>
    <div class="modal-card">
        <div class="modal-header">
            <h2><?= $this->te('Wartung planen') ?></h2>
            <button aria-label="<?= $this->te('Schließen') ?>" type="button" class="modal-close" onclick="document.getElementById('spMaintModal').style.display='none'">&times;</button>
        </div>
        <form method="post" action="/status/maintenances">
            <input type="hidden" name="_csrf" value="<?= $view->e($csrf) ?>">
            <div class="modal-body">
                <div class="form-group">
                    <label for="sp-mnt-title"><?= $this->te('Titel') ?> <span class="text-danger">*</span></label>
                    <input type="text" id="sp-mnt-title" name="title" class="form-control" required placeholder="<?= $this->te('z. B. Wartung Sage-Server') ?>">
                </div>
                <div style="display:flex;gap:.5rem;">
                    <div class="form-group" style="flex:1;">
                        <label for="sp-mnt-start"><?= $this->te('Beginn') ?> <span class="text-danger">*</span></label>
                        <input type="datetime-local" id="sp-mnt-start" name="scheduled_start" class="form-control" required>
                    </div>
                    <div class="form-group" style="flex:1;">
                        <label for="sp-mnt-end"><?= $this->te('Ende') ?></label>
                        <input type="datetime-local" id="sp-mnt-end" name="scheduled_end" class="form-control">
                    </div>
                </div>
                <div class="form-group">
                    <label for="sp-mnt-body"><?= $this->te('Beschreibung') ?></label>
                    <textarea id="sp-mnt-body" name="body" class="form-control" rows="2"></textarea>
                </div>
                <div class="form-group">
                    <label><?= $this->te('Betroffene Dienste') ?></label>
                    <?= $serviceChecks('mnt') ?>
                </div>
                <?php if (!empty($portal_enabled)): ?>
                <div class="form-group">
                    <label style="display:inline-flex;align-items:center;gap:.5rem;font-weight:normal;">
                        <input type="checkbox" name="portal_visible" value="1">
                        <?= $this->te('Im Self-Service-Portal als Hinweis anzeigen') ?>
                    </label>
                </div>
                <div class="form-group">
                    <label for="sp-mnt-location"><?= $this->te('Standort (Portal-Anzeige)') ?></label>
                    <select id="sp-mnt-location" name="location_id" class="form-control">
                        <option value=""><?= $this->te('Alle Standorte') ?></option>
                        <?php foreach (($locations ?? []) as $loc): ?>
                            <option value="<?= (int)$loc['id'] ?>"><?= str_repeat('&nbsp;&nbsp;', (int)($loc['_depth'] ?? 0)) . $this->e($loc['name']) ?></option>
                        <?php endforeach; ?>
                    </select>
                    <small class="form-hint"><?= $this->te('Leer = allen Portal-Nutzern anzeigen. Ein Oberstandort gilt auch f&uuml;r dessen Unterstandorte.') ?></small>
                </div>
                <?php endif; ?>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" onclick="document.getElementById('spMaintModal').style.display='none'"><?= $this->te('Abbrechen') ?></button>
                <button type="submit" class="btn btn-primary"><?= $this->te('Planen') ?></button>
            </div>
        </form>
    </div>
</div>

<!-- Modal: Einbetten & teilen -->
<div id="spEmbedModal" class="modal-overlay" role="dialog" aria-modal="true" style="display:none;" data-modal>
    <div class="modal-card">
        <div class="modal-header">
            <h2><?= $this->te('Einbetten &amp; teilen') ?></h2>
            <button aria-label="<?= $this->te('Schließen') ?>" type="button" class="modal-close" onclick="document.getElementById('spEmbedModal').style.display='none'">&times;</button>
        </div>
        <div class="modal-body">
            <p><?= $this->te('Öffentliche Statusseite:') ?> <a href="/status" target="_blank" rel="noopener" class="table-link"><span id="sp-url-public"><?= $this->te('/status') ?></span></a>
               <?= $this->te('&nbsp;·&nbsp; iframe-Variante:') ?> <a href="/status/embed<?= ($status_token ?? '') !== '' ? '?token=' . $this->e($status_token) : '' ?>" target="_blank" rel="noopener" class="table-link"><?= $this->te('/status/embed') ?></a></p>
            <?php if (($status_token ?? '') !== ''): ?>
            <p class="form-hint" style="margin-top:.25rem;"><?= $this->te('Der Zugriff ist an einen Token gebunden – die Adressen oben und der Einbettungscode enthalten ihn bereits.') ?></p>
            <?php endif; ?>
            <div class="form-group" style="margin-top:.5rem;">
                <label for="sp-embed-code"><?= $this->te('iframe-Einbettungscode (passt die Höhe automatisch an)') ?></label>
                <textarea class="form-control" rows="11" readonly onclick="this.select()" id="sp-embed-code" style="font-family:monospace;font-size:.8rem;"></textarea>
                <small class="form-hint"><?= $this->te('Kompletten Code (iframe + Script) anklicken (markiert alles) und mit Strg+C übernehmen. Das Script passt die Höhe an den Inhalt an – kein Scrollen im iframe.') ?></small>
            </div>
        </div>
        <div class="modal-footer">
            <button type="button" class="btn btn-secondary" onclick="document.getElementById('spEmbedModal').style.display='none'"><?= $this->te('Schließen') ?></button>
        </div>
    </div>
</div>

<script>
// Modals: Backdrop-Klick + Escape
document.querySelectorAll('.modal-overlay[data-modal]').forEach(function (o) {
    o.addEventListener('click', function (e) { if (e.target === o) o.style.display = 'none'; });
});
document.addEventListener('keydown', function (e) {
    if (e.key === 'Escape') document.querySelectorAll('.modal-overlay[data-modal]').forEach(function (o) { o.style.display = 'none'; });
});
// Inline-Element ein-/ausblenden (z. B. Update-Bearbeiten-Formular)
function spToggle(id) {
    var el = document.getElementById(id);
    if (el) { el.style.display = (el.style.display === 'none' || !el.style.display) ? 'block' : 'none'; }
}
// Dienste-Tabelle filtern
function spFilterServices(q) {
    q = (q || '').toLowerCase();
    document.querySelectorAll('#spServiceTable tbody tr').forEach(function (tr) {
        tr.style.display = tr.textContent.toLowerCase().indexOf(q) !== -1 ? '' : 'none';
    });
}
// Dienst anlegen (Formular zuruecksetzen)
function spNewService() {
    var f = document.getElementById('spServiceForm');
    f.action = '/status/services';
    f.reset();
    document.getElementById('spServiceTitle').textContent = 'Dienst anlegen';
    document.getElementById('spServiceModal').style.display = 'flex';
}
// Dienst bearbeiten (Service-Modal wiederverwenden)
function spEditService(s) {
    var f = document.getElementById('spServiceForm');
    f.action = '/status/services/' + s.id + '/update';
    document.getElementById('spServiceTitle').textContent = 'Dienst bearbeiten';
    document.getElementById('sp-svc-name').value = s.name || '';
    document.getElementById('sp-svc-desc').value = s.description || '';
    document.getElementById('sp-svc-group').value = s.group_name || '';
    document.getElementById('sp-svc-status').value = s.status;
    document.getElementById('sp-svc-sort').value = s.sort_order || 0;
    document.getElementById('spServiceModal').style.display = 'flex';
}
// Störung melden (Dialog ohne Vorbelegung)
function spNewIncident() {
    spResetIncidentSource();
    document.getElementById('spIncidentModal').style.display = 'flex';
}
// Störung aus einem Monitoring-Vorschlag melden: Titel, Auswirkung und erste
// Meldung sind vorbelegt, betroffene Dienste bleiben offen.
function spFromMonitoring(a) {
    document.getElementById('sp-inc-monitoring').value = a.id;
    document.getElementById('sp-inc-title').value      = a.title || '';
    document.getElementById('sp-inc-body').value       = a.body || '';
    document.getElementById('sp-inc-impact').value     = a.impact || 'minor';
    document.getElementById('sp-inc-status').value     = 'investigating';
    document.getElementById('spIncidentModal').style.display = 'flex';
}
// Die Herkunft gilt nur für den Vorgang, aus dem heraus der Dialog geöffnet
// wurde – ein danach von Hand gemeldeter Vorfall trägt sie nicht mit.
function spResetIncidentSource() {
    var f = document.getElementById('sp-inc-monitoring');
    if (f) { f.value = ''; }
}
// Tabs umschalten (Dienste / Störungen / Wartungen), Zustand wird gemerkt
function spShowTab(name) {
    document.querySelectorAll('#spTabs .subnav-tab').forEach(function (b) {
        b.classList.toggle('active', b.getAttribute('data-sp-tab') === name);
    });
    document.querySelectorAll('.sp-tab-panel').forEach(function (p) {
        p.classList.toggle('active', p.id === 'sp-panel-' + name);
    });
    // Kontextabhängige Suche/Aktion in der Filter-Card ein-/ausblenden
    document.querySelectorAll('[data-sp-show]').forEach(function (el) {
        el.style.display = el.getAttribute('data-sp-show') === name ? '' : 'none';
    });
    try { localStorage.setItem('sp-active-tab', name); } catch (e) {}
}
(function () {
    document.querySelectorAll('#spTabs .subnav-tab').forEach(function (b) {
        b.addEventListener('click', function () { spShowTab(b.getAttribute('data-sp-tab')); });
    });
    var saved = 'dienste';
    try { saved = localStorage.getItem('sp-active-tab') || 'dienste'; } catch (e) {}
    if (document.getElementById('sp-panel-' + saved)) { spShowTab(saved); }
})();

// iframe-Einbettungscode bauen: iframe + Listener, der die Höhe automatisch an
// den Inhalt anpasst (die Embed-Seite sendet ihre Höhe per postMessage).
(function () {
    var origin = window.location.origin;
    // Ist der Zugriff an einen Token gebunden, gehoert er in jede Adresse –
    // sonst bliebe der eingebettete Rahmen leer.
    var query  = <?= json_encode(($status_token ?? '') !== '' ? '?token=' . $status_token : '', JSON_UNESCAPED_SLASHES) ?>;
    var code = document.getElementById('sp-embed-code');
    if (code) {
        code.value =
            '<iframe id="opennit-status" src="' + origin + '/status/embed' + query + '" style="width:100%;border:0;" scrolling="no"></iframe>\n' +
            '<script>\n' +
            'window.addEventListener("message", function (e) {\n' +
            '  if (e.origin !== "' + origin + '") return;\n' +
            '  if (e.data && e.data.type === "opennit-status-height") {\n' +
            '    var f = document.getElementById("opennit-status");\n' +
            '    if (f) f.style.height = e.data.height + "px";\n' +
            '  }\n' +
            '});\n' +
            '<\/script>';
    }
    var pub = document.getElementById('sp-url-public');
    if (pub) {
        pub.textContent = origin + '/status' + query;
        var link = pub.closest('a');
        if (link) { link.href = '/status' + query; }
    }
})();

// Dienste per Drag&Drop sortieren (nativ) und Reihenfolge speichern
(function () {
    var table = document.getElementById('spServiceTable');
    if (!table) return;
    var tbody = table.querySelector('tbody');
    if (!tbody) return;
    var dragRow = null;

    tbody.addEventListener('dragstart', function (e) {
        var cell = e.target.closest('.sp-drag-cell');
        if (!cell) { e.preventDefault(); return; }
        dragRow = cell.closest('tr');
        e.dataTransfer.effectAllowed = 'move';
        try { e.dataTransfer.setData('text/plain', dragRow.getAttribute('data-id') || ''); } catch (err) {}
        dragRow.style.opacity = '0.4';
    });

    tbody.addEventListener('dragover', function (e) {
        if (!dragRow) return;
        e.preventDefault();
        var over = e.target.closest('tr');
        if (!over || over === dragRow || over.parentNode !== tbody) return;
        var rect = over.getBoundingClientRect();
        var after = (e.clientY - rect.top) > rect.height / 2;
        tbody.insertBefore(dragRow, after ? over.nextSibling : over);
    });

    tbody.addEventListener('dragend', function () {
        if (dragRow) dragRow.style.opacity = '';
        dragRow = null;
        var token = document.querySelector('input[name="_csrf"]')?.value || '';
        var fd = new FormData();
        Array.prototype.forEach.call(tbody.querySelectorAll('tr[data-id]'), function (tr) {
            fd.append('ids[]', tr.getAttribute('data-id'));
        });
        fd.append('_csrf', token);
        fetch('/status/services/reorder', {
            method: 'POST',
            credentials: 'same-origin',
            headers: { 'X-CSRF-Token': token },
            body: fd
        }).catch(function () {});
    });
}());
</script>