Auditlog: Statistik-Kacheln zeigen geladen/gesamt (weiterhin v1.1.9)

Alle vier Kacheln (Alle, Hinzufuegen, Aenderungen, Loeschungen) zeigen
jetzt "geladen / gesamt" (z.B. "300 / 466"), solange noch nicht alles
geladen ist -- Gesamtzahlen je Kategorie kommen aus einer einzigen
aggregierten SQL-Abfrage (SUM/CASE ueber die bestehenden LIKE-Muster von
category_of), nicht aus einem vollstaendigen Tabellen-Scan. Sobald alles
geladen ist, wird wieder nur die reine Zahl angezeigt (client- wie
serverseitig identische Formatierungslogik).

Dieser Fix wird per Nutzervorgabe unter der GLEICHEN Releasnummer v1.1.9
ausgeliefert (Tag wird bewusst weitergezogen statt neu vergeben, da
v1.1.9 zum Zeitpunkt dieses Commits zwar schon getaggt, aber die
Kachel-Anzeige noch nicht ausgereift war).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 21:38:30 +02:00
co-authored by Claude Sonnet 5
parent 1c0082d3d4
commit 9b4cf8c2e1
2 changed files with 34 additions and 9 deletions
+17 -1
View File
@@ -7727,13 +7727,29 @@ def activity_log():
"SELECT id, ts, username, action, target, details FROM audit_log ORDER BY id DESC LIMIT ?",
(AUDIT_LOG_PAGE_SIZE,),
).fetchall()
total_count = conn.execute("SELECT COUNT(*) AS n FROM audit_log").fetchone()["n"]
# Gesamtzahlen je Kategorie (nicht nur der geladenen Zeilen) für die
# "X / Y geladen"-Anzeige in den Statistik-Kacheln -- eine einzige
# aggregierte Abfrage statt die komplette Tabelle einzulesen. Die
# LIKE-Muster bilden exakt dieselbe Kategorisierung wie category_of()
# in _audit_log_macros.html ab (kind = action.split(".")[-1]).
totals_row = conn.execute(
"SELECT COUNT(*) AS total, "
"SUM(CASE WHEN action LIKE '%.create' OR action LIKE '%.upload' OR action LIKE '%.mkdir' THEN 1 ELSE 0 END) AS create_n, "
"SUM(CASE WHEN action LIKE '%.delete' THEN 1 ELSE 0 END) AS delete_n "
"FROM audit_log"
).fetchone()
conn.close()
total_count = totals_row["total"] or 0
total_create = totals_row["create_n"] or 0
total_delete = totals_row["delete_n"] or 0
total_edit = total_count - total_create - total_delete
oldest_loaded_id = entries[-1]["id"] if entries else None
has_more = len(entries) >= AUDIT_LOG_PAGE_SIZE and total_count > len(entries)
return render_template(
"activity_log.html", entries=entries, total_count=total_count,
total_create=total_create, total_edit=total_edit, total_delete=total_delete,
oldest_loaded_id=oldest_loaded_id, has_more=has_more,
)
+17 -8
View File
@@ -18,22 +18,24 @@
{% else %}{% set ns.edit = ns.edit + 1 %}{% endif %}
{% endfor %}
{% macro fmt_count(loaded, total) %}{% if loaded < total %}{{ loaded }} / {{ total }}{% else %}{{ loaded }}{% endif %}{% endmacro %}
<div class="stat-row" style="margin-bottom:18px;">
<div class="stat-card" data-category-filter="">
<div class="stat-label">Alle</div>
<div class="stat-value" id="statAll">{{ entries|length }}</div>
<div class="stat-value" id="statAll">{{ fmt_count(entries|length, total_count) }}</div>
</div>
<div class="stat-card" data-category-filter="create">
<div class="stat-label">Hinzufügen</div>
<div class="stat-value" id="statCreate" style="color:var(--success);">{{ ns.create }}</div>
<div class="stat-value" id="statCreate" style="color:var(--success);">{{ fmt_count(ns.create, total_create) }}</div>
</div>
<div class="stat-card" data-category-filter="edit">
<div class="stat-label">Änderungen</div>
<div class="stat-value" id="statEdit" style="color:var(--accent-strong);">{{ ns.edit }}</div>
<div class="stat-value" id="statEdit" style="color:var(--accent-strong);">{{ fmt_count(ns.edit, total_edit) }}</div>
</div>
<div class="stat-card" data-category-filter="delete">
<div class="stat-label">Löschungen</div>
<div class="stat-value" id="statDelete" style="color:var(--danger);">{{ ns.delete }}</div>
<div class="stat-value" id="statDelete" style="color:var(--danger);">{{ fmt_count(ns.delete, total_delete) }}</div>
</div>
</div>
@@ -112,16 +114,23 @@ document.addEventListener("DOMContentLoaded", function () {
/* ---- Nachladen älterer Einträge (Mehr laden / Alle laden) ---- */
const totalCount = {{ total_count }};
const totalCreate = {{ total_create }};
const totalEdit = {{ total_edit }};
const totalDelete = {{ total_delete }};
const entryCountLabel = document.getElementById("auditEntryCount");
function fmtCount(loaded, total) {
return loaded < total ? loaded + " / " + total : String(loaded);
}
function recomputeStats() {
const rows = Array.from(document.querySelectorAll("#auditTable tbody tr[data-category]"));
const counts = { create: 0, edit: 0, delete: 0 };
rows.forEach(function (r) { counts[r.dataset.category] = (counts[r.dataset.category] || 0) + 1; });
document.getElementById("statAll").textContent = rows.length;
document.getElementById("statCreate").textContent = counts.create;
document.getElementById("statEdit").textContent = counts.edit;
document.getElementById("statDelete").textContent = counts.delete;
document.getElementById("statAll").textContent = fmtCount(rows.length, totalCount);
document.getElementById("statCreate").textContent = fmtCount(counts.create, totalCreate);
document.getElementById("statEdit").textContent = fmtCount(counts.edit, totalEdit);
document.getElementById("statDelete").textContent = fmtCount(counts.delete, totalDelete);
if (entryCountLabel) {
entryCountLabel.textContent = rows.length < totalCount
? rows.length + " von " + totalCount + " Einträgen geladen"