OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

detail.php

14 KB
<?php
$this->layout('layouts/main', [
    'page_title'    => $page_title,
    'current_route' => $current_route,
    'full_width'    => true,
    'extra_css'     => ['/assets/css/ssl.css?v=' . filemtime(dirname(__DIR__, 2) . '/public/assets/css/ssl.css')],
    'extra_js'      => ['/assets/js/ssl.js?v=' . filemtime(dirname(__DIR__, 2) . '/public/assets/js/ssl.js')],
]);

$e          = $entry;
$history    = $history ?? [];
$users      = $users ?? [];
$transports = $transports ?? ['tls'];
$canManage  = $can_manage ?? false;
$view       = $this;

$g   = $e['grade'] ?? null;
$gCls = in_array($g, ['A', 'B', 'C', 'F'], true) ? $g : 'na';
$days = $e['cert_days_left'];
$transportLabel = ['tls' => 'TLS (direkt)', 'smtp' => 'SMTP STARTTLS', 'imap' => 'IMAP STARTTLS', 'pop3' => 'POP3 STARTTLS'];

// Sparkline: Verlauf chronologisch, Balkenhöhe ~ Restlaufzeit
$chrono = array_reverse($history);
$maxDays = 1;
foreach ($chrono as $h) { $maxDays = max($maxDays, (int)($h['days_left'] ?? 0)); }

$yesno = function ($v, string $okText, string $badText) use ($view): string {
    if ($v === null) { return '<span class="ssl-muted">—</span>'; }
    $ok = (int)$v === 1;
    return '<span class="status-pill ' . ($ok ? 'status-pill--success' : 'status-pill--danger') . '">' . ($ok ? $okText : $badText) . '</span>';
};
?>

<div class="ssl-wrap">
    <div class="content-header">
        <div>
            <h1><?= $view->e($e['label'] ?: $e['host']) ?></h1>
            <ol class="breadcrumb">
                <li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
                <li class="breadcrumb-item"><a href="/ssl"><?= $this->te('SSL-Zertifikate') ?></a></li>
                <li class="breadcrumb-item active"><?= $view->e($e['host']) ?></li>
            </ol>
        </div>
        <div class="content-header-actions">
            <button class="btn btn-default btn-sm ssl-check-btn" data-id="<?= (int)$e['id'] ?>" title="<?= $this->te('Jetzt prüfen') ?>"><?= $this->te('&#8635; Prüfen') ?></button>
            <?php if ($canManage): ?>
            <button class="btn btn-default btn-sm" type="button" onclick="document.getElementById('sslEditModal').classList.add('open')"><?= $this->te('Bearbeiten') ?></button>
            <button class="btn btn-default btn-sm ssl-delete-btn" data-id="<?= (int)$e['id'] ?>" data-host="<?= $view->e($e['host']) ?>" data-redirect="/ssl"><?= $this->te('Entfernen') ?></button>
            <?php endif; ?>
        </div>
    </div>

    <div class="page-body">
        <?php if (!empty($e['check_error'])): ?>
            <div class="ssl-card" style="border-left:4px solid var(--red-600);">
                <strong style="color:var(--red-700);"><?= $this->te('Letzte Prüfung fehlgeschlagen:') ?></strong>
                <span><?= $view->e($e['check_error']) ?></span>
            </div>
        <?php endif; ?>

        <div class="ssl-detail-grid">
            <!-- Linke Spalte: Bewertung -->
            <div>
                <div class="ssl-card" style="text-align:center;">
                    <div class="ssl-grade-big ssl-grade-<?= $gCls ?>" style="margin:0 auto 0.75rem;"><?= $view->e($g ?: '–') ?></div>
                    <div style="font-size:0.8rem;color:var(--gray-500);"><?= $this->te('Gesamtbewertung') ?></div>
                    <?php if ($days !== null): ?>
                        <div style="margin-top:0.75rem;font-size:1.4rem;font-weight:700;<?= $days <= 30 ? 'color:var(--red-700);' : 'color:var(--gray-900);' ?>"><?= (int)$days ?> Tage</div>
                        <div style="font-size:0.8rem;color:var(--gray-500);">bis <?= $e['cert_valid_to'] ? date('d.m.Y', strtotime($e['cert_valid_to'])) : '—' ?></div>
                    <?php endif; ?>
                </div>

                <div class="ssl-card">
                    <h3><?= $this->te('Sicherheit') ?></h3>
                    <dl class="ssl-kv">
                        <dt><?= $this->te('Vertrauenskette') ?></dt><dd><?= $yesno($e['trust_valid'] ?? null, '✓ Vertrauenswürdig', '✗ Nicht vertrauenswürdig') ?>
                            <?php if (!empty($e['trust_error'])): ?><div class="ssl-host-sub"><?= $view->e($e['trust_error']) ?></div><?php endif; ?></dd>
                        <dt><?= $this->te('Hostname-Match') ?></dt><dd><?= $yesno($e['hostname_match'] ?? null, '✓ Passt', '✗ Mismatch') ?></dd>
                        <dt><?= $this->te('Selbstsigniert') ?></dt><dd><?= ($e['is_self_signed'] ?? null) === null ? '<span class="ssl-muted">—</span>' : ((int)$e['is_self_signed'] === 1 ? 'Ja' : 'Nein') ?></dd>
                        <dt><?= $this->te('TLS-Version') ?></dt><dd><?= $view->e($e['tls_version'] ?: '—') ?></dd>
                        <dt><?= $this->te('Cipher') ?></dt><dd style="word-break:break-all;"><?= $view->e($e['tls_cipher'] ?: '—') ?></dd>
                    </dl>
                </div>

                <div class="ssl-card">
                    <h3><?= $this->te('Verwaltung') ?></h3>
                    <dl class="ssl-kv">
                        <dt><?= $this->te('Owner') ?></dt><dd><?= $view->e($e['owner_name'] ?: '—') ?></dd>
                        <dt><?= $this->te('Tags') ?></dt><dd><?= $view->e($e['tags'] ?: '—') ?></dd>
                        <dt><?= $this->te('Warnschwelle') ?></dt><dd><?= $e['warn_days'] ? (int)$e['warn_days'] . ' Tage' : 'Standard (30/14/7/1)' ?></dd>
                        <dt><?= $this->te('Transport') ?></dt><dd><?= $view->e($transportLabel[$e['transport'] ?? 'tls'] ?? 'TLS') ?></dd>
                        <dt><?= $this->te('Angelegt von') ?></dt><dd><?= $view->e($e['creator_name'] ?: '—') ?></dd>
                    </dl>
                </div>
            </div>

            <!-- Rechte Spalte: Zertifikat + Verlauf -->
            <div>
                <div class="ssl-card">
                    <h3><?= $this->te('Zertifikat') ?></h3>
                    <dl class="ssl-kv">
                        <dt><?= $this->te('Common Name') ?></dt><dd><?= $view->e($e['cert_cn'] ?: '—') ?><?= !empty($e['cert_is_wildcard']) ? ' <span class="status-pill status-pill--purple">Wildcard</span>' : '' ?></dd>
                        <dt><?= $this->te('Organisation') ?></dt><dd><?= $view->e($e['cert_org'] ?: '—') ?></dd>
                        <dt><?= $this->te('Aussteller') ?></dt><dd><?= $view->e($e['cert_issuer_cn'] ?: '—') ?><?= $e['cert_issuer_org'] ? ' <span class="ssl-host-sub">(' . $view->e($e['cert_issuer_org']) . ')</span>' : '' ?></dd>
                        <dt><?= $this->te('Gültig von – bis') ?></dt><dd><?= $e['cert_valid_from'] ? date('d.m.Y', strtotime($e['cert_valid_from'])) : '—' ?> – <?= $e['cert_valid_to'] ? date('d.m.Y', strtotime($e['cert_valid_to'])) : '—' ?></dd>
                        <dt><?= $this->te('Schlüssel') ?></dt><dd><?= $view->e($e['cert_key_type'] ?: '—') ?><?= $e['cert_key_bits'] ? ' · ' . (int)$e['cert_key_bits'] . ' bit' : '' ?></dd>
                        <dt><?= $this->te('Signatur') ?></dt><dd><?= $view->e($e['cert_sig_algo'] ?: '—') ?></dd>
                        <dt><?= $this->te('Seriennummer') ?></dt><dd style="word-break:break-all;font-family:ui-monospace,monospace;font-size:0.8rem;"><?= $view->e($e['cert_serial'] ?: '—') ?></dd>
                        <dt><?= $this->te('Chain-Tiefe') ?></dt><dd><?= $e['cert_chain_depth'] !== null ? (int)$e['cert_chain_depth'] : '—' ?></dd>
                        <?php if (!empty($e['cert_sans'])): ?>
                            <dt><?= $this->te('SANs') ?></dt><dd class="ssl-sans"><?= $view->e($e['cert_sans']) ?></dd>
                        <?php endif; ?>
                        <?php if (!empty($e['cert_ocsp_url'])): ?><dt><?= $this->t('OCSP') ?></dt><dd style="word-break:break-all;"><?= $view->e($e['cert_ocsp_url']) ?></dd><?php endif; ?>
                        <?php if (!empty($e['cert_crl_url'])): ?><dt>CRL</dt><dd style="word-break:break-all;"><?= $view->e($e['cert_crl_url']) ?></dd><?php endif; ?>
                    </dl>
                </div>

                <div class="ssl-card">
                    <h3><?= $this->t('Verlauf') ?> (<?= count($history) ?> Prüfungen)</h3>
                    <?php if (empty($history)): ?>
                        <p class="ssl-muted" style="font-size:0.85rem;"><?= $this->te('Noch keine Historie.') ?></p>
                    <?php else: ?>
                        <div class="ssl-spark">
                            <?php foreach ($chrono as $h):
                                $d = $h['days_left'];
                                if (!$h['ok'] || $d === null) { $cls = 'err'; $ht = 8; }
                                else {
                                    $cls = (int)$d <= 14 ? 'crit' : ((int)$d <= 30 ? 'warn' : '');
                                    $ht = max(4, (int)round(((int)$d / $maxDays) * 100));
                                }
                            ?>
                                <div class="ssl-spark-bar <?= $cls ?>" style="height:<?= $ht ?>%;" title="<?= date('d.m.Y H:i', strtotime($h['checked_at'])) ?> · <?= $h['ok'] ? ((int)($h['days_left'] ?? 0) . ' Tage') : 'Fehler' ?>"></div>
                            <?php endforeach; ?>
                        </div>
                        <table class="ssl-hist-table" style="margin-top:0.75rem;">
                            <thead><tr><th scope="col"><?= $this->te('Zeitpunkt') ?></th><th scope="col"><?= $this->te('Status') ?></th><th scope="col"><?= $this->te('Tage') ?></th><th scope="col"><?= $this->te('Grade') ?></th><th scope="col"><?= $this->t('TLS') ?></th></tr></thead>
                            <tbody>
                            <?php foreach (array_slice($history, 0, 20) as $h): ?>
                                <tr>
                                    <td><?= date('d.m.Y H:i', strtotime($h['checked_at'])) ?></td>
                                    <td><?= $h['ok'] ? '<span class="status-pill status-pill--success">OK</span>' : '<span class="status-pill status-pill--danger" title="' . $view->e((string)$h['error']) . '">Fehler</span>' ?></td>
                                    <td><?= $h['days_left'] !== null ? (int)$h['days_left'] : '—' ?></td>
                                    <td><?= $view->e($h['grade'] ?: '—') ?></td>
                                    <td><?= $view->e($h['tls_version'] ?: '—') ?></td>
                                </tr>
                            <?php endforeach; ?>
                            </tbody>
                        </table>
                    <?php endif; ?>
                </div>
            </div>
        </div>
    </div>
</div>

<?php if ($canManage): ?>
<!-- Bearbeiten-Modal -->
<div class="ssl-modal-overlay" id="sslEditModal">
    <div class="ssl-modal">
        <div class="ssl-modal-header">
            <h2 class="ssl-modal-title"><?= $this->te('Eintrag bearbeiten') ?></h2>
            <button aria-label="<?= $this->te('Schließen') ?>" class="ssl-modal-close" type="button" onclick="document.getElementById('sslEditModal').classList.remove('open')">&#215;</button>
        </div>
        <div class="ssl-modal-body">
            <form method="POST" action="/ssl/entries/<?= (int)$e['id'] ?>/update">
                <input type="hidden" name="_csrf" value="<?= $view->e($this->csrf()) ?>">
                <div class="ssl-field-row">
                    <div class="ssl-field"><label for="f-7f48-host"><?= $this->te('Host') ?></label><input id="f-7f48-host" type="text" name="host" value="<?= $view->e($e['host']) ?>" required></div>
                    <div class="ssl-field" style="flex:0 0 90px;"><label for="f-7f48-port"><?= $this->te('Port') ?></label><input id="f-7f48-port" type="number" name="port" value="<?= (int)$e['port'] ?>" min="1" max="65535"></div>
                </div>
                <div class="ssl-field-row">
                    <div class="ssl-field"><label for="f-7f48-label"><?= $this->te('Bezeichnung') ?></label><input id="f-7f48-label" type="text" name="label" value="<?= $view->e($e['label']) ?>"></div>
                    <div class="ssl-field">
                        <label for="f-7f48-transport"><?= $this->te('Transport') ?></label>
                        <select id="f-7f48-transport" name="transport">
                            <?php foreach ($transports as $tr): ?>
                                <option value="<?= $view->e($tr) ?>" <?= ($e['transport'] ?? 'tls') === $tr ? 'selected' : '' ?>><?= $view->e(strtoupper($tr)) ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                </div>
                <div class="ssl-field">
                    <label for="f-7f48-owner_user_id"><?= $this->te('Owner') ?></label>
                    <select id="f-7f48-owner_user_id" name="owner_user_id">
                        <option value=""><?= $this->te('— kein Owner —') ?></option>
                        <?php foreach ($users as $u): ?>
                            <option value="<?= (int)$u['id'] ?>" <?= (int)($e['owner_user_id'] ?? 0) === (int)$u['id'] ? 'selected' : '' ?>><?= $view->e($u['display_name']) ?></option>
                        <?php endforeach; ?>
                    </select>
                </div>
                <div class="ssl-field-row">
                    <div class="ssl-field"><label for="f-7f48-tags"><?= $this->te('Tags (Komma)') ?></label><input id="f-7f48-tags" type="text" name="tags" value="<?= $view->e($e['tags'] ?? '') ?>"></div>
                    <div class="ssl-field" style="flex:0 0 130px;"><label><?= $this->te('Warn-Tage') ?></label><input type="number" name="warn_days" value="<?= $e['warn_days'] ? (int)$e['warn_days'] : '' ?>" min="1" placeholder="<?= $this->te('Standard') ?>"></div>
                </div>
                <button type="submit" class="btn btn-primary"><?= $this->te('Speichern &amp; neu prüfen') ?></button>
            </form>
        </div>
    </div>
</div>
<?php endif; ?>