- GET /manage: neue Übersichtsseite mit Bulma-Karten für screen_user mit ≥2 Screens
- handleScreenUserRedirect leitet bei ≥2 Screens auf /manage statt auf ersten Screen
- On-Demand-Screenshot-Flow via MQTT:
- Backend publiziert signage/screen/{slug}/screenshot-request beim Seitenaufruf
- Player-Agent empfängt Topic, ruft TakeAndSendOnce() auf
- Player POST /api/v1/player/screenshot → Backend speichert in ScreenshotStore (RAM)
- GET /api/v1/screens/{screenId}/screenshot liefert gespeichertes Bild (authOnly)
- ScreenshotStore: In-Memory, thread-safe, kein Persistenz-Overhead
- JS-Retry nach 4s in Templates (Screenshot braucht 1-3s für MQTT-Roundtrip)
- manageTmpl zeigt Screenshot-Thumbnail beim Einzelscreen-Aufruf
- Doku: neue Endpoints, MQTT-Topics, Screenshot-Flow in SERVER-KONZEPT.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
59 lines
1.4 KiB
Go
59 lines
1.4 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
)
|
|
|
|
const maxScreenshotSize = 3 << 20 // 3 MB
|
|
|
|
func handlePlayerScreenshot(store *ScreenshotStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxScreenshotSize)
|
|
if err := r.ParseMultipartForm(maxScreenshotSize); err != nil {
|
|
http.Error(w, "bad multipart form", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
screenID := r.FormValue("screen_id")
|
|
if screenID == "" {
|
|
http.Error(w, "screen_id required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
file, header, err := r.FormFile("screenshot")
|
|
if err != nil {
|
|
http.Error(w, "screenshot file required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
data, err := io.ReadAll(file)
|
|
if err != nil {
|
|
http.Error(w, "read error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
mimeType := header.Header.Get("Content-Type")
|
|
if mimeType == "" {
|
|
mimeType = "image/png"
|
|
}
|
|
|
|
store.Save(screenID, data, mimeType)
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
}
|
|
|
|
func handleGetScreenshot(store *ScreenshotStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
screenID := r.PathValue("screenId")
|
|
data, mimeType, ok := store.Get(screenID)
|
|
if !ok {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", mimeType)
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
w.Write(data) //nolint:errcheck
|
|
}
|
|
}
|