diff --git a/app/Http/Controllers/AuditLogsController.php b/app/Http/Controllers/AuditLogsController.php new file mode 100644 index 0000000..530743a --- /dev/null +++ b/app/Http/Controllers/AuditLogsController.php @@ -0,0 +1,102 @@ +role !== 1) && (Auth::User()->role !== 2), + Response::HTTP_FORBIDDEN, + '403 Forbidden' + ); + + $logs = DB::table('audit_logs') + ->select( + 'audit_logs.id', + 'description', + 'subject_type', + 'subject_id', + 'users.name', + 'user_id', + 'host', + 'audit_logs.created_at' + ) + ->join('users', 'users.id', '=', 'user_id') + ->orderBy('audit_logs.id', 'desc')->paginate(100); + + return view('logs.index', ['logs' => $logs]); + } + + public function show(int $id) + { + // Only for admin and users + abort_if( + (Auth::User()->role !== 1) && (Auth::User()->role !== 2), + Response::HTTP_FORBIDDEN, + '403 Forbidden' + ); + + // Get audit Log + $auditLog = AuditLog::find($id); + + // Control not found + abort_if($auditLog === null, Response::HTTP_NOT_FOUND, '404 Not Found'); + + return view('logs.show', compact('auditLog')); + } + + public function history(int $id) + { + // Only for admin and users + abort_if( + (Auth::User()->role !== 1) && (Auth::User()->role !== 2), + Response::HTTP_FORBIDDEN, + '403 Forbidden' + ); + + // Get audit Log + $auditLog = AuditLog::find($id); + + abort_if($auditLog === null, 400, '400 log not found'); + + // Get the list + $auditLogs = + DB::table('audit_logs') + ->select( + 'audit_logs.id', + 'description', + 'subject_type', + 'subject_id', + 'users.name', + 'user_id', + 'host', + 'properties', + 'audit_logs.created_at' + ) + ->join('users', 'users.id', '=', 'user_id') + ->where('subject_id', $auditLog->subject_id) + ->where('subject_type', $auditLog->subject_type) + ->orderBy('audit_logs.id') + ->get(); + + abort_if($auditLogs->isEmpty(), 404, 'Not found'); + + // JSON decode all properties + foreach ($auditLogs as $auditLog) { + $auditLog->properties = json_decode(trim(stripslashes($auditLog->properties), '"')); + } + + return view('logs.history', compact('auditLogs')); + } +} diff --git a/app/Http/Controllers/ControlController.php b/app/Http/Controllers/ControlController.php index 1a2bc77..f13cd0b 100644 --- a/app/Http/Controllers/ControlController.php +++ b/app/Http/Controllers/ControlController.php @@ -255,6 +255,9 @@ public function index(Request $request) ); } + // get action plan associated + $controls = $controls-> leftjoin("actions","actions.control_id","=","c1.id"); + // Query DB $controls = $controls ->select([ @@ -265,6 +268,7 @@ public function index(Request $request) 'c1.realisation_date', 'c1.score as score', 'c1.status', + 'actions.id as action_id', 'c2.id as next_id', 'c2.plan_date as next_date', ]) diff --git a/app/Models/Action.php b/app/Models/Action.php index de4d4db..fac9873 100644 --- a/app/Models/Action.php +++ b/app/Models/Action.php @@ -2,10 +2,13 @@ namespace App\Models; +use App\Traits\Auditable; use Illuminate\Database\Eloquent\Model; class Action extends Model { + use Auditable; + public static $searchable = [ 'reference', 'type', diff --git a/app/Models/AuditLog.php b/app/Models/AuditLog.php new file mode 100644 index 0000000..799b3ba --- /dev/null +++ b/app/Models/AuditLog.php @@ -0,0 +1,49 @@ + 'collection', + ]; + + public function user() + { + return $this->belongsTo(User::class, 'user_id'); + } + + public function subjectURL() + { + // Trouver la dernière occurrence de "\" + $position = strrpos($this->subject_type, "\\"); + + // Extraire ce qui suit si "\" est trouvé + $resultat = ($position !== false) ? substr($this->subject_type, $position + 1) : $this->subject_type; + + return $resultat; + } + + protected function serializeDate(DateTimeInterface $date) + { + return $date->format('Y-m-d H:i:s'); + } +} diff --git a/app/Models/Control.php b/app/Models/Control.php index a63f94d..e515970 100644 --- a/app/Models/Control.php +++ b/app/Models/Control.php @@ -2,10 +2,13 @@ namespace App\Models; +use App\Traits\Auditable; use Illuminate\Database\Eloquent\Model; class Control extends Model { + use Auditable; + public static $searchable = [ 'name', 'objective', @@ -46,6 +49,11 @@ public function measures() return $this->belongsToMany(Measure::class)->orderBy('clause'); } + public function actionPlan() + { + return DB::table('actions')->select('id')->where("control_id",'=',$this->id)->get(); + } + public function owners() { return $this->belongsToMany(User::class, 'control_user', 'control_id')->orderBy('name'); diff --git a/app/Models/Measure.php b/app/Models/Measure.php index e319731..edd8637 100644 --- a/app/Models/Measure.php +++ b/app/Models/Measure.php @@ -2,11 +2,15 @@ namespace App\Models; +use App\Traits\Auditable; + use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\DB; class Measure extends Model { + use Auditable; + public static $searchable = [ 'name', 'clause', diff --git a/app/Models/User.php b/app/Models/User.php index 524a995..9583abb 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,6 +2,8 @@ namespace App\Models; +use App\Traits\Auditable; + use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; @@ -9,7 +11,7 @@ class User extends Authenticatable { - use HasApiTokens, HasFactory, Notifiable; + use HasApiTokens, HasFactory, Notifiable, Auditable; /** * The attributes that are mass assignable. diff --git a/app/Traits/Auditable.php b/app/Traits/Auditable.php new file mode 100644 index 0000000..3b64f7e --- /dev/null +++ b/app/Traits/Auditable.php @@ -0,0 +1,36 @@ + $description, + 'subject_id' => $model->id ?? null, + 'subject_type' => $model::class ?? null, + 'user_id' => auth()->id() ?? null, + 'properties' => substr($model, 0, 65534) ?? null, + 'host' => request()->ip() ?? null, + ]); + } +} diff --git a/database/migrations/2025_02_04_064646_create_audit_logs_table.php b/database/migrations/2025_02_04_064646_create_audit_logs_table.php new file mode 100644 index 0000000..f028da1 --- /dev/null +++ b/database/migrations/2025_02_04_064646_create_audit_logs_table.php @@ -0,0 +1,36 @@ +increments('id'); + $table->text('description'); + $table->unsignedInteger('subject_id')->nullable(); + $table->string('subject_type')->nullable(); + $table->unsignedInteger('user_id')->nullable(); + $table->text('properties')->nullable(); + $table->string('host', 45)->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('audit_logs'); + } + +}; diff --git a/database/migrations/2025_02_05_121035_cleanup.php b/database/migrations/2025_02_05_121035_cleanup.php new file mode 100644 index 0000000..4684f63 --- /dev/null +++ b/database/migrations/2025_02_05_121035_cleanup.php @@ -0,0 +1,28 @@ +dropColumn("site"); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('controls', function (Blueprint $table) { + $table->string('site')->nullable(); + }); + } +}; diff --git a/resources/lang/en/common.php b/resources/lang/en/common.php index e7fbb8c..c4159b0 100644 --- a/resources/lang/en/common.php +++ b/resources/lang/en/common.php @@ -11,12 +11,14 @@ 'delete' => 'Delete', 'download' => 'Download', 'edit' => 'Edit', + 'history' => 'History', 'import' => 'Import', 'make' => 'Make', 'new' => 'New', 'plan' => 'Plan', 'reject' => 'Reject', 'save' => 'Save', + 'show' => 'Show', 'test' => 'Test', 'unplan' => 'Unplan', 'validate' => 'Validate', diff --git a/resources/lang/en/cruds.php b/resources/lang/en/cruds.php index 434a9c4..6788c79 100644 --- a/resources/lang/en/cruds.php +++ b/resources/lang/en/cruds.php @@ -187,6 +187,18 @@ 'index' => 'Import', 'title' => 'Import security measures', ], + 'log' => [ + 'index' => 'List of logs', + 'title' => 'Log', + 'history' => 'Change history', + 'action' => 'Action', + 'subject_type' => 'Type', + 'subject_id' => 'ID', + 'user' => 'User', + 'host' => 'Host', + 'timestamp' => 'Timestamp', + 'properties' => 'Data' + ], 'login' => [ 'title' => 'Enter a password', 'identification' => 'Login', diff --git a/resources/lang/fr/common.php b/resources/lang/fr/common.php index de1be18..5c3917c 100644 --- a/resources/lang/fr/common.php +++ b/resources/lang/fr/common.php @@ -11,12 +11,14 @@ 'delete' => 'Supprimer', 'download' => 'Télécharger', 'edit' => 'Modifier', + 'history' => 'Historique', 'import' => 'Importer', 'make' => 'Faire', 'new' => 'Nouveau', 'plan' => 'Planifier', 'reject' => "Rejeter", 'save' => 'Sauver', + 'show' => 'Voir', 'unplan' => 'Déplanifier', 'validate' => 'Valider', diff --git a/resources/lang/fr/cruds.php b/resources/lang/fr/cruds.php index 8db48af..c65f2c1 100644 --- a/resources/lang/fr/cruds.php +++ b/resources/lang/fr/cruds.php @@ -185,6 +185,18 @@ 'index' => 'Importer', 'title' => 'Importer des mesures de sécurité', ], + 'log' => [ + 'index' => 'Liste des logs', + 'title' => 'Log', + 'history' => 'Historique des changements', + 'action' => 'Action', + 'subject_type' => 'Type', + 'subject_id' => 'ID', + 'user' => 'Utilisateur', + 'host' => 'Host', + 'timestamp' => 'Horodatage', + 'properties' => 'Données' + ], 'login' => [ 'title' => 'Entrez un mot de passe', 'identification' => 'Identification', diff --git a/resources/views/controls/index.blade.php b/resources/views/controls/index.blade.php index a403d85..ff85561 100644 --- a/resources/views/controls/index.blade.php +++ b/resources/views/controls/index.blade.php @@ -174,19 +174,21 @@