package handlers import ( "context" "fmt" "net/http" "time" "crowdsec-dashy/internal/crowdsec" ) // HubHandler manages the hub page (collections, parsers, scenarios, postoverflows). type HubHandler struct { deps Deps } func NewHubHandler(deps Deps) *HubHandler { return &HubHandler{deps: deps} } // HubData is passed to the hub template. type HubData struct { PageData Tab string Items []crowdsec.HubItem // current tab's items } // List renders the hub page for the active tab. func (h *HubHandler) List(w http.ResponseWriter, r *http.Request) { pd := NewPageData(r, "Hub", h.deps.CLIAvailable, h.deps.PollInterval) if f := readFlash(r); f.Message != "" { pd.Flash = f } tab := sanitizeTab(r.URL.Query().Get("tab")) data := HubData{PageData: pd, Tab: tab} if h.deps.CLIAvailable { ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second) defer cancel() var err error switch tab { case "collections": data.Items, err = h.deps.CLI.ListCollections(ctx) case "parsers": data.Items, err = h.deps.CLI.ListParsers(ctx) case "scenarios": data.Items, err = h.deps.CLI.ListScenarios(ctx) case "postoverflows": data.Items, err = h.deps.CLI.ListPostoverflows(ctx) } if err != nil { data.PageData.Flash = FlashMessage{Type: "error", Message: fmt.Sprintf("cscli error: %v", err)} } } h.deps.Renderer.Render(w, "hub", data) } // Install installs a hub item. func (h *HubHandler) Install(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, 4096) if err := r.ParseForm(); err != nil { flashRedirect(w, r, "/hub", "error", "invalid form data") return } if !checkCSRF(r) { http.Error(w, "forbidden", http.StatusForbidden) return } if !h.deps.CLIAvailable { flashRedirect(w, r, "/hub", "error", "cscli not available") return } kind := r.FormValue("kind") name := r.FormValue("name") tab := sanitizeTab(r.FormValue("tab")) if err := validateHubKind(kind); err != nil { flashRedirect(w, r, hubURL(tab), "error", err.Error()) return } ctx, cancel := context.WithTimeout(r.Context(), 120*time.Second) defer cancel() var err error switch kind { case "collections": err = h.deps.CLI.InstallCollection(ctx, name) case "parsers": err = h.deps.CLI.InstallParser(ctx, name) case "scenarios": err = h.deps.CLI.InstallScenario(ctx, name) default: flashRedirect(w, r, hubURL(tab), "error", "unsupported hub type") return } if err != nil { flashRedirect(w, r, hubURL(tab), "error", fmt.Sprintf("install failed: %v", err)) return } flashRedirect(w, r, hubURL(tab), "success", fmt.Sprintf("Installed %s", name)) } // Remove uninstalls a hub item. func (h *HubHandler) Remove(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, 4096) if err := r.ParseForm(); err != nil { flashRedirect(w, r, "/hub", "error", "invalid form data") return } if !checkCSRF(r) { http.Error(w, "forbidden", http.StatusForbidden) return } if !h.deps.CLIAvailable { flashRedirect(w, r, "/hub", "error", "cscli not available") return } kind := r.FormValue("kind") name := r.FormValue("name") tab := sanitizeTab(r.FormValue("tab")) if err := validateHubKind(kind); err != nil { flashRedirect(w, r, hubURL(tab), "error", err.Error()) return } ctx, cancel := context.WithTimeout(r.Context(), 120*time.Second) defer cancel() var err error switch kind { case "collections": err = h.deps.CLI.RemoveCollection(ctx, name) case "parsers": err = h.deps.CLI.RemoveParser(ctx, name) case "scenarios": err = h.deps.CLI.RemoveScenario(ctx, name) default: flashRedirect(w, r, hubURL(tab), "error", "unsupported hub type") return } if err != nil { flashRedirect(w, r, hubURL(tab), "error", fmt.Sprintf("remove failed: %v", err)) return } flashRedirect(w, r, hubURL(tab), "success", fmt.Sprintf("Removed %s", name)) } // Update runs cscli hub update + upgrade. func (h *HubHandler) Update(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, 1024) if err := r.ParseForm(); err != nil { flashRedirect(w, r, "/hub", "error", "invalid form data") return } if !checkCSRF(r) { http.Error(w, "forbidden", http.StatusForbidden) return } if !h.deps.CLIAvailable { flashRedirect(w, r, "/hub", "error", "cscli not available") return } ctx, cancel := context.WithTimeout(r.Context(), 180*time.Second) defer cancel() if err := h.deps.CLI.HubUpdate(ctx); err != nil { flashRedirect(w, r, "/hub", "error", fmt.Sprintf("hub update failed: %v", err)) return } flashRedirect(w, r, "/hub", "success", "Hub updated and upgraded successfully") } func sanitizeTab(tab string) string { switch tab { case "collections", "parsers", "scenarios", "postoverflows": return tab } return "collections" } func hubURL(tab string) string { return "/hub?tab=" + tab } func validateHubKind(kind string) error { switch kind { case "collections", "parsers", "scenarios", "postoverflows": return nil } return fmt.Errorf("invalid hub kind: %q", kind) }