feat : "Meilleures audiences" + admin/stats pages via flux RSS XML (v1.6.6)
- post_list.php : section AJAX qui lit /trending?period=1h en XML (DOMParser) — plus de rendu PHP - admin_stats.php : colonne "Pages les plus visitées" chargée en AJAX depuis /trending?period=14d XML - index.php/stats : suppression de topGrouped pour /post/ ; seuls /book/ et ASN restent côté serveur Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+51
-38
@@ -2,7 +2,6 @@
|
||||
$_statsSaved = isset($_GET['saved']);
|
||||
$_statsError = ($_GET['error'] ?? '') === 'write';
|
||||
$_readable = $adminData['stats_readable'] ?? false;
|
||||
$_pages = $adminData['stats_pages'] ?? [];
|
||||
$_books = $adminData['stats_books'] ?? [];
|
||||
$_asList = $adminData['stats_as'] ?? [];
|
||||
$_asGroups = $adminData['stats_as_groups'] ?? [];
|
||||
@@ -23,51 +22,19 @@ $_activeGroup = trim($_GET['group'] ?? '');
|
||||
</div>
|
||||
<?php else: ?>
|
||||
|
||||
<p class="text-muted small mb-4">14 derniers jours · visiteurs uniques · cache 60 s</p>
|
||||
<p class="text-muted small mb-4">14 derniers jours · visiteurs uniques · flux RSS XML</p>
|
||||
|
||||
<div class="row g-4">
|
||||
|
||||
<!-- Pages -->
|
||||
<!-- 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"><?= count($_pages) ?> URLs</span>
|
||||
<span class="text-muted" id="stats-pages-count"></span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<?php if (empty($_pages)): ?>
|
||||
<p class="text-muted p-3 mb-0">Aucune donnée.</p>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0 small">
|
||||
<tbody>
|
||||
<?php
|
||||
$maxP = max($_pages) ?: 1;
|
||||
$rankP = 0;
|
||||
foreach ($_pages as $url => $hits):
|
||||
$rankP++;
|
||||
$slug = rawurldecode(substr($url, 6));
|
||||
$pct = round($hits / $maxP * 100);
|
||||
?>
|
||||
<tr>
|
||||
<td class="text-muted ps-3" style="width:2rem"><?= $rankP ?></td>
|
||||
<td>
|
||||
<a href="<?= htmlspecialchars($url) ?>" target="_blank"
|
||||
class="text-decoration-none text-truncate d-block" style="max-width:260px"
|
||||
title="<?= htmlspecialchars($slug) ?>">
|
||||
<?= htmlspecialchars($slug) ?>
|
||||
</a>
|
||||
<div class="progress mt-1" style="height:3px">
|
||||
<div class="progress-bar" style="width:<?= $pct ?>%"></div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-end fw-semibold pe-3"><?= number_format($hits, 0, ',', '\u{202F}') ?> <span class="text-muted fw-normal">vis.</span></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="card-body p-0" id="stats-pages-container">
|
||||
<p class="text-muted p-3 mb-0">Chargement…</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -238,4 +205,50 @@ document.getElementById('as-groups-list').addEventListener('click', e => {
|
||||
e.target.closest('.as-group-row').remove();
|
||||
}
|
||||
});
|
||||
|
||||
// ── Chargement des pages via le flux RSS XML ──────────────────────────────────
|
||||
(function(){
|
||||
var container = document.getElementById('stats-pages-container');
|
||||
var badge = document.getElementById('stats-pages-count');
|
||||
if (!container) return;
|
||||
function esc(s){ return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
||||
fetch('/trending?period=14d')
|
||||
.then(function(r){ return r.ok ? r.text() : Promise.reject(); })
|
||||
.then(function(xml){
|
||||
var doc = new DOMParser().parseFromString(xml, 'application/xml');
|
||||
var items = Array.from(doc.querySelectorAll('item'));
|
||||
if (!items.length) {
|
||||
container.innerHTML = '<p class="text-muted p-3 mb-0">Aucune donnée.</p>';
|
||||
return;
|
||||
}
|
||||
var rows = items.map(function(item){
|
||||
var raw = (item.querySelector('title') || {textContent:''}).textContent;
|
||||
var link = ((item.querySelector('link') || {}).textContent || '').trim();
|
||||
var m = raw.match(/\((\d+)\s+visiteurs?\)$/);
|
||||
var vis = m ? parseInt(m[1], 10) : 0;
|
||||
var title = raw.replace(/\s*\(\d+\s+visiteurs?\)$/, '');
|
||||
var slug = decodeURIComponent(link.replace(/.*\/post\//, ''));
|
||||
return {title: title, link: link, slug: slug, vis: vis};
|
||||
});
|
||||
var maxV = Math.max.apply(null, rows.map(function(r){ return r.vis; })) || 1;
|
||||
var html = '<div class="table-responsive"><table class="table table-sm table-hover mb-0 small"><tbody>';
|
||||
rows.forEach(function(row, i){
|
||||
var pct = Math.round(row.vis / maxV * 100);
|
||||
var vis = row.vis.toLocaleString('fr-FR');
|
||||
html += '<tr>'
|
||||
+ '<td class="text-muted ps-3" style="width:2rem">' + (i+1) + '</td>'
|
||||
+ '<td><a href="' + esc(row.link) + '" target="_blank" class="text-decoration-none text-truncate d-block" style="max-width:260px" title="' + esc(row.slug) + '">'
|
||||
+ esc(row.title || row.slug) + '</a>'
|
||||
+ '<div class="progress mt-1" style="height:3px"><div class="progress-bar" style="width:' + pct + '%"></div></div></td>'
|
||||
+ '<td class="text-end fw-semibold pe-3">' + vis + ' <span class="text-muted fw-normal">vis.</span></td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
html += '</tbody></table></div>';
|
||||
if (badge) badge.textContent = rows.length + ' URLs';
|
||||
container.innerHTML = html;
|
||||
})
|
||||
.catch(function(){
|
||||
container.innerHTML = '<p class="text-muted p-3 mb-0">Impossible de charger le flux.</p>';
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user