From 97e7d8d6bdf3129abc052e6ee50765549270e918 Mon Sep 17 00:00:00 2001 From: Nasrul Hazim Bin Mohamad Date: Mon, 12 Feb 2024 20:39:54 +0800 Subject: [PATCH] Added Audit Trail --- .../Administration/AuditTrailController.php | 35 +++++++ app/Livewire/Datatable/AuditTrail.php | 97 +++++++++++++++++++ app/Models/Audit.php | 15 +++ app/Policies/AuditPolicy.php | 82 ++++++++++++++++ app/View/ActionColumn.php | 2 +- config/audit.php | 2 +- .../2022_03_25_164842_create_audits_table.php | 1 + .../audit-trail/index.blade.php | 14 +++ .../partials/datatable-actions.blade.php | 8 ++ .../audit-trail/partials/info.blade.php | 77 +++++++++++++++ .../audit-trail/partials/permission.blade.php | 0 .../audit-trail/partials/role.blade.php | 0 .../audit-trail/partials/table.blade.php | 41 ++++++++ .../administration/audit-trail/show.blade.php | 15 +++ resources/views/components/badge.blade.php | 20 ++++ resources/views/components/status.blade.php | 16 +++ resources/views/navigation-menu.blade.php | 20 +++- routes/web/admin.php | 21 +++- support/json.php | 14 +++ 19 files changed, 473 insertions(+), 7 deletions(-) create mode 100644 app/Http/Controllers/Administration/AuditTrailController.php create mode 100644 app/Livewire/Datatable/AuditTrail.php create mode 100644 app/Models/Audit.php create mode 100644 app/Policies/AuditPolicy.php create mode 100644 resources/views/administration/audit-trail/index.blade.php create mode 100644 resources/views/administration/audit-trail/partials/datatable-actions.blade.php create mode 100644 resources/views/administration/audit-trail/partials/info.blade.php create mode 100644 resources/views/administration/audit-trail/partials/permission.blade.php create mode 100644 resources/views/administration/audit-trail/partials/role.blade.php create mode 100644 resources/views/administration/audit-trail/partials/table.blade.php create mode 100644 resources/views/administration/audit-trail/show.blade.php create mode 100644 resources/views/components/badge.blade.php create mode 100644 resources/views/components/status.blade.php create mode 100644 support/json.php diff --git a/app/Http/Controllers/Administration/AuditTrailController.php b/app/Http/Controllers/Administration/AuditTrailController.php new file mode 100644 index 0000000..9c8c6cc --- /dev/null +++ b/app/Http/Controllers/Administration/AuditTrailController.php @@ -0,0 +1,35 @@ +authorize('viewAny', config('audit.implementation')); + + $sub = 'Audit trail log'; + + return view('administration.audit-trail.index', compact('sub')); + } + + /** + * Handle the incoming request. + */ + public function show(Request $request, string $uuid) + { + $audit = config('audit.implementation')::whereUuid($uuid)->firstOrFail(); + + $this->authorize('view', $audit); + + $sub = 'Audit trail details'; + + return view('administration.audit-trail.show', compact('audit', 'sub')); + } +} diff --git a/app/Livewire/Datatable/AuditTrail.php b/app/Livewire/Datatable/AuditTrail.php new file mode 100644 index 0000000..4e46b34 --- /dev/null +++ b/app/Livewire/Datatable/AuditTrail.php @@ -0,0 +1,97 @@ +setPrimaryKey('uuid') + ->setEagerLoadAllRelationsEnabled() + ->setAdditionalSelects([ + 'audits.user_id', + 'audits.auditable_id', + 'audits.old_values', + 'audits.new_values', + 'audits.created_at', + ]) + ->setDefaultSort('created_at', 'desc') + ->setFilterLayoutSlideDown(); + } + + public function columns(): array + { + return [ + Column::make('Event', 'event') + ->view('components.badge') + ->searchable() + ->sortable(), + Column::make('User', 'user.name') + ->searchable() + ->sortable(), + Column::make('IP Address', 'ip_address') + ->searchable() + ->sortable(), + Column::make('Created Date', 'created_at') + ->format(fn ($value) => Carbon::parse($value)->format('d-m-Y H:i:s')) + ->searchable() + ->sortable(), + Column::make('URL', 'url') + ->format(fn ($value) => str_replace(url('/'), '', $value)) + ->searchable() + ->sortable(), + Column::make('Type', 'auditable_type') + ->format(fn ($value) => class_basename($value)) + ->searchable() + ->sortable(), + ActionColumn::make('Actions', 'uuid') + ->form('') + ->setView('administration.audit-trail.partials.datatable-actions'), + ]; + } + + /** + * The base query. + */ + public function builder(): Builder + { + return Model::query(); + } + + public function filters(): array + { + return [ + SelectFilter::make('Event') + ->options([ + 'created' => 'Created', + 'updated' => 'Updated', + 'deleted' => 'Deleted', + ]) + ->filter(function (Builder $builder, $value) { + $builder->where('event', $value); + }), + SelectFilter::make('Type') + ->options(audit_type_options()) + ->filter(function (Builder $builder, $value) { + $builder->where('auditable_type', $value); + }), + DateFilter::make('Date From', 'from') + ->filter(function (Builder $builder, string $value) { + $builder->whereDate('audits.created_at', '>=', $value); + })->setFilterSlidedownColspan('2'), + DateFilter::make('Date To', 'to') + ->filter(function (Builder $builder, string $value) { + $builder->whereDate('audits.created_at', '<=', $value); + })->setFilterSlidedownColspan('2'), + ]; + } +} diff --git a/app/Models/Audit.php b/app/Models/Audit.php new file mode 100644 index 0000000..963b5c7 --- /dev/null +++ b/app/Models/Audit.php @@ -0,0 +1,15 @@ +belongsTo(User::class, 'user_id', 'id'); + } +} diff --git a/app/Policies/AuditPolicy.php b/app/Policies/AuditPolicy.php new file mode 100644 index 0000000..ba2344e --- /dev/null +++ b/app/Policies/AuditPolicy.php @@ -0,0 +1,82 @@ +user()->can('view-audit-administration'); + } + + /** + * Determine whether the user can view the model. + * + * @return \Illuminate\Auth\Access\Response|bool + */ + public function view(User $user, Audit $audit) + { + return auth()->user()->can('view-audit-administration'); + } + + /** + * Determine whether the user can create models. + * + * @return \Illuminate\Auth\Access\Response|bool + */ + public function create(User $user) + { + return false; + } + + /** + * Determine whether the user can update the model. + * + * @return \Illuminate\Auth\Access\Response|bool + */ + public function update(User $user, Audit $audit) + { + return false; + } + + /** + * Determine whether the user can delete the model. + * + * @return \Illuminate\Auth\Access\Response|bool + */ + public function delete(User $user, Audit $audit) + { + return false; + } + + /** + * Determine whether the user can restore the model. + * + * @return \Illuminate\Auth\Access\Response|bool + */ + public function restore(User $user, Audit $audit) + { + return false; + } + + /** + * Determine whether the user can permanently delete the model. + * + * @return \Illuminate\Auth\Access\Response|bool + */ + public function forceDelete(User $user, Audit $audit) + { + return false; + } +} diff --git a/app/View/ActionColumn.php b/app/View/ActionColumn.php index 795cd6e..d5d4fd4 100644 --- a/app/View/ActionColumn.php +++ b/app/View/ActionColumn.php @@ -16,7 +16,7 @@ public function form(string $form): self return $this; } - public function setView($view) + public function setView($view): self { $this->view = $view; diff --git a/config/audit.php b/config/audit.php index 321d606..287d22e 100644 --- a/config/audit.php +++ b/config/audit.php @@ -13,7 +13,7 @@ | */ - 'implementation' => OwenIt\Auditing\Models\Audit::class, + 'implementation' => App\Models\Audit::class, /* |-------------------------------------------------------------------------- diff --git a/database/migrations/2022_03_25_164842_create_audits_table.php b/database/migrations/2022_03_25_164842_create_audits_table.php index cafce9d..06e8d46 100644 --- a/database/migrations/2022_03_25_164842_create_audits_table.php +++ b/database/migrations/2022_03_25_164842_create_audits_table.php @@ -13,6 +13,7 @@ public function up(): void { Schema::create('audits', function (Blueprint $table) { $table->bigIncrements('id'); + $table->uuid('uuid'); $table->string('user_type')->nullable(); $table->unsignedBigInteger('user_id')->nullable(); $table->string('event'); diff --git a/resources/views/administration/audit-trail/index.blade.php b/resources/views/administration/audit-trail/index.blade.php new file mode 100644 index 0000000..4b5f3f2 --- /dev/null +++ b/resources/views/administration/audit-trail/index.blade.php @@ -0,0 +1,14 @@ + + +

+ {{ __('Audit Trail') }} +

+
{{ $sub }}
+
+ +
+
+ @livewire('datatable.audit-trail') +
+
+
diff --git a/resources/views/administration/audit-trail/partials/datatable-actions.blade.php b/resources/views/administration/audit-trail/partials/datatable-actions.blade.php new file mode 100644 index 0000000..61d02e3 --- /dev/null +++ b/resources/views/administration/audit-trail/partials/datatable-actions.blade.php @@ -0,0 +1,8 @@ +
+ @can('view', $row) + + + + + @endcan +
diff --git a/resources/views/administration/audit-trail/partials/info.blade.php b/resources/views/administration/audit-trail/partials/info.blade.php new file mode 100644 index 0000000..cfc735c --- /dev/null +++ b/resources/views/administration/audit-trail/partials/info.blade.php @@ -0,0 +1,77 @@ +
+
+
+

+ Audit for {{ str(class_basename($audit->auditable_type))->headline() }} + +

+
+
+ + {{ $audit->ip_address }} +
+
+
+
+ + {{ $audit->url }} +
+
+
+
+ + {{ $audit->user_agent }} +
+
+
+
+ + {{ $audit->created_at->format('Y-m-d H:i:s') }} +
+
+
+ @if ($audit->user) +
+
+ +
+
+ {{ $audit->user->name }} +
+ {{ $audit->user->email }} +
+
+ @endif +
+
+ +
+
+
+

+ Before +

+
+
+ + @include('administration.audit-trail.partials.table', ['values' => $audit->old_values]) +
+ +
+
+
+

+ After +

+
+
+ + @include('administration.audit-trail.partials.table', ['values' => $audit->new_values]) +
diff --git a/resources/views/administration/audit-trail/partials/permission.blade.php b/resources/views/administration/audit-trail/partials/permission.blade.php new file mode 100644 index 0000000..e69de29 diff --git a/resources/views/administration/audit-trail/partials/role.blade.php b/resources/views/administration/audit-trail/partials/role.blade.php new file mode 100644 index 0000000..e69de29 diff --git a/resources/views/administration/audit-trail/partials/table.blade.php b/resources/views/administration/audit-trail/partials/table.blade.php new file mode 100644 index 0000000..3bffa39 --- /dev/null +++ b/resources/views/administration/audit-trail/partials/table.blade.php @@ -0,0 +1,41 @@ + + + + + + + + + @forelse ($values as $key => $value) + @if(! str($key)->contains('id', '_id', 'uuid')) + + + + + @endif + @empty + + + + @endforelse + +
+ Field + Value
{{ strtoupper(str($key)->headline()) }} + @if(is_valid_json($value)) + @include('administration.audit-trail.partials.table', ['values' => json_decode($value, JSON_OBJECT_AS_ARRAY)]) + @else + @if(str($key)->contains('is_')) + + @else + @if(is_array($value)) + @include('administration.audit-trail.partials.table', ['values' => $value]) + @else + 80) x-data x-tooltip="{{ $value }}" @endif>{{ $value }} + @endif + @endif + @endif +
{{ __('No information available.') }}
diff --git a/resources/views/administration/audit-trail/show.blade.php b/resources/views/administration/audit-trail/show.blade.php new file mode 100644 index 0000000..e591a81 --- /dev/null +++ b/resources/views/administration/audit-trail/show.blade.php @@ -0,0 +1,15 @@ + + +

+ {{ __('Audit Details') }} +

+
{{ $sub }}
+
+ +
+
+ @include('administration.audit-trail.partials.info') +
+
+ +
diff --git a/resources/views/components/badge.blade.php b/resources/views/components/badge.blade.php new file mode 100644 index 0000000..557c897 --- /dev/null +++ b/resources/views/components/badge.blade.php @@ -0,0 +1,20 @@ +@props(['type' => 'created', 'label' => 'Created']) +@php + if(isset($row)) { + $type = $row->event; + $label = strtoupper($row->event); + } + + $color = match($type) { + 'created' => 'fill-green-500', + 'updated' => 'fill-blue-500', + 'deleted' => 'fill-red-500', + }; +@endphp + + + + {{ $label ?? '' }} + diff --git a/resources/views/components/status.blade.php b/resources/views/components/status.blade.php new file mode 100644 index 0000000..ec8bf0c --- /dev/null +++ b/resources/views/components/status.blade.php @@ -0,0 +1,16 @@ +@props([ + 'tooltip' => 'Status', + 'condition' => false, + 'okIcon' => 'o-check', + 'okClass' => 'text-green-500 border-green-500', + 'falseIcon' => 'o-x', + 'falseClass' => 'text-red-500 border-red-500', + 'okLabel' => 'Active', + 'falseLabel' => 'Inactive', + 'labelClass' => 'ml-2 text-sm', +]) +
+ + {{ $condition ? $okLabel : $falseLabel }} +
diff --git a/resources/views/navigation-menu.blade.php b/resources/views/navigation-menu.blade.php index 59c1e2b..b68c3ce 100644 --- a/resources/views/navigation-menu.blade.php +++ b/resources/views/navigation-menu.blade.php @@ -61,7 +61,13 @@ class="inline-flex items-center px-3 py-2 border border-transparent text-sm lead {{ __('Users') }} - {{-- @endcan --}} + {{-- @endcan --}} + + {{-- @can('viewAudit') --}} + + {{ __('Audit Trail') }} + + {{-- @endcan --}} @@ -307,6 +313,18 @@ class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 dark {{ __('Access Control') }} @endcan + + {{-- @can('viewUser') --}} + + {{ __('User') }} + + {{-- @endcan --}} + + {{-- @can('viewAudit') --}} + + {{ __('Audit Trail') }} + + {{-- @endcan --}} @endcan diff --git a/routes/web/admin.php b/routes/web/admin.php index 5e3afc9..8559659 100644 --- a/routes/web/admin.php +++ b/routes/web/admin.php @@ -1,13 +1,26 @@ as('admin.')->prefix('admin')->group(function () { - Route::get('access-control', [AccessControlController::class, 'index'])->name('access-control.index'); - Route::get('access-control/{uuid}', [AccessControlController::class, 'show'])->name('access-control.show'); + // Access Control + Route::get('access-control', [AccessControlController::class, 'index']) + ->name('access-control.index'); + Route::get('access-control/{uuid}', [AccessControlController::class, 'show']) + ->name('access-control.show'); - Route::get('users', [UserController::class, 'index'])->name('users.index'); - Route::get('users/{uuid}', [UserController::class, 'show'])->name('users.show'); + // User Management + Route::get('users', [UserController::class, 'index']) + ->name('users.index'); + Route::get('users/{uuid}', [UserController::class, 'show']) + ->name('users.show'); + + // Audit Trail + Route::get('audit-trail', [AuditTrailController::class, 'index']) + ->name('audit-trail.index'); + Route::get('audit-trail/{uuid}', [AuditTrailController::class, 'show']) + ->name('audit-trail.show'); }); diff --git a/support/json.php b/support/json.php new file mode 100644 index 0000000..22f7e87 --- /dev/null +++ b/support/json.php @@ -0,0 +1,14 @@ +