*/ private const array LANE_DEFINITIONS = [ [ 'key' => 'needs_triage', 'label' => 'Needs triage', 'description' => 'Unassigned findings that still need a first operator path.', 'empty_state' => 'No unassigned findings need first triage in the current scope.', ], [ 'key' => 'requires_decision', 'label' => 'Requires decision', 'description' => 'Assigned governance work and review follow-up that still need operator judgment.', 'empty_state' => 'No operator-decision items are open in the current scope.', ], [ 'key' => 'risk_exception_review', 'label' => 'Risk / exception review', 'description' => 'Accepted-risk and exception records that need approval, renewal, closure, or support review.', 'empty_state' => 'No accepted-risk or exception records need review in the current scope.', ], [ 'key' => 'evidence_required', 'label' => 'Evidence required', 'description' => 'Governance items that are still missing the linked proof needed for follow-through.', 'empty_state' => 'No visible governance items are blocked by missing evidence in the current scope.', ], [ 'key' => 'blocked', 'label' => 'Blocked', 'description' => 'Technical follow-up is still blocked on failed runs or delivery issues.', 'empty_state' => 'No blocked governance follow-up is visible in the current scope.', ], ]; protected static bool $isDiscovered = false; protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-inbox-stack'; protected static string|UnitEnum|null $navigationGroup = 'Governance'; protected static ?string $navigationLabel = 'Governance inbox'; protected static ?int $navigationSort = 5; protected static ?string $title = 'Governance Inbox'; protected static ?string $slug = 'governance/inbox'; protected string $view = 'filament.pages.governance.governance-inbox'; /** * @var array|null */ private ?array $authorizedTenants = null; /** * @var array|null */ private ?array $visibleFindingTenants = null; /** * @var array|null */ private ?array $reviewTenants = null; /** * @var array|null */ private ?array $inboxPayload = null; /** * @var array|null */ private ?array $unfilteredInboxPayload = null; /** * @var array|null */ private ?array $lanePayload = null; /** * @var array|null */ private ?array $recentlyResolvedPayload = null; private ?Workspace $workspace = null; private ?bool $visibleAlertsFamily = null; private ?bool $visibleFindingExceptionsFamily = null; public ?int $tenantId = null; public ?string $family = null; public function getSubheading(): ?string { return 'Daily operator queue for governance follow-up, accepted risk, evidence gaps, and review handoff.'; } public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration { return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly, ActionSurfaceType::ReadOnlyRegistryReport) ->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions keep the workspace governance queue calm and read-only.') ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value) ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'The governance inbox routes into existing source surfaces instead of exposing row-level secondary menus.') ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The governance inbox does not expose bulk actions.') ->satisfy(ActionSurfaceSlot::ListEmptyState, 'Empty states stay calm and explain what operator work appears here.') ->exempt(ActionSurfaceSlot::DetailHeader, 'The governance inbox owns no local detail surface or mutation workflow.'); } public static function getNavigationGroup(): string { return WorkspaceHubNavigation::workspaceWideGroup(__('localization.navigation.governance')); } public static function getNavigationUrl(): string { return WorkspaceHubNavigation::environmentFilteredUrl(static::getUrl(panel: 'admin')); } public function mount(): void { $this->authorizeWorkspaceMembership(); $this->applyRequestedTenantPrefilter(); $this->family = $this->resolveRequestedFamily(); $this->ensureAtLeastOneVisibleFamily(); $this->ensureRequestedFamilyIsVisible(); } /** * @return array */ public function appliedScope(): array { $selectedTenant = $this->selectedTenant(); $availableFamilies = collect($this->availableFamilies())->keyBy('key'); return [ 'workspace_label' => $this->workspace()?->name, 'tenant_label' => $selectedTenant?->name, 'tenant_prefilter_source' => $selectedTenant instanceof ManagedEnvironment ? 'explicit_filter' : 'none', 'family_key' => $this->family, 'family_label' => $this->family !== null ? ($availableFamilies->get($this->family)['label'] ?? Str::headline($this->family)) : 'All source families', 'total_count' => (int) ($this->inboxPayload()['total_count'] ?? 0), ]; } /** * @return list */ public function availableFamilies(): array { return collect($this->inboxPayload()['available_families'] ?? []) ->map(fn (array $family): array => $this->normalizeFamily($family)) ->values() ->all(); } /** * @return list> */ public function sections(): array { return collect($this->inboxPayload()['sections'] ?? []) ->map(fn (array $section): array => $this->normalizeSection($section)) ->values() ->all(); } /** * @return array{ * total_open_items: int, * headline: string, * counts: list, * active_counts: list, * clear_counts: list, * primary_action: array{label: string, url: string}|null, * next_recommended_item: array|null, * review_ready_supported: bool, * } */ public function operatorSummary(): array { return $this->lanePayload()['summary']; } /** * @return list> */ public function laneGroups(): array { return $this->lanePayload()['lanes']; } /** * @return array|null */ public function recentlyResolved(): ?array { if (is_array($this->recentlyResolvedPayload)) { return $this->recentlyResolvedPayload === [] ? null : $this->recentlyResolvedPayload; } if (! $this->hasVisibleFindingExceptionsFamily()) { $this->recentlyResolvedPayload = []; return null; } if ($this->family !== null && $this->family !== 'finding_exceptions') { $this->recentlyResolvedPayload = []; return null; } $workspace = $this->workspace(); if (! $workspace instanceof Workspace) { $this->recentlyResolvedPayload = []; return null; } $visibleTenants = $this->selectedTenant() instanceof ManagedEnvironment ? [$this->selectedTenant()] : $this->authorizedTenants(); $payload = app(GovernanceDecisionRegisterBuilder::class)->build( workspace: $workspace, visibleTenants: $visibleTenants, registerState: 'recently_closed', ); $count = (int) ($payload['counts']['recently_closed'] ?? 0); if ($count === 0) { $this->recentlyResolvedPayload = []; return null; } $selectedTenant = $this->selectedTenant(); $openUrl = DecisionRegister::getUrl( panel: 'admin', parameters: array_filter([ 'environment_id' => $selectedTenant?->getKey(), 'register_state' => 'recently_closed', ], static fn (mixed $value): bool => $value !== null && $value !== ''), ); $rows = collect($payload['rows'] ?? []) ->take(3) ->map(function (array $row) use ($openUrl): array { return [ 'title' => filled($row['tenant_name'] ?? null) ? (string) $row['tenant_name'] : 'Recently closed decision', 'reason' => filled($row['closure_reason'] ?? null) ? (string) $row['closure_reason'] : 'Decision closed in the register.', 'next_action_label' => 'Open decision register', 'next_action_url' => $openUrl, ]; }) ->values() ->all(); return $this->recentlyResolvedPayload = [ 'label' => 'Recently resolved', 'count' => $count, 'summary' => sprintf( '%d recently closed decision%s remain available in the Decision Register for reference and audit follow-through.', $count, $count === 1 ? '' : 's', ), 'open_url' => $openUrl, 'open_label' => 'Open recently closed decisions', 'rows' => $rows, ]; } /** * @return array{label: string, state: string, body: string} */ public function diagnosticsPanel(): array { return [ 'label' => 'Diagnostics / source detail', 'state' => 'Collapsed', 'body' => 'Raw diagnostics, payloads, and support detail stay on authorized source surfaces. This inbox keeps the operator queue, proof links, and next actions on the first screen.', ]; } /** * @return array */ public function calmEmptyState(): array { if ($this->tenantFilterAloneExcludesRows()) { return [ 'title' => 'This environment filter is hiding other visible attention', 'body' => 'The current environment scope is calm, but other visible environments in this workspace still have governance work that needs follow-up.', 'action_label' => 'Clear environment filter', 'action_url' => $this->pageUrl(['environment_id' => null, 'family' => null]), ]; } if ($this->familyFilterAloneExcludesRows()) { return [ 'title' => 'This source focus is hiding other governance work', 'body' => 'The current source-family focus is calm, but other repo-backed governance items remain open in this workspace.', 'action_label' => 'Show all source families', 'action_url' => $this->pageUrl(['family' => null]), ]; } return [ 'title' => 'No governance items need attention.', 'body' => 'Findings, decisions, accepted-risk reviews, evidence gaps, and review follow-ups will appear here when they need operator attention.', 'action_label' => null, 'action_url' => null, ]; } public function hasTenantPrefilter(): bool { return $this->selectedTenant() instanceof ManagedEnvironment; } public function isActiveFamily(?string $familyKey): bool { return $this->family === $familyKey; } public function pageUrl(array $overrides = []): string { $selectedTenant = $this->selectedTenant(); $resolvedTenant = array_key_exists('environment_id', $overrides) ? $overrides['environment_id'] : ($selectedTenant?->getKey() !== null ? (string) $selectedTenant->getKey() : null); $resolvedFamily = array_key_exists('family', $overrides) ? $overrides['family'] : $this->family; return static::getUrl( panel: 'admin', parameters: array_filter([ 'environment_id' => (is_string($resolvedTenant) || is_numeric($resolvedTenant)) && (string) $resolvedTenant !== '' ? (string) $resolvedTenant : null, 'family' => is_string($resolvedFamily) && $resolvedFamily !== '' ? $resolvedFamily : null, ], static fn (mixed $value): bool => $value !== null && $value !== ''), ); } public function navigationContext(): CanonicalNavigationContext { return CanonicalNavigationContext::forGovernanceInbox( canonicalRouteName: static::getRouteName(Filament::getPanel('admin')), tenantId: $this->tenantId, backLinkUrl: $this->pageUrl(), familyKey: $this->family, ); } /** * @return \Illuminate\Support\Collection> */ private function workbenchEntries(): \Illuminate\Support\Collection { return collect($this->sections()) ->flatMap(function (array $section): array { $entries = is_array($section['entries'] ?? null) ? $section['entries'] : []; return array_map(function (array $entry) use ($section): array { $entry['section_key'] = (string) ($section['key'] ?? $entry['family_key'] ?? 'governance'); $entry['section_label'] = (string) ($section['label'] ?? 'Governance item'); return $entry; }, $entries); }) ->values(); } /** * @return array */ private function lanePayload(): array { if (is_array($this->lanePayload)) { return $this->lanePayload; } $lanes = collect(self::LANE_DEFINITIONS) ->mapWithKeys(fn (array $definition): array => [ $definition['key'] => [ ...$definition, 'anchor_id' => 'lane-'.$definition['key'], 'count' => 0, 'items' => [], ], ]) ->all(); foreach ($this->workbenchEntries() as $entry) { $operatorItem = $this->buildOperatorItem($entry); $laneKey = (string) $operatorItem['lane_key']; if (! array_key_exists($laneKey, $lanes)) { continue; } $lanes[$laneKey]['items'][] = $operatorItem; $lanes[$laneKey]['count']++; } foreach ($lanes as $key => $lane) { $lanes[$key]['items'] = collect($lane['items']) ->sortBy([ fn (array $item): int => (int) ($item['urgency_rank'] ?? 999), fn (array $item): string => (string) ($item['title'] ?? ''), ]) ->take(self::LANE_PREVIEW_LIMIT) ->values() ->all(); } $visibleLanes = collect(self::LANE_DEFINITIONS) ->map(fn (array $definition): array => $lanes[$definition['key']]) ->filter(fn (array $lane): bool => (int) $lane['count'] > 0) ->values() ->all(); $summaryCounts = collect(self::LANE_DEFINITIONS) ->map(fn (array $definition): array => [ 'key' => $definition['key'], 'label' => $definition['label'], 'count' => (int) ($lanes[$definition['key']]['count'] ?? 0), 'description' => $definition['description'], 'state' => (int) ($lanes[$definition['key']]['count'] ?? 0) > 0 ? 'active' : 'clear', 'chip_label' => (int) ($lanes[$definition['key']]['count'] ?? 0) > 0 ? (string) ((int) ($lanes[$definition['key']]['count'] ?? 0)) : 'Clear', ]) ->values() ->all(); $totalOpenItems = collect($summaryCounts)->sum('count'); $nextRecommendedItem = $this->nextRecommendedItem($visibleLanes); return $this->lanePayload = [ 'lanes' => $visibleLanes, 'summary' => [ 'total_open_items' => $totalOpenItems, 'headline' => $totalOpenItems === 1 ? '1 open governance item needs attention' : sprintf('%d open governance items need attention', $totalOpenItems), 'counts' => $summaryCounts, 'active_counts' => collect($summaryCounts)->where('state', 'active')->values()->all(), 'clear_counts' => collect($summaryCounts)->where('state', 'clear')->values()->all(), 'primary_action' => $nextRecommendedItem['primary_action'] ?? null, 'next_recommended_item' => $nextRecommendedItem, 'review_ready_supported' => false, ], ]; } /** * @param array $entry * @return array */ private function buildOperatorItem(array $entry): array { $laneKey = $this->classifyLane($entry); $tenant = $this->tenantForEntry($entry); $primaryActionUrl = filled($entry['primary_action_url'] ?? null) ? (string) $entry['primary_action_url'] : (filled($entry['destination_url'] ?? null) ? (string) $entry['destination_url'] : null); $primaryAction = $this->normalizeAction([ 'label' => (string) ($entry['primary_action_label'] ?? 'Open source'), 'url' => $primaryActionUrl, ]); return [ 'lane_key' => $laneKey, 'lane_label' => $this->laneDefinition($laneKey)['label'], 'title' => (string) ($entry['headline'] ?? 'Governance item'), 'status_label' => (string) ($entry['status_label'] ?? 'Needs attention'), 'reason_heading' => $laneKey === 'blocked' ? 'Blocker' : 'Reason', 'reason_label' => (string) ($entry['reason_label'] ?? 'Reason unavailable'), 'impact_label' => (string) ($entry['impact_label'] ?? 'Impact unavailable'), 'source_label' => (string) ($entry['section_label'] ?? 'Source record'), 'environment_label' => filled($entry['tenant_label'] ?? null) ? (string) $entry['tenant_label'] : 'Workspace-wide', 'context_label' => filled($entry['subline'] ?? null) ? (string) $entry['subline'] : null, 'owner_label' => (string) ($entry['owner_label'] ?? 'Owner unavailable'), 'due_label' => (string) ($entry['due_label'] ?? 'Due date unavailable'), 'evidence_label' => (string) ($entry['evidence_label'] ?? 'Evidence unavailable'), 'exception_label' => (string) ($entry['exception_label'] ?? 'Accepted-risk state unavailable'), 'primary_action' => $primaryAction, 'secondary_actions' => $this->secondaryActionsForEntry($entry, $tenant, $primaryActionUrl), 'linked_records' => $this->linkedRecordsForEntry($entry, $tenant), 'urgency_rank' => (int) ($entry['urgency_rank'] ?? 999), ]; } /** * @param array $entry */ private function classifyLane(array $entry): string { $familyKey = (string) ($entry['family_key'] ?? ''); return match ($familyKey) { 'intake_findings' => 'needs_triage', 'finding_exceptions' => 'risk_exception_review', 'stale_operations', 'alert_delivery_failures' => 'blocked', 'assigned_findings' => (($entry['evidence_state'] ?? null) === 'missing') ? 'evidence_required' : 'requires_decision', 'review_follow_up' => 'requires_decision', default => 'requires_decision', }; } /** * @return array{key: string, label: string, description: string, empty_state: string} */ private function laneDefinition(string $laneKey): array { foreach (self::LANE_DEFINITIONS as $definition) { if ($definition['key'] === $laneKey) { return $definition; } } return self::LANE_DEFINITIONS[1]; } /** * @param list> $visibleLanes */ private function nextRecommendedItem(array $visibleLanes): ?array { foreach ($visibleLanes as $lane) { $items = is_array($lane['items'] ?? null) ? $lane['items'] : []; foreach ($items as $item) { if (! is_array($item)) { continue; } $primaryAction = $this->normalizeAction($item['primary_action'] ?? null); if ($primaryAction === null) { continue; } return [ 'headline' => $this->recommendedActionHeadline($item, $primaryAction), 'lane_key' => (string) ($lane['key'] ?? $item['lane_key'] ?? 'requires_decision'), 'lane_label' => (string) ($lane['label'] ?? $item['lane_label'] ?? 'Requires decision'), 'lane_url' => filled($lane['anchor_id'] ?? null) ? '#'.$lane['anchor_id'] : null, 'title' => (string) ($item['title'] ?? 'Governance item'), 'status_label' => (string) ($item['status_label'] ?? 'Needs attention'), 'environment_label' => (string) ($item['environment_label'] ?? 'Workspace-wide'), 'reason_heading' => (string) ($item['reason_heading'] ?? 'Reason'), 'reason_label' => (string) ($item['reason_label'] ?? 'Reason unavailable'), 'impact_label' => (string) ($item['impact_label'] ?? 'Impact unavailable'), 'primary_action' => $primaryAction, ]; } } return null; } /** * @param array $item * @param array{label: string, url: string} $primaryAction */ private function recommendedActionHeadline(array $item, array $primaryAction): string { $title = (string) ($item['title'] ?? 'Governance item'); $actionLabel = (string) ($primaryAction['label'] ?? 'Open source'); $lowerAction = Str::lower($actionLabel); if (Str::startsWith($title, 'Finding #') && Str::contains($lowerAction, 'finding')) { $verb = trim((string) Str::of($actionLabel)->before(' finding')); if ($verb !== '') { return Str::ucfirst($verb).' '.$title; } } if (Str::startsWith($title, 'Terminal follow-up operation') && Str::contains($lowerAction, 'operation')) { return 'Open terminal operation proof'; } return $actionLabel.': '.$title; } /** * @param array $entry * @return list */ private function secondaryActionsForEntry(array $entry, ?ManagedEnvironment $tenant, ?string $primaryActionUrl): array { $familyKey = (string) ($entry['family_key'] ?? ''); $actions = []; $environmentId = is_numeric($entry['managed_environment_id'] ?? null) ? (int) $entry['managed_environment_id'] : null; if (in_array($familyKey, ['assigned_findings', 'intake_findings', 'finding_exceptions'], true)) { $this->appendUniqueLink( $actions, 'Open decision register', $this->decisionRegisterUrlFor($tenant), [$primaryActionUrl], ); } if (in_array($familyKey, ['assigned_findings', 'intake_findings', 'review_follow_up'], true)) { $this->appendUniqueLink( $actions, 'Open evidence overview', $this->evidenceOverviewUrlFor($tenant), [$primaryActionUrl], ); } if ($familyKey === 'review_follow_up' && $tenant instanceof ManagedEnvironment) { $this->appendUniqueLink( $actions, 'Open Customer Review Workspace', CustomerReviewWorkspace::environmentFilterUrl($tenant), [$primaryActionUrl], ); } if (in_array($familyKey, ['stale_operations', 'alert_delivery_failures', 'assigned_findings', 'intake_findings'], true) && $tenant instanceof ManagedEnvironment) { $this->appendUniqueLink( $actions, 'Open environment', ManagedEnvironmentLinks::viewUrl($tenant), [$primaryActionUrl], ); } if ($familyKey === 'finding_exceptions') { $this->appendUniqueLink( $actions, 'Open source finding', $entry['destination_url'] ?? null, [$primaryActionUrl], ); } if ($environmentId === null && filled($entry['destination_url'] ?? null)) { $this->appendUniqueLink( $actions, 'Open source', $entry['destination_url'] ?? null, [$primaryActionUrl], ); } return array_slice($actions, 0, 3); } /** * @param array $entry * @return list */ private function linkedRecordsForEntry(array $entry, ?ManagedEnvironment $tenant): array { $familyKey = (string) ($entry['family_key'] ?? ''); $records = []; $this->appendUniqueLink($records, 'Source record', $entry['destination_url'] ?? null); $this->appendUniqueLink($records, 'Evidence path', $entry['evidence_path_url'] ?? null); if (in_array($familyKey, ['assigned_findings', 'intake_findings', 'finding_exceptions'], true)) { $this->appendUniqueLink($records, 'Decision register', $this->decisionRegisterUrlFor($tenant)); } if ($familyKey === 'review_follow_up' && $tenant instanceof ManagedEnvironment) { $this->appendUniqueLink($records, 'Customer Review Workspace', CustomerReviewWorkspace::environmentFilterUrl($tenant)); } if ($familyKey === 'stale_operations') { $this->appendUniqueLink($records, 'Operation proof', $entry['destination_url'] ?? null); } return array_slice($records, 0, 4); } /** * @param list $links * @param list $ignoredUrls */ private function appendUniqueLink(array &$links, string $label, mixed $url, array $ignoredUrls = []): void { if (! is_string($url) || $url === '' || in_array($url, $ignoredUrls, true)) { return; } foreach ($links as $existingLink) { if (($existingLink['url'] ?? null) === $url) { return; } } $links[] = [ 'label' => $label, 'url' => $url, ]; } /** * @param array $family * @return array{key: string, label: string, count: int} */ private function normalizeFamily(array $family): array { $key = (string) ($family['key'] ?? 'governance'); return [ 'key' => $key, 'label' => filled($family['label'] ?? null) ? (string) $family['label'] : Str::headline($key), 'count' => (int) ($family['count'] ?? 0), ]; } /** * @param array $section * @return array */ private function normalizeSection(array $section): array { $key = (string) ($section['key'] ?? 'governance'); $label = filled($section['label'] ?? null) ? (string) $section['label'] : Str::headline($key); return [ 'key' => $key, 'label' => $label, 'count' => (int) ($section['count'] ?? 0), 'summary' => filled($section['summary'] ?? null) ? (string) $section['summary'] : 'No summary is available for this source family.', 'dominant_action' => $this->normalizeAction([ 'label' => (string) ($section['dominant_action_label'] ?? 'Open '.$label), 'url' => $section['dominant_action_url'] ?? null, ]), 'dominant_action_label' => (string) ($section['dominant_action_label'] ?? 'Open '.$label), 'dominant_action_url' => is_string($section['dominant_action_url'] ?? null) ? (string) $section['dominant_action_url'] : null, 'entries' => collect($section['entries'] ?? []) ->filter(fn (mixed $entry): bool => is_array($entry)) ->map(fn (array $entry): array => $this->normalizeSourceEntry($entry)) ->values() ->all(), 'empty_state' => filled($section['empty_state'] ?? null) ? (string) $section['empty_state'] : 'No source records are visible in the current scope.', ]; } /** * @param array $entry * @return array */ private function normalizeSourceEntry(array $entry): array { return [ ...$entry, 'headline' => filled($entry['headline'] ?? null) ? (string) $entry['headline'] : 'Governance item', 'status_label' => filled($entry['status_label'] ?? null) ? (string) $entry['status_label'] : 'Needs attention', 'destination_url' => is_string($entry['destination_url'] ?? null) ? (string) $entry['destination_url'] : null, ]; } /** * @return array{label: string, url: string}|null */ private function normalizeAction(mixed $action): ?array { if (! is_array($action)) { return null; } $url = $action['url'] ?? null; if (! is_string($url) || $url === '') { return null; } $label = filled($action['label'] ?? null) ? (string) $action['label'] : 'Open source'; return [ 'label' => $label, 'url' => $url, ]; } private function tenantForEntry(array $entry): ?ManagedEnvironment { $environmentId = is_numeric($entry['managed_environment_id'] ?? null) ? (int) $entry['managed_environment_id'] : null; if (! is_int($environmentId)) { return null; } foreach ($this->authorizedTenants() as $tenant) { if ((int) $tenant->getKey() === $environmentId) { return $tenant; } } return null; } private function decisionRegisterUrlFor(?ManagedEnvironment $tenant = null): string { return DecisionRegister::getUrl( panel: 'admin', parameters: array_filter([ 'environment_id' => $tenant?->getKey(), ], static fn (mixed $value): bool => $value !== null && $value !== ''), ); } private function evidenceOverviewUrlFor(?ManagedEnvironment $tenant = null): string { return route('admin.evidence.overview', array_filter([ 'environment_id' => $tenant?->getKey(), ], static fn (mixed $value): bool => $value !== null && $value !== '')); } private function authorizeWorkspaceMembership(): void { $user = auth()->user(); $workspace = $this->workspace(); if (! $user instanceof User) { abort(403); } if (! $workspace instanceof Workspace) { throw new NotFoundHttpException; } $resolver = app(WorkspaceCapabilityResolver::class); if (! $resolver->isMember($user, $workspace)) { throw new NotFoundHttpException; } } private function ensureAtLeastOneVisibleFamily(): void { if ( $this->hasVisibleOperationsFamily() || $this->visibleFindingTenants() !== [] || $this->hasVisibleFindingExceptionsFamily() || $this->reviewTenants() !== [] || $this->hasVisibleAlertsFamily() ) { return; } abort(403); } private function ensureRequestedFamilyIsVisible(): void { if ($this->family === null) { return; } if (in_array($this->family, collect($this->availableFamilies())->pluck('key')->all(), true)) { return; } throw new NotFoundHttpException; } private function hasVisibleOperationsFamily(): bool { return $this->authorizedTenants() !== []; } private function hasVisibleAlertsFamily(): bool { if (is_bool($this->visibleAlertsFamily)) { return $this->visibleAlertsFamily; } $user = auth()->user(); $workspace = $this->workspace(); if (! $user instanceof User || ! $workspace instanceof Workspace) { return $this->visibleAlertsFamily = false; } return $this->visibleAlertsFamily = app(WorkspaceCapabilityResolver::class)->can($user, $workspace, Capabilities::ALERTS_VIEW); } private function hasVisibleFindingExceptionsFamily(): bool { if (is_bool($this->visibleFindingExceptionsFamily)) { return $this->visibleFindingExceptionsFamily; } if ($this->authorizedTenants() === []) { return $this->visibleFindingExceptionsFamily = false; } $user = auth()->user(); $workspace = $this->workspace(); if (! $user instanceof User || ! $workspace instanceof Workspace) { return $this->visibleFindingExceptionsFamily = false; } return $this->visibleFindingExceptionsFamily = app(WorkspaceCapabilityResolver::class) ->can($user, $workspace, Capabilities::FINDING_EXCEPTION_APPROVE); } /** * @return array */ private function visibleFindingTenants(): array { if ($this->visibleFindingTenants !== null) { return $this->visibleFindingTenants; } $user = auth()->user(); $tenants = $this->authorizedTenants(); if (! $user instanceof User || $tenants === []) { return $this->visibleFindingTenants = []; } $resolver = app(CapabilityResolver::class); $resolver->primeMemberships( $user, array_map(static fn (ManagedEnvironment $tenant): int => (int) $tenant->getKey(), $tenants), ); return $this->visibleFindingTenants = array_values(array_filter( $tenants, fn (ManagedEnvironment $tenant): bool => $resolver->can($user, $tenant, Capabilities::TENANT_FINDINGS_VIEW), )); } /** * @return array */ private function reviewTenants(): array { if ($this->reviewTenants !== null) { return $this->reviewTenants; } $user = auth()->user(); $workspace = $this->workspace(); if (! $user instanceof User || ! $workspace instanceof Workspace) { return $this->reviewTenants = []; } $service = app(EnvironmentReviewRegisterService::class); if (! $service->canAccessWorkspace($user, $workspace)) { return $this->reviewTenants = []; } return $this->reviewTenants = $service->authorizedTenants($user, $workspace); } /** * @return array */ private function authorizedTenants(): array { if ($this->authorizedTenants !== null) { return $this->authorizedTenants; } $user = auth()->user(); $workspace = $this->workspace(); if (! $user instanceof User || ! $workspace instanceof Workspace) { return $this->authorizedTenants = []; } return $this->authorizedTenants = $user->accessibleManagedEnvironmentsQuery((int) $workspace->getKey()) ->where('managed_environments.lifecycle_status', 'active') ->orderBy('managed_environments.name') ->get(['managed_environments.id', 'managed_environments.name', 'managed_environments.slug', 'managed_environments.workspace_id']) ->all(); } private function applyRequestedTenantPrefilter(): void { $workspace = $this->workspace(); if (! $workspace instanceof Workspace) { return; } $filter = WorkspaceHubEnvironmentFilter::fromRequest(request(), $workspace); if (! $filter instanceof WorkspaceHubEnvironmentFilter) { return; } $environmentId = $filter->environmentId(); foreach ($this->authorizedTenants() as $tenant) { if ((int) $tenant->getKey() === $environmentId) { $this->tenantId = $environmentId; return; } } throw new NotFoundHttpException; } private function resolveRequestedFamily(): ?string { $family = request()->query('family'); if (! is_string($family)) { return null; } return in_array($family, [ 'assigned_findings', 'intake_findings', 'finding_exceptions', 'stale_operations', 'alert_delivery_failures', 'review_follow_up', ], true) ? $family : null; } private function workspace(): ?Workspace { if ($this->workspace instanceof Workspace) { return $this->workspace; } $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request()); if (! is_int($workspaceId)) { return null; } return $this->workspace = Workspace::query()->whereKey($workspaceId)->first(); } /** * @return array */ private function inboxPayload(): array { if (is_array($this->inboxPayload)) { return $this->inboxPayload; } $user = auth()->user(); $workspace = $this->workspace(); if (! $user instanceof User || ! $workspace instanceof Workspace) { return $this->inboxPayload = [ 'sections' => [], 'available_families' => [], 'family_counts' => [], 'total_count' => 0, ]; } return $this->inboxPayload = app(GovernanceInboxSectionBuilder::class)->build( user: $user, workspace: $workspace, authorizedTenants: $this->authorizedTenants(), visibleFindingTenants: $this->visibleFindingTenants(), reviewTenants: $this->reviewTenants(), canViewAlerts: $this->hasVisibleAlertsFamily(), canViewFindingExceptions: $this->hasVisibleFindingExceptionsFamily(), selectedTenant: $this->selectedTenant(), selectedFamily: $this->family, navigationContext: $this->navigationContext(), ); } /** * @return array */ private function unfilteredInboxPayload(): array { if (is_array($this->unfilteredInboxPayload)) { return $this->unfilteredInboxPayload; } $user = auth()->user(); $workspace = $this->workspace(); if (! $user instanceof User || ! $workspace instanceof Workspace) { return $this->unfilteredInboxPayload = [ 'sections' => [], 'available_families' => [], 'family_counts' => [], 'total_count' => 0, ]; } return $this->unfilteredInboxPayload = app(GovernanceInboxSectionBuilder::class)->build( user: $user, workspace: $workspace, authorizedTenants: $this->authorizedTenants(), visibleFindingTenants: $this->visibleFindingTenants(), reviewTenants: $this->reviewTenants(), canViewAlerts: $this->hasVisibleAlertsFamily(), canViewFindingExceptions: $this->hasVisibleFindingExceptionsFamily(), selectedTenant: null, selectedFamily: null, navigationContext: $this->navigationContext(), ); } private function selectedTenant(): ?ManagedEnvironment { if (! is_int($this->tenantId)) { return null; } foreach ($this->authorizedTenants() as $tenant) { if ((int) $tenant->getKey() === $this->tenantId) { return $tenant; } } return null; } private function tenantFilterAloneExcludesRows(): bool { if (! is_int($this->tenantId) || $this->family !== null) { return false; } if ($this->laneGroups() !== []) { return false; } return (int) ($this->unfilteredInboxPayload()['total_count'] ?? 0) > 0; } private function familyFilterAloneExcludesRows(): bool { if ($this->family === null) { return false; } if ($this->laneGroups() !== []) { return false; } return (int) ($this->unfilteredInboxPayload()['total_count'] ?? 0) > 0; } }