|null */ public ?array $statusCounts = null; public static function canAccess(): bool { return FindingResource::canAccess(); } public function mount(): void { $tenant = Tenant::current(); $user = auth()->user(); if (! $user instanceof User) { abort(403, 'Not allowed'); } $latestSuccessful = InventorySyncRun::query() ->where('tenant_id', $tenant->getKey()) ->where('status', InventorySyncRun::STATUS_SUCCESS) ->whereNotNull('finished_at') ->orderByDesc('finished_at') ->first(); if (! $latestSuccessful instanceof InventorySyncRun) { $this->state = 'blocked'; $this->message = 'No successful inventory runs found yet.'; return; } $scopeKey = (string) $latestSuccessful->selection_hash; $this->scopeKey = $scopeKey; $selector = app(DriftRunSelector::class); $comparison = $selector->selectBaselineAndCurrent($tenant, $scopeKey); if ($comparison === null) { $this->state = 'blocked'; $this->message = 'Need at least 2 successful runs for this scope to calculate drift.'; return; } $baseline = $comparison['baseline']; $current = $comparison['current']; $this->baselineRunId = (int) $baseline->getKey(); $this->currentRunId = (int) $current->getKey(); $this->baselineFinishedAt = $baseline->finished_at?->toDateTimeString(); $this->currentFinishedAt = $current->finished_at?->toDateTimeString(); $existingOperationRun = OperationRun::query() ->where('tenant_id', $tenant->getKey()) ->where('type', 'drift.generate') ->where('context->scope_key', $scopeKey) ->where('context->baseline_run_id', (int) $baseline->getKey()) ->where('context->current_run_id', (int) $current->getKey()) ->latest('id') ->first(); if ($existingOperationRun instanceof OperationRun) { $this->operationRunId = (int) $existingOperationRun->getKey(); } $exists = Finding::query() ->where('tenant_id', $tenant->getKey()) ->where('finding_type', Finding::FINDING_TYPE_DRIFT) ->where('scope_key', $scopeKey) ->where('baseline_run_id', $baseline->getKey()) ->where('current_run_id', $current->getKey()) ->exists(); if ($exists) { $this->state = 'ready'; $newCount = (int) Finding::query() ->where('tenant_id', $tenant->getKey()) ->where('finding_type', Finding::FINDING_TYPE_DRIFT) ->where('scope_key', $scopeKey) ->where('baseline_run_id', $baseline->getKey()) ->where('current_run_id', $current->getKey()) ->where('status', Finding::STATUS_NEW) ->count(); $this->statusCounts = [Finding::STATUS_NEW => $newCount]; return; } $existingOperationRun?->refresh(); if ($existingOperationRun instanceof OperationRun && in_array($existingOperationRun->status, ['queued', 'running'], true) ) { $this->state = 'generating'; $this->operationRunId = (int) $existingOperationRun->getKey(); return; } if ($existingOperationRun instanceof OperationRun && $existingOperationRun->status === 'completed' ) { $counts = is_array($existingOperationRun->summary_counts ?? null) ? $existingOperationRun->summary_counts : []; $created = (int) ($counts['created'] ?? 0); if ($existingOperationRun->outcome === 'failed') { $this->state = 'error'; $this->message = 'Drift generation failed for this comparison. See the run for details.'; $this->operationRunId = (int) $existingOperationRun->getKey(); return; } if ($created === 0) { $this->state = 'ready'; $this->statusCounts = [Finding::STATUS_NEW => 0]; $this->message = 'No drift findings for this comparison. If you changed settings after the current run, run Inventory Sync again to capture a newer snapshot.'; $this->operationRunId = (int) $existingOperationRun->getKey(); return; } } if (! $user->canSyncTenant($tenant)) { $this->state = 'blocked'; $this->message = 'You can view existing drift findings and run history, but you do not have permission to generate drift.'; return; } /** @var BulkSelectionIdentity $selection */ $selection = app(BulkSelectionIdentity::class); $selectionIdentity = $selection->fromQuery([ 'scope_key' => $scopeKey, 'baseline_run_id' => (int) $baseline->getKey(), 'current_run_id' => (int) $current->getKey(), ]); /** @var OperationRunService $opService */ $opService = app(OperationRunService::class); $opRun = $opService->enqueueBulkOperation( tenant: $tenant, type: 'drift.generate', targetScope: [ 'entra_tenant_id' => (string) ($tenant->tenant_id ?? $tenant->external_id), ], selectionIdentity: $selectionIdentity, dispatcher: function ($operationRun) use ($tenant, $user, $baseline, $current, $scopeKey): void { GenerateDriftFindingsJob::dispatch( tenantId: (int) $tenant->getKey(), userId: (int) $user->getKey(), baselineRunId: (int) $baseline->getKey(), currentRunId: (int) $current->getKey(), scopeKey: $scopeKey, operationRun: $operationRun, ); }, initiator: $user, extraContext: [ 'scope_key' => $scopeKey, 'baseline_run_id' => (int) $baseline->getKey(), 'current_run_id' => (int) $current->getKey(), ], emitQueuedNotification: false, ); $this->operationRunId = (int) $opRun->getKey(); $this->state = 'generating'; if (! $opRun->wasRecentlyCreated) { Notification::make() ->title('Drift generation already active') ->body('This operation is already queued or running.') ->warning() ->actions([ Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($opRun, $tenant)), ]) ->send(); return; } OpsUxBrowserEvents::dispatchRunEnqueued($this); OperationUxPresenter::queuedToast((string) $opRun->type) ->actions([ Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($opRun, $tenant)), ]) ->send(); } public function getFindingsUrl(): string { return FindingResource::getUrl('index', tenant: Tenant::current()); } public function getBaselineRunUrl(): ?string { if (! is_int($this->baselineRunId)) { return null; } return InventorySyncRunResource::getUrl('view', ['record' => $this->baselineRunId], tenant: Tenant::current()); } public function getCurrentRunUrl(): ?string { if (! is_int($this->currentRunId)) { return null; } return InventorySyncRunResource::getUrl('view', ['record' => $this->currentRunId], tenant: Tenant::current()); } public function getOperationRunUrl(): ?string { if (! is_int($this->operationRunId)) { return null; } return OperationRunLinks::view($this->operationRunId, Tenant::current()); } }