Files
crowdsec-dashy/CLAUDE.md
T

137 lines
6.0 KiB
Markdown

# 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
```bash
# 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, version
- `allowedActions` (second arg): list, add, delete, install, remove, update, upgrade, validate
- All other user-supplied values validated against `safeArg` regex `^[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
1. `internal/handlers/mypage.go` — handler struct with `ServeHTTP` or named methods
2. `web/templates/pages/mypage.html` — starts with `{{template "base" .}}`
3. Add `NavItem` to `SidebarNav` in `renderer.go`
4. `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) |