feat(ui): Übersichtsseite – globaler Override-Banner
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9aabf18aa2
commit
c263d97cca
3 changed files with 82 additions and 7 deletions
|
|
@ -1392,6 +1392,29 @@ const screenOverviewTmpl = `<!DOCTYPE html>
|
|||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title is-4 mb-5">Meine Bildschirme</h1>
|
||||
<!-- Globaler Override-Banner -->
|
||||
<div id="global-override-section" style="margin-bottom:1rem">
|
||||
{{if .GlobalOverride}}
|
||||
<div class="notification {{if eq .GlobalOverride.Type "off"}}is-warning{{else}}is-info{{end}} is-light py-2 px-3" style="display:flex;align-items:center;gap:1rem;flex-wrap:wrap">
|
||||
<span>
|
||||
Alle Monitore <strong>{{if eq .GlobalOverride.Type "off"}}ausgeschaltet{{else}}eingeschaltet{{end}}</strong>
|
||||
bis {{.GlobalOverride.Until.Format "02.01.2006 15:04"}}
|
||||
</span>
|
||||
<button class="button is-small" type="button" onclick="deleteGlobalOverride()">Override aufheben</button>
|
||||
</div>
|
||||
{{else}}
|
||||
<div style="display:flex;gap:.5rem;flex-wrap:wrap;align-items:center">
|
||||
<button class="button is-small is-danger is-light" type="button" onclick="showGlobalOverrideForm('off')">Alle ausschalten bis…</button>
|
||||
<button class="button is-small is-success is-light" type="button" onclick="showGlobalOverrideForm('on')">Alle einschalten bis…</button>
|
||||
<span id="override-result" style="font-size:.8rem;color:#6b7280"></span>
|
||||
</div>
|
||||
<div id="global-override-form" style="display:none;margin-top:.5rem;gap:.5rem;align-items:center;flex-wrap:wrap">
|
||||
<input type="datetime-local" id="global-override-until" class="input is-small" style="width:16rem">
|
||||
<button class="button is-small is-primary" type="button" onclick="setGlobalOverride()">Setzen</button>
|
||||
<button class="button is-small is-light" type="button" onclick="hideGlobalOverrideForm()">Abbrechen</button>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{if gt (len .Cards) 1}}
|
||||
<div class="bulk-bar">
|
||||
<span style="font-size:.875rem;font-weight:600;color:#374151">Alle Displays:</span>
|
||||
|
|
@ -1513,6 +1536,45 @@ function bulkDisplay(state) {
|
|||
}).catch(function(){});
|
||||
});
|
||||
}
|
||||
|
||||
var _globalOverrideType = '';
|
||||
|
||||
function showGlobalOverrideForm(type) {
|
||||
_globalOverrideType = type;
|
||||
var form = document.getElementById('global-override-form');
|
||||
if (form) form.style.display = 'flex';
|
||||
}
|
||||
|
||||
function hideGlobalOverrideForm() {
|
||||
var form = document.getElementById('global-override-form');
|
||||
if (form) form.style.display = 'none';
|
||||
}
|
||||
|
||||
function setGlobalOverride() {
|
||||
var val = document.getElementById('global-override-until').value;
|
||||
if (!val) {
|
||||
document.getElementById('override-result').textContent = 'Bitte Datum und Uhrzeit angeben';
|
||||
return;
|
||||
}
|
||||
var dt = new Date(val);
|
||||
fetch('/api/v1/global-override', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json', 'X-CSRF-Token': getCsrf(), 'X-Requested-With': 'fetch'},
|
||||
body: JSON.stringify({type: _globalOverrideType, until: dt.toISOString()})
|
||||
}).then(function(r) {
|
||||
if (r.ok) { location.reload(); }
|
||||
else { document.getElementById('override-result').textContent = 'Fehler beim Setzen'; }
|
||||
}).catch(function() { document.getElementById('override-result').textContent = 'Netzwerkfehler'; });
|
||||
}
|
||||
|
||||
function deleteGlobalOverride() {
|
||||
fetch('/api/v1/global-override', {
|
||||
method: 'DELETE',
|
||||
headers: {'X-CSRF-Token': getCsrf(), 'X-Requested-With': 'fetch'}
|
||||
}).then(function(r) {
|
||||
if (r.ok) { location.reload(); }
|
||||
}).catch(function(){});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>`
|
||||
|
|
|
|||
|
|
@ -283,10 +283,11 @@ func HandleRemoveUserFromScreen(screens *store.ScreenStore) http.HandlerFunc {
|
|||
type screenCard struct {
|
||||
Screen *store.Screen
|
||||
DisplayState string
|
||||
OverrideOnUntil *time.Time
|
||||
}
|
||||
|
||||
// HandleScreenOverview renders a card-based overview of all accessible screens for a screen_user.
|
||||
func HandleScreenOverview(screens *store.ScreenStore, notifier *mqttnotifier.Notifier, cfg config.Config) http.HandlerFunc {
|
||||
func HandleScreenOverview(screens *store.ScreenStore, schedules *store.ScreenScheduleStore, overrides *store.GlobalOverrideStore, notifier *mqttnotifier.Notifier, cfg config.Config) http.HandlerFunc {
|
||||
t := template.Must(template.New("screenOverview").Funcs(tmplFuncs).Parse(screenOverviewTmpl))
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
u := reqcontext.UserFromContext(r.Context())
|
||||
|
|
@ -310,11 +311,23 @@ func HandleScreenOverview(screens *store.ScreenStore, notifier *mqttnotifier.Not
|
|||
cards := make([]screenCard, 0, len(accessible))
|
||||
for _, sc := range accessible {
|
||||
ds, _ := screens.GetDisplayState(r.Context(), sc.ID)
|
||||
cards = append(cards, screenCard{Screen: sc, DisplayState: ds})
|
||||
sched, _ := schedules.Get(r.Context(), sc.ID)
|
||||
var overrideOnUntil *time.Time
|
||||
if sched != nil && sched.OverrideOnUntil != nil && time.Now().Before(*sched.OverrideOnUntil) {
|
||||
overrideOnUntil = sched.OverrideOnUntil
|
||||
}
|
||||
cards = append(cards, screenCard{Screen: sc, DisplayState: ds, OverrideOnUntil: overrideOnUntil})
|
||||
}
|
||||
|
||||
var activeOverride *store.GlobalOverride
|
||||
if o, err := overrides.Get(r.Context()); err == nil && o != nil && time.Now().Before(o.Until) {
|
||||
activeOverride = o
|
||||
}
|
||||
|
||||
renderTemplate(w, t, map[string]any{
|
||||
"Cards": cards,
|
||||
"CSRFToken": csrfToken,
|
||||
"GlobalOverride": activeOverride,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ func registerManageRoutes(mux *http.ServeMux, d RouterDeps) {
|
|||
// ── Playlist management UI ────────────────────────────────────────────
|
||||
// authScreen enforces that screen_user only accesses their permitted screens.
|
||||
mux.Handle("GET /manage",
|
||||
authOnly(http.HandlerFunc(manage.HandleScreenOverview(d.ScreenStore, notifier, d.Config))))
|
||||
authOnly(http.HandlerFunc(manage.HandleScreenOverview(d.ScreenStore, d.ScheduleStore, d.GlobalOverrideStore, notifier, d.Config))))
|
||||
mux.Handle("GET /manage/{screenSlug}",
|
||||
authScreen(http.HandlerFunc(manage.HandleManageUI(d.TenantStore, d.ScreenStore, d.ScheduleStore, d.MediaStore, d.PlaylistStore, d.Config, notifier))))
|
||||
mux.Handle("POST /manage/{screenSlug}/upload",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue