feat: style policy general tab
This commit is contained in:
parent
2b10e086ea
commit
a10c4914c4
@ -59,18 +59,10 @@ public static function infolist(Schema $schema): Schema
|
||||
->label('')
|
||||
->view('filament.infolists.entries.normalized-settings')
|
||||
->state(function (Policy $record) {
|
||||
$snapshot = static::latestSnapshot($record);
|
||||
$normalized = static::normalizedPolicyState($record);
|
||||
$split = static::splitGeneralBlock($normalized);
|
||||
|
||||
$normalized = app(PolicyNormalizer::class)->normalize(
|
||||
$snapshot,
|
||||
$record->policy_type,
|
||||
$record->platform
|
||||
);
|
||||
|
||||
$normalized['context'] = 'policy';
|
||||
$normalized['record_id'] = (string) $record->getKey();
|
||||
|
||||
return $normalized;
|
||||
return $split['normalized'];
|
||||
})
|
||||
->visible(fn (Policy $record) => $record->policy_type === 'settingsCatalogPolicy' &&
|
||||
$record->versions()->exists()
|
||||
@ -80,15 +72,10 @@ public static function infolist(Schema $schema): Schema
|
||||
->label('')
|
||||
->view('filament.infolists.entries.policy-settings-standard')
|
||||
->state(function (Policy $record) {
|
||||
$snapshot = static::latestSnapshot($record);
|
||||
$normalized = static::normalizedPolicyState($record);
|
||||
$split = static::splitGeneralBlock($normalized);
|
||||
|
||||
$normalizer = app(PolicyNormalizer::class);
|
||||
|
||||
return $normalizer->normalize(
|
||||
$snapshot,
|
||||
$record->policy_type,
|
||||
$record->platform
|
||||
);
|
||||
return $split['normalized'];
|
||||
})
|
||||
->visible(fn (Policy $record) => $record->policy_type !== 'settingsCatalogPolicy' &&
|
||||
$record->versions()->exists()
|
||||
@ -100,6 +87,19 @@ public static function infolist(Schema $schema): Schema
|
||||
->helperText('This policy has been inventoried but no configuration snapshot has been captured yet.')
|
||||
->visible(fn (Policy $record) => ! $record->versions()->exists()),
|
||||
]),
|
||||
Tab::make('General')
|
||||
->schema([
|
||||
ViewEntry::make('policy_general')
|
||||
->label('')
|
||||
->view('filament.infolists.entries.policy-general')
|
||||
->state(function (Policy $record) {
|
||||
$normalized = static::normalizedPolicyState($record);
|
||||
$split = static::splitGeneralBlock($normalized);
|
||||
|
||||
return $split['general'];
|
||||
}),
|
||||
])
|
||||
->visible(fn (Policy $record) => $record->versions()->exists()),
|
||||
Tab::make('JSON')
|
||||
->schema([
|
||||
ViewEntry::make('snapshot_json')
|
||||
@ -331,6 +331,68 @@ private static function latestSnapshot(Policy $record): array
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function normalizedPolicyState(Policy $record): array
|
||||
{
|
||||
static $cache = [];
|
||||
|
||||
$cacheKey = (string) $record->getKey();
|
||||
|
||||
if (isset($cache[$cacheKey])) {
|
||||
return $cache[$cacheKey];
|
||||
}
|
||||
|
||||
$snapshot = static::latestSnapshot($record);
|
||||
|
||||
$normalized = app(PolicyNormalizer::class)->normalize(
|
||||
$snapshot,
|
||||
$record->policy_type,
|
||||
$record->platform
|
||||
);
|
||||
|
||||
$normalized['context'] = 'policy';
|
||||
$normalized['record_id'] = (string) $record->getKey();
|
||||
|
||||
$cache[$cacheKey] = $normalized;
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{settings?: array<int, array<string, mixed>>} $normalized
|
||||
* @return array{normalized: array<string, mixed>, general: ?array<string, mixed>}
|
||||
*/
|
||||
private static function splitGeneralBlock(array $normalized): array
|
||||
{
|
||||
$general = null;
|
||||
$filtered = [];
|
||||
|
||||
foreach ($normalized['settings'] ?? [] as $block) {
|
||||
if (! is_array($block)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$title = $block['title'] ?? null;
|
||||
|
||||
if (is_string($title) && strtolower($title) === 'general') {
|
||||
$general = $block;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$filtered[] = $block;
|
||||
}
|
||||
|
||||
$normalized['settings'] = $filtered;
|
||||
|
||||
return [
|
||||
'normalized' => $normalized,
|
||||
'general' => $general,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{label:?string,category:?string,restore:?string,risk:?string}|array<string,string>|array<string,mixed>
|
||||
*/
|
||||
|
||||
@ -23,7 +23,7 @@ class AdminPanelProvider extends PanelProvider
|
||||
{
|
||||
public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
$panel = $panel
|
||||
->default()
|
||||
->id('admin')
|
||||
->path('admin')
|
||||
@ -55,5 +55,11 @@ public function panel(Panel $panel): Panel
|
||||
->authMiddleware([
|
||||
Authenticate::class,
|
||||
]);
|
||||
|
||||
if (! app()->runningUnitTests()) {
|
||||
$panel->viteTheme('resources/css/filament/admin/theme.css');
|
||||
}
|
||||
|
||||
return $panel;
|
||||
}
|
||||
}
|
||||
|
||||
5
resources/css/filament/admin/theme.css
Normal file
5
resources/css/filament/admin/theme.css
Normal file
@ -0,0 +1,5 @@
|
||||
@import '../../../../vendor/filament/filament/resources/css/theme.css';
|
||||
|
||||
@source '../../../../app/Filament/**/*';
|
||||
@source '../../../../resources/views/filament/**/*.blade.php';
|
||||
@source '../../../../resources/views/livewire/**/*.blade.php';
|
||||
@ -0,0 +1,138 @@
|
||||
@php
|
||||
$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',
|
||||
];
|
||||
@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');
|
||||
$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);
|
||||
@endphp
|
||||
|
||||
<div class="group relative overflow-hidden rounded-xl border border-gray-200/70 bg-gradient-to-br from-white via-amber-50/40 to-amber-100/60 p-4 shadow-sm transition duration-200 hover:-translate-y-0.5 hover:border-amber-200/70 hover:shadow-md dark:border-gray-700/60 dark:from-gray-950 dark:via-gray-900 dark:to-amber-950/30 dark:hover:border-amber-700/60">
|
||||
<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 uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
||||
{{ $entry['key'] ?? '-' }}
|
||||
</dt>
|
||||
<dd class="mt-2">
|
||||
@if ($isListValue)
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@foreach ($value 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) }}</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-lg 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">
|
||||
{{ is_string($value) ? $value : json_encode($value, JSON_PRETTY_PRINT) }}
|
||||
</div>
|
||||
@endif
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@ -78,6 +78,9 @@
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertSee('Settings'); // Settings tab should appear for Settings Catalog
|
||||
$response->assertSee('General');
|
||||
$response->assertSee('JSON');
|
||||
$response->assertSee('bg-gradient-to-br');
|
||||
});
|
||||
|
||||
it('shows display names instead of definition IDs', function () {
|
||||
|
||||
@ -5,7 +5,11 @@ import tailwindcss from '@tailwindcss/vite';
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
laravel({
|
||||
input: ['resources/css/app.css', 'resources/js/app.js'],
|
||||
input: [
|
||||
'resources/css/app.css',
|
||||
'resources/css/filament/admin/theme.css',
|
||||
'resources/js/app.js',
|
||||
],
|
||||
refresh: true,
|
||||
}),
|
||||
tailwindcss(),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user