223 lines
4.9 KiB
Go
223 lines
4.9 KiB
Go
// backend\autostart_pause.go
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
var autostartPaused int32 // 0=false, 1=true
|
|
|
|
// --- SSE subscribers für Autostart-State ---
|
|
var autostartSubsMu sync.Mutex
|
|
var autostartSubs = map[chan bool]struct{}{}
|
|
|
|
func broadcastAutostartPaused(paused bool) {
|
|
autostartSubsMu.Lock()
|
|
defer autostartSubsMu.Unlock()
|
|
|
|
for ch := range autostartSubs {
|
|
// non-blocking: wenn Client langsam ist, droppen wir Updates (neuester Zustand kommt eh wieder)
|
|
select {
|
|
case ch <- paused:
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
|
|
func isAutostartPaused() bool {
|
|
return atomic.LoadInt32(&autostartPaused) == 1
|
|
}
|
|
|
|
func setAutostartPaused(v bool) {
|
|
old := isAutostartPaused()
|
|
|
|
if v {
|
|
atomic.StoreInt32(&autostartPaused, 1)
|
|
} else {
|
|
atomic.StoreInt32(&autostartPaused, 0)
|
|
}
|
|
|
|
// nur wenn sich der Zustand wirklich geändert hat -> pushen
|
|
if old != v {
|
|
broadcastAutostartPaused(v)
|
|
}
|
|
}
|
|
|
|
type autostartPauseReq struct {
|
|
Paused *bool `json:"paused"`
|
|
}
|
|
|
|
func autostartPauseHandler(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"paused": isAutostartPaused(),
|
|
})
|
|
return
|
|
|
|
case http.MethodPost:
|
|
// erlaubt: JSON body { "paused": true/false }
|
|
// oder Query ?paused=1/0/true/false
|
|
var val *bool
|
|
|
|
ct := strings.ToLower(r.Header.Get("Content-Type"))
|
|
if strings.Contains(ct, "application/json") {
|
|
var req autostartPauseReq
|
|
_ = json.NewDecoder(r.Body).Decode(&req)
|
|
val = req.Paused
|
|
}
|
|
|
|
if val == nil {
|
|
q := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("paused")))
|
|
if q != "" {
|
|
b := q == "1" || q == "true" || q == "yes" || q == "on"
|
|
val = &b
|
|
}
|
|
}
|
|
|
|
if val == nil {
|
|
http.Error(w, "missing 'paused' (json body or query)", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
setAutostartPaused(*val)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"paused": isAutostartPaused(),
|
|
})
|
|
return
|
|
|
|
default:
|
|
http.Error(w, "Nur GET/POST erlaubt", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
}
|
|
|
|
func writeAutostartState(w http.ResponseWriter) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"paused": isAutostartPaused(),
|
|
})
|
|
}
|
|
|
|
// GET /api/autostart/state
|
|
func autostartStateHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "Nur GET erlaubt", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
writeAutostartState(w)
|
|
}
|
|
|
|
// GET/POST /api/autostart/pause (POST ohne Body pausiert)
|
|
func autostartPauseQuickHandler(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
writeAutostartState(w)
|
|
return
|
|
case http.MethodPost:
|
|
setAutostartPaused(true)
|
|
writeAutostartState(w)
|
|
return
|
|
default:
|
|
http.Error(w, "Nur GET/POST erlaubt", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
}
|
|
|
|
// POST /api/autostart/resume (optional auch GET)
|
|
func autostartResumeHandler(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
writeAutostartState(w)
|
|
return
|
|
case http.MethodPost:
|
|
setAutostartPaused(false)
|
|
writeAutostartState(w)
|
|
return
|
|
default:
|
|
http.Error(w, "Nur GET/POST erlaubt", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
}
|
|
|
|
// GET /api/autostart/state/stream (SSE)
|
|
func autostartStateStreamHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "Nur GET erlaubt", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
fl, ok := w.(http.Flusher)
|
|
if !ok {
|
|
http.Error(w, "Streaming nicht unterstützt", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
w.Header().Set("Connection", "keep-alive")
|
|
w.Header().Set("X-Accel-Buffering", "no") // wichtig falls Proxy/Nginx
|
|
|
|
ch := make(chan bool, 1)
|
|
|
|
// subscribe
|
|
autostartSubsMu.Lock()
|
|
autostartSubs[ch] = struct{}{}
|
|
autostartSubsMu.Unlock()
|
|
|
|
// cleanup
|
|
defer func() {
|
|
autostartSubsMu.Lock()
|
|
delete(autostartSubs, ch)
|
|
autostartSubsMu.Unlock()
|
|
close(ch)
|
|
}()
|
|
|
|
send := func(paused bool) {
|
|
payload := map[string]any{
|
|
"paused": paused,
|
|
"ts": time.Now().UTC().Format(time.RFC3339Nano),
|
|
}
|
|
|
|
b, _ := json.Marshal(payload)
|
|
_, _ = io.WriteString(w, "event: autostart\n")
|
|
_, _ = io.WriteString(w, "data: ")
|
|
_, _ = w.Write(b)
|
|
_, _ = io.WriteString(w, "\n\n")
|
|
fl.Flush()
|
|
}
|
|
|
|
// initial state sofort senden
|
|
send(isAutostartPaused())
|
|
|
|
ctx := r.Context()
|
|
hb := time.NewTicker(15 * time.Second)
|
|
defer hb.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case v := <-ch:
|
|
send(v)
|
|
case <-hb.C:
|
|
// heartbeat gegen Proxy timeouts
|
|
_, _ = io.WriteString(w, ": keep-alive\n\n")
|
|
fl.Flush()
|
|
}
|
|
}
|
|
}
|