withDefaults(new ActionSurfaceDefaults( moreGroupLabel: 'More', exportIsDefaultBulkActionForReadOnly: false, )) ->exempt( ActionSurfaceSlot::ListHeader, 'Run-log list intentionally has no list-header actions; navigation actions are provided by Monitoring shell pages.', ) ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ViewAction->value) ->exempt( ActionSurfaceSlot::ListBulkMoreGroup, 'Operation runs are immutable records; bulk export is deferred and tracked outside this retrofit.', ) ->exempt( ActionSurfaceSlot::ListEmptyState, 'Empty-state action is intentionally omitted; users can adjust filters/date range in-page.', ) ->satisfy( ActionSurfaceSlot::DetailHeader, 'Tenantless detail view keeps back-navigation, refresh, related links, and resumable operation actions in the header.', ); } public static function getEloquentQuery(): Builder { $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(); return parent::getEloquentQuery() ->with('user') ->latest('id') ->when($workspaceId, fn (Builder $query) => $query->where('workspace_id', (int) $workspaceId)) ->when(! $workspaceId, fn (Builder $query) => $query->whereRaw('1 = 0')); } public static function form(Schema $schema): Schema { return $schema; } public static function infolist(Schema $schema): Schema { return $schema ->schema([ ViewEntry::make('enterprise_detail') ->label('') ->view('filament.infolists.entries.enterprise-detail.layout') ->state(fn (OperationRun $record): array => static::enterpriseDetailPage($record)->toArray()) ->columnSpanFull(), ]); } public static function table(Table $table): Table { return $table ->defaultSort('created_at', 'desc') ->paginated(TablePaginationProfiles::resource()) ->persistFiltersInSession() ->persistSearchInSession() ->persistSortInSession() ->columns([ Tables\Columns\TextColumn::make('status') ->badge() ->formatStateUsing(BadgeRenderer::label(BadgeDomain::OperationRunStatus)) ->color(BadgeRenderer::color(BadgeDomain::OperationRunStatus)) ->icon(BadgeRenderer::icon(BadgeDomain::OperationRunStatus)) ->iconColor(BadgeRenderer::iconColor(BadgeDomain::OperationRunStatus)), 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() ->formatStateUsing(BadgeRenderer::label(BadgeDomain::OperationRunOutcome)) ->color(BadgeRenderer::color(BadgeDomain::OperationRunOutcome)) ->icon(BadgeRenderer::icon(BadgeDomain::OperationRunOutcome)) ->iconColor(BadgeRenderer::iconColor(BadgeDomain::OperationRunOutcome)), ]) ->filters([ Tables\Filters\SelectFilter::make('tenant_id') ->label('Tenant') ->options(function (): array { $activeTenant = app(OperateHubShell::class)->activeEntitledTenant(request()); if ($activeTenant instanceof Tenant) { return [ (string) $activeTenant->getKey() => $activeTenant->getFilamentName(), ]; } $user = auth()->user(); if (! $user instanceof User) { return []; } return collect($user->getTenants(Filament::getCurrentOrDefaultPanel())) ->mapWithKeys(static fn (Tenant $tenant): array => [ (string) $tenant->getKey() => $tenant->getFilamentName(), ]) ->all(); }) ->default(function (): ?string { $activeTenant = app(OperateHubShell::class)->activeEntitledTenant(request()); if (! $activeTenant instanceof Tenant) { return null; } $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(); if ($workspaceId === null || (int) $activeTenant->workspace_id !== (int) $workspaceId) { return null; } return (string) $activeTenant->getKey(); }) ->searchable(), Tables\Filters\SelectFilter::make('type') ->options(function (): array { $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(); $activeTenant = app(OperateHubShell::class)->activeEntitledTenant(request()); if ($workspaceId === null) { return []; } $types = OperationRun::query() ->where('workspace_id', (int) $workspaceId) ->when( $activeTenant instanceof Tenant, fn (Builder $query): Builder => $query->where('tenant_id', (int) $activeTenant->getKey()), ) ->select('type') ->distinct() ->orderBy('type') ->pluck('type', 'type') ->all(); return FilterOptionCatalog::operationTypes(array_keys($types)); }), 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 { $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(); if ($workspaceId === null) { return []; } $activeTenant = app(OperateHubShell::class)->activeEntitledTenant(request()); $tenantId = $activeTenant instanceof Tenant ? (int) $activeTenant->getKey() : null; return OperationRun::query() ->where('workspace_id', (int) $workspaceId) ->when($tenantId, fn (Builder $query): Builder => $query->where('tenant_id', $tenantId)) ->whereNotNull('initiator_name') ->select('initiator_name') ->distinct() ->orderBy('initiator_name') ->pluck('initiator_name', 'initiator_name') ->all(); }) ->searchable(), FilterPresets::dateRange('created_at', 'Created', 'created_at', [ 'from' => now()->subDays(30)->toDateString(), 'until' => now()->toDateString(), ]), ]) ->actions([ Actions\ViewAction::make() ->label('View run') ->url(fn (OperationRun $record): string => OperationRunLinks::tenantlessView($record)), ]) ->bulkActions([]) ->emptyStateHeading('No operation runs found') ->emptyStateDescription('Queued, running, and completed operations will appear here when work is triggered in this scope.') ->emptyStateIcon('heroicon-o-queue-list'); } private static function enterpriseDetailPage(OperationRun $record): \App\Support\Ui\EnterpriseDetail\EnterpriseDetailPageData { $factory = new \App\Support\Ui\EnterpriseDetail\EnterpriseDetailSectionFactory; $statusSpec = BadgeRenderer::spec(BadgeDomain::OperationRunStatus, $record->status); $outcomeSpec = BadgeRenderer::spec(BadgeDomain::OperationRunOutcome, $record->outcome); $targetScope = static::targetScopeDisplay($record); $summaryLine = \App\Support\OpsUx\SummaryCountsNormalizer::renderSummaryLine(is_array($record->summary_counts) ? $record->summary_counts : []); $builder = \App\Support\Ui\EnterpriseDetail\EnterpriseDetailBuilder::make('operation_run', 'workspace-context') ->header(new \App\Support\Ui\EnterpriseDetail\SummaryHeaderData( title: OperationCatalog::label((string) $record->type), subtitle: 'Run #'.$record->getKey(), statusBadges: [ $factory->statusBadge($statusSpec->label, $statusSpec->color, $statusSpec->icon, $statusSpec->iconColor), $factory->statusBadge($outcomeSpec->label, $outcomeSpec->color, $outcomeSpec->icon, $outcomeSpec->iconColor), ], keyFacts: [ $factory->keyFact('Target', $targetScope ?? 'No target scope details were recorded for this run.'), $factory->keyFact('Initiator', $record->initiator_name), $factory->keyFact('Elapsed', RunDurationInsights::elapsedHuman($record)), $factory->keyFact('Expected', RunDurationInsights::expectedHuman($record)), ], descriptionHint: 'Run identity, outcome, scope, and related next steps stay ahead of stored payloads and diagnostic fragments.', )) ->addSection( $factory->factsSection( id: 'run_summary', kind: 'core_details', title: 'Run summary', items: [ $factory->keyFact('Operation', OperationCatalog::label((string) $record->type)), $factory->keyFact('Initiator', $record->initiator_name), $factory->keyFact('Target scope', $targetScope ?? 'No target scope details were recorded for this run.'), $factory->keyFact('Expected duration', RunDurationInsights::expectedHuman($record)), ], ), $factory->viewSection( id: 'related_context', kind: 'related_context', title: 'Related context', view: 'filament.infolists.entries.related-context', viewData: ['entries' => app(RelatedNavigationResolver::class) ->detailEntries(CrossResourceNavigationMatrix::SOURCE_OPERATION_RUN, $record)], emptyState: $factory->emptyState('No related context is available for this record.'), ), ) ->addSupportingCard( $factory->supportingFactsCard( kind: 'status', title: 'Current state', items: array_values(array_filter([ $factory->keyFact('Status', $statusSpec->label, badge: $factory->statusBadge($statusSpec->label, $statusSpec->color, $statusSpec->icon, $statusSpec->iconColor)), $factory->keyFact('Outcome', $outcomeSpec->label, badge: $factory->statusBadge($outcomeSpec->label, $outcomeSpec->color, $outcomeSpec->icon, $outcomeSpec->iconColor)), $summaryLine !== null ? $factory->keyFact('Counts', $summaryLine) : null, RunDurationInsights::stuckGuidance($record) !== null ? $factory->keyFact('Guidance', RunDurationInsights::stuckGuidance($record)) : null, ])), ), $factory->supportingFactsCard( kind: 'timestamps', title: 'Timing', items: [ $factory->keyFact('Created', static::formatDetailTimestamp($record->created_at)), $factory->keyFact('Started', static::formatDetailTimestamp($record->started_at)), $factory->keyFact('Completed', static::formatDetailTimestamp($record->completed_at)), $factory->keyFact('Elapsed', RunDurationInsights::elapsedHuman($record)), ], ), ) ->addTechnicalSection( $factory->technicalDetail( title: 'Context', entries: [ $factory->keyFact('Identity hash', $record->run_identity_hash), $factory->keyFact('Workspace scope', $record->workspace_id), $factory->keyFact('Tenant scope', $record->tenant_id), ], description: 'Stored run context stays available for debugging without dominating the default reading path.', view: 'filament.infolists.entries.snapshot-json', viewData: ['payload' => static::contextPayload($record)], ), ); $counts = static::summaryCountFacts($record, $factory); if ($counts !== []) { $builder->addSection( $factory->factsSection( id: 'counts', kind: 'current_status', title: 'Counts', items: $counts, ), ); } if (! empty($record->failure_summary)) { $builder->addSection( $factory->viewSection( id: 'failures', kind: 'operational_context', title: 'Failures', view: 'filament.infolists.entries.snapshot-json', viewData: ['payload' => $record->failure_summary ?? []], ), ); } if ((string) $record->type === 'baseline_compare') { $baselineCompareFacts = static::baselineCompareFacts($record, $factory); $baselineCompareEvidence = static::baselineCompareEvidencePayload($record); if ($baselineCompareFacts !== []) { $builder->addSection( $factory->factsSection( id: 'baseline_compare', kind: 'operational_context', title: 'Baseline compare', items: $baselineCompareFacts, ), ); } if ($baselineCompareEvidence !== []) { $builder->addSection( $factory->viewSection( id: 'baseline_compare_evidence', kind: 'operational_context', title: 'Baseline compare evidence', view: 'filament.infolists.entries.snapshot-json', viewData: ['payload' => $baselineCompareEvidence], ), ); } } if ((string) $record->type === 'baseline_capture') { $baselineCaptureEvidence = static::baselineCaptureEvidencePayload($record); if ($baselineCaptureEvidence !== []) { $builder->addSection( $factory->viewSection( id: 'baseline_capture_evidence', kind: 'operational_context', title: 'Baseline capture evidence', view: 'filament.infolists.entries.snapshot-json', viewData: ['payload' => $baselineCaptureEvidence], ), ); } } if (VerificationReportViewer::shouldRenderForRun($record)) { $builder->addSection( $factory->viewSection( id: 'verification_report', kind: 'operational_context', title: 'Verification report', view: 'filament.components.verification-report-viewer', viewData: static::verificationReportViewData($record), ), ); } return $builder->build(); } /** * @return list> */ private static function summaryCountFacts( OperationRun $record, \App\Support\Ui\EnterpriseDetail\EnterpriseDetailSectionFactory $factory, ): array { $counts = \App\Support\OpsUx\SummaryCountsNormalizer::normalize(is_array($record->summary_counts) ? $record->summary_counts : []); return array_map( static fn (string $key, int $value): array => $factory->keyFact(ucfirst(str_replace('_', ' ', $key)), $value), array_keys($counts), array_values($counts), ); } /** * @return list> */ private static function baselineCompareFacts( OperationRun $record, \App\Support\Ui\EnterpriseDetail\EnterpriseDetailSectionFactory $factory, ): array { $context = is_array($record->context) ? $record->context : []; $facts = []; $fidelity = data_get($context, 'baseline_compare.fidelity'); if (is_string($fidelity) && trim($fidelity) !== '') { $facts[] = $factory->keyFact('Fidelity', $fidelity); } $proof = data_get($context, 'baseline_compare.coverage.proof'); $uncoveredTypes = data_get($context, 'baseline_compare.coverage.uncovered_types'); $uncoveredTypes = is_array($uncoveredTypes) ? array_values(array_filter($uncoveredTypes, 'is_string')) : []; $facts[] = $factory->keyFact( 'Coverage', match (true) { $proof === false => 'Unproven', $uncoveredTypes !== [] => 'Warnings', $proof === true => 'Covered', default => 'Unknown', }, ); $reasonCode = data_get($context, 'baseline_compare.reason_code'); if (is_string($reasonCode) && trim($reasonCode) !== '') { $enum = BaselineCompareReasonCode::tryFrom(trim($reasonCode)); $facts[] = $factory->keyFact( 'Why no findings', $enum?->message() ?? trim($reasonCode), trim($reasonCode), ); } if ($uncoveredTypes !== []) { sort($uncoveredTypes, SORT_STRING); $facts[] = $factory->keyFact('Uncovered types', implode(', ', array_slice($uncoveredTypes, 0, 12)).(count($uncoveredTypes) > 12 ? '…' : '')); } $inventorySyncRunId = data_get($context, 'baseline_compare.inventory_sync_run_id'); if (is_numeric($inventorySyncRunId)) { $facts[] = $factory->keyFact('Inventory sync run', '#'.(int) $inventorySyncRunId); } return $facts; } /** * @return array */ private static function baselineCompareEvidencePayload(OperationRun $record): array { $context = is_array($record->context) ? $record->context : []; return array_filter([ 'subjects_total' => is_numeric(data_get($context, 'baseline_compare.subjects_total')) ? (int) data_get($context, 'baseline_compare.subjects_total') : null, 'evidence_gaps_count' => is_numeric(data_get($context, 'baseline_compare.evidence_gaps.count')) ? (int) data_get($context, 'baseline_compare.evidence_gaps.count') : null, 'resume_token' => data_get($context, 'baseline_compare.resume_token'), 'evidence_capture' => is_array(data_get($context, 'baseline_compare.evidence_capture')) ? data_get($context, 'baseline_compare.evidence_capture') : null, 'evidence_gaps' => is_array(data_get($context, 'baseline_compare.evidence_gaps')) ? data_get($context, 'baseline_compare.evidence_gaps') : null, ], static fn (mixed $value): bool => $value !== null && $value !== []); } /** * @return array */ private static function baselineCaptureEvidencePayload(OperationRun $record): array { $context = is_array($record->context) ? $record->context : []; return array_filter([ 'subjects_total' => is_numeric(data_get($context, 'baseline_capture.subjects_total')) ? (int) data_get($context, 'baseline_capture.subjects_total') : null, 'gaps_count' => is_numeric(data_get($context, 'baseline_capture.gaps.count')) ? (int) data_get($context, 'baseline_capture.gaps.count') : null, 'resume_token' => data_get($context, 'baseline_capture.resume_token'), 'evidence_capture' => is_array(data_get($context, 'baseline_capture.evidence_capture')) ? data_get($context, 'baseline_capture.evidence_capture') : null, 'gaps' => is_array(data_get($context, 'baseline_capture.gaps')) ? data_get($context, 'baseline_capture.gaps') : null, ], static fn (mixed $value): bool => $value !== null && $value !== []); } /** * @return array */ private static function verificationReportViewData(OperationRun $record): array { $report = VerificationReportViewer::report($record); $fingerprint = is_array($report) ? VerificationReportViewer::fingerprint($report) : null; $changeIndicator = VerificationReportChangeIndicator::forRun($record); $previousRunUrl = null; if ($changeIndicator !== null) { $tenant = app(OperateHubShell::class)->activeEntitledTenant(request()); $previousRunUrl = $tenant instanceof Tenant ? OperationRunLinks::view($changeIndicator['previous_report_id'], $tenant) : OperationRunLinks::tenantlessView($changeIndicator['previous_report_id']); } $acknowledgements = VerificationCheckAcknowledgement::query() ->where('tenant_id', (int) ($record->tenant_id ?? 0)) ->where('workspace_id', (int) ($record->workspace_id ?? 0)) ->where('operation_run_id', (int) $record->getKey()) ->with('acknowledgedByUser') ->get() ->mapWithKeys(static function (VerificationCheckAcknowledgement $ack): array { $user = $ack->acknowledgedByUser; return [ (string) $ack->check_key => [ 'check_key' => (string) $ack->check_key, 'ack_reason' => (string) $ack->ack_reason, 'acknowledged_at' => $ack->acknowledged_at?->toJSON(), 'expires_at' => $ack->expires_at?->toJSON(), 'acknowledged_by' => $user instanceof User ? [ 'id' => (int) $user->getKey(), 'name' => (string) $user->name, ] : null, ], ]; }) ->all(); return [ 'report' => $report, 'run' => [ 'id' => (int) $record->getKey(), 'type' => (string) $record->type, 'status' => (string) $record->status, 'outcome' => (string) $record->outcome, 'started_at' => $record->started_at?->toJSON(), 'completed_at' => $record->completed_at?->toJSON(), ], 'fingerprint' => $fingerprint, 'changeIndicator' => $changeIndicator, 'previousRunUrl' => $previousRunUrl, 'acknowledgements' => $acknowledgements, 'redactionNotes' => VerificationReportViewer::redactionNotes($report), ]; } /** * @return array */ private static function contextPayload(OperationRun $record): array { $context = is_array($record->context) ? $record->context : []; if (array_key_exists('verification_report', $context)) { $context['verification_report'] = [ 'redacted' => true, 'note' => 'Rendered in the Verification report section.', ]; } return $context; } private static function formatDetailTimestamp(mixed $value): string { if (! $value instanceof \Illuminate\Support\Carbon) { return '—'; } return $value->toDayDateTimeString(); } public static function getPages(): array { return []; } private static function targetScopeDisplay(OperationRun $record): ?string { $context = is_array($record->context) ? $record->context : []; $targetScope = $context['target_scope'] ?? null; if (! is_array($targetScope)) { return null; } $entraTenantName = $targetScope['entra_tenant_name'] ?? null; $entraTenantId = $targetScope['entra_tenant_id'] ?? null; $directoryContextId = $targetScope['directory_context_id'] ?? null; $entraTenantName = is_string($entraTenantName) ? trim($entraTenantName) : null; $entraTenantId = is_string($entraTenantId) ? trim($entraTenantId) : null; $directoryContextId = match (true) { is_string($directoryContextId) => trim($directoryContextId), is_int($directoryContextId) => (string) $directoryContextId, default => null, }; $entra = null; if ($entraTenantName !== null && $entraTenantName !== '') { $entra = $entraTenantId ? "{$entraTenantName} ({$entraTenantId})" : $entraTenantName; } elseif ($entraTenantId !== null && $entraTenantId !== '') { $entra = $entraTenantId; } $parts = array_values(array_filter([ $entra, $directoryContextId ? "directory_context_id: {$directoryContextId}" : null, ], fn (?string $value): bool => $value !== null && $value !== '')); return $parts !== [] ? implode(' · ', $parts) : null; } }