clupilot/app/resources/views/livewire/pages/security/index.blade.php

169 lines
8.9 KiB
PHP

<?php
use App\Models\Certificate;
use App\Models\SecurityEvent;
use App\Models\TeamMember;
use Livewire\Attributes\Layout;
use Livewire\Volt\Component;
new #[Layout('layouts.app')] class extends Component {
public function with(): array
{
$events24h = SecurityEvent::where('occurred_at', '>=', now()->subDay())->count();
$blocked24h = SecurityEvent::where('occurred_at', '>=', now()->subDay())->where('blocked', true)->count();
$highEvents = SecurityEvent::query()
->whereIn('severity', ['high', 'critical'])
->orderByDesc('occurred_at')
->limit(8)
->get();
$feedEvents = SecurityEvent::query()
->orderByDesc('occurred_at')
->limit(12)
->get();
$certs = Certificate::query()
->with('site')
->orderBy('expires_at')
->limit(10)
->get();
$totalMembers = TeamMember::count() ?: 1;
$with2fa = TeamMember::where('two_fa_enabled', true)->count();
return [
'eventsTotal' => $events24h,
'blockedCount' => $blocked24h,
'advisories' => $highEvents,
'feedEvents' => $feedEvents,
'certs' => $certs,
'twoFaCoverage' => (int) round($with2fa / $totalMembers * 100),
'rulesActive' => 42,
'sslExpiringSoon' => Certificate::where('expires_at', '<=', now()->addDays(30))->count(),
];
}
}; ?>
<div>
<div class="clu-topbar">
<div>
<h1 class="clu-page-title">Sicherheit</h1>
<div class="clu-page-sub">{{ $blockedCount }} blockiert / 24h · {{ $advisories->count() }} advisories</div>
</div>
<span class="clu-status-chip"><span class="clu-pulse-dot"></span> firewall aktiv</span>
<div class="spacer"></div>
<button class="clu-btn-primary" style="height:36px;padding:0 14px;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M11 5l-2 7h6l-2 7"/><circle cx="12" cy="12" r="9"/></svg>
Scan starten
</button>
</div>
<div class="grid gap-[14px] mb-[18px]" style="grid-template-columns:repeat(auto-fit,minmax(220px,1fr));">
<div class="clu-metric danger">
<div class="clu-metric-row"><span class="clu-metric-label">Blockiert / 24h</span>
<span class="clu-metric-ico"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M5 5l14 14"/></svg></span>
</div>
<div class="clu-metric-value">{{ $blockedCount }}</div>
<div class="clu-metric-delta bad">↑ brute-force trend</div>
</div>
<div class="clu-metric">
<div class="clu-metric-row"><span class="clu-metric-label">Firewall Rules</span>
<span class="clu-metric-ico"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/></svg></span>
</div>
<div class="clu-metric-value">{{ $rulesActive }}</div>
<div class="clu-metric-delta">aktiv</div>
</div>
<div class="clu-metric @if($sslExpiringSoon > 0) warning @endif">
<div class="clu-metric-row"><span class="clu-metric-label">SSL läuft bald ab</span>
<span class="clu-metric-ico"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg></span>
</div>
<div class="clu-metric-value">{{ $sslExpiringSoon }}</div>
<div class="clu-metric-delta @if($sslExpiringSoon > 0) warn @endif">in den nächsten 30 Tagen</div>
</div>
<div class="clu-metric success">
<div class="clu-metric-row"><span class="clu-metric-label">2FA Coverage</span>
<span class="clu-metric-ico"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M9 12l2 2 4-4"/></svg></span>
</div>
<div class="clu-metric-value">{{ $twoFaCoverage }}<span class="unit">%</span></div>
<div class="clu-metric-delta">Team mit 2FA</div>
</div>
</div>
@if ($advisories->isNotEmpty())
<div class="clu-card mb-[18px]">
<div class="clu-card-head">
<h3>Offene Advisories</h3>
<span class="meta">{{ $advisories->count() }} aktiv</span>
</div>
<div class="clu-card-body" style="display:flex;flex-direction:column;gap:10px;">
@foreach ($advisories as $a)
<div class="flex items-start gap-[12px] p-[12px] rounded-[11px]"
style="background:rgba(219,59,59,0.06);border:1px solid rgba(219,59,59,0.18);">
<div class="clu-feed-ico error" style="flex:none;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.3 3.86l-8.55 14.83A2 2 0 0 0 3.46 22h17.08a2 2 0 0 0 1.71-3.31L13.7 3.86a2 2 0 0 0-3.4 0z"/><path d="M12 9v4M12 17h.01"/></svg>
</div>
<div class="flex-1 min-w-0">
<div class="font-semibold text-[13px]">{{ $a->message ?? ucfirst(str_replace('_', ' ', $a->type)) }}</div>
<div class="clu-feed-meta">{{ strtoupper($a->severity) }} · {{ $a->source_ip ?? '—' }} · {{ $a->occurred_at?->diffForHumans() }}</div>
</div>
<button class="clu-ghost-btn" type="button">Action</button>
</div>
@endforeach
</div>
</div>
@endif
<div class="grid gap-[14px]" style="grid-template-columns:minmax(0,1fr) minmax(280px,1fr);">
<div class="clu-card">
<div class="clu-card-head"><h3>Threat Feed</h3><span class="meta">live</span></div>
<div class="clu-card-body tight">
<div class="clu-feed">
@forelse ($feedEvents as $e)
<div class="clu-feed-row">
<div class="clu-feed-ico @if($e->severity === 'critical') error @elseif($e->severity === 'high') warning @endif">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/></svg>
</div>
<div class="clu-feed-body">
<div class="clu-feed-msg">{{ $e->message ?? ucfirst(str_replace('_', ' ', $e->type)) }}</div>
<div class="clu-feed-meta">{{ $e->source_ip ?? '—' }} · {{ $e->country ?? '—' }} · {{ $e->occurred_at?->diffForHumans() }}</div>
</div>
</div>
@empty
<div class="clu-card-body" style="text-align:center;padding:30px;color:var(--color-muted);">Keine Events.</div>
@endforelse
</div>
</div>
</div>
<div class="clu-card">
<div class="clu-card-head"><h3>SSL Zertifikate</h3><span class="meta">{{ $certs->count() }} nächste</span></div>
<div class="clu-card-body tight" style="overflow-x:auto;">
<table class="clu-table">
<thead><tr><th>Domain</th><th>Aussteller</th><th>Tage</th><th>Status</th></tr></thead>
<tbody>
@forelse ($certs as $c)
<tr>
<td>{{ $c->domain }}</td>
<td class="font-mono text-[11.5px]">{{ $c->issuer }}</td>
<td class="font-mono">{{ $c->days_to_expiry }}d</td>
<td>
@if ($c->status === 'critical')
<span class="clu-health bad">kritisch</span>
@elseif ($c->status === 'warning')
<span class="clu-health warn">bald ablauf</span>
@else
<span class="clu-health">ok</span>
@endif
</td>
</tr>
@empty
<tr><td colspan="4" style="text-align:center;padding:30px;color:var(--color-muted);">Keine Zertifikate.</td></tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>