- 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
117 lines
7.3 KiB
PHP
117 lines
7.3 KiB
PHP
<h2 class="mb-4"><i class="bi bi-journal-text"></i> Logs</h2>
|
|
|
|
<ul class="nav nav-tabs mb-3" id="logTabs" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link <?= $activeTab === 'admin' ? 'active' : '' ?>" id="admin-log-tab" data-bs-toggle="tab" data-bs-target="#admin-log" type="button" role="tab">Activiteiten log</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link <?= $activeTab === 'requests' ? 'active' : '' ?>" id="request-log-tab" data-bs-toggle="tab" data-bs-target="#request-log" type="button" role="tab">Requests log</button>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="tab-content">
|
|
<!-- Admin activity log -->
|
|
<div class="tab-pane fade <?= $activeTab === 'admin' ? 'show active' : '' ?>" id="admin-log" role="tabpanel">
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<small class="text-muted"><?= count($adminLogs) ?> regels</small>
|
|
<div>
|
|
<a href="/admin/logs?tab=admin&download=1" class="btn btn-sm btn-outline-secondary"><i class="bi bi-download"></i> Download</a>
|
|
<a href="/admin/logs?tab=admin&clear=1" class="btn btn-sm btn-outline-danger" onclick="return confirm('Activiteiten log wissen?')"><i class="bi bi-trash"></i> Wissen</a>
|
|
</div>
|
|
</div>
|
|
<div class="card shadow-sm">
|
|
<div class="card-body p-0" style="max-height: 500px; overflow-y: auto;">
|
|
<?php if (empty($adminLogs)): ?>
|
|
<p class="text-muted p-3 mb-0">Geen activiteiten geregistreerd.</p>
|
|
<?php else: ?>
|
|
<table class="table table-sm table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Tijd</th>
|
|
<th>Niveau</th>
|
|
<th>IP</th>
|
|
<th>Bericht</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($adminLogs as $log): ?>
|
|
<tr>
|
|
<td class="text-nowrap small text-muted"><?= htmlspecialchars($log['time']) ?></td>
|
|
<td><span class="badge bg-<?= $log['level'] === 'warning' ? 'warning text-dark' : ($log['level'] === 'error' ? 'danger' : 'info') ?>"><?= htmlspecialchars($log['level']) ?></span></td>
|
|
<td><code class="small"><?= htmlspecialchars($log['ip']) ?></code></td>
|
|
<td><?= htmlspecialchars($log['message']) ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Request log -->
|
|
<div class="tab-pane fade <?= $activeTab === 'requests' ? 'show active' : '' ?>" id="request-log" role="tabpanel">
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<small class="text-muted"><?= count($requestLogs) ?> regels</small>
|
|
<div>
|
|
<a href="/admin/logs?tab=requests&download=1" class="btn btn-sm btn-outline-secondary"><i class="bi bi-download"></i> Download</a>
|
|
<a href="/admin/logs?tab=requests&clear=1" class="btn btn-sm btn-outline-danger" onclick="return confirm('Requestlog wissen?')"><i class="bi bi-trash"></i> Wissen</a>
|
|
</div>
|
|
</div>
|
|
<div class="card shadow-sm">
|
|
<div class="card-body p-0" style="max-height: 500px; overflow-y: auto;">
|
|
<?php if (empty($requestLogs)): ?>
|
|
<p class="text-muted p-3 mb-0">Geen requests geregistreerd.</p>
|
|
<?php else: ?>
|
|
<table class="table table-sm table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Tijd</th>
|
|
<th>IP</th>
|
|
<th>Land</th>
|
|
<th>Pagina</th>
|
|
<th>Type / Gebruiker</th>
|
|
<th>Status</th>
|
|
<th>Taal</th>
|
|
<th>User agent</th>
|
|
<th>Referrer</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($requestLogs as $log): ?>
|
|
<tr class="<?= str_starts_with($log['status'] ?? 'ok', 'blocked') ? 'table-danger' : '' ?>">
|
|
<td class="text-nowrap small text-muted"><?= htmlspecialchars($log['time']) ?></td>
|
|
<td><code class="small"><?= htmlspecialchars($log['ip']) ?></code></td>
|
|
<td class="text-nowrap small" title="<?= htmlspecialchars(GeoIP::getCountryName($log['country'] ?: null)) ?>">
|
|
<?= GeoIP::getCountryFlagEmoji($log['country'] ?: null) ?>
|
|
<span class="text-muted"><?= htmlspecialchars($log['country'] ?: '—') ?></span>
|
|
</td>
|
|
<td><?= htmlspecialchars($log['page']) ?></td>
|
|
<td>
|
|
<span class="badge bg-<?= $log['visitor_info']['badge'] ?>">
|
|
<i class="bi <?= $log['visitor_info']['icon'] ?>"></i> <?= $log['visitor_info']['label'] ?>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<?php if (str_starts_with($log['status'] ?? 'ok', 'blocked')): ?>
|
|
<span class="badge bg-danger" title="<?= htmlspecialchars($log['status']) ?>">
|
|
<i class="bi bi-shield-x"></i> Geblokkeerd
|
|
</span>
|
|
<?php else: ?>
|
|
<span class="badge bg-success" title="Toegestaan">
|
|
<i class="bi bi-check-circle"></i> OK
|
|
</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td><?= htmlspecialchars($log['lang']) ?></td>
|
|
<td class="small text-muted" title="<?= htmlspecialchars($log['ua']) ?>"><?= htmlspecialchars(substr($log['ua'], 0, 50)) ?><?= strlen($log['ua']) > 50 ? '…' : '' ?></td>
|
|
<td class="small text-muted" title="<?= htmlspecialchars($log['referrer']) ?>"><?= htmlspecialchars(substr($log['referrer'], 0, 30)) ?><?= strlen($log['referrer']) > 30 ? '…' : '' ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|