TenantAtlas/resources/views/filament/infolists/entries/policy-general.blade.php
ahmido 412dd7ad66 feat/017-policy-types-mam-endpoint-security-baselines (#23)
Hydrate configurationPolicies/{id}/settings for endpoint security/baseline policies so snapshots include real rule data.
Treat those types like Settings Catalog policies in the normalizer so they show the searchable settings table, recognizable categories, and readable choice values (firewall-specific formatting + interface badge parsing).
Improve “General” tab cards: badge lists for platforms/technologies, template reference summary (name/family/version/ID), and ISO timestamps rendered as YYYY‑MM‑DD HH:MM:SS; added regression test for the view.

Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local>
Reviewed-on: #23
2026-01-03 02:06:35 +00:00

209 lines
11 KiB
PHP

@php
use Carbon\CarbonImmutable;
use Illuminate\Support\Str;
$general = $getState();
$entries = is_array($general) ? ($general['entries'] ?? []) : [];
$cards = [];
foreach ($entries as $entry) {
if (! is_array($entry)) {
continue;
}
$key = $entry['key'] ?? null;
$value = $entry['value'] ?? null;
$decoded = null;
if (is_string($value)) {
$trimmed = trim($value);
if ($trimmed !== '' && (str_starts_with($trimmed, '{') || str_starts_with($trimmed, '['))) {
$decodedValue = json_decode($trimmed, true);
if (json_last_error() === JSON_ERROR_NONE) {
$decoded = $decodedValue;
$value = $decodedValue;
}
}
}
$isEmpty = $value === null
|| $value === ''
|| $value === '-'
|| (is_array($value) && $value === []);
if ($isEmpty) {
continue;
}
$label = is_string($key) && $key !== '' ? $key : 'Field';
$cards[] = [
'key' => $label,
'key_lower' => strtolower($label),
'value' => $value,
'decoded' => $decoded,
];
}
$toneMap = [
'name' => ['icon' => 'heroicon-o-tag', 'ring' => 'ring-amber-200/70 dark:ring-amber-800/60', 'tone' => 'amber'],
'platform' => ['icon' => 'heroicon-o-computer-desktop', 'ring' => 'ring-sky-200/70 dark:ring-sky-800/60', 'tone' => 'sky'],
'settings' => ['icon' => 'heroicon-o-adjustments-horizontal', 'ring' => 'ring-emerald-200/70 dark:ring-emerald-800/60', 'tone' => 'emerald'],
'template' => ['icon' => 'heroicon-o-rectangle-stack', 'ring' => 'ring-rose-200/70 dark:ring-rose-800/60', 'tone' => 'rose'],
'technology' => ['icon' => 'heroicon-o-cpu-chip', 'ring' => 'ring-teal-200/70 dark:ring-teal-800/60', 'tone' => 'teal'],
'default' => ['icon' => 'heroicon-o-document-text', 'ring' => 'ring-gray-200/70 dark:ring-gray-700/60', 'tone' => 'slate'],
];
$toneClasses = [
'amber' => 'bg-amber-100/80 text-amber-700 dark:bg-amber-900/40 dark:text-amber-200',
'sky' => 'bg-sky-100/80 text-sky-700 dark:bg-sky-900/40 dark:text-sky-200',
'emerald' => 'bg-emerald-100/80 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-200',
'rose' => 'bg-rose-100/80 text-rose-700 dark:bg-rose-900/40 dark:text-rose-200',
'teal' => 'bg-teal-100/80 text-teal-700 dark:bg-teal-900/40 dark:text-teal-200',
'slate' => 'bg-slate-100/80 text-slate-700 dark:bg-slate-900/40 dark:text-slate-200',
];
$formatIsoDateTime = static function (string $value): ?string {
$trimmed = trim($value);
if ($trimmed === '') {
return null;
}
if (! preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/', $trimmed)) {
return null;
}
// Graph can return 7 fractional digits; PHP supports 6 (microseconds).
$normalized = preg_replace('/\.(\d{6})\d+Z$/', '.$1Z', $trimmed);
try {
return CarbonImmutable::parse($normalized)->toDateTimeString();
} catch (\Throwable) {
return null;
}
};
@endphp
@if (empty($cards))
<p class="text-sm text-gray-600 dark:text-gray-400">No general metadata available.</p>
@else
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
@foreach ($cards as $entry)
@php
$keyLower = $entry['key_lower'] ?? '';
$value = $entry['value'] ?? null;
$isPlatform = str_contains($keyLower, 'platform');
$isTechnologies = str_contains($keyLower, 'technolog');
$isTemplateReference = str_contains($keyLower, 'template');
$isDateTime = is_string($value) && ($formattedDateTime = $formatIsoDateTime($value)) !== null;
$toneKey = match (true) {
str_contains($keyLower, 'name') => 'name',
str_contains($keyLower, 'platform') => 'platform',
str_contains($keyLower, 'setting') => 'settings',
str_contains($keyLower, 'template') => 'template',
str_contains($keyLower, 'technology') => 'technology',
default => 'default',
};
$tone = $toneMap[$toneKey] ?? $toneMap['default'];
$toneClass = $toneClasses[$tone['tone'] ?? 'slate'] ?? $toneClasses['slate'];
$isJsonValue = is_array($value) && ! (array_is_list($value) && array_reduce($value, fn ($carry, $item) => $carry && is_scalar($item), true));
$isListValue = is_array($value) && array_is_list($value) && array_reduce($value, fn ($carry, $item) => $carry && is_scalar($item), true);
$isBooleanValue = is_bool($value);
$isBooleanString = is_string($value) && in_array(strtolower($value), ['true', 'false', 'enabled', 'disabled'], true);
$isNumericValue = is_numeric($value);
$badgeItems = null;
if ($isListValue) {
$badgeItems = $value;
} elseif (($isPlatform || $isTechnologies) && is_string($value)) {
$split = array_values(array_filter(array_map('trim', explode(',', $value)), static fn (string $item): bool => $item !== ''));
$badgeItems = $split !== [] ? $split : [$value];
}
@endphp
<div class="tp-policy-general-card group relative overflow-hidden rounded-xl border border-gray-200/70 bg-white p-4 shadow-sm transition duration-200 hover:-translate-y-0.5 hover:border-gray-300/70 hover:shadow-md dark:border-gray-700/60 dark:bg-gray-900 dark:hover:border-gray-600">
<div class="flex items-start gap-3">
<div class="flex h-10 w-10 items-center justify-center rounded-lg ring-1 {{ $tone['ring'] ?? '' }} {{ $toneClass }}">
<x-filament::icon icon="{{ $tone['icon'] ?? 'heroicon-o-document-text' }}" class="h-5 w-5" />
</div>
<div class="min-w-0 flex-1">
<dt class="text-xs font-semibold tracking-wide text-gray-500 dark:text-gray-400">
{{ $entry['key'] ?? '-' }}
</dt>
<dd class="mt-2 text-left">
@if ($isTemplateReference && is_array($value))
@php
$templateDisplayName = $value['templateDisplayName'] ?? null;
$templateFamily = $value['templateFamily'] ?? null;
$templateDisplayVersion = $value['templateDisplayVersion'] ?? null;
$templateId = $value['templateId'] ?? null;
$familyLabel = is_string($templateFamily) && $templateFamily !== '' ? Str::headline($templateFamily) : null;
@endphp
<div class="space-y-2">
<div class="text-sm font-semibold text-gray-900 dark:text-white">
{{ is_string($templateDisplayName) && $templateDisplayName !== '' ? $templateDisplayName : 'Template' }}
</div>
<div class="flex flex-wrap gap-2">
@if ($familyLabel)
<x-filament::badge color="gray" size="sm">{{ $familyLabel }}</x-filament::badge>
@endif
@if (is_string($templateDisplayVersion) && $templateDisplayVersion !== '')
<x-filament::badge color="gray" size="sm">{{ $templateDisplayVersion }}</x-filament::badge>
@endif
</div>
@if (is_string($templateId) && $templateId !== '')
<div class="text-xs font-mono text-gray-500 dark:text-gray-400 break-all">
{{ $templateId }}
</div>
@endif
</div>
@elseif ($isDateTime)
<div class="text-sm font-semibold text-gray-900 dark:text-white tabular-nums">
{{ $formattedDateTime }}
</div>
@elseif (is_array($badgeItems) && $badgeItems !== [])
<div class="flex flex-wrap gap-2">
@foreach ($badgeItems as $item)
<x-filament::badge :color="$isPlatform ? 'info' : 'gray'" size="sm">
{{ $item }}
</x-filament::badge>
@endforeach
</div>
@elseif ($isJsonValue)
<pre class="whitespace-pre-wrap rounded-lg border border-gray-200 bg-gray-50 p-2 text-xs font-mono text-gray-700 dark:border-gray-700 dark:bg-gray-900/60 dark:text-gray-200">{{ json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) }}</pre>
@elseif ($isBooleanValue || $isBooleanString)
@php
$boolValue = $isBooleanValue
? $value
: in_array(strtolower($value), ['true', 'enabled'], true);
$boolLabel = $boolValue ? 'Enabled' : 'Disabled';
@endphp
<x-filament::badge :color="$boolValue ? 'success' : 'gray'" size="sm">
{{ $boolLabel }}
</x-filament::badge>
@elseif ($isNumericValue)
<div class="text-sm font-semibold text-gray-900 dark:text-white tabular-nums">
{{ number_format((float) $value) }}
</div>
@else
<div class="text-sm text-gray-900 dark:text-white whitespace-pre-wrap break-words text-left">
{{ is_string($value) ? $value : json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) }}
</div>
@endif
</dd>
</div>
</div>
</div>
@endforeach
</div>
@endif