TenantAtlas/app/Support/Navigation/RelatedNavigationResolver.php
ahmido 8ee1174c8d feat: add resolved reference presentation layer (#161)
## Summary
- add the shared resolved-reference foundation with registry, resolvers, presenters, and badge semantics
- refactor related context, assignment evidence, and policy-version assignment rendering toward label-first reference presentation
- add Spec 132 artifacts and focused Pest coverage for reference resolution, degraded states, canonical linking, and tenant-context carryover

## Verification
- `vendor/bin/sail bin pint --dirty --format agent`
- focused Pest verification was marked complete in the task artifact

## Notes
- this PR is opened from the current session branch
- `specs/132-guid-context-resolver/tasks.md` reflects in-progress completion state for the implemented tasks

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #161
2026-03-10 18:52:52 +00:00

846 lines
32 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Support\Navigation;
use App\Filament\Resources\BackupSetResource;
use App\Filament\Resources\BaselineSnapshotResource;
use App\Filament\Resources\FindingResource;
use App\Filament\Resources\PolicyVersionResource;
use App\Filament\Resources\RestoreRunResource;
use App\Models\BackupSet;
use App\Models\BaselineProfile;
use App\Models\BaselineSnapshot;
use App\Models\BaselineSnapshotItem;
use App\Models\Finding;
use App\Models\OperationRun;
use App\Models\Policy;
use App\Models\PolicyVersion;
use App\Models\RestoreRun;
use App\Models\Tenant;
use App\Models\User;
use App\Models\Workspace;
use App\Services\Auth\CapabilityResolver;
use App\Services\Auth\WorkspaceCapabilityResolver;
use App\Support\Auth\Capabilities;
use App\Support\OperationRunLinks;
use App\Support\References\ReferenceClass;
use App\Support\References\ReferenceDescriptor;
use App\Support\References\ReferenceResolverRegistry;
use App\Support\References\RelatedContextReferenceAdapter;
use Filament\Facades\Filament;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
final class RelatedNavigationResolver
{
public function __construct(
private readonly CrossResourceNavigationMatrix $matrix,
private readonly RelatedActionLabelCatalog $labels,
private readonly CapabilityResolver $capabilityResolver,
private readonly WorkspaceCapabilityResolver $workspaceCapabilityResolver,
private readonly ReferenceResolverRegistry $referenceResolverRegistry,
private readonly RelatedContextReferenceAdapter $relatedContextReferenceAdapter,
) {}
/**
* @return list<array{
* key: string,
* label: string,
* value: string,
* secondaryValue: ?string,
* targetUrl: ?string,
* targetKind: string,
* availability: string,
* unavailableReason: ?string,
* contextBadge: ?string,
* priority: int,
* actionLabel: string
* }>
*/
public function detailEntries(string $sourceType, Model $record): array
{
return array_map(
static fn (RelatedContextEntry $entry): array => $entry->toArray(),
$this->resolveEntries($sourceType, CrossResourceNavigationMatrix::SURFACE_DETAIL_SECTION, $record),
);
}
public function primaryListAction(string $sourceType, Model $record): ?RelatedContextEntry
{
$entries = array_values(array_filter(
$this->resolveEntries($sourceType, CrossResourceNavigationMatrix::SURFACE_LIST_ROW, $record),
static fn (RelatedContextEntry $entry): bool => $entry->isAvailable(),
));
return $entries[0] ?? null;
}
/**
* @return array<string, string>
*/
public function operationLinks(OperationRun $run, ?Tenant $tenant): array
{
$entries = array_filter(
$this->resolveEntries(CrossResourceNavigationMatrix::SOURCE_OPERATION_RUN, CrossResourceNavigationMatrix::SURFACE_DETAIL_SECTION, $run),
static fn (RelatedContextEntry $entry): bool => $entry->isAvailable(),
);
$links = [];
foreach ($entries as $entry) {
$links[$entry->actionLabel] = (string) $entry->targetUrl;
}
if ($tenant instanceof Tenant) {
$links = ['Open operations' => OperationRunLinks::index($tenant)] + $links;
} else {
$links = ['Open operations' => OperationRunLinks::index()] + $links;
}
return $links;
}
/**
* @return list<RelatedContextEntry>
*/
public function headerEntries(string $sourceType, Model $record): array
{
return $this->resolveEntries($sourceType, CrossResourceNavigationMatrix::SURFACE_DETAIL_HEADER, $record);
}
/**
* @return list<RelatedContextEntry>
*/
private function resolveEntries(string $sourceType, string $surface, Model $record): array
{
$entries = [];
foreach ($this->matrix->rulesFor($sourceType, $surface) as $rule) {
$entry = $this->resolveRule($rule, $record);
if ($entry instanceof RelatedContextEntry) {
$entries[] = $entry;
}
}
usort(
$entries,
static fn (RelatedContextEntry $left, RelatedContextEntry $right): int => $left->priority <=> $right->priority,
);
return $entries;
}
private function resolveRule(NavigationMatrixRule $rule, Model $record): ?RelatedContextEntry
{
return match ($rule->sourceType) {
CrossResourceNavigationMatrix::SOURCE_FINDING => $record instanceof Finding ? $this->resolveFindingRule($rule, $record) : null,
CrossResourceNavigationMatrix::SOURCE_POLICY_VERSION => $record instanceof PolicyVersion ? $this->resolvePolicyVersionRule($rule, $record) : null,
CrossResourceNavigationMatrix::SOURCE_BASELINE_SNAPSHOT => $record instanceof BaselineSnapshot ? $this->resolveBaselineSnapshotRule($rule, $record) : null,
CrossResourceNavigationMatrix::SOURCE_BACKUP_SET => $record instanceof BackupSet ? $this->resolveBackupSetRule($rule, $record) : null,
CrossResourceNavigationMatrix::SOURCE_OPERATION_RUN => $record instanceof OperationRun ? $this->resolveOperationRunRule($rule, $record) : null,
CrossResourceNavigationMatrix::SOURCE_BASELINE_PROFILE => $record instanceof BaselineProfile ? $this->resolveBaselineProfileRule($rule, $record) : null,
default => null,
};
}
private function resolveFindingRule(NavigationMatrixRule $rule, Finding $finding): ?RelatedContextEntry
{
return match ($rule->relationKey) {
'baseline_snapshot' => $this->baselineSnapshotEntry(
rule: $rule,
snapshotId: $this->findingSnapshotId($finding),
workspaceId: (int) $finding->workspace_id,
),
'source_run' => $this->operationRunEntry(
rule: $rule,
runId: $this->findingRunId($finding),
workspaceId: (int) $finding->workspace_id,
context: $this->contextForFinding($finding, $rule->sourceSurface),
),
'current_policy_version' => $this->policyVersionEntry(
rule: $rule,
policyVersionId: $this->findingPolicyVersionId($finding),
tenantId: (int) $finding->tenant_id,
),
'parent_policy' => $this->parentPolicyEntryForFinding($rule, $finding),
'baseline_profile' => $this->baselineProfileEntry(
rule: $rule,
profileId: $this->findingProfileId($finding),
workspaceId: (int) $finding->workspace_id,
),
default => null,
};
}
private function resolvePolicyVersionRule(NavigationMatrixRule $rule, PolicyVersion $version): ?RelatedContextEntry
{
return match ($rule->relationKey) {
'parent_policy' => $this->policyEntry(
rule: $rule,
policy: $version->policy,
),
'baseline_snapshot' => $this->policyVersionSnapshotEntry($rule, $version),
'baseline_profile' => $this->baselineProfileEntry(
rule: $rule,
profileId: is_numeric($version->baseline_profile_id ?? null) ? (int) $version->baseline_profile_id : null,
workspaceId: (int) $version->workspace_id,
),
'source_run' => $this->operationRunEntry(
rule: $rule,
runId: is_numeric($version->operation_run_id ?? null) ? (int) $version->operation_run_id : null,
workspaceId: (int) $version->workspace_id,
context: $this->contextForPolicyVersion($version, $rule->sourceSurface),
),
default => null,
};
}
private function resolveBaselineSnapshotRule(NavigationMatrixRule $rule, BaselineSnapshot $snapshot): ?RelatedContextEntry
{
return match ($rule->relationKey) {
'baseline_profile' => $this->policyProfileEntry(
rule: $rule,
profile: $snapshot->baselineProfile,
),
'source_run' => $this->snapshotRunEntry($rule, $snapshot),
'policy_version' => $this->snapshotPolicyVersionEntry($rule, $snapshot),
default => null,
};
}
private function resolveBackupSetRule(NavigationMatrixRule $rule, BackupSet $backupSet): ?RelatedContextEntry
{
return match ($rule->relationKey) {
'source_run' => $this->backupSetRunEntry($rule, $backupSet),
'operations' => $this->operationsEntry(
rule: $rule,
tenant: $backupSet->tenant,
context: $this->contextForBackupSet($backupSet, $rule->sourceSurface),
),
default => null,
};
}
private function resolveOperationRunRule(NavigationMatrixRule $rule, OperationRun $run): ?RelatedContextEntry
{
$context = is_array($run->context) ? $run->context : [];
return match ($rule->relationKey) {
'backup_set' => $this->backupSetEntry(
rule: $rule,
backupSetId: is_numeric($context['backup_set_id'] ?? null) ? (int) $context['backup_set_id'] : null,
tenantId: is_numeric($run->tenant_id ?? null) ? (int) $run->tenant_id : null,
),
'restore_run' => $this->restoreRunEntry(
rule: $rule,
restoreRunId: is_numeric($context['restore_run_id'] ?? null) ? (int) $context['restore_run_id'] : null,
tenantId: is_numeric($run->tenant_id ?? null) ? (int) $run->tenant_id : null,
),
'baseline_profile' => $this->baselineProfileEntry(
rule: $rule,
profileId: is_numeric($context['baseline_profile_id'] ?? null) ? (int) $context['baseline_profile_id'] : null,
workspaceId: (int) $run->workspace_id,
),
'baseline_snapshot' => $this->baselineSnapshotEntry(
rule: $rule,
snapshotId: is_numeric($context['baseline_snapshot_id'] ?? null) ? (int) $context['baseline_snapshot_id'] : null,
workspaceId: (int) $run->workspace_id,
),
'parent_policy' => $this->operationRunPolicyEntry($rule, $run),
'operations' => $this->operationsEntry(
rule: $rule,
tenant: $run->tenant,
context: $this->contextForOperationRun($run),
),
default => null,
};
}
private function resolveBaselineProfileRule(NavigationMatrixRule $rule, BaselineProfile $profile): ?RelatedContextEntry
{
return match ($rule->relationKey) {
'baseline_snapshot' => $this->baselineSnapshotEntry(
rule: $rule,
snapshotId: is_numeric($profile->active_snapshot_id ?? null) ? (int) $profile->active_snapshot_id : null,
workspaceId: (int) $profile->workspace_id,
),
default => null,
};
}
private function findingSnapshotId(Finding $finding): ?int
{
$snapshotId = Arr::get($finding->evidence_jsonb ?? [], 'provenance.baseline_snapshot_id');
if (! is_numeric($snapshotId)) {
$snapshotId = Arr::get($finding->evidence_jsonb ?? [], 'baseline_snapshot_id');
}
return is_numeric($snapshotId) ? (int) $snapshotId : null;
}
private function findingProfileId(Finding $finding): ?int
{
$profileId = Arr::get($finding->evidence_jsonb ?? [], 'provenance.baseline_profile_id');
return is_numeric($profileId) ? (int) $profileId : null;
}
private function findingPolicyVersionId(Finding $finding): ?int
{
$policyVersionId = Arr::get($finding->evidence_jsonb ?? [], 'current.policy_version_id');
if (! is_numeric($policyVersionId)) {
$policyVersionId = Arr::get($finding->evidence_jsonb ?? [], 'baseline.policy_version_id');
}
return is_numeric($policyVersionId) ? (int) $policyVersionId : null;
}
private function findingRunId(Finding $finding): ?int
{
$runId = Arr::get($finding->evidence_jsonb ?? [], 'provenance.compare_operation_run_id');
if (is_numeric($runId)) {
return (int) $runId;
}
if (is_numeric($finding->current_operation_run_id ?? null)) {
return (int) $finding->current_operation_run_id;
}
return is_numeric($finding->baseline_operation_run_id ?? null)
? (int) $finding->baseline_operation_run_id
: null;
}
private function policyVersionSnapshotEntry(NavigationMatrixRule $rule, PolicyVersion $version): ?RelatedContextEntry
{
$snapshotItem = BaselineSnapshotItem::query()
->where('meta_jsonb->version_reference->policy_version_id', (int) $version->getKey())
->whereHas('snapshot', fn ($query) => $query->where('workspace_id', (int) $version->workspace_id))
->with('snapshot.baselineProfile')
->latest('id')
->first();
if (! $snapshotItem instanceof BaselineSnapshotItem) {
return null;
}
$snapshot = $snapshotItem->snapshot;
return $snapshot instanceof BaselineSnapshot
? $this->baselineSnapshotEntry($rule, (int) $snapshot->getKey(), (int) $version->workspace_id)
: null;
}
private function snapshotRunEntry(NavigationMatrixRule $rule, BaselineSnapshot $snapshot): ?RelatedContextEntry
{
$candidate = OperationRun::query()
->where('workspace_id', (int) $snapshot->workspace_id)
->where('context->baseline_snapshot_id', (int) $snapshot->getKey())
->orderByDesc('completed_at')
->orderByDesc('id')
->first();
return $candidate instanceof OperationRun
? $this->operationRunEntry(
rule: $rule,
runId: (int) $candidate->getKey(),
workspaceId: (int) $snapshot->workspace_id,
context: $this->contextForBaselineSnapshot($snapshot, $rule->sourceSurface),
)
: null;
}
private function snapshotPolicyVersionEntry(NavigationMatrixRule $rule, BaselineSnapshot $snapshot): ?RelatedContextEntry
{
$snapshotItem = BaselineSnapshotItem::query()
->where('baseline_snapshot_id', (int) $snapshot->getKey())
->whereNotNull('meta_jsonb->version_reference->policy_version_id')
->orderBy('id')
->first();
if (! $snapshotItem instanceof BaselineSnapshotItem) {
return null;
}
$policyVersionId = data_get($snapshotItem->meta_jsonb, 'version_reference.policy_version_id');
return $this->policyVersionEntry(
rule: $rule,
policyVersionId: is_numeric($policyVersionId) ? (int) $policyVersionId : null,
tenantId: $this->activeTenantId(),
);
}
private function backupSetRunEntry(NavigationMatrixRule $rule, BackupSet $backupSet): ?RelatedContextEntry
{
$candidate = OperationRun::query()
->where('tenant_id', (int) $backupSet->tenant_id)
->where('context->backup_set_id', (int) $backupSet->getKey())
->orderByDesc('completed_at')
->orderByDesc('id')
->first();
return $candidate instanceof OperationRun
? $this->operationRunEntry(
rule: $rule,
runId: (int) $candidate->getKey(),
workspaceId: (int) $backupSet->workspace_id,
context: $this->contextForBackupSet($backupSet, $rule->sourceSurface),
)
: null;
}
private function operationRunPolicyEntry(NavigationMatrixRule $rule, OperationRun $run): ?RelatedContextEntry
{
$policyId = data_get($run->context, 'policy_id');
if (! is_numeric($policyId)) {
return null;
}
$policy = Policy::query()
->whereKey((int) $policyId)
->where('tenant_id', (int) $run->tenant_id)
->first();
return $this->policyEntry($rule, $policy);
}
private function parentPolicyEntryForFinding(NavigationMatrixRule $rule, Finding $finding): ?RelatedContextEntry
{
$policyVersionId = $this->findingPolicyVersionId($finding);
if ($policyVersionId === null) {
return null;
}
$version = PolicyVersion::query()
->with('policy')
->whereKey($policyVersionId)
->where('tenant_id', (int) $finding->tenant_id)
->first();
if (! $version instanceof PolicyVersion) {
return null;
}
return $this->policyEntry($rule, $version->policy);
}
private function baselineSnapshotEntry(
NavigationMatrixRule $rule,
?int $snapshotId,
int $workspaceId,
): ?RelatedContextEntry {
if ($snapshotId === null || $snapshotId <= 0) {
return null;
}
return $this->resolveReferenceEntry(
rule: $rule,
descriptor: new ReferenceDescriptor(
referenceClass: ReferenceClass::BaselineSnapshot,
rawIdentifier: (string) $snapshotId,
workspaceId: $workspaceId,
sourceType: $rule->sourceType,
sourceSurface: $rule->sourceSurface,
linkedModelId: $snapshotId,
),
);
}
private function baselineProfileEntry(
NavigationMatrixRule $rule,
?int $profileId,
int $workspaceId,
): ?RelatedContextEntry {
if ($profileId === null || $profileId <= 0) {
return null;
}
$profile = BaselineProfile::query()
->whereKey($profileId)
->where('workspace_id', $workspaceId)
->first();
if (! $profile instanceof BaselineProfile) {
return $this->unavailableEntry($rule, (string) $profileId, 'missing');
}
return $this->policyProfileEntry($rule, $profile);
}
private function policyProfileEntry(NavigationMatrixRule $rule, ?BaselineProfile $profile): ?RelatedContextEntry
{
if (! $profile instanceof BaselineProfile) {
return null;
}
return $this->resolveReferenceEntry(
rule: $rule,
descriptor: new ReferenceDescriptor(
referenceClass: ReferenceClass::BaselineProfile,
rawIdentifier: (string) $profile->getKey(),
workspaceId: (int) $profile->workspace_id,
sourceType: $rule->sourceType,
sourceSurface: $rule->sourceSurface,
fallbackLabel: (string) $profile->name,
linkedModelId: (int) $profile->getKey(),
),
);
}
private function operationRunEntry(
NavigationMatrixRule $rule,
?int $runId,
int $workspaceId,
?CanonicalNavigationContext $context = null,
): ?RelatedContextEntry {
if ($runId === null || $runId <= 0) {
return null;
}
return $this->resolveReferenceEntry(
rule: $rule,
descriptor: new ReferenceDescriptor(
referenceClass: ReferenceClass::OperationRun,
rawIdentifier: (string) $runId,
workspaceId: $workspaceId,
sourceType: $rule->sourceType,
sourceSurface: $rule->sourceSurface,
linkedModelId: $runId,
context: [
'navigation_context' => $context,
],
),
);
}
private function policyVersionEntry(
NavigationMatrixRule $rule,
?int $policyVersionId,
?int $tenantId,
): ?RelatedContextEntry {
if ($policyVersionId === null || $policyVersionId <= 0 || $tenantId === null || $tenantId <= 0) {
return null;
}
return $this->resolveReferenceEntry(
rule: $rule,
descriptor: new ReferenceDescriptor(
referenceClass: ReferenceClass::PolicyVersion,
rawIdentifier: (string) $policyVersionId,
tenantId: $tenantId,
sourceType: $rule->sourceType,
sourceSurface: $rule->sourceSurface,
linkedModelId: $policyVersionId,
),
);
}
private function policyEntry(NavigationMatrixRule $rule, ?Policy $policy): ?RelatedContextEntry
{
if (! $policy instanceof Policy) {
return null;
}
return $this->resolveReferenceEntry(
rule: $rule,
descriptor: new ReferenceDescriptor(
referenceClass: ReferenceClass::Policy,
rawIdentifier: (string) $policy->getKey(),
tenantId: is_numeric($policy->tenant_id ?? null) ? (int) $policy->tenant_id : null,
sourceType: $rule->sourceType,
sourceSurface: $rule->sourceSurface,
fallbackLabel: (string) ($policy->display_name ?: 'Policy'),
linkedModelId: (int) $policy->getKey(),
),
);
}
private function backupSetEntry(
NavigationMatrixRule $rule,
?int $backupSetId,
?int $tenantId,
): ?RelatedContextEntry {
if ($backupSetId === null || $backupSetId <= 0 || $tenantId === null || $tenantId <= 0) {
return null;
}
return $this->resolveReferenceEntry(
rule: $rule,
descriptor: new ReferenceDescriptor(
referenceClass: ReferenceClass::BackupSet,
rawIdentifier: (string) $backupSetId,
tenantId: $tenantId,
sourceType: $rule->sourceType,
sourceSurface: $rule->sourceSurface,
linkedModelId: $backupSetId,
),
);
}
private function resolveReferenceEntry(NavigationMatrixRule $rule, ReferenceDescriptor $descriptor): ?RelatedContextEntry
{
return $this->relatedContextReferenceAdapter->adapt(
rule: $rule,
reference: $this->referenceResolverRegistry->resolve($descriptor),
);
}
private function restoreRunEntry(
NavigationMatrixRule $rule,
?int $restoreRunId,
?int $tenantId,
): ?RelatedContextEntry {
if ($restoreRunId === null || $restoreRunId <= 0 || $tenantId === null || $tenantId <= 0) {
return null;
}
$restoreRun = RestoreRun::query()
->with('tenant')
->whereKey($restoreRunId)
->where('tenant_id', $tenantId)
->first();
if (! $restoreRun instanceof RestoreRun) {
return $this->unavailableEntry($rule, (string) $restoreRunId, 'missing');
}
if (! $this->canOpenTenantRecord($restoreRun->tenant, Capabilities::TENANT_VIEW)) {
return $this->unavailableEntry($rule, '#'.$restoreRunId, 'unauthorized');
}
return RelatedContextEntry::available(
key: $rule->relationKey,
label: $this->labels->entryLabel($rule->relationKey),
value: 'Restore run',
secondaryValue: '#'.$restoreRun->getKey(),
targetUrl: RestoreRunResource::getUrl('view', ['record' => $restoreRun], tenant: $restoreRun->tenant),
targetKind: $rule->targetType,
priority: $rule->priority,
actionLabel: $this->labels->actionLabel($rule->relationKey),
contextBadge: 'Tenant',
);
}
private function operationsEntry(
NavigationMatrixRule $rule,
?Tenant $tenant,
?CanonicalNavigationContext $context = null,
): ?RelatedContextEntry {
if (! $tenant instanceof Tenant) {
return null;
}
if (! $this->canOpenTenantRecord($tenant, Capabilities::TENANT_VIEW)) {
return null;
}
return RelatedContextEntry::available(
key: $rule->relationKey,
label: $this->labels->entryLabel($rule->relationKey),
value: 'Operations',
secondaryValue: $tenant->name,
targetUrl: OperationRunLinks::index($tenant, $context),
targetKind: $rule->targetType,
priority: $rule->priority,
actionLabel: $this->labels->actionLabel($rule->relationKey),
contextBadge: 'Tenant context',
);
}
private function unavailableEntry(NavigationMatrixRule $rule, ?string $referenceValue, string $reason): ?RelatedContextEntry
{
if ($rule->missingStatePolicy === 'hide') {
return null;
}
return RelatedContextEntry::unavailable(
key: $rule->relationKey,
label: $this->labels->entryLabel($rule->relationKey),
state: new UnavailableRelationState(
relationKey: $rule->relationKey,
referenceValue: $referenceValue,
reason: $reason,
message: $this->labels->unavailableMessage($rule->relationKey, $reason),
showReference: $rule->missingStatePolicy === 'show_reference_only',
),
targetKind: $rule->targetType,
priority: $rule->priority,
actionLabel: $this->labels->actionLabel($rule->relationKey),
);
}
private function canOpenOperationRun(OperationRun $run): bool
{
$user = auth()->user();
return $user instanceof User && $user->can('view', $run);
}
private function canOpenWorkspaceBaselines(int $workspaceId): bool
{
$user = auth()->user();
if (! $user instanceof User) {
return false;
}
$workspace = Workspace::query()->whereKey($workspaceId)->first();
if (! $workspace instanceof Workspace) {
return false;
}
return $this->workspaceCapabilityResolver->isMember($user, $workspace)
&& $this->workspaceCapabilityResolver->can($user, $workspace, Capabilities::WORKSPACE_BASELINES_VIEW);
}
private function canOpenTenantRecord(?Tenant $tenant, string $capability): bool
{
$user = auth()->user();
return $tenant instanceof Tenant
&& $user instanceof User
&& $this->capabilityResolver->isMember($user, $tenant)
&& $this->capabilityResolver->can($user, $tenant, $capability);
}
private function canOpenPolicyVersion(PolicyVersion $version): bool
{
$tenant = $version->tenant;
if (! $tenant instanceof Tenant || ! $this->canOpenTenantRecord($tenant, Capabilities::TENANT_VIEW)) {
return false;
}
if (in_array((string) $version->capture_purpose?->value, ['baseline_capture', 'baseline_compare'], true)) {
$user = auth()->user();
return $user instanceof User
&& $this->capabilityResolver->isMember($user, $tenant)
&& $this->capabilityResolver->can($user, $tenant, Capabilities::TENANT_SYNC);
}
return true;
}
private function canOpenPolicy(Policy $policy): bool
{
return $this->canOpenTenantRecord($policy->tenant, Capabilities::TENANT_VIEW);
}
private function contextForFinding(Finding $finding, string $surface): CanonicalNavigationContext
{
$tenant = $finding->tenant;
$backLabel = $surface === CrossResourceNavigationMatrix::SURFACE_LIST_ROW ? 'Back to findings' : 'Back to finding';
$backUrl = $surface === CrossResourceNavigationMatrix::SURFACE_LIST_ROW
? FindingResource::getUrl('index', tenant: $tenant)
: FindingResource::getUrl('view', ['record' => $finding], tenant: $tenant);
return new CanonicalNavigationContext(
sourceSurface: 'finding.'.$surface,
canonicalRouteName: 'admin.operations.view',
tenantId: $tenant?->getKey(),
backLinkLabel: $backLabel,
backLinkUrl: $backUrl,
filterPayload: $tenant instanceof Tenant ? [
'tableFilters' => [
'tenant_id' => ['value' => (string) $tenant->getKey()],
],
] : [],
);
}
private function contextForPolicyVersion(PolicyVersion $version, string $surface): CanonicalNavigationContext
{
$tenant = $version->tenant;
$backLabel = $surface === CrossResourceNavigationMatrix::SURFACE_LIST_ROW ? 'Back to policy versions' : 'Back to policy version';
$backUrl = $surface === CrossResourceNavigationMatrix::SURFACE_LIST_ROW
? PolicyVersionResource::getUrl('index', tenant: $tenant)
: PolicyVersionResource::getUrl('view', ['record' => $version], tenant: $tenant);
return new CanonicalNavigationContext(
sourceSurface: 'policy_version.'.$surface,
canonicalRouteName: 'admin.operations.view',
tenantId: $tenant?->getKey(),
backLinkLabel: $backLabel,
backLinkUrl: $backUrl,
filterPayload: $tenant instanceof Tenant ? [
'tableFilters' => [
'tenant_id' => ['value' => (string) $tenant->getKey()],
],
] : [],
);
}
private function contextForBaselineSnapshot(BaselineSnapshot $snapshot, string $surface): CanonicalNavigationContext
{
$backLabel = $surface === CrossResourceNavigationMatrix::SURFACE_LIST_ROW ? 'Back to baseline snapshots' : 'Back to baseline snapshot';
$backUrl = $surface === CrossResourceNavigationMatrix::SURFACE_LIST_ROW
? BaselineSnapshotResource::getUrl(panel: 'admin')
: BaselineSnapshotResource::getUrl('view', ['record' => $snapshot], panel: 'admin');
return new CanonicalNavigationContext(
sourceSurface: 'baseline_snapshot.'.$surface,
canonicalRouteName: 'admin.operations.view',
backLinkLabel: $backLabel,
backLinkUrl: $backUrl,
);
}
private function contextForBackupSet(BackupSet $backupSet, string $surface): CanonicalNavigationContext
{
$tenant = $backupSet->tenant;
$backLabel = $surface === CrossResourceNavigationMatrix::SURFACE_LIST_ROW ? 'Back to backup sets' : 'Back to backup set';
$backUrl = $surface === CrossResourceNavigationMatrix::SURFACE_LIST_ROW
? BackupSetResource::getUrl('index', tenant: $tenant)
: BackupSetResource::getUrl('view', ['record' => $backupSet], tenant: $tenant);
return new CanonicalNavigationContext(
sourceSurface: 'backup_set.'.$surface,
canonicalRouteName: 'admin.operations.view',
tenantId: $tenant?->getKey(),
backLinkLabel: $backLabel,
backLinkUrl: $backUrl,
filterPayload: $tenant instanceof Tenant ? [
'tableFilters' => [
'tenant_id' => ['value' => (string) $tenant->getKey()],
],
] : [],
);
}
private function contextForOperationRun(OperationRun $run): CanonicalNavigationContext
{
$tenant = $run->tenant;
return new CanonicalNavigationContext(
sourceSurface: 'operation_run.detail_section',
canonicalRouteName: 'admin.operations.index',
tenantId: $tenant?->getKey(),
backLinkLabel: 'Back to operations',
backLinkUrl: OperationRunLinks::index($tenant),
filterPayload: $tenant instanceof Tenant ? [
'tableFilters' => [
'tenant_id' => ['value' => (string) $tenant->getKey()],
],
] : [],
);
}
private function activeTenantId(): ?int
{
$tenant = Filament::getTenant();
return $tenant instanceof Tenant ? (int) $tenant->getKey() : null;
}
}