package web import ( "encoding/json" "net/http" "strconv" "strings" ) // handleOSAdmin serves the operator's OS-update controls (behind the operator login, like every route here): // // POST /os/ring/ ring=0|1 // POST /os/enabled/ on=1|0 // POST /os/approve-now approve the current ring-0 set at once (an operator event) // GET /os/fleet one line per box (JSON) func (s *Server) handleOSAdmin(w http.ResponseWriter, r *http.Request, path string) { if s.osUpdates == nil { http.Error(w, "os updates not configured", http.StatusServiceUnavailable) return } reply := func(v any, err error) { if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(v) } switch { case r.Method == http.MethodGet && path == "/os/fleet": reply(s.osUpdates.FleetJSON()) case r.Method == http.MethodPost && strings.HasPrefix(path, "/os/ring/"): n, err := strconv.Atoi(r.FormValue("ring")) if err != nil { http.Error(w, "ring must be 0 or 1", http.StatusBadRequest) return } reply(map[string]bool{"ok": true}, s.osUpdates.SetRing(strings.TrimPrefix(path, "/os/ring/"), n)) case r.Method == http.MethodPost && strings.HasPrefix(path, "/os/enabled/"): on := r.FormValue("on") if on != "0" && on != "1" { http.Error(w, "on must be 0 or 1", http.StatusBadRequest) return } reply(map[string]bool{"ok": true}, s.osUpdates.SetEnabled(strings.TrimPrefix(path, "/os/enabled/"), on == "1")) case r.Method == http.MethodPost && path == "/os/approve-now": id, err := s.osUpdates.ApproveNow() reply(map[string]string{"release_id": id}, err) default: http.Error(w, "not found", http.StatusNotFound) } }