Latest Posts

Integrating audit logs into your FilamentPHP admin panel

Tutorials

Following on from my recent introduction to my auditing package, I'm going to show how quick and easy it is to implement an audit log viewer into FilamentPHP admin panel


What is FilamentPHP?

If you haven't already heard about it, FilamentPHP is an awesome open source admin panel that runs on AlpineJS and Livewire. I've recently switched to using it extensively, because it makes developing admin panels in particular a quick and painless process!

The end result of this short tutorial will be the below:

Audit logs within FilamentPHP

Install FilamentPHP

This post won't be so much about how to use FilamentPHP, but just our implementation of Audit Logs. So for the purpose of this, I'll assume you have already got FilamentPHP installed.

So if you haven't yet installed and configured FilamentPHP for your project, I'd recommend reading their documentation.

Install the Simple Laravel Auditor package

If you haven't already, get the auditor package installed:

composer require motomedialab/simple-laravel-audit

php artisan migrate

Creating the resource

By default, Filament resources are listed under app/Filament/Resources and for now we'll stick with that.

Inside this folder, create your class called AuditLogResource. I'll also assume that your project has just one User model namespaced under App\Models\User.

We need to create just two files:

app/Filament/Resources/AuditLogResource.php

<?php

namespace App\Filament\Resources;

use App\Models\User;
use Filament\Tables;
use App\Enums\AccessLevel;
use Filament\Tables\Table;
use Filament\Resources\Resource;
use Filament\Support\Enums\MaxWidth;
use Illuminate\Database\Eloquent\Model;
use Filament\Infolists\Components\Section;
use Filament\Infolists\Components\TextEntry;
use Filament\Infolists\Components\KeyValueEntry;
use App\Filament\Resources\AuditLogResource\Pages;

class AuditLogResource extends Resource
{
    protected static ?string $navigationIcon = 'heroicon-o-exclamation-triangle';

    public static function getModel(): string
    {
        return config('simple-auditor.model');
    }

    public static function canViewAny(): bool
    {
			  // ToDo: determine whether your user should have access
			  return true;
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                Tables\Columns\TextColumn::make('id')
                    ->label('ID')
                    ->numeric(),

                Tables\Columns\TextColumn::make('message')
                    ->searchable()
                    ->sortable(),

                Tables\Columns\TextColumn::make('user')
                    ->sortable()
                    ->toggleable()
                    ->placeholder('-')
                    ->state(fn (Model $model) => $model->user_id ? User::find($model->user_id)?->name : null),

                Tables\Columns\TextColumn::make('ip_address')
                    ->toggleable()
                    ->searchable()
                    ->label('IP Address')
                    ->placeholder('-'),

                Tables\Columns\TextColumn::make('created_at')
                    ->toggleable()
                    ->sortable()
                    ->label('Date')
                    ->dateTime(),
            ])
            ->actions([
                Tables\Actions\ViewAction::make()
                    ->modalWidth(MaxWidth::Large)
                    ->infolist([

                        Section::make()
                            ->columns(2)
                            ->schema([

                                TextEntry::make('message'),

                                TextEntry::make('user')
                                    ->placeholder('-')
                                    ->state(
                                        fn (Model $model) => $model->user_id ? User::find($model->user_id)?->name : null
                                    ),

                                TextEntry::make('ip_address')
                                    ->label('IP Address')
                                    ->placeholder('-'),

                                TextEntry::make('created_at')
                                    ->dateTime(),
                            ]),

                        KeyValueEntry::make('context')
                            ->label('Additional data')
                            ->hidden(fn ($state) => empty($state))
                            ->placeholder('-'),
                    ])
            ])
            ->paginationPageOptions(['10', '25', '50', '100'])
            ->defaultSort('created_at', 'desc');
    }

    public static function getPages(): array
    {
        return [
            'index' => Pages\ListAuditLogs::route('/'),
        ];
    }
}

This first file is a standard Filament resource file and lays out our structure. For the purpose of the audit logs, we don't need any forms, just a way to view each log and associated details.

app/Filament/Resources/AuditLogResource/Pages/ListAuditLogs.php

<?php

namespace App\Filament\Resources\AuditLogResource\Pages;

use App\Filament\Resources\AuditLogResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;

class ListAuditLogs extends ListRecords
{
    protected static string $resource = AuditLogResource::class;
}

And the above is just boilerplate so Filament knows what the main index page should be.

The table is configured in such a way that you can see each record in a row on the table and view each record within a popup modal. In addition there is searching, toggling and configured pagination. I have limited the pagination to 100 records per page, because audit tables can get quite large!

And with that, you have a complete Filament resource that you can see within your FilamentPHP admin panel. It will show a list of all audit logs, and allow you to perform basic searching and sorting.

I think it's fair to say that Filament is extremely powerful.

Configure your auditing

At this point, chances are you don't have any data in your table. If you don't, I'd recommend having a quick read of my previous article here

If you enjoyed this article, please consider subscribing!