Fixed /stats layout

This commit is contained in:
2026-06-19 22:49:01 +02:00
parent 63240a6cf7
commit c30399e498
5 changed files with 17 additions and 24 deletions
+4
View File
@@ -160,6 +160,10 @@ Image=localhost/kver:latest
PublishPort=127.0.0.1:7777:8080 PublishPort=127.0.0.1:7777:8080
Volume=kver-data:/app/data Volume=kver-data:/app/data
Volume=kver-media:/app/static/media 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, # Healthcheck hier statt im Containerfile: Podman baut OCI-Images,
# die kennen kein eingebettetes HEALTHCHECK. # die kennen kein eingebettetes HEALTHCHECK.
HealthCmd=wget -qO /dev/null http://localhost:8080/entry/feed/0 HealthCmd=wget -qO /dev/null http://localhost:8080/entry/feed/0
-18
View File
@@ -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)". // Netze nach Betreiber (autonomes System). Leere asn ('') -> "(unbekannt)".
topASNs := []map[string]any{} topASNs := []map[string]any{}
if rows, err := db.Query( if rows, err := db.Query(
@@ -264,7 +247,6 @@ func handleStatsDetail(w http.ResponseWriter, r *http.Request) {
"impressions_total": imprTotal, "impressions_total": imprTotal,
"visitors_total": visitorsTotal, "visitors_total": visitorsTotal,
"daily": daily, "daily": daily,
"top_pages": topPages,
"top_asns": topASNs, "top_asns": topASNs,
}) })
} }
+1
View File
@@ -345,6 +345,7 @@ hr {
} }
.bar-value { .bar-value {
flex: 0 0 auto; flex: 0 0 auto;
text-align: right;
font-size: 0.95rem; font-size: 0.95rem;
} }
+10 -4
View File
@@ -34,6 +34,9 @@ function renderBars(container, items, formatLabel) {
return; return;
} }
const max = Math.max(...items.map((it) => it.value), 1); 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) { for (const it of items) {
const row = document.createElement("div"); const row = document.createElement("div");
row.className = "bar-row"; row.className = "bar-row";
@@ -51,6 +54,7 @@ function renderBars(container, items, formatLabel) {
const val = document.createElement("span"); const val = document.createElement("span");
val.className = "bar-value"; val.className = "bar-value";
val.style.width = `${valWidth}ch`;
val.textContent = it.value; val.textContent = it.value;
row.append(label, track, val); row.append(label, track, val);
@@ -86,16 +90,18 @@ async function render() {
// --- Aufrufe pro Tag (Backend liefert absteigend -> für die Achse drehen) --- // --- Aufrufe pro Tag (Backend liefert absteigend -> für die Achse drehen) ---
const daily = (s.daily ?? []).slice().reverse(); const daily = (s.daily ?? []).slice().reverse();
const dayAxis = (day) => day.slice(5); // "MM-DD" reicht auf der Achse
renderBars( renderBars(
document.getElementById("stat-daily"), document.getElementById("stat-daily"),
daily.map((d) => ({ label: d.day, value: d.impressions })), 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( renderBars(
document.getElementById("stat-pages"), document.getElementById("stat-networks"),
(s.top_pages ?? []).map((p) => ({ label: p.path, value: p.hits })), daily.map((d) => ({ label: d.day, value: d.visitors })),
dayAxis,
); );
} }
+2 -2
View File
@@ -41,8 +41,8 @@
<h3>Aufrufe pro Tag</h3> <h3>Aufrufe pro Tag</h3>
<div id="stat-daily" class="bars"></div> <div id="stat-daily" class="bars"></div>
<h3>Beliebteste Seiten</h3> <h3>Verschiedene Netze pro Tag</h3>
<div id="stat-pages" class="bars"></div> <div id="stat-networks" class="bars"></div>
</section> </section>
</main> </main>