add map to dashboard

This commit is contained in:
lelo 2025-12-20 10:01:26 +00:00
parent 51f99b75d4
commit 44ec9fc0a0
2 changed files with 146 additions and 28 deletions

View File

@ -572,6 +572,24 @@ def dashboard():
cursor = log_db.execute(query, params_for_filter)
locations = cursor.fetchall()
# Map data grouped by coordinates
map_rows = []
try:
query = f'''
SELECT city, country, latitude, longitude, COUNT(*) as count
FROM file_access_log
WHERE timestamp >= ? {filetype_filter_sql}
AND latitude IS NOT NULL AND longitude IS NOT NULL
GROUP BY city, country, latitude, longitude
ORDER BY count DESC
'''
with log_db:
cursor = log_db.execute(query, params_for_filter)
map_rows = cursor.fetchall()
except sqlite3.OperationalError as exc:
# Keep dashboard working even if older DBs lack location columns
print(f"[dashboard] map query skipped: {exc}")
# 7. Summary stats
# total_accesses
query = f'''
@ -629,6 +647,20 @@ def dashboard():
location_data.sort(key=lambda x: x['count'], reverse=True)
location_data = location_data[:20]
# Prepare map data (limit to keep map readable)
map_data = []
for city, country, lat, lon, cnt in map_rows:
if lat is None or lon is None:
continue
map_data.append({
'city': city,
'country': country,
'lat': lat,
'lon': lon,
'count': cnt
})
map_data = map_data[:200]
title_short = app_config.get('TITLE_SHORT', 'Default Title')
title_long = app_config.get('TITLE_LONG' , 'Default Title')
@ -644,6 +676,7 @@ def dashboard():
unique_user=unique_user,
cached_percentage=cached_percentage,
timeframe_data=timeframe_data,
map_data=map_data,
admin_enabled=auth.is_admin(),
title_short=title_short,
title_long=title_long

View File

@ -4,6 +4,20 @@
{# page title #}
{% block title %}Dashboard{% endblock %}
{% block head_extra %}
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<style>
#dashboard-map {
width: 100%;
height: 420px;
min-height: 320px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
</style>
{% endblock %}
{# page content #}
{% block content %}
@ -185,35 +199,47 @@
</div>
</div>
<!-- IP Address Access Data -->
<div class="card mb-4">
<div class="card-header">
Verteilung der Zugriffe
<!-- Map + Location Distribution -->
<div class="row mb-4">
<div class="col-lg-7 mb-3">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Zugriffe auf der Karte</h5>
<div id="dashboard-map"></div>
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>Anzahl Downloads</th>
<th>Stadt</th>
<th>Land</th>
</tr>
</thead>
<tbody>
{% for loc in location_data %}
<tr>
<td>{{ loc.count }}</td>
<td>{{ loc.city }}</td>
<td>{{ loc.country }}</td>
</tr>
{% else %}
<tr>
<td colspan="3">No access data available for the selected timeframe.</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="col-lg-5 mb-3">
<div class="card h-100">
<div class="card-header">
Verteilung der Zugriffe
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered mb-0">
<thead>
<tr>
<th>Anzahl Downloads</th>
<th>Stadt</th>
<th>Land</th>
</tr>
</thead>
<tbody>
{% for loc in location_data %}
<tr>
<td>{{ loc.count }}</td>
<td>{{ loc.city }}</td>
<td>{{ loc.country }}</td>
</tr>
{% else %}
<tr>
<td colspan="3">No access data available for the selected timeframe.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
@ -225,6 +251,65 @@
{% block scripts %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const rawMapData = {{ map_data|tojson|default('[]') }};
const mapData = Array.isArray(rawMapData) ? rawMapData : [];
// Leaflet map with aggregated downloads (fail-safe so charts still render)
try {
(function initDashboardMap() {
const mapElement = document.getElementById('dashboard-map');
if (!mapElement || typeof L === 'undefined') return;
const map = L.map(mapElement).setView([50, 10], 4);
const bounds = L.latLngBounds();
const defaultCenter = [50, 10];
const defaultZoom = 4;
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
maxZoom: 19
}).addTo(map);
mapData.forEach(point => {
if (point.lat === null || point.lon === null) return;
const radius = Math.max(6, 4 + Math.log(point.count + 1) * 4);
const marker = L.circleMarker([point.lat, point.lon], {
radius,
color: '#ff0000',
fillColor: '#ff4444',
fillOpacity: 0.75,
weight: 2
}).addTo(map);
marker.bindPopup(`
<strong>${point.city}, ${point.country}</strong><br>
Downloads: ${point.count}
`);
bounds.extend([point.lat, point.lon]);
});
if (mapData.length === 0) {
const msg = document.createElement('div');
msg.textContent = 'Keine Geo-Daten für den ausgewählten Zeitraum.';
msg.className = 'text-muted small mt-3';
mapElement.appendChild(msg);
}
if (!bounds.isEmpty()) {
map.fitBounds(bounds, { padding: [24, 24] });
} else {
map.setView(defaultCenter, defaultZoom);
}
const refreshSize = () => setTimeout(() => map.invalidateSize(), 150);
refreshSize();
window.addEventListener('resize', refreshSize);
window.addEventListener('orientationchange', refreshSize);
})();
} catch (err) {
console.error('Dashboard map init failed:', err);
}
// Data passed from the backend as JSON
let distinctDeviceData = {{ distinct_device_data|tojson }};
let timeframeData = {{ timeframe_data|tojson }};