## Summary - add the Spec 181 restore-safety layer with scope fingerprinting, preview/check integrity states, execution safety snapshots, result attention, and operator-facing copy across the wizard, restore detail, and canonical operation detail - add focused unit and feature coverage for restore-safety assessment, result attention, and restore-linked operation detail - switch the finding exceptions queue `Inspect exception` action to a native Filament slide-over while preserving query-param-backed inline summary behavior ## Testing - `vendor/bin/sail artisan test --compact tests/Feature/Monitoring/FindingExceptionsQueueTest.php tests/Feature/Filament/RestoreSafetyIntegrityWizardTest.php tests/Feature/Filament/RestoreResultAttentionSurfaceTest.php tests/Feature/Operations/RestoreLinkedOperationDetailTest.php tests/Unit/Support/RestoreSafety` ## Notes - Spec 181 checklist is complete (`specs/181-restore-safety-integrity/checklists/requirements.md`) - the branch still has unchecked follow-up tasks in `specs/181-restore-safety-integrity/tasks.md`: `T012`, `T018`, `T019`, `T023`, `T025`, `T029`, `T032`, `T033`, `T041`, `T042`, `T043`, `T044` - Filament v5 / Livewire v4 compliance is preserved, no panel provider registration changes were made, no global-search behavior was added, destructive actions remain confirmation-gated, and no new Filament assets were introduced Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #210
173 lines
8.8 KiB
PHP
173 lines
8.8 KiB
PHP
@php
|
|
$fieldWrapperView = $getFieldWrapperView();
|
|
|
|
$results = $getState() ?? [];
|
|
$results = is_array($results) ? $results : [];
|
|
|
|
$summary = $summary ?? [];
|
|
$summary = is_array($summary) ? $summary : [];
|
|
|
|
$checksIntegrity = $checksIntegrity ?? [];
|
|
$checksIntegrity = is_array($checksIntegrity) ? $checksIntegrity : [];
|
|
|
|
$executionReadiness = $executionReadiness ?? [];
|
|
$executionReadiness = is_array($executionReadiness) ? $executionReadiness : [];
|
|
|
|
$safetyAssessment = $safetyAssessment ?? [];
|
|
$safetyAssessment = is_array($safetyAssessment) ? $safetyAssessment : [];
|
|
|
|
$blocking = (int) ($summary['blocking'] ?? ($checksIntegrity['blocking_count'] ?? 0));
|
|
$warning = (int) ($summary['warning'] ?? ($checksIntegrity['warning_count'] ?? 0));
|
|
$safe = (int) ($summary['safe'] ?? 0);
|
|
|
|
$ranAt = $ranAt ?? ($checksIntegrity['ran_at'] ?? null);
|
|
$ranAtLabel = null;
|
|
|
|
if (is_string($ranAt) && $ranAt !== '') {
|
|
try {
|
|
$ranAtLabel = \Carbon\CarbonImmutable::parse($ranAt)->format('Y-m-d H:i');
|
|
} catch (\Throwable) {
|
|
$ranAtLabel = $ranAt;
|
|
}
|
|
}
|
|
|
|
$severitySpec = static function (?string $severity): \App\Support\Badges\BadgeSpec {
|
|
return \App\Support\Badges\BadgeRenderer::spec(\App\Support\Badges\BadgeDomain::RestoreCheckSeverity, $severity);
|
|
};
|
|
|
|
$integritySpec = $severitySpec($checksIntegrity['state'] ?? 'not_run');
|
|
$integritySummary = $checksIntegrity['display_summary'] ?? 'Run checks for the current scope before real execution.';
|
|
$nextAction = $safetyAssessment['primary_next_action'] ?? 'rerun_checks';
|
|
$nextActionLabel = \App\Support\RestoreSafety\RestoreSafetyCopy::primaryNextAction(is_string($nextAction) ? $nextAction : 'rerun_checks');
|
|
$startabilitySummary = $executionReadiness['display_summary'] ?? 'Execution readiness is unavailable.';
|
|
$startabilityTone = (bool) ($executionReadiness['allowed'] ?? false) ? 'success' : 'warning';
|
|
$limitedList = static function (array $items, int $limit = 5): array {
|
|
if (count($items) <= $limit) {
|
|
return $items;
|
|
}
|
|
|
|
return array_slice($items, 0, $limit);
|
|
};
|
|
@endphp
|
|
|
|
<x-dynamic-component :component="$fieldWrapperView" :field="$field">
|
|
<div class="space-y-4">
|
|
<x-filament::section
|
|
heading="Safety checks"
|
|
:description="$ranAtLabel ? ('Last run: ' . $ranAtLabel) : 'Checks tell you whether the current scope can be defended, not just whether it can start.'"
|
|
>
|
|
<div class="space-y-3">
|
|
<div class="flex flex-wrap gap-2">
|
|
<x-filament::badge :color="$integritySpec->color" :icon="$integritySpec->icon" size="sm">
|
|
{{ $integritySpec->label }}
|
|
</x-filament::badge>
|
|
<x-filament::badge :color="$startabilityTone" size="sm">
|
|
{{ (bool) ($executionReadiness['allowed'] ?? false) ? 'Technically startable' : 'Technical blocker present' }}
|
|
</x-filament::badge>
|
|
@if (($safetyAssessment['state'] ?? null) === 'ready_with_caution')
|
|
<x-filament::badge color="warning" size="sm">
|
|
Ready with caution
|
|
</x-filament::badge>
|
|
@elseif (($safetyAssessment['state'] ?? null) === 'ready')
|
|
<x-filament::badge color="success" size="sm">
|
|
Ready
|
|
</x-filament::badge>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="rounded-lg border border-slate-200 bg-slate-50 px-3 py-3 text-sm text-slate-900 dark:border-white/10 dark:bg-white/5 dark:text-slate-100">
|
|
<div class="font-medium">What the current checks prove</div>
|
|
<div class="mt-1">{{ $integritySummary }}</div>
|
|
<div class="mt-2 text-xs text-slate-600 dark:text-slate-300">
|
|
Technical startability: {{ $startabilitySummary }}
|
|
</div>
|
|
<div class="mt-2 text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
|
|
Primary next step
|
|
</div>
|
|
<div class="mt-1 text-xs text-slate-600 dark:text-slate-300">
|
|
{{ $nextActionLabel }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-wrap gap-2">
|
|
<x-filament::badge :color="$blocking > 0 ? $severitySpec('blocking')->color : 'gray'">
|
|
{{ $blocking }} {{ \Illuminate\Support\Str::lower($severitySpec('blocking')->label) }}
|
|
</x-filament::badge>
|
|
<x-filament::badge :color="$warning > 0 ? $severitySpec('warning')->color : 'gray'">
|
|
{{ $warning }} {{ \Illuminate\Support\Str::lower($severitySpec('warning')->label) }}
|
|
</x-filament::badge>
|
|
<x-filament::badge :color="$safe > 0 ? $severitySpec('safe')->color : 'gray'">
|
|
{{ $safe }} {{ \Illuminate\Support\Str::lower($severitySpec('safe')->label) }}
|
|
</x-filament::badge>
|
|
</div>
|
|
|
|
@if (($checksIntegrity['invalidation_reasons'] ?? []) !== [])
|
|
<div class="text-xs text-amber-800 dark:text-amber-200">
|
|
Invalidated by: {{ implode(', ', array_map(static fn (string $reason): string => \Illuminate\Support\Str::replace('_', ' ', $reason), $checksIntegrity['invalidation_reasons'])) }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</x-filament::section>
|
|
|
|
@if ($results === [])
|
|
<x-filament::section>
|
|
<div class="text-sm text-gray-600 dark:text-gray-300">
|
|
No checks have been recorded for this scope yet.
|
|
</div>
|
|
</x-filament::section>
|
|
@else
|
|
<div class="space-y-3">
|
|
@foreach ($results as $result)
|
|
@php
|
|
$severity = is_array($result) ? ($result['severity'] ?? 'safe') : 'safe';
|
|
$title = is_array($result) ? ($result['title'] ?? $result['code'] ?? 'Check') : 'Check';
|
|
$message = is_array($result) ? ($result['message'] ?? null) : null;
|
|
$meta = is_array($result) ? ($result['meta'] ?? []) : [];
|
|
$meta = is_array($meta) ? $meta : [];
|
|
$unmappedGroups = $meta['unmapped'] ?? [];
|
|
$unmappedGroups = is_array($unmappedGroups) ? $limitedList($unmappedGroups) : [];
|
|
$spec = $severitySpec($severity);
|
|
@endphp
|
|
|
|
<x-filament::section>
|
|
<div class="flex 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 (is_string($message) && $message !== '')
|
|
<div class="text-sm text-gray-600 dark:text-gray-300">
|
|
{{ $message }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
<x-filament::badge :color="$spec->color" :icon="$spec->icon" size="sm">
|
|
{{ $spec->label }}
|
|
</x-filament::badge>
|
|
</div>
|
|
|
|
@if ($unmappedGroups !== [])
|
|
<div class="mt-3">
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
|
Unmapped groups
|
|
</div>
|
|
<ul class="mt-2 space-y-1 text-sm text-gray-700 dark:text-gray-200">
|
|
@foreach ($unmappedGroups as $group)
|
|
@php
|
|
$label = is_array($group) ? ($group['label'] ?? $group['id'] ?? null) : null;
|
|
@endphp
|
|
@if (is_string($label) && $label !== '')
|
|
<li>{{ $label }}</li>
|
|
@endif
|
|
@endforeach
|
|
</ul>
|
|
</div>
|
|
@endif
|
|
</x-filament::section>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</x-dynamic-component>
|