label('Open finding') ->icon('heroicon-o-arrow-top-right-on-square') ->color('gray') ->url(function (): ?string { $record = $this->getRecord(); if (! $record instanceof FindingException || ! $record->finding || ! $record->tenant) { return null; } return FindingResource::getUrl('view', ['record' => $record->finding], panel: 'tenant', tenant: $record->tenant); }), Action::make('renew_exception') ->label('Renew exception') ->icon('heroicon-o-arrow-path') ->color('warning') ->visible(fn (): bool => $this->canManageRecord() && $this->getRecord() instanceof FindingException && $this->getRecord()->canBeRenewed()) ->fillForm(fn (): array => [ 'owner_user_id' => $this->getRecord() instanceof FindingException ? $this->getRecord()->owner_user_id : null, ]) ->requiresConfirmation() ->form([ Select::make('owner_user_id') ->label('Owner') ->required() ->options(fn (): array => FindingExceptionResource::canViewAny() ? $this->tenantMemberOptions() : []) ->searchable(), Textarea::make('request_reason') ->label('Renewal reason') ->rows(4) ->required() ->maxLength(2000), DateTimePicker::make('review_due_at') ->label('Review due at') ->required() ->seconds(false), DateTimePicker::make('expires_at') ->label('Requested expiry') ->seconds(false), Repeater::make('evidence_references') ->label('Evidence references') ->schema([ TextInput::make('label') ->label('Label') ->required() ->maxLength(255), TextInput::make('source_type') ->label('Source type') ->required() ->maxLength(255), TextInput::make('source_id') ->label('Source ID') ->maxLength(255), TextInput::make('source_fingerprint') ->label('Fingerprint') ->maxLength(255), DateTimePicker::make('measured_at') ->label('Measured at') ->seconds(false), ]) ->defaultItems(0) ->collapsed(), ]) ->action(function (array $data, FindingExceptionService $service): void { $record = $this->getRecord(); $user = auth()->user(); if (! $record instanceof FindingException || ! $user instanceof User) { abort(404); } try { $service->renew($record, $user, $data); } catch (InvalidArgumentException $exception) { Notification::make() ->title('Renewal request failed') ->body($exception->getMessage()) ->danger() ->send(); return; } Notification::make() ->title('Renewal request submitted') ->success() ->send(); $this->refreshFormData(['status', 'current_validity_state', 'review_due_at']); }), Action::make('revoke_exception') ->label('Revoke exception') ->icon('heroicon-o-no-symbol') ->color('danger') ->visible(fn (): bool => $this->canManageRecord() && $this->getRecord() instanceof FindingException && $this->getRecord()->canBeRevoked()) ->requiresConfirmation() ->form([ Textarea::make('revocation_reason') ->label('Revocation reason') ->rows(4) ->required() ->maxLength(2000), ]) ->action(function (array $data, FindingExceptionService $service): void { $record = $this->getRecord(); $user = auth()->user(); if (! $record instanceof FindingException || ! $user instanceof User) { abort(404); } try { $service->revoke($record, $user, $data); } catch (InvalidArgumentException $exception) { Notification::make() ->title('Exception revocation failed') ->body($exception->getMessage()) ->danger() ->send(); return; } Notification::make() ->title('Exception revoked') ->success() ->send(); $this->refreshFormData(['status', 'current_validity_state', 'revocation_reason', 'revoked_at']); }), ]; } /** * @return array */ private function tenantMemberOptions(): array { $record = $this->getRecord(); if (! $record instanceof FindingException) { return []; } $tenant = $record->tenant; if (! $tenant instanceof Tenant) { return []; } return \App\Models\TenantMembership::query() ->where('tenant_id', (int) $tenant->getKey()) ->join('users', 'users.id', '=', 'tenant_memberships.user_id') ->orderBy('users.name') ->pluck('users.name', 'users.id') ->mapWithKeys(fn (string $name, int|string $id): array => [(int) $id => $name]) ->all(); } private function canManageRecord(): bool { $record = $this->getRecord(); $user = auth()->user(); return $record instanceof FindingException && $record->tenant instanceof Tenant && $user instanceof User && $user->canAccessTenant($record->tenant) && $user->can(Capabilities::FINDING_EXCEPTION_MANAGE, $record->tenant); } }