scopeMode === FindingsLifecycleBackfillScope::MODE_ALL_TENANTS) { return 'All tenants'; } $tenantName = $this->selectedTenantName(); if ($tenantName !== null) { return "Single tenant ({$tenantName})"; } return $this->tenantId !== null ? "Single tenant (#{$this->tenantId})" : 'Single tenant'; } public function lastRun(): ?OperationRun { $platformTenant = Tenant::query()->where('external_id', 'platform')->first(); if (! $platformTenant instanceof Tenant) { return null; } return OperationRun::query() ->where('workspace_id', (int) $platformTenant->workspace_id) ->where('type', FindingsLifecycleBackfillRunbookService::RUNBOOK_KEY) ->latest('id') ->first(); } public function selectedTenantName(): ?string { if ($this->tenantId === null) { return null; } return Tenant::query()->whereKey($this->tenantId)->value('name'); } public static function canAccess(): bool { $user = auth('platform')->user(); if (! $user instanceof PlatformUser) { return false; } return $user->hasCapability(PlatformCapabilities::OPS_VIEW) && $user->hasCapability(PlatformCapabilities::RUNBOOKS_VIEW); } /** * @return array */ protected function getHeaderActions(): array { return [ Action::make('preflight') ->label('Preflight') ->color('gray') ->icon('heroicon-o-magnifying-glass') ->form($this->scopeForm()) ->action(function (array $data, FindingsLifecycleBackfillRunbookService $runbookService): void { $scope = FindingsLifecycleBackfillScope::fromArray([ 'mode' => $data['scope_mode'] ?? null, 'tenant_id' => $data['tenant_id'] ?? null, ]); $this->scopeMode = $scope->mode; $this->tenantId = $scope->tenantId; $this->preflight = $runbookService->preflight($scope); Notification::make() ->title('Preflight complete') ->success() ->send(); }), Action::make('run') ->label('Run…') ->icon('heroicon-o-play') ->color('danger') ->requiresConfirmation() ->modalHeading('Run: Rebuild Findings Lifecycle') ->modalDescription('This operation may modify customer data. Review the preflight and confirm before running.') ->form($this->runForm()) ->disabled(fn (): bool => ! is_array($this->preflight) || (int) ($this->preflight['affected_count'] ?? 0) <= 0) ->action(function (array $data, FindingsLifecycleBackfillRunbookService $runbookService): void { if (! is_array($this->preflight) || (int) ($this->preflight['affected_count'] ?? 0) <= 0) { throw ValidationException::withMessages([ 'preflight' => 'Run preflight first.', ]); } $scope = $this->scopeMode === FindingsLifecycleBackfillScope::MODE_SINGLE_TENANT ? FindingsLifecycleBackfillScope::singleTenant((int) $this->tenantId) : FindingsLifecycleBackfillScope::allTenants(); $user = auth('platform')->user(); if (! $user instanceof PlatformUser) { abort(403); } if (! $user->hasCapability(PlatformCapabilities::RUNBOOKS_RUN) || ! $user->hasCapability(PlatformCapabilities::RUNBOOKS_FINDINGS_LIFECYCLE_BACKFILL) ) { abort(403); } if ($scope->isAllTenants()) { $typedConfirmation = (string) ($data['typed_confirmation'] ?? ''); if ($typedConfirmation !== 'BACKFILL') { throw ValidationException::withMessages([ 'typed_confirmation' => 'Please type BACKFILL to confirm.', ]); } } $reason = RunbookReason::fromNullableArray([ 'reason_code' => $data['reason_code'] ?? null, 'reason_text' => $data['reason_text'] ?? null, ]); $run = $runbookService->start( scope: $scope, initiator: $user, reason: $reason, source: 'system_ui', ); $viewUrl = SystemOperationRunLinks::view($run); $toast = $run->wasRecentlyCreated ? OperationUxPresenter::queuedToast((string) $run->type)->body('The runbook will execute in the background.') : OperationUxPresenter::alreadyQueuedToast((string) $run->type); $toast ->actions([ Action::make('view_run') ->label('View run') ->url($viewUrl), ]) ->send(); }), ]; } /** * @return array */ private function scopeForm(): array { return [ Radio::make('scope_mode') ->label('Scope') ->options([ FindingsLifecycleBackfillScope::MODE_ALL_TENANTS => 'All tenants', FindingsLifecycleBackfillScope::MODE_SINGLE_TENANT => 'Single tenant', ]) ->default($this->scopeMode) ->live() ->required(), Select::make('tenant_id') ->label('Tenant') ->searchable() ->visible(fn (callable $get): bool => $get('scope_mode') === FindingsLifecycleBackfillScope::MODE_SINGLE_TENANT) ->required(fn (callable $get): bool => $get('scope_mode') === FindingsLifecycleBackfillScope::MODE_SINGLE_TENANT) ->getSearchResultsUsing(function (string $search, AllowedTenantUniverse $universe): array { return $universe ->query() ->where('name', 'like', "%{$search}%") ->orderBy('name') ->limit(25) ->pluck('name', 'id') ->all(); }) ->getOptionLabelUsing(function ($value, AllowedTenantUniverse $universe): ?string { if (! is_numeric($value)) { return null; } return $universe ->query() ->whereKey((int) $value) ->value('name'); }), ]; } /** * @return array */ private function runForm(): array { return [ TextInput::make('typed_confirmation') ->label('Type BACKFILL to confirm') ->visible(fn (): bool => $this->scopeMode === FindingsLifecycleBackfillScope::MODE_ALL_TENANTS) ->required(fn (): bool => $this->scopeMode === FindingsLifecycleBackfillScope::MODE_ALL_TENANTS) ->in(['BACKFILL']) ->validationMessages([ 'in' => 'Please type BACKFILL to confirm.', ]), Select::make('reason_code') ->label('Reason code') ->options(RunbookReason::options()) ->required(function (BreakGlassSession $breakGlass): bool { return $this->scopeMode === FindingsLifecycleBackfillScope::MODE_ALL_TENANTS || $breakGlass->isActive(); }), Textarea::make('reason_text') ->label('Reason') ->rows(4) ->maxLength(500) ->required(function (BreakGlassSession $breakGlass): bool { return $this->scopeMode === FindingsLifecycleBackfillScope::MODE_ALL_TENANTS || $breakGlass->isActive(); }), ]; } }