# CrowdSec Dashy — Project Plan > Pure Go · No NPM · Tailwind CDN · Zero third-party Go packages (except SQLite if needed) --- ## 1. Architecture Overview ### Data Access — Two Layers | Layer | What it covers | Auth | |---|---|---| | **CrowdSec LAPI** (REST, `net/http`) | Decisions CRUD, Alerts read/delete, health check | Machine login + password → JWT | | **cscli exec** (`os/exec`) | Bouncers, Machines, Hub (collections/parsers/scenarios), Metrics | Binary path (mounted volume in Docker) | > **Critical finding**: `cscli bouncers add/delete/list`, `machines add/delete/list`, and all `hub` commands hit the database directly and are **not** exposed through the REST API. Any Docker deployment must bind-mount the `cscli` binary or the app must gracefully degrade those sections. ### Deployment Modes ``` Mode A — Sidecar Docker (recommended) crowdsec container ←→ crowdsec-dashy container - shared Docker network (LAPI at http://crowdsec:8080) - /usr/local/bin/cscli bind-mounted read-only from host - CROWDSEC_API_LOGIN / PASSWORD as machine credentials Mode B — Host binary - Direct localhost:8080 LAPI - cscli already on PATH ``` ### No SQLite needed All state lives in CrowdSec's own database. No local DB required. --- ## 2. Environment Variables ```bash PORT=:8080 # UI listen address CROWDSEC_API_URL=http://localhost:8080 # LAPI base URL CROWDSEC_API_LOGIN=crowdsec-dashy # machine login (create with cscli machines add) CROWDSEC_API_PASSWORD=changeme # machine password CSCLI_PATH=/usr/local/bin/cscli # path to cscli binary UI_USERNAME=admin # basic-auth for the UI itself UI_PASSWORD=changeme # basic-auth for the UI itself UI_SESSION_SECRET=random32chars # HMAC session signing key POLL_INTERVAL_SEC=15 # dashboard live-poll interval ``` --- ## 3. Project Structure ``` crowdsec-dashy/ ├── go.mod (module crowdsec-dashy, go 1.22) ├── Dockerfile ├── docker-compose.yml │ ├── cmd/server/ │ └── main.go entrypoint, env config, server init │ ├── internal/ │ ├── config/ │ │ └── config.go env-var loading, validation │ │ │ ├── crowdsec/ │ │ ├── types.go Decision, Alert, Bouncer, Machine, Hub*, Metrics structs │ │ ├── lapi.go REST client: login, decisions CRUD, alerts, health │ │ └── cli.go cscli wrapper: bouncers, machines, hub, metrics │ │ │ ├── handlers/ │ │ ├── renderer.go template engine + PageData (sidebar nav) │ │ ├── dashboard.go GET / │ │ ├── decisions.go GET/POST /decisions, POST /decisions/delete │ │ ├── alerts.go GET /alerts, POST /alerts/delete │ │ ├── bouncers.go GET/POST /bouncers │ │ ├── machines.go GET/POST /machines │ │ ├── hub.go GET /hub, POST /hub/install, POST /hub/remove │ │ ├── metrics.go GET /metrics-ui │ │ └── api.go JSON API: /api/v1/stats, /api/v1/decisions, etc. │ │ │ ├── middleware/ │ │ └── middleware.go BasicAuth, SecureHeaders, Logger, Recovery │ │ │ └── router/ │ └── router.go route wiring │ └── web/ ├── templates/ │ ├── layouts/ │ │ └── base.html sidebar shell (not top-nav) │ └── pages/ │ ├── dashboard.html │ ├── decisions.html │ ├── alerts.html │ ├── bouncers.html │ ├── machines.html │ ├── hub.html │ ├── metrics.html │ └── error.html └── static/ ├── css/app.css component classes, sidebar, badges, tables └── js/ ├── app.js global: sidebar toggle, flash dismiss ├── dashboard.js SSE/polling for live stats + sparklines └── tables.js client-side filter/sort, confirm-delete modal ``` --- ## 4. Pages & Features ### 4.1 Dashboard (`/`) - **Stat cards**: Active Bans, Alerts (24h), Bouncers connected, Machines registered - **Live-update**: JS polls `/api/v1/stats` every N seconds, updates counters in-place - **Recent activity**: Last 10 alerts table (scenario, IP, date, origin) - **Recent bans**: Last 10 decisions table - **CrowdSec version + health** badge (green/red dot) ### 4.2 Decisions (`/decisions`) - Table: IP/Range, Type (ban/captcha), Scenario, Origin, Duration, Expires At - **Filters**: type, origin, scope (IP / Range / Country) - **Actions**: Delete individual (POST /decisions/delete/{id}), Bulk delete (checked rows) - **Add Decision** form: IP, scope, type, duration (PRG pattern) - Pagination (server-side, offset/limit passed to LAPI) ### 4.3 Alerts (`/alerts`) - Table: ID, Scenario, IP, Country, ASN, Date, Decisions count - Click row → detail drawer (all events in alert, full metadata) - Delete individual alert, bulk delete selected - Filter by scenario, IP, date range (client-side JS) ### 4.4 Bouncers (`/bouncers`) - Table: Name, IP, Last Seen, Version, Type, Valid - **Add** bouncer (runs `cscli bouncers add `, shows generated API key once — modal) - **Delete** bouncer (POST with confirm) - Status badge: green (valid) / red (revoked) - *Graceful degradation*: if cscli unavailable, show inline warning banner ### 4.5 Machines (`/machines`) - Table: Name, IP, Last Update, Version, Auth Type, Status, Last Heartbeat - Add machine (`cscli machines add`) - Validate pending machine (`cscli machines validate`) - Delete machine - *Graceful degradation*: cscli required ### 4.6 Hub (`/hub`) - Tabbed view: Collections | Parsers | Scenarios | Postoverflows - Each tab: list installed (with version), list available to install - Install / Remove actions → POST → runs `cscli hub install/remove` - Update all button (`cscli hub update && cscli hub upgrade`) - *Graceful degradation*: cscli required ### 4.7 Metrics (`/metrics-ui`) - Parsed output of `cscli metrics` (acquisition, parsers, scenarios, LAPI) - Tables grouped by category: Acquisition Sources, Parser Stats, Scenario Stats, LAPI Stats - Auto-refresh toggle (polls every 30s) - *Graceful degradation*: cscli required --- ## 5. Internal API (`/api/v1/...`) Used by frontend JS — all return JSON, all protected by same BasicAuth. | Method | Path | Description | |---|---|---| | GET | `/api/v1/stats` | Dashboard summary counts | | GET | `/api/v1/decisions` | Paginated decisions (query: page, limit, type, scope) | | DELETE | `/api/v1/decisions/{id}` | Delete one decision | | GET | `/api/v1/alerts` | Paginated alerts | | DELETE | `/api/v1/alerts/{id}` | Delete one alert | | GET | `/api/v1/health` | LAPI health + cscli availability | --- ## 6. Authentication The UI sits behind **HTTP Basic Auth** (`middleware.BasicAuth`) — simple and works in Docker behind a reverse proxy with TLS. The app itself authenticates to CrowdSec LAPI as a **machine** (not a bouncer) to get full read+write access to decisions and alerts. Flow: 1. On startup, `lapi.Login()` POSTs credentials to `/v1/watchers/login` → JWT 2. JWT stored in memory, refreshed on 401 response 3. All LAPI requests carry `Authorization: Bearer ` --- ## 7. UI Design **Aesthetic**: Industrial/utilitarian dark — think terminal output meets ops dashboard. Not a generic admin panel. - **Layout**: Fixed left sidebar (240px), collapsible on mobile. Content area with header bar. - **Palette**: Near-black surface (`#080b10`), blue-grey panels (`#0f1520`), electric cyan accent (`#00d4ff`), threat-red (`#ff3b3b`), safe-green (`#00e676`) - **Typography**: `JetBrains Mono` for data/numbers/badges, `IBM Plex Sans` for labels/headings - **Tables**: Monospaced IP addresses, color-coded type badges (ban=red, captcha=amber, custom=blue) - **Sidebar nav**: Icon + label, active state left-border accent, subtle hover - **Stat cards**: Large mono number, label, delta indicator, thin accent top-border - **No external chart libs**: Sparklines drawn with inline SVG `` from JS --- ## 8. Build Phases ### Phase 1 — Foundation - `go.mod`, config, folder structure - `crowdsec/types.go` — all structs - `crowdsec/lapi.go` — HTTP client, login, decisions, alerts - `crowdsec/cli.go` — exec wrapper, JSON parsing for bouncers/machines/hub/metrics - `middleware/middleware.go` — BasicAuth + existing middleware - `router/router.go` skeleton - `base.html` — sidebar shell + Tailwind config ### Phase 2 — Dashboard + API - `handlers/api.go` — `/api/v1/stats`, `/api/v1/health` - `handlers/dashboard.go` + `dashboard.html` - `static/js/dashboard.js` — polling, counter animation, sparklines ### Phase 3 — Decisions & Alerts - `handlers/decisions.go` + `decisions.html` - `handlers/alerts.go` + `alerts.html` - `static/js/tables.js` — filter, sort, bulk-select, confirm modal ### Phase 4 — Bouncers & Machines - `handlers/bouncers.go` + `bouncers.html` - `handlers/machines.go` + `machines.html` - Graceful degradation UI pattern ### Phase 5 — Hub & Metrics - `handlers/hub.go` + `hub.html` - `handlers/metrics.go` + `metrics.html` ### Phase 6 — Docker + Polish - `Dockerfile` (multi-stage: build → scratch/alpine) - `docker-compose.yml` with bind-mount examples - Error page handler - README with setup instructions --- ## 9. Docker Compose Example ```yaml version: "3.9" services: crowdsec: image: crowdsecurity/crowdsec:latest volumes: - crowdsec-data:/var/lib/crowdsec/data - crowdsec-config:/etc/crowdsec networks: - cs-net crowdsec-dashy: build: . ports: - "8080:8080" environment: CROWDSEC_API_URL: http://crowdsec:8080 CROWDSEC_API_LOGIN: crowdsec-dashy CROWDSEC_API_PASSWORD: changeme CSCLI_PATH: /usr/local/bin/cscli UI_USERNAME: admin UI_PASSWORD: changeme volumes: # Bind-mount cscli from the host for CLI features - /usr/local/bin/cscli:/usr/local/bin/cscli:ro networks: - cs-net depends_on: - crowdsec networks: cs-net: volumes: crowdsec-data: crowdsec-config: ``` --- ## 10. Security Checklist - [ ] All routes behind BasicAuth middleware - [ ] LAPI credentials in env only, never in templates or logs - [ ] `cscli` exec: args passed as slice (no shell injection), strict allow-list of commands - [ ] All POST forms validate inputs server-side before exec/API call - [ ] PRG pattern on all state-changing POSTs - [ ] `http.MaxBytesReader` on all POST handlers - [ ] Security headers middleware (CSP, X-Frame-Options, etc.) - [ ] Server timeouts (Read/Write/Idle) - [ ] Template buffer pattern (render to bytes.Buffer first) - [ ] JWT token never logged - [ ] Generated bouncer API keys shown exactly once, never stored by the UI --- ## 11. go.mod ``` module crowdsec-dashy go 1.22 ``` **Zero external Go dependencies.** All CrowdSec communication via stdlib `net/http` + `os/exec`.