Topbar auf globalen Prüf-Timer reduziert, Aktionen+Beschreibung über Tabellen, sortierbare Listen

- Topbar zeigt rechts jetzt ausschließlich den "Nächste Prüfung"-Countdown,
  konsistent auf jeder Seite (auch anonymes Dashboard) statt nur auf dem
  Dashboard. Neuer Context-Processor inject_check_timer()/get_last_run_at()
  liefert last_run/interval global, ohne dass jede Route das selbst
  berechnen muss.
- "+ Neu ..."-Buttons (Devices, Switches, Zugangsdaten, Benutzer, Gruppen)
  aus der Topbar entfernt und stattdessen in einen .section-head direkt
  über der jeweiligen Tabelle verschoben, zusammen mit einer kurzen
  Beschreibung der Seite (bisher nur bei Zugangsdaten/Gruppen vorhanden,
  jetzt auch bei Geräte/Switche/Benutzer).
- Live-Log: eigene lokale Timer-Pill entfernt (redundant zum globalen
  Timer), "Aktualisieren"-Button in denselben section-head verschoben.
  Dashboard: Suchfeld aus der Topbar in den Seiteninhalt verschoben, lokale
  Timer-Anzeige entfernt (übernimmt die globale Topbar-Pill), Reload-bei-
  Intervallende-Logik bleibt als separater, unsichtbarer Scheduler erhalten.
- Neue generische Tabellen-Sortierung (app.js: initSortableTables): Klick
  auf eine Spaltenüberschrift mit data-sort-key sortiert die Zeilen anhand
  von data-sort-<key>-Attributen. Unterstützt auch Akkordeon-Tabellen mit
  mehreren <tbody> (Gruppen: Haupt- + Detail-Zeile bleiben als Einheit
  zusammen, die virtuelle "Admin"-Zeile bleibt über data-sort-pinned immer
  oben). Angewendet auf Geräte, Switche, Zugangsdaten, Benutzer, Gruppen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 15:21:30 +02:00
co-authored by Claude Sonnet 5
parent e4cceb4091
commit 0fcb6c94cb
12 changed files with 287 additions and 127 deletions
+13 -16
View File
@@ -2,15 +2,13 @@
{% set active_page = "index" %}
{% block page_title %}Dashboard{% endblock %}
{% block page_sub %}<div class="topbar-sub">{{ device_count }} Geräte{{ " im Bestand" if current_user.is_authenticated else "" }}</div>{% endblock %}
{% block topbar_right %}
<div class="search-input" style="min-width:180px;">
{% block content %}
<div class="search-input" style="max-width:320px; margin-bottom:18px;">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
<input type="text" id="tileSearch" placeholder="Gerät suchen…">
</div>
<span class="timer-pill"><span class="dot"></span><span id="dashboard-timer">Nächste Prüfung in --s</span></span>
{% endblock %}
{% block content %}
<div class="stat-row">
<div class="stat-card">
@@ -169,28 +167,27 @@
{% block scripts %}
<script>
document.addEventListener("DOMContentLoaded", () => {
const intervalMinutes = {{ interval | int }};
const intervalMinutes = {{ global_check_interval | int }};
const intervalMilliseconds = intervalMinutes * 60 * 1000;
const isAuthenticated = {{ "true" if current_user.is_authenticated else "false" }};
// Vom Server ermittelter Zeitpunkt des letzten echten Prüf-Durchlaufs
// (aus dem Logfile) — damit startet der Countdown nicht bei jedem
// Seitenaufruf wieder von vorn, sondern zeigt die tatsächlich
// verbleibende Zeit bis zur nächsten Prüfung durch poe.sh.
const lastRunAt = {{ last_run_epoch_ms | tojson }};
// verbleibende Zeit bis zur nächsten Prüfung durch poe.sh. Der
// sichtbare Countdown selbst läuft global in der Topbar (app.js); hier
// wird nur überwacht, wann ein neuer Durchlauf fertig ist, um die
// Dashboard-Kacheln dann automatisch neu zu laden.
const lastRunAt = {{ global_last_run_epoch_ms | tojson }};
let lastUpdateTime = lastRunAt || Date.now();
function updateTimer() {
function checkForReload() {
const now = Date.now();
const elapsed = now - lastUpdateTime;
const remainingMs = intervalMilliseconds - (elapsed % intervalMilliseconds);
const remainingSec = Math.ceil(remainingMs / 1000);
const el = document.getElementById("dashboard-timer");
if (el) el.innerText = `Nächste Prüfung in ${remainingSec}s`;
if (remainingSec <= 1) window.location.reload();
if (Math.ceil(remainingMs / 1000) <= 1) window.location.reload();
}
setInterval(updateTimer, 1000);
updateTimer();
if (intervalMilliseconds) setInterval(checkForReload, 1000);
// Suchfilter über alle Abschnitte hinweg; Abschnitt wird komplett
// ausgeblendet, wenn keine seiner Kacheln mehr passt.