user(); if (! $user instanceof PlatformUser) { return false; } return $user->hasCapability(PlatformCapabilities::ACCESS_SYSTEM_PANEL) && $user->hasCapability(PlatformCapabilities::OPS_CONTROLS_MANAGE); } public function mount(): void { abort_unless(static::canAccess(), 403); } public function getHeader(): ?View { return view('filament.system.pages.ops.partials.controls-header', [ 'breadcrumbs' => filament()->hasBreadcrumbs() ? $this->getBreadcrumbs() : [], 'heading' => $this->getHeading(), 'subheading' => $this->getSubheading(), ]); } /** * @return array */ protected function getHeaderActions(): array { return [ $this->pauseRestoreExecuteAction(), $this->resumeRestoreExecuteAction(), $this->viewHistoryRestoreExecuteAction(), ]; } /** * @return array> */ public function controlCards(): array { $catalog = app(OperationalControlCatalog::class); return array_map( fn (string $controlKey): array => $this->controlSummary($controlKey), $catalog->keys(), ); } /** * @return array */ public function controlSummary(string $controlKey): array { $definition = app(OperationalControlCatalog::class)->definition($controlKey); $activations = $this->activeActivationsForControl($controlKey); $effectiveState = $activations->isEmpty() ? 'enabled' : 'paused'; $stateLabel = match (true) { $activations->contains(fn (OperationalControlActivation $activation): bool => $activation->scope_type === 'global') => 'Paused globally', $activations->isNotEmpty() => sprintf('Workspace pauses active (%d)', $activations->where('scope_type', 'workspace')->count()), default => 'Enabled', }; return [ 'control_key' => $controlKey, 'action_slug' => $this->actionSlug($controlKey), 'label' => (string) $definition['label'], 'effective_state' => $effectiveState, 'state_label' => $stateLabel, 'supported_scopes' => $definition['supported_scopes'], 'affected_surfaces' => $definition['affected_surfaces'], 'active_activations' => $activations ->map(fn (OperationalControlActivation $activation): array => $this->activationSummary($activation)) ->values() ->all(), 'history_count' => $this->recentAuditEventsForControl($controlKey)->count(), ]; } /** * @return array{control_key: string, scope_type: string, workspace_id: ?int, workspace_count: int, tenant_count: int, summary: string} */ public function scopeImpactPreview(string $controlKey, string $scopeType, ?int $workspaceId): array { $label = app(OperationalControlCatalog::class)->label($controlKey); if ($scopeType === 'workspace') { $workspace = is_int($workspaceId) ? Workspace::query()->whereKey($workspaceId)->first() : null; if (! $workspace instanceof Workspace) { return [ 'control_key' => $controlKey, 'scope_type' => $scopeType, 'workspace_id' => null, 'workspace_count' => 0, 'tenant_count' => 0, 'summary' => 'Select a workspace to preview the scope impact.', ]; } $tenantCount = Tenant::query() ->where('workspace_id', (int) $workspace->getKey()) ->where('external_id', '!=', 'platform') ->count(); return [ 'control_key' => $controlKey, 'scope_type' => $scopeType, 'workspace_id' => (int) $workspace->getKey(), 'workspace_count' => 1, 'tenant_count' => $tenantCount, 'summary' => sprintf('%s will affect workspace %s and %d %s.', $label, $workspace->name, $tenantCount, $tenantCount === 1 ? 'tenant' : 'tenants'), ]; } $tenantCount = Tenant::query() ->where('external_id', '!=', 'platform') ->count(); $workspaceCount = Tenant::query() ->where('external_id', '!=', 'platform') ->distinct('workspace_id') ->count('workspace_id'); return [ 'control_key' => $controlKey, 'scope_type' => 'global', 'workspace_id' => null, 'workspace_count' => $workspaceCount, 'tenant_count' => $tenantCount, 'summary' => sprintf('%s will affect %d %s across %d %s.', $label, $workspaceCount, $workspaceCount === 1 ? 'workspace' : 'workspaces', $tenantCount, $tenantCount === 1 ? 'tenant' : 'tenants'), ]; } public function pauseRestoreExecuteAction(): Action { return $this->pauseActionFor('restore.execute'); } public function resumeRestoreExecuteAction(): Action { return $this->resumeActionFor('restore.execute'); } public function viewHistoryRestoreExecuteAction(): Action { return $this->historyActionFor('restore.execute'); } private function pauseActionFor(string $controlKey): Action { $label = app(OperationalControlCatalog::class)->label($controlKey); return Action::make('pause_'.$this->actionSlug($controlKey)) ->label('Pause '.$label) ->icon('heroicon-o-pause') ->color('danger') ->requiresConfirmation() ->modalHeading('Pause '.$label) ->modalDescription('Review the scope impact, reason, and optional expiry before confirming this control change.') ->form($this->pauseFormSchema($controlKey)) ->action(function (array $data, AuditRecorder $auditRecorder, WorkspaceAuditLogger $workspaceAuditLogger) use ($controlKey, $label): void { $actor = $this->controlsActor(); [$scopeType, $workspace, $reasonText, $expiresAt] = $this->normalizePauseInput($data); $scopeQuery = $this->activationScopeQuery($controlKey, $scopeType, $workspace); (clone $scopeQuery) ->whereNotNull('expires_at') ->where('expires_at', '<=', now()) ->delete(); $activation = (clone $scopeQuery)->notExpired()->first(); $auditAction = $activation instanceof OperationalControlActivation ? AuditActionId::OperationalControlUpdated : AuditActionId::OperationalControlPaused; if ($activation instanceof OperationalControlActivation) { $activation->fill([ 'reason_text' => $reasonText, 'expires_at' => $expiresAt, 'updated_by_platform_user_id' => (int) $actor->getKey(), ])->save(); } else { $activation = OperationalControlActivation::query()->create([ 'control_key' => $controlKey, 'scope_type' => $scopeType, 'workspace_id' => $workspace instanceof Workspace ? (int) $workspace->getKey() : null, 'reason_text' => $reasonText, 'expires_at' => $expiresAt, 'created_by_platform_user_id' => (int) $actor->getKey(), ]); } $this->recordControlMutation( auditAction: $auditAction, activation: $activation, actor: $actor, auditRecorder: $auditRecorder, workspaceAuditLogger: $workspaceAuditLogger, ); Notification::make() ->title(sprintf('%s %s', $label, $auditAction === AuditActionId::OperationalControlPaused ? 'paused' : 'updated')) ->success() ->send(); }); } private function resumeActionFor(string $controlKey): Action { $label = app(OperationalControlCatalog::class)->label($controlKey); return Action::make('resume_'.$this->actionSlug($controlKey)) ->label('Resume '.$label) ->icon('heroicon-o-play') ->color('gray') ->requiresConfirmation() ->modalHeading('Resume '.$label) ->modalDescription('Remove the selected pause so new starts can proceed again.') ->form($this->resumeFormSchema($controlKey)) ->action(function (array $data, AuditRecorder $auditRecorder, WorkspaceAuditLogger $workspaceAuditLogger) use ($controlKey, $label): void { $actor = $this->controlsActor(); [$scopeType, $workspace] = $this->normalizeResumeInput($data); $activation = $this->activationScopeQuery($controlKey, $scopeType, $workspace) ->notExpired() ->first(); if (! $activation instanceof OperationalControlActivation) { Notification::make() ->title(sprintf('%s already enabled', $label)) ->warning() ->send(); return; } $activationSnapshot = $activation->replicate(); $activationSnapshot->forceFill($activation->getAttributes()); $activation->delete(); $this->recordControlMutation( auditAction: AuditActionId::OperationalControlResumed, activation: $activationSnapshot, actor: $actor, auditRecorder: $auditRecorder, workspaceAuditLogger: $workspaceAuditLogger, ); Notification::make() ->title($label.' resumed') ->success() ->send(); }); } private function historyActionFor(string $controlKey): Action { $label = app(OperationalControlCatalog::class)->label($controlKey); return Action::make('view_history_'.$this->actionSlug($controlKey)) ->label('View '.$label.' history') ->link() ->modalHeading($label.' history') ->modalSubmitAction(false) ->modalCancelActionLabel('Close') ->modalContent(fn () => view('filament.system.pages.ops.partials.operational-control-history', [ 'events' => $this->recentAuditEventsForControl($controlKey), 'label' => $label, ])); } /** * @return array */ private function pauseFormSchema(string $controlKey): array { return [ Radio::make('scope_type') ->label('Scope') ->options([ 'global' => 'Global', 'workspace' => 'One workspace', ]) ->default('global') ->live() ->required(), Select::make('workspace_id') ->label('Workspace') ->searchable() ->visible(fn (callable $get): bool => $get('scope_type') === 'workspace') ->required(fn (callable $get): bool => $get('scope_type') === 'workspace') ->live() ->getSearchResultsUsing(function (string $search): array { return Workspace::query() ->where('name', 'like', "%{$search}%") ->orderBy('name') ->limit(25) ->pluck('name', 'id') ->all(); }) ->getOptionLabelUsing(function ($value): ?string { if (! is_numeric($value)) { return null; } return Workspace::query()->whereKey((int) $value)->value('name'); }), Textarea::make('reason_text') ->label('Reason') ->required() ->minLength(5) ->maxLength(500) ->rows(4), DateTimePicker::make('expires_at') ->label('Expires at') ->seconds(false) ->nullable(), Placeholder::make('scope_preview') ->label('Scope impact preview') ->content(function (callable $get) use ($controlKey): string { $preview = $this->scopeImpactPreview( $controlKey, (string) ($get('scope_type') ?? 'global'), is_numeric($get('workspace_id')) ? (int) $get('workspace_id') : null, ); return (string) $preview['summary']; }), ]; } /** * @return array */ private function resumeFormSchema(string $controlKey): array { return [ Radio::make('scope_type') ->label('Scope') ->options([ 'global' => 'Global', 'workspace' => 'One workspace', ]) ->default('global') ->live() ->required(), Select::make('workspace_id') ->label('Workspace') ->searchable() ->visible(fn (callable $get): bool => $get('scope_type') === 'workspace') ->required(fn (callable $get): bool => $get('scope_type') === 'workspace') ->getSearchResultsUsing(function (string $search): array { return Workspace::query() ->where('name', 'like', "%{$search}%") ->orderBy('name') ->limit(25) ->pluck('name', 'id') ->all(); }) ->getOptionLabelUsing(function ($value): ?string { if (! is_numeric($value)) { return null; } return Workspace::query()->whereKey((int) $value)->value('name'); }), Placeholder::make('scope_preview') ->label('Resume impact preview') ->content(function (callable $get) use ($controlKey): string { $preview = $this->scopeImpactPreview( $controlKey, (string) ($get('scope_type') ?? 'global'), is_numeric($get('workspace_id')) ? (int) $get('workspace_id') : null, ); return (string) $preview['summary']; }), ]; } private function controlsActor(): PlatformUser { $actor = auth('platform')->user(); if (! $actor instanceof PlatformUser) { abort(403); } if (! $actor->hasCapability(PlatformCapabilities::OPS_CONTROLS_MANAGE)) { abort(403); } return $actor; } /** * @return array{0: string, 1: ?Workspace, 2: string, 3: ?CarbonInterface} */ private function normalizePauseInput(array $data): array { [$scopeType, $workspace] = $this->resolveScopeInput($data); $reasonText = trim((string) ($data['reason_text'] ?? '')); if ($reasonText === '') { throw ValidationException::withMessages([ 'reason_text' => 'A reason is required.', ]); } $expiresAt = null; if (filled($data['expires_at'] ?? null)) { $expiresAt = Carbon::parse((string) $data['expires_at']); if ($expiresAt->lessThanOrEqualTo(now())) { throw ValidationException::withMessages([ 'expires_at' => 'Expiry must be in the future.', ]); } } return [$scopeType, $workspace, $reasonText, $expiresAt]; } /** * @return array{0: string, 1: ?Workspace} */ private function normalizeResumeInput(array $data): array { return $this->resolveScopeInput($data); } /** * @return array{0: string, 1: ?Workspace} */ private function resolveScopeInput(array $data): array { $scopeType = (string) ($data['scope_type'] ?? 'global'); if (! in_array($scopeType, ['global', 'workspace'], true)) { throw ValidationException::withMessages([ 'scope_type' => 'Invalid scope selected.', ]); } if ($scopeType === 'global') { return [$scopeType, null]; } $workspaceId = $data['workspace_id'] ?? null; if (! is_numeric($workspaceId)) { throw ValidationException::withMessages([ 'workspace_id' => 'A workspace is required for workspace scope.', ]); } $workspace = Workspace::query()->whereKey((int) $workspaceId)->first(); if (! $workspace instanceof Workspace) { throw ValidationException::withMessages([ 'workspace_id' => 'The selected workspace could not be found.', ]); } return [$scopeType, $workspace]; } private function activationScopeQuery(string $controlKey, string $scopeType, ?Workspace $workspace): \Illuminate\Database\Eloquent\Builder { $query = OperationalControlActivation::query() ->forControl($controlKey) ->where('scope_type', $scopeType); if ($scopeType === 'workspace') { $query->where('workspace_id', (int) $workspace?->getKey()); } else { $query->whereNull('workspace_id'); } return $query; } private function recordControlMutation( AuditActionId $auditAction, OperationalControlActivation $activation, PlatformUser $actor, AuditRecorder $auditRecorder, WorkspaceAuditLogger $workspaceAuditLogger, ): void { $label = app(OperationalControlCatalog::class)->label((string) $activation->control_key); $summary = sprintf('%s %s', $label, match ($auditAction) { AuditActionId::OperationalControlPaused => 'paused', AuditActionId::OperationalControlUpdated => 'updated', AuditActionId::OperationalControlResumed => 'resumed', default => 'changed', }); $metadata = array_filter([ 'control_key' => (string) $activation->control_key, 'scope_type' => (string) $activation->scope_type, 'workspace_id' => is_numeric($activation->workspace_id) ? (int) $activation->workspace_id : null, 'reason_text' => $activation->reason_text, 'expires_at' => $activation->expires_at?->toIso8601String(), 'actor_id' => (int) $actor->getKey(), ], static fn (mixed $value): bool => $value !== null && $value !== ''); if ((string) $activation->scope_type === 'global') { $auditRecorder->record( action: $auditAction, context: ['metadata' => $metadata], actor: AuditActorSnapshot::platform($actor), target: new AuditTargetSnapshot( type: 'operational_control', id: (string) $activation->getKey(), label: $label, ), outcome: 'success', summary: $summary, ); return; } $workspace = Workspace::query()->whereKey((int) $activation->workspace_id)->firstOrFail(); $workspaceAuditLogger->log( workspace: $workspace, action: $auditAction, context: ['metadata' => $metadata], actor: $actor, status: 'success', resourceType: 'operational_control', resourceId: (string) $activation->getKey(), targetLabel: $label, summary: $summary, ); } /** * @return Collection */ private function activeActivationsForControl(string $controlKey): Collection { return OperationalControlActivation::query() ->forControl($controlKey) ->notExpired() ->with(['workspace', 'createdBy', 'updatedBy']) ->orderByRaw("CASE WHEN scope_type = 'global' THEN 0 ELSE 1 END") ->orderBy('workspace_id') ->orderBy('id') ->get(); } /** * @return array */ private function activationSummary(OperationalControlActivation $activation): array { $owner = $activation->updatedBy ?? $activation->createdBy; $workspaceName = $activation->workspace?->name; return [ 'id' => (int) $activation->getKey(), 'scope_type' => (string) $activation->scope_type, 'scope_label' => (string) $activation->scope_type === 'global' ? 'Global' : sprintf('Workspace: %s', $workspaceName ?? '#'.(int) $activation->workspace_id), 'workspace_id' => is_numeric($activation->workspace_id) ? (int) $activation->workspace_id : null, 'workspace_name' => $workspaceName, 'reason_text' => (string) $activation->reason_text, 'expires_at' => $activation->expires_at?->toIso8601String(), 'expires_label' => $activation->expires_at?->diffForHumans() ?? 'No expiry', 'owner_name' => $owner?->name ?: $owner?->email ?: 'Unknown operator', ]; } /** * @return Collection */ private function recentAuditEventsForControl(string $controlKey): Collection { return AuditLog::query() ->where('metadata->control_key', $controlKey) ->whereIn('action', [ AuditActionId::OperationalControlPaused->value, AuditActionId::OperationalControlUpdated->value, AuditActionId::OperationalControlResumed->value, AuditActionId::OperationalControlExecutionBlocked->value, ]) ->latestFirst() ->limit(10) ->get(); } private function actionSlug(string $controlKey): string { return str_replace('.', '_', $controlKey); } }