// 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() } } }