## Summary - add a first-class finding exception domain with request, approval, rejection, renewal, and revocation lifecycle support - add tenant-scoped exception register, finding governance surfaces, and a canonical workspace approval queue in Filament - add audit, badge, evidence, and review-pack integrations plus focused Pest coverage for workflow, authorization, and governance validity ## Validation - vendor/bin/sail bin pint --dirty --format agent - CI=1 vendor/bin/sail artisan test --compact - manual integrated-browser smoke test for the request-exception happy path, tenant register visibility, and canonical queue visibility ## Notes - Filament implementation remains on v5 with Livewire v4-compatible surfaces - canonical queue lives in the admin panel; provider registration stays in bootstrap/providers.php - finding exceptions stay out of global search in this rollout Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #184
129 lines
6.8 KiB
PHP
129 lines
6.8 KiB
PHP
<x-filament-panels::page>
|
|
<x-filament::section>
|
|
<div class="flex flex-col gap-3">
|
|
<div class="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
|
Canonical risk-acceptance approvals
|
|
</div>
|
|
|
|
<div class="text-sm text-gray-600 dark:text-gray-300">
|
|
Review pending exception requests across entitled tenants without leaving the Monitoring area.
|
|
</div>
|
|
</div>
|
|
</x-filament::section>
|
|
|
|
{{ $this->table }}
|
|
|
|
@php
|
|
$selectedException = $this->selectedFindingException();
|
|
@endphp
|
|
|
|
@if ($selectedException)
|
|
<x-filament::section
|
|
:heading="'Finding exception #'.$selectedException->getKey()"
|
|
:description="$selectedException->requested_at?->toDayDateTimeString()"
|
|
>
|
|
<div class="grid gap-4 lg:grid-cols-3">
|
|
<div class="rounded-2xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900">
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
|
Status
|
|
</div>
|
|
<div class="mt-2 text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
{{ \App\Support\Badges\BadgeRenderer::label(\App\Support\Badges\BadgeDomain::FindingExceptionStatus)($selectedException->status) }}
|
|
</div>
|
|
<div class="mt-1 text-sm text-gray-600 dark:text-gray-300">
|
|
{{ \App\Support\Badges\BadgeRenderer::label(\App\Support\Badges\BadgeDomain::FindingRiskGovernanceValidity)($selectedException->current_validity_state) }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="rounded-2xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900">
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
|
Scope
|
|
</div>
|
|
<div class="mt-2 text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
{{ $selectedException->tenant?->name ?? 'Unknown tenant' }}
|
|
</div>
|
|
<div class="mt-1 text-sm text-gray-600 dark:text-gray-300">
|
|
Finding #{{ $selectedException->finding_id }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="rounded-2xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900">
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
|
Review timing
|
|
</div>
|
|
<div class="mt-2 text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
Review due {{ $selectedException->review_due_at?->toDayDateTimeString() ?? '—' }}
|
|
</div>
|
|
<div class="mt-1 text-sm text-gray-600 dark:text-gray-300">
|
|
Expires {{ $selectedException->expires_at?->toDayDateTimeString() ?? '—' }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6 grid gap-4 lg:grid-cols-2">
|
|
<div class="rounded-2xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900">
|
|
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
|
Request
|
|
</div>
|
|
<dl class="mt-3 space-y-3">
|
|
<div>
|
|
<dt class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
|
Requested by
|
|
</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
|
{{ $selectedException->requester?->name ?? 'Unknown requester' }}
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
|
Owner
|
|
</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
|
{{ $selectedException->owner?->name ?? 'Unassigned' }}
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
|
Reason
|
|
</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
|
{{ $selectedException->request_reason }}
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
|
|
<div class="rounded-2xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900">
|
|
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
|
Decision history
|
|
</div>
|
|
|
|
@if ($selectedException->decisions->isEmpty())
|
|
<div class="mt-3 text-sm text-gray-600 dark:text-gray-300">
|
|
No decisions have been recorded yet.
|
|
</div>
|
|
@else
|
|
<div class="mt-3 space-y-3">
|
|
@foreach ($selectedException->decisions as $decision)
|
|
<div class="rounded-xl border border-gray-200 px-3 py-3 dark:border-gray-800">
|
|
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
{{ ucfirst(str_replace('_', ' ', $decision->decision_type)) }}
|
|
</div>
|
|
<div class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
{{ $decision->actor?->name ?? 'Unknown actor' }} · {{ $decision->decided_at?->toDayDateTimeString() ?? '—' }}
|
|
</div>
|
|
@if (filled($decision->reason))
|
|
<div class="mt-2 text-sm text-gray-700 dark:text-gray-300">
|
|
{{ $decision->reason }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</x-filament::section>
|
|
@endif
|
|
</x-filament-panels::page>
|