Sync to Pi: alle Features die live deployed sind

Aus dem Backup und Live-Pull vom Pi (10.11.3.144):
- dashboard.py: Grid-Linien nur im freien Hintergrund (nicht durch Widgets)
- templates/index.html: komplett redesigned mit Sidebar + Topbar + Toast + Modal
- plugins/clock.py: responsive Layout (1x1 bis 4x4)
- plugins/system.py, weather.py, minimax.py: mit Threshold-Bars und Color-Variants
- plugins/base.py: NEU — fetch_with_retry Helper (3x retry mit backoff)
  + render_error_banner für fehlgeschlagene API-Plugins
  (grosses rotes "!" Icon mit Plugin-Name und Fehler statt Crash)

Cleanup: Helfer-Chaos (renderer.py/2/3, design_a/b/c.html, clock_classic.py,
23x clock_*.png, alte test_*.py) wurde bereits im vorherigen Commit entfernt.

Co-Authored-By: Hermes <noreply@hermes.local>
This commit is contained in:
epaper-dashboard
2026-08-26 22:11:36 +04:00
co-authored by Hermes
parent af9a99a379
commit 1f142f5245
23 changed files with 6250 additions and 950 deletions
+41
View File
@@ -229,9 +229,50 @@ def is_wifi_connected() -> bool:
return "connected" in out.lower()
# ============================================================================
# Recovery threshold helper (used by admin.py)
# ============================================================================
def get_recovery_threshold() -> int:
"""Liest recovery_threshold_s aus config.json (default 60s)."""
try:
from pathlib import Path as _P
cfg_path = _P("/home/koptikp/epaper-dashboard/port/config.json")
if cfg_path.exists():
import json as _json
cfg = _json.loads(cfg_path.read_text())
return int(cfg.get("recovery_threshold_s", 60))
except Exception:
pass
return 60
# ============================================================================
# Watchdog
# ============================================================================
def _check_recovery_needed(state: dict, threshold_s: int) -> dict:
"""Entscheidet, ob die Recovery-Info UI angezeigt werden soll.
Returns dict mit:
show: bool
reason: 'offline' | 'ap_active' | 'connecting_timeout' | 'no_ip'
duration_s: Sekunden im aktuellen (nicht-client) Zustand
"""
mode = state.get("mode", "unknown")
if mode == "client":
return {"show": False, "reason": "client", "duration_s": 0}
# Im AP-Modus: immer anzeigen
if mode == "ap":
return {"show": True, "reason": "ap_active", "duration_s": state.get("since_change_s", 0)}
if mode == "offline":
return {"show": True, "reason": "offline", "duration_s": state.get("since_change_s", 0)}
if mode == "connecting":
# Connecting dauert zu lange? Zeige info.
return {"show": state.get("since_change_s", 0) > threshold_s,
"reason": "connecting_timeout",
"duration_s": state.get("since_change_s", 0)}
return {"show": False, "reason": "unknown", "duration_s": state.get("since_change_s", 0)}
class Watchdog:
def __init__(self):
self.state = NetState()