schema([ TextInput::make('display_name') ->label('Display name') ->required() ->disabled(fn (): bool => ! UiEnforcement::for(Capabilities::PROVIDER_MANAGE)->isAllowed()) ->maxLength(255), TextInput::make('entra_tenant_id') ->label('Entra tenant ID') ->required() ->maxLength(255) ->disabled(fn (): bool => ! UiEnforcement::for(Capabilities::PROVIDER_MANAGE)->isAllowed()) ->rules(['uuid']), Toggle::make('is_default') ->label('Default connection') ->disabled(fn (): bool => ! UiEnforcement::for(Capabilities::PROVIDER_MANAGE)->isAllowed()) ->helperText('Exactly one default connection is required per tenant/provider.'), TextInput::make('status') ->label('Status') ->disabled() ->dehydrated(false), TextInput::make('health_status') ->label('Health') ->disabled() ->dehydrated(false), ]); } public static function table(Table $table): Table { return $table ->modifyQueryUsing(function (Builder $query): Builder { $tenantId = Tenant::current()?->getKey(); return $query->when($tenantId, fn (Builder $q) => $q->where('tenant_id', $tenantId)); }) ->defaultSort('display_name') ->columns([ Tables\Columns\TextColumn::make('display_name')->label('Name')->searchable(), Tables\Columns\TextColumn::make('provider')->label('Provider')->toggleable(), Tables\Columns\TextColumn::make('entra_tenant_id')->label('Entra tenant ID')->copyable()->toggleable(), Tables\Columns\IconColumn::make('is_default')->label('Default')->boolean(), Tables\Columns\TextColumn::make('status') ->label('Status') ->badge() ->formatStateUsing(BadgeRenderer::label(BadgeDomain::ProviderConnectionStatus)) ->color(BadgeRenderer::color(BadgeDomain::ProviderConnectionStatus)) ->icon(BadgeRenderer::icon(BadgeDomain::ProviderConnectionStatus)) ->iconColor(BadgeRenderer::iconColor(BadgeDomain::ProviderConnectionStatus)), Tables\Columns\TextColumn::make('health_status') ->label('Health') ->badge() ->formatStateUsing(BadgeRenderer::label(BadgeDomain::ProviderConnectionHealth)) ->color(BadgeRenderer::color(BadgeDomain::ProviderConnectionHealth)) ->icon(BadgeRenderer::icon(BadgeDomain::ProviderConnectionHealth)) ->iconColor(BadgeRenderer::iconColor(BadgeDomain::ProviderConnectionHealth)), Tables\Columns\TextColumn::make('last_health_check_at')->label('Last check')->since()->toggleable(), ]) ->filters([ SelectFilter::make('status') ->label('Status') ->options([ 'connected' => 'Connected', 'needs_consent' => 'Needs consent', 'error' => 'Error', 'disabled' => 'Disabled', ]) ->query(function (Builder $query, array $data): Builder { $value = $data['value'] ?? null; if (! is_string($value) || $value === '') { return $query; } return $query->where('status', $value); }), SelectFilter::make('health_status') ->label('Health') ->options([ 'ok' => 'OK', 'degraded' => 'Degraded', 'down' => 'Down', 'unknown' => 'Unknown', ]) ->query(function (Builder $query, array $data): Builder { $value = $data['value'] ?? null; if (! is_string($value) || $value === '') { return $query; } return $query->where('health_status', $value); }), ]) ->actions([ Actions\ActionGroup::make([ UiEnforcement::for(Capabilities::PROVIDER_MANAGE)->apply( Actions\EditAction::make(), ), UiEnforcement::for(Capabilities::PROVIDER_RUN)->apply(Actions\Action::make('check_connection') ->label('Check connection') ->icon('heroicon-o-check-badge') ->color('success') ->visible(fn (ProviderConnection $record): bool => $record->status !== 'disabled') ->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([ 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([ 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([ Actions\Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($result->run, $tenant)), ]) ->send(); })), UiEnforcement::for(Capabilities::PROVIDER_RUN)->apply(Actions\Action::make('inventory_sync') ->label('Inventory sync') ->icon('heroicon-o-arrow-path') ->color('info') ->visible(fn (ProviderConnection $record): bool => $record->status !== 'disabled') ->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([ 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([ 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([ Actions\Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($result->run, $tenant)), ]) ->send(); })), UiEnforcement::for(Capabilities::PROVIDER_RUN)->apply(Actions\Action::make('compliance_snapshot') ->label('Compliance snapshot') ->icon('heroicon-o-shield-check') ->color('info') ->visible(fn (ProviderConnection $record): bool => $record->status !== 'disabled') ->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([ 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([ 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([ Actions\Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($result->run, $tenant)), ]) ->send(); })), UiEnforcement::for(Capabilities::PROVIDER_MANAGE)->apply(Actions\Action::make('set_default') ->label('Set as default') ->icon('heroicon-o-star') ->color('primary') ->visible(fn (ProviderConnection $record): bool => $record->status !== 'disabled' && ! $record->is_default) ->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_MANAGE)->apply(Actions\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)->apply(Actions\Action::make('enable_connection') ->label('Enable connection') ->icon('heroicon-o-play') ->color('success') ->visible(fn (ProviderConnection $record): bool => $record->status === 'disabled') ->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)->apply(Actions\Action::make('disable_connection') ->label('Disable connection') ->icon('heroicon-o-archive-box-x-mark') ->color('danger') ->requiresConfirmation() ->visible(fn (ProviderConnection $record): bool => $record->status !== 'disabled') ->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'), ]) ->bulkActions([]); } public static function getEloquentQuery(): Builder { $tenantId = Tenant::current()?->getKey(); return parent::getEloquentQuery() ->when($tenantId, fn (Builder $query) => $query->where('tenant_id', $tenantId)) ->latest('id'); } public static function getPages(): array { return [ 'index' => Pages\ListProviderConnections::route('/'), 'create' => Pages\CreateProviderConnection::route('/create'), 'edit' => Pages\EditProviderConnection::route('/{record}/edit'), ]; } }