208 lines
8.6 KiB
PHP
208 lines
8.6 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Resources;
|
|
|
|
use App\Filament\Clusters\Inventory\InventoryCluster;
|
|
use App\Filament\Resources\InventoryItemResource\Pages;
|
|
use App\Models\InventoryItem;
|
|
use App\Models\Tenant;
|
|
use App\Services\Inventory\DependencyQueryService;
|
|
use App\Services\Inventory\DependencyTargets\DependencyTargetResolver;
|
|
use App\Support\Enums\RelationshipType;
|
|
use App\Support\Inventory\InventoryPolicyTypeMeta;
|
|
use BackedEnum;
|
|
use Filament\Actions;
|
|
use Filament\Infolists\Components\TextEntry;
|
|
use Filament\Infolists\Components\ViewEntry;
|
|
use Filament\Resources\Resource;
|
|
use Filament\Schemas\Components\Section;
|
|
use Filament\Schemas\Schema;
|
|
use Filament\Tables;
|
|
use Filament\Tables\Table;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use UnitEnum;
|
|
|
|
class InventoryItemResource extends Resource
|
|
{
|
|
protected static ?string $model = InventoryItem::class;
|
|
|
|
protected static ?string $cluster = InventoryCluster::class;
|
|
|
|
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-rectangle-stack';
|
|
|
|
protected static string|UnitEnum|null $navigationGroup = 'Inventory';
|
|
|
|
public static function form(Schema $schema): Schema
|
|
{
|
|
return $schema;
|
|
}
|
|
|
|
public static function infolist(Schema $schema): Schema
|
|
{
|
|
return $schema
|
|
->schema([
|
|
Section::make('Inventory Item')
|
|
->schema([
|
|
TextEntry::make('display_name')->label('Name'),
|
|
TextEntry::make('policy_type')
|
|
->label('Type')
|
|
->badge()
|
|
->state(fn (InventoryItem $record): string => static::typeMeta($record->policy_type)['label'] ?? (string) $record->policy_type),
|
|
TextEntry::make('category')
|
|
->badge()
|
|
->state(fn (InventoryItem $record): string => $record->category
|
|
?: (static::typeMeta($record->policy_type)['category'] ?? 'Unknown')),
|
|
TextEntry::make('platform')->badge(),
|
|
TextEntry::make('external_id')->label('External ID'),
|
|
TextEntry::make('last_seen_at')->label('Last seen')->dateTime(),
|
|
TextEntry::make('last_seen_run_id')
|
|
->label('Last sync run')
|
|
->url(function (InventoryItem $record): ?string {
|
|
if (! $record->last_seen_run_id) {
|
|
return null;
|
|
}
|
|
|
|
return InventorySyncRunResource::getUrl('view', ['record' => $record->last_seen_run_id], tenant: Tenant::current());
|
|
})
|
|
->openUrlInNewTab(),
|
|
TextEntry::make('support_restore')
|
|
->label('Restore')
|
|
->badge()
|
|
->state(fn (InventoryItem $record): string => static::typeMeta($record->policy_type)['restore'] ?? 'enabled'),
|
|
TextEntry::make('support_risk')
|
|
->label('Risk')
|
|
->badge()
|
|
->state(fn (InventoryItem $record): string => static::typeMeta($record->policy_type)['risk'] ?? 'normal'),
|
|
])
|
|
->columns(2)
|
|
->columnSpanFull(),
|
|
|
|
Section::make('Dependencies')
|
|
->schema([
|
|
ViewEntry::make('dependencies')
|
|
->label('')
|
|
->view('filament.components.dependency-edges')
|
|
->state(function (InventoryItem $record) {
|
|
$direction = request()->query('direction', 'all');
|
|
$relationshipType = request()->query('relationship_type', 'all');
|
|
$relationshipType = is_string($relationshipType) ? $relationshipType : 'all';
|
|
|
|
$relationshipType = $relationshipType === 'all'
|
|
? null
|
|
: RelationshipType::tryFrom($relationshipType)?->value;
|
|
|
|
$service = app(DependencyQueryService::class);
|
|
$resolver = app(DependencyTargetResolver::class);
|
|
$tenant = Tenant::current();
|
|
|
|
$edges = collect();
|
|
if ($direction === 'inbound' || $direction === 'all') {
|
|
$edges = $edges->merge($service->getInboundEdges($record, $relationshipType));
|
|
}
|
|
if ($direction === 'outbound' || $direction === 'all') {
|
|
$edges = $edges->merge($service->getOutboundEdges($record, $relationshipType));
|
|
}
|
|
|
|
return $resolver->attachRenderedTargets($edges->take(100), $tenant); // both directions combined
|
|
})
|
|
->columnSpanFull(),
|
|
])
|
|
->columnSpanFull(),
|
|
|
|
Section::make('Metadata (Safe Subset)')
|
|
->schema([
|
|
ViewEntry::make('meta_jsonb')
|
|
->label('')
|
|
->view('filament.infolists.entries.snapshot-json')
|
|
->state(fn (InventoryItem $record) => $record->meta_jsonb ?? [])
|
|
->columnSpanFull(),
|
|
])
|
|
->columnSpanFull(),
|
|
]);
|
|
}
|
|
|
|
public static function table(Table $table): Table
|
|
{
|
|
$typeOptions = collect(static::allTypeMeta())
|
|
->mapWithKeys(fn (array $row) => [($row['type'] ?? null) => ($row['label'] ?? $row['type'] ?? null)])
|
|
->filter(fn ($label, $type) => is_string($type) && $type !== '' && is_string($label) && $label !== '')
|
|
->all();
|
|
|
|
$categoryOptions = collect(static::allTypeMeta())
|
|
->mapWithKeys(fn (array $row) => [($row['category'] ?? null) => ($row['category'] ?? null)])
|
|
->filter(fn ($value, $key) => is_string($key) && $key !== '')
|
|
->all();
|
|
|
|
return $table
|
|
->defaultSort('last_seen_at', 'desc')
|
|
->columns([
|
|
Tables\Columns\TextColumn::make('display_name')
|
|
->label('Name')
|
|
->searchable(),
|
|
Tables\Columns\TextColumn::make('policy_type')
|
|
->label('Type')
|
|
->badge()
|
|
->formatStateUsing(fn (?string $state): string => static::typeMeta($state)['label'] ?? (string) $state),
|
|
Tables\Columns\TextColumn::make('category')
|
|
->badge(),
|
|
Tables\Columns\TextColumn::make('platform')
|
|
->badge(),
|
|
Tables\Columns\TextColumn::make('last_seen_at')
|
|
->label('Last seen')
|
|
->since(),
|
|
Tables\Columns\TextColumn::make('lastSeenRun.status')
|
|
->label('Run')
|
|
->badge(),
|
|
])
|
|
->filters([
|
|
Tables\Filters\SelectFilter::make('policy_type')
|
|
->options($typeOptions)
|
|
->searchable(),
|
|
Tables\Filters\SelectFilter::make('category')
|
|
->options($categoryOptions)
|
|
->searchable(),
|
|
])
|
|
->actions([
|
|
Actions\ViewAction::make(),
|
|
])
|
|
->bulkActions([]);
|
|
}
|
|
|
|
public static function getEloquentQuery(): Builder
|
|
{
|
|
$tenantId = Tenant::current()->getKey();
|
|
|
|
return parent::getEloquentQuery()
|
|
->when($tenantId, fn (Builder $query) => $query->where('tenant_id', $tenantId))
|
|
->with('lastSeenRun');
|
|
}
|
|
|
|
public static function getPages(): array
|
|
{
|
|
return [
|
|
'index' => Pages\ListInventoryItems::route('/'),
|
|
'view' => Pages\ViewInventoryItem::route('/{record}'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array{label:?string,category:?string,restore:?string,risk:?string}|array<string,mixed>
|
|
*/
|
|
private static function typeMeta(?string $type): array
|
|
{
|
|
if ($type === null) {
|
|
return [];
|
|
}
|
|
|
|
return InventoryPolicyTypeMeta::metaFor($type);
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private static function allTypeMeta(): array
|
|
{
|
|
return InventoryPolicyTypeMeta::all();
|
|
}
|
|
}
|