base dashboard and login

This commit is contained in:
2026-05-17 08:28:16 +00:00
parent 64f4f3c5d4
commit 7066415af5
39 changed files with 3327 additions and 72 deletions
+124
View File
@@ -0,0 +1,124 @@
package handlers
import (
"context"
"fmt"
"net/http"
"time"
"crowdsec-dashy/internal/crowdsec"
)
// BouncersHandler manages the bouncers page and its POST actions.
type BouncersHandler struct {
deps Deps
}
func NewBouncersHandler(deps Deps) *BouncersHandler {
return &BouncersHandler{deps: deps}
}
// BouncersData is passed to the bouncers template.
type BouncersData struct {
PageData
Bouncers []crowdsec.Bouncer
NewBouncer *crowdsec.AddBouncerResult // set immediately after add; shown exactly once
}
// List renders the bouncers list.
func (h *BouncersHandler) List(w http.ResponseWriter, r *http.Request) {
pd := NewPageData(r, "Bouncers", h.deps.CLIAvailable, h.deps.PollInterval)
if f := readFlash(r); f.Message != "" {
pd.Flash = f
}
var bouncers []crowdsec.Bouncer
if h.deps.CLIAvailable {
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
defer cancel()
var err error
bouncers, err = h.deps.CLI.ListBouncers(ctx)
if err != nil {
pd.Flash = FlashMessage{Type: "error", Message: fmt.Sprintf("cscli error: %v", err)}
}
}
h.deps.Renderer.Render(w, "bouncers", BouncersData{PageData: pd, Bouncers: bouncers})
}
// Add registers a new bouncer and renders the API key reveal page (no redirect).
// The key is shown exactly once in the POST response and never stored by the UI.
func (h *BouncersHandler) Add(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 4096)
if err := r.ParseForm(); err != nil {
flashRedirect(w, r, "/bouncers", "error", "invalid form data")
return
}
if !checkCSRF(r) {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
if !h.deps.CLIAvailable {
flashRedirect(w, r, "/bouncers", "error", "cscli not available")
return
}
name := r.FormValue("name")
if ok, _ := matchName(name); !ok {
flashRedirect(w, r, "/bouncers", "error", "invalid name: use 1-64 alphanumeric/dash/underscore characters")
return
}
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
defer cancel()
result, err := h.deps.CLI.AddBouncer(ctx, name)
if err != nil {
flashRedirect(w, r, "/bouncers", "error", fmt.Sprintf("failed to add bouncer: %v", err))
return
}
bouncers, _ := h.deps.CLI.ListBouncers(ctx)
pd := NewPageData(r, "Bouncers", h.deps.CLIAvailable, h.deps.PollInterval)
h.deps.Renderer.Render(w, "bouncers", BouncersData{
PageData: pd,
Bouncers: bouncers,
NewBouncer: result,
})
}
// Delete removes a bouncer by name.
func (h *BouncersHandler) Delete(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 4096)
if err := r.ParseForm(); err != nil {
flashRedirect(w, r, "/bouncers", "error", "invalid form data")
return
}
if !checkCSRF(r) {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
if !h.deps.CLIAvailable {
flashRedirect(w, r, "/bouncers", "error", "cscli not available")
return
}
name := r.FormValue("name")
if ok, _ := matchName(name); !ok {
flashRedirect(w, r, "/bouncers", "error", "invalid bouncer name")
return
}
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
defer cancel()
if err := h.deps.CLI.DeleteBouncer(ctx, name); err != nil {
flashRedirect(w, r, "/bouncers", "error", fmt.Sprintf("failed to delete bouncer: %v", err))
return
}
flashRedirect(w, r, "/bouncers", "success", fmt.Sprintf("Bouncer %q deleted", name))
}