Files
syslogdash/pages/worldmap.php
T
2026-07-30 16:26:21 +02:00

223 lines
9.7 KiB
PHP

<?php
use SyslogDash\SyslogQuery;
use SyslogDash\GeoIP;
$period = (int)($_GET['period'] ?? 30);
if (!in_array($period, [7, 30, 90, 365, 0])) $period = 30;
$sq = new SyslogQuery();
$countryStats = $sq->getCountryStats($period);
$daily = $sq->getDailyStats($period);
$totalLogs = array_sum(array_column($countryStats, 'count'));
$mapCountries = [];
foreach ($countryStats as $c) {
$mapCountries[$c['code']] = $c['count'];
}
$drillCountry = $_GET['country'] ?? null;
$drillHosts = [];
if ($drillCountry) {
foreach ($countryStats as $c) {
if (strtoupper($c['code']) === strtoupper($drillCountry)) {
$drillHosts = $c['hosts'];
break;
}
}
}
$svgPath = __DIR__ . '/assets/img/world-map.svg';
$svgContent = '';
if (file_exists($svgPath)) {
$svgContent = file_get_contents($svgPath);
$styles = '';
$maxCountry = count($countryStats) > 0 ? $countryStats[0]['count'] : 1;
foreach ($countryStats as $c) {
if ($c['code'] === 'UNKNOWN') continue;
$ratio = $c['count'] / $maxCountry;
if ($ratio > 0.66) $fill = '#052c65';
elseif ($ratio > 0.33) $fill = '#0d6efd';
elseif ($ratio > 0.1) $fill = '#6ea8fe';
else $fill = '#cfe2ff';
$styles .= "#{$c['code']} { fill: {$fill}; }";
}
$svgContent = str_replace('</style>', $styles . '</style>', $svgContent);
}
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="mb-0"><i class="bi bi-globe2 me-2"></i>Wereldkaart</h2>
<div class="btn-group btn-group-sm">
<a href="?page=worldmap&period=7" class="btn btn-outline-primary <?= $period === 7 ? 'active' : '' ?>">7 dagen</a>
<a href="?page=worldmap&period=30" class="btn btn-outline-primary <?= $period === 30 ? 'active' : '' ?>">30 dagen</a>
<a href="?page=worldmap&period=90" class="btn btn-outline-primary <?= $period === 90 ? 'active' : '' ?>">90 dagen</a>
<a href="?page=worldmap&period=365" class="btn btn-outline-primary <?= $period === 365 ? 'active' : '' ?>">365 dagen</a>
<a href="?page=worldmap&period=0" class="btn btn-outline-primary <?= $period === 0 ? 'active' : '' ?>">Alles</a>
</div>
</div>
<?php if ($drillCountry): ?>
<div class="alert alert-info d-flex justify-content-between align-items-center">
<span><i class="bi bi-funnel me-2"></i>Gefilterd op land: <strong><?= GeoIP::getCountryFlagEmoji($drillCountry) ?> <?= GeoIP::getCountryName($drillCountry) ?></strong></span>
<a href="?page=worldmap&period=<?= $period ?>" class="btn btn-sm btn-outline-danger"><i class="bi bi-x"></i> Filter wissen</a>
</div>
<?php endif; ?>
<div class="row g-4 mb-4">
<div class="col-md-3">
<div class="stat-card">
<div class="stat-icon bg-primary-subtle text-primary"><i class="bi bi-globe"></i></div>
<div class="stat-body">
<div class="stat-value"><?= count($countryStats) ?></div>
<div class="stat-label">Landen</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="stat-card">
<div class="stat-icon bg-success-subtle text-success"><i class="bi bi-envelope"></i></div>
<div class="stat-body">
<div class="stat-value"><?= number_format($totalLogs, 0, ',', '.') ?></div>
<div class="stat-label">Berichten</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="stat-card">
<div class="stat-icon bg-info-subtle text-info"><i class="bi bi-question-circle"></i></div>
<div class="stat-body">
<div class="stat-value"><?= $countryStats[0]['code'] === 'UNKNOWN' ? number_format($countryStats[0]['count'], 0, ',', '.') : '0' ?></div>
<div class="stat-label">Onbekend (geen GeoIP)</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="stat-card">
<div class="stat-icon bg-warning-subtle text-warning"><i class="bi bi-hdd-network"></i></div>
<div class="stat-body">
<div class="stat-value"><?= $sq->getUniqueHosts($period) ?></div>
<div class="stat-label">Unieke hosts</div>
</div>
</div>
</div>
</div>
<div class="card shadow-sm mb-4">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<span><i class="bi bi-map me-2"></i>Wereldkaart</span>
<small class="text-muted">
<span class="d-inline-block me-1" style="width:14px;height:14px;background:#cfe2ff;border-radius:2px;vertical-align:middle"></span> weinig
<span class="d-inline-block mx-1" style="width:14px;height:14px;background:#6ea8fe;border-radius:2px;vertical-align:middle"></span> gemiddeld
<span class="d-inline-block mx-1" style="width:14px;height:14px;background:#0d6efd;border-radius:2px;vertical-align:middle"></span> veel
<span class="d-inline-block mx-1" style="width:14px;height:14px;background:#052c65;border-radius:2px;vertical-align:middle"></span> meest
</small>
</div>
<div class="card-body p-3 position-relative world-map-wrapper">
<?php if ($svgContent): ?>
<div id="worldMapContainer" style="max-width:1000px;margin:0 auto;cursor:pointer;">
<?= $svgContent ?>
</div>
<div id="mapTooltip" class="map-tooltip" style="display:none;"></div>
<?php else: ?>
<div class="alert alert-warning mb-0">
<i class="bi bi-exclamation-triangle me-2"></i>Geen wereldkaart SVG gevonden. Genereer deze met:
<code>php cli/generate-map.php</code>
</div>
<?php endif; ?>
</div>
</div>
<?php if ($drillCountry && !empty($drillHosts)): ?>
<div class="card shadow-sm mb-4">
<div class="card-header bg-white"><i class="bi bi-hdd-network me-2"></i>Hosts in <?= GeoIP::getCountryName($drillCountry) ?></div>
<div class="card-body p-0">
<div class="list-group list-group-flush">
<?php foreach ($drillHosts as $host): ?>
<a href="?page=logs&host=<?= urlencode($host) ?>" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
<?= htmlspecialchars($host) ?>
<i class="bi bi-arrow-right text-muted"></i>
</a>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endif; ?>
<div class="card shadow-sm">
<div class="card-header bg-white"><i class="bi bi-list-columns me-2"></i>Landen (<?= count($countryStats) ?>)</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>#</th>
<th>Land</th>
<th>Berichten</th>
<th>%</th>
<th></th>
</tr>
</thead>
<tbody>
<?php $maxC = $countryStats[0]['count'] ?? 1; ?>
<?php foreach ($countryStats as $i => $c): ?>
<tr class="<?= $drillCountry && strtoupper($c['code']) === strtoupper($drillCountry) ? 'table-active' : '' ?>">
<td class="text-muted"><?= $i + 1 ?></td>
<td>
<a href="?page=worldmap&period=<?= $period ?>&country=<?= urlencode($c['code']) ?>" class="text-decoration-none">
<?= GeoIP::getCountryFlagEmoji($c['code']) ?>
<?= GeoIP::getCountryName($c['code']) ?>
</a>
</td>
<td><?= number_format($c['count'], 0, ',', '.') ?></td>
<td><?= $totalLogs > 0 ? round($c['count'] / $totalLogs * 100, 1) : 0 ?>%</td>
<td>
<div class="progress" style="height:6px;min-width:80px">
<div class="progress-bar" style="width:<?= $c['count'] / $maxC * 100 ?>%"></div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
var counts = <?= json_encode($mapCountries) ?>;
var container = document.getElementById('worldMapContainer');
var tooltip = document.getElementById('mapTooltip');
if (!container || !tooltip) return;
container.addEventListener('mousemove', function (e) {
var target = e.target.closest('.country');
if (!target) { tooltip.style.display = 'none'; return; }
var code = target.getAttribute('id');
var name = target.getAttribute('data-name') || code;
var n = counts[code] || 0;
tooltip.textContent = name + ': ' + n.toLocaleString('nl-NL') + ' bericht' + (n === 1 ? '' : 'en');
tooltip.style.display = 'block';
var r = container.getBoundingClientRect();
var x = e.clientX - r.left + 12;
var y = e.clientY - r.top + 12;
if (x + 200 > r.width) x = e.clientX - r.left - 200;
tooltip.style.left = x + 'px';
tooltip.style.top = y + 'px';
});
container.addEventListener('mouseleave', function () {
tooltip.style.display = 'none';
});
container.addEventListener('click', function (e) {
var target = e.target.closest('.country');
if (!target) return;
var code = target.getAttribute('id');
if (code) {
window.location.href = '?page=worldmap&period=<?= $period ?>&country=' + encodeURIComponent(code);
}
});
});
</script>
<?php $sq = null; ?>