Files
2026-03-05 10:25:39 +01:00

146 lines
6.3 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
if (($_GET['page'] ?? '') !== 'audit') return;
require_once __DIR__ . '/../../public/includes/init_admin.php';
$roles = (array)($_SESSION['role'] ?? []);
if (!in_array('admin', $roles, true)) {
echo '<div class="container my-4"><div class="alert alert-danger">'
. htmlspecialchars((string)($translations['audite_access_denied'] ?? 'Accès refusé.'), ENT_QUOTES, 'UTF-8')
. '</div></div>';
return;
}
require_once __DIR__.'/_lib_audit.php';
// reauth (10 min)
$mustReauth=function(int $ttl=600){$t=(int)($_SESSION['admin_reauth_at']??0);return ($t===0||(time()-$t)>$ttl);};
if ($mustReauth()){ $qs=http_build_query(['page'=>'reauth','redir'=>($_SERVER['REQUEST_URI']??'index.php?page=maintenance')]); header('Location:?'.$qs); exit; }
if (!isset($pdo)) { $db = realpath(__DIR__.'/../../includes/db.php'); if ($db) require_once $db; }
if (!isset($pdo) || !($pdo instanceof PDO)) {
echo '<div class="container my-4"><div class="alert alert-danger">'
. htmlspecialchars((string)($translations['audite_db_unavailable'] ?? 'DB indisponible.'), ENT_QUOTES, 'UTF-8')
. '</div></div>';
return;
}
try { $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch(Throwable) {}
$e = static fn($s)=>htmlspecialchars((string)$s,ENT_QUOTES,'UTF-8');
if (empty($_SESSION['csrf_token'])) $_SESSION['csrf_token']=bin2hex(random_bytes(32));
$CSRF=$_SESSION['csrf_token'];
// filtres
$qAdmin = (int)($_GET['admin_id'] ?? 0);
$qAct = trim($_GET['action'] ?? '');
$qFrom = trim($_GET['from'] ?? '');
$qTo = trim($_GET['to'] ?? '');
$page = max(1, (int)($_GET['p'] ?? 1));
$per = min(200, max(10,(int)($_GET['per'] ?? 50)));
$w=[]; $p=[];
if ($qAdmin>0){ $w[]='admin_id=:aid'; $p[':aid']=$qAdmin; }
if ($qAct!==''){ $w[]='action=:act'; $p[':act']=$qAct; }
if ($qFrom!==''){ $w[]='created_at>=:f'; $p[':f']=$qFrom.' 00:00:00'; }
if ($qTo!==''){ $w[]='created_at<=:t'; $p[':t']=$qTo.' 23:59:59'; }
$where = $w?('WHERE '.implode(' AND ',$w)):'';
$cnt = $pdo->prepare("SELECT COUNT(*) FROM admin_audit $where");
$cnt->execute($p); $total=(int)$cnt->fetchColumn();
$pages = max(1,(int)ceil($total/$per));
$off = ($page-1)*$per;
$sql = "SELECT id,admin_id,action,scope,target_table,target_id,meta_json,INET6_NTOA(ip) ip,user_agent,created_at
FROM admin_audit $where ORDER BY id DESC LIMIT $per OFFSET $off";
$stmt=$pdo->prepare($sql); $stmt->execute($p);
$rows=$stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<div class="shadow p-4 rounded bg-light">
<div class="container my-4">
<div class="d-flex align-items-center gap-2 mb-3">
<h3 class="m-0"><?= $e($translations['audite_title'] ?? 'Journal d’audit (actions admin)') ?></h3>
<span class="badge text-bg-secondary">
<?php
$tpl = (string)($translations['audite_total_badge'] ?? '{N} lignes');
echo $e(str_replace('{N}', (string)((int)$total), $tpl));
?>
</span>
</div>
<form class="row g-2 mb-3" method="get">
<input type="hidden" name="page" value="audit">
<div class="col-6 col-md-2">
<input class="form-control" name="admin_id" type="number"
placeholder="<?= $e($translations['audite_ph_admin_id'] ?? 'admin_id') ?>"
value="<?= $qAdmin ?>">
</div>
<div class="col-6 col-md-2">
<input class="form-control" name="action"
placeholder="<?= $e($translations['audite_ph_action'] ?? 'action') ?>"
value="<?= $e($qAct) ?>">
</div>
<div class="col-6 col-md-2"><input class="form-control" name="from" type="date" value="<?= $e($qFrom) ?>"></div>
<div class="col-6 col-md-2"><input class="form-control" name="to" type="date" value="<?= $e($qTo) ?>"></div>
<div class="col-6 col-md-2">
<select class="form-select" name="per">
<?php foreach ([25,50,100,200] as $n): ?>
<option value="<?= $n ?>" <?= $per===$n?'selected':'' ?>>
<?php
$tpl = (string)($translations['audite_per_page'] ?? '{N}/page');
echo $e(str_replace('{N}', (string)$n, $tpl));
?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-6 col-md-2 d-grid">
<button class="btn btn-outline-secondary"><?= $e($translations['audite_btn_filter'] ?? 'Filtrer') ?></button>
</div>
</form>
<div class="card">
<div class="table-responsive">
<table class="table table-sm table-striped align-middle mb-0 mobile-cards">
<thead><tr>
<th><?= $e($translations['audite_th_id'] ?? '#') ?></th>
<th><?= $e($translations['audite_th_admin'] ?? 'Admin') ?></th>
<th><?= $e($translations['audite_th_action'] ?? 'Action') ?></th>
<th><?= $e($translations['audite_th_scope'] ?? 'Scope') ?></th>
<th><?= $e($translations['audite_th_target'] ?? 'Cible') ?></th>
<th><?= $e($translations['audite_th_ip'] ?? 'IP') ?></th>
<th><?= $e($translations['audite_th_date'] ?? 'Date') ?></th>
<th><?= $e($translations['audite_th_meta'] ?? 'Meta') ?></th>
</tr></thead>
<tbody>
<?php foreach($rows as $r): ?>
<tr>
<td><?= (int)$r['id'] ?></td>
<td><?= (int)$r['admin_id'] ?></td>
<td><span class="badge text-bg-dark"><?= $e($r['action']) ?></span></td>
<td><?= $e($r['scope'] ?? '') ?></td>
<td><small><?= $e($r['target_table'] ?? '') ?><?= $r['target_id']?(' #'.(int)$r['target_id']):'' ?></small></td>
<td><small><?= $e($r['ip'] ?? '') ?></small></td>
<td><small><?= $e($r['created_at']) ?></small></td>
<td><code class="small"><?= $e($r['meta_json'] ?? '') ?></code></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if ($pages>1): ?>
<div class="card-footer d-flex justify-content-between">
<div>
<?php
$tpl = (string)($translations['audite_page_x_y'] ?? 'Page {X} / {Y}');
echo $e(str_replace(['{X}','{Y}'], [(string)$page, (string)$pages], $tpl));
?>
</div>
<div class="btn-group">
<?php for($i=max(1,$page-2);$i<=min($pages,$page+2);$i++):
$q=$_GET; $q['p']=$i; ?>
<a class="btn btn-sm <?= $i===$page?'btn-dark':'btn-outline-dark' ?>" href="?<?= http_build_query($q) ?>"><?= $i ?></a>
<?php endfor; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>