6.0 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Additional Instructions
- personal overrides: @~/.claude/CLAUDE.md
Commands
# Build
go build -o crowdsec-dashy ./cmd/server
# Production build (static binary, stripped)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o crowdsec-dashy ./cmd/server
# Run
./crowdsec-dashy
# Lint / vet
go vet ./...
# Tests (none yet — project is in early build phases)
go test ./...
# Docker
docker compose up -d
docker compose build
Architecture
Go 1.26. Zero external Go dependencies. Stdlib only (net/http, html/template, os/exec, encoding/json).
Data access — two parallel layers
| Layer | Package | Coverage |
|---|---|---|
| CrowdSec LAPI (REST) | internal/crowdsec/lapi.go |
Decisions CRUD, Alerts, health |
cscli exec wrapper |
internal/crowdsec/cli.go |
Bouncers, Machines, Hub, Metrics |
All shared structs (Decision, Alert, Bouncer, Machine, HubItem, MetricsSection, etc.) live in internal/crowdsec/types.go.
Bouncers, machines, hub, and metrics are not exposed via REST — they require cscli hitting the database directly. Features that need cscli check Deps.CLIAvailable and render an inline warning banner when the binary is absent.
Package layout
cmd/server/main.go entrypoint — loads config, authenticates LAPI, starts HTTP server
internal/config/config.go env-var loading; CscliAvailable() stat-checks the binary path
internal/crowdsec/
types.go all shared structs
lapi.go REST client (JWT auth, auto-retry on 401)
cli.go cscli exec wrapper (strict allow-list)
internal/handlers/
renderer.go Renderer, PageData, Deps, SidebarNav
dashboard.go / api.go / ... one file per page + JSON API handler
internal/middleware/middleware.go BasicAuth, Logger, SecureHeaders, Recovery
internal/router/router.go constructs Deps, wires all routes
web/templates/layouts/base.html sidebar shell
web/templates/pages/*.html one file per page
web/static/css/app.css all component styles
web/static/js/dashboard.js live stat polling + sparklines
web/static/js/tables.js client-side filter/sort/bulk-select
Request flow
HTTP request
→ middleware chain (BasicAuth → Logger → SecureHeaders → Recovery)
→ router.go (stdlib mux)
→ handler struct (receives Deps: Renderer, LAPI, CLI, CLIAvailable, PollInterval)
→ handler renders to bytes.Buffer first, then writes to ResponseWriter
Dependency injection
Deps struct (internal/handlers/renderer.go:246) is constructed once in router.New() and passed to every handler constructor. Handlers are structs, not functions.
Template system
Renderer (internal/handlers/renderer.go) parses all page templates at startup: each page in web/templates/pages/*.html is combined with web/templates/layouts/base.html into a named *template.Template. Templates are keyed by filename without extension (e.g. "dashboard", "decisions"). All render calls go through Renderer.Render(w, name, data) using the buffer-then-write pattern.
Template functions available: inc, dec, dict, decisionBadgeClass, originBadgeClass, hubStatusClass, safeHTML, boolIcon, truncate, join.
LAPI authentication
On startup, lapi.Login() POSTs to /v1/watchers/login → JWT stored in memory. All subsequent requests carry Authorization: Bearer <jwt>. On 401, doJSON() auto-retries with a fresh login once before failing.
cscli exec security
All args passed as a slice (never through shell). Two allow-lists enforced before exec.CommandContext:
allowedSubcommands(first arg): bouncers, machines, collections, parsers, scenarios, postoverflows, hub, metrics, versionallowedActions(second arg): list, add, delete, install, remove, update, upgrade, validate- All other user-supplied values validated against
safeArgregex^[a-zA-Z0-9_./:@\-]+$
POST pattern
All state-changing POST handlers use PRG (Post/Redirect/Get). Flash messages are attached to PageData via WithFlash(type, msg) and displayed on the redirected GET. All POST handlers must apply http.MaxBytesReader before parsing the body.
Internal JSON API
Used by frontend JS — all return JSON, protected by the same BasicAuth middleware.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/stats |
Dashboard summary counts |
| GET | /api/v1/health |
LAPI health + cscli availability |
Adding a new page
internal/handlers/mypage.go— handler struct withServeHTTPor named methodsweb/templates/pages/mypage.html— starts with{{template "base" .}}- Add
NavItemtoSidebarNavinrenderer.go router.go—mux.Handle("/mypage", handlers.NewMyPageHandler(deps))
UI design
Dark industrial aesthetic. Palette: near-black surface #080b10, blue-grey panels #0f1520, cyan accent #00d4ff, threat-red #ff3b3b, safe-green #00e676. Fonts: JetBrains Mono for data, IBM Plex Sans for labels. All styles in web/static/css/app.css. No external JS frameworks — sparklines drawn with inline SVG <polyline>.
Dashboard live stats poll /api/v1/stats via web/static/js/dashboard.js. Client-side table filter/sort/bulk-select in web/static/js/tables.js.
Environment variables
| Variable | Default | Notes |
|---|---|---|
PORT |
:8080 |
Listen address |
CROWDSEC_API_URL |
http://localhost:8080 |
LAPI base URL |
CROWDSEC_API_LOGIN |
(required) | Machine login |
CROWDSEC_API_PASSWORD |
(required) | Machine password |
CSCLI_PATH |
/usr/local/bin/cscli |
Binary path; CLI features disabled if absent |
UI_USERNAME |
admin |
Basic Auth username |
UI_PASSWORD |
changeme |
Basic Auth password |
UI_SESSION_SECRET |
(required, ≥32 chars) | HMAC signing key |
POLL_INTERVAL_SEC |
15 |
Dashboard poll interval (seconds) |