user(); if (! $user instanceof PlatformUser) { return false; } return $user->hasCapability(PlatformCapabilities::USE_BREAK_GLASS); } public static function getNavigationBadge(): ?string { $total = Workspace::query()->count(); $withOwners = WorkspaceMembership::query() ->where('role', WorkspaceRole::Owner->value) ->distinct('workspace_id') ->count('workspace_id'); $ownerless = $total - $withOwners; return $ownerless > 0 ? (string) $ownerless : null; } public static function getNavigationBadgeColor(): string|array|null { return 'danger'; } public function mount(): void { $this->mountInteractsWithTable(); } /** * @return array|WidgetConfiguration> */ protected function getHeaderWidgets(): array { return [ RepairWorkspaceOwnersStats::class, ]; } public function table(Table $table): Table { return $table ->heading('Workspaces') ->description('Current workspace ownership status.') ->defaultSort('name', 'asc') ->query(function (): Builder { return Workspace::query() ->withCount([ 'memberships as owner_count' => function (Builder $query): void { $query->where('role', WorkspaceRole::Owner->value); }, 'memberships as member_count', 'tenants as tenant_count', ]); }) ->columns([ TextColumn::make('name') ->label('Workspace') ->searchable() ->sortable(), TextColumn::make('owner_count') ->label('Owners') ->badge() ->color(fn (int $state): string => $state > 0 ? 'success' : 'danger') ->sortable(), TextColumn::make('member_count') ->label('Members') ->sortable(), TextColumn::make('tenant_count') ->label('Tenants') ->sortable(), TextColumn::make('updated_at') ->label('Last activity') ->since() ->sortable(), ]) ->emptyStateHeading('No workspaces') ->emptyStateDescription('No workspaces exist in the system yet.') ->bulkActions([]); } /** * @return array */ public function getRecentBreakGlassActions(): array { return AuditLog::query() ->where('action', 'like', '%break_glass%') ->orderByDesc('recorded_at') ->limit(10) ->get() ->map(fn (AuditLog $log): array => [ 'action' => (string) $log->action, 'actor' => $log->actor_email ?: 'Unknown', 'workspace' => $log->metadata['metadata']['workspace_id'] ?? null ? Workspace::query()->whereKey((int) $log->metadata['metadata']['workspace_id'])->value('name') : null, 'recorded_at' => $log->recorded_at?->diffForHumans() ?? 'Unknown', ]) ->all(); } /** * @return array */ protected function getHeaderActions(): array { $breakGlass = app(BreakGlassSession::class); return [ Action::make('assign_owner') ->label('Emergency: Assign Owner') ->icon('heroicon-o-shield-exclamation') ->color('danger') ->requiresConfirmation() ->modalHeading('Assign workspace owner') ->modalDescription('This is a recovery action. It is audited and should only be used when the workspace owner set is broken.') ->form([ Select::make('workspace_id') ->label('Workspace') ->required() ->searchable() ->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'); }), Select::make('target_user_id') ->label('User') ->required() ->searchable() ->getSearchResultsUsing(function (string $search): array { return User::query() ->where('email', 'like', "%{$search}%") ->orderBy('email') ->limit(25) ->pluck('email', 'id') ->all(); }) ->getOptionLabelUsing(function ($value): ?string { if (! is_numeric($value)) { return null; } return User::query()->whereKey((int) $value)->value('email'); }), Textarea::make('reason') ->label('Reason') ->required() ->minLength(5) ->maxLength(500) ->rows(4), ]) ->action(function (array $data, BreakGlassSession $breakGlass, WorkspaceAuditLogger $auditLogger): void { $platformUser = auth('platform')->user(); if (! $platformUser instanceof PlatformUser) { abort(403); } if (! $platformUser->hasCapability(PlatformCapabilities::USE_BREAK_GLASS)) { abort(403); } if (! $breakGlass->isActive()) { abort(403); } $workspaceId = (int) ($data['workspace_id'] ?? 0); $targetUserId = (int) ($data['target_user_id'] ?? 0); $reason = (string) ($data['reason'] ?? ''); $workspace = Workspace::query()->whereKey($workspaceId)->firstOrFail(); $targetUser = User::query()->whereKey($targetUserId)->firstOrFail(); $membership = WorkspaceMembership::query()->firstOrNew([ 'workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $targetUser->getKey(), ]); $fromRole = $membership->exists ? (string) $membership->role : null; $membership->forceFill([ 'role' => WorkspaceRole::Owner->value, ])->save(); $auditLogger->log( workspace: $workspace, action: AuditActionId::WorkspaceMembershipBreakGlassAssignOwner->value, context: [ 'metadata' => [ 'workspace_id' => (int) $workspace->getKey(), 'actor_user_id' => (int) $platformUser->getKey(), 'target_user_id' => (int) $targetUser->getKey(), 'attempted_role' => WorkspaceRole::Owner->value, 'from_role' => $fromRole, 'reason' => trim($reason), 'source' => 'break_glass', ], ], actor: null, status: 'success', resourceType: 'workspace', resourceId: (string) $workspace->getKey(), actorId: (int) $platformUser->getKey(), actorEmail: $platformUser->email, actorName: $platformUser->name, ); Notification::make() ->title('Owner assigned') ->success() ->send(); }) ->disabled(fn (): bool => ! $breakGlass->isActive()) ->tooltip(fn (): ?string => ! $breakGlass->isActive() ? 'Activate break-glass mode on the Dashboard first.' : null), ]; } }