feat: Verlauf im Dashboard seitenweise blaettern
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
41
app.py
41
app.py
@@ -50,6 +50,8 @@ MEDIA_DIR = os.path.join(BASE_DIR, "media") # Medien-Dateien je Standort/S
|
||||
CONFIG_FILE = os.path.join(BASE_DIR, "config.json") # Persistente Konfiguration
|
||||
USERS_FILE = os.path.join(BASE_DIR, "users.json") # User-Datenbank
|
||||
HISTORY_FILE = os.path.join(BASE_DIR, "history.json") # Aktivitätsverlauf
|
||||
HISTORY_MAX = 1000 # so viele Einträge werden aufbewahrt
|
||||
HISTORY_PER_PAGE = 25 # Einträge je Seite im Dashboard-Verlauf
|
||||
|
||||
APP_VERSION = "6.2.8"
|
||||
UPLOAD_EXTENSIONS = {".jpg", ".jpeg", ".png", ".mp4"}
|
||||
@@ -183,24 +185,33 @@ def add_history_entry(action, detail, user_email):
|
||||
except (json.JSONDecodeError, OSError):
|
||||
history = []
|
||||
history.insert(0, entry) # Neueste zuerst
|
||||
# Maximal 200 Einträge behalten
|
||||
history = history[:200]
|
||||
# Maximal HISTORY_MAX Einträge behalten
|
||||
history = history[:HISTORY_MAX]
|
||||
with open(HISTORY_FILE, "w") as f:
|
||||
json.dump(history, f, indent=2)
|
||||
|
||||
|
||||
def get_history(limit=50):
|
||||
"""Gibt die letzten history-Einträge zurück."""
|
||||
def _load_history():
|
||||
"""Liest den kompletten Verlauf; bei defekter Datei eine leere Liste."""
|
||||
if not os.path.exists(HISTORY_FILE):
|
||||
return []
|
||||
try:
|
||||
with open(HISTORY_FILE) as f:
|
||||
history = json.load(f)
|
||||
return history[:limit]
|
||||
return json.load(f)
|
||||
except (json.JSONDecodeError, OSError):
|
||||
return []
|
||||
|
||||
|
||||
def get_history(limit=50, offset=0):
|
||||
"""Gibt einen Ausschnitt des Verlaufs zurück (neueste zuerst)."""
|
||||
return _load_history()[offset:offset + limit]
|
||||
|
||||
|
||||
def get_history_count():
|
||||
"""Anzahl aller gespeicherten Verlaufseintraege (fuer die Seitennavigation)."""
|
||||
return len(_load_history())
|
||||
|
||||
|
||||
def get_screen_config(cfg, site, screen):
|
||||
"""Holt die Konfiguration für einen bestimmten Screen.
|
||||
Erzeugt automatisch fehlende Dict-Ebenen (sites → site → screens → screen)."""
|
||||
@@ -2080,8 +2091,19 @@ def admin_dashboard():
|
||||
admin_count = sum(1 for u in users_data.values() if u.get("role") == "admin")
|
||||
superuser_count = sum(1 for u in users_data.values() if u.get("role") == "superuser")
|
||||
|
||||
# Kennzahlen und Sparklines bleiben bewusst am jüngsten Ausschnitt haengen,
|
||||
# damit sie sich beim Blaettern im Verlauf nicht veraendern.
|
||||
history = get_history(30)
|
||||
|
||||
gesamt = get_history_count()
|
||||
seiten = max(1, (gesamt + HISTORY_PER_PAGE - 1) // HISTORY_PER_PAGE)
|
||||
try:
|
||||
seite = int(request.args.get("page", 1))
|
||||
except ValueError:
|
||||
seite = 1
|
||||
seite = min(max(seite, 1), seiten)
|
||||
history_page = get_history(HISTORY_PER_PAGE, (seite - 1) * HISTORY_PER_PAGE)
|
||||
|
||||
# Sparkline-Daten aus dem Verlauf berechnen
|
||||
def build_sparkline(entries, creates, deletes):
|
||||
seq = list(reversed(entries))
|
||||
@@ -2130,7 +2152,12 @@ def admin_dashboard():
|
||||
user_trend=user_trend,
|
||||
admin_trend=admin_trend,
|
||||
superuser_trend=superuser_trend,
|
||||
history=history,
|
||||
history=history_page,
|
||||
history_page=seite,
|
||||
history_pages=seiten,
|
||||
history_total=gesamt,
|
||||
history_from=(seite - 1) * HISTORY_PER_PAGE + 1 if gesamt else 0,
|
||||
history_to=min(seite * HISTORY_PER_PAGE, gesamt),
|
||||
site_list=get_accessible_sites(cfg, current_user),
|
||||
current_site=current_site_for(cfg, current_user),
|
||||
version=APP_VERSION,
|
||||
|
||||
@@ -121,6 +121,9 @@
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="ti ti-history me-1"></i>Verlauf</h3>
|
||||
{% if history_total %}
|
||||
<span class="card-subtitle text-secondary small ms-auto">{{ history_from }}–{{ history_to }} von {{ history_total }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="list-group list-group-flush">
|
||||
@@ -171,6 +174,29 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% if history_pages > 1 %}
|
||||
<div class="card-footer d-flex align-items-center">
|
||||
<p class="m-0 text-secondary small">Seite {{ history_page }} von {{ history_pages }}</p>
|
||||
<ul class="pagination m-0 ms-auto">
|
||||
<li class="page-item {% if history_page <= 1 %}disabled{% endif %}">
|
||||
<a class="page-link" href="?page={{ history_page - 1 }}"><i class="ti ti-chevron-left"></i> zurück</a>
|
||||
</li>
|
||||
{# Nur ein Fenster um die aktuelle Seite zeigen, sonst wird die Leiste bei vielen Seiten zu lang #}
|
||||
{% for p in range(1, history_pages + 1) %}
|
||||
{% if p == 1 or p == history_pages or (p >= history_page - 2 and p <= history_page + 2) %}
|
||||
<li class="page-item {% if p == history_page %}active{% endif %}">
|
||||
<a class="page-link" href="?page={{ p }}">{{ p }}</a>
|
||||
</li>
|
||||
{% elif p == history_page - 3 or p == history_page + 3 %}
|
||||
<li class="page-item disabled"><span class="page-link">…</span></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<li class="page-item {% if history_page >= history_pages %}disabled{% endif %}">
|
||||
<a class="page-link" href="?page={{ history_page + 1 }}">weiter <i class="ti ti-chevron-right"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user