# CrowdSec UI

A lightweight, dependency-free web dashboard for monitoring and managing CrowdSec.

**Stack**: Go 1.26 · `html/template` · Tailwind CSS (CDN) · Zero external Go packages

---

## Features

| Feature | Transport | Requires |
|---|---|---|
| Decisions (list, add, delete) | LAPI REST | Machine credentials |
| Alerts (list, delete) | LAPI REST | Machine credentials |
| Live dashboard stats | LAPI REST (polled) | Machine credentials |
| Bouncers (list, add, delete) | `cscli exec` | cscli binary |
| Machines (list, validate, delete) | `cscli exec` | cscli binary |
| Hub (collections/parsers/scenarios) | `cscli exec` | cscli binary |
| Metrics | `cscli exec` | cscli binary |

> All `cscli`-backed features degrade gracefully with a banner if the binary is unavailable.

---

## Quick Start

### 1. Register the UI as a CrowdSec machine

```bash
# On the host running CrowdSec:
sudo cscli machines add crowdsec-dashy --password your-password -a -f /etc/crowdsec/dashy_credentials.yaml

# Or if CrowdSec is in Docker:
docker exec crowdsec cscli machines add crowdsec-dashy --password your-password -a -f /etc/crowdsec/dashy_credentials.yaml
```

### 2. Configure environment variables

Copy or edit `docker-compose.yml` and set:

```yaml
CROWDSEC_API_LOGIN: "crowdsec-dashy"
CROWDSEC_API_PASSWORD: "your-password"     # must match step 1
UI_USERNAME: "admin"
UI_PASSWORD: "your-ui-password"            # Basic Auth for the web UI
UI_SESSION_SECRET: "32-char-random-string"
```

### 3. Set up cscli bind-mount (for CLI features)

```bash
# If CrowdSec is in Docker, extract cscli:
docker cp crowdsec:/usr/local/bin/cscli /usr/local/bin/cscli
chmod +x /usr/local/bin/cscli

# The docker-compose.yml already bind-mounts it:
# - /usr/local/bin/cscli:/usr/local/bin/cscli:ro
```

### 4. Launch

```bash
docker compose up -d
```

Open `http://localhost:8080` — browser will prompt for Basic Auth.

---

## Running Directly (no Docker)

```bash
# Build
go build -o crowdsec-dashy ./cmd/server

# Set env vars
export CROWDSEC_API_URL="http://localhost:8080"
export CROWDSEC_API_LOGIN="crowdsec-dashy"
export CROWDSEC_API_PASSWORD="your-password"
export UI_USERNAME="admin"
export UI_PASSWORD="your-ui-password"
export UI_SESSION_SECRET="32-char-random-string"

# Run
./crowdsec-dashy
```

---

## Environment Variables

| Variable | Default | Description |
|---|---|---|
| `PORT` | `:8080` | Listen address |
| `CROWDSEC_API_URL` | `http://localhost:8080` | CrowdSec LAPI base URL |
| `CROWDSEC_API_LOGIN` | *(required)* | Machine login (cscli machines add) |
| `CROWDSEC_API_PASSWORD` | *(required)* | Machine password |
| `CSCLI_PATH` | `/usr/local/bin/cscli` | Path to cscli binary |
| `UI_USERNAME` | `admin` | Basic Auth username |
| `UI_PASSWORD` | `changeme` | Basic Auth password — **change this** |
| `UI_SESSION_SECRET` | *(required, ≥32 chars)* | HMAC signing key |
| `POLL_INTERVAL_SEC` | `15` | Dashboard live-poll interval |

---

## Project Structure

```
crowdsec-dashy/
├── cmd/server/main.go              entrypoint
├── internal/
│   ├── config/config.go            env-var loading
│   ├── crowdsec/
│   │   ├── types.go                all shared structs
│   │   ├── lapi.go                 REST client (decisions, alerts)
│   │   └── cli.go                  cscli exec wrapper
│   ├── handlers/
│   │   ├── renderer.go             template engine + PageData
│   │   ├── dashboard.go
│   │   ├── decisions.go
│   │   ├── alerts.go
│   │   ├── bouncers.go
│   │   ├── machines.go
│   │   ├── hub.go
│   │   ├── metrics.go
│   │   └── api.go                  JSON API for dashboard polling
│   ├── middleware/middleware.go     BasicAuth, Logger, SecureHeaders, Recovery
│   └── router/router.go            route wiring
└── web/
    ├── templates/layouts/base.html sidebar shell
    ├── templates/pages/            one file per page
    └── static/css/app.css          all component styles
        static/js/app.js            global JS
        static/js/dashboard.js      stats polling
```

---

## Adding a New Page

1. Create `internal/handlers/mypage.go` with a handler struct
2. Create `web/templates/pages/mypage.html` starting with `{{template "base" .}}`
3. Add to `SidebarNav` in `renderer.go`
4. Add `mux.Handle("/mypage", handlers.NewMyPageHandler(deps))` in `router.go`

---

## Security Notes

- All routes protected by HTTP Basic Auth — put behind TLS (nginx/Caddy) in production
- `cscli` args passed as a slice (never through a shell); strict allow-list of subcommands
- LAPI JWT stored in memory only, never logged
- Generated bouncer API keys shown exactly once, never stored by the UI
- Security headers on every response: CSP, X-Frame-Options, X-Content-Type-Options
- Server has explicit Read/Write/Idle timeouts

---

## Dependencies

**Go**: zero external packages — stdlib only.
**Frontend**: Tailwind CSS via CDN (CSS only, no JS framework), Google Fonts via CDN.
