323 lines
17 KiB
PHP
323 lines
17 KiB
PHP
<x-slot:breadcrumbParent>Dashboard</x-slot:breadcrumbParent>
|
|
<x-slot:breadcrumb>Übersicht</x-slot:breadcrumb>
|
|
|
|
<div wire:poll.30s>
|
|
|
|
{{-- Hero Banner --}}
|
|
<div class="mw-hero">
|
|
<div class="mw-hero-icon">
|
|
<svg width="18" height="18" viewBox="0 0 20 20" fill="none">
|
|
<rect x="1" y="5.5" width="18" height="4.5" rx="2" stroke="#6a7a96" stroke-width="1.3"/>
|
|
<rect x="1" y="12" width="18" height="4.5" rx="2" stroke="#6a7a96" stroke-width="1.3"/>
|
|
<circle cx="15.5" cy="7.75" r="1.1" fill="#22c55e"/>
|
|
<circle cx="15.5" cy="14.25" r="1.1" fill="#22c55e"/>
|
|
<circle cx="13" cy="7.75" r="1.1" fill="#22c55e"/>
|
|
<circle cx="13" cy="14.25" r="1.1" fill="#22c55e"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<div class="mw-hero-title">Mail Server</div>
|
|
</div>
|
|
<div class="mw-hero-divider"></div>
|
|
<div class="mw-hero-stats">
|
|
<div class="mw-hstat">
|
|
<div class="mw-hstat-n ok">{{ $domainCount }}</div>
|
|
<div class="mw-hstat-l">Domains</div>
|
|
</div>
|
|
<div class="mw-hstat">
|
|
<div class="mw-hstat-n">{{ $mailboxCount }}</div>
|
|
<div class="mw-hstat-l">Postfächer</div>
|
|
</div>
|
|
<div class="mw-hstat">
|
|
<div class="mw-hstat-n ok">{{ $servicesActive }}/{{ $servicesTotal }}</div>
|
|
<div class="mw-hstat-l">Dienste</div>
|
|
</div>
|
|
<div class="mw-hstat">
|
|
<div class="mw-hstat-n">{{ $alertCount }}</div>
|
|
<div class="mw-hstat-l">Warnungen</div>
|
|
</div>
|
|
</div>
|
|
<div class="mw-hero-host">{{ $mailHostname }}</div>
|
|
</div>
|
|
|
|
{{-- System-Ressourcen --}}
|
|
<div class="mw-section">
|
|
<span class="mw-section-title">System-Ressourcen</span>
|
|
<div class="mw-section-line"></div>
|
|
</div>
|
|
|
|
<div class="mw-metric-grid">
|
|
|
|
@php $cpuClass = $cpu > 80 ? 'mw-bar-high' : ($cpu > 50 ? 'mw-bar-mid' : 'mw-bar-low'); @endphp
|
|
<div class="mw-metric-card">
|
|
<div class="mw-mc-top">
|
|
<span class="mw-mc-label">CPU</span>
|
|
<div class="mw-mc-icon"><svg width="12" height="12" viewBox="0 0 13 13" fill="none"><rect x=".5" y=".5" width="12" height="12" rx="2" stroke="#606880" stroke-width="1.1"/><rect x="3" y="3" width="7" height="7" rx="1" stroke="#606880" stroke-width="1"/></svg></div>
|
|
</div>
|
|
<div class="mw-mc-value">{{ $cpu }}<span style="font-size:15px;font-weight:600;">%</span></div>
|
|
<div class="mw-mc-sub">{{ $cpuCores }} Cores · {{ $cpuMhz }} GHz</div>
|
|
<div class="mw-bar {{ $cpuClass }}"><div class="mw-bar-fill" style="width:{{ $cpu }}%"></div></div>
|
|
</div>
|
|
|
|
@php $ramClass = $ramPercent > 80 ? 'mw-bar-high' : ($ramPercent > 50 ? 'mw-bar-mid' : 'mw-bar-low'); @endphp
|
|
<div class="mw-metric-card">
|
|
<div class="mw-mc-top">
|
|
<span class="mw-mc-label">RAM</span>
|
|
<div class="mw-mc-icon"><svg width="12" height="12" viewBox="0 0 13 13" fill="none"><rect x=".5" y="3" width="12" height="7" rx="1.5" stroke="#606880" stroke-width="1.1"/><path d="M3.5 3V2M6.5 3V2M9.5 3V2" stroke="#606880" stroke-width="1" stroke-linecap="round"/></svg></div>
|
|
</div>
|
|
<div class="mw-mc-value">{{ $ramPercent }}<span style="font-size:15px;font-weight:600;">%</span></div>
|
|
<div class="mw-mc-sub">{{ $ramUsed }} GB / {{ $ramTotal }} GB</div>
|
|
<div class="mw-bar {{ $ramClass }}"><div class="mw-bar-fill" style="width:{{ $ramPercent }}%"></div></div>
|
|
</div>
|
|
|
|
@php
|
|
$loadVal = floatval($load1);
|
|
$loadMax = max(1, $cpuCores);
|
|
$loadPct = min(100, round($loadVal / $loadMax * 100));
|
|
$loadClass = $loadPct > 80 ? 'mw-bar-high' : ($loadPct > 50 ? 'mw-bar-mid' : 'mw-bar-low');
|
|
@endphp
|
|
<div class="mw-metric-card">
|
|
<div class="mw-mc-top">
|
|
<span class="mw-mc-label">Load</span>
|
|
<div class="mw-mc-icon"><svg width="12" height="12" viewBox="0 0 13 13" fill="none"><path d="M1 10L4 6.5l3 2 3-5 2 1.5" stroke="#606880" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
|
|
</div>
|
|
<div class="mw-mc-value" style="font-size:22px;letter-spacing:-.5px;">{{ $load1 }}</div>
|
|
<div class="mw-mc-sub">{{ $load5 }} · {{ $load15 }} (1/5/15m)</div>
|
|
<div class="mw-bar {{ $loadClass }}"><div class="mw-bar-fill" style="width:{{ $loadPct }}%"></div></div>
|
|
</div>
|
|
|
|
<div class="mw-metric-card">
|
|
<div class="mw-mc-top">
|
|
<span class="mw-mc-label">Uptime</span>
|
|
<div class="mw-mc-icon"><svg width="12" height="12" viewBox="0 0 13 13" fill="none"><circle cx="6.5" cy="6.5" r="5" stroke="#606880" stroke-width="1.1"/><path d="M6.5 3.5V6.5l2 1.5" stroke="#606880" stroke-width="1.1" stroke-linecap="round"/></svg></div>
|
|
</div>
|
|
<div class="mw-mc-value" style="font-size:20px;letter-spacing:-.5px;">{{ $uptimeDays }}d {{ $uptimeHours }}h</div>
|
|
<div class="mw-mc-sub">Stabil · kein Neustart</div>
|
|
<div class="mw-bar mw-bar-uptime"><div class="mw-bar-fill" style="width:100%"></div></div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{{-- Dienste & Schutz --}}
|
|
<div class="mw-section">
|
|
<span class="mw-section-title">Dienste & Schutz</span>
|
|
<div class="mw-section-line"></div>
|
|
</div>
|
|
|
|
<div class="mw-status-grid">
|
|
|
|
<div class="mw-status-card">
|
|
<div class="mw-sc-head">
|
|
<div class="mw-sc-icon" style="background:var(--mw-grbg);border:1px solid var(--mw-grbd);">
|
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 1.5L12.5 4v4.5c0 2.8-2.3 4.5-5.5 5C3.8 13 1.5 11.3 1.5 8.5V4L7 1.5Z" stroke="#22c55e" stroke-width="1.2" stroke-linejoin="round"/><path d="M4.5 7l2 2 3-3" stroke="#22c55e" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
</div>
|
|
<div>
|
|
<div class="mw-sc-title">WoltGuard</div>
|
|
<div class="mw-sc-sub">System-Wächter</div>
|
|
</div>
|
|
<span class="mw-sc-badge mw-badge-ok">alle Dienste OK</span>
|
|
</div>
|
|
<div class="mw-sc-body"><b>{{ $servicesActive }}/{{ $servicesTotal }}</b> Dienste aktiv</div>
|
|
</div>
|
|
|
|
<div class="mw-status-card">
|
|
<div class="mw-sc-head">
|
|
<div class="mw-sc-icon" style="background:var(--mw-bg4);border:1px solid var(--mw-b2);">
|
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M12.5 7A5.5 5.5 0 1 1 7 1.5" stroke="#606880" stroke-width="1.2" stroke-linecap="round"/><path d="M7 .5l2.5 1.5L7 3.5" stroke="#606880" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
</div>
|
|
<div>
|
|
<div class="mw-sc-title">Updates</div>
|
|
<div class="mw-sc-sub">{{ config('app.version', 'vdev') }}</div>
|
|
</div>
|
|
<span class="mw-sc-badge mw-badge-mute">aktuell</span>
|
|
</div>
|
|
<div class="mw-sc-body">System ist auf dem neuesten Stand.</div>
|
|
<div class="mw-sc-progress"><div class="mw-sc-progress-fill" style="width:100%"></div></div>
|
|
</div>
|
|
|
|
<div class="mw-status-card">
|
|
<div class="mw-sc-head">
|
|
<div class="mw-sc-icon" style="background:var(--mw-bg4);border:1px solid var(--mw-b2);">
|
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2.5 9H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-.5" stroke="#606880" stroke-width="1.2" stroke-linecap="round"/><rect x="3" y="8.5" width="8" height="5" rx="1.5" stroke="#606880" stroke-width="1.2"/></svg>
|
|
</div>
|
|
<div>
|
|
<div class="mw-sc-title">Backup</div>
|
|
<div class="mw-sc-sub" title="{{ $backup['last_at_full'] ?? '' }}">
|
|
@if($backup['last_at'])
|
|
Zuletzt {{ $backup['last_at'] }}
|
|
@elseif($backup['status'] === 'unconfigured')
|
|
Nicht konfiguriert
|
|
@elseif($backup['status'] === 'running')
|
|
Läuft gerade…
|
|
@else
|
|
Noch nicht ausgeführt
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@php
|
|
$bStatus = $backup['status'] ?? 'unknown';
|
|
$bBadge = match($bStatus) {
|
|
'ok' => ['class' => 'mw-badge-ok', 'label' => 'OK'],
|
|
'failed' => ['class' => 'mw-badge-fail', 'label' => 'Fehler'],
|
|
'running' => ['class' => 'mw-badge-info', 'label' => 'Läuft'],
|
|
'queued' => ['class' => 'mw-badge-info', 'label' => 'Warteschlange'],
|
|
'unconfigured' => ['class' => 'mw-badge-mute', 'label' => 'Nicht konfiguriert'],
|
|
'pending' => ['class' => 'mw-badge-warn', 'label' => 'Ausstehend'],
|
|
default => ['class' => 'mw-badge-warn', 'label' => 'Unbekannt'],
|
|
};
|
|
@endphp
|
|
<span class="mw-sc-badge {{ $bBadge['class'] }}">{{ $bBadge['label'] }}</span>
|
|
</div>
|
|
<div class="mw-sc-body">
|
|
@if($backup['size'])
|
|
Größe: <strong style="color:var(--mw-t2)">{{ $backup['size'] }}</strong>
|
|
@else
|
|
Größe: —
|
|
@endif
|
|
@if($backup['duration'])
|
|
· {{ $backup['duration'] }}
|
|
@endif
|
|
@if($backup['next_at'])
|
|
<br>Nächstes: <strong style="color:var(--mw-t2)">{{ $backup['next_at'] }}</strong>
|
|
@elseif($backup['status'] !== 'unconfigured' && !$backup['enabled'])
|
|
<br><span style="color:var(--mw-t4)">Zeitplan deaktiviert</span>
|
|
@endif
|
|
</div>
|
|
@if($backup['status'] === 'unconfigured')
|
|
<a href="{{ route('ui.system.settings') }}#backup" class="mw-sc-btn">Konfigurieren</a>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="mw-status-card">
|
|
<div class="mw-sc-head">
|
|
<div class="mw-sc-icon" style="background:var(--mw-bg4);border:1px solid var(--mw-b2);">
|
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2L13 12H1L7 2Z" stroke="#606880" stroke-width="1.2" stroke-linejoin="round"/><path d="M7 6.5v2.5" stroke="#606880" stroke-width="1.4" stroke-linecap="round"/><circle cx="7" cy="10.5" r=".6" fill="#606880"/></svg>
|
|
</div>
|
|
<div>
|
|
<div class="mw-sc-title">Alerts</div>
|
|
<div class="mw-sc-sub">System-Warnungen</div>
|
|
</div>
|
|
<span class="mw-sc-badge mw-badge-mute">{{ $alertCount }} offen</span>
|
|
</div>
|
|
<div class="mw-sc-body" style="font-style:italic;color:var(--mw-t4);">
|
|
{{ $alertCount === 0 ? 'Keine Warnungen.' : ($alertCount . ' aktive Warnungen.') }}
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{{-- Infrastruktur --}}
|
|
<div class="mw-section">
|
|
<span class="mw-section-title">Infrastruktur</span>
|
|
<div class="mw-section-line"></div>
|
|
</div>
|
|
|
|
<div class="mw-bottom-grid">
|
|
|
|
<div class="mw-card">
|
|
<div class="mw-card-title">Dienste & Ports</div>
|
|
@php $svcHalf = (int) ceil(count($services) / 2); @endphp
|
|
<div class="mw-svc-grid">
|
|
<div>
|
|
@foreach($services as $svc)
|
|
@if($loop->index < $svcHalf)
|
|
@php $st = $svc['status']; @endphp
|
|
<div class="mw-svc-row">
|
|
<div class="mw-svc-dot {{ $st === 'online' ? 'ok' : ($st === 'offline' ? 'off' : 'na') }}"></div>
|
|
<div class="mw-svc-name">{{ $svc['name'] }}<span class="mw-svc-type"> {{ $svc['type'] }}</span></div>
|
|
<span class="mw-svc-badge {{ $st }}">{{ $st === 'online' ? 'Online' : ($st === 'offline' ? 'Offline' : 'N/A') }}</span>
|
|
</div>
|
|
@endif
|
|
@endforeach
|
|
</div>
|
|
<div>
|
|
@foreach($services as $svc)
|
|
@if($loop->index >= $svcHalf)
|
|
@php $st = $svc['status']; @endphp
|
|
<div class="mw-svc-row">
|
|
<div class="mw-svc-dot {{ $st === 'online' ? 'ok' : ($st === 'offline' ? 'off' : 'na') }}"></div>
|
|
<div class="mw-svc-name">{{ $svc['name'] }}<span class="mw-svc-type"> {{ $svc['type'] }}</span></div>
|
|
<span class="mw-svc-badge {{ $st }}">{{ $st === 'online' ? 'Online' : ($st === 'offline' ? 'Offline' : 'N/A') }}</span>
|
|
</div>
|
|
@endif
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
<div class="mw-ports">
|
|
@foreach([25, 465, 587, 110, 143, 993, 995, 80, 443] as $port)
|
|
@php $isActive = $ports[$port] ?? false; @endphp
|
|
<span class="mw-port {{ $isActive ? 'active' : 'inactive' }}" title="{{ $isActive ? 'Lauscht' : 'Nicht aktiv' }}">:{{ $port }}</span>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mw-right-col">
|
|
|
|
<div class="mw-card">
|
|
<div class="mw-card-title">Storage</div>
|
|
<div class="mw-donut-wrap">
|
|
<svg width="68" height="68" viewBox="0 0 68 68" style="flex-shrink:0">
|
|
<circle cx="34" cy="34" r="26" fill="none" stroke="var(--mw-bg4)" stroke-width="8"/>
|
|
<circle cx="34" cy="34" r="26" fill="none" stroke="var(--mw-v)" stroke-width="8"
|
|
stroke-dasharray="{{ $diskUsedPercent * 1.6336 }} 163.36"
|
|
stroke-dashoffset="40.84" stroke-linecap="round"
|
|
transform="rotate(-90 34 34)" opacity=".7"/>
|
|
<text x="34" y="38" text-anchor="middle" font-size="11" font-weight="700" fill="#edf0fc">{{ $diskUsedPercent }}%</text>
|
|
</svg>
|
|
<div style="flex:1">
|
|
<div class="mw-legend-row">
|
|
<div class="mw-legend-dot" style="background:var(--mw-v);opacity:.7"></div>
|
|
<div class="mw-legend-label">Belegt</div>
|
|
<div class="mw-legend-val">{{ $diskUsedGb }} GB</div>
|
|
</div>
|
|
<div class="mw-legend-row">
|
|
<div class="mw-legend-dot" style="background:var(--mw-bg4);border:1px solid var(--mw-b2)"></div>
|
|
<div class="mw-legend-label">Frei</div>
|
|
<div class="mw-legend-val">{{ $diskFreeGb }} GB</div>
|
|
</div>
|
|
<div class="mw-legend-row">
|
|
<div class="mw-legend-dot" style="background:var(--mw-b2)"></div>
|
|
<div class="mw-legend-label">Gesamt</div>
|
|
<div class="mw-legend-val">{{ $diskTotalGb }} GB</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mw-card">
|
|
<div class="mw-card-title">Mail & Sicherheit</div>
|
|
<div class="mw-mini-grid">
|
|
<div class="mw-mini-card">
|
|
<div class="mw-mini-val {{ $spamBlocked > 0 ? '' : '' }}">{{ $spamBlocked }}</div>
|
|
<div class="mw-mini-label">Spam geblockt</div>
|
|
</div>
|
|
<div class="mw-mini-card">
|
|
<div class="mw-mini-val" style="{{ $spamTagged > 0 ? 'color:var(--mw-am)' : '' }}">{{ $spamTagged }}</div>
|
|
<div class="mw-mini-label">Tagged</div>
|
|
</div>
|
|
<div class="mw-mini-card">
|
|
<div class="mw-mini-val" style="color:var(--mw-gr)">{{ $hamCount }}</div>
|
|
<div class="mw-mini-label">Ham erkannt</div>
|
|
</div>
|
|
</div>
|
|
<div style="margin-top:10px;padding-top:10px;border-top:1px solid var(--mw-b1);display:flex;gap:10px;align-items:center;flex-wrap:wrap;">
|
|
<a href="{{ route('ui.mail.queues.index') }}" style="display:flex;align-items:center;gap:5px;font-size:11.5px;color:var(--mw-t3);text-decoration:none;" class="mw-sec-link">
|
|
<svg width="11" height="11" viewBox="0 0 14 14" fill="none"><path d="M1 4h12M4 4V2h6v2M4 12V7m6 5V7" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>
|
|
Queue:
|
|
<span style="{{ $queueDeferred > 0 ? 'color:var(--mw-am);font-weight:600;' : 'color:var(--mw-t2);' }}">{{ $queueTotal }} {{ $queueDeferred > 0 ? "({$queueDeferred} def.)" : '' }}</span>
|
|
</a>
|
|
<span style="color:var(--mw-b2);">|</span>
|
|
<a href="{{ route('ui.mail.quarantine.index') }}" style="display:flex;align-items:center;gap:5px;font-size:11.5px;color:var(--mw-t3);text-decoration:none;" class="mw-sec-link">
|
|
<svg width="11" height="11" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="5.5" stroke="currentColor" stroke-width="1.2"/><path d="M7 4v4l2.5 2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>
|
|
Rspamd: <span style="color:var(--mw-t2);">{{ $spamRejected }} reject</span>
|
|
</a>
|
|
<span style="color:var(--mw-b2);">|</span>
|
|
<span style="font-size:11px;color:var(--mw-t4);">{{ $clamVer }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|