diff --git a/notes/deploy.md b/notes/deploy.md index 10b1e16..63e1d94 100644 --- a/notes/deploy.md +++ b/notes/deploy.md @@ -160,6 +160,10 @@ Image=localhost/kver:latest PublishPort=127.0.0.1:7777:8080 Volume=kver-data:/app/data Volume=kver-media:/app/static/media +# Optional: ASN-Auflösung der Impressions. Pfad zeigt ins kver-data-Volume, +# daher zur Laufzeit setzen (nicht ins Image backen). Zeile weglassen, wenn +# keine GeoLite2-ASN.mmdb vorhanden ist -> ASN-Auflösung bleibt still aus. +Environment=KVER_GEOIP_ASN=/app/data/GeoLite2-ASN.mmdb # Healthcheck hier statt im Containerfile: Podman baut OCI-Images, # die kennen kein eingebettetes HEALTHCHECK. HealthCmd=wget -qO /dev/null http://localhost:8080/entry/feed/0 diff --git a/stats.go b/stats.go index 4d46d2a..3eef3da 100644 --- a/stats.go +++ b/stats.go @@ -219,23 +219,6 @@ func handleStatsDetail(w http.ResponseWriter, r *http.Request) { } } - // Beliebteste Seiten nach Impressions. - topPages := []map[string]any{} - if rows, err := db.Query( - `SELECT path, SUM(hits) AS h - FROM impression - GROUP BY path - ORDER BY h DESC - LIMIT 10`); err == nil { - defer rows.Close() - for rows.Next() { - var path string - var hits int64 - rows.Scan(&path, &hits) - topPages = append(topPages, map[string]any{"path": path, "hits": hits}) - } - } - // Netze nach Betreiber (autonomes System). Leere asn ('') -> "(unbekannt)". topASNs := []map[string]any{} if rows, err := db.Query( @@ -264,7 +247,6 @@ func handleStatsDetail(w http.ResponseWriter, r *http.Request) { "impressions_total": imprTotal, "visitors_total": visitorsTotal, "daily": daily, - "top_pages": topPages, "top_asns": topASNs, }) } diff --git a/web/css/app.css b/web/css/app.css index d46aef6..d31e33e 100644 --- a/web/css/app.css +++ b/web/css/app.css @@ -345,6 +345,7 @@ hr { } .bar-value { flex: 0 0 auto; + text-align: right; font-size: 0.95rem; } diff --git a/web/js/stats.js b/web/js/stats.js index a887592..69a8ba6 100644 --- a/web/js/stats.js +++ b/web/js/stats.js @@ -34,6 +34,9 @@ function renderBars(container, items, formatLabel) { return; } const max = Math.max(...items.map((it) => it.value), 1); + // Wertespalte für alle Zeilen gleich breit: an der längsten Zahl ausrichten + // (in ch), damit die Tracks bündig sind und nicht je nach Stellenzahl wandern. + const valWidth = Math.max(...items.map((it) => String(it.value).length)); for (const it of items) { const row = document.createElement("div"); row.className = "bar-row"; @@ -51,6 +54,7 @@ function renderBars(container, items, formatLabel) { const val = document.createElement("span"); val.className = "bar-value"; + val.style.width = `${valWidth}ch`; val.textContent = it.value; row.append(label, track, val); @@ -86,16 +90,18 @@ async function render() { // --- Aufrufe pro Tag (Backend liefert absteigend -> für die Achse drehen) --- const daily = (s.daily ?? []).slice().reverse(); + const dayAxis = (day) => day.slice(5); // "MM-DD" reicht auf der Achse renderBars( document.getElementById("stat-daily"), daily.map((d) => ({ label: d.day, value: d.impressions })), - (day) => day.slice(5), // "MM-DD" reicht auf der Achse + dayAxis, ); - // --- Beliebteste Seiten --- + // --- Verschiedene Netze pro Tag --- renderBars( - document.getElementById("stat-pages"), - (s.top_pages ?? []).map((p) => ({ label: p.path, value: p.hits })), + document.getElementById("stat-networks"), + daily.map((d) => ({ label: d.day, value: d.visitors })), + dayAxis, ); } diff --git a/web/stats.html b/web/stats.html index 6817f87..897529f 100644 --- a/web/stats.html +++ b/web/stats.html @@ -41,8 +41,8 @@