# 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 <name>`, 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 <jwt>`

---

## 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 `<polyline>` 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`.
