fix: visitors.json clés perdues, bouton AS inaccessible, graphique visiteurs

- Fix array_merge → + pour préserver clés 7/14/30 dans visitors.json
- Bouton ✕ exclusion AS sorti du div 9rem + stopPropagation
- Handler délégué unique (removeEventListener avant de rajouter)
- Graphique trend : visiteurs uniques/jour depuis ip_data (top 200)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 22:39:21 +02:00
parent dbbe60f28e
commit fce4ae6a79
4 changed files with 36 additions and 19 deletions
+12
View File
@@ -5,6 +5,18 @@ Format : [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/) — versionnag
---
## [1.6.35] - 2026-05-19
### Corrigé
- `visitors.json` : utilisation de `+` au lieu de `array_merge` pour préserver les clés entières 7/14/30 (array_merge les renumérote en 0/1/2)
- Admin stats / Visiteurs par pays : bouton ✕ déplacé hors du div 9rem (il était écrasé par le nom de l'AS) ; `e.stopPropagation()` ajouté pour ne pas déclencher l'accordéon
- Admin stats / Visiteurs par pays : listener délégué stocké et retiré avant réajout (évite l'accumulation de handlers après chaque `renderCountry()`)
### Modifié
- Graphique "Trafic total" → "Visiteurs uniques / jour" calculé depuis les IPs du top 200 (approximation)
---
## [1.6.34] - 2026-05-19
### Ajouté
+22 -17
View File
@@ -75,6 +75,7 @@ var _csrf = (typeof FOLIO_CSRF !== 'undefined') ? FOLIO_CSRF : '';
var ipData = (typeof FOLIO_IP_DATA !== 'undefined') ? FOLIO_IP_DATA : {};
if (!el || !asList.length) { return; }
var _countryClickHandler = null;
var dispNames = null;
try { dispNames = new Intl.DisplayNames(['fr'], { type: 'region' }); } catch (e) {}
function countryName(code) {
@@ -277,17 +278,17 @@ var _csrf = (typeof FOLIO_CSRF !== 'undefined') ? FOLIO_CSRF : '';
var toggleAttrs = hasIps ? ' data-bs-toggle="collapse" data-bs-target="#' + asId + '" role="button"' : '';
var chevron = hasIps ? '<span class="text-muted ms-1" style="font-size:.65rem">▾</span>' : '';
var excludeBtn = n.asn
? '<button class="btn btn-sm py-0 px-1 ms-2 text-muted border-0 exclude-as-btn" style="font-size:.65rem" title="Exclure cet AS des stats" data-asn="' + esc(n.asn) + '" data-name="' + esc(n.name || '') + '">✕</button>'
? '<button class="btn btn-sm py-0 px-1 text-muted border-0 exclude-as-btn" style="font-size:.65rem;flex-shrink:0" title="Exclure cet AS des stats" data-asn="' + esc(n.asn) + '" data-name="' + esc(n.name || '') + '">✕</button>'
: '';
return '<div>'
+ '<div class="d-flex align-items-center gap-2 py-1"' + toggleAttrs + '>'
+ '<div class="small d-flex align-items-center" style="width:9rem;flex-shrink:0">'
+ esc(n.name || '?')
+ (n.asn ? ' <span class="text-muted">AS' + esc(n.asn) + '</span>' : '')
+ '<div class="d-flex align-items-center gap-1 py-1"' + toggleAttrs + '>'
+ '<div class="small d-flex align-items-center" style="min-width:0;flex:1 1 9rem;overflow:hidden">'
+ '<span class="text-truncate">' + esc(n.name || '?') + '</span>'
+ (n.asn ? '&nbsp;<span class="text-muted text-nowrap">AS' + esc(n.asn) + '</span>' : '')
+ chevron
+ excludeBtn
+ '</div>'
+ excludeBtn
+ '<div class="flex-grow-1"><div class="progress" style="height:4px">'
+ '<div class="progress-bar bg-info" style="width:' + npct + '%"></div>'
+ '</div></div>'
@@ -336,13 +337,15 @@ var _csrf = (typeof FOLIO_CSRF !== 'undefined') ? FOLIO_CSRF : '';
el.innerHTML = html;
// Délégation : boutons exclure / inclure
el.addEventListener('click', function (e) {
// Délégation : boutons exclure / inclure (handler unique pour éviter les doublons)
if (_countryClickHandler) { el.removeEventListener('click', _countryClickHandler); }
_countryClickHandler = function (e) {
var btn = e.target.closest('.exclude-as-btn');
if (btn) { excludeAs(btn.getAttribute('data-asn'), btn.getAttribute('data-name')); return; }
if (btn) { e.stopPropagation(); excludeAs(btn.getAttribute('data-asn'), btn.getAttribute('data-name')); return; }
btn = e.target.closest('.include-as-btn');
if (btn) { includeAs(btn.getAttribute('data-asn')); }
}, { once: true });
if (btn) { e.stopPropagation(); includeAs(btn.getAttribute('data-asn')); }
};
el.addEventListener('click', _countryClickHandler);
}
renderCountry();
@@ -543,14 +546,14 @@ var _csrf = (typeof FOLIO_CSRF !== 'undefined') ? FOLIO_CSRF : '';
var dots = pts.map(function (p) {
return '<circle cx="' + p.x.toFixed(1) + '" cy="' + p.y.toFixed(1) + '" r="14"'
+ ' fill="transparent" cursor="default">'
+ '<title>' + esc(p.l) + ' : ' + p.v + ' vis.</title>'
+ '<title>' + esc(p.l) + ' : ' + p.v + ' visiteur(s)</title>'
+ '</circle>'
+ '<circle cx="' + p.x.toFixed(1) + '" cy="' + p.y.toFixed(1) + '" r="3"'
+ ' fill="var(--bs-primary,#0d6efd)" stroke="#fff" stroke-width="1.5" pointer-events="none"/>';
}).join('');
trendEl.innerHTML =
'<p class="small text-muted mb-2 fw-semibold">Trafic total — 30 derniers jours</p>'
'<p class="small text-muted mb-2 fw-semibold">Visiteurs uniques / jour — 30 derniers jours <span class="fw-normal opacity-50">(top 200 IPs)</span></p>'
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + VW + ' ' + VH + '"'
+ ' style="width:100%;height:480px;display:block;overflow:visible">'
+ '<defs>'
@@ -690,11 +693,13 @@ var _csrf = (typeof FOLIO_CSRF !== 'undefined') ? FOLIO_CSRF : '';
return { title: title, link: link, slug: slug, vis: vis, daily: daily };
});
var nDays = Object.values(pagesByDay)[0] ? Object.values(pagesByDay)[0].length : 30;
var totals = new Array(nDays).fill(0);
Object.values(pagesByDay).forEach(function (arr) {
arr.forEach(function (v, i) { if (i < nDays) { totals[i] += v; } });
// Visiteurs uniques par jour — compté sur les IPs du top 200 (approximation)
var dailyVisitors = new Array(nDays).fill(0);
Object.keys(ipData).forEach(function (ip) {
var daily = ipData[ip].daily || [];
daily.forEach(function (v, i) { if (i < nDays && v > 0) { dailyVisitors[i]++; } });
});
trendChart(totals);
trendChart(dailyVisitors);
multiLineChart(pagesByDay, rows);
var html = '<div class="table-responsive"><table class="table table-sm table-hover mb-0 small w-100"><tbody>';
+1 -1
View File
@@ -2823,7 +2823,7 @@ switch ($action) {
if ($_artUuid !== null && preg_match('/^[0-9a-f\-]{36}$/i', $_artUuid)) {
@file_put_contents(
DATA_PATH . '/' . $_artUuid . '/visitors.json',
json_encode(array_merge($_artCounts, ['updated' => time()]), JSON_UNESCAPED_UNICODE)
json_encode($_artCounts + ['updated' => time()], JSON_UNESCAPED_UNICODE)
);
}
}
+1 -1
View File
@@ -1 +1 @@
1.6.34
1.6.35