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:
@@ -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 ? ' <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>';
|
||||
|
||||
Reference in New Issue
Block a user