STYLE+FIX: power.html Redesign + Auth wiederhergestellt
- power.html komplett neu gestaltet im Design-System von index.html
(CSS-Variablen, gleiche Typografie, Farben, Abstände)
- Status-Banner, Card-Layout, Toggle-Styles, Buttons alle einheitlich
- fetch('/api/power') mit credentials:'include' für Browser-Auth
- fetch('/api/wake') mit credentials:'include' + Fehlerbehandlung
- Auth auf /api/power wieder aktiviert (POST-Schutz) mit
credentials:'include' Browser-Auth
This commit is contained in:
@@ -753,8 +753,9 @@ def power_settings():
|
|||||||
|
|
||||||
@app.route("/api/power", methods=["GET"])
|
@app.route("/api/power", methods=["GET"])
|
||||||
def api_power():
|
def api_power():
|
||||||
"""JSON-Status fuer Live-Anzeige in der UI (GET ohne Auth)."""
|
"""JSON-Status fuer Live-Anzeige in der UI."""
|
||||||
# GET = nur Lesen, keine Aenderung -> kein Auth noetig
|
a = require_auth()
|
||||||
|
if a: return a
|
||||||
cfg = dashboard_mod.load_config()
|
cfg = dashboard_mod.load_config()
|
||||||
power = cfg.get("power", {})
|
power = cfg.get("power", {})
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|||||||
+225
-140
@@ -2,184 +2,269 @@
|
|||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Energie — ePaper Dashboard</title>
|
<title>Energie — ePaper Dashboard</title>
|
||||||
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' fill='%230a0a0f'/%3E%3Crect x='10' y='10' width='80' height='80' fill='%23fafafa'/%3E%3Crect x='15' y='15' width='35' height='35' fill='%230080ff'/%3E%3Crect x='50' y='15' width='35' height='35' fill='%23ff0080'/%3E%3Crect x='15' y='50' width='35' height='35' fill='%2300cc00'/%3E%3Crect x='50' y='50' width='35' height='35' fill='%23ff8000'/%3E%3C/svg%3E">
|
||||||
<style>
|
<style>
|
||||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
:root {
|
||||||
background: #f4f5f7; color: #1a1a1a; margin: 0; padding: 24px; }
|
--bg: #0a0a0f;
|
||||||
h1 { font-size: 22px; margin: 0 0 4px; }
|
--surface: #15151c;
|
||||||
h2 { font-size: 15px; font-weight: 600; color: #555; margin: 24px 0 8px;
|
--surface-2: #1f1f28;
|
||||||
text-transform: uppercase; letter-spacing: 0.5px; }
|
--surface-3: #2a2a35;
|
||||||
.back { float: right; font-size: 13px; color: #2563eb; text-decoration: none; }
|
--border: #2e2e3a;
|
||||||
.card { background: #fff; border: 1px solid #e1e3e8; border-radius: 8px;
|
--border-light: #3a3a48;
|
||||||
padding: 20px 24px; max-width: 720px; }
|
--fg: #f0f0f5;
|
||||||
.row { display: flex; align-items: center; gap: 12px; padding: 12px 0;
|
--fg-muted: #9090a0;
|
||||||
border-bottom: 1px solid #f0f1f3; }
|
--fg-dim: #6a6a78;
|
||||||
.row:last-child { border-bottom: none; }
|
--accent: #6d8eff;
|
||||||
.row label { flex: 1; font-size: 14px; }
|
--accent-bright: #8da9ff;
|
||||||
.row .hint { font-size: 12px; color: #888; display: block; margin-top: 2px; }
|
--accent-glow: rgba(109, 142, 255, 0.15);
|
||||||
.toggle { position: relative; width: 44px; height: 24px; }
|
--ok: #4ade80;
|
||||||
.toggle input { opacity: 0; width: 0; height: 0; }
|
--warn: #fbbf24;
|
||||||
.slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;
|
--alert: #f87171;
|
||||||
background: #d1d5db; border-radius: 12px; transition: 0.2s; }
|
--info: #60a5fa;
|
||||||
.slider::before { content: ""; position: absolute; height: 18px; width: 18px;
|
--success: #22c55e;
|
||||||
left: 3px; bottom: 3px; background: white; border-radius: 50%;
|
}
|
||||||
transition: 0.2s; }
|
* { box-sizing: border-box; }
|
||||||
.toggle input:checked + .slider { background: #10b981; }
|
body {
|
||||||
.toggle input:checked + .slider::before { transform: translateX(20px); }
|
font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, sans-serif;
|
||||||
input[type="time"] { padding: 6px 10px; border: 1px solid #d1d5db;
|
background: var(--bg); color: var(--fg);
|
||||||
border-radius: 6px; font-size: 14px; }
|
margin: 0; line-height: 1.5;
|
||||||
button { background: #2563eb; color: white; border: none; padding: 10px 20px;
|
-webkit-font-smoothing: antialiased;
|
||||||
border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; }
|
min-height: 100vh;
|
||||||
button:hover { background: #1d4ed8; }
|
}
|
||||||
button.secondary { background: #6b7280; }
|
.container { max-width: 1400px; margin: 0 auto; padding: 24px 32px 80px; }
|
||||||
button.secondary:hover { background: #4b5563; }
|
|
||||||
.status { display: inline-block; padding: 4px 10px; border-radius: 4px;
|
/* Header */
|
||||||
font-size: 12px; font-weight: 600; }
|
.page-header {
|
||||||
.status.wach { background: #d1fae5; color: #065f46; }
|
display: flex; align-items: center; justify-content: space-between;
|
||||||
.status.schlaft { background: #fef3c7; color: #92400e; }
|
margin-bottom: 32px; padding-bottom: 20px;
|
||||||
.pill { display: inline-block; background: #e0e7ff; color: #3730a3;
|
border-bottom: 1px solid var(--border);
|
||||||
padding: 2px 8px; border-radius: 10px; font-size: 12px;
|
}
|
||||||
margin-left: 8px; }
|
.page-header h1 { font-size: 22px; font-weight: 700; margin: 0; color: var(--fg); }
|
||||||
.footer { margin-top: 16px; font-size: 12px; color: #6b7280; }
|
.back-btn {
|
||||||
.pulse { animation: pulse 2s ease-in-out infinite; }
|
display: inline-flex; align-items: center; gap: 8px;
|
||||||
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
|
background: var(--surface-2); color: var(--fg-muted);
|
||||||
|
border: 1px solid var(--border); border-radius: 8px;
|
||||||
|
padding: 8px 16px; font-size: 14px; font-weight: 500;
|
||||||
|
text-decoration: none; cursor: pointer; transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.back-btn:hover { background: var(--surface-3); color: var(--fg); }
|
||||||
|
|
||||||
|
/* Status Banner */
|
||||||
|
.status-banner {
|
||||||
|
display: flex; align-items: center; gap: 16px;
|
||||||
|
background: var(--surface); border: 1px solid var(--border);
|
||||||
|
border-radius: 12px; padding: 16px 20px; margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.status-pill {
|
||||||
|
display: inline-flex; align-items: center; justify-content: center;
|
||||||
|
min-width: 70px; padding: 6px 14px; border-radius: 20px;
|
||||||
|
font-size: 13px; font-weight: 600; letter-spacing: 0.3px;
|
||||||
|
}
|
||||||
|
.status-pill.wach { background: rgba(74, 222, 128, 0.15); color: var(--ok); }
|
||||||
|
.status-pill.schlaft { background: rgba(251, 191, 36, 0.15); color: var(--warn); animation: pulse 2s ease-in-out infinite; }
|
||||||
|
.status-text { font-size: 14px; color: var(--fg-muted); flex: 1; }
|
||||||
|
.status-text strong { color: var(--fg); }
|
||||||
|
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
|
||||||
|
|
||||||
|
/* Cards */
|
||||||
|
.card {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.card-title {
|
||||||
|
font-size: 13px; font-weight: 600; color: var(--fg-muted);
|
||||||
|
text-transform: uppercase; letter-spacing: 0.5px;
|
||||||
|
margin: 0 0 16px;
|
||||||
|
}
|
||||||
|
.form-row {
|
||||||
|
display: flex; align-items: center; gap: 12px;
|
||||||
|
padding: 14px 0; border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.form-row:last-child { border-bottom: none; padding-bottom: 0; }
|
||||||
|
.form-row label { flex: 1; font-size: 14px; color: var(--fg); }
|
||||||
|
.form-row .hint { font-size: 12px; color: var(--fg-dim); display: block; margin-top: 2px; }
|
||||||
|
|
||||||
|
/* Toggle */
|
||||||
|
.toggle { position: relative; width: 44px; height: 24px; flex-shrink: 0; }
|
||||||
|
.toggle input { opacity: 0; width: 0; height: 0; }
|
||||||
|
.slider {
|
||||||
|
position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
background: var(--surface-3); border-radius: 12px; transition: 0.2s;
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
.slider::before {
|
||||||
|
content: ""; position: absolute; height: 18px; width: 18px;
|
||||||
|
left: 3px; bottom: 2px; background: var(--fg-dim);
|
||||||
|
border-radius: 50%; transition: 0.2s;
|
||||||
|
}
|
||||||
|
.toggle input:checked + .slider { background: rgba(74, 222, 128, 0.2); border-color: var(--ok); }
|
||||||
|
.toggle input:checked + .slider::before { transform: translateX(20px); background: var(--ok); }
|
||||||
|
|
||||||
|
/* Time inputs */
|
||||||
|
input[type="time"] {
|
||||||
|
padding: 8px 12px; border: 1px solid var(--border);
|
||||||
|
border-radius: 8px; font-size: 14px; background: var(--surface-2);
|
||||||
|
color: var(--fg); outline: none; transition: border-color 0.15s;
|
||||||
|
}
|
||||||
|
input[type="time"]:focus { border-color: var(--accent); }
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn {
|
||||||
|
display: inline-flex; align-items: center; gap: 8px;
|
||||||
|
background: var(--accent); color: #fff;
|
||||||
|
border: none; padding: 10px 20px; border-radius: 8px;
|
||||||
|
font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.btn:hover { background: var(--accent-bright); }
|
||||||
|
.btn.secondary { background: var(--surface-3); color: var(--fg-muted); border: 1px solid var(--border); }
|
||||||
|
.btn.secondary:hover { background: var(--surface-3); color: var(--fg); }
|
||||||
|
.btn-group { display: flex; gap: 12px; margin-top: 20px; }
|
||||||
|
|
||||||
|
/* Info text */
|
||||||
|
.info-box {
|
||||||
|
background: var(--accent-glow); border: 1px solid rgba(109, 142, 255, 0.3);
|
||||||
|
border-radius: 8px; padding: 14px 16px; margin-top: 20px;
|
||||||
|
}
|
||||||
|
.info-box p { margin: 0; font-size: 13px; color: var(--fg-muted); line-height: 1.6; }
|
||||||
|
|
||||||
|
/* Error */
|
||||||
|
.error-msg { color: var(--alert); font-size: 13px; margin-top: 8px; display: none; }
|
||||||
|
.error-msg.visible { display: block; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<a class="back" href="/">← zurück zum Dashboard</a>
|
<div class="container">
|
||||||
<h1>Energie</h1>
|
<div class="page-header">
|
||||||
<p style="color:#6b7280; font-size:13px; margin:4px 0 0">
|
<h1>⚡ Energie</h1>
|
||||||
DeepSleep spart Strom, weil das ePaper das letzte Bild ohne Strom haelt.
|
<a class="back-btn" href="/">← Dashboard</a>
|
||||||
WiFi bleibt an, damit du das Display jederzeit wecken kannst.
|
</div>
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="card" style="margin-top:20px">
|
<div class="status-banner">
|
||||||
<h2>Status <span id="status-pill" class="pill">…</span></h2>
|
<span id="status-pill" class="status-pill schlaft">—</span>
|
||||||
<div id="status-text" style="font-size:14px; padding:8px 0">lade…</div>
|
<span id="status-text" class="status-text">Lädt...</span>
|
||||||
<button class="secondary" id="wake-btn" type="button">Display jetzt wecken</button>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card" style="margin-top:20px">
|
<form method="post" action="/power" id="power-form">
|
||||||
<h2>DeepSleep</h2>
|
<div class="card">
|
||||||
<form method="post" action="/power">
|
<div class="form-row">
|
||||||
<div class="row">
|
<label for="deepsleep_enabled">
|
||||||
<label>
|
DeepSleep aktiv
|
||||||
DeepSleep aktivieren
|
<span class="hint">Display schaltet sich nachts aus, nur noch Netzwerk aktiv</span>
|
||||||
<span class="hint">Display bleibt zwischen den Zeiten eingefroren, kein Refresh.</span>
|
</label>
|
||||||
</label>
|
<label class="toggle">
|
||||||
<span class="toggle">
|
<input type="checkbox" name="deepsleep_enabled" id="deepsleep_enabled"
|
||||||
<input type="checkbox" name="deepsleep_enabled"
|
{% if power.deepsleep_enabled %}checked{% endif %}>
|
||||||
{% if power.deepsleep_enabled %}checked{% endif %}>
|
<span class="slider"></span>
|
||||||
<span class="slider"></span>
|
</label>
|
||||||
</span>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="deepsleep_start">Schlafenszeit (von)</label>
|
||||||
|
<input type="time" name="deepsleep_start" id="deepsleep_start"
|
||||||
|
value="{{ power.deepsleep_start or '23:00' }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="deepsleep_end">Aufwachzeit (bis)</label>
|
||||||
|
<input type="time" name="deepsleep_end" id="deepsleep_end"
|
||||||
|
value="{{ power.deepsleep_end or '06:00' }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="cpu_throttle">
|
||||||
|
CPU drosseln
|
||||||
|
<span class="hint">Reduziert Dauerstromverbrauch um ~0.5–1W</span>
|
||||||
|
</label>
|
||||||
|
<label class="toggle">
|
||||||
|
<input type="checkbox" name="cpu_throttle" id="cpu_throttle"
|
||||||
|
{% if power.cpu_throttle %}checked{% endif %}>
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="wifi_off">
|
||||||
|
WLAN nachts aus
|
||||||
|
<span class="hint">Schaltet WLAN nach Schlafenszeit ab (wenn implementiert)</span>
|
||||||
|
</label>
|
||||||
|
<label class="toggle">
|
||||||
|
<input type="checkbox" name="wifi_off" id="wifi_off"
|
||||||
|
{% if power.wifi_off %}checked{% endif %}>
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<label>
|
<div id="error-msg" class="error-msg"></div>
|
||||||
Schlafenszeit
|
|
||||||
<span class="hint">Start und Ende im 24h-Format. Mitternacht-Überschreitung
|
<div class="btn-group">
|
||||||
(z.B. 23:30 → 06:30) wird automatisch erkannt.</span>
|
<button type="submit" class="btn">💾 Speichern</button>
|
||||||
</label>
|
<button type="button" class="btn secondary" id="wake-btn">⚡ Aufwecken</button>
|
||||||
<span>
|
|
||||||
<input type="time" name="deepsleep_start"
|
|
||||||
value="{{ power.deepsleep_start }}">
|
|
||||||
bis
|
|
||||||
<input type="time" name="deepsleep_end"
|
|
||||||
value="{{ power.deepsleep_end }}">
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<label>
|
|
||||||
CPU-Taktung drosseln (Pi)
|
|
||||||
<span class="hint">Während DeepSleep auf 600 MHz takten. Spart ~0.5W.
|
|
||||||
Beim Aufwachen wird automatisch auf Normal-Takt zurückgeschaltet.</span>
|
|
||||||
</label>
|
|
||||||
<span class="toggle">
|
|
||||||
<input type="checkbox" name="cpu_throttle"
|
|
||||||
{% if power.cpu_throttle %}checked{% endif %}>
|
|
||||||
<span class="slider"></span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<label>
|
|
||||||
WiFi im DeepSleep aus
|
|
||||||
<span class="hint"><strong>Nicht empfohlen.</strong> Solange WiFi aus ist, ist
|
|
||||||
das Admin-UI nicht erreichbar — du kannst das Display dann nicht wecken
|
|
||||||
ausser durch physischen Neustart. Nur aktivieren wenn du weisst was du tust.</span>
|
|
||||||
</label>
|
|
||||||
<span class="toggle">
|
|
||||||
<input type="checkbox" name="wifi_off"
|
|
||||||
{% if power.wifi_off %}checked{% endif %}>
|
|
||||||
<span class="slider"></span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div style="padding-top:16px">
|
|
||||||
<button type="submit">Speichern</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card" style="margin-top:20px">
|
<div class="info-box">
|
||||||
<h2>Wie es funktioniert</h2>
|
<p><strong>Z-Indikator:</strong> Auf dem Display erscheint oben rechts ein "Zz" + Aufwach-Zeit wenn DeepSleep aktiv ist.</p>
|
||||||
<ul style="font-size:13px; line-height:1.6; color:#374151">
|
<p style="margin-top:8px">Geschätzte Ersparnis: bei 7h DeepSleep/Tag + 3min Refresh-Intervall ca. 30% weniger Display-Refresh-Strom. Plus Pi-CPU-Throttle ~0.5–1W Dauerersparnis.</p>
|
||||||
<li><strong>Wann:</strong> DeepSleep gilt zwischen den eingestellten Zeiten, jeden Tag.</li>
|
</div>
|
||||||
<li><strong>Display:</strong> ePaper hält das letzte Bild ohne Strom — Refresh kostet nur
|
|
||||||
die ~30 Sekunden alle 3 Minuten (~0.5 Wh pro Refresh).</li>
|
|
||||||
<li><strong>Daten:</strong> Plugin-Fetch läuft weiter, damit beim Aufwachen frische Daten da sind.</li>
|
|
||||||
<li><strong>WiFi:</strong> Bleibt an (default), Admin-UI ist erreichbar.</li>
|
|
||||||
<li><strong>Aufwachen:</strong> "Display jetzt wecken" Button, oder warten bis Ende der Schlafenszeit.</li>
|
|
||||||
<li><strong>Z-Indikator:</strong> Auf dem Display erscheint oben rechts ein "Zz" + Aufwach-Zeit.</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="footer">
|
|
||||||
Geschätzte Ersparnis: bei 7h DeepSleep/Tag + 3min Refresh-Intervall ca. 30% weniger
|
|
||||||
Display-Refresh-Strom. Plus Pi-CPU-Throttle ~0.5-1W Dauerersparnis.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
async function refreshStatus() {
|
async function refreshStatus() {
|
||||||
try {
|
try {
|
||||||
const r = await fetch('/api/power');
|
const r = await fetch('/api/power', { credentials: 'include' });
|
||||||
|
if (!r.ok) throw new Error('Status nicht abrufbar');
|
||||||
const d = await r.json();
|
const d = await r.json();
|
||||||
const pill = document.getElementById('status-pill');
|
const pill = document.getElementById('status-pill');
|
||||||
const text = document.getElementById('status-text');
|
const text = document.getElementById('status-text');
|
||||||
if (d.is_deepsleep) {
|
if (d.is_deepsleep) {
|
||||||
pill.className = 'pill';
|
pill.className = 'status-pill schlaft';
|
||||||
pill.style.background = '#fef3c7';
|
|
||||||
pill.style.color = '#92400e';
|
|
||||||
pill.textContent = 'schläft';
|
pill.textContent = 'schläft';
|
||||||
pill.classList.add('pulse');
|
|
||||||
text.innerHTML = `Display schläft bis <strong>${d.next_wake}</strong> `
|
text.innerHTML = `Display schläft bis <strong>${d.next_wake}</strong> `
|
||||||
+ `(${formatDuration(d.seconds_until_wake)} verbleibend)`;
|
+ `(${formatDuration(d.seconds_until_wake)} verbleibend)`;
|
||||||
} else {
|
} else {
|
||||||
pill.className = 'pill';
|
pill.className = 'status-pill wach';
|
||||||
pill.style.background = '#d1fae5';
|
|
||||||
pill.style.color = '#065f46';
|
|
||||||
pill.textContent = 'wach';
|
pill.textContent = 'wach';
|
||||||
pill.classList.remove('pulse');
|
const next = d.deepsleep_enabled ? ` Nächster DeepSleep um ${d.deepsleep_start}.` : '';
|
||||||
text.innerHTML = `Display ist wach. Normaler Refresh-Modus aktiv.`
|
text.innerHTML = `Display ist wach. Normaler Refresh-Modus.${next}`;
|
||||||
+ (d.deepsleep_enabled ? ` Nächster DeepSleep um ${d.deepsleep_start}.` : '');
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
document.getElementById('status-text').textContent = 'Status nicht abrufbar: ' + e;
|
document.getElementById('status-text').textContent = 'Status nicht abrufbar (' + e.message + ')';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDuration(secs) {
|
function formatDuration(secs) {
|
||||||
const h = Math.floor(secs / 3600);
|
const h = Math.floor(secs / 3600);
|
||||||
const m = Math.floor((secs % 3600) / 60);
|
const m = Math.floor((secs % 3600) / 60);
|
||||||
if (h > 0) return `${h}h ${m}m`;
|
if (h > 0) return `${h}h ${m}m`;
|
||||||
return `${m}m`;
|
return `${m}m`;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('wake-btn').addEventListener('click', async () => {
|
document.getElementById('wake-btn').addEventListener('click', async () => {
|
||||||
const r = await fetch('/api/wake', { method: 'POST' });
|
const btn = document.getElementById('wake-btn');
|
||||||
const d = await r.json();
|
btn.disabled = true;
|
||||||
if (d.ok) {
|
try {
|
||||||
document.getElementById('status-text').textContent = 'Wake-Trigger gesendet — Display wacht auf.';
|
const r = await fetch('/api/wake', { method: 'POST', credentials: 'include' });
|
||||||
setTimeout(refreshStatus, 1000);
|
const d = await r.json();
|
||||||
} else {
|
if (d.ok) {
|
||||||
alert('Wake fehlgeschlagen: ' + d.error);
|
btn.textContent = '✓ Aufgeweckt!';
|
||||||
|
setTimeout(() => { btn.textContent = '⚡ Aufwecken'; btn.disabled = false; }, 2000);
|
||||||
|
} else {
|
||||||
|
throw new Error(d.message || 'Fehler');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
document.getElementById('error-msg').textContent = 'Aufwecken fehlgeschlagen: ' + e.message;
|
||||||
|
document.getElementById('error-msg').classList.add('visible');
|
||||||
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Init
|
||||||
refreshStatus();
|
refreshStatus();
|
||||||
setInterval(refreshStatus, 30000);
|
setInterval(refreshStatus, 30000);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user