Quelltext des aktuellen Standes
<?php $this->layout('layouts/main', ['page_title' => $page_title, 'current_route' => $current_route, 'full_width' => true]);
$weak = $weak ?? []; $reusedGroups = $reused_groups ?? []; $old = $old ?? []; $expiring = $expiring ?? [];
$total = $total ?? 0; $hibpEnabled = $hibp_enabled ?? false; $isVaultAdmin = $is_vault_admin ?? false;
$reusedCount = array_sum(array_map('count', $reusedGroups));
?>
<style>
a, a:hover { text-decoration:none; }
.vh-wrap { max-width:1000px; margin:0 auto; padding:1rem 1.5rem 3rem; }
.vh-list { margin:0; padding:0; list-style:none; }
.vh-list li { padding:0.45rem 0.2rem; border-bottom:1px solid var(--gray-100); font-size:0.85rem; display:flex; gap:0.6rem; align-items:center; }
.vh-scope { font-size:0.7rem; color:var(--gray-400); background:var(--gray-100); border-radius:3px; padding:0.1rem 0.35rem; flex-shrink:0; }
.vh-group { border:1px solid var(--amber-200); background:var(--amber-50); border-radius:0.5rem; padding:0.5rem 0.75rem; margin-bottom:0.5rem; font-size:0.85rem; }
</style>
<div class="content-header">
<div>
<h1><?= $this->te('Passwort-Gesundheit') ?></h1>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
<li class="breadcrumb-item"><a href="/vault"><?= $this->te('Passwörter') ?></a></li>
<li class="breadcrumb-item active"><?= $this->te('Gesundheit') ?></li>
</ol>
</div>
<div class="content-header-actions">
<a href="/vault" class="btn btn-default btn-sm"><i class="fas fa-arrow-left"></i> <?= $this->te('Zurück zum Tresor') ?></a>
</div>
</div>
<div class="vh-wrap">
<div class="kpi-row">
<div class="kpi-col"><div class="small-box bg-info"><div class="inner"><h3><?= (int)$total ?></h3><p><?= $this->te('Einträge geprüft') ?></p></div><div class="icon"><i class="fas fa-key"></i></div></div></div>
<div class="kpi-col"><div class="small-box <?= count($weak) ? 'bg-danger' : 'bg-success' ?>"><div class="inner"><h3><?= count($weak) ?></h3><p><?= $this->te('Schwache Passwörter') ?></p></div><div class="icon"><i class="fas fa-unlock"></i></div></div></div>
<div class="kpi-col"><div class="small-box <?= $reusedCount ? 'bg-warning' : 'bg-success' ?>"><div class="inner"><h3><?= (int)$reusedCount ?></h3><p><?= $this->te('Mehrfach verwendet') ?></p></div><div class="icon"><i class="fas fa-clone"></i></div></div></div>
<div class="kpi-col"><div class="small-box <?= count($old) ? 'bg-warning' : 'bg-success' ?>"><div class="inner"><h3><?= count($old) ?></h3><p><?= $this->te('Älter als 1 Jahr') ?></p></div><div class="icon"><i class="fas fa-hourglass-half"></i></div></div></div>
<div class="kpi-col"><div class="small-box <?= count($expiring) ? 'bg-danger' : 'bg-success' ?>"><div class="inner"><h3><?= count($expiring) ?></h3><p><?= $this->te('Abgelaufen / läuft ab') ?></p></div><div class="icon"><i class="fas fa-calendar-times"></i></div></div></div>
</div>
<div class="detail-card">
<div class="detail-card-header"><span class="detail-card-title"><i class="fas fa-shield-alt"></i> <?= $this->te('Leak-Prüfung (Have I Been Pwned)') ?></span></div>
<div class="detail-card-body">
<p style="font-size:0.83rem;color:var(--gray-500);margin:0 0 0.7rem;">
<?= $this->te('Prüft anonymisiert (k-Anonymity: es verlassen nur die ersten 5 Zeichen des SHA-1-Hashes das System), ob Passwörter in bekannten Datenlecks auftauchen.') ?>
</p>
<?php if ($hibpEnabled): ?>
<button type="button" class="btn btn-primary btn-sm" id="hibpBtn" data-act="vhRunHibp"><i class="fas fa-search"></i> <?= $this->te('Jetzt prüfen') ?></button>
<div id="hibpResult" style="margin-top:0.8rem;"></div>
<?php else: ?>
<p style="font-size:0.83rem;color:var(--amber-800);background:var(--amber-100);border:1px solid var(--amber-300);border-radius:0.4rem;padding:0.5rem 0.75rem;">
<?= $this->te('Die Leak-Prüfung ist deaktiviert (es würde eine externe Anfrage an api.pwnedpasswords.com gestellt).') ?>
</p>
<?php endif; ?>
<?php if ($isVaultAdmin): ?>
<form method="POST" action="/vault/health/hibp-setting" style="margin-top:0.8rem;">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<input type="hidden" name="enabled" value="<?= $hibpEnabled ? '0' : '1' ?>">
<button type="submit" class="btn btn-default btn-xs">
<i class="fas fa-cog"></i> <?= $this->t('Leak-Prüfung') ?> <?= $hibpEnabled ? 'deaktivieren' : 'aktivieren' ?>
</button>
</form>
<?php endif; ?>
</div>
</div>
<?php
$sections = [
['Schwache Passwörter (kurz oder zu wenige Zeichenklassen)', $weak, 'fa-unlock'],
['Älter als 1 Jahr (seitdem nicht geändert)', $old, 'fa-hourglass-half'],
['Abgelaufen oder läuft in 14 Tagen ab', $expiring, 'fa-calendar-times'],
];
foreach ($sections as [$label, $list, $icon]): if (empty($list)) continue; ?>
<div class="detail-card">
<div class="detail-card-header"><span class="detail-card-title"><i class="fas <?= $icon ?>"></i> <?= $this->e($label) ?> (<?= count($list) ?>)</span></div>
<div class="detail-card-body">
<ul class="vh-list">
<?php foreach ($list as $e): ?>
<li>
<span class="vh-scope"><?= $this->e($e['_scope'] ?? '') ?></span>
<span><?= $this->e($e['title'] ?? '') ?></span>
<?php if (!empty($e['expires_at'])): ?><span style="color:var(--gray-400);font-size:0.75rem;"><?= $this->te('(Ablauf: {datum})', ['datum' => date('d.m.Y', strtotime((string)$e['expires_at']))]) ?></span><?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
</div>
</div>
<?php endforeach; ?>
<?php if (!empty($reusedGroups)): ?>
<div class="detail-card">
<div class="detail-card-header"><span class="detail-card-title"><i class="fas fa-clone"></i> <?= $this->te('Mehrfach verwendete Passwörter ({anzahl} Gruppen)', ['anzahl' => count($reusedGroups)]) ?></span></div>
<div class="detail-card-body">
<?php foreach ($reusedGroups as $group): ?>
<div class="vh-group">
<strong><?= $this->te('Gleiches Passwort bei {anzahl} Einträgen:', ['anzahl' => count($group)]) ?></strong>
<?= $this->e(implode(', ', array_map(fn($e) => ($e['title'] ?? '') . ' (' . ($e['_scope'] ?? '') . ')', $group))) ?>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
<script<?= $this->scriptNonce() ?>>
function vhRunHibp() {
var btn = document.getElementById('hibpBtn');
var out = document.getElementById('hibpResult');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Prüfe…';
fetch('/vault/health/hibp', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' },
body: '_csrf=' + encodeURIComponent(<?= json_encode($this->csrf(), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP) ?>)
})
.then(function (r) { return r.json(); })
.then(function (data) {
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-search"></i> Jetzt prüfen';
if (!data.success) { out.textContent = data.message || 'Fehler.'; return; }
if (!data.leaked.length) {
out.innerHTML = '<div style="color:#15803d;font-size:0.85rem;">✓ Keines der ' + data.checked + ' geprüften Passwörter wurde in bekannten Leaks gefunden.</div>';
return;
}
var html = '<div style="color:#b91c1c;font-size:0.85rem;font-weight:600;margin-bottom:0.4rem;">' + data.leaked.length + ' Passwort/Passwörter in bekannten Leaks gefunden – bitte umgehend ändern:</div><ul class="vh-list">';
data.leaked.forEach(function (l) {
var t = document.createElement('span'); t.textContent = l.titles.join(', ');
html += '<li><span class="vh-scope">' + l.count.toLocaleString('de-DE') + '× geleakt</span> ' + t.innerHTML + '</li>';
});
out.innerHTML = html + '</ul>';
})
.catch(function () {
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-search"></i> Jetzt prüfen';
out.textContent = 'Verbindungsfehler.';
});
}
</script>
<?= $this->render('vault/_autolock', ['vault_idle_ttl' => $vault_idle_ttl ?? null]) ?>