satisfy(ActionSurfaceSlot::ListHeader, 'Create backup set is available in the list header.') ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value) ->satisfy(ActionSurfaceSlot::ListRowMoreMenu, 'Restore, archive, and force delete remain grouped in the More menu on table rows.') ->satisfy(ActionSurfaceSlot::ListBulkMoreGroup, 'Bulk archive, restore, and force delete stay grouped under More.') ->satisfy(ActionSurfaceSlot::ListEmptyState, 'Create backup set remains available from the empty state.') ->satisfy(ActionSurfaceSlot::DetailHeader, 'Primary related navigation and grouped mutation actions render in the view header.'); } public static function canViewAny(): bool { $tenant = static::resolveTenantContextForCurrentPanel(); $user = auth()->user(); if (! $tenant instanceof ManagedEnvironment || ! $user instanceof User) { return false; } /** @var CapabilityResolver $resolver */ $resolver = app(CapabilityResolver::class); return $resolver->isMember($user, $tenant) && $resolver->can($user, $tenant, Capabilities::TENANT_VIEW); } public static function canCreate(): bool { $tenant = static::resolveTenantContextForCurrentPanel(); $user = auth()->user(); if (! $tenant instanceof ManagedEnvironment || ! $user instanceof User) { return false; } /** @var CapabilityResolver $resolver */ $resolver = app(CapabilityResolver::class); return $resolver->isMember($user, $tenant) && $resolver->can($user, $tenant, Capabilities::TENANT_SYNC); } public static function getEloquentQuery(): Builder { return static::getTenantOwnedEloquentQuery(); } public static function resolveScopedRecordOrFail(int|string $key): \Illuminate\Database\Eloquent\Model { return static::resolveTenantOwnedRecordOrFail($key, parent::getEloquentQuery()->withTrashed()); } public static function form(Schema $schema): Schema { return $schema ->schema([ Forms\Components\TextInput::make('name') ->label('Backup name') ->default(fn () => now()->format('Y-m-d H:i:s').' backup') ->required(), ]); } public static function makeCreateAction(): Actions\CreateAction { $action = Actions\CreateAction::make() ->label('Create backup set'); UiEnforcement::forAction($action) ->requireCapability(Capabilities::TENANT_SYNC) ->apply(); return $action; } public static function table(Table $table): Table { return $table ->defaultSort('created_at', 'desc') ->paginated(TablePaginationProfiles::resource()) ->persistFiltersInSession() ->persistSearchInSession() ->persistSortInSession() ->modifyQueryUsing(fn (Builder $query): Builder => $query->with([ 'items' => fn ($itemQuery) => $itemQuery->select([ 'id', 'backup_set_id', 'payload', 'metadata', 'assignments', ]), ])) ->columns([ Tables\Columns\TextColumn::make('name') ->searchable() ->sortable(), Tables\Columns\TextColumn::make('backup_quality') ->label('Restore-point decision') ->state(fn (BackupSet $record): string => static::backupSetListDecision($record)) ->description(fn (BackupSet $record): string => static::backupSetListGuidance($record)) ->wrap(), Tables\Columns\TextColumn::make('item_count')->label('Items captured')->numeric()->sortable(), Tables\Columns\TextColumn::make('status') ->label('Lifecycle') ->badge() ->formatStateUsing(BadgeRenderer::label(BadgeDomain::BackupSetStatus)) ->color(BadgeRenderer::color(BadgeDomain::BackupSetStatus)) ->icon(BadgeRenderer::icon(BadgeDomain::BackupSetStatus)) ->iconColor(BadgeRenderer::iconColor(BadgeDomain::BackupSetStatus)) ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('completed_at')->label('Completed')->dateTime()->since()->sortable(), Tables\Columns\TextColumn::make('created_by')->label('Created by')->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('created_at')->label('Captured')->dateTime()->since()->sortable()->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ Tables\Filters\TrashedFilter::make() ->label('Archived') ->placeholder('Active') ->trueLabel('All') ->falseLabel('Archived'), ]) ->recordUrl(fn (BackupSet $record): string => static::getUrl('view', ['record' => $record])) ->actions([ static::primaryRelatedAction(), ActionGroup::make([ UiEnforcement::forAction( Actions\Action::make('restore') ->label('Restore') ->color('success') ->icon('heroicon-o-arrow-uturn-left') ->requiresConfirmation() ->visible(fn (BackupSet $record): bool => $record->trashed()) ->action(function (BackupSet $record, AuditLogger $auditLogger) { $tenant = static::resolveTenantContextForCurrentPanel(); $record->restore(); $record->items()->withTrashed()->restore(); if ($record->tenant) { $auditLogger->log( tenant: $record->tenant, action: 'backup.restored', resourceType: 'backup_set', resourceId: (string) $record->id, status: 'success', context: ['metadata' => ['name' => $record->name]] ); } Notification::make() ->title('Backup set restored') ->success() ->send(); }) ) ->preserveVisibility() ->requireCapability(Capabilities::TENANT_MANAGE) ->apply(), UiEnforcement::forAction( Actions\Action::make('archive') ->label('Archive') ->color('danger') ->icon('heroicon-o-archive-box-x-mark') ->requiresConfirmation() ->visible(fn (BackupSet $record): bool => ! $record->trashed()) ->action(function (BackupSet $record, AuditLogger $auditLogger) { $tenant = static::resolveTenantContextForCurrentPanel(); $record->delete(); if ($record->tenant) { $auditLogger->log( tenant: $record->tenant, action: 'backup.deleted', resourceType: 'backup_set', resourceId: (string) $record->id, status: 'success', context: ['metadata' => ['name' => $record->name]] ); } Notification::make() ->title('Backup set archived') ->success() ->send(); }) ) ->preserveVisibility() ->requireCapability(Capabilities::TENANT_MANAGE) ->apply(), UiEnforcement::forAction( Actions\Action::make('forceDelete') ->label('Force delete') ->color('danger') ->icon('heroicon-o-trash') ->requiresConfirmation() ->visible(fn (BackupSet $record): bool => $record->trashed()) ->action(function (BackupSet $record, AuditLogger $auditLogger) { $tenant = static::resolveTenantContextForCurrentPanel(); if ($record->restoreRuns()->withTrashed()->exists()) { Notification::make() ->title('Cannot force delete backup set') ->body('Backup sets referenced by restore runs cannot be removed.') ->danger() ->send(); return; } if ($record->tenant) { $auditLogger->log( tenant: $record->tenant, action: 'backup.force_deleted', resourceType: 'backup_set', resourceId: (string) $record->id, status: 'success', context: ['metadata' => ['name' => $record->name]] ); } $record->items()->withTrashed()->forceDelete(); $record->forceDelete(); Notification::make() ->title('Backup set permanently deleted') ->success() ->send(); }) ) ->preserveVisibility() ->requireCapability(Capabilities::TENANT_DELETE) ->apply(), ])->icon('heroicon-o-ellipsis-vertical'), ]) ->bulkActions([ BulkActionGroup::make([ UiEnforcement::forBulkAction( BulkAction::make('bulk_delete') ->label('Archive Backup Sets') ->icon('heroicon-o-archive-box-x-mark') ->color('danger') ->requiresConfirmation() ->hidden(function (HasTable $livewire): bool { $trashedFilterState = $livewire->getTableFilterState(TrashedFilter::class) ?? []; $value = $trashedFilterState['value'] ?? null; $isOnlyTrashed = in_array($value, [0, '0', false], true); return $isOnlyTrashed; }) ->modalDescription('This archives backup sets (soft delete). Already archived backup sets will be skipped.') ->form(function (Collection $records) { if ($records->count() >= 10) { return [ Forms\Components\TextInput::make('confirmation') ->label('Type DELETE to confirm') ->required() ->in(['DELETE']) ->validationMessages([ 'in' => 'Please type DELETE to confirm.', ]), ]; } return []; }) ->action(function (Collection $records) { $tenant = static::resolveTenantContextForCurrentPanel(); $user = auth()->user(); $count = $records->count(); $ids = $records->pluck('id')->toArray(); if (! $tenant instanceof ManagedEnvironment) { return; } $initiator = $user instanceof User ? $user : null; /** @var BulkSelectionIdentity $selection */ $selection = app(BulkSelectionIdentity::class); $selectionIdentity = $selection->fromIds($ids); /** @var OperationRunService $runs */ $runs = app(OperationRunService::class); $opRun = $runs->enqueueBulkOperation( tenant: $tenant, type: 'backup_set.delete', targetScope: [ 'entra_tenant_id' => (string) ($tenant->managed_environment_id ?? $tenant->external_id), ], selectionIdentity: $selectionIdentity, dispatcher: function ($operationRun) use ($tenant, $initiator, $ids): void { BulkBackupSetDeleteJob::dispatch( tenantId: (int) $tenant->getKey(), userId: (int) ($initiator?->getKey() ?? 0), backupSetIds: $ids, operationRun: $operationRun, ); }, initiator: $initiator, extraContext: [ 'backup_set_count' => $count, ], emitQueuedNotification: false, ); OperationUxPresenter::queuedToast('backup_set.delete') ->actions([ Actions\Action::make('view_run') ->label(OperationRunLinks::openLabel()) ->url(OperationRunLinks::view($opRun, $tenant)), ]) ->send(); }) ->deselectRecordsAfterCompletion(), ) ->requireCapability(Capabilities::TENANT_MANAGE) ->apply(), UiEnforcement::forBulkAction( BulkAction::make('bulk_restore') ->label('Restore Backup Sets') ->icon('heroicon-o-arrow-uturn-left') ->color('success') ->requiresConfirmation() ->hidden(function (HasTable $livewire): bool { $trashedFilterState = $livewire->getTableFilterState(TrashedFilter::class) ?? []; $value = $trashedFilterState['value'] ?? null; $isOnlyTrashed = in_array($value, [0, '0', false], true); return ! $isOnlyTrashed; }) ->modalHeading(fn (Collection $records) => "Restore {$records->count()} backup sets?") ->modalDescription('Archived backup sets will be restored back to the active list. Active backup sets will be skipped.') ->action(function (Collection $records) { $tenant = static::resolveTenantContextForCurrentPanel(); $user = auth()->user(); $count = $records->count(); $ids = $records->pluck('id')->toArray(); if (! $tenant instanceof ManagedEnvironment) { return; } $initiator = $user instanceof User ? $user : null; /** @var BulkSelectionIdentity $selection */ $selection = app(BulkSelectionIdentity::class); $selectionIdentity = $selection->fromIds($ids); /** @var OperationRunService $runs */ $runs = app(OperationRunService::class); $opRun = $runs->enqueueBulkOperation( tenant: $tenant, type: 'backup_set.restore', targetScope: [ 'entra_tenant_id' => (string) ($tenant->managed_environment_id ?? $tenant->external_id), ], selectionIdentity: $selectionIdentity, dispatcher: function ($operationRun) use ($tenant, $initiator, $ids): void { BulkBackupSetRestoreJob::dispatch( tenantId: (int) $tenant->getKey(), userId: (int) ($initiator?->getKey() ?? 0), backupSetIds: $ids, operationRun: $operationRun, ); }, initiator: $initiator, extraContext: [ 'backup_set_count' => $count, ], emitQueuedNotification: false, ); OperationUxPresenter::queuedToast('backup_set.restore') ->actions([ Actions\Action::make('view_run') ->label(OperationRunLinks::openLabel()) ->url(OperationRunLinks::view($opRun, $tenant)), ]) ->send(); }) ->deselectRecordsAfterCompletion(), ) ->requireCapability(Capabilities::TENANT_MANAGE) ->apply(), UiEnforcement::forBulkAction( BulkAction::make('bulk_force_delete') ->label('Force Delete Backup Sets') ->icon('heroicon-o-trash') ->color('danger') ->requiresConfirmation() ->hidden(function (HasTable $livewire): bool { $trashedFilterState = $livewire->getTableFilterState(TrashedFilter::class) ?? []; $value = $trashedFilterState['value'] ?? null; $isOnlyTrashed = in_array($value, [0, '0', false], true); return ! $isOnlyTrashed; }) ->modalHeading(fn (Collection $records) => "Force delete {$records->count()} backup sets?") ->modalDescription('This is permanent. Only archived backup sets will be permanently deleted; active backup sets will be skipped.') ->form(function (Collection $records) { if ($records->count() >= 10) { return [ Forms\Components\TextInput::make('confirmation') ->label('Type DELETE to confirm') ->required() ->in(['DELETE']) ->validationMessages([ 'in' => 'Please type DELETE to confirm.', ]), ]; } return []; }) ->action(function (Collection $records) { $tenant = static::resolveTenantContextForCurrentPanel(); $user = auth()->user(); $count = $records->count(); $ids = $records->pluck('id')->toArray(); if (! $tenant instanceof ManagedEnvironment) { return; } $initiator = $user instanceof User ? $user : null; /** @var BulkSelectionIdentity $selection */ $selection = app(BulkSelectionIdentity::class); $selectionIdentity = $selection->fromIds($ids); /** @var OperationRunService $runs */ $runs = app(OperationRunService::class); $opRun = $runs->enqueueBulkOperation( tenant: $tenant, type: 'backup_set.force_delete', targetScope: [ 'entra_tenant_id' => (string) ($tenant->managed_environment_id ?? $tenant->external_id), ], selectionIdentity: $selectionIdentity, dispatcher: function ($operationRun) use ($tenant, $initiator, $ids): void { BulkBackupSetForceDeleteJob::dispatch( tenantId: (int) $tenant->getKey(), userId: (int) ($initiator?->getKey() ?? 0), backupSetIds: $ids, operationRun: $operationRun, ); }, initiator: $initiator, extraContext: [ 'backup_set_count' => $count, ], emitQueuedNotification: false, ); OperationUxPresenter::queuedToast('backup_set.force_delete') ->actions([ Actions\Action::make('view_run') ->label(OperationRunLinks::openLabel()) ->url(OperationRunLinks::view($opRun, $tenant)), ]) ->send(); }) ->deselectRecordsAfterCompletion(), ) ->requireCapability(Capabilities::TENANT_DELETE) ->apply(), ])->label('More'), ]) ->emptyStateHeading('No backup sets') ->emptyStateDescription('Create a backup set to capture policy versions and assignments for later restore review.') ->emptyStateIcon('heroicon-o-archive-box') ->emptyStateActions([ static::makeCreateAction(), ]); } public static function infolist(Schema $schema): Schema { return $schema ->schema([ Infolists\Components\ViewEntry::make('enterprise_detail') ->label('') ->view('filament.infolists.entries.enterprise-detail.layout') ->state(fn (BackupSet $record): array => static::enterpriseDetailPage($record)->toArray()) ->columnSpanFull(), ]); } public static function getRelations(): array { return [ BackupItemsRelationManager::class, ]; } public static function getPages(): array { return [ 'index' => Pages\ListBackupSets::route('/'), 'create' => Pages\CreateBackupSet::route('/create'), 'view' => Pages\ViewBackupSet::route('/{record}'), ]; } /** * @return array{label:?string,category:?string,restore:?string,risk:?string}|array */ private static function typeMeta(?string $type): array { if ($type === null) { return []; } $types = array_merge( config('tenantpilot.supported_policy_types', []), config('tenantpilot.foundation_types', []) ); return collect($types) ->firstWhere('type', $type) ?? []; } /** * @return list */ public static function relatedContextEntries(BackupSet $record): array { return app(RelatedNavigationResolver::class) ->detailEntries(CrossResourceNavigationMatrix::SOURCE_BACKUP_SET, $record); } private static function primaryRelatedAction(): Actions\Action { return Actions\Action::make('primary_drill_down') ->label(fn (BackupSet $record): string => static::primaryRelatedEntry($record)?->actionLabel ?? 'Open related record') ->url(fn (BackupSet $record): ?string => static::primaryRelatedEntry($record)?->targetUrl) ->hidden(fn (BackupSet $record): bool => ! (static::primaryRelatedEntry($record)?->isAvailable() ?? false)) ->color('gray'); } private static function primaryRelatedEntry(BackupSet $record): ?RelatedContextEntry { return app(RelatedNavigationResolver::class) ->primaryListAction(CrossResourceNavigationMatrix::SOURCE_BACKUP_SET, $record); } /** * Create a backup set via the domain service instead of direct model mass-assignment. */ public static function createBackupSet(array $data): BackupSet { /** @var ManagedEnvironment $tenant */ $tenant = static::resolveTenantContextForCurrentPanel(); /** @var BackupService $service */ $service = app(BackupService::class); return $service->createBackupSet( tenant: $tenant, policyIds: $data['policy_ids'] ?? [], name: $data['name'] ?? null, actorEmail: auth()->user()?->email, actorName: auth()->user()?->name, includeAssignments: $data['include_assignments'] ?? false, includeScopeTags: $data['include_scope_tags'] ?? false, ); } private static function enterpriseDetailPage(BackupSet $record): EnterpriseDetailPageData { $factory = new EnterpriseDetailSectionFactory; $statusSpec = BadgeRenderer::spec(BadgeDomain::BackupSetStatus, $record->status); $metadata = is_array($record->metadata) ? $record->metadata : []; $metadataKeyCount = count($metadata); $relatedContext = static::relatedContextEntries($record); $isArchived = $record->trashed(); $qualitySummary = static::backupQualitySummary($record); $backupHealthAssessment = static::backupHealthContinuityAssessment($record); $backupHealthBadge = $backupHealthAssessment instanceof TenantBackupHealthAssessment ? $factory->statusBadge( static::backupHealthContinuityLabel($backupHealthAssessment), $backupHealthAssessment->tone(), 'heroicon-m-exclamation-triangle', ) : null; $descriptionHint = $backupHealthAssessment instanceof TenantBackupHealthAssessment ? trim($backupHealthAssessment->headline.' '.($backupHealthAssessment->supportingMessage ?? '')) : null; return EnterpriseDetailBuilder::make('backup_set', 'tenant') ->header(new SummaryHeaderData( title: (string) $record->name, subtitle: 'Backup set #'.$record->getKey(), statusBadges: array_values(array_filter([ $backupHealthBadge, $factory->statusBadge($statusSpec->label, $statusSpec->color, $statusSpec->icon, $statusSpec->iconColor), $isArchived ? $factory->statusBadge('Archived', 'warning') : null, ])), descriptionHint: $descriptionHint, )) ->decisionZone($factory->decisionZone( facts: static::backupDecisionFacts($factory, $qualitySummary, $backupHealthAssessment, $backupHealthBadge), primaryNextStep: $factory->primaryNextStep( static::backupDecisionNextAction($qualitySummary), 'Restore-point decision', ), description: 'Start here to decide whether this backup set is usable for restore review before reading lifecycle, operation, or raw metadata.', compactCounts: static::backupDecisionCounts($factory, $qualitySummary), attentionNote: $backupHealthAssessment?->positiveClaimBoundary ?? $qualitySummary->positiveClaimBoundary, title: 'Restore-point decision', )) ->addSection( $factory->viewSection( id: 'related_context', kind: 'related_context', title: 'Related operation and evidence', view: 'filament.infolists.entries.related-context', viewData: ['entries' => $relatedContext], description: 'Operation links stay available for traceability, but they are secondary to backup usability and included-item truth.', emptyState: $factory->emptyState('No related context is available for this record.'), collapsible: true, collapsed: true, ), ) ->addTechnicalSection( $factory->technicalDetail( title: 'Technical and lifecycle detail', entries: [ $factory->keyFact('Lifecycle status', $statusSpec->label, badge: $factory->statusBadge($statusSpec->label, $statusSpec->color, $statusSpec->icon, $statusSpec->iconColor)), $factory->keyFact('Record state', $isArchived ? 'Archived' : 'Active'), $factory->keyFact('Created by', $record->created_by), $factory->keyFact('Completed', static::formatDetailTimestamp($record->completed_at)), $factory->keyFact('Captured', static::formatDetailTimestamp($record->created_at)), $factory->keyFact('Metadata keys', $metadataKeyCount), ], description: 'Lifecycle, timing, IDs, and raw metadata stay available here without taking over the restore-point decision.', view: $metadata !== [] ? 'filament.infolists.entries.snapshot-json' : null, viewData: ['payload' => $metadata], emptyState: $factory->emptyState('No backup metadata was recorded for this backup set.'), ), ) ->build(); } private static function backupSetListDecision(BackupSet $record): string { $summary = static::backupQualitySummary($record); if ($summary->totalItems === 0) { return 'Blocked: no items captured'; } if ($summary->hasDegradations()) { return 'Action needed before restore review'; } return 'Usable for restore review'; } private static function backupSetListGuidance(BackupSet $record): string { $summary = static::backupQualitySummary($record); if ($summary->totalItems === 0) { return 'No backup items were captured. Create or refresh a backup set before restore review.'; } if ($summary->hasDegradations()) { return $summary->compactSummary.'. Inspect degraded items before relying on this backup.'; } return sprintf( '%s captured. Review included items before relying on this backup.', static::formatItemCount($summary->totalItems), ); } private static function backupUsabilityLabel(BackupQualitySummary $summary): string { if ($summary->totalItems === 0) { return 'Blocked until items are captured'; } if ($summary->hasDegradations()) { return 'Action needed before restore review'; } return 'Usable for restore review'; } /** * @return list> */ private static function backupDecisionFacts( EnterpriseDetailSectionFactory $factory, BackupQualitySummary $summary, ?TenantBackupHealthAssessment $assessment, ?array $backupHealthBadge, ): array { return array_values(array_filter([ $factory->keyFact('Usability', static::backupUsabilityLabel($summary)), $factory->keyFact('Reason', static::backupDecisionReason($summary)), $factory->keyFact('Impact', static::backupDecisionImpact($summary)), $factory->keyFact('Items captured', static::formatItemCount($summary->totalItems)), $summary->totalItems === 0 ? $factory->keyFact('Current blocker', 'No backup items were captured.') : null, $summary->hasDegradations() ? $factory->keyFact('Current degradation', $summary->compactSummary) : null, $assessment instanceof TenantBackupHealthAssessment ? $factory->keyFact('Backup posture', static::backupHealthContinuityLabel($assessment), badge: $backupHealthBadge) : null, ])); } private static function backupDecisionReason(BackupQualitySummary $summary): string { if ($summary->totalItems === 0) { return 'No backup item inventory is available on this restore point.'; } if ($summary->hasDegradations()) { return $summary->summaryMessage; } return 'Captured item inventory is available for operator review.'; } private static function backupDecisionImpact(BackupQualitySummary $summary): string { if ($summary->totalItems === 0) { return 'Restore review should wait for a backup set with captured items.'; } if ($summary->hasDegradations()) { return 'Treat this backup as investigation input until degraded items are reviewed.'; } return 'Use this as restore review input only after confirming included items below.'; } private static function backupDecisionNextAction(BackupQualitySummary $summary): string { if ($summary->totalItems === 0) { return 'Create or refresh a backup set before starting restore review.'; } if ($summary->hasDegradations()) { return 'Review degraded included items and source operation before continuing into restore.'; } return 'Review included items below before starting any separate restore workflow.'; } /** * @return array{ * summaryLine?: ?string, * primaryFacts: list>, * diagnosticFacts: list> * }|null */ private static function backupDecisionCounts(EnterpriseDetailSectionFactory $factory, BackupQualitySummary $summary): ?array { $facts = static::nonZeroQualityFacts($factory, $summary); if ($facts === [] && $summary->totalItems > 0 && ! $summary->hasDegradations()) { return null; } return $factory->countPresentation( summaryLine: $summary->summaryMessage, primaryFacts: $facts, ); } /** * @return list> */ private static function nonZeroQualityFacts(EnterpriseDetailSectionFactory $factory, BackupQualitySummary $summary): array { return array_values(array_filter([ $summary->degradedItemCount > 0 ? $factory->keyFact('Degraded items', $summary->degradedItemCount) : null, $summary->metadataOnlyCount > 0 ? $factory->keyFact('Metadata-only items', $summary->metadataOnlyCount) : null, $summary->assignmentIssueCount > 0 ? $factory->keyFact('Assignment issues', $summary->assignmentIssueCount) : null, $summary->orphanedAssignmentCount > 0 ? $factory->keyFact('Orphaned assignments', $summary->orphanedAssignmentCount) : null, $summary->integrityWarningCount > 0 ? $factory->keyFact('Integrity warnings', $summary->integrityWarningCount) : null, $summary->unknownQualityCount > 0 ? $factory->keyFact('Unknown quality', $summary->unknownQualityCount) : null, ])); } private static function formatItemCount(int $count): string { return sprintf('%d item%s', $count, $count === 1 ? '' : 's'); } private static function formatDetailTimestamp(mixed $value): string { if (! $value instanceof Carbon) { return '—'; } return $value->toDayDateTimeString(); } private static function backupQualitySummary(BackupSet $record): \App\Support\BackupQuality\BackupQualitySummary { if ($record->trashed()) { $record->setRelation('items', $record->items()->withTrashed()->select([ 'id', 'backup_set_id', 'payload', 'metadata', 'assignments', ])->get()); } elseif (! $record->relationLoaded('items')) { $record->loadMissing([ 'items' => fn ($query) => $query->select([ 'id', 'backup_set_id', 'payload', 'metadata', 'assignments', ]), ]); } return app(BackupQualityResolver::class)->summarizeBackupSet($record); } private static function backupHealthContinuityAssessment(BackupSet $record): ?TenantBackupHealthAssessment { $requestedReason = request()->string('backup_health_reason')->toString(); if (! in_array($requestedReason, [ TenantBackupHealthAssessment::REASON_LATEST_BACKUP_STALE, TenantBackupHealthAssessment::REASON_LATEST_BACKUP_DEGRADED, ], true)) { return null; } /** @var TenantBackupHealthResolver $resolver */ $resolver = app(TenantBackupHealthResolver::class); $assessment = $resolver->assess((int) $record->managed_environment_id); if ($assessment->latestRelevantBackupSetId !== (int) $record->getKey()) { return null; } if ($assessment->primaryReason !== $requestedReason) { return null; } return $assessment; } private static function backupHealthContinuityLabel(TenantBackupHealthAssessment $assessment): string { return match ($assessment->primaryReason) { TenantBackupHealthAssessment::REASON_LATEST_BACKUP_STALE => 'Latest backup is stale', TenantBackupHealthAssessment::REASON_LATEST_BACKUP_DEGRADED => 'Latest backup is degraded', default => ucfirst($assessment->posture), }; } }