package handlers import ( "context" "fmt" "net/http" "regexp" "time" "crowdsec-dashy/internal/crowdsec" ) // MachinesHandler manages the machines page and its POST actions. type MachinesHandler struct { deps Deps } func NewMachinesHandler(deps Deps) *MachinesHandler { return &MachinesHandler{deps: deps} } // MachinesData is passed to the machines template. type MachinesData struct { PageData Machines []crowdsec.Machine } var validMachineID = regexp.MustCompile(`^[a-zA-Z0-9_.\-]{1,128}$`) // List renders the machines list. func (h *MachinesHandler) List(w http.ResponseWriter, r *http.Request) { pd := NewPageData(r, "Machines", h.deps.CLIAvailable, h.deps.PollInterval) if f := readFlash(r); f.Message != "" { pd.Flash = f } var machines []crowdsec.Machine if h.deps.CLIAvailable { ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second) defer cancel() var err error machines, err = h.deps.CLI.ListMachines(ctx) if err != nil { pd.Flash = FlashMessage{Type: "error", Message: fmt.Sprintf("cscli error: %v", err)} } } h.deps.Renderer.Render(w, "machines", MachinesData{PageData: pd, Machines: machines}) } // Delete removes a machine by ID. func (h *MachinesHandler) Delete(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, 4096) if err := r.ParseForm(); err != nil { flashRedirect(w, r, "/machines", "error", "invalid form data") return } if !checkCSRF(r) { http.Error(w, "forbidden", http.StatusForbidden) return } if !h.deps.CLIAvailable { flashRedirect(w, r, "/machines", "error", "cscli not available") return } id := r.FormValue("id") if !validMachineID.MatchString(id) { flashRedirect(w, r, "/machines", "error", "invalid machine id") return } ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second) defer cancel() if err := h.deps.CLI.DeleteMachine(ctx, id); err != nil { flashRedirect(w, r, "/machines", "error", fmt.Sprintf("failed to delete machine: %v", err)) return } flashRedirect(w, r, "/machines", "success", fmt.Sprintf("Machine %q deleted", id)) } // Validate approves a pending machine registration. func (h *MachinesHandler) Validate(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, 4096) if err := r.ParseForm(); err != nil { flashRedirect(w, r, "/machines", "error", "invalid form data") return } if !checkCSRF(r) { http.Error(w, "forbidden", http.StatusForbidden) return } if !h.deps.CLIAvailable { flashRedirect(w, r, "/machines", "error", "cscli not available") return } id := r.FormValue("id") if !validMachineID.MatchString(id) { flashRedirect(w, r, "/machines", "error", "invalid machine id") return } ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second) defer cancel() if err := h.deps.CLI.ValidateMachine(ctx, id); err != nil { flashRedirect(w, r, "/machines", "error", fmt.Sprintf("failed to validate machine: %v", err)) return } flashRedirect(w, r, "/machines", "success", fmt.Sprintf("Machine %q validated", id)) }