|null */ public ?array $navigationContextPayload = null; public bool $opsUxIsTabHidden = false; /** * @return array */ protected function getHeaderActions(): array { $operateHubShell = app(OperateHubShell::class); $navigationContext = $this->navigationContext(); $actions = [ Action::make('operate_hub_scope_run_detail') ->label($operateHubShell->scopeLabel(request())) ->color('gray') ->disabled(), ]; $activeTenant = $operateHubShell->activeEntitledTenant(request()); if ($navigationContext?->backLinkLabel !== null && $navigationContext->backLinkUrl !== null) { $actions[] = Action::make('operate_hub_back_to_origin_run_detail') ->label($navigationContext->backLinkLabel) ->color('gray') ->url($navigationContext->backLinkUrl); } elseif ($activeTenant instanceof Tenant) { $actions[] = Action::make('operate_hub_back_to_tenant_run_detail') ->label('← Back to '.$activeTenant->name) ->color('gray') ->url(\App\Filament\Pages\TenantDashboard::getUrl(panel: 'tenant', tenant: $activeTenant)); } else { $actions[] = Action::make('operate_hub_back_to_operations') ->label('Back to Operations') ->color('gray') ->url(fn (): string => route('admin.operations.index')); } if ($activeTenant instanceof Tenant) { $actions[] = Action::make('operate_hub_show_all_operations') ->label('Show all operations') ->color('gray') ->url(fn (): string => route('admin.operations.index')); } $actions[] = Action::make('refresh') ->label('Refresh') ->icon('heroicon-o-arrow-path') ->color('gray') ->url(fn (): string => isset($this->run) ? OperationRunLinks::tenantlessView($this->run, $navigationContext) : route('admin.operations.index')); if (! isset($this->run)) { return $actions; } $user = auth()->user(); $tenant = $this->run->tenant; if ($tenant instanceof Tenant && (! $user instanceof User || ! app(CapabilityResolver::class)->isMember($user, $tenant))) { $tenant = null; } $related = OperationRunLinks::related($this->run, $tenant); $relatedActions = []; foreach ($related as $label => $url) { $relatedActions[] = Action::make(Str::slug((string) $label, '_')) ->label((string) $label) ->url((string) $url) ->openUrlInNewTab(); } if ($relatedActions !== []) { $actions[] = ActionGroup::make($relatedActions) ->label('Open') ->icon('heroicon-o-arrow-top-right-on-square') ->color('gray'); } $actions[] = $this->resumeCaptureAction(); return $actions; } public function mount(OperationRun $run): void { $user = auth()->user(); if (! $user instanceof User) { abort(403); } $this->authorize('view', $run); $activeTenant = app(OperateHubShell::class)->activeEntitledTenant(request()); if ( $activeTenant instanceof Tenant && (int) ($run->tenant_id ?? 0) !== (int) $activeTenant->getKey() ) { abort(404); } $this->run = $run->loadMissing(['workspace', 'tenant', 'user']); $this->navigationContextPayload = is_array(request()->query('nav')) ? request()->query('nav') : null; } public function infolist(Schema $schema): Schema { return OperationRunResource::infolist($schema); } public function defaultInfolist(Schema $schema): Schema { return $schema ->record($this->run) ->columns(2); } public function redactionIntegrityNote(): ?string { return isset($this->run) ? RedactionIntegrity::noteForRun($this->run) : null; } public function pollInterval(): ?string { if (! isset($this->run)) { return null; } if ($this->opsUxIsTabHidden === true) { return null; } if (filled($this->mountedActions ?? null)) { return null; } return RunDetailPolling::interval($this->run); } public function content(Schema $schema): Schema { return $schema->schema([ EmbeddedSchema::make('infolist'), ]); } private function resumeCaptureAction(): Action { return Action::make('resumeCapture') ->label('Resume capture') ->icon('heroicon-o-forward') ->requiresConfirmation() ->modalHeading('Resume capture') ->modalDescription('This will start a follow-up operation to capture remaining baseline evidence for this scope.') ->visible(fn (): bool => $this->canResumeCapture()) ->action(function (): void { $user = auth()->user(); if (! $user instanceof User) { abort(403); } if (! isset($this->run)) { Notification::make() ->title('Run not loaded') ->danger() ->send(); return; } $service = app(BaselineEvidenceCaptureResumeService::class); $result = $service->resume($this->run, $user); if (! ($result['ok'] ?? false)) { $reason = is_string($result['reason_code'] ?? null) ? (string) $result['reason_code'] : 'unknown'; Notification::make() ->title('Cannot resume capture') ->body('Reason: '.str_replace('.', ' ', $reason)) ->danger() ->send(); return; } $run = $result['run'] ?? null; if (! $run instanceof OperationRun) { Notification::make() ->title('Cannot resume capture') ->body('Reason: missing operation run') ->danger() ->send(); return; } $viewAction = Action::make('view_run') ->label('View run') ->url(OperationRunLinks::tenantlessView($run)); if (! $run->wasRecentlyCreated && in_array((string) $run->status, ['queued', 'running'], true)) { OpsUxBrowserEvents::dispatchRunEnqueued($this); OperationUxPresenter::alreadyQueuedToast((string) $run->type) ->actions([$viewAction]) ->send(); return; } OpsUxBrowserEvents::dispatchRunEnqueued($this); OperationUxPresenter::queuedToast((string) $run->type) ->actions([$viewAction]) ->send(); }); } private function navigationContext(): ?CanonicalNavigationContext { if (! is_array($this->navigationContextPayload)) { return CanonicalNavigationContext::fromRequest(request()); } $request = request()->duplicate(query: ['nav' => $this->navigationContextPayload]); return CanonicalNavigationContext::fromRequest($request); } private function canResumeCapture(): bool { if (! isset($this->run)) { return false; } if ((string) $this->run->status !== 'completed') { return false; } if (! in_array((string) $this->run->type, ['baseline_capture', 'baseline_compare'], true)) { return false; } $context = is_array($this->run->context) ? $this->run->context : []; $tokenKey = (string) $this->run->type === 'baseline_capture' ? 'baseline_capture.resume_token' : 'baseline_compare.resume_token'; $token = data_get($context, $tokenKey); if (! is_string($token) || trim($token) === '') { return false; } $user = auth()->user(); if (! $user instanceof User) { return false; } $workspace = $this->run->workspace; if (! $workspace instanceof \App\Models\Workspace) { return false; } $resolver = app(WorkspaceCapabilityResolver::class); return $resolver->isMember($user, $workspace) && $resolver->can($user, $workspace, Capabilities::WORKSPACE_BASELINES_MANAGE); } }