schema([ Section::make('Run') ->schema([ TextEntry::make('type') ->badge() ->formatStateUsing(fn (?string $state): string => OperationCatalog::label((string) $state)), TextEntry::make('status') ->badge() ->color(fn (OperationRun $record): string => static::statusColor($record->status)), TextEntry::make('outcome') ->badge() ->color(fn (OperationRun $record): string => static::outcomeColor($record->outcome)), TextEntry::make('initiator_name')->label('Initiator'), TextEntry::make('elapsed') ->label('Elapsed') ->getStateUsing(fn (OperationRun $record): string => RunDurationInsights::elapsedHuman($record)), TextEntry::make('expected_duration') ->label('Expected') ->getStateUsing(fn (OperationRun $record): string => RunDurationInsights::expectedHuman($record) ?? '—'), TextEntry::make('stuck_guidance') ->label('') ->getStateUsing(fn (OperationRun $record): ?string => RunDurationInsights::stuckGuidance($record)) ->visible(fn (OperationRun $record): bool => RunDurationInsights::stuckGuidance($record) !== null), TextEntry::make('created_at')->dateTime(), TextEntry::make('started_at')->dateTime()->placeholder('—'), TextEntry::make('completed_at')->dateTime()->placeholder('—'), TextEntry::make('run_identity_hash')->label('Identity hash')->copyable(), ]) ->extraAttributes([ 'x-init' => '$wire.set(\'opsUxIsTabHidden\', document.hidden)', 'x-on:visibilitychange.window' => '$wire.set(\'opsUxIsTabHidden\', document.hidden)', ]) ->poll(function (OperationRun $record, $livewire): ?string { if (($livewire->opsUxIsTabHidden ?? false) === true) { return null; } if (filled($livewire->mountedActions ?? null)) { return null; } return RunDetailPolling::interval($record); }) ->columns(2) ->columnSpanFull(), Section::make('Counts') ->schema([ ViewEntry::make('summary_counts') ->label('') ->view('filament.infolists.entries.snapshot-json') ->state(fn (OperationRun $record): array => $record->summary_counts ?? []) ->columnSpanFull(), ]) ->visible(fn (OperationRun $record): bool => ! empty($record->summary_counts)) ->columnSpanFull(), Section::make('Failures') ->schema([ ViewEntry::make('failure_summary') ->label('') ->view('filament.infolists.entries.snapshot-json') ->state(fn (OperationRun $record): array => $record->failure_summary ?? []) ->columnSpanFull(), ]) ->visible(fn (OperationRun $record): bool => ! empty($record->failure_summary)) ->columnSpanFull(), Section::make('Context') ->schema([ ViewEntry::make('context') ->label('') ->view('filament.infolists.entries.snapshot-json') ->state(fn (OperationRun $record): array => $record->context ?? []) ->columnSpanFull(), ]) ->columnSpanFull(), ]); } public static function table(Table $table): Table { return $table ->defaultSort('created_at', 'desc') ->modifyQueryUsing(function (Builder $query): Builder { $tenantId = Tenant::current()?->getKey(); return $query->when($tenantId, fn (Builder $q) => $q->where('tenant_id', $tenantId)); }) ->columns([ Tables\Columns\TextColumn::make('status') ->badge() ->color(fn (OperationRun $record): string => static::statusColor($record->status)), Tables\Columns\TextColumn::make('type') ->label('Operation') ->formatStateUsing(fn (?string $state): string => OperationCatalog::label((string) $state)) ->searchable() ->sortable(), Tables\Columns\TextColumn::make('initiator_name') ->label('Initiator') ->searchable(), Tables\Columns\TextColumn::make('created_at') ->label('Started') ->since() ->sortable(), Tables\Columns\TextColumn::make('duration') ->getStateUsing(function (OperationRun $record): string { if ($record->started_at && $record->completed_at) { return $record->completed_at->diffForHumans($record->started_at, true); } return '—'; }), Tables\Columns\TextColumn::make('outcome') ->badge() ->color(fn (OperationRun $record): string => static::outcomeColor($record->outcome)), ]) ->filters([ Tables\Filters\SelectFilter::make('type') ->options(function (): array { $tenantId = Tenant::current()?->getKey(); if (! $tenantId) { return []; } return OperationRun::query() ->where('tenant_id', $tenantId) ->select('type') ->distinct() ->orderBy('type') ->pluck('type', 'type') ->all(); }), Tables\Filters\SelectFilter::make('status') ->options([ OperationRunStatus::Queued->value => 'Queued', OperationRunStatus::Running->value => 'Running', OperationRunStatus::Completed->value => 'Completed', ]), Tables\Filters\SelectFilter::make('outcome') ->options(OperationRunOutcome::uiLabels(includeReserved: false)), Tables\Filters\SelectFilter::make('initiator_name') ->label('Initiator') ->options(function (): array { $tenantId = Tenant::current()?->getKey(); if (! $tenantId) { return []; } return OperationRun::query() ->where('tenant_id', $tenantId) ->whereNotNull('initiator_name') ->select('initiator_name') ->distinct() ->orderBy('initiator_name') ->pluck('initiator_name', 'initiator_name') ->all(); }) ->searchable(), Tables\Filters\Filter::make('created_at') ->label('Created') ->form([ DatePicker::make('created_from') ->label('From'), DatePicker::make('created_until') ->label('Until'), ]) ->default(fn (): array => [ 'created_from' => now()->subDays(30), 'created_until' => now(), ]) ->query(function (Builder $query, array $data): Builder { $from = $data['created_from'] ?? null; if ($from) { $query->whereDate('created_at', '>=', $from); } $until = $data['created_until'] ?? null; if ($until) { $query->whereDate('created_at', '<=', $until); } return $query; }), ]) ->actions([ Actions\ViewAction::make(), ]) ->bulkActions([]); } public static function getEloquentQuery(): Builder { return parent::getEloquentQuery() ->with('user') ->latest('id'); } public static function getPages(): array { return [ 'index' => Pages\ListOperationRuns::route('/'), 'view' => Pages\ViewOperationRun::route('/{record}'), ]; } private static function statusColor(?string $status): string { return match ($status) { 'queued' => 'secondary', 'running' => 'warning', 'completed' => 'success', default => 'gray', }; } private static function outcomeColor(?string $outcome): string { return match ($outcome) { 'succeeded' => 'success', 'partially_succeeded' => 'warning', 'failed' => 'danger', 'cancelled' => 'gray', default => 'gray', }; } }