Features: - AuditResource with table listing all audit logs - ViewAudit page showing detailed changes with diff table - Filters: date range, user, model type, event type - Search by user name - Auto-refresh every 60 seconds - Dark mode support for changes view - Fixed audits migration with proper schema - Added audit.view permission (admin only by default) Files: - src/app/Models/Audit.php - src/app/Filament/Resources/AuditResource.php - src/app/Filament/Resources/AuditResource/Pages/ - src/resources/views/filament/infolists/entries/audit-changes.blade.php - Updated audits migration with full schema
232 lines
8.4 KiB
PHP
232 lines
8.4 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Resources;
|
|
|
|
use App\Filament\Resources\AuditResource\Pages;
|
|
use App\Models\Audit;
|
|
use Filament\Forms;
|
|
use Filament\Forms\Form;
|
|
use Filament\Resources\Resource;
|
|
use Filament\Tables;
|
|
use Filament\Tables\Table;
|
|
use Filament\Infolists;
|
|
use Filament\Infolists\Infolist;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
|
|
class AuditResource extends Resource
|
|
{
|
|
protected static ?string $model = Audit::class;
|
|
|
|
protected static ?string $navigationIcon = 'heroicon-o-clipboard-document-list';
|
|
|
|
protected static ?string $navigationGroup = 'System';
|
|
|
|
protected static ?string $navigationLabel = 'Audit Trail';
|
|
|
|
protected static ?int $navigationSort = 100;
|
|
|
|
protected static ?string $modelLabel = 'Audit Log';
|
|
|
|
protected static ?string $pluralModelLabel = 'Audit Trail';
|
|
|
|
public static function canAccess(): bool
|
|
{
|
|
return auth()->user()?->can('audit.view') ?? false;
|
|
}
|
|
|
|
public static function canCreate(): bool
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public static function canEdit($record): bool
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public static function canDelete($record): bool
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public static function form(Form $form): Form
|
|
{
|
|
return $form->schema([]);
|
|
}
|
|
|
|
public static function table(Table $table): Table
|
|
{
|
|
return $table
|
|
->columns([
|
|
Tables\Columns\TextColumn::make('created_at')
|
|
->label('Date/Time')
|
|
->dateTime('M j, Y H:i:s')
|
|
->sortable(),
|
|
|
|
Tables\Columns\TextColumn::make('user.name')
|
|
->label('User')
|
|
->default('System')
|
|
->searchable()
|
|
->sortable(),
|
|
|
|
Tables\Columns\BadgeColumn::make('event')
|
|
->label('Action')
|
|
->colors([
|
|
'success' => 'created',
|
|
'warning' => 'updated',
|
|
'danger' => 'deleted',
|
|
'info' => 'restored',
|
|
])
|
|
->formatStateUsing(fn (string $state): string => ucfirst($state)),
|
|
|
|
Tables\Columns\TextColumn::make('auditable_type')
|
|
->label('Model')
|
|
->formatStateUsing(fn (string $state): string => class_basename($state))
|
|
->sortable(),
|
|
|
|
Tables\Columns\TextColumn::make('auditable_id')
|
|
->label('Record ID')
|
|
->sortable(),
|
|
|
|
Tables\Columns\TextColumn::make('ip_address')
|
|
->label('IP Address')
|
|
->toggleable(isToggledHiddenByDefault: true),
|
|
|
|
Tables\Columns\TextColumn::make('url')
|
|
->label('URL')
|
|
->limit(30)
|
|
->toggleable(isToggledHiddenByDefault: true),
|
|
])
|
|
->defaultSort('created_at', 'desc')
|
|
->filters([
|
|
Tables\Filters\SelectFilter::make('event')
|
|
->label('Action')
|
|
->options([
|
|
'created' => 'Created',
|
|
'updated' => 'Updated',
|
|
'deleted' => 'Deleted',
|
|
'restored' => 'Restored',
|
|
]),
|
|
|
|
Tables\Filters\SelectFilter::make('user_id')
|
|
->label('User')
|
|
->relationship('user', 'name')
|
|
->searchable()
|
|
->preload(),
|
|
|
|
Tables\Filters\SelectFilter::make('auditable_type')
|
|
->label('Model')
|
|
->options(fn () => Audit::query()
|
|
->distinct()
|
|
->pluck('auditable_type')
|
|
->mapWithKeys(fn ($type) => [$type => class_basename($type)])
|
|
->toArray()
|
|
),
|
|
|
|
Tables\Filters\Filter::make('created_at')
|
|
->form([
|
|
Forms\Components\DatePicker::make('from')
|
|
->label('From Date'),
|
|
Forms\Components\DatePicker::make('until')
|
|
->label('Until Date'),
|
|
])
|
|
->query(function (Builder $query, array $data): Builder {
|
|
return $query
|
|
->when(
|
|
$data['from'],
|
|
fn (Builder $query, $date): Builder => $query->whereDate('created_at', '>=', $date),
|
|
)
|
|
->when(
|
|
$data['until'],
|
|
fn (Builder $query, $date): Builder => $query->whereDate('created_at', '<=', $date),
|
|
);
|
|
})
|
|
->indicateUsing(function (array $data): array {
|
|
$indicators = [];
|
|
if ($data['from'] ?? null) {
|
|
$indicators['from'] = 'From: ' . $data['from'];
|
|
}
|
|
if ($data['until'] ?? null) {
|
|
$indicators['until'] = 'Until: ' . $data['until'];
|
|
}
|
|
return $indicators;
|
|
}),
|
|
])
|
|
->actions([
|
|
Tables\Actions\ViewAction::make(),
|
|
])
|
|
->bulkActions([])
|
|
->poll('60s');
|
|
}
|
|
|
|
public static function infolist(Infolist $infolist): Infolist
|
|
{
|
|
return $infolist
|
|
->schema([
|
|
Infolists\Components\Section::make('Audit Details')
|
|
->schema([
|
|
Infolists\Components\Grid::make(3)
|
|
->schema([
|
|
Infolists\Components\TextEntry::make('created_at')
|
|
->label('Date/Time')
|
|
->dateTime('F j, Y H:i:s'),
|
|
|
|
Infolists\Components\TextEntry::make('user.name')
|
|
->label('User')
|
|
->default('System'),
|
|
|
|
Infolists\Components\TextEntry::make('event')
|
|
->label('Action')
|
|
->badge()
|
|
->color(fn (string $state): string => match ($state) {
|
|
'created' => 'success',
|
|
'updated' => 'warning',
|
|
'deleted' => 'danger',
|
|
'restored' => 'info',
|
|
default => 'gray',
|
|
})
|
|
->formatStateUsing(fn (string $state): string => ucfirst($state)),
|
|
]),
|
|
|
|
Infolists\Components\Grid::make(3)
|
|
->schema([
|
|
Infolists\Components\TextEntry::make('auditable_type')
|
|
->label('Model')
|
|
->formatStateUsing(fn (string $state): string => class_basename($state)),
|
|
|
|
Infolists\Components\TextEntry::make('auditable_id')
|
|
->label('Record ID'),
|
|
|
|
Infolists\Components\TextEntry::make('ip_address')
|
|
->label('IP Address')
|
|
->default('N/A'),
|
|
]),
|
|
|
|
Infolists\Components\TextEntry::make('url')
|
|
->label('URL')
|
|
->columnSpanFull(),
|
|
]),
|
|
|
|
Infolists\Components\Section::make('Changes')
|
|
->schema([
|
|
Infolists\Components\ViewEntry::make('changes')
|
|
->view('filament.infolists.entries.audit-changes')
|
|
->columnSpanFull(),
|
|
]),
|
|
]);
|
|
}
|
|
|
|
public static function getRelations(): array
|
|
{
|
|
return [];
|
|
}
|
|
|
|
public static function getPages(): array
|
|
{
|
|
return [
|
|
'index' => Pages\ListAudits::route('/'),
|
|
'view' => Pages\ViewAudit::route('/{record}'),
|
|
];
|
|
}
|
|
}
|