getId() !== 'admin') { return false; } return parent::shouldRegisterNavigation(); } public static function canViewAny(): bool { $user = auth()->user(); if (! $user instanceof User) { return false; } $workspace = self::resolveWorkspace(); if (! $workspace instanceof Workspace) { return false; } $resolver = app(\App\Services\Auth\WorkspaceCapabilityResolver::class); return $resolver->isMember($user, $workspace) && $resolver->can($user, $workspace, Capabilities::WORKSPACE_BASELINES_VIEW); } public static function canCreate(): bool { return false; } public static function canEdit(Model $record): bool { return false; } public static function canDelete(Model $record): bool { return false; } public static function canView(Model $record): bool { return self::canViewAny(); } public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration { return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::CrudListAndView) ->exempt(ActionSurfaceSlot::ListHeader, 'Snapshots are created by capture runs; no list-header actions.') ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ViewAction->value) ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'Snapshots are immutable; no row actions besides view.') ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'Snapshots are immutable; no bulk actions.') ->exempt(ActionSurfaceSlot::ListEmptyState, 'Empty-state CTA is intentionally omitted; snapshots appear after baseline captures.') ->exempt(ActionSurfaceSlot::DetailHeader, 'View page is informational and currently has no header actions.'); } public static function getEloquentQuery(): Builder { $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request()); return parent::getEloquentQuery() ->with('baselineProfile') ->when( $workspaceId !== null, fn (Builder $query): Builder => $query->where('workspace_id', (int) $workspaceId), ) ->when( $workspaceId === null, fn (Builder $query): Builder => $query->whereRaw('1 = 0'), ); } public static function form(Schema $schema): Schema { return $schema; } public static function table(Table $table): Table { return $table ->defaultSort('captured_at', 'desc') ->paginated(\App\Support\Filament\TablePaginationProfiles::resource()) ->persistFiltersInSession() ->persistSearchInSession() ->persistSortInSession() ->columns([ TextColumn::make('id') ->label('Snapshot') ->formatStateUsing(static fn (?int $state): string => $state ? '#'.$state : '—') ->sortable(), TextColumn::make('baselineProfile.name') ->label('Baseline') ->wrap() ->searchable() ->placeholder('—'), TextColumn::make('captured_at') ->label('Captured') ->since() ->sortable(), TextColumn::make('fidelity_summary') ->label('Fidelity') ->getStateUsing(static fn (BaselineSnapshot $record): string => self::fidelitySummary($record)) ->wrap(), TextColumn::make('snapshot_state') ->label('State') ->badge() ->getStateUsing(static fn (BaselineSnapshot $record): string => self::stateLabel($record)) ->color(static fn (BaselineSnapshot $record): string => self::hasGaps($record) ? 'warning' : 'success'), ]) ->filters([ SelectFilter::make('baseline_profile_id') ->label('Baseline') ->options(static::baselineProfileOptions()) ->searchable(), SelectFilter::make('snapshot_state') ->label('State') ->options(static::snapshotStateOptions()) ->query(fn (Builder $query, array $data): Builder => static::applySnapshotStateFilter($query, $data['value'] ?? null)), FilterPresets::dateRange('captured_at', 'Captured', 'captured_at'), ]) ->actions([ ViewAction::make()->label('View'), ]) ->bulkActions([]) ->emptyStateHeading('No baseline snapshots') ->emptyStateDescription('Capture a baseline snapshot to review evidence fidelity and compare tenants over time.') ->emptyStateIcon('heroicon-o-camera'); } public static function infolist(Schema $schema): Schema { return $schema ->schema([ Section::make('Snapshot') ->schema([ TextEntry::make('id') ->label('Snapshot') ->formatStateUsing(static fn (?int $state): string => $state ? '#'.$state : '—'), TextEntry::make('baselineProfile.name') ->label('Baseline'), TextEntry::make('captured_at') ->label('Captured') ->dateTime(), TextEntry::make('snapshot_state') ->label('State') ->badge() ->getStateUsing(static fn (BaselineSnapshot $record): string => self::stateLabel($record)) ->color(static fn (BaselineSnapshot $record): string => self::hasGaps($record) ? 'warning' : 'success'), TextEntry::make('fidelity_summary') ->label('Fidelity') ->getStateUsing(static fn (BaselineSnapshot $record): string => self::fidelitySummary($record)), TextEntry::make('evidence_gaps') ->label('Evidence gaps') ->getStateUsing(static fn (BaselineSnapshot $record): int => self::gapsCount($record)), TextEntry::make('snapshot_identity_hash') ->label('Identity hash') ->copyable() ->columnSpanFull(), ]) ->columns(2) ->columnSpanFull(), Section::make('Summary') ->schema([ ViewEntry::make('summary_jsonb') ->label('') ->view('filament.infolists.entries.snapshot-json') ->state(static fn (BaselineSnapshot $record): array => is_array($record->summary_jsonb) ? $record->summary_jsonb : []) ->columnSpanFull(), ]) ->columnSpanFull(), Section::make('Intune RBAC Role Definition References') ->schema([ RepeatableEntry::make('rbac_role_definition_references') ->label('') ->state(static fn (BaselineSnapshot $record): array => self::rbacRoleDefinitionReferences($record)) ->schema([ TextEntry::make('display_name') ->label('Role definition'), TextEntry::make('role_source') ->label('Role source') ->badge(), TextEntry::make('permission_blocks') ->label('Permission blocks'), TextEntry::make('identity_strategy') ->label('Identity') ->badge(), TextEntry::make('policy_version_reference') ->label('Baseline evidence'), TextEntry::make('observed_at') ->label('Observed at') ->placeholder('—'), ]) ->columns(2) ->columnSpanFull(), ]) ->visible(static fn (BaselineSnapshot $record): bool => self::rbacRoleDefinitionReferences($record) !== []) ->columnSpanFull(), ]); } public static function getPages(): array { return [ 'index' => Pages\ListBaselineSnapshots::route('/'), 'view' => Pages\ViewBaselineSnapshot::route('/{record}'), ]; } /** * @return array */ private static function baselineProfileOptions(): array { $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request()); if ($workspaceId === null) { return []; } return BaselineProfile::query() ->where('workspace_id', (int) $workspaceId) ->orderBy('name') ->pluck('name', 'id') ->all(); } /** * @return array */ private static function snapshotStateOptions(): array { return [ 'complete' => 'Complete', 'with_gaps' => 'Captured with gaps', ]; } private static function resolveWorkspace(): ?Workspace { $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request()); if ($workspaceId === null) { return null; } return Workspace::query()->whereKey($workspaceId)->first(); } private static function summary(BaselineSnapshot $snapshot): array { return is_array($snapshot->summary_jsonb) ? $snapshot->summary_jsonb : []; } private static function fidelityCounts(BaselineSnapshot $snapshot): array { $summary = self::summary($snapshot); $counts = $summary['fidelity_counts'] ?? null; $counts = is_array($counts) ? $counts : []; $content = $counts['content'] ?? 0; $meta = $counts['meta'] ?? 0; return [ 'content' => is_numeric($content) ? (int) $content : 0, 'meta' => is_numeric($meta) ? (int) $meta : 0, ]; } private static function fidelitySummary(BaselineSnapshot $snapshot): string { $counts = self::fidelityCounts($snapshot); return sprintf('Content %d, Meta %d', (int) ($counts['content'] ?? 0), (int) ($counts['meta'] ?? 0)); } private static function gapsCount(BaselineSnapshot $snapshot): int { $summary = self::summary($snapshot); $gaps = $summary['gaps'] ?? null; $gaps = is_array($gaps) ? $gaps : []; $count = $gaps['count'] ?? 0; return is_numeric($count) ? (int) $count : 0; } private static function hasGaps(BaselineSnapshot $snapshot): bool { return self::gapsCount($snapshot) > 0; } /** * @return list */ private static function rbacRoleDefinitionReferences(BaselineSnapshot $snapshot): array { return $snapshot->items() ->where('policy_type', 'intuneRoleDefinition') ->orderBy('id') ->get() ->map(static function (\App\Models\BaselineSnapshotItem $item): array { $meta = is_array($item->meta_jsonb) ? $item->meta_jsonb : []; $policyVersionId = data_get($meta, 'version_reference.policy_version_id'); $rolePermissionCount = data_get($meta, 'rbac.role_permission_count'); $identityStrategy = (string) data_get($meta, 'identity.strategy', 'display_name'); return [ 'display_name' => (string) data_get($meta, 'display_name', '—'), 'role_source' => match (data_get($meta, 'rbac.is_built_in')) { true => 'Built-in', false => 'Custom', default => 'Unknown', }, 'permission_blocks' => is_numeric($rolePermissionCount) ? (string) ((int) $rolePermissionCount) : '—', 'identity_strategy' => $identityStrategy === 'external_id' ? 'Role definition ID' : 'Display name', 'policy_version_reference' => is_numeric($policyVersionId) ? 'Policy version #'.((int) $policyVersionId) : 'Metadata only', 'observed_at' => data_get($meta, 'evidence.observed_at'), ]; }) ->all(); } private static function stateLabel(BaselineSnapshot $snapshot): string { return self::hasGaps($snapshot) ? 'Captured with gaps' : 'Complete'; } private static function applySnapshotStateFilter(Builder $query, mixed $value): Builder { if (! is_string($value) || trim($value) === '') { return $query; } $gapCountExpression = self::gapCountExpression($query); return match ($value) { 'complete' => $query->whereRaw("{$gapCountExpression} = 0"), 'with_gaps' => $query->whereRaw("{$gapCountExpression} > 0"), default => $query, }; } private static function gapCountExpression(Builder $query): string { return match ($query->getConnection()->getDriverName()) { 'sqlite' => "COALESCE(CAST(json_extract(summary_jsonb, '$.gaps.count') AS INTEGER), 0)", 'pgsql' => "COALESCE((summary_jsonb #>> '{gaps,count}')::int, 0)", default => "COALESCE(CAST(JSON_EXTRACT(summary_jsonb, '$.gaps.count') AS UNSIGNED), 0)", }; } }