satisfy(ActionSurfaceSlot::ListHeader, 'The page header exposes Show all operations while row clicks remain the only inspect model.') ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value) ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'Stuck operations remain scan-first and intentionally omit bulk actions.') ->satisfy(ActionSurfaceSlot::ListEmptyState, 'The empty state explains when no operations cross the stuck thresholds and repeats the Show all operations CTA.') ->exempt(ActionSurfaceSlot::DetailHeader, 'Row navigation opens the canonical system run detail page, which owns header actions.'); } public static function getNavigationBadge(): ?string { $count = app(StuckRunClassifier::class) ->apply(OperationRun::query()) ->count(); return $count > 0 ? (string) $count : null; } public static function getNavigationBadgeColor(): string|array|null { return 'warning'; } public static function canAccess(): bool { $user = auth('platform')->user(); if (! $user instanceof PlatformUser) { return false; } return $user->hasCapability(PlatformCapabilities::OPERATIONS_VIEW) || ($user->hasCapability(PlatformCapabilities::OPS_VIEW) && $user->hasCapability(PlatformCapabilities::RUNBOOKS_VIEW)); } public function mount(): void { $this->mountInteractsWithTable(); } /** * @return array */ protected function getHeaderActions(): array { return [ Action::make('show_all_operations') ->label('Show all operations') ->url(SystemOperationRunLinks::index()), ]; } public function table(Table $table): Table { return $table ->defaultSort('id', 'desc') ->paginated(\App\Support\Filament\TablePaginationProfiles::customPage()) ->query(function (): Builder { return app(StuckRunClassifier::class)->apply( OperationRun::query() ->with(['tenant', 'workspace']) ); }) ->columns([ TextColumn::make('id') ->label('ID') ->state(fn (OperationRun $record): string => '#'.$record->getKey()), TextColumn::make('status') ->badge() ->formatStateUsing(BadgeRenderer::label(BadgeDomain::OperationRunStatus)) ->color(BadgeRenderer::color(BadgeDomain::OperationRunStatus)) ->icon(BadgeRenderer::icon(BadgeDomain::OperationRunStatus)) ->iconColor(BadgeRenderer::iconColor(BadgeDomain::OperationRunStatus)), TextColumn::make('stuck_class') ->label('Stuck class') ->state(function (OperationRun $record): string { $classification = app(StuckRunClassifier::class)->classify($record); return $classification === OperationRunStatus::Queued->value ? 'Queued too long' : 'Running too long'; }), TextColumn::make('type') ->label('Operation') ->formatStateUsing(fn (?string $state): string => OperationCatalog::label((string) $state)) ->searchable(), TextColumn::make('workspace.name') ->label('Workspace') ->toggleable(), TextColumn::make('tenant.name') ->label('Tenant') ->formatStateUsing(fn (?string $state): string => $state ?: 'Tenantless') ->toggleable(), TextColumn::make('created_at')->label('Started')->since(), ]) ->recordUrl(fn (OperationRun $record): string => SystemOperationRunLinks::view($record)) ->actions([]) ->emptyStateHeading('No stuck operations found') ->emptyStateDescription('Queued and running operations that exceed the thresholds will appear here.') ->emptyStateActions([ Action::make('show_all_operations_empty') ->label('Show all operations') ->url(SystemOperationRunLinks::index()) ->button(), ]) ->bulkActions([]); } }