Files
admin-netmonster-db/admin/audit_log.php
T
2026-10-21 22:00:06 +02:00

169 lines
6.2 KiB
PHP

<?php
/**
* admin/audit_log.php
*
* Historique des modifications effectuees dans l'admin (creation,
* edition, suppression de sites/cellules, fusions, imports valides).
* Page en lecture seule : l'audit log ne doit jamais pouvoir etre
* modifie ou supprime depuis l'interface, pour garder une tracabilite
* fiable (regle de securite implicite : integrite de la piste d'audit).
*
* Hypothese de schema (a adapter si necessaire) :
* audit_log(id, created_at, admin_username, action, entity_type,
* entity_id, details)
*
* Cette page ne fait AUCUNE ecriture en base : c'est un simple
* consultateur filtrant, protege uniquement par require_admin_auth().
* Aucun jeton CSRF n'est necessaire car aucun POST ne modifie l'etat.
*/
declare(strict_types=1);
require_once __DIR__ . '/admin_auth.php';
require_once __DIR__ . '/../lib/db.php';
require_once __DIR__ . '/_nav.php';
require_admin_auth();
$db = pdo();
$filterUser = trim((string)filter_input(INPUT_GET, 'user', FILTER_UNSAFE_RAW));
$filterAction = trim((string)filter_input(INPUT_GET, 'action', FILTER_UNSAFE_RAW));
$filterEntity = trim((string)filter_input(INPUT_GET, 'entity_type', FILTER_UNSAFE_RAW));
$dateFrom = trim((string)filter_input(INPUT_GET, 'date_from', FILTER_UNSAFE_RAW));
$dateTo = trim((string)filter_input(INPUT_GET, 'date_to', FILTER_UNSAFE_RAW));
$where = [];
$params = [];
if ($filterUser !== '') {
$where[] = 'admin_username LIKE ?';
$params[] = '%' . $filterUser . '%';
}
if ($filterAction !== '') {
$where[] = 'action = ?';
$params[] = $filterAction;
}
if ($filterEntity !== '') {
$where[] = 'entity_type = ?';
$params[] = $filterEntity;
}
if ($dateFrom !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateFrom)) {
$where[] = 'created_at >= ?';
$params[] = $dateFrom . ' 00:00:00';
}
if ($dateTo !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateTo)) {
$where[] = 'created_at <= ?';
$params[] = $dateTo . ' 23:59:59';
}
$whereSql = $where ? ('WHERE ' . implode(' AND ', $where)) : '';
$page = max(1, filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1);
$perPage = 50;
$offset = ($page - 1) * $perPage;
$countStmt = $db->prepare("SELECT COUNT(*) FROM audit_log $whereSql");
$countStmt->execute($params);
$total = (int)$countStmt->fetchColumn();
$sql = "SELECT id, created_at, admin_username, action, entity_type, entity_id, details
FROM audit_log
$whereSql
ORDER BY created_at DESC
LIMIT $perPage OFFSET $offset";
$stmt = $db->prepare($sql);
$stmt->execute($params);
$entries = $stmt->fetchAll(PDO::FETCH_ASSOC);
$totalPages = max(1, (int)ceil($total / $perPage));
$user = current_admin_username();
function build_query_string(array $overrides): string
{
$base = [
'user' => $_GET['user'] ?? '',
'action' => $_GET['action'] ?? '',
'entity_type' => $_GET['entity_type'] ?? '',
'date_from' => $_GET['date_from'] ?? '',
'date_to' => $_GET['date_to'] ?? '',
'page' => $_GET['page'] ?? 1,
];
$merged = array_merge($base, $overrides);
return http_build_query($merged);
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Journal d'audit — NetMonster Admin</title>
<link rel="stylesheet" href="assets/theme.css">
</head>
<body>
<?php render_admin_nav('audit_log.php', $user); ?>
<main class="container">
<h1>Journal d'audit</h1>
<p>Historique en lecture seule des modifications effectuees dans l'administration. Ce journal ne peut pas etre modifie depuis l'interface.</p>
<section class="card">
<form method="get" class="filters">
<label>Utilisateur
<input type="text" name="user" value="<?= htmlspecialchars($filterUser, ENT_QUOTES, 'UTF-8') ?>">
</label>
<label>Action
<input type="text" name="action" value="<?= htmlspecialchars($filterAction, ENT_QUOTES, 'UTF-8') ?>" placeholder="create, update, delete...">
</label>
<label>Type d'entite
<input type="text" name="entity_type" value="<?= htmlspecialchars($filterEntity, ENT_QUOTES, 'UTF-8') ?>" placeholder="site, cell, import...">
</label>
<label>Du
<input type="date" name="date_from" value="<?= htmlspecialchars($dateFrom, ENT_QUOTES, 'UTF-8') ?>">
</label>
<label>Au
<input type="date" name="date_to" value="<?= htmlspecialchars($dateTo, ENT_QUOTES, 'UTF-8') ?>">
</label>
<button type="submit" class="btn btn-primary">Filtrer</button>
<a href="audit_log.php" class="btn btn-secondary">Reinitialiser</a>
</form>
</section>
<section class="card">
<h2>Entrees (<?= $total ?>)</h2>
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Utilisateur</th>
<th>Action</th>
<th>Entite</th>
<th>ID</th>
<th>Details</th>
</tr>
</thead>
<tbody>
<?php foreach ($entries as $entry): ?>
<tr>
<td><?= htmlspecialchars((string)$entry['created_at'], ENT_QUOTES, 'UTF-8') ?></td>
<td><?= htmlspecialchars((string)$entry['admin_username'], ENT_QUOTES, 'UTF-8') ?></td>
<td><?= htmlspecialchars((string)$entry['action'], ENT_QUOTES, 'UTF-8') ?></td>
<td><?= htmlspecialchars((string)$entry['entity_type'], ENT_QUOTES, 'UTF-8') ?></td>
<td><?= (int)$entry['entity_id'] ?></td>
<td><code><?= htmlspecialchars((string)$entry['details'], ENT_QUOTES, 'UTF-8') ?></code></td>
</tr>
<?php endforeach; ?>
<?php if (empty($entries)): ?>
<tr><td colspan="6">Aucune entree pour ces filtres.</td></tr>
<?php endif; ?>
</tbody>
</table>
<nav class="pagination">
<?php for ($p = 1; $p <= $totalPages; $p++): ?>
<a href="audit_log.php?<?= build_query_string(['page' => $p]) ?>" class="<?= $p === $page ? 'active' : '' ?>"><?= $p ?></a>
<?php endfor; ?>
</nav>
</section>
</main>
</body>
</html>