shouldMakeDefault = (bool) ($data['is_default'] ?? false); unset($data['is_default']); return $data; } protected function afterSave(): void { $tenant = Tenant::current(); $record = $this->getRecord(); $changedFields = array_values(array_diff(array_keys($record->getChanges()), ['updated_at'])); if ($this->shouldMakeDefault && ! $record->is_default) { $record->makeDefault(); $this->defaultWasChanged = true; } $hasDefault = $tenant->providerConnections() ->where('provider', $record->provider) ->where('is_default', true) ->exists(); if (! $hasDefault) { $record->makeDefault(); $this->defaultWasChanged = true; } $user = auth()->user(); $actorId = $user instanceof User ? (int) $user->getKey() : null; $actorEmail = $user instanceof User ? $user->email : null; $actorName = $user instanceof User ? $user->name : null; if ($changedFields !== []) { app(AuditLogger::class)->log( tenant: $tenant, action: 'provider_connection.updated', context: [ 'metadata' => [ 'provider' => $record->provider, 'entra_tenant_id' => $record->entra_tenant_id, 'fields' => $changedFields, ], ], actorId: $actorId, actorEmail: $actorEmail, actorName: $actorName, resourceType: 'provider_connection', resourceId: (string) $record->getKey(), status: 'success', ); } if ($this->defaultWasChanged) { app(AuditLogger::class)->log( tenant: $tenant, action: 'provider_connection.default_set', context: [ 'metadata' => [ 'provider' => $record->provider, 'entra_tenant_id' => $record->entra_tenant_id, ], ], actorId: $actorId, actorEmail: $actorEmail, actorName: $actorName, resourceType: 'provider_connection', resourceId: (string) $record->getKey(), status: 'success', ); } } protected function getHeaderActions(): array { return [ Actions\DeleteAction::make() ->visible(false), Actions\ActionGroup::make([ UiEnforcement::for(Capabilities::PROVIDER_VIEW) ->andVisibleWhen(function (ProviderConnection $record): bool { $tenant = Tenant::current(); return $tenant instanceof Tenant && OperationRun::query() ->where('tenant_id', $tenant->getKey()) ->where('type', 'provider.connection.check') ->where('context->provider_connection_id', (int) $record->getKey()) ->exists(); }) ->apply( Action::make('view_last_check_run') ->label('View last check run') ->icon('heroicon-o-eye') ->color('gray') ->url(function (ProviderConnection $record): ?string { $tenant = Tenant::current(); if (! $tenant instanceof Tenant) { return null; } $run = OperationRun::query() ->where('tenant_id', $tenant->getKey()) ->where('type', 'provider.connection.check') ->where('context->provider_connection_id', (int) $record->getKey()) ->orderByDesc('id') ->first(); if (! $run instanceof OperationRun) { return null; } return OperationRunLinks::view($run, $tenant); }), ), UiEnforcement::for(Capabilities::PROVIDER_RUN) ->andVisibleWhen(fn (ProviderConnection $record): bool => $record->status !== 'disabled') ->apply( Action::make('check_connection') ->label('Check connection') ->icon('heroicon-o-check-badge') ->color('success') ->action(function (ProviderConnection $record, ProviderOperationStartGate $gate): void { UiEnforcement::for(Capabilities::PROVIDER_RUN)->authorizeOrAbort(); $tenant = Tenant::current(); $user = auth()->user(); if (! $tenant instanceof Tenant || ! $user instanceof User) { return; } $initiator = $user; $result = $gate->start( tenant: $tenant, connection: $record, operationType: 'provider.connection.check', dispatcher: function (OperationRun $operationRun) use ($tenant, $initiator, $record): void { ProviderConnectionHealthCheckJob::dispatch( tenantId: (int) $tenant->getKey(), userId: (int) $initiator->getKey(), providerConnectionId: (int) $record->getKey(), operationRun: $operationRun, ); }, initiator: $initiator, ); if ($result->status === 'scope_busy') { Notification::make() ->title('Scope busy') ->body('Another provider operation is already running for this connection.') ->warning() ->actions([ Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($result->run, $tenant)), ]) ->send(); return; } if ($result->status === 'deduped') { Notification::make() ->title('Run already queued') ->body('A connection check is already queued or running.') ->warning() ->actions([ Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($result->run, $tenant)), ]) ->send(); return; } Notification::make() ->title('Connection check queued') ->body('Health check was queued and will run in the background.') ->success() ->actions([ Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($result->run, $tenant)), ]) ->send(); }), ), UiEnforcement::for(Capabilities::PROVIDER_MANAGE)->apply( Action::make('update_credentials') ->label('Update credentials') ->icon('heroicon-o-key') ->color('primary') ->modalDescription('Client secret is stored encrypted and will never be shown again.') ->form([ TextInput::make('client_id') ->label('Client ID') ->required() ->maxLength(255), TextInput::make('client_secret') ->label('Client secret') ->password() ->required() ->maxLength(255), ]) ->action(function (array $data, ProviderConnection $record, CredentialManager $credentials, AuditLogger $auditLogger): void { UiEnforcement::for(Capabilities::PROVIDER_MANAGE)->authorizeOrAbort(); $tenant = Tenant::current(); if (! $tenant instanceof Tenant) { return; } $credentials->upsertClientSecretCredential( connection: $record, clientId: (string) $data['client_id'], clientSecret: (string) $data['client_secret'], ); $user = auth()->user(); $actorId = $user instanceof User ? (int) $user->getKey() : null; $actorEmail = $user instanceof User ? $user->email : null; $actorName = $user instanceof User ? $user->name : null; $auditLogger->log( tenant: $tenant, action: 'provider_connection.credentials_updated', context: [ 'metadata' => [ 'provider' => $record->provider, 'entra_tenant_id' => $record->entra_tenant_id, ], ], actorId: $actorId, actorEmail: $actorEmail, actorName: $actorName, resourceType: 'provider_connection', resourceId: (string) $record->getKey(), status: 'success', ); Notification::make() ->title('Credentials updated') ->success() ->send(); }), ), UiEnforcement::for(Capabilities::PROVIDER_MANAGE) ->andVisibleWhen(function (ProviderConnection $record): bool { $tenant = Tenant::current(); return $record->status !== 'disabled' && ! $record->is_default && $tenant instanceof Tenant && ProviderConnection::query() ->where('tenant_id', $tenant->getKey()) ->where('provider', $record->provider) ->count() > 1; }) ->apply( Action::make('set_default') ->label('Set as default') ->icon('heroicon-o-star') ->color('primary') ->action(function (ProviderConnection $record, AuditLogger $auditLogger): void { UiEnforcement::for(Capabilities::PROVIDER_MANAGE)->authorizeOrAbort(); $tenant = Tenant::current(); if (! $tenant instanceof Tenant) { return; } $record->makeDefault(); $user = auth()->user(); $actorId = $user instanceof User ? (int) $user->getKey() : null; $actorEmail = $user instanceof User ? $user->email : null; $actorName = $user instanceof User ? $user->name : null; $auditLogger->log( tenant: $tenant, action: 'provider_connection.default_set', context: [ 'metadata' => [ 'provider' => $record->provider, 'entra_tenant_id' => $record->entra_tenant_id, ], ], actorId: $actorId, actorEmail: $actorEmail, actorName: $actorName, resourceType: 'provider_connection', resourceId: (string) $record->getKey(), status: 'success', ); Notification::make() ->title('Default connection updated') ->success() ->send(); }), ), UiEnforcement::for(Capabilities::PROVIDER_RUN) ->andVisibleWhen(fn (ProviderConnection $record): bool => $record->status !== 'disabled') ->apply( Action::make('inventory_sync') ->label('Inventory sync') ->icon('heroicon-o-arrow-path') ->color('info') ->action(function (ProviderConnection $record, ProviderOperationStartGate $gate): void { UiEnforcement::for(Capabilities::PROVIDER_RUN)->authorizeOrAbort(); $tenant = Tenant::current(); $user = auth()->user(); if (! $tenant instanceof Tenant || ! $user instanceof User) { return; } $initiator = $user; $result = $gate->start( tenant: $tenant, connection: $record, operationType: 'inventory.sync', dispatcher: function (OperationRun $operationRun) use ($tenant, $initiator, $record): void { ProviderInventorySyncJob::dispatch( tenantId: (int) $tenant->getKey(), userId: (int) $initiator->getKey(), providerConnectionId: (int) $record->getKey(), operationRun: $operationRun, ); }, initiator: $initiator, ); if ($result->status === 'scope_busy') { Notification::make() ->title('Scope is busy') ->body('Another provider operation is already running for this connection.') ->danger() ->actions([ Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($result->run, $tenant)), ]) ->send(); return; } if ($result->status === 'deduped') { Notification::make() ->title('Run already queued') ->body('An inventory sync is already queued or running.') ->warning() ->actions([ Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($result->run, $tenant)), ]) ->send(); return; } Notification::make() ->title('Inventory sync queued') ->body('Inventory sync was queued and will run in the background.') ->success() ->actions([ Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($result->run, $tenant)), ]) ->send(); }), ), UiEnforcement::for(Capabilities::PROVIDER_RUN) ->andVisibleWhen(fn (ProviderConnection $record): bool => $record->status !== 'disabled') ->apply( Action::make('compliance_snapshot') ->label('Compliance snapshot') ->icon('heroicon-o-shield-check') ->color('info') ->action(function (ProviderConnection $record, ProviderOperationStartGate $gate): void { UiEnforcement::for(Capabilities::PROVIDER_RUN)->authorizeOrAbort(); $tenant = Tenant::current(); $user = auth()->user(); if (! $tenant instanceof Tenant || ! $user instanceof User) { return; } $initiator = $user; $result = $gate->start( tenant: $tenant, connection: $record, operationType: 'compliance.snapshot', dispatcher: function (OperationRun $operationRun) use ($tenant, $initiator, $record): void { ProviderComplianceSnapshotJob::dispatch( tenantId: (int) $tenant->getKey(), userId: (int) $initiator->getKey(), providerConnectionId: (int) $record->getKey(), operationRun: $operationRun, ); }, initiator: $initiator, ); if ($result->status === 'scope_busy') { Notification::make() ->title('Scope is busy') ->body('Another provider operation is already running for this connection.') ->danger() ->actions([ Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($result->run, $tenant)), ]) ->send(); return; } if ($result->status === 'deduped') { Notification::make() ->title('Run already queued') ->body('A compliance snapshot is already queued or running.') ->warning() ->actions([ Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($result->run, $tenant)), ]) ->send(); return; } Notification::make() ->title('Compliance snapshot queued') ->body('Compliance snapshot was queued and will run in the background.') ->success() ->actions([ Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($result->run, $tenant)), ]) ->send(); }), ), UiEnforcement::for(Capabilities::PROVIDER_MANAGE) ->andVisibleWhen(fn (ProviderConnection $record): bool => $record->status === 'disabled') ->apply( Action::make('enable_connection') ->label('Enable connection') ->icon('heroicon-o-play') ->color('success') ->action(function (ProviderConnection $record, AuditLogger $auditLogger): void { UiEnforcement::for(Capabilities::PROVIDER_MANAGE)->authorizeOrAbort(); $tenant = Tenant::current(); if (! $tenant instanceof Tenant) { return; } $hadCredentials = $record->credential()->exists(); $status = $hadCredentials ? 'connected' : 'needs_consent'; $previousStatus = (string) $record->status; $record->update([ 'status' => $status, 'health_status' => 'unknown', 'last_health_check_at' => null, 'last_error_reason_code' => null, 'last_error_message' => null, ]); $user = auth()->user(); $actorId = $user instanceof User ? (int) $user->getKey() : null; $actorEmail = $user instanceof User ? $user->email : null; $actorName = $user instanceof User ? $user->name : null; $auditLogger->log( tenant: $tenant, action: 'provider_connection.enabled', context: [ 'metadata' => [ 'provider' => $record->provider, 'entra_tenant_id' => $record->entra_tenant_id, 'from_status' => $previousStatus, 'to_status' => $status, 'credentials_present' => $hadCredentials, ], ], actorId: $actorId, actorEmail: $actorEmail, actorName: $actorName, resourceType: 'provider_connection', resourceId: (string) $record->getKey(), status: 'success', ); if (! $hadCredentials) { Notification::make() ->title('Connection enabled (credentials missing)') ->body('Add credentials before running checks or operations.') ->warning() ->send(); return; } Notification::make() ->title('Provider connection enabled') ->success() ->send(); }), ), UiEnforcement::for(Capabilities::PROVIDER_MANAGE) ->andVisibleWhen(fn (ProviderConnection $record): bool => $record->status !== 'disabled') ->apply( Action::make('disable_connection') ->label('Disable connection') ->icon('heroicon-o-archive-box-x-mark') ->color('danger') ->requiresConfirmation() ->action(function (ProviderConnection $record, AuditLogger $auditLogger): void { UiEnforcement::for(Capabilities::PROVIDER_MANAGE)->authorizeOrAbort(); $tenant = Tenant::current(); if (! $tenant instanceof Tenant) { return; } $previousStatus = (string) $record->status; $record->update([ 'status' => 'disabled', ]); $user = auth()->user(); $actorId = $user instanceof User ? (int) $user->getKey() : null; $actorEmail = $user instanceof User ? $user->email : null; $actorName = $user instanceof User ? $user->name : null; $auditLogger->log( tenant: $tenant, action: 'provider_connection.disabled', context: [ 'metadata' => [ 'provider' => $record->provider, 'entra_tenant_id' => $record->entra_tenant_id, 'from_status' => $previousStatus, ], ], actorId: $actorId, actorEmail: $actorEmail, actorName: $actorName, resourceType: 'provider_connection', resourceId: (string) $record->getKey(), status: 'success', ); Notification::make() ->title('Provider connection disabled') ->warning() ->send(); }), ), ]) ->label('Actions') ->icon('heroicon-o-ellipsis-vertical') ->color('gray'), ]; } protected function getFormActions(): array { if (UiEnforcement::for(Capabilities::PROVIDER_MANAGE)->isAllowed()) { return parent::getFormActions(); } return [ $this->getCancelFormAction(), ]; } protected function handleRecordUpdate(Model $record, array $data): Model { UiEnforcement::for(Capabilities::PROVIDER_MANAGE)->authorizeOrAbort(); return parent::handleRecordUpdate($record, $data); } }