Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m12s
Added BaselineSubjectResolution page and supporting logic to visualize missing identities, ambiguous matches, and skipped coverages per Spec 384.
427 lines
16 KiB
PHP
427 lines
16 KiB
PHP
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
use App\Filament\Pages\InventoryCoverage as InventoryCoveragePage;
|
|
use App\Filament\Resources\BackupScheduleResource;
|
|
use App\Filament\Resources\BackupSetResource;
|
|
use App\Filament\Resources\BaselineSnapshotResource;
|
|
use App\Filament\Resources\EntraGroupResource;
|
|
use App\Filament\Resources\EnvironmentReviewResource;
|
|
use App\Filament\Resources\EvidenceSnapshotResource;
|
|
use App\Filament\Resources\InventoryItemResource;
|
|
use App\Filament\Resources\PolicyResource;
|
|
use App\Filament\Resources\RestoreRunResource;
|
|
use App\Filament\Resources\ReviewPackResource;
|
|
use App\Models\EnvironmentReview;
|
|
use App\Models\EvidenceSnapshot;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\OperationRun;
|
|
use App\Models\RestoreRun;
|
|
use App\Models\ReviewPack;
|
|
use App\Models\Workspace;
|
|
use App\Services\Baselines\BaselineSubjectResolutionQuery;
|
|
use App\Support\Navigation\CanonicalNavigationContext;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
|
|
final class OperationRunLinks
|
|
{
|
|
public static function collectionLabel(): string
|
|
{
|
|
return 'Operations';
|
|
}
|
|
|
|
public static function openCollectionLabel(): string
|
|
{
|
|
return 'Open operations';
|
|
}
|
|
|
|
public static function collectionScopeDescription(): string
|
|
{
|
|
return 'Broader admin view across recent and historical operations.';
|
|
}
|
|
|
|
public static function viewInCollectionLabel(): string
|
|
{
|
|
return 'View in Operations';
|
|
}
|
|
|
|
public static function singularLabel(): string
|
|
{
|
|
return 'Operation';
|
|
}
|
|
|
|
public static function openLabel(): string
|
|
{
|
|
return 'Open operation';
|
|
}
|
|
|
|
public static function advancedMonitoringLabel(): string
|
|
{
|
|
return 'Open operation in Monitoring (advanced)';
|
|
}
|
|
|
|
public static function advancedMonitoringDescription(): string
|
|
{
|
|
return 'Diagnostics-only link to the canonical admin operation viewer.';
|
|
}
|
|
|
|
public static function identifierLabel(): string
|
|
{
|
|
return 'Operation ID';
|
|
}
|
|
|
|
public static function identifier(OperationRun|int $run): string
|
|
{
|
|
$runId = $run instanceof OperationRun ? (int) $run->getKey() : (int) $run;
|
|
|
|
return 'Operation #'.$runId;
|
|
}
|
|
|
|
public static function index(
|
|
?ManagedEnvironment $tenant = null,
|
|
?CanonicalNavigationContext $context = null,
|
|
?string $activeTab = null,
|
|
bool $allTenants = false,
|
|
?string $problemClass = null,
|
|
?string $operationType = null,
|
|
?Workspace $workspace = null,
|
|
): string {
|
|
$workspace = $tenant instanceof ManagedEnvironment
|
|
? self::resolveWorkspace($tenant)
|
|
: ($workspace ?? self::resolveWorkspace());
|
|
|
|
if (! $workspace instanceof Workspace) {
|
|
return url('/admin');
|
|
}
|
|
|
|
$parameters = $context?->toQuery() ?? [];
|
|
|
|
$parameters['workspace'] = $workspace;
|
|
|
|
if ($tenant instanceof ManagedEnvironment) {
|
|
$parameters['environment_id'] = (int) $tenant->getKey();
|
|
}
|
|
|
|
if (is_string($activeTab) && $activeTab !== '') {
|
|
$parameters['activeTab'] = $activeTab;
|
|
}
|
|
|
|
if (
|
|
is_string($problemClass)
|
|
&& in_array($problemClass, OperationRun::problemClasses(), true)
|
|
&& $problemClass !== OperationRun::PROBLEM_CLASS_NONE
|
|
) {
|
|
$parameters['problemClass'] = $problemClass;
|
|
|
|
if (! is_string($activeTab) || $activeTab === '') {
|
|
$parameters['activeTab'] = $problemClass;
|
|
}
|
|
}
|
|
|
|
if (is_string($operationType) && trim($operationType) !== '') {
|
|
$parameters['operation_type'] = OperationCatalog::canonicalCode($operationType);
|
|
}
|
|
|
|
return route('admin.operations.index', $parameters);
|
|
}
|
|
|
|
public static function tenantlessView(OperationRun|int $run, ?CanonicalNavigationContext $context = null): string
|
|
{
|
|
$runId = $run instanceof OperationRun ? (int) $run->getKey() : (int) $run;
|
|
|
|
$workspace = self::resolveWorkspace($run);
|
|
|
|
if (! $workspace instanceof Workspace) {
|
|
return url('/admin');
|
|
}
|
|
|
|
return route('admin.operations.view', array_merge(
|
|
['workspace' => $workspace, 'run' => $runId],
|
|
$context?->toQuery() ?? [],
|
|
));
|
|
}
|
|
|
|
public static function viewFromOperationsIndex(OperationRun|int $run, string $backUrl): string
|
|
{
|
|
return self::tenantlessView(
|
|
$run,
|
|
self::operationsIndexNavigationContext($run, $backUrl),
|
|
);
|
|
}
|
|
|
|
public static function operationsIndexNavigationContext(OperationRun|int $run, string $backUrl): CanonicalNavigationContext
|
|
{
|
|
return new CanonicalNavigationContext(
|
|
sourceSurface: 'operations.index',
|
|
canonicalRouteName: 'admin.operations.index',
|
|
tenantId: $run instanceof OperationRun && is_numeric($run->managed_environment_id)
|
|
? (int) $run->managed_environment_id
|
|
: null,
|
|
backLinkLabel: 'Back to Operations',
|
|
backLinkUrl: $backUrl,
|
|
);
|
|
}
|
|
|
|
public static function withNavigationContext(string $url, ?CanonicalNavigationContext $context): string
|
|
{
|
|
if (! $context instanceof CanonicalNavigationContext) {
|
|
return $url;
|
|
}
|
|
|
|
if (! self::canCarryNavigationContext($url) || self::hasNavigationContext($url)) {
|
|
return $url;
|
|
}
|
|
|
|
return url()->query($url, $context->toQuery());
|
|
}
|
|
|
|
public static function view(OperationRun|int $run, ManagedEnvironment $tenant, ?CanonicalNavigationContext $context = null): string
|
|
{
|
|
return self::tenantlessView($run, $context);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public static function related(OperationRun $run, ?ManagedEnvironment $tenant): array
|
|
{
|
|
$context = is_array($run->context) ? $run->context : [];
|
|
|
|
$links = [];
|
|
|
|
$links[self::collectionLabel()] = self::index($tenant);
|
|
|
|
if (! $tenant instanceof ManagedEnvironment) {
|
|
return $links;
|
|
}
|
|
|
|
$providerConnectionId = $context['provider_connection_id'] ?? null;
|
|
$canonicalType = $run->canonicalOperationType();
|
|
|
|
if (is_numeric($providerConnectionId)) {
|
|
$links['Provider Connections'] = ManagedEnvironmentLinks::providerConnectionsUrl($tenant);
|
|
$links['Provider Connection'] = ManagedEnvironmentLinks::providerConnectionUrl((int) $providerConnectionId, 'edit', $tenant);
|
|
}
|
|
|
|
if ($canonicalType === 'inventory.sync') {
|
|
$links['Inventory'] = InventoryItemResource::getUrl('index', tenant: $tenant);
|
|
|
|
if ($run->inventoryCoverage() instanceof \App\Support\Inventory\InventoryCoverage) {
|
|
$links['Inventory Coverage'] = InventoryCoveragePage::getUrl(tenant: $tenant);
|
|
}
|
|
}
|
|
|
|
if ($canonicalType === 'policy.sync') {
|
|
$links['Policies'] = PolicyResource::getUrl('index', tenant: $tenant);
|
|
|
|
$policyId = $context['policy_id'] ?? null;
|
|
if (is_numeric($policyId)) {
|
|
$links['Policy'] = PolicyResource::getUrl('view', ['record' => (int) $policyId], tenant: $tenant);
|
|
}
|
|
}
|
|
|
|
if ($canonicalType === 'directory.groups.sync') {
|
|
$links['Directory Groups'] = EntraGroupResource::scopedUrl('index', tenant: $tenant);
|
|
}
|
|
|
|
if ($canonicalType === 'baseline.compare') {
|
|
$links['Drift'] = ManagedEnvironmentLinks::baselineCompareUrl($tenant);
|
|
|
|
$summary = app(BaselineSubjectResolutionQuery::class)->summary($tenant, (int) $run->getKey());
|
|
|
|
if ((int) ($summary['actionable_count'] ?? 0) > 0) {
|
|
$links['Baseline Subject Resolution'] = ManagedEnvironmentLinks::baselineSubjectResolutionUrl($tenant, [
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
if ($canonicalType === 'baseline.capture') {
|
|
$snapshotId = $run->reconciledRelatedBaselineSnapshotId()
|
|
?? (is_numeric(data_get($context, 'baseline_snapshot_id'))
|
|
? (int) data_get($context, 'baseline_snapshot_id')
|
|
: (is_numeric(data_get($context, 'result.snapshot_id'))
|
|
? (int) data_get($context, 'result.snapshot_id')
|
|
: null));
|
|
|
|
if (is_numeric($snapshotId)) {
|
|
$links['Baseline Snapshot'] = BaselineSnapshotResource::getUrl('view', ['record' => (int) $snapshotId], panel: 'admin');
|
|
}
|
|
}
|
|
|
|
if ($canonicalType === 'backup_set.update') {
|
|
$links['Backup Sets'] = BackupSetResource::getUrl('index', tenant: $tenant);
|
|
|
|
$backupSetId = $context['backup_set_id'] ?? null;
|
|
if (is_numeric($backupSetId)) {
|
|
$links['Backup Set'] = BackupSetResource::getUrl('view', ['record' => (int) $backupSetId], tenant: $tenant);
|
|
}
|
|
}
|
|
|
|
if (in_array($canonicalType, ['backup.schedule.execute', 'backup.schedule.retention', 'backup.schedule.purge'], true)) {
|
|
$links['Backup Schedules'] = BackupScheduleResource::getUrl('index', tenant: $tenant);
|
|
|
|
$backupSetId = $run->reconciledRelatedBackupSetId()
|
|
?? (is_numeric($context['backup_set_id'] ?? null) ? (int) $context['backup_set_id'] : null);
|
|
|
|
if ($backupSetId !== null) {
|
|
$links['Backup Sets'] = BackupSetResource::getUrl('index', tenant: $tenant);
|
|
$links['Backup Set'] = BackupSetResource::getUrl('view', ['record' => $backupSetId], tenant: $tenant);
|
|
}
|
|
}
|
|
|
|
if ($canonicalType === 'restore.execute') {
|
|
$links['Restore Runs'] = RestoreRunResource::getUrl('index', tenant: $tenant);
|
|
|
|
$restoreRunId = $context['restore_run_id'] ?? null;
|
|
if (is_numeric($restoreRunId)) {
|
|
$restoreRun = RestoreRun::query()
|
|
->whereKey((int) $restoreRunId)
|
|
->where('workspace_id', (int) $run->workspace_id)
|
|
->where('managed_environment_id', (int) $run->managed_environment_id)
|
|
->first();
|
|
|
|
if (
|
|
$restoreRun instanceof RestoreRun
|
|
&& (
|
|
! is_numeric($restoreRun->operation_run_id)
|
|
|| (int) $restoreRun->operation_run_id === (int) $run->getKey()
|
|
)
|
|
) {
|
|
$links['Restore Run'] = RestoreRunResource::getUrl('view', ['record' => $restoreRun], tenant: $tenant);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($canonicalType === 'tenant.evidence.snapshot.generate') {
|
|
$snapshot = null;
|
|
$relatedSnapshotId = $run->reconciledRelatedEvidenceSnapshotId();
|
|
|
|
if ($relatedSnapshotId !== null) {
|
|
$snapshot = EvidenceSnapshot::query()
|
|
->whereKey($relatedSnapshotId)
|
|
->where('workspace_id', (int) $run->workspace_id)
|
|
->where('managed_environment_id', (int) $run->managed_environment_id)
|
|
->first();
|
|
}
|
|
|
|
if (! $snapshot instanceof EvidenceSnapshot) {
|
|
$snapshot = EvidenceSnapshot::query()
|
|
->where('workspace_id', (int) $run->workspace_id)
|
|
->where('managed_environment_id', (int) $run->managed_environment_id)
|
|
->where('operation_run_id', (int) $run->getKey())
|
|
->latest('id')
|
|
->first();
|
|
}
|
|
|
|
if ($snapshot instanceof EvidenceSnapshot) {
|
|
$links['Evidence Snapshot'] = EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant);
|
|
}
|
|
}
|
|
|
|
if ($canonicalType === 'environment.review.compose') {
|
|
$review = null;
|
|
$relatedReviewId = $run->reconciledRelatedReviewId();
|
|
|
|
if ($relatedReviewId !== null) {
|
|
$review = EnvironmentReview::query()
|
|
->whereKey($relatedReviewId)
|
|
->where('workspace_id', (int) $run->workspace_id)
|
|
->where('managed_environment_id', (int) $run->managed_environment_id)
|
|
->first();
|
|
}
|
|
|
|
if (! $review instanceof EnvironmentReview) {
|
|
$review = EnvironmentReview::query()
|
|
->where('workspace_id', (int) $run->workspace_id)
|
|
->where('managed_environment_id', (int) $run->managed_environment_id)
|
|
->where('operation_run_id', (int) $run->getKey())
|
|
->latest('id')
|
|
->first();
|
|
}
|
|
|
|
if ($review instanceof EnvironmentReview) {
|
|
$links['ManagedEnvironment Review'] = EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review], $tenant);
|
|
}
|
|
}
|
|
|
|
if ($canonicalType === 'environment.review_pack.generate') {
|
|
$pack = null;
|
|
$relatedReviewPackId = $run->reconciledRelatedReviewPackId();
|
|
|
|
if ($relatedReviewPackId !== null) {
|
|
$pack = ReviewPack::query()
|
|
->whereKey($relatedReviewPackId)
|
|
->where('workspace_id', (int) $run->workspace_id)
|
|
->where('managed_environment_id', (int) $run->managed_environment_id)
|
|
->first();
|
|
}
|
|
|
|
if (! $pack instanceof ReviewPack) {
|
|
$pack = ReviewPack::query()
|
|
->where('workspace_id', (int) $run->workspace_id)
|
|
->where('managed_environment_id', (int) $run->managed_environment_id)
|
|
->where('operation_run_id', (int) $run->getKey())
|
|
->latest('id')
|
|
->first();
|
|
}
|
|
|
|
if ($pack instanceof ReviewPack) {
|
|
$links['Review Pack'] = ReviewPackResource::getUrl('view', ['record' => $pack], tenant: $tenant);
|
|
}
|
|
}
|
|
|
|
return array_filter($links, static fn (?string $url): bool => is_string($url) && $url !== '');
|
|
}
|
|
|
|
private static function resolveWorkspace(ManagedEnvironment|OperationRun|int|null $subject = null): ?Workspace
|
|
{
|
|
if ($subject instanceof ManagedEnvironment) {
|
|
return $subject->workspace()->first();
|
|
}
|
|
|
|
if ($subject instanceof OperationRun) {
|
|
return Workspace::query()->whereKey((int) $subject->workspace_id)->first();
|
|
}
|
|
|
|
if (is_int($subject) && $subject > 0) {
|
|
$run = OperationRun::query()
|
|
->select(['id', 'workspace_id'])
|
|
->whereKey($subject)
|
|
->first();
|
|
|
|
if ($run instanceof OperationRun) {
|
|
return Workspace::query()->whereKey((int) $run->workspace_id)->first();
|
|
}
|
|
}
|
|
|
|
return app(WorkspaceContext::class)->currentWorkspace(request());
|
|
}
|
|
|
|
private static function hasNavigationContext(string $url): bool
|
|
{
|
|
$query = parse_url($url, PHP_URL_QUERY);
|
|
|
|
return is_string($query)
|
|
&& (str_contains($query, 'nav%5B') || str_contains($query, 'nav['));
|
|
}
|
|
|
|
private static function canCarryNavigationContext(string $url): bool
|
|
{
|
|
$path = parse_url($url, PHP_URL_PATH);
|
|
|
|
if (! is_string($path) || ! str_starts_with($path, '/admin')) {
|
|
return false;
|
|
}
|
|
|
|
$host = parse_url($url, PHP_URL_HOST);
|
|
|
|
if ($host === null || $host === false) {
|
|
return true;
|
|
}
|
|
|
|
return $host === parse_url(url('/'), PHP_URL_HOST);
|
|
}
|
|
}
|