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
|
CONFIG_FILE = os.path.join(BASE_DIR, "config.json") # Persistente Konfiguration
|
||||||
USERS_FILE = os.path.join(BASE_DIR, "users.json") # User-Datenbank
|
USERS_FILE = os.path.join(BASE_DIR, "users.json") # User-Datenbank
|
||||||
HISTORY_FILE = os.path.join(BASE_DIR, "history.json") # Aktivitätsverlauf
|
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"
|
APP_VERSION = "6.2.8"
|
||||||
UPLOAD_EXTENSIONS = {".jpg", ".jpeg", ".png", ".mp4"}
|
UPLOAD_EXTENSIONS = {".jpg", ".jpeg", ".png", ".mp4"}
|
||||||
@@ -183,24 +185,33 @@ def add_history_entry(action, detail, user_email):
|
|||||||
except (json.JSONDecodeError, OSError):
|
except (json.JSONDecodeError, OSError):
|
||||||
history = []
|
history = []
|
||||||
history.insert(0, entry) # Neueste zuerst
|
history.insert(0, entry) # Neueste zuerst
|
||||||
# Maximal 200 Einträge behalten
|
# Maximal HISTORY_MAX Einträge behalten
|
||||||
history = history[:200]
|
history = history[:HISTORY_MAX]
|
||||||
with open(HISTORY_FILE, "w") as f:
|
with open(HISTORY_FILE, "w") as f:
|
||||||
json.dump(history, f, indent=2)
|
json.dump(history, f, indent=2)
|
||||||
|
|
||||||
|
|
||||||
def get_history(limit=50):
|
def _load_history():
|
||||||
"""Gibt die letzten history-Einträge zurück."""
|
"""Liest den kompletten Verlauf; bei defekter Datei eine leere Liste."""
|
||||||
if not os.path.exists(HISTORY_FILE):
|
if not os.path.exists(HISTORY_FILE):
|
||||||
return []
|
return []
|
||||||
try:
|
try:
|
||||||
with open(HISTORY_FILE) as f:
|
with open(HISTORY_FILE) as f:
|
||||||
history = json.load(f)
|
return json.load(f)
|
||||||
return history[:limit]
|
|
||||||
except (json.JSONDecodeError, OSError):
|
except (json.JSONDecodeError, OSError):
|
||||||
return []
|
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):
|
def get_screen_config(cfg, site, screen):
|
||||||
"""Holt die Konfiguration für einen bestimmten Screen.
|
"""Holt die Konfiguration für einen bestimmten Screen.
|
||||||
Erzeugt automatisch fehlende Dict-Ebenen (sites → site → screens → 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")
|
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")
|
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)
|
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
|
# Sparkline-Daten aus dem Verlauf berechnen
|
||||||
def build_sparkline(entries, creates, deletes):
|
def build_sparkline(entries, creates, deletes):
|
||||||
seq = list(reversed(entries))
|
seq = list(reversed(entries))
|
||||||
@@ -2130,7 +2152,12 @@ def admin_dashboard():
|
|||||||
user_trend=user_trend,
|
user_trend=user_trend,
|
||||||
admin_trend=admin_trend,
|
admin_trend=admin_trend,
|
||||||
superuser_trend=superuser_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),
|
site_list=get_accessible_sites(cfg, current_user),
|
||||||
current_site=current_site_for(cfg, current_user),
|
current_site=current_site_for(cfg, current_user),
|
||||||
version=APP_VERSION,
|
version=APP_VERSION,
|
||||||
|
|||||||
@@ -121,6 +121,9 @@
|
|||||||
<div class="card mt-4">
|
<div class="card mt-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title"><i class="ti ti-history me-1"></i>Verlauf</h3>
|
<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>
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<div class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
@@ -171,6 +174,29 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user