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
+52 -2
View File
@@ -89,7 +89,15 @@ def require_auth():
@app.route("/")
def index():
a = require_auth()
if a: return a
if a:
# Demo-Modus: ?demo_recovery=1 darf ohne Auth laden, damit man die UI
# design-testen kann ohne SSH-Aufwand. Static markup ohne live data.
if request.args.get("demo_recovery") or request.args.get("demo"):
demo = True
else:
return a
else:
demo = False
cfg = dashboard_mod.load_config()
slots = cfg.get("slots", []) # legacy
@@ -128,7 +136,8 @@ def index():
plugin_configs=plugin_configs,
grid=grid,
size_presets=layout_mod.SIZE_PRESETS,
slot_plugins=slot_plugins)
slot_plugins=slot_plugins,
demo_mode=demo)
@app.route("/config", methods=["POST"])
@@ -524,6 +533,47 @@ def plugins_json():
return jsonify(out)
@app.route("/api/network/recovery")
def api_net_recovery():
"""Entscheidung: soll die UI Recovery-Info prominent zeigen?"""
a = require_auth()
if a: return a
state = net.get_watchdog().get_state()
threshold = net.get_recovery_threshold()
decision = net._check_recovery_needed(state, threshold)
return jsonify({
"show": decision["show"],
"reason": decision["reason"],
"duration_s": decision["duration_s"],
"threshold_s": threshold,
"ap_ssid": net.AP_SSID,
"ap_password": net.AP_PASSWORD,
"ap_url": "http://10.42.0.1:8080",
"current_mode": state.get("mode"),
"current_ssid": state.get("ssid"),
"current_ip": state.get("ip"),
"error": state.get("error", ""),
})
@app.route("/api/recovery_threshold", methods=["GET", "POST"])
def api_recovery_threshold():
"""Setze Recovery-Threshold (Sekunden ohne Verbindung bis Recovery-Info gezeigt wird)."""
a = require_auth()
if a: return a
cfg = dashboard_mod.load_config()
if request.method == "GET":
return jsonify({"threshold_s": cfg.get("recovery_threshold_s", 60)})
try:
threshold = int(request.form.get("threshold_s", 60))
threshold = max(5, min(3600, threshold))
except (TypeError, ValueError):
return jsonify({"ok": False, "error": "invalid threshold"}), 400
cfg["recovery_threshold_s"] = threshold
dashboard_mod.save_config(cfg)
return jsonify({"ok": True, "threshold_s": threshold})
@app.route("/status.json")
def status_json():
a = require_auth()