v1.6.33 : exclusion AS, compteurs 7/14/30j, 👍 uniquement
- Carte visiteurs uniques non-bot : 7 / 14 / 30 jours en tête de /admin/stats - Bouton ✕ par AS pour l'exclure des stats ; section AS exclus avec ↺ - Alerte IPs sans résolution AS dans la carte pays - Parser : fenêtre 30 jours, calcul visiteurs uniques toutes IPs non-bot - Graphiques adaptés à 30 jours (labels x/3) - Réactions articles : 👍 uniquement (suppression 🔥 et 🤔) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+75
-15
@@ -2765,7 +2765,7 @@ switch ($action) {
|
||||
if ($statsRaw === null) {
|
||||
$cutoff14 = strtotime('-14 days midnight') ?: (time() - 14 * 86400);
|
||||
$tParser = new TrendingParser('/var/log/apache2', apacheAccessLog());
|
||||
$accessParser = new AccessLogParser('/var/log/apache2', apacheAccessLog(), '', 600, 14, $botPatterns);
|
||||
$accessParser = new AccessLogParser('/var/log/apache2', apacheAccessLog(), '', 600, 30, $botPatterns);
|
||||
$accessStats = $accessParser->stats();
|
||||
$topIps = array_slice($accessStats['ips'], 0, 200, true);
|
||||
$asnMap = (new AsnLookup())->batchLookup(array_keys($topIps));
|
||||
@@ -2785,23 +2785,31 @@ switch ($action) {
|
||||
}
|
||||
|
||||
$statsRaw = [
|
||||
'readable' => $accessParser->isReadable(),
|
||||
'books' => $tParser->top($cutoff14, 20, ['/book/']),
|
||||
'as' => AsnLookup::aggregateByAs($topIps, $asnMap),
|
||||
'pages_by_day' => $accessStats['pages_by_day'] ?? [],
|
||||
'ip_data' => $ipData,
|
||||
'all_uas' => $accessStats['all_uas'] ?? [],
|
||||
'readable' => $accessParser->isReadable(),
|
||||
'books' => $tParser->top($cutoff14, 20, ['/book/']),
|
||||
'as' => AsnLookup::aggregateByAs($topIps, $asnMap),
|
||||
'pages_by_day' => $accessStats['pages_by_day'] ?? [],
|
||||
'ip_data' => $ipData,
|
||||
'all_uas' => $accessStats['all_uas'] ?? [],
|
||||
'unique_visitors' => $accessStats['unique_visitors'] ?? [7 => 0, 14 => 0, 30 => 0],
|
||||
];
|
||||
@file_put_contents($statsCacheFile, json_encode($statsRaw));
|
||||
}
|
||||
$adminData['stats_readable'] = $statsRaw['readable'];
|
||||
$adminData['stats_books'] = $statsRaw['books'];
|
||||
$adminData['stats_as'] = $statsRaw['as'];
|
||||
$adminData['stats_as_groups'] = AsnLookup::applyGroups($statsRaw['as'], asGroups());
|
||||
$adminData['as_groups'] = asGroups();
|
||||
$adminData['stats_pages_by_day'] = $statsRaw['pages_by_day'] ?? [];
|
||||
$adminData['stats_ip_data'] = $statsRaw['ip_data'] ?? [];
|
||||
$adminData['stats_all_uas'] = $statsRaw['all_uas'] ?? [];
|
||||
$adminData['stats_readable'] = $statsRaw['readable'];
|
||||
$adminData['stats_books'] = $statsRaw['books'];
|
||||
$adminData['stats_as'] = $statsRaw['as'];
|
||||
$adminData['stats_as_groups'] = AsnLookup::applyGroups($statsRaw['as'], asGroups());
|
||||
$adminData['as_groups'] = asGroups();
|
||||
$adminData['stats_pages_by_day'] = $statsRaw['pages_by_day'] ?? [];
|
||||
$adminData['stats_ip_data'] = $statsRaw['ip_data'] ?? [];
|
||||
$adminData['stats_all_uas'] = $statsRaw['all_uas'] ?? [];
|
||||
$adminData['stats_unique_visitors'] = $statsRaw['unique_visitors'] ?? [7 => 0, 14 => 0, 30 => 0];
|
||||
|
||||
// AS exclus (chargé en direct, pas mis en cache)
|
||||
$excludedAsFile = DATA_PATH . '/excluded_as.json';
|
||||
$adminData['excluded_as'] = is_file($excludedAsFile)
|
||||
? (json_decode((string) file_get_contents($excludedAsFile), true) ?: [])
|
||||
: [];
|
||||
}
|
||||
|
||||
if ($tab === 'categories') {
|
||||
@@ -3290,6 +3298,58 @@ switch ($action) {
|
||||
echo json_encode(['ok' => true, 'pattern' => $addPattern]);
|
||||
exit;
|
||||
|
||||
case 'admin_add_excluded_as':
|
||||
requireAuth();
|
||||
if (!isAdmin() || $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(403);
|
||||
exit;
|
||||
}
|
||||
$csrf = $_POST['_csrf'] ?? '';
|
||||
if ($csrf !== ($_session['csrf'] ?? '')) {
|
||||
http_response_code(403);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['ok' => false, 'error' => 'csrf']);
|
||||
exit;
|
||||
}
|
||||
$asn = trim((string) ($_POST['asn'] ?? ''));
|
||||
if ($asn === '') {
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['ok' => false, 'error' => 'empty']);
|
||||
exit;
|
||||
}
|
||||
$excludedAsFile = DATA_PATH . '/excluded_as.json';
|
||||
$excludedAs = is_file($excludedAsFile) ? (json_decode((string) file_get_contents($excludedAsFile), true) ?: []) : [];
|
||||
if (!in_array($asn, $excludedAs, true)) {
|
||||
$excludedAs[] = $asn;
|
||||
@file_put_contents($excludedAsFile, json_encode($excludedAs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
|
||||
}
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['ok' => true, 'asn' => $asn]);
|
||||
exit;
|
||||
|
||||
case 'admin_remove_excluded_as':
|
||||
requireAuth();
|
||||
if (!isAdmin() || $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(403);
|
||||
exit;
|
||||
}
|
||||
$csrf = $_POST['_csrf'] ?? '';
|
||||
if ($csrf !== ($_session['csrf'] ?? '')) {
|
||||
http_response_code(403);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['ok' => false, 'error' => 'csrf']);
|
||||
exit;
|
||||
}
|
||||
$asn = trim((string) ($_POST['asn'] ?? ''));
|
||||
$excludedAsFile = DATA_PATH . '/excluded_as.json';
|
||||
$excludedAs = is_file($excludedAsFile) ? (json_decode((string) file_get_contents($excludedAsFile), true) ?: []) : [];
|
||||
$excludedAs = array_values(array_filter($excludedAs, static fn ($a) => $a !== $asn));
|
||||
@file_put_contents($excludedAsFile, json_encode($excludedAs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['ok' => true, 'asn' => $asn]);
|
||||
exit;
|
||||
|
||||
case 'admin_create_role':
|
||||
requireAuth();
|
||||
if (!isAdmin() || $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
|
||||
Reference in New Issue
Block a user