$arguments * @param array $context */ public function mountAction(string $name, array $arguments = [], array $context = []): mixed { if (($context['table'] ?? false) === true && filled($context['recordKey'] ?? null) && in_array($name, ['triage', 'start_progress', 'assign', 'resolve', 'close', 'request_exception', 'reopen'], true)) { try { FindingResource::resolveScopedRecordOrFail($context['recordKey']); } catch (ModelNotFoundException) { abort(404); } } return parent::mountAction($name, $arguments, $context); } public function mount(): void { $this->syncCanonicalAdminTenantFilterState(); parent::mount(); $this->applyRequestedDashboardPrefilter(); } protected function getHeaderWidgets(): array { return [ BaselineCompareCoverageBanner::class, FindingStatsOverview::class, ]; } /** * @return array */ public function getTabs(): array { $stats = FindingResource::findingStatsForCurrentTenant(); return [ 'all' => Tab::make('All') ->icon('heroicon-m-list-bullet'), 'needs_action' => Tab::make('Needs action') ->icon('heroicon-m-exclamation-triangle') ->modifyQueryUsing(fn (Builder $query): Builder => $query ->whereIn('status', Finding::openStatusesForQuery())) ->badge($stats['open'] > 0 ? $stats['open'] : null) ->badgeColor('warning'), 'overdue' => Tab::make('Overdue') ->icon('heroicon-m-clock') ->modifyQueryUsing(fn (Builder $query): Builder => $query ->whereIn('status', Finding::openStatusesForQuery()) ->whereNotNull('due_at') ->where('due_at', '<', now())) ->badge($stats['overdue'] > 0 ? $stats['overdue'] : null) ->badgeColor('danger'), 'risk_accepted' => Tab::make('Risk accepted') ->icon('heroicon-m-shield-check') ->modifyQueryUsing(fn (Builder $query): Builder => $query ->where('status', Finding::STATUS_RISK_ACCEPTED)), 'resolved' => Tab::make('Resolved') ->icon('heroicon-m-archive-box') ->modifyQueryUsing(fn (Builder $query): Builder => $query ->whereIn('status', [Finding::STATUS_RESOLVED, Finding::STATUS_CLOSED])), ]; } protected function getHeaderActions(): array { $actions = []; if ((bool) config('tenantpilot.allow_admin_maintenance_actions', false)) { $actions[] = UiEnforcement::forAction( Actions\Action::make('backfill_lifecycle') ->label('Backfill findings lifecycle') ->icon('heroicon-o-wrench-screwdriver') ->color('gray') ->requiresConfirmation() ->modalHeading('Backfill findings lifecycle') ->modalDescription('This will backfill legacy Findings data (lifecycle fields, SLA due dates, and drift duplicate consolidation) for the current tenant. The operation runs in the background.') ->action(function (OperationRunService $operationRuns): void { $user = auth()->user(); if (! $user instanceof User) { abort(403); } $tenant = static::resolveTenantContextForCurrentPanel(); if (! $tenant instanceof Tenant) { abort(404); } $opRun = $operationRuns->ensureRunWithIdentity( tenant: $tenant, type: 'findings.lifecycle.backfill', identityInputs: [ 'tenant_id' => (int) $tenant->getKey(), 'trigger' => 'backfill', ], context: [ 'workspace_id' => (int) $tenant->workspace_id, 'initiator_user_id' => (int) $user->getKey(), ], initiator: $user, ); $runUrl = OperationRunLinks::view($opRun, $tenant); if ($opRun->wasRecentlyCreated === false) { OpsUxBrowserEvents::dispatchRunEnqueued($this); OperationUxPresenter::alreadyQueuedToast((string) $opRun->type) ->actions([ Actions\Action::make('view_run') ->label('Open operation') ->url($runUrl), ]) ->send(); return; } $operationRuns->dispatchOrFail($opRun, function () use ($tenant, $user): void { BackfillFindingLifecycleJob::dispatch( tenantId: (int) $tenant->getKey(), workspaceId: (int) $tenant->workspace_id, initiatorUserId: (int) $user->getKey(), ); }); OpsUxBrowserEvents::dispatchRunEnqueued($this); OperationUxPresenter::queuedToast((string) $opRun->type) ->body('The backfill will run in the background. You can continue working while it completes.') ->actions([ Actions\Action::make('view_run') ->label('Open operation') ->url($runUrl), ]) ->send(); }) ) ->preserveVisibility() ->requireCapability(Capabilities::TENANT_MANAGE) ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION) ->apply(); } $actions[] = UiEnforcement::forAction( Actions\Action::make('triage_all_matching') ->label('Triage all matching') ->icon('heroicon-o-check') ->color('gray') ->requiresConfirmation() ->visible(fn (): bool => $this->getStatusFilterValue() === Finding::STATUS_NEW) ->modalDescription(function (): string { $count = $this->getAllMatchingCount(); return "You are about to triage {$count} finding".($count === 1 ? '' : 's').' matching the current filters.'; }) ->form(function (): array { $count = $this->getAllMatchingCount(); if ($count <= 100) { return []; } return [ TextInput::make('confirmation') ->label('Type TRIAGE to confirm') ->required() ->in(['TRIAGE']) ->validationMessages([ 'in' => 'Please type TRIAGE to confirm.', ]), ]; }) ->action(function (FindingWorkflowService $workflow): void { $query = $this->buildAllMatchingQuery(); $count = (clone $query)->count(); if ($count === 0) { Notification::make() ->title('No matching findings') ->body('There are no new findings matching the current filters to triage.') ->warning() ->send(); return; } $user = auth()->user(); $tenant = static::resolveTenantContextForCurrentPanel(); if (! $user instanceof User) { abort(403); } if (! $tenant instanceof Tenant) { abort(404); } $triagedCount = 0; $skippedCount = 0; $failedCount = 0; $query->orderBy('id')->chunkById(200, function ($findings) use ($workflow, $tenant, $user, &$triagedCount, &$skippedCount, &$failedCount): void { foreach ($findings as $finding) { if (! $finding instanceof Finding) { $skippedCount++; continue; } if (! in_array((string) $finding->status, [ Finding::STATUS_NEW, Finding::STATUS_REOPENED, Finding::STATUS_ACKNOWLEDGED, ], true)) { $skippedCount++; continue; } try { $workflow->triage($finding, $tenant, $user); $triagedCount++; } catch (Throwable) { $failedCount++; } } }); $this->deselectAllTableRecords(); $this->resetPage(); $body = "Triaged {$triagedCount} finding".($triagedCount === 1 ? '' : 's').'.'; if ($skippedCount > 0) { $body .= " Skipped {$skippedCount}."; } if ($failedCount > 0) { $body .= " Failed {$failedCount}."; } Notification::make() ->title('Bulk triage completed') ->body($body) ->status($failedCount > 0 ? 'warning' : 'success') ->send(); }) ) ->preserveVisibility() ->requireCapability(Capabilities::TENANT_FINDINGS_TRIAGE) ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION) ->apply(); return $actions; } protected function buildAllMatchingQuery(): Builder { $query = FindingResource::getEloquentQuery(); $query->where('status', Finding::STATUS_NEW); $findingType = $this->getFindingTypeFilterValue(); if (is_string($findingType) && $findingType !== '') { $query->where('finding_type', $findingType); } if ($this->filterIsActive('overdue')) { $query->whereNotNull('due_at')->where('due_at', '<', now()); } if ($this->filterIsActive('high_severity')) { $query->whereIn('severity', [ Finding::SEVERITY_HIGH, Finding::SEVERITY_CRITICAL, ]); } if ($this->filterIsActive('my_assigned')) { $userId = auth()->id(); if (is_numeric($userId)) { $query->where('assignee_user_id', (int) $userId); } else { $query->whereRaw('1 = 0'); } } $scopeKeyState = $this->getTableFilterState('scope_key') ?? []; $scopeKey = Arr::get($scopeKeyState, 'scope_key'); if (is_string($scopeKey) && $scopeKey !== '') { $query->where('scope_key', $scopeKey); } $runIdsState = $this->getTableFilterState('run_ids') ?? []; $baselineRunId = Arr::get($runIdsState, 'baseline_operation_run_id'); if (is_numeric($baselineRunId)) { $query->where('baseline_operation_run_id', (int) $baselineRunId); } $currentRunId = Arr::get($runIdsState, 'current_operation_run_id'); if (is_numeric($currentRunId)) { $query->where('current_operation_run_id', (int) $currentRunId); } return $query; } private function syncCanonicalAdminTenantFilterState(): void { app(CanonicalAdminTenantFilterState::class)->sync( $this->getTableFiltersSessionKey(), tenantSensitiveFilters: ['scope_key', 'run_ids'], request: request(), tenantFilterName: null, ); } private function applyRequestedDashboardPrefilter(): void { $requestedTab = request()->query('tab'); $requestedStatus = request()->query('status'); $requestedFindingType = request()->query('finding_type'); $requestedGovernanceValidity = request()->query('governance_validity'); $requestedHighSeverity = request()->query('high_severity'); $hasDashboardPrefilter = $requestedTab !== null || $requestedStatus !== null || $requestedFindingType !== null || $requestedGovernanceValidity !== null || $requestedHighSeverity !== null; if (! $hasDashboardPrefilter) { return; } foreach (['status', 'finding_type', 'workflow_family', 'governance_validity'] as $filterName) { data_forget($this->tableFilters, $filterName); data_forget($this->tableDeferredFilters, $filterName); } foreach (['high_severity', 'overdue', 'my_assigned'] as $filterName) { data_forget($this->tableFilters, "{$filterName}.isActive"); data_forget($this->tableDeferredFilters, "{$filterName}.isActive"); } if (in_array($requestedTab, array_keys($this->getTabs()), true)) { $this->activeTab = (string) $requestedTab; } if (is_string($requestedStatus) && $requestedStatus !== '') { $this->tableFilters['status']['value'] = $requestedStatus; $this->tableDeferredFilters['status']['value'] = $requestedStatus; } if (is_string($requestedFindingType) && $requestedFindingType !== '') { $this->tableFilters['finding_type']['value'] = $requestedFindingType; $this->tableDeferredFilters['finding_type']['value'] = $requestedFindingType; } if (is_string($requestedGovernanceValidity) && $requestedGovernanceValidity !== '') { $this->tableFilters['governance_validity']['value'] = $requestedGovernanceValidity; $this->tableDeferredFilters['governance_validity']['value'] = $requestedGovernanceValidity; } $highSeverity = filter_var($requestedHighSeverity, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE); if ($highSeverity === true) { $this->tableFilters['high_severity']['isActive'] = true; $this->tableDeferredFilters['high_severity']['isActive'] = true; } } private function filterIsActive(string $filterName): bool { $state = $this->getTableFilterState($filterName); if ($state === true) { return true; } if (is_array($state)) { $isActive = Arr::get($state, 'isActive'); return $isActive === true; } return false; } protected function getAllMatchingCount(): int { return (int) $this->buildAllMatchingQuery()->count(); } protected function getStatusFilterValue(): string { $state = $this->getTableFilterState('status') ?? []; $value = Arr::get($state, 'value'); return is_string($value) && $value !== '' ? $value : Finding::STATUS_NEW; } protected function getFindingTypeFilterValue(): ?string { $state = $this->getTableFilterState('finding_type') ?? []; $value = Arr::get($state, 'value'); return is_string($value) && $value !== '' ? $value : null; } }