Add central Audit Trail viewer in Filament admin

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
This commit is contained in:
2026-03-11 09:10:38 +02:00
parent f903914c2b
commit e380721938
7 changed files with 451 additions and 1 deletions

111
src/app/Models/Audit.php Normal file
View File

@@ -0,0 +1,111 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Audit extends Model
{
protected $table = 'audits';
protected $guarded = [];
protected $casts = [
'old_values' => 'array',
'new_values' => 'array',
];
/**
* Get the user that performed the action.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Get the auditable model.
*/
public function auditable(): MorphTo
{
return $this->morphTo();
}
/**
* Get the event badge color.
*/
public function getEventColorAttribute(): string
{
return match ($this->event) {
'created' => 'success',
'updated' => 'warning',
'deleted' => 'danger',
'restored' => 'info',
default => 'gray',
};
}
/**
* Get a human-readable model name.
*/
public function getModelNameAttribute(): string
{
$class = class_basename($this->auditable_type);
return preg_replace('/(?<!^)[A-Z]/', ' $0', $class);
}
/**
* Get formatted changes for display.
*/
public function getFormattedChangesAttribute(): array
{
$changes = [];
$oldValues = $this->old_values ?? [];
$newValues = $this->new_values ?? [];
$allKeys = array_unique(array_merge(array_keys($oldValues), array_keys($newValues)));
foreach ($allKeys as $key) {
$old = $oldValues[$key] ?? null;
$new = $newValues[$key] ?? null;
if ($old !== $new) {
$changes[] = [
'field' => $key,
'old' => $this->formatValue($old),
'new' => $this->formatValue($new),
];
}
}
return $changes;
}
/**
* Format a value for display.
*/
protected function formatValue($value): string
{
if (is_null($value)) {
return '(empty)';
}
if (is_bool($value)) {
return $value ? 'Yes' : 'No';
}
if (is_array($value)) {
return json_encode($value);
}
$stringValue = (string) $value;
if (strlen($stringValue) > 100) {
return substr($stringValue, 0, 100) . '...';
}
return $stringValue;
}
}