feat(ui): per-Screen-Override in Übersichtskarte und Detailseite

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jesko Anschütz 2026-03-27 20:24:21 +01:00
parent c263d97cca
commit fc94f56162
2 changed files with 92 additions and 0 deletions

View file

@ -982,6 +982,27 @@ const manageTmpl = `<!DOCTYPE html>
</div>
<p id="schedule-save-ok" class="save-ok mt-2" style="font-size:.8rem"> Gespeichert</p>
</div>
<!-- Per-Screen Override (Einschalten bis) -->
<div class="box mb-3">
<h3 class="title is-6 mb-2">Einschalten bis (Override)</h3>
{{if not_expired .Schedule.OverrideOnUntil}}
<p style="font-size:.875rem;color:#059669;margin-bottom:.5rem">
&#x23F0; Aktiv bis {{.Schedule.OverrideOnUntil.Format "02.01.2006 15:04"}}
</p>
<button class="button is-small is-light" type="button"
onclick="clearScreenOverridePage()">Override aufheben</button>
{{else}}
<p style="font-size:.8rem;color:#6b7280;margin-bottom:.5rem">
&#220;berschreibt Zeitplan und Wochenend-Sperre &#x2014; Monitor bleibt bis zum angegebenen Zeitpunkt eingeschaltet.
</p>
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap">
<input type="datetime-local" id="screen-override-until" class="input is-small" style="width:16rem">
<button class="button is-small is-success" type="button"
onclick="setScreenOverridePage()">Setzen</button>
</div>
{{end}}
<p id="screen-override-ok" class="save-ok mt-2" style="font-size:.8rem">&#x2713; Gespeichert</p>
</div>
<!-- Upload (collapsed) -->
<div class="box mb-3">
<details id="upload-details">
@ -1189,6 +1210,33 @@ function saveSchedule() {
}).catch(function() { showToast('Netzwerkfehler', 'is-danger'); });
}
function setScreenOverridePage() {
var val = document.getElementById('screen-override-until').value;
if (!val) { showToast('Bitte Datum und Uhrzeit angeben', 'is-warning'); return; }
var dt = new Date(val);
fetch('/api/v1/screens/' + SCREEN_SLUG + '/override', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-CSRF-Token': getCsrf(), 'X-Requested-With': 'fetch'},
body: JSON.stringify({on_until: dt.toISOString()})
}).then(function(r) {
if (r.ok) {
var ok = document.getElementById('screen-override-ok');
if (ok) { ok.classList.add('show'); setTimeout(function() { ok.classList.remove('show'); }, 2000); }
} else { showToast('Fehler beim Setzen des Overrides', 'is-danger'); }
}).catch(function() { showToast('Netzwerkfehler', 'is-danger'); });
}
function clearScreenOverridePage() {
fetch('/api/v1/screens/' + SCREEN_SLUG + '/override', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-CSRF-Token': getCsrf(), 'X-Requested-With': 'fetch'},
body: JSON.stringify({on_until: null})
}).then(function(r) {
if (r.ok) { location.reload(); }
else { showToast('Fehler beim Aufheben des Overrides', 'is-danger'); }
}).catch(function() { showToast('Netzwerkfehler', 'is-danger'); });
}
// ─── ?msg= toast ─────────────────────────────────────────────────
(function() {
var msg = new URLSearchParams(window.location.search).get('msg');
@ -1447,6 +1495,24 @@ const screenOverviewTmpl = `<!DOCTYPE html>
<button class="button is-small is-danger is-light" type="button"
onclick="sendDisplayCmd('{{.Screen.Slug}}','off')">Aus</button>
</div>
<!-- Per-Screen Override -->
<div style="margin-top:.5rem;font-size:.8rem">
{{if .OverrideOnUntil}}
<span style="color:#059669">&#x23F0; Ein bis {{.OverrideOnUntil.Format "02.01. 15:04"}}</span>
<button class="button is-small is-light" style="padding:0 .4rem;height:1.4rem" type="button"
onclick="clearScreenOverride('{{.Screen.Slug}}')">&#x2715;</button>
{{else}}
<details style="display:inline">
<summary style="cursor:pointer;color:#6b7280">Einschalten bis&#x2026;</summary>
<div style="display:flex;gap:.3rem;align-items:center;margin-top:.3rem;flex-wrap:wrap">
<input type="datetime-local" id="override-until-{{.Screen.Slug}}"
class="input is-small" style="width:13rem;font-size:.75rem">
<button class="button is-small is-success is-light" type="button"
onclick="setScreenOverride('{{.Screen.Slug}}')">Setzen</button>
</div>
</details>
{{end}}
</div>
</div>
</div>
</div>
@ -1575,6 +1641,29 @@ function deleteGlobalOverride() {
if (r.ok) { location.reload(); }
}).catch(function(){});
}
function setScreenOverride(slug) {
var val = document.getElementById('override-until-' + slug).value;
if (!val) return;
var dt = new Date(val);
fetch('/api/v1/screens/' + slug + '/override', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-CSRF-Token': getCsrf(), 'X-Requested-With': 'fetch'},
body: JSON.stringify({on_until: dt.toISOString()})
}).then(function(r) {
if (r.ok) { location.reload(); }
}).catch(function(){});
}
function clearScreenOverride(slug) {
fetch('/api/v1/screens/' + slug + '/override', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-CSRF-Token': getCsrf(), 'X-Requested-With': 'fetch'},
body: JSON.stringify({on_until: null})
}).then(function(r) {
if (r.ok) { location.reload(); }
}).catch(function(){});
}
</script>
</body>
</html>`

View file

@ -137,6 +137,9 @@ var tmplFuncs = template.FuncMap{
}
return t.Format("02.01.2006 15:04")
},
"not_expired": func(t *time.Time) bool {
return t != nil && time.Now().Before(*t)
},
}
// HandleAdminUI renders the admin overview page (screens + users tabs).