admin: Betriebsüberwachung im Backend (Queue-Worker & Deployment-Checks)
Neue Reports-Seite (settings:manage) zeigt den Queue-Zustand (ältester wartender Job, Worker aktiv/gestoppt, fehlgeschlagene Jobs) und Deployment-Checks als Ampel: Permissions ausgerollt, APP_DEBUG, Session-Secure-Cookie, ausstehende Medien-Privatisierung. Damit ist die für Magic-Link-Login und fristrelevante Mails kritische Queue-Abhängigkeit aus dem Admin sichtbar. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
22975e762e
commit
573e4ca31c
3 changed files with 543 additions and 0 deletions
146
resources/views/livewire/admin/reports/system-health.blade.php
Normal file
146
resources/views/livewire/admin/reports/system-health.blade.php
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
|
||||
use App\Services\Admin\AdminSystemHealth;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\Title;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new #[Layout('components.layouts.app'), Title('Betriebsüberwachung')] class extends Component
|
||||
{
|
||||
public function mount(): void
|
||||
{
|
||||
$this->authorize('settings:manage');
|
||||
}
|
||||
|
||||
public function refresh(): void
|
||||
{
|
||||
// Löst nur ein Re-Render aus; snapshot() wird in with() neu berechnet.
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function with(AdminSystemHealth $health): array
|
||||
{
|
||||
return [
|
||||
'health' => $health->snapshot(),
|
||||
];
|
||||
}
|
||||
}; ?>
|
||||
|
||||
@php
|
||||
$statusMeta = [
|
||||
'ok' => ['color' => 'green', 'label' => __('OK'), 'variant' => 'ok'],
|
||||
'warn' => ['color' => 'amber', 'label' => __('Achtung'), 'variant' => 'warn'],
|
||||
'critical' => ['color' => 'red', 'label' => __('Kritisch'), 'variant' => 'warn'],
|
||||
];
|
||||
$overall = $statusMeta[$health['overall']] ?? $statusMeta['warn'];
|
||||
$queue = $health['queue'];
|
||||
@endphp
|
||||
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<x-portal.page-header
|
||||
:badge="__('Admin Backend')"
|
||||
:eyebrow="__('Administration · Reports')"
|
||||
:title="__('Betriebsüberwachung')"
|
||||
:description="__('Zustand der Queue (Magic-Link-Login & fristrelevante Mails) sowie Deployment-Checks.')"
|
||||
>
|
||||
<x-slot:actions>
|
||||
<flux:badge :color="$overall['color']" size="lg">{{ __('Gesamtstatus') }}: {{ $overall['label'] }}</flux:badge>
|
||||
<flux:button size="sm" variant="filled" icon="arrow-path" wire:click="refresh">
|
||||
{{ __('Aktualisieren') }}
|
||||
</flux:button>
|
||||
</x-slot:actions>
|
||||
</x-portal.page-header>
|
||||
|
||||
<p class="text-sm text-[color:var(--color-text-muted)]">
|
||||
{{ __('Stand') }}: {{ $health['generated_at']->format('d.m.Y H:i:s') }}
|
||||
</p>
|
||||
|
||||
{{-- ============== QUEUE-KPIs ============== --}}
|
||||
<section class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
<x-portal.stat-card :variant="$queue['status'] === 'ok' ? 'ok' : 'warn'" :label="__('Queue-Worker')"
|
||||
:value="$queue['supported'] ? ($queue['worker_running'] ? __('Aktiv') : __('Gestoppt?')) : __('n/a')">
|
||||
<x-slot:meta>{{ __('Treiber') }}: {{ $queue['driver'] }}</x-slot:meta>
|
||||
<x-slot:trend>
|
||||
@if ($queue['oldest_pending_seconds'] !== null)
|
||||
{{ __('ältester Job') }}: {{ $queue['oldest_pending_seconds'] }}s
|
||||
@else
|
||||
{{ __('keine Wartejobs') }}
|
||||
@endif
|
||||
</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
|
||||
<x-portal.stat-card variant="primary" :label="__('Wartende Jobs')" :value="number_format($queue['pending'])">
|
||||
<x-slot:meta>{{ __('verzögert') }}: {{ number_format($queue['delayed']) }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('in Verarbeitung') }}: {{ number_format($queue['reserved']) }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
|
||||
<x-portal.stat-card :variant="$queue['failed_recent'] > 0 ? 'warn' : 'muted'" :label="__('Fehlgeschlagen (24h)')"
|
||||
:value="number_format($queue['failed_recent'])">
|
||||
<x-slot:meta>{{ __('gesamt') }}: {{ number_format($queue['failed_total']) }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('failed_jobs') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
|
||||
<x-portal.stat-card :variant="$overall['variant']" :label="__('Gesamtstatus')" :value="$overall['label']">
|
||||
<x-slot:meta>{{ __('Betrieb') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('Queue + Deployment') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
</section>
|
||||
|
||||
{{-- ============== DEPLOYMENT-CHECKS ============== --}}
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Deployment- & Konfigurations-Checks') }}</span>
|
||||
</div>
|
||||
<div class="divide-y divide-[color:var(--color-bg-rule)]">
|
||||
@foreach ($health['checks'] as $check)
|
||||
@php $meta = $statusMeta[$check['status']] ?? $statusMeta['warn']; @endphp
|
||||
<div class="flex items-start justify-between gap-4 p-4">
|
||||
<div class="min-w-0">
|
||||
<p class="font-medium">{{ $check['label'] }}</p>
|
||||
<p class="text-sm text-[color:var(--color-text-muted)] break-words">{{ $check['detail'] }}</p>
|
||||
</div>
|
||||
<flux:badge :color="$meta['color']" size="sm" class="shrink-0">{{ $meta['label'] }}</flux:badge>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</article>
|
||||
|
||||
{{-- ============== FEHLGESCHLAGENE JOBS ============== --}}
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Zuletzt fehlgeschlagene Jobs') }}</span>
|
||||
<span class="badge hub">{{ count($queue['recent_failures']) }}</span>
|
||||
</div>
|
||||
@if (count($queue['recent_failures']) === 0)
|
||||
<div class="p-5 text-sm text-[color:var(--color-text-muted)]">
|
||||
{{ __('Keine fehlgeschlagenen Jobs.') }}
|
||||
</div>
|
||||
@else
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="text-left text-[color:var(--color-text-muted)]">
|
||||
<th class="p-3">{{ __('Zeitpunkt') }}</th>
|
||||
<th class="p-3">{{ __('Queue') }}</th>
|
||||
<th class="p-3">{{ __('Job') }}</th>
|
||||
<th class="p-3">{{ __('Fehler') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-[color:var(--color-bg-rule)]">
|
||||
@foreach ($queue['recent_failures'] as $failure)
|
||||
<tr>
|
||||
<td class="p-3 whitespace-nowrap">{{ $failure['failed_at']->format('d.m. H:i') }}</td>
|
||||
<td class="p-3 whitespace-nowrap">{{ $failure['queue'] }}</td>
|
||||
<td class="p-3">{{ $failure['job'] }}</td>
|
||||
<td class="p-3 text-[color:var(--color-text-muted)] break-words">{{ $failure['error'] }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</article>
|
||||
</div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue