mirror of
https://github.com/Dvorinka/Dash.git
synced 2026-10-28 15:51:27 +00:00
docs+release: Phase 4 launch artifacts
README hero with real screenshots, install/config/widget docs, goreleaser for dash + dash-agent, GHCR release workflow, changelog, community files, agent systemd unit and Dockerfile, version stamping via -X main.version. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
97b8f5e87f
commit
a89a81d46c
23 files changed
+785
-13
No files matched your search
@@ -0,0 +1,71 @@
|
||||
# Configuration
|
||||
|
||||
Dash is configured from the UI. The only environment the server understands:
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `DASH_PORT` | `8080` (`3000` in Docker) | listen port |
|
||||
| `DASH_DATA_DIR` | `./data` (`/data` in Docker) | SQLite + uploaded icons |
|
||||
| `DASH_DEV` | unset | `1` = dev mode: API only, no embedded UI |
|
||||
|
||||
## Data directory
|
||||
|
||||
```
|
||||
data/
|
||||
dash.db # SQLite — boards, monitors, domains, systems, ops
|
||||
icons/ # uploaded service icons
|
||||
```
|
||||
|
||||
Back up the directory to back up everything. `/api/export` produces a JSON
|
||||
snapshot; `/api/import/csv` bulk-loads monitors and domains from CSV.
|
||||
|
||||
## Notifications
|
||||
|
||||
**Settings → Notifications** holds one generic webhook URL
|
||||
(`notify_webhook`). On monitor up/down transitions and domain events, Dash
|
||||
POSTs a JSON payload with `event`, `name`, `text`, `content`, `message`, and
|
||||
`at` fields — shaped so Slack, Discord, and ntfy all render it sensibly.
|
||||
**Test** sends a `test` event. Delivery is async and non-fatal; failures are
|
||||
logged.
|
||||
|
||||
## Monitors
|
||||
|
||||
HTTP(s), TCP, ping (ICMP), DNS, keyword, JSON-path, and push types. Per-monitor
|
||||
interval, timeout, retries, headers/method/body, expected values, tags, and
|
||||
notes. Push monitors expose `POST /api/push/:token` for cron-style jobs — the
|
||||
monitor goes down if no push arrives within the expected window.
|
||||
|
||||
## Domains
|
||||
|
||||
WHOIS (RDAP first, TCP WHOIS fallback), DNS records, TLS certificate chain,
|
||||
and provider detection run daily and on demand. Expiry and certificate
|
||||
deadlines surface in the UI and through notifications.
|
||||
|
||||
## Systems
|
||||
|
||||
Bearer-token push ingest at `POST /api/systems/ingest`. A system is marked
|
||||
offline when no sample arrives within a few intervals. Stats are pruned during
|
||||
the hourly sweep.
|
||||
|
||||
## Operations
|
||||
|
||||
- **Incidents** — open/ack/resolve/close, with per-incident update timeline.
|
||||
A monitor going down opens an incident automatically; recovery resolves it.
|
||||
- **Maintenance windows** — suppress monitor alerts for the window's scope.
|
||||
- **Status pages** — public, unauthenticated views at `/status/:slug` listing
|
||||
selected monitors and systems with live state.
|
||||
- **Badges** — `GET /api/badge/monitor/:id.svg` and
|
||||
`GET /api/badge/domain/:id.svg` for embedding status shields.
|
||||
- **Metrics** — `GET /api/metrics` in Prometheus exposition format.
|
||||
|
||||
## Importers
|
||||
|
||||
Settings → Import accepts:
|
||||
|
||||
- Dash JSON export (full board restore)
|
||||
- Homepage `services.yml`
|
||||
- Homarr board JSON
|
||||
- Dashy `conf.yml`
|
||||
- CSV of monitors/domains (`/api/import/csv`)
|
||||
|
||||
Foreign formats map onto sections, items, and URLs; unknown fields are dropped.
|
||||
@@ -0,0 +1,124 @@
|
||||
# Installation
|
||||
|
||||
Dash ships as a single container image or a single static binary. All state
|
||||
lives in one directory: a SQLite database and uploaded icons.
|
||||
|
||||
## Docker (recommended)
|
||||
|
||||
```sh
|
||||
docker run -d --name dash \
|
||||
-v ./data:/data \
|
||||
-p 3000:3000 \
|
||||
--restart unless-stopped \
|
||||
ghcr.io/dvorinka/dash:latest
|
||||
```
|
||||
|
||||
Or compose:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
dash:
|
||||
image: ghcr.io/dvorinka/dash:latest
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- ./data:/data
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
Open http://localhost:3000.
|
||||
|
||||
To build the image locally instead of pulling:
|
||||
|
||||
```sh
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
## Binary
|
||||
|
||||
Release archives contain a static `dash` binary (no CGO, no dependencies).
|
||||
|
||||
```sh
|
||||
tar xzf dash_linux_amd64.tar.gz
|
||||
./dash # listens on :8080, stores data in ./data
|
||||
```
|
||||
|
||||
Set `DASH_PORT` and `DASH_DATA_DIR` to change the defaults.
|
||||
|
||||
## dash-agent (system monitoring)
|
||||
|
||||
The agent collects CPU, memory, disk, network, load, temperatures, and Docker
|
||||
container state, then pushes a JSON sample to Dash every few seconds.
|
||||
|
||||
1. In the UI, go to **Systems → New system**, copy the generated token.
|
||||
2. Download the `dash-agent` binary for the host's architecture from the
|
||||
release page, or build it:
|
||||
|
||||
```sh
|
||||
cd apps/backend && go build -o dash-agent ./cmd/dash-agent
|
||||
```
|
||||
|
||||
3. Run it:
|
||||
|
||||
```sh
|
||||
dash-agent -url http://dash:3000 -token <token>
|
||||
```
|
||||
|
||||
### Flags / environment
|
||||
|
||||
| Flag | Env | Default | Purpose |
|
||||
|---|---|---|---|
|
||||
| `-url` | `DASH_URL` | — | Dash base URL |
|
||||
| `-token` | `DASH_TOKEN` | — | system token from the UI |
|
||||
| `-interval` | `DASH_INTERVAL` | `10` | push interval, seconds |
|
||||
| `-once` | — | `false` | print one sample and exit (no push) |
|
||||
|
||||
If `/var/run/docker.sock` exists, container states are included automatically.
|
||||
|
||||
### systemd
|
||||
|
||||
`packaging/dash-agent.service` is a ready-made unit:
|
||||
|
||||
```sh
|
||||
sudo install -m755 dash-agent /usr/local/bin/dash-agent
|
||||
sudo install -m644 packaging/dash-agent.service /etc/systemd/system/
|
||||
sudo mkdir -p /etc/dash
|
||||
printf 'DASH_URL=http://dash:3000\nDASH_TOKEN=<token>\n' | sudo tee /etc/dash/agent.env
|
||||
sudo systemctl enable --now dash-agent
|
||||
```
|
||||
|
||||
### Docker (limited)
|
||||
|
||||
`infra/Dockerfile.agent` builds a minimal agent image. A containerized agent
|
||||
only sees its own `/proc` — mount the host's procfs and use host PID mode for
|
||||
meaningful numbers:
|
||||
|
||||
```sh
|
||||
docker run -d --name dash-agent --pid=host \
|
||||
-v /proc:/host/proc:ro \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock:ro \
|
||||
-e DASH_URL=http://dash:3000 -e DASH_TOKEN=<token> \
|
||||
ghcr.io/dvorinka/dash-agent:latest
|
||||
```
|
||||
|
||||
The systemd install above is the recommended path.
|
||||
|
||||
## Reverse proxy
|
||||
|
||||
Dash serves the UI and API on one port — a plain reverse proxy works:
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
```
|
||||
|
||||
## Upgrading
|
||||
|
||||
Pull the new image and restart, or replace the binary. Goose migrations run
|
||||
automatically on boot; `data/` carries forward.
|
||||
|
||||
```sh
|
||||
docker pull ghcr.io/dvorinka/dash:latest && docker compose up -d
|
||||
```
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -0,0 +1,89 @@
|
||||
# Widget development
|
||||
|
||||
A widget is two pieces:
|
||||
|
||||
1. a Go **fetcher** in `apps/backend/internal/widget/` that declares config
|
||||
fields and pulls live data, and
|
||||
2. a React **view** in `apps/frontend/src/widgets/` that renders the payload
|
||||
inside a tile.
|
||||
|
||||
Renderers own the tile chrome — widgets never touch layout, drag-drop, or
|
||||
popovers.
|
||||
|
||||
## Backend fetcher
|
||||
|
||||
One file, registered in `init()`:
|
||||
|
||||
```go
|
||||
package widget
|
||||
|
||||
type pihole struct{}
|
||||
|
||||
func init() { Register(pihole{}) }
|
||||
|
||||
func (pihole) Meta() Type {
|
||||
return Type{
|
||||
Type: "pihole",
|
||||
Name: "Pi-hole",
|
||||
Description: "Queries and block rate",
|
||||
Fields: []Field{
|
||||
{Key: "endpoint", Label: "Endpoint", Placeholder: "http://pi.hole", Required: true},
|
||||
{Key: "token", Label: "API token", Secret: true},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (pihole) Fetch(ctx context.Context, cfg json.RawMessage) (any, error) {
|
||||
// cfg holds the item's config JSON. Never log it — it may hold keys.
|
||||
}
|
||||
```
|
||||
|
||||
- `Meta()` drives the add-widget dialog: `GET /api/widgets/types` returns
|
||||
every registered type with its fields.
|
||||
- `Fetch` is called by `GET /api/widgets/:id/data`; responses are cached ~30s
|
||||
in-process. Return any JSON-marshalable value — it reaches the frontend as
|
||||
`data`.
|
||||
- Read config with the `cfgStr`/`cfgNum` helpers in the package.
|
||||
- Fetchers needing the database (monitor, domain, system) live in
|
||||
`internal/api` and register via `widget.Register` at router setup — the
|
||||
`widget` package stays a leaf with no DB dependency.
|
||||
|
||||
## Frontend view
|
||||
|
||||
One component in `apps/frontend/src/widgets/`, one line in the registry at
|
||||
`src/widgets/index.tsx`:
|
||||
|
||||
```tsx
|
||||
export function PiholeWidget({ data, error }: { item: Item; data?: WidgetData; error?: string }) {
|
||||
if (error) return <TileError msg={error} />;
|
||||
if (!data) return <TileLoading />;
|
||||
return <div>{data.blockedPercent}%</div>;
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
const registry: WidgetRegistry = {
|
||||
pihole: { view: PiholeWidget },
|
||||
};
|
||||
```
|
||||
|
||||
- `useWidgetData` polls `/api/widgets/:id/data` every 30s. `data` is the
|
||||
fetcher's payload, `error` is a human-readable upstream failure.
|
||||
- Payloads are free-form; read fields defensively (values may be absent on
|
||||
first paint).
|
||||
- `local: true` marks a widget as frontend-only (e.g. clock) — no fetcher, no
|
||||
polling.
|
||||
|
||||
## Config fields
|
||||
|
||||
`Fields` in `Meta()` render inputs in the add-widget dialog. `Secret: true`
|
||||
masks the input. Config is stored in `items.config` JSON, sent back to the
|
||||
fetcher on every poll, and never logged.
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] `apps/backend/internal/widget/<name>.go` — fetcher + `init()` + `Meta()`
|
||||
- [ ] `apps/frontend/src/widgets/<name>.tsx` — view component
|
||||
- [ ] registry entry in `src/widgets/index.tsx`
|
||||
- [ ] error and empty-data states render cleanly in the tile
|
||||
- [ ] upstream timeouts bounded (respect `ctx`, keep HTTP client timeouts)
|
||||
Reference in new issue
Block a user