label(fn (): string => $this->activeSnapshotEntry()?->actionLabel ?? 'View snapshot') ->url(fn (): ?string => $this->activeSnapshotEntry()?->targetUrl) ->hidden(fn (): bool => ! ($this->activeSnapshotEntry()?->isAvailable() ?? false)) ->color('gray'), $this->captureAction(), $this->compareNowAction(), EditAction::make() ->visible(fn (): bool => $this->hasManageCapability()), ]; } private function activeSnapshotEntry(): ?RelatedContextEntry { return app(RelatedNavigationResolver::class) ->headerEntries(CrossResourceNavigationMatrix::SOURCE_BASELINE_PROFILE, $this->getRecord())[0] ?? null; } private function captureAction(): Action { /** @var BaselineProfile $profile */ $profile = $this->getRecord(); $captureMode = $profile->capture_mode instanceof BaselineCaptureMode ? $profile->capture_mode : BaselineCaptureMode::Opportunistic; $label = $captureMode === BaselineCaptureMode::FullContent ? 'Capture baseline (full content)' : 'Capture baseline'; $modalDescription = $captureMode === BaselineCaptureMode::FullContent ? 'Select the source tenant. This will capture content evidence on demand (redacted) and may take longer depending on scope.' : 'Select the source tenant whose current inventory will be captured as the baseline snapshot.'; $action = Action::make('capture') ->label($label) ->icon('heroicon-o-camera') ->color('primary') ->requiresConfirmation() ->modalHeading($label) ->modalDescription($modalDescription) ->form([ Select::make('source_tenant_id') ->label('Source Tenant') ->options(fn (): array => $this->getWorkspaceTenantOptions()) ->required() ->searchable(), ]) ->action(function (array $data): void { $user = auth()->user(); if (! $user instanceof User) { abort(403); } /** @var BaselineProfile $profile */ $profile = $this->getRecord(); $sourceTenant = Tenant::query()->find((int) $data['source_tenant_id']); if (! $sourceTenant instanceof Tenant) { Notification::make() ->title('Source tenant not found') ->danger() ->send(); return; } $service = app(BaselineCaptureService::class); $result = $service->startCapture($profile, $sourceTenant, $user); if (! $result['ok']) { $reasonCode = is_string($result['reason_code'] ?? null) ? (string) $result['reason_code'] : 'unknown'; $message = match ($reasonCode) { BaselineReasonCodes::CAPTURE_ROLLOUT_DISABLED => 'Full-content baseline capture is currently disabled for controlled rollout.', BaselineReasonCodes::CAPTURE_PROFILE_NOT_ACTIVE => 'This baseline profile is not active.', BaselineReasonCodes::CAPTURE_MISSING_SOURCE_TENANT => 'The selected tenant is not available for this baseline profile.', default => 'Reason: '.str_replace('.', ' ', $reasonCode), }; Notification::make() ->title('Cannot start capture') ->body($message) ->danger() ->send(); return; } $run = $result['run'] ?? null; if (! $run instanceof \App\Models\OperationRun) { Notification::make() ->title('Cannot start capture') ->body('Reason: missing operation run') ->danger() ->send(); return; } $viewAction = Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($run, $sourceTenant)); 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(); }); return WorkspaceUiEnforcement::forTableAction($action, fn (): ?Workspace => Workspace::query() ->whereKey((int) $this->getRecord()->workspace_id) ->first()) ->requireCapability(Capabilities::WORKSPACE_BASELINES_MANAGE) ->apply(); } private function compareNowAction(): Action { /** @var BaselineProfile $profile */ $profile = $this->getRecord(); $captureMode = $profile->capture_mode instanceof BaselineCaptureMode ? $profile->capture_mode : BaselineCaptureMode::Opportunistic; $label = $captureMode === BaselineCaptureMode::FullContent ? 'Compare now (full content)' : 'Compare now'; $modalDescription = $captureMode === BaselineCaptureMode::FullContent ? 'Select the target tenant. This will refresh content evidence on demand (redacted) before comparing.' : 'Select the target tenant to compare its current inventory against the active baseline snapshot.'; return Action::make('compareNow') ->label($label) ->icon('heroicon-o-play') ->requiresConfirmation() ->modalHeading($label) ->modalDescription($modalDescription) ->form([ Select::make('target_tenant_id') ->label('Target Tenant') ->options(fn (): array => $this->getEligibleCompareTenantOptions()) ->required() ->searchable(), ]) ->disabled(fn (): bool => $this->getEligibleCompareTenantOptions() === []) ->action(function (array $data): void { $user = auth()->user(); if (! $user instanceof User) { abort(403); } /** @var BaselineProfile $profile */ $profile = $this->getRecord(); $targetTenant = Tenant::query()->find((int) $data['target_tenant_id']); if (! $targetTenant instanceof Tenant || (int) $targetTenant->workspace_id !== (int) $profile->workspace_id) { Notification::make() ->title('Target tenant not found') ->danger() ->send(); return; } $assignment = BaselineTenantAssignment::query() ->where('workspace_id', (int) $profile->workspace_id) ->where('tenant_id', (int) $targetTenant->getKey()) ->where('baseline_profile_id', (int) $profile->getKey()) ->first(); if (! $assignment instanceof BaselineTenantAssignment) { Notification::make() ->title('Tenant not assigned') ->body('This tenant is not assigned to this baseline profile.') ->warning() ->send(); return; } $resolver = app(CapabilityResolver::class); if (! $resolver->can($user, $targetTenant, Capabilities::TENANT_SYNC)) { Notification::make() ->title('Permission denied') ->danger() ->send(); return; } $service = app(BaselineCompareService::class); $result = $service->startCompare($targetTenant, $user); if (! ($result['ok'] ?? false)) { $reasonCode = is_string($result['reason_code'] ?? null) ? (string) $result['reason_code'] : 'unknown'; $message = match ($reasonCode) { BaselineReasonCodes::COMPARE_ROLLOUT_DISABLED => 'Full-content baseline compare is currently disabled for controlled rollout.', BaselineReasonCodes::COMPARE_PROFILE_NOT_ACTIVE => 'This baseline profile is not active.', BaselineReasonCodes::COMPARE_NO_ACTIVE_SNAPSHOT => 'This baseline profile has no active snapshot.', default => 'Reason: '.str_replace('.', ' ', $reasonCode), }; Notification::make() ->title('Cannot start comparison') ->body($message) ->danger() ->send(); return; } $run = $result['run'] ?? null; if (! $run instanceof \App\Models\OperationRun) { Notification::make() ->title('Cannot start comparison') ->body('Reason: missing operation run') ->danger() ->send(); return; } $viewAction = Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($run, $targetTenant)); 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(); }); } /** * @return array */ private function getWorkspaceTenantOptions(): array { $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request()); if ($workspaceId === null) { return []; } return Tenant::query() ->where('workspace_id', $workspaceId) ->orderBy('name') ->pluck('name', 'id') ->all(); } /** * @return array */ private function getEligibleCompareTenantOptions(): array { $user = auth()->user(); if (! $user instanceof User) { return []; } /** @var BaselineProfile $profile */ $profile = $this->getRecord(); $tenantIds = BaselineTenantAssignment::query() ->where('workspace_id', (int) $profile->workspace_id) ->where('baseline_profile_id', (int) $profile->getKey()) ->pluck('tenant_id') ->all(); if ($tenantIds === []) { return []; } $resolver = app(CapabilityResolver::class); $options = []; $tenants = Tenant::query() ->where('workspace_id', (int) $profile->workspace_id) ->whereIn('id', $tenantIds) ->orderBy('name') ->get(['id', 'name']); foreach ($tenants as $tenant) { if (! $tenant instanceof Tenant) { continue; } if (! $resolver->can($user, $tenant, Capabilities::TENANT_SYNC)) { continue; } $options[(int) $tenant->getKey()] = (string) $tenant->name; } return $options; } private function hasManageCapability(): bool { $user = auth()->user(); if (! $user instanceof User) { return false; } $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request()); if ($workspaceId === null) { return false; } $workspace = Workspace::query()->whereKey($workspaceId)->first(); if (! $workspace instanceof Workspace) { return false; } $resolver = app(\App\Services\Auth\WorkspaceCapabilityResolver::class); return $resolver->isMember($user, $workspace) && $resolver->can($user, $workspace, Capabilities::WORKSPACE_BASELINES_MANAGE); } }