169 lines
6.2 KiB
PHP
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>
|