UI-Fixes (Button-Wrap, Suchfeld-Icon, Log-Farben, Nav-Persistenz), manueller Check-Trigger, echter Live-Refresh
- section-head: Button ("+ Neue Zugangsdaten" etc.) rutscht bei langer
Beschreibung nicht mehr in die nächste Zeile — bleibt immer rechts oben,
der Beschreibungstext wickelt stattdessen innerhalb seiner eigenen Spalte.
- Suchfeld-Icon überlappte den Platzhaltertext: eine spätere, generische
Input-Regel mit gleicher Spezifität hat die padding-left-Regel des
Lupe-Icons überschrieben (reine Reihenfolge-im-Stylesheet-Sache) — Selektor
spezifischer gemacht.
- Live-Log: Neustart-bezogene Zeilen (manueller Neustart, automatischer
PoE-Restart bei Ausfall) werden jetzt orange markiert statt farblos.
- Aufgeklappte Nav-Gruppen bleiben über Seitenwechsel hinweg erhalten
(localStorage), zusätzlich zur automatischen Aufklappung der Gruppe der
aktuell aktiven Seite.
- Datei-Auswahl (Import) im Dark Mode gestylt (Text + Button), inkl.
color-scheme-bewusster Browser-Widgets.
- Manueller "Jetzt prüfen"-Trigger (Icon neben dem globalen Timer, nur
Admins): startet rpi-check.service neu -> frisches Logfile + sofortiger
Durchlauf, Countdown synchronisiert sich auf den manuellen Zeitpunkt.
- Dashboard und Live-Log aktualisieren sich jetzt per AJAX im Hintergrund
(kein voller Seiten-Reload mehr): neue Route /dashboard/tiles liefert das
Kachel-Fragment (_dashboard_tiles.html, von index.html eingebunden und vom
Live-Update nachgeladen), get_log() liefert den aktuellen Dateinamen als
Header mit, damit die Anzeige nach einem Service-Neustart aktuell bleibt.
- Root-Cause für "Intervall bleibt immer bei 300s": in der WSL-Testumgebung
lief poe_wrapper.py nur als manuell gestarteter Hintergrundprozess statt
als echter rpi-check.service — "systemctl restart" lief dadurch ins Leere.
Echten systemd-Service in WSL eingerichtet und Intervalländerung end-to-end
verifiziert (Log-Zeilenabstand folgt jetzt dem neu gesetzten Intervall).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+62
-19
@@ -740,16 +740,10 @@ def get_device_status(devices):
|
||||
# Dashboard
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
"""
|
||||
Dashboard als Kachel-Ansicht, in drei Abschnitten: Offline, Online,
|
||||
Deaktiviert (jeweils alphabetisch) — jeweils nur gerendert, wenn nicht
|
||||
leer. Ohne Login: keine deaktivierten Geräte, nur Online/Offline/Gesamt-
|
||||
Statistik, keine Interaktion. Eingeloggt: alle Geräte, volle
|
||||
Statistik-Kacheln, Klick-Details, manueller PoE-Neustart bzw. bei
|
||||
deaktivierten Geräten eine Aktivieren-Option.
|
||||
"""
|
||||
def _dashboard_context():
|
||||
"""Gemeinsam von index() und dem AJAX-Partial /dashboard/tiles genutzt,
|
||||
damit das Live-Update des Dashboards (ohne vollen Seiten-Reload) exakt
|
||||
dieselben Daten liefert wie der initiale Seitenaufruf."""
|
||||
conn = get_db_connection()
|
||||
all_devices = conn.execute(
|
||||
"SELECT mac, name, rpi_ip, switch_hostname, port, is_active FROM devices ORDER BY name ASC"
|
||||
@@ -781,8 +775,7 @@ def index():
|
||||
|
||||
last_run_epoch_ms = int(last_run_at.timestamp() * 1000) if last_run_at else None
|
||||
|
||||
return render_template(
|
||||
"index.html",
|
||||
return dict(
|
||||
offline_devices=offline_devices, online_devices=online_devices, disabled_devices=disabled_devices,
|
||||
device_count=len(visible_devices),
|
||||
status=status, last_seen=last_seen,
|
||||
@@ -791,6 +784,54 @@ def index():
|
||||
)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
"""
|
||||
Dashboard als Kachel-Ansicht, in drei Abschnitten: Offline, Online,
|
||||
Deaktiviert (jeweils alphabetisch) — jeweils nur gerendert, wenn nicht
|
||||
leer. Ohne Login: keine deaktivierten Geräte, nur Online/Offline/Gesamt-
|
||||
Statistik, keine Interaktion. Eingeloggt: alle Geräte, volle
|
||||
Statistik-Kacheln, Klick-Details, manueller PoE-Neustart bzw. bei
|
||||
deaktivierten Geräten eine Aktivieren-Option.
|
||||
"""
|
||||
return render_template("index.html", **_dashboard_context())
|
||||
|
||||
|
||||
@app.route("/dashboard/tiles")
|
||||
def dashboard_tiles():
|
||||
"""AJAX-Partial für das Live-Update der Dashboard-Kacheln (Status,
|
||||
Statistik) ohne vollen Seiten-Reload — liefert dasselbe Fragment, das
|
||||
index.html beim initialen Aufruf einbindet (siehe _dashboard_tiles.html)."""
|
||||
return render_template("_dashboard_tiles.html", **_dashboard_context())
|
||||
|
||||
|
||||
def _restart_check_service():
|
||||
"""Startet rpi-check.service (poe_wrapper.py -> poe.sh) neu, damit ein
|
||||
geändertes Prüfintervall sofort greift (poe.sh liest SLEEP nur einmal
|
||||
beim Start aus der DB) — läuft dabei automatisch auch ein sofortiger
|
||||
Prüf-Durchlauf mit frischem Logfile an. Schlägt in Umgebungen ohne
|
||||
systemd (z.B. Container) lautlos fehl, statt die Anfrage abzubrechen."""
|
||||
try:
|
||||
subprocess.run(["systemctl", "restart", "rpi-check.service"], check=False)
|
||||
except FileNotFoundError:
|
||||
pass # z.B. lokale Entwicklungsumgebung ohne systemd
|
||||
|
||||
|
||||
@app.route("/check/run-now", methods=["POST"])
|
||||
@login_required
|
||||
def run_check_now():
|
||||
"""Manueller 'Jetzt prüfen'-Trigger (Zahnrad-Icon neben dem Prüf-Timer):
|
||||
startet denselben Service neu wie eine Intervalländerung — das erzeugt
|
||||
sofort ein frisches Logfile mit einem neuen Durchlauf, der Countdown
|
||||
'Nächste Prüfung' setzt sich dadurch automatisch auf den manuell
|
||||
ausgelösten Zeitpunkt zurück."""
|
||||
if not current_user.is_admin:
|
||||
return {"success": False, "msg": "Keine Berechtigung."}, 403
|
||||
_restart_check_service()
|
||||
log_action("check.run_now", "Prüfintervall")
|
||||
return {"success": True, "msg": "Prüfung gestartet."}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Settings
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -809,11 +850,7 @@ def settings():
|
||||
set_setting("interval", new_interval)
|
||||
set_setting("check_interval", new_interval * 60)
|
||||
log_action("settings.update", "Prüfintervall", f"{new_interval} Minuten")
|
||||
|
||||
try:
|
||||
subprocess.run(["systemctl", "restart", "rpi-check.service"], check=False)
|
||||
except FileNotFoundError:
|
||||
pass # z.B. lokale Entwicklungsumgebung ohne systemd
|
||||
_restart_check_service()
|
||||
|
||||
flash(f"Intervall auf {new_interval} Minuten gesetzt und Service neu gestartet!", "success")
|
||||
return redirect(url_for("settings"))
|
||||
@@ -1594,9 +1631,15 @@ def get_log():
|
||||
return "Keine Logfiles gefunden."
|
||||
try:
|
||||
with open(latest_log, "r") as f:
|
||||
return f.read()
|
||||
content = f.read()
|
||||
except Exception as e:
|
||||
return f"Fehler beim Lesen des Logs: {e}"
|
||||
content = f"Fehler beim Lesen des Logs: {e}"
|
||||
# Dateiname als Header mitschicken, damit die Anzeige auch nach einem
|
||||
# Service-Neustart (neues Logfile, z.B. durch "Jetzt prüfen") ohne
|
||||
# Seiten-Reload aktuell bleibt.
|
||||
response = app.response_class(content, mimetype="text/plain")
|
||||
response.headers["X-Log-Name"] = os.path.basename(latest_log)
|
||||
return response
|
||||
|
||||
|
||||
@app.route("/logs")
|
||||
|
||||
Reference in New Issue
Block a user