diff --git a/.gitignore b/.gitignore index 309a4ae322..a4e724e526 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,7 @@ graphify-out # Scribe (auto-generated, contains auth tokens) .scribe/ +specs/ +.specify/ + +AGENTS.md \ No newline at end of file diff --git a/app/Http/Controllers/Setting/LogAktivitasController.php b/app/Http/Controllers/Setting/LogAktivitasController.php new file mode 100644 index 0000000000..30e63c4cc2 --- /dev/null +++ b/app/Http/Controllers/Setting/LogAktivitasController.php @@ -0,0 +1,17 @@ + auth()->user() ? auth()->user()->name : 'Sistem', + 'exit_code' => $exitCode, + ]); return response()->json([ 'success' => false, @@ -88,10 +89,16 @@ public function createBackup() } Log::info('Ending backup process.'); + ActivityLogService::log('backup database', 'Proses backup database berhasil.', [ + 'user_name' => auth()->user() ? auth()->user()->name : 'Sistem', + ]); return response()->json(['success' => true, 'message' => 'Backup completed successfully']); } catch (\Exception $e) { Log::error('Backup process failed: ' . $e->getMessage(), ['exception' => $e]); + ActivityLogService::logFailed('backup database gagal', 'Proses backup database gagal: ' . $e->getMessage(), [ + 'user_name' => auth()->user() ? auth()->user()->name : 'Sistem', + ]); return response()->json(['success' => false, 'message' => 'Backup process failed', 'error' => $e->getMessage()], 500); } @@ -103,6 +110,11 @@ public function downloadBackup($file) $filePath = "{$this->destination}/{$file}"; if ($disk->exists($filePath)) { + ActivityLogService::log('unduh backup database', "Mengunduh file backup: {$file}", [ + 'user_name' => auth()->user() ? auth()->user()->name : 'Sistem', + 'file' => $file, + ]); + return $disk->download($filePath); } @@ -116,32 +128,17 @@ public function deleteBackup($file) if ($disk->exists($filePath)) { $disk->delete($filePath); + ActivityLogService::log('hapus backup database', "Menghapus file backup: {$file}", [ + 'user_name' => auth()->user() ? auth()->user()->name : 'Sistem', + 'file' => $file, + ]); + return redirect()->route('setting.pengaturan-database.backup')->with('success', 'Backup berhasil dihapus'); } return redirect()->route('setting.pengaturan-database.backup')->with('error', 'Backup tidak ditemukan'); } - /** - * Fungsi untuk format ukuran file - */ - private function formatSizeUnits($bytes) - { - if ($bytes >= 1073741824) { - return number_format($bytes / 1073741824, 2) . ' GB'; - } elseif ($bytes >= 1048576) { - return number_format($bytes / 1048576, 2) . ' MB'; - } elseif ($bytes >= 1024) { - return number_format($bytes / 1024, 2) . ' KB'; - } elseif ($bytes > 1) { - return $bytes . ' bytes'; - } elseif ($bytes == 1) { - return $bytes . ' byte'; - } else { - return '0 bytes'; - } - } - // RESTORE DATABASE public function restoreDatabase() @@ -152,7 +149,6 @@ public function restoreDatabase() return view('setting.pengaturan_database.table-restore', compact('page_title', 'page_description')); } - public function restoreBackup(Request $request) { $request->validate([ @@ -179,7 +175,7 @@ public function restoreBackup(Request $request) $filename = basename($path); $allowedChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-'; - if (! preg_match("/^[" . $allowedChars . "]+$/", $filename)) { + if (! preg_match('/^[' . $allowedChars . ']+$/', $filename)) { return response()->json([ 'success' => false, 'message' => 'Nama file tidak valid. Hanya boleh mengandung huruf (a-z/A-Z), angka (0-9), titik (.), dan garis bawah (_).', @@ -209,12 +205,19 @@ public function restoreBackup(Request $request) Log::info('Restore from ZIP: ' . $finalPath); $result = $this->restoreFromZip($finalPath); + ActivityLogService::log('restore database', "Restore database berhasil. {$result['files_restored']} file asset dipulihkan.", [ + 'user_name' => auth()->user() ? auth()->user()->name : 'Sistem', + ]); + return response()->json([ 'success' => true, 'message' => "Restore berhasil. Database dan {$result['files_restored']} file asset telah dipulihkan.", ], 200); } catch (\Exception $e) { Log::error('Restore error: ' . $e->getMessage()); + ActivityLogService::logFailed('restore database gagal', 'Restore database gagal: ' . $e->getMessage(), [ + 'user_name' => auth()->user() ? auth()->user()->name : 'Sistem', + ]); return response()->json([ 'success' => false, @@ -226,6 +229,30 @@ public function restoreBackup(Request $request) } } + /** + * Fungsi untuk format ukuran file. + */ + private function formatSizeUnits($bytes) + { + if ($bytes >= 1073741824) { + return number_format($bytes / 1073741824, 2) . ' GB'; + } + if ($bytes >= 1048576) { + return number_format($bytes / 1048576, 2) . ' MB'; + } + if ($bytes >= 1024) { + return number_format($bytes / 1024, 2) . ' KB'; + } + if ($bytes > 1) { + return $bytes . ' bytes'; + } + if ($bytes == 1) { + return $bytes . ' byte'; + } + return '0 bytes'; + + } + private function runRestoreDatabase($sqlFilePath) { // Ambil konfigurasi database diff --git a/app/Http/Livewire/Pengaturan/LogAktivitasTable.php b/app/Http/Livewire/Pengaturan/LogAktivitasTable.php new file mode 100644 index 0000000000..b3999b79fb --- /dev/null +++ b/app/Http/Livewire/Pengaturan/LogAktivitasTable.php @@ -0,0 +1,93 @@ + ['except' => ''], + 'dateTo' => ['except' => ''], + 'userId' => ['except' => ''], + 'event' => ['except' => ''], + 'keyword' => ['except' => ''], + ]; + + public function mount(): void + { + $this->dateFrom = now()->subDays(30)->format('Y-m-d'); + $this->dateTo = now()->format('Y-m-d'); + } + + public function render() + { + $activities = app(ActivityLogService::class) + ->getFilteredActivities($this->dateFrom, $this->dateTo, $this->userId, $this->event, $this->keyword) + ->latest() + ->paginate(25); + + $users = \App\Models\User::orderBy('name')->get(); + $events = Activity::select('event')->distinct()->pluck('event'); + + return view('livewire.pengaturan.log-aktivitas-table', compact('activities', 'users', 'events')); + } + + public function showDetail(int $id): void + { + $this->selectedActivityId = $id; + $this->selectedActivity = Activity::with('causer')->findOrFail($id); + $this->showModal = true; + } + + public function closeDetail(): void + { + $this->selectedActivityId = null; + $this->selectedActivity = null; + $this->showModal = false; + } + + /** + * Cek apakah aktivitas merupakan kegagalan (failed login atau error). + */ + public function isActivityFailed($activity): bool + { + // Cek flag 'failed' di properties + if (isset($activity->properties['failed']) && $activity->properties['failed']) { + return true; + } + + // Cek nama event yang mengandung kata 'gagal' + if (in_array(strtolower($activity->event), ['login gagal', 'gagal'])) { + return true; + } + + return false; + } + + public function resetFilters(): void + { + $this->dateFrom = now()->subDays(30)->format('Y-m-d'); + $this->dateTo = now()->format('Y-m-d'); + $this->userId = ''; + $this->event = ''; + $this->keyword = ''; + $this->closeDetail(); + } +} diff --git a/app/Models/DataUmum.php b/app/Models/DataUmum.php index d06c3e0548..aa992ad6ef 100644 --- a/app/Models/DataUmum.php +++ b/app/Models/DataUmum.php @@ -31,12 +31,14 @@ namespace App\Models; -use Illuminate\Database\Eloquent\Model; +use App\Observers\DataUmumObserver; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; class DataUmum extends Model { use HasFactory; + // Attributes protected $table = 'das_data_umum'; @@ -114,4 +116,9 @@ public function setPathAttribute($value) $this->attributes['path'] = $value; } + + protected static function booted(): void + { + static::observe(DataUmumObserver::class); + } } diff --git a/app/Models/Komplain.php b/app/Models/Komplain.php index d0b9e19fa5..b96491ddbf 100644 --- a/app/Models/Komplain.php +++ b/app/Models/Komplain.php @@ -31,12 +31,14 @@ namespace App\Models; +use App\Observers\KomplainObserver; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Komplain extends Model { use HasFactory; + protected $table = 'das_komplain'; protected $fillable = [ @@ -76,12 +78,17 @@ public static function generateID() $id = mt_rand(100000, 999999); $pid = ''; - if (! Komplain::where('komplain_id', '=', $id)->exists()) { + if (! self::where('komplain_id', '=', $id)->exists()) { $pid = $id; } else { - self::generateID(); + return self::generateID(); } return $pid; } + + protected static function booted(): void + { + static::observe(KomplainObserver::class); + } } diff --git a/app/Models/Profil.php b/app/Models/Profil.php index 1efeb48090..ed14190359 100644 --- a/app/Models/Profil.php +++ b/app/Models/Profil.php @@ -31,8 +31,9 @@ namespace App\Models; -use Illuminate\Database\Eloquent\Model; +use App\Observers\ProfilObserver; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Cache; class Profil extends Model @@ -93,9 +94,9 @@ public function strukturOrganisasi() // return $this->hasMany(Penduduk::class, 'kecamatan_id', 'kecamatan_id')->where('status_dasar', 1); // } - protected static function boot() + protected static function booted(): void { - parent::boot(); + static::observe(ProfilObserver::class); static::saved(function () { Cache::forget('profil'); diff --git a/app/Models/SettingAplikasi.php b/app/Models/SettingAplikasi.php index 79267e6a2a..e4e9cf6184 100644 --- a/app/Models/SettingAplikasi.php +++ b/app/Models/SettingAplikasi.php @@ -31,6 +31,7 @@ namespace App\Models; +use App\Observers\SettingAplikasiObserver; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Cache; @@ -38,6 +39,9 @@ class SettingAplikasi extends Model { use HasFactory; + + public $timestamps = false; + protected $table = 'das_setting'; protected $fillable = [ @@ -49,18 +53,12 @@ class SettingAplikasi extends Model 'option', ]; - public $timestamps = false; - - protected static function boot() + protected static function booted(): void { - parent::boot(); + static::observe(SettingAplikasiObserver::class); static::saved(function () { Cache::forget('setting'); }); - - static::updated(function () { - Cache::forget('setting'); - }); } } diff --git a/app/Models/User.php b/app/Models/User.php index 1357697313..6affd3b780 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -60,11 +60,6 @@ class User extends Authenticatable implements JWTSubject */ public const DEFAULT_PASSWORD = '12345678'; - protected static function booted(): void - { - static::observe(UserObserver::class); - } - /** * Field yang diizinkan untuk mass assignment. * @@ -155,7 +150,7 @@ public function scopeSuspend($query, $email) } /** - * Hash password + * Hash password. */ public function setPasswordAttribute($input) { @@ -200,4 +195,9 @@ public function passwordHistories() { return $this->hasMany(PasswordHistory::class); } + + protected static function booted(): void + { + static::observe(UserObserver::class); + } } diff --git a/app/Observers/DataUmumObserver.php b/app/Observers/DataUmumObserver.php new file mode 100644 index 0000000000..038bd1f9fd --- /dev/null +++ b/app/Observers/DataUmumObserver.php @@ -0,0 +1,60 @@ +wasChanged()) { + $changed = $dataUmum->getChanges(); + ActivityLogService::logAttributeChange( + 'insert data umum', + 'created', + auth()->id() ?? null, + $changed + ); + } + } + + public function updating(DataUmum $dataUmum): void + { + // About to update + } + + public function updated(DataUmum $dataUmum): void + { + if ($dataUmum->wasChanged()) { + $changed = $dataUmum->getChanges(); + ActivityLogService::logAttributeChange( + 'ubah data umum', + 'updated', + auth()->id() ?? null, + $changed + ); + } + } + + public function deleting(DataUmum $dataUmum): void + { + // About to delete + } + + public function deleted(DataUmum $dataUmum): void + { + ActivityLogService::logAttributeChange( + 'hapus data umum', + 'deleted', + auth()->id() ?? null, + $dataUmum->getOriginal() + ); + } +} \ No newline at end of file diff --git a/app/Observers/KomplainObserver.php b/app/Observers/KomplainObserver.php new file mode 100644 index 0000000000..2f46b24ff1 --- /dev/null +++ b/app/Observers/KomplainObserver.php @@ -0,0 +1,60 @@ +wasChanged()) { + $changed = $komplain->getChanges(); + ActivityLogService::logAttributeChange( + 'insert komplain', + 'created', + auth()->id() ?? null, + $changed + ); + } + } + + public function updating(Komplain $komplain): void + { + // About to update + } + + public function updated(Komplain $komplain): void + { + if ($komplain->wasChanged()) { + $changed = $komplain->getChanges(); + ActivityLogService::logAttributeChange( + 'ubah komplain', + 'updated', + auth()->id() ?? null, + $changed + ); + } + } + + public function deleting(Komplain $komplain): void + { + // About to delete + } + + public function deleted(Komplain $komplain): void + { + ActivityLogService::logAttributeChange( + 'hapus komplain', + 'deleted', + auth()->id() ?? null, + $komplain->getOriginal() + ); + } +} \ No newline at end of file diff --git a/app/Observers/ProfilObserver.php b/app/Observers/ProfilObserver.php new file mode 100644 index 0000000000..5192ceb050 --- /dev/null +++ b/app/Observers/ProfilObserver.php @@ -0,0 +1,60 @@ +wasChanged()) { + $changed = $profil->getChanges(); + ActivityLogService::logAttributeChange( + 'insert profil', + 'created', + auth()->id() ?? null, + $changed + ); + } + } + + public function updating(Profil $profil): void + { + // About to update + } + + public function updated(Profil $profil): void + { + if ($profil->wasChanged()) { + $changed = $profil->getChanges(); + ActivityLogService::logAttributeChange( + 'ubah profil', + 'updated', + auth()->id() ?? null, + $changed + ); + } + } + + public function deleting(Profil $profil): void + { + // About to delete + } + + public function deleted(Profil $profil): void + { + ActivityLogService::logAttributeChange( + 'hapus profil', + 'deleted', + auth()->id() ?? null, + $profil->getOriginal() + ); + } +} \ No newline at end of file diff --git a/app/Observers/SettingAplikasiObserver.php b/app/Observers/SettingAplikasiObserver.php new file mode 100644 index 0000000000..622a943af5 --- /dev/null +++ b/app/Observers/SettingAplikasiObserver.php @@ -0,0 +1,67 @@ +wasChanged()) { + $changed = $setting->getChanges(); + ActivityLogService::logAttributeChange( + 'tambah pengaturan aplikasi', + 'created', + auth()->id() ?? null, + $changed, + subject: $setting, + causer: auth()->user() + ); + } + } + + public function updating(SettingAplikasi $setting): void + { + // About to update + } + + public function updated(SettingAplikasi $setting): void + { + if ($setting->wasChanged()) { + $changed = $setting->getChanges(); + ActivityLogService::logAttributeChange( + 'ubah pengaturan aplikasi', + 'updated', + auth()->id() ?? null, + $changed, + subject: $setting, + causer: auth()->user() + ); + } + } + + public function deleting(SettingAplikasi $setting): void + { + // About to delete + } + + public function deleted(SettingAplikasi $setting): void + { + ActivityLogService::logAttributeChange( + 'hapus pengaturan aplikasi', + 'deleted', + auth()->id() ?? null, + $setting->getOriginal(), + subject: $setting, + causer: auth()->user() + ); + } +} diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index f80c06b1b1..3517d1d71c 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -3,10 +3,29 @@ namespace App\Observers; use App\Models\User; +use App\Services\ActivityLogService; use App\Services\PasswordHistoryService; class UserObserver { + public function creating(User $user): void + { + // New user creation + } + + public function created(User $user): void + { + if ($user->wasChanged()) { + $changed = $user->getChanges(); + ActivityLogService::logAttributeChange( + 'tambah pengguna', + 'created', + auth()->id() ?? null, + $changed + ); + } + } + public function saving(User $user): void { if ($user->isDirty('password') && $user->getOriginal('password')) { @@ -16,10 +35,40 @@ public function saving(User $user): void } } - public function saved(User $user): void + public function updating(User $user): void + { + // About to update + } + + public function updated(User $user): void { + if ($user->wasChanged()) { + $changed = $user->getChanges(); + ActivityLogService::logAttributeChange( + 'ubah pengguna', + 'updated', + auth()->id() ?? null, + $changed + ); + } + if ($user->wasChanged('password')) { app(PasswordHistoryService::class)->prune($user); } } -} + + public function deleting(User $user): void + { + // About to delete + } + + public function deleted(User $user): void + { + ActivityLogService::logAttributeChange( + 'suspend pengguna', + 'deleted', + auth()->id() ?? null, + $user->getOriginal() + ); + } +} \ No newline at end of file diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 253a57cdb4..a9f28e67ed 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -31,14 +31,19 @@ namespace App\Providers; +use App\Services\ActivityLogService; use App\Services\CacheService; use App\Support\Collection; +use Illuminate\Auth\Events\Failed; +use Illuminate\Auth\Events\Login; +use Illuminate\Auth\Events\Logout; +use Illuminate\Foundation\AliasLoader; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\Paginator; -use Illuminate\Foundation\AliasLoader; use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Validator; @@ -96,6 +101,40 @@ public function boot(): void return true; }); } + + // Auto-log login + Event::listen(Login::class, function (Login $event) { + $user = $event->user; + $userId = $user ? $user->id : null; + ActivityLogService::log('login', "User: {$user->name} (ID: {$user->id})", [ + 'user_name' => $user ? $user->name : 'Sistem', + 'user_id' => $userId, + 'event' => 'login', + ]); + }); + + // Auto-log logout + Event::listen(Logout::class, function (Logout $event) { + $user = $event->user; + $userId = $user ? $user->id : null; + ActivityLogService::log('logout', "User: {$user?->name} (ID: {$userId})", [ + 'user_name' => $user ? $user->name : 'Sistem', + 'user_id' => $userId, + 'event' => 'logout', + ]); + }); + + // Auto-log failed login + Event::listen(Failed::class, function (Failed $event) { + $user = $event->user; + $userId = $user ? $user->id : null; + ActivityLogService::logFailed('login gagal', "User: {$user?->name} (ID: {$user?->id}) login gagal", [ + 'user_name' => $user ? $user->name : 'Sistem', + 'user_id' => $userId, + 'event' => 'login gagal', + 'attempted_username' => $event->username ?? null, + ], $userId); + }); } /** @@ -223,7 +262,7 @@ protected function file(): void */ protected function paginate(): void { - /** + /* * Paginate a standard Laravel Collection. * * @param int $perPage diff --git a/app/Services/ActivityLogService.php b/app/Services/ActivityLogService.php new file mode 100644 index 0000000000..cbfeeecc4e --- /dev/null +++ b/app/Services/ActivityLogService.php @@ -0,0 +1,133 @@ +whereDate('created_at', '>=', $dateFrom); + } + + if ($dateTo) { + $query->whereDate('created_at', '<=', $dateTo); + } + + if ($userId) { + $query->where('causer_id', (int) $userId); + } + + if ($event) { + $query->where('event', $event); + } + + if ($keyword) { + $query->where(function ($q) use ($keyword) { + $q->where('description', 'like', "%{$keyword}%") + ->orWhere('properties->url_slug', 'like', "%{$keyword}%"); + }); + } + + return $query; + } + + public static function log(string $event, ?string $description = null, ?array $properties = []): ?Activity + { + if (! self::isActivityLogAvailable()) { + return null; + } + + $request = request(); + $properties = array_merge($properties ?? [], [ + 'ip_address' => $request ? $request->ip() : null, + 'user_agent' => $request ? $request->userAgent() : null, + 'url_slug' => $request ? $request->path() : null, + ]); + + return activity() + ->event($event) + ->withProperties($properties) + ->log($description ?? $event); + } + + public static function logFailed(string $event, ?string $description = null, ?array $properties = [], ?int $userId = null): ?Activity + { + if (! self::isActivityLogAvailable()) { + return null; + } + + $request = request(); + $properties = array_merge($properties ?? [], [ + 'ip_address' => $request ? $request->ip() : null, + 'user_agent' => $request ? $request->userAgent() : null, + 'url_slug' => $request ? $request->path() : null, + ]); + + if ($userId === null) { + $userId = $request ? ($request->user() ? $request->user()->id : null) : null; + } + + $properties['failed'] = true; + + return activity() + ->event($event) + ->withProperties($properties) + ->log($description ?? $event); + } + + public static function logAttributeChange(string $event, string $action, ?int $userId = null, array $changedAttributes = [], ?Model $subject = null, ?Model $causer = null): ?Activity + { + if (! self::isActivityLogAvailable()) { + return null; + } + + $request = request(); + $properties = [ + 'ip_address' => $request ? $request->ip() : null, + 'user_agent' => $request ? $request->userAgent() : null, + 'url_slug' => $request ? $request->path() : null, + 'action' => $action, + 'changed_attributes' => $changedAttributes, + ]; + + if ($userId === null) { + $userId = $request ? ($request->user() ? $request->user()->id : null) : null; + } + + $properties['user_id'] = $userId; + + $logger = activity() + ->event($event) + ->withProperties($properties); + + if ($subject) { + $logger->on($subject); + } + + if ($causer) { + $logger->causedBy($causer); + } + + return $logger->log($event); + } + + private static function isActivityLogAvailable(): bool + { + if (self::$activityLogAvailable !== null) { + return self::$activityLogAvailable; + } + + self::$activityLogAvailable = Schema::hasTable('activity_log'); + + return self::$activityLogAvailable; + } +} diff --git a/catatan_rilis.md b/catatan_rilis.md index f17a8c7472..5ee2f84821 100644 --- a/catatan_rilis.md +++ b/catatan_rilis.md @@ -5,6 +5,7 @@ Terimakasih [isi disini] yang telah berkontribusi langsung mengembangkan aplikas #### FITUR +1. [#1324](https://github.com/OpenSID/OpenDK/issues/1324) Penambahan fitur log aktivitas. #### BUG diff --git a/composer.json b/composer.json index 8977befef0..a6520fdfb2 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ "rap2hpoutre/laravel-log-viewer": "^3.0", "sentry/sentry-laravel": "^4.0", "simplepie/simplepie": "^1.0", + "spatie/laravel-activitylog": "^5.1", "spatie/laravel-backup": "^10.0", "spatie/laravel-fractal": "*", "spatie/laravel-html": "^3.2", diff --git a/composer.lock b/composer.lock index bd37c5fd43..2fe00f3a3e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "df56aba5c112793320edc63900fe707c", + "content-hash": "f5cc95c71f7062fe078645ed3419301c", "packages": [ { "name": "bensampo/laravel-enum", @@ -2445,20 +2445,20 @@ }, { "name": "laravel/framework", - "version": "v13.24.0", + "version": "v13.25.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "6d481710375d2aa67656922ef760cdd2b18bcfe0" + "reference": "ed36fe882bd4eed4e6ff75343cbad8dbda03fdba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/6d481710375d2aa67656922ef760cdd2b18bcfe0", - "reference": "6d481710375d2aa67656922ef760cdd2b18bcfe0", + "url": "https://api.github.com/repos/laravel/framework/zipball/ed36fe882bd4eed4e6ff75343cbad8dbda03fdba", + "reference": "ed36fe882bd4eed4e6ff75343cbad8dbda03fdba", "shasum": "" }, "require": { - "brick/math": "^0.14.2 || ^0.15 || ^0.16 || ^0.17 || ^0.18", + "brick/math": "^0.14.2 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19", "composer-runtime-api": "^2.2", "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.4", @@ -2668,7 +2668,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-08-04T15:54:59+00:00" + "time": "2026-08-11T13:56:22+00:00" }, { "name": "laravel/helpers", @@ -2985,16 +2985,16 @@ }, { "name": "league/commonmark", - "version": "2.9.2", + "version": "2.10.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "72e9a87efcf41a8e83be3ed0866b69d77565cb12" + "reference": "d2d1aa8b35e072966c89bc0c66cf926e56767dc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/72e9a87efcf41a8e83be3ed0866b69d77565cb12", - "reference": "72e9a87efcf41a8e83be3ed0866b69d77565cb12", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d2d1aa8b35e072966c89bc0c66cf926e56767dc4", + "reference": "d2d1aa8b35e072966c89bc0c66cf926e56767dc4", "shasum": "" }, "require": { @@ -3031,7 +3031,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.10-dev" + "dev-main": "2.11-dev" } }, "autoload": { @@ -3088,7 +3088,7 @@ "type": "tidelift" } ], - "time": "2026-08-11T00:58:45+00:00" + "time": "2026-08-11T16:06:25+00:00" }, { "name": "league/config", @@ -3690,16 +3690,16 @@ }, { "name": "maatwebsite/excel", - "version": "3.1.69", + "version": "3.1.70", "source": { "type": "git", "url": "https://github.com/SpartnerNL/Laravel-Excel.git", - "reference": "ae5d65b7c9a2fac43bff4d44f796ac95d7a8e760" + "reference": "b5cafdfcf7ec63924e83303763be8fcae340f70b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/ae5d65b7c9a2fac43bff4d44f796ac95d7a8e760", - "reference": "ae5d65b7c9a2fac43bff4d44f796ac95d7a8e760", + "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/b5cafdfcf7ec63924e83303763be8fcae340f70b", + "reference": "b5cafdfcf7ec63924e83303763be8fcae340f70b", "shasum": "" }, "require": { @@ -3707,7 +3707,7 @@ "ext-json": "*", "illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0||^12.0||^13.0", "php": "^7.0||^8.0", - "phpoffice/phpspreadsheet": "^1.30.4", + "phpoffice/phpspreadsheet": "^1.30.5", "psr/simple-cache": "^1.0||^2.0||^3.0" }, "require-dev": { @@ -3755,7 +3755,7 @@ ], "support": { "issues": "https://github.com/SpartnerNL/Laravel-Excel/issues", - "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.69" + "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.70" }, "funding": [ { @@ -3767,7 +3767,7 @@ "type": "github" } ], - "time": "2026-04-30T20:03:58+00:00" + "time": "2026-08-13T09:11:54+00:00" }, { "name": "maennchen/zipstream-php", @@ -4352,16 +4352,16 @@ }, { "name": "nette/schema", - "version": "v1.3.5", + "version": "v1.3.6", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002" + "reference": "c54350438cd6914616f790a49cb424605f421562" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002", - "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002", + "url": "https://api.github.com/repos/nette/schema/zipball/c54350438cd6914616f790a49cb424605f421562", + "reference": "c54350438cd6914616f790a49cb424605f421562", "shasum": "" }, "require": { @@ -4413,9 +4413,9 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.5" + "source": "https://github.com/nette/schema/tree/v1.3.6" }, - "time": "2026-02-23T03:47:12+00:00" + "time": "2026-08-16T21:58:41+00:00" }, { "name": "nette/utils", @@ -4567,23 +4567,23 @@ }, { "name": "nunomaduro/collision", - "version": "v8.9.5", + "version": "v8.9.4", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "fb53eacd509a1d303858e2d20cfebf2d630254ec" + "reference": "716af8f95a470e9094cfca09ed897b023be191a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/fb53eacd509a1d303858e2d20cfebf2d630254ec", - "reference": "fb53eacd509a1d303858e2d20cfebf2d630254ec", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/716af8f95a470e9094cfca09ed897b023be191a5", + "reference": "716af8f95a470e9094cfca09ed897b023be191a5", "shasum": "" }, "require": { "filp/whoops": "^2.18.4", "nunomaduro/termwind": "^2.4.0", "php": "^8.2.0", - "symfony/console": "^7.4.14 || ^8.1.1" + "symfony/console": "^7.4.8 || ^8.0.8" }, "conflict": { "laravel/framework": "<11.48.0 || >=14.0.0", @@ -4591,12 +4591,12 @@ }, "require-dev": { "brianium/paratest": "^7.8.5", - "larastan/larastan": "^3.10.0", - "laravel/framework": "^11.48.0 || ^12.56.0 || ^13.20.0", - "laravel/pint": "^1.29.3", - "orchestra/testbench-core": "^9.12.0 || ^10.12.1 || ^11.3.5", - "pestphp/pest": "^3.8.5 || ^4.7.5 || ^5.0.0", - "sebastian/environment": "^7.2.1 || ^8.1.2 || ^9.3.2" + "larastan/larastan": "^3.9.6", + "laravel/framework": "^11.48.0 || ^12.56.0 || ^13.5.0", + "laravel/pint": "^1.29.1", + "orchestra/testbench-core": "^9.12.0 || ^10.12.1 || ^11.2.1", + "pestphp/pest": "^3.8.5 || ^4.4.3 || ^5.0.0", + "sebastian/environment": "^7.2.1 || ^8.0.4 || ^9.3.0" }, "type": "library", "extra": { @@ -4659,7 +4659,7 @@ "type": "patreon" } ], - "time": "2026-07-15T19:09:14+00:00" + "time": "2026-04-21T14:04:20+00:00" }, { "name": "nunomaduro/termwind", @@ -6270,6 +6270,99 @@ ], "time": "2026-02-21T21:10:25+00:00" }, + { + "name": "spatie/laravel-activitylog", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-activitylog.git", + "reference": "97c9f7e82033b18dc9db25de276f9b71e01449ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/97c9f7e82033b18dc9db25de276f9b71e01449ea", + "reference": "97c9f7e82033b18dc9db25de276f9b71e01449ea", + "shasum": "" + }, + "require": { + "illuminate/config": "^13.0", + "illuminate/database": "^13.0", + "illuminate/support": "^13.0", + "php": "^8.4", + "spatie/laravel-package-tools": "^1.6.3" + }, + "require-dev": { + "ext-json": "*", + "larastan/larastan": "^3.0", + "laravel/pint": "^1.29", + "orchestra/testbench": "^11.0", + "pestphp/pest": "^5.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Activitylog\\ActivitylogServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Activitylog\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Sebastian De Deyne", + "email": "sebastian@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Tom Witkowski", + "email": "dev.gummibeer@gmail.com", + "homepage": "https://gummibeer.de", + "role": "Developer" + } + ], + "description": "A very simple activity logger to monitor the users of your website or application", + "homepage": "https://github.com/spatie/laravel-activitylog", + "keywords": [ + "activity", + "laravel", + "log", + "spatie", + "user" + ], + "support": { + "issues": "https://github.com/spatie/laravel-activitylog/issues", + "source": "https://github.com/spatie/laravel-activitylog/tree/5.1.0" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-08-12T12:07:38+00:00" + }, { "name": "spatie/laravel-backup", "version": "10.3.1", @@ -7095,47 +7188,39 @@ }, { "name": "symfony/console", - "version": "v7.4.16", + "version": "v8.0.15", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f4c69c9aed03abf933b294257d618bdd9b30a06d" + "reference": "7327288efcaf02a3838d2de0ae23915d64713e14" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f4c69c9aed03abf933b294257d618bdd9b30a06d", - "reference": "f4c69c9aed03abf933b294257d618bdd9b30a06d", + "url": "https://api.github.com/repos/symfony/console/zipball/7327288efcaf02a3838d2de0ae23915d64713e14", + "reference": "7327288efcaf02a3838d2de0ae23915d64713e14", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.2|^8.0" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/lock": "<6.4", - "symfony/process": "<6.4" + "symfony/string": "^7.4|^8.0" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/lock": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -7169,7 +7254,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.16" + "source": "https://github.com/symfony/console/tree/v8.0.15" }, "funding": [ { @@ -7189,7 +7274,7 @@ "type": "tidelift" } ], - "time": "2026-07-31T12:37:14+00:00" + "time": "2026-07-27T13:52:45+00:00" }, { "name": "symfony/css-selector", @@ -9046,20 +9131,20 @@ }, { "name": "symfony/process", - "version": "v7.4.13", + "version": "v8.0.13", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "f5804be144caceb570f6747519999636b664f24c" + "reference": "4f23d9c7637ead9ed19f697fe93cb87fd9b379d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/f5804be144caceb570f6747519999636b664f24c", - "reference": "f5804be144caceb570f6747519999636b664f24c", + "url": "https://api.github.com/repos/symfony/process/zipball/4f23d9c7637ead9ed19f697fe93cb87fd9b379d4", + "reference": "4f23d9c7637ead9ed19f697fe93cb87fd9b379d4", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4" }, "type": "library", "autoload": { @@ -9087,7 +9172,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.4.13" + "source": "https://github.com/symfony/process/tree/v8.0.13" }, "funding": [ { @@ -9107,7 +9192,7 @@ "type": "tidelift" } ], - "time": "2026-05-23T16:05:06+00:00" + "time": "2026-05-23T18:05:53+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -10504,16 +10589,16 @@ }, { "name": "yajra/laravel-datatables-oracle", - "version": "v13.1.6", + "version": "v13.2.0", "source": { "type": "git", "url": "https://github.com/yajra/laravel-datatables.git", - "reference": "662cbca5d7c7cecd1e21b123eafda0485f0f033d" + "reference": "1a3d674cdc0ef6a226c76f5ff0c3b6d8f3af15cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yajra/laravel-datatables/zipball/662cbca5d7c7cecd1e21b123eafda0485f0f033d", - "reference": "662cbca5d7c7cecd1e21b123eafda0485f0f033d", + "url": "https://api.github.com/repos/yajra/laravel-datatables/zipball/1a3d674cdc0ef6a226c76f5ff0c3b6d8f3af15cc", + "reference": "1a3d674cdc0ef6a226c76f5ff0c3b6d8f3af15cc", "shasum": "" }, "require": { @@ -10581,7 +10666,7 @@ ], "support": { "issues": "https://github.com/yajra/laravel-datatables/issues", - "source": "https://github.com/yajra/laravel-datatables/tree/v13.1.6" + "source": "https://github.com/yajra/laravel-datatables/tree/v13.2.0" }, "funding": [ { @@ -10589,7 +10674,7 @@ "type": "github" } ], - "time": "2026-07-31T03:18:01+00:00" + "time": "2026-08-14T03:20:47+00:00" } ], "packages-dev": [ @@ -12237,19 +12322,21 @@ }, { "name": "hamcrest/hamcrest-php", - "version": "v2.1.1", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + "reference": "b61cd040da1a4925bc90a51c074f5297e7c0fa52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", - "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b61cd040da1a4925bc90a51c074f5297e7c0fa52", + "reference": "b61cd040da1a4925bc90a51c074f5297e7c0fa52", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-dom": "*", "php": "^7.4|^8.0" }, "replace": { @@ -12258,13 +12345,15 @@ "kodova/hamcrest-php": "*" }, "require-dev": { + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -12282,9 +12371,9 @@ ], "support": { "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + "source": "https://github.com/hamcrest/hamcrest-php/tree/v3.0.0" }, - "time": "2025-04-30T06:54:44+00:00" + "time": "2026-03-17T11:56:53+00:00" }, { "name": "justinrainbow/json-schema", @@ -12717,29 +12806,28 @@ }, { "name": "mockery/mockery", - "version": "1.6.12", + "version": "1.6.13", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + "reference": "9cb54414cdcd2ec5ca292e7ba19dba3a3444885d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", - "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "url": "https://api.github.com/repos/mockery/mockery/zipball/9cb54414cdcd2ec5ca292e7ba19dba3a3444885d", + "reference": "9cb54414cdcd2ec5ca292e7ba19dba3a3444885d", "shasum": "" }, "require": { - "hamcrest/hamcrest-php": "^2.0.1", - "lib-pcre": ">=7.0", + "hamcrest/hamcrest-php": "^2.0 || ^3.0", "php": ">=7.3" }, "conflict": { "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.6.17", - "symplify/easy-coding-standard": "^12.1.14" + "phpunit/phpunit": "^9.6.36", + "symplify/easy-coding-standard": "^13.2.17" }, "type": "library", "autoload": { @@ -12796,24 +12884,24 @@ "security": "https://github.com/mockery/mockery/security/advisories", "source": "https://github.com/mockery/mockery" }, - "time": "2024-05-16T03:13:13+00:00" + "time": "2026-08-15T03:07:32+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.13.4", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.0" }, "conflict": { "doctrine/collections": "<1.6.8", @@ -12848,55 +12936,55 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" + "url": "https://github.com/mnapoli", + "type": "github" } ], - "time": "2025-08-01T08:46:24+00:00" + "time": "2026-08-11T10:17:44+00:00" }, { "name": "pestphp/pest", - "version": "v4.7.8", + "version": "v4.7.0", "source": { "type": "git", "url": "https://github.com/pestphp/pest.git", - "reference": "5b2293f67adcf1b2320b33f521b94a692d18f360" + "reference": "2fc75cfcf03c041c804778fa894282234adc3c66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest/zipball/5b2293f67adcf1b2320b33f521b94a692d18f360", - "reference": "5b2293f67adcf1b2320b33f521b94a692d18f360", + "url": "https://api.github.com/repos/pestphp/pest/zipball/2fc75cfcf03c041c804778fa894282234adc3c66", + "reference": "2fc75cfcf03c041c804778fa894282234adc3c66", "shasum": "" }, "require": { "brianium/paratest": "^7.20.0", "composer/xdebug-handler": "^3.0.5", - "nunomaduro/collision": "^8.9.5", + "nunomaduro/collision": "^8.9.4", "nunomaduro/termwind": "^2.4.0", "pestphp/pest-plugin": "^4.0.0", "pestphp/pest-plugin-arch": "^4.0.2", "pestphp/pest-plugin-mutate": "^4.0.1", "pestphp/pest-plugin-profanity": "^4.2.1", "php": "^8.3.0", - "phpunit/phpunit": "^12.5.33", - "symfony/process": "^7.4.13|^8.1.0" + "phpunit/phpunit": "^12.5.24", + "symfony/process": "^7.4.8|^8.0.8" }, "conflict": { "filp/whoops": "<2.18.3", - "phpunit/phpunit": ">12.5.33", + "phpunit/phpunit": ">12.5.24", "sebastian/exporter": "<7.0.0", "webmozart/assert": "<1.11.0" }, "require-dev": { - "mrpunyapal/peststan": "^0.2.12", + "mrpunyapal/peststan": "^0.2.9", "pestphp/pest-dev-tools": "^4.1.0", "pestphp/pest-plugin-browser": "^4.3.1", "pestphp/pest-plugin-type-coverage": "^4.0.4", - "psy/psysh": "^0.12.24" + "psy/psysh": "^0.12.22" }, "bin": [ "bin/pest" @@ -12923,6 +13011,7 @@ "Pest\\Plugins\\Verbose", "Pest\\Plugins\\Version", "Pest\\Plugins\\Shard", + "Pest\\Plugins\\Tia", "Pest\\Plugins\\Parallel" ] }, @@ -12962,7 +13051,7 @@ ], "support": { "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v4.7.8" + "source": "https://github.com/pestphp/pest/tree/v4.7.0" }, "funding": [ { @@ -12974,7 +13063,7 @@ "type": "github" } ], - "time": "2026-08-03T20:49:44+00:00" + "time": "2026-05-03T16:09:32+00:00" }, { "name": "pestphp/pest-plugin", @@ -14261,43 +14350,43 @@ }, { "name": "phpunit/phpunit", - "version": "12.5.33", + "version": "12.5.24", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b98e028a26c5c5ba7e4a54be96ccf35f2914d184" + "reference": "d75dd30597caa80e72fad2ef7904601a30ef1046" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b98e028a26c5c5ba7e4a54be96ccf35f2914d184", - "reference": "b98e028a26c5c5ba7e4a54be96ccf35f2914d184", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d75dd30597caa80e72fad2ef7904601a30ef1046", + "reference": "d75dd30597caa80e72fad2ef7904601a30ef1046", "shasum": "" }, "require": { "ext-dom": "*", - "ext-filter": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", + "ext-xml": "*", "ext-xmlwriter": "*", "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.3", - "phpunit/php-code-coverage": "^12.5.7", + "phpunit/php-code-coverage": "^12.5.6", "phpunit/php-file-iterator": "^6.0.1", "phpunit/php-invoker": "^6.0.0", "phpunit/php-text-template": "^5.0.0", "phpunit/php-timer": "^8.0.0", - "sebastian/cli-parser": "^4.2.1", - "sebastian/comparator": "^7.1.8", + "sebastian/cli-parser": "^4.2.0", + "sebastian/comparator": "^7.1.6", "sebastian/diff": "^7.0.0", - "sebastian/environment": "^8.1.2", - "sebastian/exporter": "^7.0.3", - "sebastian/global-state": "^8.0.3", + "sebastian/environment": "^8.1.0", + "sebastian/exporter": "^7.0.2", + "sebastian/global-state": "^8.0.2", "sebastian/object-enumerator": "^7.0.0", "sebastian/recursion-context": "^7.0.1", - "sebastian/type": "^6.0.4", + "sebastian/type": "^6.0.3", "sebastian/version": "^6.0.0", "staabm/side-effects-detector": "^1.0.5" }, @@ -14339,7 +14428,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.33" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.24" }, "funding": [ { @@ -14347,7 +14436,7 @@ "type": "other" } ], - "time": "2026-07-28T13:58:09+00:00" + "time": "2026-05-01T04:21:04+00:00" }, { "name": "psy/psysh", diff --git a/config/activitylog.php b/config/activitylog.php new file mode 100644 index 0000000000..27cd6aa06e --- /dev/null +++ b/config/activitylog.php @@ -0,0 +1,73 @@ + env('ACTIVITYLOG_ENABLED', true), + + /* + * When the clean command is executed, all recording activities older than + * the number of days specified here will be deleted. + */ + 'clean_after_days' => 365, + + /* + * If no log name is passed to the activity() helper + * we use this default log name. + */ + 'default_log_name' => 'default', + + /* + * You can specify an auth driver here that gets user models. + * If this is null we'll use the current Laravel auth driver. + */ + 'default_auth_driver' => null, + + /* + * If set to true, the subject relationship on activities + * will include soft deleted models. + */ + 'include_soft_deleted_subjects' => false, + + /* + * This model will be used to log activity. + * It should implement the Spatie\Activitylog\Contracts\Activity interface + * and extend Illuminate\Database\Eloquent\Model. + */ + 'activity_model' => Activity::class, + + /* + * These attributes will be excluded from logging for all models. + * Model-specific exclusions via logExcept() are merged with these. + */ + 'default_except_attributes' => [], + + /* + * When enabled, activities are buffered in memory and inserted in a + * single bulk query after the response has been sent to the client. + * This can significantly reduce the number of database queries when + * many activities are logged during a single request. + * + * Only enable this if your application logs a high volume of activities + * per request. Buffered activities will not have an ID until the + * buffer is flushed. + */ + 'buffer' => [ + 'enabled' => env('ACTIVITYLOG_BUFFER_ENABLED', false), + ], + + /* + * These action classes can be overridden to customize how activities + * are logged and cleaned. Your custom classes must extend the originals. + */ + 'actions' => [ + 'log_activity' => LogActivityAction::class, + 'clean_log' => CleanActivityLogAction::class, + ], +]; diff --git a/database/migrations/2026_08_14_104239_create_activity_log_table.php b/database/migrations/2026_08_14_104239_create_activity_log_table.php new file mode 100644 index 0000000000..5c17c24c31 --- /dev/null +++ b/database/migrations/2026_08_14_104239_create_activity_log_table.php @@ -0,0 +1,23 @@ +id(); + $table->string('log_name')->nullable()->index(); + $table->text('description'); + $table->nullableMorphs('subject', 'subject'); + $table->string('event')->nullable(); + $table->nullableMorphs('causer', 'causer'); + $table->json('attribute_changes')->nullable(); + $table->json('properties')->nullable(); + $table->timestamps(); + }); + } +}; diff --git a/database/seeders/RoleSpatieSeeder.php b/database/seeders/RoleSpatieSeeder.php index dab54988b5..fe95f39064 100644 --- a/database/seeders/RoleSpatieSeeder.php +++ b/database/seeders/RoleSpatieSeeder.php @@ -102,7 +102,7 @@ class RoleSpatieSeeder extends Seeder 'access.setting.aplikasi' => ['view', 'edit'], 'access.setting.info_sistem' => ['view'], 'access.setting.database' => ['view', 'export', 'import'], - 'access.setting.jenis_dokumen' => ['view', 'create', 'edit', 'delete'], + 'access.setting.jenis_dokumen' => ['view', 'create', 'edit', 'delete'], ]; /** @@ -180,7 +180,7 @@ public function run() 'access.setting.coa', 'access.setting.themes', 'access.setting.aplikasi', - 'access.setting.info_sistem', + 'access.setting.info_sistem', 'access.setting.database', 'access.setting.jenis_dokumen', ]; diff --git a/public/css/admin-style.css b/public/css/admin-style.css index a193a1927b..187c0bdd15 100644 --- a/public/css/admin-style.css +++ b/public/css/admin-style.css @@ -96,4 +96,52 @@ body.sidebar-collapse .sidebar-kabupaten { } .box-body .table-responsive .dropdown-menu { z-index: 1050; +} + +/* Simple centered modal for activity detail */ +.modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 1040; +} + +.modal-simple { + position: fixed; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + z-index: 1050; +} + +.modal-simple-content { + background: #fff; + border-radius: 6px; + width: 100%; + max-width: 800px; + max-height: calc(100vh - 40px); + overflow: hidden; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35); +} + +.modal-simple-header { + padding: 15px; + border-bottom: 1px solid #e5e5e5; + display: flex; + align-items: center; + justify-content: space-between; +} + +.modal-simple-body { + padding: 15px; + overflow-y: auto; + max-height: calc(100vh - 160px); +} + +.modal-simple-footer { + padding: 15px; + border-top: 1px solid #e5e5e5; + text-align: right; } \ No newline at end of file diff --git a/resources/views/layouts/dashboard_template.blade.php b/resources/views/layouts/dashboard_template.blade.php index 7694fb1443..6749e7c957 100644 --- a/resources/views/layouts/dashboard_template.blade.php +++ b/resources/views/layouts/dashboard_template.blade.php @@ -21,6 +21,7 @@ + @livewireStyles @stack('css') @@ -102,6 +103,7 @@ + @livewireScripts @stack('scripts')