Implements the 074 verification checklist framework. Highlights: - Versioned verification report contract stored in operation_runs.context.verification_report (DB-only viewer). - Strict sanitizer/redaction (evidence pointers only; no tokens/headers/payloads) + schema validation. - Centralized BADGE-001 semantics for check status, severity, and overall report outcome. - Deterministic start (dedupe while active) via shared StartVerification service; capability-first authorization (non-member 404, member missing capability 403). - Completion audit event (verification.completed) with redacted metadata. - Integrations: OperationRun detail viewer, onboarding wizard verification step, provider connection start surfaces. Tests: - vendor/bin/sail artisan test --compact tests/Feature/Verification tests/Unit/Badges/VerificationBadgesTest.php - vendor/bin/sail bin pint --dirty Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box> Reviewed-on: #89
179 lines
9.2 KiB
PHP
179 lines
9.2 KiB
PHP
@php
|
||
$report = isset($getState) ? $getState() : ($report ?? null);
|
||
$report = is_array($report) ? $report : null;
|
||
|
||
$summary = $report['summary'] ?? null;
|
||
$summary = is_array($summary) ? $summary : null;
|
||
|
||
$counts = $summary['counts'] ?? null;
|
||
$counts = is_array($counts) ? $counts : [];
|
||
|
||
$checks = $report['checks'] ?? null;
|
||
$checks = is_array($checks) ? $checks : [];
|
||
@endphp
|
||
|
||
<div class="space-y-4">
|
||
@if ($report === null || $summary === null)
|
||
<div class="rounded-lg border border-gray-200 bg-white p-4 text-sm text-gray-600 shadow-sm dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300">
|
||
<div class="font-medium text-gray-900 dark:text-white">
|
||
Verification report unavailable
|
||
</div>
|
||
<div class="mt-1">
|
||
This run doesn’t have a report yet. If it’s still running, refresh in a moment. If it already completed, start verification again.
|
||
</div>
|
||
</div>
|
||
@else
|
||
@php
|
||
$overallSpec = \App\Support\Badges\BadgeRenderer::spec(
|
||
\App\Support\Badges\BadgeDomain::VerificationReportOverall,
|
||
$summary['overall'] ?? null,
|
||
);
|
||
@endphp
|
||
|
||
<div class="flex flex-wrap items-center gap-2">
|
||
<x-filament::badge :color="$overallSpec->color" :icon="$overallSpec->icon">
|
||
{{ $overallSpec->label }}
|
||
</x-filament::badge>
|
||
|
||
<x-filament::badge color="gray">
|
||
{{ (int) ($counts['total'] ?? 0) }} total
|
||
</x-filament::badge>
|
||
<x-filament::badge color="success">
|
||
{{ (int) ($counts['pass'] ?? 0) }} pass
|
||
</x-filament::badge>
|
||
<x-filament::badge color="danger">
|
||
{{ (int) ($counts['fail'] ?? 0) }} fail
|
||
</x-filament::badge>
|
||
<x-filament::badge color="warning">
|
||
{{ (int) ($counts['warn'] ?? 0) }} warn
|
||
</x-filament::badge>
|
||
<x-filament::badge color="gray">
|
||
{{ (int) ($counts['skip'] ?? 0) }} skip
|
||
</x-filament::badge>
|
||
<x-filament::badge color="info">
|
||
{{ (int) ($counts['running'] ?? 0) }} running
|
||
</x-filament::badge>
|
||
</div>
|
||
|
||
@if ($checks === [])
|
||
<div class="rounded-lg border border-gray-200 bg-white p-4 text-sm text-gray-600 shadow-sm dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300">
|
||
No checks found in this report. Start verification again to generate a fresh report.
|
||
</div>
|
||
@else
|
||
<div class="space-y-3">
|
||
@foreach ($checks as $check)
|
||
@php
|
||
$check = is_array($check) ? $check : [];
|
||
|
||
$title = $check['title'] ?? 'Check';
|
||
$title = is_string($title) && trim($title) !== '' ? $title : 'Check';
|
||
|
||
$message = $check['message'] ?? null;
|
||
$message = is_string($message) && trim($message) !== '' ? $message : null;
|
||
|
||
$statusSpec = \App\Support\Badges\BadgeRenderer::spec(
|
||
\App\Support\Badges\BadgeDomain::VerificationCheckStatus,
|
||
$check['status'] ?? null,
|
||
);
|
||
|
||
$severitySpec = \App\Support\Badges\BadgeRenderer::spec(
|
||
\App\Support\Badges\BadgeDomain::VerificationCheckSeverity,
|
||
$check['severity'] ?? null,
|
||
);
|
||
|
||
$evidence = $check['evidence'] ?? [];
|
||
$evidence = is_array($evidence) ? $evidence : [];
|
||
|
||
$nextSteps = $check['next_steps'] ?? [];
|
||
$nextSteps = is_array($nextSteps) ? $nextSteps : [];
|
||
@endphp
|
||
|
||
<details class="rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900">
|
||
<summary class="flex cursor-pointer items-start justify-between gap-4">
|
||
<div class="space-y-1">
|
||
<div class="text-sm font-medium text-gray-900 dark:text-white">
|
||
{{ $title }}
|
||
</div>
|
||
@if ($message)
|
||
<div class="text-sm text-gray-600 dark:text-gray-300">
|
||
{{ $message }}
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
<div class="flex shrink-0 flex-wrap items-center justify-end gap-2">
|
||
<x-filament::badge :color="$severitySpec->color" :icon="$severitySpec->icon" size="sm">
|
||
{{ $severitySpec->label }}
|
||
</x-filament::badge>
|
||
<x-filament::badge :color="$statusSpec->color" :icon="$statusSpec->icon" size="sm">
|
||
{{ $statusSpec->label }}
|
||
</x-filament::badge>
|
||
</div>
|
||
</summary>
|
||
|
||
@if ($evidence !== [] || $nextSteps !== [])
|
||
<div class="mt-4 space-y-4">
|
||
@if ($evidence !== [])
|
||
<div>
|
||
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
||
Evidence
|
||
</div>
|
||
<ul class="mt-2 space-y-1 text-sm text-gray-700 dark:text-gray-200">
|
||
@foreach ($evidence as $pointer)
|
||
@php
|
||
$pointer = is_array($pointer) ? $pointer : [];
|
||
$kind = $pointer['kind'] ?? null;
|
||
$value = $pointer['value'] ?? null;
|
||
@endphp
|
||
|
||
@if (is_string($kind) && $kind !== '' && (is_string($value) || is_int($value)))
|
||
<li>
|
||
<span class="font-medium">{{ $kind }}:</span>
|
||
<span>{{ is_int($value) ? $value : $value }}</span>
|
||
</li>
|
||
@endif
|
||
@endforeach
|
||
</ul>
|
||
</div>
|
||
@endif
|
||
|
||
@if ($nextSteps !== [])
|
||
<div>
|
||
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
||
Next steps
|
||
</div>
|
||
<ul class="mt-2 space-y-1 text-sm">
|
||
@foreach ($nextSteps as $step)
|
||
@php
|
||
$step = is_array($step) ? $step : [];
|
||
$label = $step['label'] ?? null;
|
||
$url = $step['url'] ?? null;
|
||
$isExternal = is_string($url) && (str_starts_with($url, 'http://') || str_starts_with($url, 'https://'));
|
||
@endphp
|
||
|
||
@if (is_string($label) && $label !== '' && is_string($url) && $url !== '')
|
||
<li>
|
||
<a
|
||
href="{{ $url }}"
|
||
class="text-primary-600 hover:underline dark:text-primary-400"
|
||
@if ($isExternal)
|
||
target="_blank" rel="noreferrer"
|
||
@endif
|
||
>
|
||
{{ $label }}
|
||
</a>
|
||
</li>
|
||
@endif
|
||
@endforeach
|
||
</ul>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
@endif
|
||
</details>
|
||
@endforeach
|
||
</div>
|
||
@endif
|
||
@endif
|
||
</div>
|