TenantAtlas/app/Filament/Resources/FindingResource/Pages/ListFindings.php

154 lines
5.4 KiB
PHP

<?php
namespace App\Filament\Resources\FindingResource\Pages;
use App\Filament\Resources\FindingResource;
use App\Models\Finding;
use App\Support\Auth\Capabilities;
use App\Support\Rbac\UiEnforcement;
use App\Support\Rbac\UiTooltips;
use Filament\Actions;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\ListRecords;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
class ListFindings extends ListRecords
{
protected static string $resource = FindingResource::class;
protected function getHeaderActions(): array
{
return [
UiEnforcement::forAction(
Actions\Action::make('acknowledge_all_matching')
->label('Acknowledge 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 acknowledge {$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 ACKNOWLEDGE to confirm')
->required()
->in(['ACKNOWLEDGE'])
->validationMessages([
'in' => 'Please type ACKNOWLEDGE to confirm.',
]),
];
})
->action(function (array $data): 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.')
->warning()
->send();
return;
}
$updated = $query->update([
'status' => Finding::STATUS_ACKNOWLEDGED,
'acknowledged_at' => now(),
'acknowledged_by_user_id' => auth()->id(),
]);
$this->deselectAllTableRecords();
$this->resetPage();
Notification::make()
->title('Bulk acknowledge completed')
->body("Acknowledged {$updated} finding".($updated === 1 ? '' : 's').'.')
->success()
->send();
})
)
->preserveVisibility()
->requireCapability(Capabilities::TENANT_FINDINGS_ACKNOWLEDGE)
->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
->apply(),
];
}
protected function buildAllMatchingQuery(): Builder
{
$query = Finding::query();
$tenantId = \Filament\Facades\Filament::getTenant()?->getKey();
if (! is_numeric($tenantId)) {
return $query->whereRaw('1 = 0');
}
$query->where('tenant_id', (int) $tenantId);
$query->where('status', Finding::STATUS_NEW);
$findingType = $this->getFindingTypeFilterValue();
if (is_string($findingType) && $findingType !== '') {
$query->where('finding_type', $findingType);
}
$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_run_id');
if (is_numeric($baselineRunId)) {
$query->where('baseline_run_id', (int) $baselineRunId);
}
$currentRunId = Arr::get($runIdsState, 'current_run_id');
if (is_numeric($currentRunId)) {
$query->where('current_run_id', (int) $currentRunId);
}
return $query;
}
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
: Finding::FINDING_TYPE_DRIFT;
}
}