@php $statusStyles = [ 'running' => ['bg' => '#1e3a5f', 'color' => '#60a5fa', 'label' => 'RUN'], 'ok' => ['bg' => '#14532d', 'color' => '#86efac', 'label' => 'OK'], 'error' => ['bg' => '#7f1d1d', 'color' => '#fca5a5', 'label' => 'ERR'], 'warn' => ['bg' => '#78350f', 'color' => '#fcd34d', 'label' => 'WARN'], 'info' => ['bg' => '#1e3a5f', 'color' => '#93c5fd', 'label' => 'INFO'], ]; $groupLabels = [ 'last_5_min' => 'LAST 5 MINUTES', 'earlier_today' => 'EARLIER TODAY', 'yesterday' => 'YESTERDAY', 'older' => 'OLDER', ]; $runningJobs = $this->groupedLogs['running'] ?? []; $otherGroups = array_filter( $this->groupedLogs, fn($k) => $k !== 'running' && $k !== 'queued', ARRAY_FILTER_USE_KEY ); @endphp {{-- wire:poll on root only — x-data is on the inner div so morphdom never re-initialises the Alpine scope during polls --}}
{{-- Header --}}
cydekick$ console
@if($this->counts['running'] > 0) live · {{ $this->counts['running'] }} running @else idle @endif
{{-- Tabs — active state driven by Alpine, never reset by Livewire polls --}}
@foreach(['running' => 'Running', 'activity' => 'Activity', 'jobs' => 'Jobs Completed', 'queue' => 'Queue', 'errors' => 'Errors', 'warnings' => 'Warnings', 'scheduled' => 'Scheduled'] as $tabKey => $tabLabel) @php $count = $this->counts[$tabKey]; @endphp @endforeach
{{-- Running tab empty state --}} @if(count($runningJobs) === 0 && count($queuedJobs ?? []) === 0)
⬡ no jobs running
@endif {{-- Pinned running job cards — only visible on the Running tab --}} @if(count($runningJobs) > 0)
@foreach($runningJobs as $log) {{-- wire:key preserves Alpine state (timer interval) across Livewire polls for the same job --}}
@if($log->isBatch()) {{-- Batch job — distributed progress across many child jobs --}} @php $bpct = $log->batch_percent; $isQueuing = $log->batch_done === 0; @endphp
{{ $log->display_name }} @if(! $isQueuing) {{ $bpct }}% @endif
@if($isQueuing)
@else
@endif
@if($isQueuing) Building queue… {{ number_format($log->total_items) }} jobs to dispatch @else
{{ number_format($log->batch_done) }} of {{ number_format($log->total_items) }} items @if(($log->failed_items ?? 0) > 0) · {{ number_format($log->failed_items) }} failed @endif @if($log->batch_rate) {{ $log->batch_rate }} @endif @if($log->batch_eta_label) {{ $log->batch_eta_label }} @endif
@endif @elseif($log->progress) {{-- Single-job progress bar --}}
{{ $log->display_name }} {{ $log->progress_percent }}%
@if(! empty($log->progress['phase']))
{{ $log->progress['phase'] }}
@endif
{{ number_format($log->progress['done'] ?? 0) }} / {{ number_format($log->progress['total'] ?? 0) }} · {{ $log->progress_percent }}% @else {{-- Unknown total — swish animation --}}
{{ $log->display_name }}
@endif
@endforeach
@endif {{-- Queued batch cards — "NEXT" placeholder shown on Running tab only --}} @php $queuedJobs = $this->groupedLogs['queued'] ?? []; @endphp @if(count($queuedJobs) > 0)
@foreach($queuedJobs as $log)
{{ $log->display_name }} NEXT
{{ number_format($log->total_items) }} jobs queued · starts when current job completes
@endforeach
@endif {{-- Log body — only shown on the four log tabs; Queue/Running/Scheduled have their own content --}}
@forelse($otherGroups as $group => $entries) {{-- Group header — entries are pre-filtered server-side, so no x-show needed here --}} @if($groupLabels[$group] ?? null)
— {{ $groupLabels[$group] }}
@endif @foreach($entries as $log) @php $s = $statusStyles[$log->status] ?? $statusStyles['info']; @endphp {{-- No x-show needed — server already filtered to the active tab's entries only --}}
{{ $log->created_at->format('H:i:s') }} {{ $s['label'] }}
{{ $log->display_name }} @if($log->message)
{{ $log->message }}
@endif
@if($log->duration_label) ⏱ {{ $log->duration_label }} @endif
@endforeach @empty @if(! in_array($activeTab, ['running', 'queue', 'scheduled']))
_
no activity
@endif @endforelse
{{-- Queue tab — jobs table breakdown --}}
@php $qs = $this->queueStats; @endphp @if($qs['total'] === 0)
queue is empty
@else
{{ number_format($qs['total']) }} jobs @if($qs['oldest_age'] !== null) oldest waiting: {{ $qs['oldest_age'] }}m ago @endif
JOB
QUEUE
WAIT
RUN
@foreach($qs['rows'] as $row)
{{ $row['display_name'] }}
{{ $row['queue'] }}
{{ number_format($row['waiting']) }}
{{ $row['reserved'] > 0 ? number_format($row['reserved']) : '—' }}
@foreach($row['breakdown'] as $ch)
↳ {{ $ch['name'] }}
{{ number_format($ch['waiting']) }}
{{ $ch['reserved'] > 0 ? number_format($ch['reserved']) : '—' }}
@endforeach @endforeach
WAIT = waiting · RUN = reserved by worker
@endif
{{-- Scheduled tab — outside log body so it's never polluted by log entries --}}
@forelse($this->scheduledEvents as $event)
{{ $event['name'] }} {{ $event['label'] }}
@empty
no scheduled tasks registered
@endforelse
{{-- Footer --}}
filter or run: · · {{ now()->format('H:i:s') }}
{{-- end Alpine x-data --}}
{{-- end wire:poll root --}}