*/ protected function getViewData(): array { $tenant = Filament::getTenant(); if (! $tenant instanceof Tenant) { return [ 'pollingInterval' => null, 'items' => [], 'healthyChecks' => [], ]; } $tenantId = (int) $tenant->getKey(); $aggregate = $this->governanceAggregate($tenant); $compareAssessment = $aggregate->summaryAssessment; $backupHealth = $this->backupHealthAssessment($tenant); $items = []; if (($backupHealthItem = $this->backupHealthAttentionItem($tenant, $backupHealth)) instanceof BackupHealthDashboardSignal) { $items[] = array_merge( $backupHealthItem->toArray(), $this->backupHealthActionPayload($tenant, $backupHealthItem->actionTarget, $backupHealthItem->actionLabel) ); } $overdueOpenCount = $aggregate->overdueOpenFindingsCount; $lapsedGovernanceCount = $aggregate->lapsedGovernanceCount; $expiringGovernanceCount = $aggregate->expiringGovernanceCount; $highSeverityCount = $aggregate->highSeverityActiveFindingsCount; $staleActiveOperationsCount = (int) OperationRun::query() ->where('tenant_id', $tenantId) ->activeStaleAttention() ->count(); $terminalFollowUpOperationsCount = (int) OperationRun::query() ->where('tenant_id', $tenantId) ->terminalFollowUp() ->count(); $activeRuns = (int) OperationRun::query() ->where('tenant_id', $tenantId) ->healthyActive() ->count(); if ($lapsedGovernanceCount > 0) { $items[] = [ 'key' => 'lapsed_governance', 'title' => 'Lapsed accepted-risk governance', 'body' => "{$lapsedGovernanceCount} accepted-risk finding(s) no longer have valid supporting governance.", 'badge' => 'Governance', 'badgeColor' => 'danger', ...$this->findingsAction( $tenant, 'Open findings', [ 'tab' => 'risk_accepted', 'governance_validity' => FindingException::VALIDITY_MISSING_SUPPORT, ], ), ]; } if ($overdueOpenCount > 0) { $items[] = [ 'key' => 'overdue_findings', 'title' => 'Overdue findings', 'body' => "{$overdueOpenCount} open finding(s) are overdue and still need workflow follow-up.", 'badge' => 'Findings', 'badgeColor' => 'danger', ...$this->findingsAction( $tenant, 'Open findings', ['tab' => 'overdue'], ), ]; } if ($expiringGovernanceCount > 0) { $items[] = [ 'key' => 'expiring_governance', 'title' => 'Expiring accepted-risk governance', 'body' => "{$expiringGovernanceCount} accepted-risk finding(s) need governance review soon.", 'badge' => 'Governance', 'badgeColor' => 'warning', ...$this->findingsAction( $tenant, 'Open findings', [ 'tab' => 'risk_accepted', 'governance_validity' => FindingException::VALIDITY_EXPIRING, ], ), ]; } if ($highSeverityCount > 0) { $items[] = [ 'key' => 'high_severity_active_findings', 'title' => 'High severity active findings', 'body' => "{$highSeverityCount} high or critical finding(s) are still active.", 'badge' => 'Findings', 'badgeColor' => 'danger', ...$this->findingsAction( $tenant, 'Open findings', [ 'tab' => 'needs_action', 'high_severity' => 1, ], ), ]; } if ($compareAssessment->stateFamily !== 'positive') { $items[] = [ 'key' => 'baseline_compare_posture', 'title' => 'Baseline compare posture', 'body' => $compareAssessment->headline, 'supportingMessage' => $compareAssessment->supportingMessage, 'badge' => 'Baseline', 'badgeColor' => $compareAssessment->tone, 'actionLabel' => 'Open Baseline Compare', 'actionUrl' => BaselineCompareLanding::getUrl(panel: 'tenant', tenant: $tenant), ]; } if ($staleActiveOperationsCount > 0) { $items[] = [ 'key' => 'operations_stale_attention', 'title' => 'Active operations look stale', 'body' => "{$staleActiveOperationsCount} run(s) are still marked active but are past the lifecycle window.", 'badge' => 'Operations', 'badgeColor' => 'warning', 'actionLabel' => 'Open stale operations', 'actionUrl' => OperationRunLinks::index( $tenant, activeTab: OperationRun::PROBLEM_CLASS_ACTIVE_STALE_ATTENTION, problemClass: OperationRun::PROBLEM_CLASS_ACTIVE_STALE_ATTENTION, ), ]; } if ($terminalFollowUpOperationsCount > 0) { $items[] = [ 'key' => 'operations_terminal_follow_up', 'title' => 'Terminal operations need follow-up', 'body' => "{$terminalFollowUpOperationsCount} run(s) finished blocked, partially, failed, or were automatically reconciled.", 'badge' => 'Operations', 'badgeColor' => 'danger', 'actionLabel' => 'Open terminal follow-up', 'actionUrl' => OperationRunLinks::index( $tenant, activeTab: OperationRun::PROBLEM_CLASS_TERMINAL_FOLLOW_UP, problemClass: OperationRun::PROBLEM_CLASS_TERMINAL_FOLLOW_UP, ), ]; } $healthyChecks = []; if ($items === []) { $healthyChecks = [ ...array_filter([$this->backupHealthHealthyCheck($backupHealth)]), [ 'title' => 'Baseline compare looks trustworthy', 'body' => $aggregate->headline, ], [ 'title' => 'No overdue findings', 'body' => 'No open findings are currently overdue for this tenant.', ], [ 'title' => 'Accepted-risk governance is healthy', 'body' => 'No accepted-risk findings currently need governance follow-up.', ], [ 'title' => 'No high severity active findings', 'body' => 'No high severity findings are currently open for this tenant.', ], $activeRuns > 0 ? [ 'title' => 'Operations are active', 'body' => "{$activeRuns} run(s) are active, but nothing currently needs follow-up.", ] : [ 'title' => 'No active operations', 'body' => 'Nothing is currently running for this tenant.', ], ]; } return [ 'pollingInterval' => ActiveRuns::pollingIntervalForTenant($tenant), 'items' => $items, 'healthyChecks' => $healthyChecks, ]; } /** * @param array $parameters * @return array */ private function findingsAction(Tenant $tenant, string $label, array $parameters): array { $url = $this->canOpenFindings($tenant) ? FindingResource::getUrl('index', $parameters, panel: 'tenant', tenant: $tenant) : null; return [ 'actionLabel' => $label, 'actionUrl' => $url, 'actionDisabled' => $url === null, 'helperText' => $url === null ? UiTooltips::INSUFFICIENT_PERMISSION : null, ]; } private function canOpenFindings(Tenant $tenant): bool { $user = auth()->user(); return $user instanceof User && $user->canAccessTenant($tenant) && $user->can(Capabilities::TENANT_FINDINGS_VIEW, $tenant); } private function governanceAggregate(Tenant $tenant): TenantGovernanceAggregate { /** @var TenantGovernanceAggregateResolver $resolver */ $resolver = app(TenantGovernanceAggregateResolver::class); /** @var TenantGovernanceAggregate $aggregate */ $aggregate = $resolver->forTenant($tenant); return $aggregate; } private function backupHealthAssessment(Tenant $tenant): TenantBackupHealthAssessment { /** @var TenantBackupHealthResolver $resolver */ $resolver = app(TenantBackupHealthResolver::class); return $resolver->assess($tenant); } private function backupHealthAttentionItem(Tenant $tenant, TenantBackupHealthAssessment $assessment): ?BackupHealthDashboardSignal { if (! $assessment->hasActiveReason()) { return null; } return new BackupHealthDashboardSignal( title: $this->backupHealthAttentionTitle($assessment), body: $assessment->supportingMessage ?? $assessment->headline, tone: $assessment->tone(), badge: 'Backups', badgeColor: $assessment->tone(), actionTarget: $assessment->primaryActionTarget, actionLabel: $assessment->primaryActionTarget?->label, ); } /** * @return array{title: string, body: string}|null */ private function backupHealthHealthyCheck(TenantBackupHealthAssessment $assessment): ?array { if (! $assessment->healthyClaimAllowed) { return null; } return [ 'title' => 'Backups are recent and healthy', 'body' => $assessment->supportingMessage ?? 'The latest completed backup is recent and shows no material degradation.', ]; } /** * @return array{actionLabel: string|null, actionUrl: string|null, actionDisabled: bool, helperText: string|null} */ private function backupHealthActionPayload(Tenant $tenant, ?BackupHealthActionTarget $target, ?string $label): array { if (! $target instanceof BackupHealthActionTarget) { return [ 'actionLabel' => $label, 'actionUrl' => null, 'actionDisabled' => false, 'helperText' => null, ]; } if (! $this->canOpenBackupSurfaces($tenant)) { return [ 'actionLabel' => $label ?? $target->label, 'actionUrl' => null, 'actionDisabled' => true, 'helperText' => UiTooltips::INSUFFICIENT_PERMISSION, ]; } return match ($target->surface) { BackupHealthActionTarget::SURFACE_BACKUP_SETS_INDEX => [ 'actionLabel' => $label ?? $target->label, 'actionUrl' => BackupSetResource::getUrl('index', [ 'backup_health_reason' => $target->reason, ], panel: 'tenant', tenant: $tenant), 'actionDisabled' => false, 'helperText' => null, ], BackupHealthActionTarget::SURFACE_BACKUP_SCHEDULES_INDEX => [ 'actionLabel' => $label ?? $target->label, 'actionUrl' => BackupScheduleResource::getUrl('index', [ 'backup_health_reason' => $target->reason, ], panel: 'tenant', tenant: $tenant), 'actionDisabled' => false, 'helperText' => null, ], BackupHealthActionTarget::SURFACE_BACKUP_SET_VIEW => $this->backupHealthBackupSetActionPayload($tenant, $target, $label), default => [ 'actionLabel' => $label, 'actionUrl' => null, 'actionDisabled' => false, 'helperText' => null, ], }; } /** * @return array{actionLabel: string|null, actionUrl: string|null, actionDisabled: bool, helperText: string|null} */ private function backupHealthBackupSetActionPayload(Tenant $tenant, BackupHealthActionTarget $target, ?string $label): array { if (! is_numeric($target->recordId)) { return [ 'actionLabel' => 'Open backup sets', 'actionUrl' => BackupSetResource::getUrl('index', [ 'backup_health_reason' => $target->reason, ], panel: 'tenant', tenant: $tenant), 'actionDisabled' => false, 'helperText' => 'The latest backup detail is no longer available.', ]; } try { BackupSetResource::resolveScopedRecordOrFail($target->recordId); return [ 'actionLabel' => $label ?? $target->label, 'actionUrl' => BackupSetResource::getUrl('view', [ 'record' => $target->recordId, 'backup_health_reason' => $target->reason, ], panel: 'tenant', tenant: $tenant), 'actionDisabled' => false, 'helperText' => null, ]; } catch (ModelNotFoundException) { return [ 'actionLabel' => 'Open backup sets', 'actionUrl' => BackupSetResource::getUrl('index', [ 'backup_health_reason' => $target->reason, ], panel: 'tenant', tenant: $tenant), 'actionDisabled' => false, 'helperText' => 'The latest backup detail is no longer available.', ]; } } private function backupHealthAttentionTitle(TenantBackupHealthAssessment $assessment): string { return match ($assessment->primaryReason) { TenantBackupHealthAssessment::REASON_NO_BACKUP_BASIS => 'No usable backup basis', TenantBackupHealthAssessment::REASON_LATEST_BACKUP_STALE => 'Latest backup is stale', TenantBackupHealthAssessment::REASON_LATEST_BACKUP_DEGRADED => 'Latest backup is degraded', TenantBackupHealthAssessment::REASON_SCHEDULE_FOLLOW_UP => 'Backup schedules need follow-up', default => $assessment->headline, }; } private function canOpenBackupSurfaces(Tenant $tenant): bool { $user = auth()->user(); return $user instanceof User && $user->canAccessTenant($tenant) && $user->can(Capabilities::TENANT_VIEW, $tenant); } }