- Add GeoIP class with provider chain: local DB-IP Lite, MaxMind .mmdb, external API - Add built-in pure-PHP MMDBReader so .mmdb works without Composer - Add cli/geoip-update.php to download DB-IP Lite and build a compact binary index - Add cli/generate-world-map.php to generate the world map SVG from Natural Earth TopoJSON - Add Analytics class aggregating stats in admin/storage/stats.json with LOCK_EX - Add admin statistics page with choropleth world map, country list, top pages, daily chart, referrers and a period filter - Add GeoIP and privacy settings with database update and stats reset buttons - Add optional IP anonymization and configurable retention period - Add country field to requests.log (parser accepts 7, 8 or 9 fields) - Add country column to request log and KPI cards to the dashboard - Ignore GeoIP binaries and stats.json in Git - Update TODO.md, guides and version to 1.9.0
182 lines
9.1 KiB
PHP
182 lines
9.1 KiB
PHP
<h2 class="mb-4"><i class="bi bi-speedometer2"></i> Dashboard</h2>
|
|
|
|
<?php
|
|
$aTotals = $analyticsSummary['totals'] ?? [];
|
|
$aCountries = $analyticsSummary['countries'] ?? [];
|
|
$topCountry = null;
|
|
foreach ($aCountries as $cc => $cnt) {
|
|
if ($cc !== 'UNKNOWN') { $topCountry = $cc; break; }
|
|
}
|
|
?>
|
|
<div class="row g-4 mb-4">
|
|
<div class="col-md-4">
|
|
<div class="card stat-card shadow-sm">
|
|
<div class="card-body d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="text-muted mb-1">Weergaven (30 dagen)</h6>
|
|
<h3 class="mb-0"><?= number_format((int)($aTotals['views'] ?? 0), 0, ',', '.') ?></h3>
|
|
</div>
|
|
<i class="bi bi-eye stat-icon text-primary"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card stat-card shadow-sm">
|
|
<div class="card-body d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="text-muted mb-1">Unieke bezoekers (30 dagen)</h6>
|
|
<h3 class="mb-0"><?= number_format((int)($aTotals['uniques'] ?? 0), 0, ',', '.') ?></h3>
|
|
</div>
|
|
<i class="bi bi-people stat-icon text-success"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card stat-card shadow-sm">
|
|
<div class="card-body d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="text-muted mb-1">Grootste land</h6>
|
|
<h3 class="mb-0">
|
|
<?= GeoIP::getCountryFlagEmoji($topCountry) ?>
|
|
<span class="fs-5"><?= htmlspecialchars(GeoIP::getCountryName($topCountry)) ?></span>
|
|
</h3>
|
|
</div>
|
|
<a href="/admin/statistics" class="btn btn-sm btn-outline-secondary">Statistieken →</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-4 mb-4">
|
|
<div class="col-md-3">
|
|
<div class="card stat-card shadow-sm">
|
|
<div class="card-body d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="text-muted mb-1">Pagina's</h6>
|
|
<h3 class="mb-0"><?= $stats['pages'] ?></h3>
|
|
</div>
|
|
<i class="bi bi-file-earmark-text stat-icon text-primary"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card stat-card shadow-sm">
|
|
<div class="card-body d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="text-muted mb-1">Mappen</h6>
|
|
<h3 class="mb-0"><?= $stats['directories'] ?></h3>
|
|
</div>
|
|
<i class="bi bi-folder stat-icon text-warning"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card stat-card shadow-sm">
|
|
<div class="card-body d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="text-muted mb-1">Plugins</h6>
|
|
<h3 class="mb-0"><?= $stats['plugins'] ?></h3>
|
|
</div>
|
|
<i class="bi bi-plug stat-icon text-success"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card stat-card shadow-sm">
|
|
<div class="card-body d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="text-muted mb-1">Content grootte</h6>
|
|
<h3 class="mb-0"><?= $stats['content_size'] ?></h3>
|
|
</div>
|
|
<i class="bi bi-hdd stat-icon text-info"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-4">
|
|
<div class="col-md-6">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header"><i class="bi bi-info-circle"></i> Site informatie</div>
|
|
<div class="card-body">
|
|
<table class="table table-sm mb-0">
|
|
<tr><td class="text-muted">Site titel</td><td><?= htmlspecialchars($siteConfig['site_title'] ?? 'CodePress') ?></td></tr>
|
|
<tr><td class="text-muted">Standaard taal</td><td><?= htmlspecialchars($siteConfig['language']['default'] ?? 'nl') ?></td></tr>
|
|
<tr><td class="text-muted">Auteur</td><td><?= htmlspecialchars($siteConfig['author']['name'] ?? '-') ?></td></tr>
|
|
<tr><td class="text-muted">CodePress versie</td><td><?= htmlspecialchars($stats['cms_version']) ?></td></tr>
|
|
<tr><td class="text-muted">PHP versie</td><td><?= $stats['php_version'] ?></td></tr>
|
|
<tr><td class="text-muted">Besturingssysteem</td><td><?= htmlspecialchars($stats['os']) ?></td></tr>
|
|
<tr><td class="text-muted">Config geladen</td><td><?= $stats['config_exists'] ? '<span class="badge bg-success">Ja</span>' : '<span class="badge bg-danger">Nee</span>' ?></td></tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<span><i class="bi bi-activity"></i> Recente activiteit</span>
|
|
<a href="/admin/logs?tab=admin" class="btn btn-sm btn-outline-secondary">Bekijk alle →</a>
|
|
</div>
|
|
<div class="card-body" style="max-height: 300px; overflow-y: auto;">
|
|
<?php if (empty($recentLogs)): ?>
|
|
<p class="text-muted mb-0">Geen activiteit geregistreerd.</p>
|
|
<?php else: ?>
|
|
<ul class="list-unstyled mb-0">
|
|
<?php foreach ($recentLogs as $log): ?>
|
|
<li class="mb-2 pb-2 border-bottom small">
|
|
<span class="text-muted"><?= htmlspecialchars($log['time']) ?></span>
|
|
<span class="badge bg-<?= $log['level'] === 'warning' ? 'warning text-dark' : ($log['level'] === 'error' ? 'danger' : 'info') ?> me-1"><?= htmlspecialchars($log['level']) ?></span>
|
|
<code class="text-muted"><?= htmlspecialchars($log['ip']) ?></code>
|
|
<?= htmlspecialchars($log['message']) ?>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<span><i class="bi bi-globe"></i> Recente requests</span>
|
|
<a href="/admin/logs?tab=requests" class="btn btn-sm btn-outline-secondary">Bekijk alle →</a>
|
|
</div>
|
|
<div class="card-body" style="max-height: 300px; overflow-y: auto;">
|
|
<?php if (empty($recentRequests)): ?>
|
|
<p class="text-muted mb-0">Geen requests geregistreerd.</p>
|
|
<?php else: ?>
|
|
<ul class="list-unstyled mb-0">
|
|
<?php foreach ($recentRequests as $log): ?>
|
|
<li class="mb-2 pb-2 border-bottom small d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<span class="text-muted me-1"><?= htmlspecialchars($log['time']) ?></span>
|
|
<code class="text-muted me-1"><?= htmlspecialchars($log['ip']) ?></code>
|
|
<span class="fw-bold"><?= htmlspecialchars($log['page']) ?></span>
|
|
</div>
|
|
<?php if (!empty($log['visitor_info'])): ?>
|
|
<span class="badge bg-<?= $log['visitor_info']['badge'] ?>" title="<?= htmlspecialchars($log['ua']) ?>">
|
|
<i class="bi <?= $log['visitor_info']['icon'] ?>"></i> <?= $log['visitor_info']['label'] ?>
|
|
</span>
|
|
<?php endif; ?>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header"><i class="bi bi-lightning"></i> Snelle acties</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
<a href="/admin/content-new" class="btn btn-outline-primary"><i class="bi bi-plus-lg"></i> Nieuwe pagina</a>
|
|
<a href="/admin/config" class="btn btn-outline-secondary"><i class="bi bi-sliders"></i> Configuratie bewerken</a>
|
|
<a href="/admin/content" class="btn btn-outline-info"><i class="bi bi-folder2-open"></i> Content beheren</a>
|
|
<a href="index.php" target="_blank" class="btn btn-outline-success"><i class="bi bi-box-arrow-up-right"></i> Website bekijken</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|