feat: admin audit logging across all admin actions

- AdminLogger: JSON-lines → /var/log/xamxam.log (prod) / storage/logs/admin.log (dev)
  + best-effort DB mirror to admin_audit_log table
- DB: admin_audit_log table, share_links.is_archived column
- ShareLink: archive() replaces delete(), toggleActive() returns new state,
  listActive()/listArchived() split, validateLink blocks archived slugs
- All action handlers wired: publish, unpublish, visibility, delete, csv/db export,
  tfe add/edit, tags, pages, apropos, form-help, access-request, maintenance,
  settings (formulaire toggles, objet types, smtp update), smtp-test
- acces.php: archive button replaces delete; collapsible archived links section
- setup-server.sh: provision /var/log/xamxam.log (www-data:xamxam 640)
This commit is contained in:
Pontoporeia
2026-05-04 17:34:26 +02:00
parent 5f24dcae7e
commit ca5983075d
24 changed files with 521 additions and 33 deletions

View File

@@ -9,13 +9,13 @@
<h2 id="acces-liens-title">Accès étudiant·e</h2>
<div class="admin-list-toolbar__right">
<button type="button" class="admin-btn admin-btn--sm" id="open-create-dialog">
Créer un lien
+ Créer un lien
</button>
</div>
</div>
<?php if (empty($links)): ?>
<p class="admin-empty">Aucun lien d'accès créé. Cliquez sur « Créer un lien » pour générer un lien partageable.</p>
<p class="admin-empty">Aucun lien d'accès créé. Cliquez sur «Créer un lien» pour générer un lien partageable.</p>
<?php else: ?>
<table>
<thead>
@@ -92,12 +92,12 @@
🔑
</button>
<form method="post" action="actions/acces-etudiante.php" class="publish-form"
onsubmit="return confirm('Supprimer ce lien ? Les soumissions via ce lien seront bloquées.')">
onsubmit="return confirm('Archiver ce lien? Il ne sera plus accessible, mais les statistiques seront conservées.')">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="action" value="archive">
<input type="hidden" name="id" value="<?= $link['id'] ?>">
<button type="submit" class="admin-btn-sm admin-btn-delete" title="Supprimer">
🗑
<button type="submit" class="admin-btn-sm admin-btn-delete" title="Archiver">
🗄
</button>
</form>
</div>
@@ -107,6 +107,48 @@
</tbody>
</table>
<?php endif; ?>
<?php if (!empty($archivedLinks)): ?>
<details style="margin-top:var(--space-m);">
<summary style="cursor:pointer;font-weight:600;color:var(--text-secondary);font-size:var(--step--1);">
Liens archivés (<?= count($archivedLinks) ?>)
</summary>
<table style="margin-top:var(--space-s);opacity:0.75;">
<thead>
<tr>
<th scope="col">Lien</th>
<th scope="col">Objet</th>
<th scope="col">Utilisations</th>
<th scope="col">Expiration</th>
<th scope="col">Créé le</th>
</tr>
</thead>
<tbody>
<?php foreach ($archivedLinks as $link): ?>
<?php
$created = date('d/m/Y H:i', strtotime($link['created_at']));
$expires = $link['expires_at'] ? date('d/m/Y', strtotime($link['expires_at'])) : '—';
?>
<tr>
<td>
<code style="font-size:var(--step--2);color:var(--text-secondary);text-decoration:line-through;"><?= htmlspecialchars($link['slug']) ?></code>
</td>
<td>
<?php if ($link['objet_restriction']): ?>
<span class="status-badge"><?= htmlspecialchars($link['objet_restriction']) ?></span>
<?php else: ?>
<span style="color:var(--text-secondary);font-size:var(--step--2);">Tous</span>
<?php endif; ?>
</td>
<td style="text-align:center;"><?= intval($link['usage_count']) ?></td>
<td><?= $expires ?></td>
<td><?= $created ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</details>
<?php endif; ?>
</section>
<!-- ══════════════════════════════════════════════════════════════
@@ -158,7 +200,7 @@
par <?= htmlspecialchars($req['authors']) ?>
<?php endif; ?>
<?php if (!empty($req['year'])): ?>
<?= htmlspecialchars($req['year']) ?>
- <?= htmlspecialchars($req['year']) ?>
<?php endif; ?>
</p>
</div>
@@ -264,7 +306,7 @@
<div>
<label for="create-objet">Type d'objet (optionnel)</label>
<select id="create-objet" name="objet_restriction">
<option value=""> Tous les types </option>
<option value="">- Tous les types -</option>
<option value="tfe">TFE</option>
<option value="thèse">Thèse</option>
<option value="frart">Frart</option>