feat : sparklines 14j stats + filtre IPs LAN (v1.6.27)

- Admin stats : sparklines SVG par page (120×28 px, courbe + dégradé),
  carte « Pages les plus visitées » en pleine largeur
- AccessLogParser : données par jour (pages_by_day) sur 14 jours
- AccessLogParser : IPs privées/LAN exclues de la répartition réseau
- ArticleManager : suppression opérateur nullsafe superflu (PHPStan)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 18:20:19 +02:00
parent 007895d24a
commit e8b361e720
6 changed files with 154 additions and 75 deletions
+22 -22
View File
@@ -1,11 +1,12 @@
<?php
$_statsSaved = isset($_GET['saved']);
$_statsError = ($_GET['error'] ?? '') === 'write';
$_readable = $adminData['stats_readable'] ?? false;
$_books = $adminData['stats_books'] ?? [];
$_asList = $adminData['stats_as'] ?? [];
$_asGroups = $adminData['stats_as_groups'] ?? [];
$_groups = $adminData['as_groups'] ?? [];
$_statsSaved = isset($_GET['saved']);
$_statsError = ($_GET['error'] ?? '') === 'write';
$_readable = $adminData['stats_readable'] ?? false;
$_books = $adminData['stats_books'] ?? [];
$_asList = $adminData['stats_as'] ?? [];
$_asGroups = $adminData['stats_as_groups'] ?? [];
$_groups = $adminData['as_groups'] ?? [];
$_pagesByDay = $adminData['stats_pages_by_day'] ?? [];
$_activeGroup = trim($_GET['group'] ?? '');
?>
@@ -24,20 +25,19 @@ $_activeGroup = trim($_GET['group'] ?? '');
<p class="text-muted small mb-4">14 derniers jours · visiteurs uniques · flux RSS XML</p>
<div class="row g-4">
<script>var FOLIO_PAGES_BY_DAY = <?= json_encode($_pagesByDay, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>;</script>
<!-- Pages (chargées via le flux RSS XML /trending?period=14d) -->
<div class="col-lg-6">
<div class="card h-100">
<div class="card-header bg-transparent py-2 small fw-semibold d-flex justify-content-between">
<span>Pages les plus visitées</span>
<span class="text-muted" id="stats-pages-count"></span>
</div>
<div class="card-body p-0" id="stats-pages-container">
<p class="text-muted p-3 mb-0">Chargement…</p>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-transparent py-2 small fw-semibold d-flex justify-content-between">
<span>Pages les plus visitées</span>
<span class="text-muted" id="stats-pages-count"></span>
</div>
<div class="card-body p-0" id="stats-pages-container">
<p class="text-muted p-3 mb-0">Chargement…</p>
</div>
</div>
<div class="row g-4">
<!-- Livres -->
<div class="col-lg-6">
@@ -60,7 +60,7 @@ $_activeGroup = trim($_GET['group'] ?? '');
$rankB++;
$slug = rawurldecode(substr($url, 6));
$pct = round($hits / $maxB * 100);
?>
?>
<tr>
<td class="text-muted ps-3" style="width:2rem"><?= $rankB ?></td>
<td>
@@ -112,7 +112,7 @@ $_activeGroup = trim($_GET['group'] ?? '');
} else {
$displayAs = $_asList;
}
?>
?>
<?php if (empty($displayAs)): ?>
<p class="text-muted p-3 mb-0">
<?= empty($_asList) ? 'Aucune IP résolue (LAN ou logs vides).' : 'Aucun AS dans ce groupe.' ?>
@@ -153,7 +153,7 @@ $_activeGroup = trim($_GET['group'] ?? '');
</div>
</div>
<?php endif; // readable ?>
<?php endif; // readable?>
<!-- Groupes de réseaux -->
<div class="card mt-4" style="max-width:600px">