v1.6.30 : agents utilisateur dans le drill-down IP
- Drill-down IP : user agents affichés sous l'adresse IP, top 5 par fréquence - AccessLogParser : regex COMBINED étendue pour capturer le UA (groupe 5) - Tracking ipAgents [ip => [ua => count]], ip_agents dans le résultat de stats() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,10 @@ function esc(s) {
|
||||
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function trunc(s, n) {
|
||||
return s.length > n ? s.slice(0, n) + '…' : s;
|
||||
}
|
||||
|
||||
// ── Visiteurs par pays ────────────────────────────────────────────────────────
|
||||
(function () {
|
||||
var el = document.getElementById('stats-country-container');
|
||||
@@ -29,7 +33,7 @@ function esc(s) {
|
||||
var d = ipData[ip];
|
||||
var key = d.asn || '__unknown__';
|
||||
if (!ipsByAsn[key]) { ipsByAsn[key] = []; }
|
||||
ipsByAsn[key].push({ ip: ip, hits: d.hits, daily: d.daily, paths: d.paths });
|
||||
ipsByAsn[key].push({ ip: ip, hits: d.hits, daily: d.daily, paths: d.paths, agents: d.agents || [] });
|
||||
});
|
||||
Object.keys(ipsByAsn).forEach(function (k) {
|
||||
ipsByAsn[k].sort(function (a, b) { return b.hits - a.hits; });
|
||||
@@ -85,8 +89,16 @@ function esc(s) {
|
||||
var asnKey = n.asn || '__unknown__';
|
||||
var ips = ipsByAsn[asnKey] || [];
|
||||
|
||||
// Lignes IP avec mini sparkline + chemins triés par date desc
|
||||
// Lignes IP : adresse + agents à gauche, sparkline, chemins, hits
|
||||
var ipRows = ips.slice(0, 20).map(function (ipInfo) {
|
||||
// Agents sous l'IP
|
||||
var agentsHtml = '';
|
||||
(ipInfo.agents || []).forEach(function (ua) {
|
||||
agentsHtml += '<div style="font-size:.65rem;color:#adb5bd;line-height:1.4;word-break:break-all">'
|
||||
+ esc(trunc(ua, 55)) + '</div>';
|
||||
});
|
||||
|
||||
// Chemins triés par date desc
|
||||
var articles = [], books = [];
|
||||
Object.keys(ipInfo.paths || {}).forEach(function (path) {
|
||||
var p = ipInfo.paths[path];
|
||||
@@ -100,10 +112,9 @@ function esc(s) {
|
||||
|
||||
function pathLine(p, prefix) {
|
||||
var slug = decodeURIComponent(p.path.replace(prefix, ''));
|
||||
var label = slug.length > 40 ? slug.slice(0, 40) + '…' : slug;
|
||||
return '<div style="font-size:.75rem;line-height:1.5">'
|
||||
+ '<a href="' + esc(p.path) + '" target="_blank" style="color:#495057">'
|
||||
+ esc(label) + '</a>'
|
||||
+ esc(trunc(slug, 40)) + '</a>'
|
||||
+ ' <span style="color:#adb5bd">(' + p.cnt + ')</span></div>';
|
||||
}
|
||||
|
||||
@@ -119,8 +130,10 @@ function esc(s) {
|
||||
if (!pathsHtml) { pathsHtml = '<span style="font-size:.75rem;color:#adb5bd">—</span>'; }
|
||||
|
||||
return '<div class="d-flex gap-2 py-2 border-bottom align-items-start">'
|
||||
+ '<code style="width:9rem;flex-shrink:0;font-size:.72rem;color:#6c757d;padding-top:2px">'
|
||||
+ esc(ipInfo.ip) + '</code>'
|
||||
+ '<div style="width:9rem;flex-shrink:0">'
|
||||
+ '<code style="font-size:.72rem;color:#6c757d">' + esc(ipInfo.ip) + '</code>'
|
||||
+ agentsHtml
|
||||
+ '</div>'
|
||||
+ '<div style="flex-shrink:0;padding-top:2px">' + ipSparkline(ipInfo.daily || []) + '</div>'
|
||||
+ '<div class="flex-grow-1">' + pathsHtml + '</div>'
|
||||
+ '<div class="text-end text-muted small" style="width:4rem;flex-shrink:0;padding-top:2px">'
|
||||
@@ -383,12 +396,11 @@ function esc(s) {
|
||||
|
||||
var legend = series.map(function (s, si) {
|
||||
var color = COLORS[si % COLORS.length];
|
||||
var short = s.title.length > 32 ? s.title.slice(0, 32) + '…' : s.title;
|
||||
return '<span class="d-inline-flex align-items-center gap-1 me-3 mb-1 small">'
|
||||
+ '<svg width="16" height="3" style="flex-shrink:0"><line x1="0" y1="1.5" x2="16" y2="1.5"'
|
||||
+ ' stroke="' + color + '" stroke-width="2.5" stroke-linecap="round"/></svg>'
|
||||
+ '<span class="text-truncate" style="max-width:160px" title="' + esc(s.title) + '">'
|
||||
+ esc(short) + '</span></span>';
|
||||
+ esc(trunc(s.title, 32)) + '</span></span>';
|
||||
}).join('');
|
||||
|
||||
el.innerHTML =
|
||||
|
||||
@@ -2748,6 +2748,7 @@ switch ($action) {
|
||||
'country' => $info['country'],
|
||||
'daily' => $daily,
|
||||
'paths' => $accessStats['ip_top_paths'][$ip] ?? [],
|
||||
'agents' => $accessStats['ip_agents'][$ip] ?? [],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.6.29
|
||||
1.6.30
|
||||
|
||||
Reference in New Issue
Block a user