merge: agent session work
This commit is contained in:
commit
b2608a3470
@ -59,18 +59,10 @@ public static function infolist(Schema $schema): Schema
|
|||||||
->label('')
|
->label('')
|
||||||
->view('filament.infolists.entries.normalized-settings')
|
->view('filament.infolists.entries.normalized-settings')
|
||||||
->state(function (Policy $record) {
|
->state(function (Policy $record) {
|
||||||
$snapshot = static::latestSnapshot($record);
|
$normalized = static::normalizedPolicyState($record);
|
||||||
|
$split = static::splitGeneralBlock($normalized);
|
||||||
|
|
||||||
$normalized = app(PolicyNormalizer::class)->normalize(
|
return $split['normalized'];
|
||||||
$snapshot,
|
|
||||||
$record->policy_type,
|
|
||||||
$record->platform
|
|
||||||
);
|
|
||||||
|
|
||||||
$normalized['context'] = 'policy';
|
|
||||||
$normalized['record_id'] = (string) $record->getKey();
|
|
||||||
|
|
||||||
return $normalized;
|
|
||||||
})
|
})
|
||||||
->visible(fn (Policy $record) => $record->policy_type === 'settingsCatalogPolicy' &&
|
->visible(fn (Policy $record) => $record->policy_type === 'settingsCatalogPolicy' &&
|
||||||
$record->versions()->exists()
|
$record->versions()->exists()
|
||||||
@ -80,15 +72,10 @@ public static function infolist(Schema $schema): Schema
|
|||||||
->label('')
|
->label('')
|
||||||
->view('filament.infolists.entries.policy-settings-standard')
|
->view('filament.infolists.entries.policy-settings-standard')
|
||||||
->state(function (Policy $record) {
|
->state(function (Policy $record) {
|
||||||
$snapshot = static::latestSnapshot($record);
|
$normalized = static::normalizedPolicyState($record);
|
||||||
|
$split = static::splitGeneralBlock($normalized);
|
||||||
|
|
||||||
$normalizer = app(PolicyNormalizer::class);
|
return $split['normalized'];
|
||||||
|
|
||||||
return $normalizer->normalize(
|
|
||||||
$snapshot,
|
|
||||||
$record->policy_type,
|
|
||||||
$record->platform
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
->visible(fn (Policy $record) => $record->policy_type !== 'settingsCatalogPolicy' &&
|
->visible(fn (Policy $record) => $record->policy_type !== 'settingsCatalogPolicy' &&
|
||||||
$record->versions()->exists()
|
$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.')
|
->helperText('This policy has been inventoried but no configuration snapshot has been captured yet.')
|
||||||
->visible(fn (Policy $record) => ! $record->versions()->exists()),
|
->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')
|
Tab::make('JSON')
|
||||||
->schema([
|
->schema([
|
||||||
ViewEntry::make('snapshot_json')
|
ViewEntry::make('snapshot_json')
|
||||||
@ -331,6 +331,68 @@ private static function latestSnapshot(Policy $record): array
|
|||||||
return [];
|
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>
|
* @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
|
public function panel(Panel $panel): Panel
|
||||||
{
|
{
|
||||||
return $panel
|
$panel = $panel
|
||||||
->default()
|
->default()
|
||||||
->id('admin')
|
->id('admin')
|
||||||
->path('admin')
|
->path('admin')
|
||||||
@ -55,5 +55,11 @@ public function panel(Panel $panel): Panel
|
|||||||
->authMiddleware([
|
->authMiddleware([
|
||||||
Authenticate::class,
|
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->assertOk();
|
||||||
$response->assertSee('Settings'); // Settings tab should appear for Settings Catalog
|
$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 () {
|
it('shows display names instead of definition IDs', function () {
|
||||||
|
|||||||
@ -5,7 +5,11 @@ import tailwindcss from '@tailwindcss/vite';
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
laravel({
|
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,
|
refresh: true,
|
||||||
}),
|
}),
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user