Files
crowdsec-dashy/internal/handlers/alerts.go
T

131 lines
3.1 KiB
Go
Raw Normal View History

2026-05-17 08:28:16 +00:00
package handlers
import (
"context"
"fmt"
2026-05-17 14:12:06 +00:00
"log"
2026-05-17 08:28:16 +00:00
"net/http"
"strconv"
"strings"
"time"
"crowdsec-dashy/internal/crowdsec"
)
// AlertsHandler manages the alerts page and its POST actions.
type AlertsHandler struct {
deps Deps
}
func NewAlertsHandler(deps Deps) *AlertsHandler {
return &AlertsHandler{deps: deps}
}
2026-05-17 14:12:06 +00:00
const alertsPerPage = 50
2026-05-17 08:28:16 +00:00
// AlertsData is passed to the alerts template.
type AlertsData struct {
PageData
2026-05-17 14:12:06 +00:00
Alerts []crowdsec.Alert
Filter crowdsec.AlertFilter
Page int
HasNext bool
ShowUpdates bool
2026-05-17 08:28:16 +00:00
}
2026-05-17 14:12:06 +00:00
// List renders the paginated alerts list.
2026-05-17 08:28:16 +00:00
func (h *AlertsHandler) List(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
defer cancel()
2026-05-17 14:12:06 +00:00
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
if page < 1 {
page = 1
}
showUpdates := r.URL.Query().Get("show_updates") == "1"
2026-05-17 08:28:16 +00:00
filter := crowdsec.AlertFilter{
2026-05-17 14:12:06 +00:00
Limit: alertsPerPage + 1,
Offset: (page - 1) * alertsPerPage,
2026-05-17 08:28:16 +00:00
Scenario: r.URL.Query().Get("scenario"),
IP: r.URL.Query().Get("ip"),
Since: r.URL.Query().Get("since"),
}
alerts, err := h.deps.LAPI.ListAlerts(ctx, filter)
if err != nil {
2026-05-17 14:12:06 +00:00
log.Printf("alerts: LAPI error: %v", err)
pd := NewPageData(r, "Alerts", h.deps.CLIAvailable, h.deps.PollInterval)
if crowdsec.IsForbidden(err) {
pd = pd.WithFlash("error", "Access denied: re-register machine with admin rights: cscli machines delete crowdsec-dashy && cscli machines add crowdsec-dashy -a")
} else {
pd = pd.WithFlash("error", "Failed to fetch alerts from LAPI.")
}
h.deps.Renderer.Render(w, "alerts", AlertsData{PageData: pd})
2026-05-17 08:28:16 +00:00
return
}
2026-05-17 14:12:06 +00:00
hasNext := len(alerts) > alertsPerPage
if hasNext {
alerts = alerts[:alertsPerPage]
}
2026-05-17 08:28:16 +00:00
pd := NewPageData(r, "Alerts", h.deps.CLIAvailable, h.deps.PollInterval)
if f := readFlash(r); f.Message != "" {
pd.Flash = f
}
h.deps.Renderer.Render(w, "alerts", AlertsData{
2026-05-17 14:12:06 +00:00
PageData: pd,
Alerts: alerts,
Filter: filter,
Page: page,
HasNext: hasNext,
ShowUpdates: showUpdates,
2026-05-17 08:28:16 +00:00
})
}
// Delete processes alert deletion (POST; `id` field may repeat for bulk).
func (h *AlertsHandler) Delete(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 4096)
if err := r.ParseForm(); err != nil {
flashRedirect(w, r, "/alerts", "error", "invalid form data")
return
}
if !checkCSRF(r) {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
ids := r.Form["id"]
if len(ids) == 0 {
flashRedirect(w, r, "/alerts", "error", "no alert selected")
return
}
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
defer cancel()
var errs []string
deleted := 0
for _, s := range ids {
id, err := strconv.ParseInt(s, 10, 64)
if err != nil || id <= 0 {
errs = append(errs, fmt.Sprintf("invalid id: %q", s))
continue
}
if err := h.deps.LAPI.DeleteAlert(ctx, id); err != nil {
errs = append(errs, fmt.Sprintf("id %d: %v", id, err))
continue
}
deleted++
}
if len(errs) > 0 {
flashRedirect(w, r, "/alerts", "error", strings.Join(errs, "; "))
return
}
flashRedirect(w, r, "/alerts", "success", fmt.Sprintf("%d alert(s) deleted", deleted))
}