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
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
-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)".
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,
})
}
+1
View File
@@ -345,6 +345,7 @@ hr {
}
.bar-value {
flex: 0 0 auto;
text-align: right;
font-size: 0.95rem;
}
+10 -4
View File
@@ -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,
);
}
+2 -2
View File
@@ -41,8 +41,8 @@
<h3>Aufrufe pro Tag</h3>
<div id="stat-daily" class="bars"></div>
<h3>Beliebteste Seiten</h3>
<div id="stat-pages" class="bars"></div>
<h3>Verschiedene Netze pro Tag</h3>
<div id="stat-networks" class="bars"></div>
</section>
</main>