Init Git Prod Mars 2026

This commit is contained in:
Oleg Barykin committed 2026-03-05 10:25:39 +01:00
commit d81efe93b1
362 files changed
+86627

No files matched your search

+145
View File
@@ -0,0 +1,145 @@
<?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>