## Summary - add a shared baseline compare summary assessment and assessor for compact trust propagation - harden dashboard, landing, and banner baseline compare surfaces against false all-clear claims - add focused Pest coverage for dashboard, landing, banner, reason translation, and canonical detail parity ## Validation - vendor/bin/sail bin pint --dirty --format agent - vendor/bin/sail artisan test --compact tests/Feature/Baselines/BaselineCompareSummaryAssessmentTest.php tests/Feature/Baselines/BaselineCompareExplanationFallbackTest.php tests/Feature/Filament/BaselineCompareNowWidgetTest.php tests/Feature/Filament/NeedsAttentionWidgetTest.php tests/Feature/Filament/BaselineCompareExplanationSurfaceTest.php tests/Feature/Filament/BaselineCompareLandingWhyNoFindingsTest.php tests/Feature/Filament/BaselineCompareCoverageBannerTest.php tests/Feature/Filament/BaselineCompareSummaryConsistencyTest.php tests/Feature/Filament/OperationRunBaselineTruthSurfaceTest.php tests/Feature/ReasonTranslation/ReasonTranslationExplanationTest.php ## Notes - Livewire compliance: Filament v5 / Livewire v4 stack unchanged - Provider registration: unchanged, Laravel 12 providers remain in bootstrap/providers.php - Global search: no searchable resource behavior changed - Destructive actions: none introduced by this change - Assets: no new assets registered; existing deploy process remains unchanged Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #196
137 lines
6.3 KiB
PHP
137 lines
6.3 KiB
PHP
@php
|
|
/** @var array<string, mixed>|null $summaryAssessment */
|
|
$summary = is_array($summaryAssessment ?? null) ? $summaryAssessment : null;
|
|
$summaryState = (string) ($summary['stateFamily'] ?? 'unavailable');
|
|
$summaryTone = (string) ($summary['tone'] ?? 'gray');
|
|
$findingsCount = (int) ($summary['findingsVisibleCount'] ?? 0);
|
|
$highSeverityCount = (int) ($summary['highSeverityCount'] ?? 0);
|
|
$nextAction = is_array($summary['nextAction'] ?? null) ? $summary['nextAction'] : ['label' => 'Review baseline compare', 'target' => 'none'];
|
|
|
|
$summaryLabel = match ($summaryState) {
|
|
'positive' => 'Aligned',
|
|
'caution' => 'Needs review',
|
|
'stale' => 'Refresh recommended',
|
|
'action_required' => 'Action required',
|
|
'in_progress' => 'In progress',
|
|
default => 'Unavailable',
|
|
};
|
|
|
|
[$cardClasses, $iconClasses, $textClasses] = match ($summaryTone) {
|
|
'success' => [
|
|
'rounded-lg border border-success-300 bg-success-50 p-4 dark:border-success-700 dark:bg-success-950/40',
|
|
'h-5 w-5 shrink-0 text-success-600 dark:text-success-400',
|
|
'text-success-900 dark:text-success-100',
|
|
],
|
|
'danger' => [
|
|
'rounded-lg border border-danger-300 bg-danger-50 p-4 dark:border-danger-700 dark:bg-danger-950/40',
|
|
'h-5 w-5 shrink-0 text-danger-600 dark:text-danger-400',
|
|
'text-danger-900 dark:text-danger-100',
|
|
],
|
|
'info' => [
|
|
'rounded-lg border border-info-300 bg-info-50 p-4 dark:border-info-700 dark:bg-info-950/40',
|
|
'h-5 w-5 shrink-0 text-info-600 dark:text-info-400',
|
|
'text-info-900 dark:text-info-100',
|
|
],
|
|
'warning' => [
|
|
'rounded-lg border border-warning-300 bg-warning-50 p-4 dark:border-warning-700 dark:bg-warning-950/40',
|
|
'h-5 w-5 shrink-0 text-warning-600 dark:text-warning-400',
|
|
'text-warning-900 dark:text-warning-100',
|
|
],
|
|
default => [
|
|
'rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-800 dark:bg-white/5',
|
|
'h-5 w-5 shrink-0 text-gray-500 dark:text-gray-400',
|
|
'text-gray-900 dark:text-white',
|
|
],
|
|
};
|
|
|
|
$summaryIcon = match ($summaryState) {
|
|
'positive' => 'heroicon-o-check-circle',
|
|
'action_required' => 'heroicon-o-exclamation-triangle',
|
|
'in_progress' => 'heroicon-o-arrow-path',
|
|
'stale' => 'heroicon-o-clock',
|
|
default => 'heroicon-o-information-circle',
|
|
};
|
|
@endphp
|
|
|
|
<x-filament::section heading="Baseline Governance">
|
|
@if ($landingUrl)
|
|
<x-slot name="afterHeader">
|
|
<x-filament::link :href="$landingUrl" size="sm" icon="heroicon-m-arrow-top-right-on-square" icon-position="after">
|
|
Details
|
|
</x-filament::link>
|
|
</x-slot>
|
|
@endif
|
|
|
|
@if (! $hasAssignment)
|
|
<div class="flex items-start gap-3 rounded-lg bg-gray-50 p-4 dark:bg-white/5">
|
|
<x-heroicon-o-link-slash class="mt-0.5 h-5 w-5 shrink-0 text-gray-400 dark:text-gray-500" />
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-950 dark:text-white">No Baseline Assigned</div>
|
|
<div class="mt-0.5 text-sm text-gray-500 dark:text-gray-400">Assign a baseline profile to start monitoring drift.</div>
|
|
</div>
|
|
</div>
|
|
@elseif ($summary)
|
|
<div class="flex flex-col gap-3">
|
|
<div class="flex items-center justify-between text-sm">
|
|
<div class="text-gray-600 dark:text-gray-300">
|
|
Baseline: <span class="font-medium text-gray-950 dark:text-white">{{ $profileName }}</span>
|
|
</div>
|
|
|
|
@if ($lastComparedAt)
|
|
<div class="text-gray-500 dark:text-gray-400">{{ $lastComparedAt }}</div>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="{{ $cardClasses }}">
|
|
<div class="flex items-start gap-3">
|
|
<x-filament::icon :icon="$summaryIcon" class="{{ $iconClasses }}" />
|
|
|
|
<div class="min-w-0 flex-1 space-y-3">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<x-filament::badge :color="$summaryTone" size="sm">
|
|
{{ $summaryLabel }}
|
|
</x-filament::badge>
|
|
|
|
@if ($findingsCount > 0)
|
|
<x-filament::badge color="danger" size="sm">
|
|
{{ $findingsCount }} {{ Str::plural('finding', $findingsCount) }}
|
|
</x-filament::badge>
|
|
@endif
|
|
|
|
@if ($highSeverityCount > 0)
|
|
<x-filament::badge color="danger" size="sm">
|
|
{{ $highSeverityCount }} high severity
|
|
</x-filament::badge>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="space-y-1">
|
|
<div class="text-sm font-semibold {{ $textClasses }}">
|
|
{{ $summary['headline'] }}
|
|
</div>
|
|
|
|
@if (filled($summary['supportingMessage'] ?? null))
|
|
<div class="text-sm text-gray-700 dark:text-gray-200">
|
|
{{ $summary['supportingMessage'] }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
<div>
|
|
@if (filled($nextActionUrl))
|
|
<x-filament::link :href="$nextActionUrl" size="sm" class="font-medium">
|
|
{{ $nextAction['label'] }}
|
|
</x-filament::link>
|
|
@elseif (filled($nextAction['label'] ?? null))
|
|
<div class="text-xs font-medium uppercase tracking-wide text-gray-600 dark:text-gray-300">
|
|
{{ $nextAction['label'] }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</x-filament::section>
|