, * attention_items: list, * attention_empty_state: array{ * title: string, * body: string, * action_label: string, * action_url: string * }, * recent_operations: list, * recent_operations_empty_state: array{ * title: string, * body: string, * action_label: string, * action_url: string * }, * quick_actions: list, * zero_tenant_state: ?array{ * title: string, * body: string, * action_label: string, * action_url: string * } * } */ public function build(Workspace $workspace, User $user): array { $accessibleTenants = $this->accessibleTenants($workspace, $user); $accessibleTenantIds = $accessibleTenants ->pluck('id') ->map(static fn (mixed $id): int => (int) $id) ->all(); $canViewAlerts = $this->workspaceCapabilityResolver->can($user, $workspace, Capabilities::ALERTS_VIEW); $recentOperations = $this->recentOperations((int) $workspace->getKey(), $accessibleTenantIds); $attentionItems = $this->attentionItems((int) $workspace->getKey(), $accessibleTenantIds, $canViewAlerts); $quickActions = $this->quickActions($workspace, $accessibleTenants->count(), $canViewAlerts, $user); $zeroTenantState = null; if ($accessibleTenants->isEmpty()) { $fallbackAction = collect($quickActions) ->first(fn (array $action): bool => in_array($action['key'], ['manage_workspaces', 'switch_workspace'], true)); $zeroTenantState = [ 'title' => 'No accessible tenants in this workspace', 'body' => 'You can still review workspace-wide operations or switch to another workspace while tenant access is being set up.', 'action_label' => is_array($fallbackAction) ? $fallbackAction['label'] : 'Switch workspace', 'action_url' => is_array($fallbackAction) ? $fallbackAction['url'] : $this->switchWorkspaceUrl(), ]; } return [ 'workspace' => [ 'id' => (int) $workspace->getKey(), 'name' => (string) $workspace->name, 'slug' => filled($workspace->slug) ? (string) $workspace->slug : null, ], 'accessible_tenant_count' => $accessibleTenants->count(), 'summary_metrics' => $this->summaryMetrics( workspaceId: (int) $workspace->getKey(), accessibleTenantCount: $accessibleTenants->count(), accessibleTenantIds: $accessibleTenantIds, canViewAlerts: $canViewAlerts, needsAttentionCount: count($attentionItems), ), 'attention_items' => $attentionItems, 'attention_empty_state' => [ 'title' => 'Nothing urgent in your current scope', 'body' => 'Recent operations and alert deliveries look healthy right now.', 'action_label' => $canViewAlerts ? 'Open alerts' : 'Open operations', 'action_url' => $canViewAlerts ? '/admin/alerts' : route('admin.operations.index'), ], 'recent_operations' => $recentOperations, 'recent_operations_empty_state' => [ 'title' => 'No recent operations yet', 'body' => 'Workspace-wide activity will show up here once syncs, evaluations, or restores start running.', 'action_label' => 'Open operations', 'action_url' => route('admin.operations.index'), ], 'quick_actions' => $quickActions, 'zero_tenant_state' => $zeroTenantState, ]; } /** * @return Collection */ private function accessibleTenants(Workspace $workspace, User $user): Collection { return Tenant::query() ->where('workspace_id', (int) $workspace->getKey()) ->where('status', 'active') ->whereIn('id', $user->tenantMemberships()->select('tenant_id')) ->orderBy('name') ->get(['id', 'name', 'external_id', 'workspace_id']); } /** * @param array $accessibleTenantIds * @return list */ private function summaryMetrics( int $workspaceId, int $accessibleTenantCount, array $accessibleTenantIds, bool $canViewAlerts, int $needsAttentionCount, ): array { $activeOperationsCount = (int) $this->scopeToAuthorizedTenants( OperationRun::query(), $workspaceId, $accessibleTenantIds, ) ->whereIn('status', [ OperationRunStatus::Queued->value, OperationRunStatus::Running->value, ]) ->count(); $metrics = [ [ 'key' => 'accessible_tenants', 'label' => 'Accessible tenants', 'value' => $accessibleTenantCount, 'description' => $accessibleTenantCount > 0 ? 'Tenant drill-down stays explicit from this workspace home.' : 'No tenant memberships are available in this workspace yet.', 'destination_url' => $accessibleTenantCount > 0 ? ChooseTenant::getUrl(panel: 'admin') : null, 'color' => 'primary', ], [ 'key' => 'active_operations', 'label' => 'Active operations', 'value' => $activeOperationsCount, 'description' => 'Workspace-wide runs that are still queued or in progress.', 'destination_url' => route('admin.operations.index'), 'color' => $activeOperationsCount > 0 ? 'warning' : 'gray', ], ]; if ($canViewAlerts) { $failedAlertDeliveriesCount = (int) $this->scopeToAuthorizedTenants( AlertDelivery::query(), $workspaceId, $accessibleTenantIds, ) ->where('created_at', '>=', now()->subDays(7)) ->where('status', AlertDelivery::STATUS_FAILED) ->count(); $metrics[] = [ 'key' => 'alerts', 'label' => 'Alert failures (7d)', 'value' => $failedAlertDeliveriesCount, 'description' => 'Failed alert deliveries in the last 7 days.', 'destination_url' => AlertDeliveryResource::getUrl(panel: 'admin'), 'color' => $failedAlertDeliveriesCount > 0 ? 'danger' : 'gray', ]; } $metrics[] = [ 'key' => 'needs_attention', 'label' => 'Needs attention', 'value' => $needsAttentionCount, 'description' => 'Urgent workspace-safe items surfaced below.', 'destination_url' => $needsAttentionCount > 0 ? route('admin.operations.index') : null, 'color' => $needsAttentionCount > 0 ? 'warning' : 'gray', ]; return $metrics; } /** * @param array $accessibleTenantIds * @return list */ private function attentionItems(int $workspaceId, array $accessibleTenantIds, bool $canViewAlerts): array { $items = []; $latestFailedRun = $this->scopeToAuthorizedTenants( OperationRun::query()->with('tenant'), $workspaceId, $accessibleTenantIds, ) ->where('status', OperationRunStatus::Completed->value) ->whereIn('outcome', [ OperationRunOutcome::Failed->value, OperationRunOutcome::PartiallySucceeded->value, ]) ->latest('created_at') ->first(); if ($latestFailedRun instanceof OperationRun) { $items[] = [ 'title' => OperationCatalog::label((string) $latestFailedRun->type).' needs review', 'body' => 'Latest outcome: '.BadgeRenderer::spec(BadgeDomain::OperationRunOutcome, $latestFailedRun->outcome)->label.'.', 'url' => route('admin.operations.view', ['run' => (int) $latestFailedRun->getKey()]), 'badge' => 'Operations', 'badge_color' => $latestFailedRun->outcome === OperationRunOutcome::Failed->value ? 'danger' : 'warning', ]; } $activeRunsCount = (int) $this->scopeToAuthorizedTenants( OperationRun::query(), $workspaceId, $accessibleTenantIds, ) ->whereIn('status', [ OperationRunStatus::Queued->value, OperationRunStatus::Running->value, ]) ->count(); if ($activeRunsCount > 0) { $items[] = [ 'title' => 'Operations are still running', 'body' => $activeRunsCount.' workspace run(s) are active right now.', 'url' => route('admin.operations.index'), 'badge' => 'Operations', 'badge_color' => 'warning', ]; } if ($canViewAlerts) { $failedAlertDeliveriesCount = (int) $this->scopeToAuthorizedTenants( AlertDelivery::query(), $workspaceId, $accessibleTenantIds, ) ->where('created_at', '>=', now()->subDays(7)) ->where('status', AlertDelivery::STATUS_FAILED) ->count(); if ($failedAlertDeliveriesCount > 0) { $items[] = [ 'title' => 'Alert deliveries failed', 'body' => $failedAlertDeliveriesCount.' alert delivery attempt(s) failed in the last 7 days.', 'url' => AlertDeliveryResource::getUrl(panel: 'admin'), 'badge' => 'Alerts', 'badge_color' => 'danger', ]; } } return array_slice($items, 0, 5); } /** * @param array $accessibleTenantIds * @return list */ private function recentOperations(int $workspaceId, array $accessibleTenantIds): array { $statusSpec = BadgeRenderer::label(BadgeDomain::OperationRunStatus); $statusColorSpec = BadgeRenderer::color(BadgeDomain::OperationRunStatus); $outcomeSpec = BadgeRenderer::label(BadgeDomain::OperationRunOutcome); $outcomeColorSpec = BadgeRenderer::color(BadgeDomain::OperationRunOutcome); return $this->scopeToAuthorizedTenants( OperationRun::query()->with('tenant'), $workspaceId, $accessibleTenantIds, ) ->latest('created_at') ->limit(5) ->get() ->map(function (OperationRun $run) use ($statusSpec, $statusColorSpec, $outcomeSpec, $outcomeColorSpec): array { return [ 'id' => (int) $run->getKey(), 'title' => OperationCatalog::label((string) $run->type), 'tenant_label' => $run->tenant instanceof Tenant ? (string) $run->tenant->name : null, 'status_label' => $statusSpec($run->status), 'status_color' => $statusColorSpec($run->status), 'outcome_label' => $outcomeSpec($run->outcome), 'outcome_color' => $outcomeColorSpec($run->outcome), 'guidance' => OperationUxPresenter::surfaceGuidance($run), 'started_at' => $run->created_at?->diffForHumans() ?? 'just now', 'url' => route('admin.operations.view', ['run' => (int) $run->getKey()]), ]; }) ->all(); } /** * @param array $accessibleTenantIds */ private function scopeToAuthorizedTenants(Builder $query, int $workspaceId, array $accessibleTenantIds): Builder { return $query ->where('workspace_id', $workspaceId) ->where(function (Builder $query) use ($accessibleTenantIds): void { $query->whereNull('tenant_id'); if ($accessibleTenantIds !== []) { $query->orWhereIn('tenant_id', $accessibleTenantIds); } }); } /** * @return list */ private function quickActions(Workspace $workspace, int $accessibleTenantCount, bool $canViewAlerts, User $user): array { $actions = [ [ 'key' => 'choose_tenant', 'label' => 'Choose tenant', 'description' => 'Deliberately enter tenant context from this workspace.', 'url' => ChooseTenant::getUrl(panel: 'admin'), 'icon' => 'heroicon-o-building-office-2', 'color' => 'primary', 'visible' => $accessibleTenantCount > 0, ], [ 'key' => 'operations', 'label' => 'Open operations', 'description' => 'Review current and recent workspace-wide runs.', 'url' => route('admin.operations.index'), 'icon' => 'heroicon-o-queue-list', 'color' => 'gray', 'visible' => true, ], [ 'key' => 'alerts', 'label' => 'Open alerts', 'description' => 'Inspect alert overview, rules, and deliveries.', 'url' => '/admin/alerts', 'icon' => 'heroicon-o-bell-alert', 'color' => 'gray', 'visible' => $canViewAlerts, ], [ 'key' => 'switch_workspace', 'label' => 'Switch workspace', 'description' => 'Change the active workspace context.', 'url' => $this->switchWorkspaceUrl(), 'icon' => 'heroicon-o-arrows-right-left', 'color' => 'gray', 'visible' => true, ], [ 'key' => 'manage_workspaces', 'label' => 'Manage workspaces', 'description' => 'Open workspace management and memberships.', 'url' => route('filament.admin.resources.workspaces.index'), 'icon' => 'heroicon-o-squares-2x2', 'color' => 'gray', 'visible' => $this->canManageWorkspaces($workspace, $user), ], ]; return collect($actions) ->filter(fn (array $action): bool => (bool) $action['visible']) ->map(function (array $action): array { unset($action['visible']); return $action; }) ->values() ->all(); } private function canManageWorkspaces(Workspace $workspace, User $user): bool { if ($this->workspaceCapabilityResolver->can($user, $workspace, Capabilities::WORKSPACE_MANAGE)) { return true; } $roles = WorkspaceRoleCapabilityMap::rolesWithCapability(Capabilities::WORKSPACE_MEMBERSHIP_MANAGE); return $user->workspaceMemberships() ->whereIn('role', $roles) ->exists(); } private function switchWorkspaceUrl(): string { return route('filament.admin.pages.choose-workspace').'?choose=1'; } }