- 83 Komponenten: Livewire\Volt\Component → Livewire\Component
- 73 Routen (admin, auth, customer): Volt::route() → Route::livewire()
- 88 Testdateien: Volt::test/actingAs → Livewire::test/actingAs
- VoltServiceProvider gelöscht; composer remove livewire/volt
- config/livewire.php auf v4-Schlüssel (component_layout, component_placeholder,
component_locations, make_command: SFC ohne ⚡). Der alte Schlüssel layout war
wirkungslos; alle gerouteten Komponenten setzen ihr Layout selbst.
- CLAUDE.md, AGENTS.md, boost.json, .cursorrules, Skill volt-development
- Roadmap: erledigt; Larastan erfasst SFCs weiterhin nicht (Blade-Dateien)
Suite 2.325 grün, einer übersprungen; Larastan und Pint ohne Meldung;
route:cache und view:cache laufen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
147 lines
6.5 KiB
PHP
147 lines
6.5 KiB
PHP
<?php
|
|
|
|
use App\Services\Admin\AdminSystemHealth;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Attributes\Title;
|
|
use Livewire\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') }}
|
|
· {{ __('Version') }}: {{ config('app.version') ?? __('unbekannt (keine RELEASE-Datei)') }}
|
|
</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>
|