TenantAtlas/apps/platform/app/Filament/Widgets/TenantConfiguration/CoverageV2ResourceInstancesTable.php
ahmido 69d4ecbbd2 feat: complete spec 421 Entra comparable/renderable pack (#488)
Implements the bounded Spec 421 Entra comparable/renderable pack on the existing Coverage v2 operator surface.

- Adds typed Conditional Access normalization, comparison, and render summaries
- Keeps Security Defaults and other optional Entra types deferred until evidence-backed
- Preserves the existing Coverage v2 surface with claim-guard and redaction hardening
- Includes focused unit, feature, and browser coverage already recorded in the implementation report

Validation is documented in `specs/421-entra-core-comparable-renderable-pack/implementation-report.md`.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #488
2026-06-27 22:12:01 +00:00

201 lines
9.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Filament\Widgets\TenantConfiguration;
use App\Models\ManagedEnvironment;
use App\Models\TenantConfigurationResource;
use App\Models\User;
use App\Services\Auth\ManagedEnvironmentAccessScopeResolver;
use App\Services\TenantConfiguration\CoverageV2ReadinessReadModel;
use App\Support\Auth\Capabilities;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\Filament\TablePaginationProfiles;
use Filament\Actions\Action;
use Filament\Support\Enums\FontFamily;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Livewire\Attributes\Locked;
class CoverageV2ResourceInstancesTable extends TableWidget
{
protected static bool $isLazy = false;
protected int|string|array $columnSpan = 'full';
#[Locked]
public ?int $environmentId = null;
public function mount(?int $environmentId = null): void
{
$this->environmentId = $environmentId;
$this->authorizeEnvironment();
}
public function table(Table $table): Table
{
return $table
->heading('Resource instances')
->deferLoading(false)
->query(fn (): Builder => app(CoverageV2ReadinessReadModel::class)->resourceInstanceQuery($this->environment()))
->searchable()
->searchPlaceholder('Search resources')
->paginated(TablePaginationProfiles::productSurface())
->columns([
TextColumn::make('source_display_name')
->label('Resource')
->searchable()
->sortable()
->description(fn (TenantConfigurationResource $record): string => (string) $record->canonical_resource_key)
->limit(48)
->tooltip(fn (TenantConfigurationResource $record): string => (string) ($record->source_display_name ?: $record->canonical_resource_key))
->action($this->inspectAction()),
TextColumn::make('resourceType.display_name')
->label('Resource type')
->searchable()
->sortable()
->wrap(),
TextColumn::make('providerConnection.display_name')
->label('Provider connection')
->sortable()
->placeholder('Unassigned provider connection')
->limit(32)
->tooltip(fn (?string $state): ?string => filled($state) ? $state : null)
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('coverage_level')
->label('Coverage level')
->state(fn (TenantConfigurationResource $record): ?string => $record->latestEvidence?->coverage_level?->value)
->badge()
->placeholder('Not captured')
->formatStateUsing(BadgeRenderer::label(BadgeDomain::CoverageV2CoverageLevel))
->color(BadgeRenderer::color(BadgeDomain::CoverageV2CoverageLevel))
->icon(BadgeRenderer::icon(BadgeDomain::CoverageV2CoverageLevel))
->iconColor(BadgeRenderer::iconColor(BadgeDomain::CoverageV2CoverageLevel)),
TextColumn::make('latest_evidence_state')
->label('Evidence state')
->badge()
->sortable()
->formatStateUsing(BadgeRenderer::label(BadgeDomain::CoverageV2EvidenceState))
->color(BadgeRenderer::color(BadgeDomain::CoverageV2EvidenceState))
->icon(BadgeRenderer::icon(BadgeDomain::CoverageV2EvidenceState))
->iconColor(BadgeRenderer::iconColor(BadgeDomain::CoverageV2EvidenceState)),
TextColumn::make('latest_identity_state')
->label('Identity state')
->badge()
->sortable()
->formatStateUsing(BadgeRenderer::label(BadgeDomain::CoverageV2IdentityState))
->color(BadgeRenderer::color(BadgeDomain::CoverageV2IdentityState))
->icon(BadgeRenderer::icon(BadgeDomain::CoverageV2IdentityState))
->iconColor(BadgeRenderer::iconColor(BadgeDomain::CoverageV2IdentityState)),
TextColumn::make('latest_claim_state')
->label('Claim state')
->badge()
->sortable()
->formatStateUsing(BadgeRenderer::label(BadgeDomain::CoverageV2ClaimState))
->color(BadgeRenderer::color(BadgeDomain::CoverageV2ClaimState))
->icon(BadgeRenderer::icon(BadgeDomain::CoverageV2ClaimState))
->iconColor(BadgeRenderer::iconColor(BadgeDomain::CoverageV2ClaimState)),
TextColumn::make('latest_captured_at')
->label('Last captured')
->dateTime()
->sortable()
->placeholder('Not captured'),
TextColumn::make('latest_payload_hash')
->label('Evidence hash')
->state(fn (TenantConfigurationResource $record): ?string => $record->latestEvidence?->payload_hash ?: $record->latest_payload_hash)
->copyable()
->limit(16)
->fontFamily(FontFamily::Mono)
->placeholder('No hash')
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
SelectFilter::make('resource_type_id')
->label('Resource type')
->options(fn (): array => \App\Models\TenantConfigurationResourceType::query()
->active()
->orderBy('display_name')
->pluck('display_name', 'id')
->mapWithKeys(fn (string $label, int|string $id): array => [(string) $id => $label])
->all()),
SelectFilter::make('provider_connection_id')
->label('Provider connection')
->options(fn (): array => app(CoverageV2ReadinessReadModel::class)->providerConnectionOptions($this->environment())),
SelectFilter::make('coverage_level')
->label('Coverage level')
->options(CoverageV2ReadinessReadModel::coverageLevelOptions())
->query(fn (Builder $query, array $data): Builder => filled($data['value'] ?? null)
? $query->whereHas('latestEvidence', fn (Builder $latestEvidence): Builder => $latestEvidence->where('coverage_level', $data['value']))
: $query),
SelectFilter::make('latest_evidence_state')
->label('Evidence state')
->options(CoverageV2ReadinessReadModel::evidenceStateOptions()),
SelectFilter::make('latest_identity_state')
->label('Identity state')
->options(CoverageV2ReadinessReadModel::identityStateOptions()),
SelectFilter::make('latest_claim_state')
->label('Claim state')
->options(CoverageV2ReadinessReadModel::claimStateOptions()),
SelectFilter::make('source_class')
->label('Source class')
->options(CoverageV2ReadinessReadModel::sourceClassOptions()),
])
->recordUrl(null)
->bulkActions([])
->emptyStateHeading('No captured Coverage v2 resources')
->emptyStateDescription('No Coverage v2 resource rows match this environment and provider scope. Capture prerequisites must be satisfied before this page can support activation-readiness review.')
->emptyStateIcon('heroicon-o-shield-exclamation');
}
private function environment(): ManagedEnvironment
{
$environment = ManagedEnvironment::query()
->whereKey($this->environmentId)
->first();
if (! $environment instanceof ManagedEnvironment) {
abort(404);
}
return $environment;
}
private function authorizeEnvironment(): void
{
$environment = $this->environment();
$user = auth()->user();
if (! $user instanceof User) {
abort(403);
}
$decision = app(ManagedEnvironmentAccessScopeResolver::class)
->decision($user, $environment, Capabilities::EVIDENCE_VIEW);
if (! $decision->allowed()) {
abort($decision->denialHttpStatus ?? 403);
}
}
private function inspectAction(): Action
{
return Action::make('inspect')
->label('Inspect')
->icon('heroicon-o-eye')
->color('gray')
->slideOver()
->modalSubmitAction(false)
->modalCancelActionLabel('Close')
->modalHeading(fn (TenantConfigurationResource $record): string => (string) ($record->source_display_name ?: $record->canonical_resource_key))
->modalContent(fn (TenantConfigurationResource $record): View => view('filament.modals.tenant-configuration.coverage-v2-resource-inspect', [
'details' => app(CoverageV2ReadinessReadModel::class)->inspectDetails($record, $this->environment(), auth()->user()),
]));
}
}