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,31 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: Something is broken
|
||||||
|
labels: [bug]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: what
|
||||||
|
attributes:
|
||||||
|
label: What happened
|
||||||
|
description: What you did, what you expected, what happened instead.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Logs
|
||||||
|
description: Relevant server logs or browser console output.
|
||||||
|
render: text
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Dash version
|
||||||
|
description: Image tag or release version.
|
||||||
|
placeholder: v0.7.0
|
||||||
|
- type: dropdown
|
||||||
|
id: install
|
||||||
|
attributes:
|
||||||
|
label: Install method
|
||||||
|
options:
|
||||||
|
- Docker
|
||||||
|
- Binary
|
||||||
|
- Built from source
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Suggest an addition
|
||||||
|
labels: [enhancement]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: What problem does this solve
|
||||||
|
description: Concrete use case, not just "add X".
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: idea
|
||||||
|
attributes:
|
||||||
|
label: Proposed shape
|
||||||
|
description: Optional. How it might look or work.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
## Summary
|
||||||
|
|
||||||
|
<!-- What does this change and why? -->
|
||||||
|
|
||||||
|
## Test plan
|
||||||
|
|
||||||
|
- [ ] `npm run typecheck` + `npm run lint` + `npm run build` green
|
||||||
|
- [ ] `go build ./...` + `go vet ./...` + `go test ./...` green
|
||||||
|
- [ ] Smoke-tested the affected flow manually
|
||||||
|
|
||||||
|
<!-- Screenshots for UI changes. -->
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
name: release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ["v*"]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
binaries:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
cache: npm
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm run build
|
||||||
|
|
||||||
|
# go:embed all:dist reads internal/web/dist at compile time.
|
||||||
|
- run: rm -rf apps/backend/internal/web/dist && cp -r apps/frontend/dist apps/backend/internal/web/dist
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: apps/backend/go.mod
|
||||||
|
cache-dependency-path: apps/backend/go.sum
|
||||||
|
|
||||||
|
- uses: goreleaser/goreleaser-action@v6
|
||||||
|
with:
|
||||||
|
distribution: goreleaser
|
||||||
|
version: "~> v2"
|
||||||
|
args: release --clean
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- uses: docker/metadata-action@v5
|
||||||
|
id: meta
|
||||||
|
with:
|
||||||
|
images: ghcr.io/${{ github.repository }}
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=raw,value=latest
|
||||||
|
|
||||||
|
- uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: infra/Dockerfile
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
project_name: dash
|
||||||
|
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
# The embedded UI must be built and copied into internal/web/dist before
|
||||||
|
# any Go build. The release workflow does this; fail loudly here if the
|
||||||
|
# stub is still in place.
|
||||||
|
- sh -c 'test -d apps/backend/internal/web/dist/assets'
|
||||||
|
|
||||||
|
builds:
|
||||||
|
- id: dash
|
||||||
|
dir: apps/backend
|
||||||
|
binary: dash
|
||||||
|
main: ./cmd/dash
|
||||||
|
env: [CGO_ENABLED=0]
|
||||||
|
goos: [linux, darwin, windows]
|
||||||
|
goarch: [amd64, arm64]
|
||||||
|
ldflags: ["-s", "-w", "-X main.version={{.Version}}"]
|
||||||
|
|
||||||
|
- id: dash-agent
|
||||||
|
dir: apps/backend
|
||||||
|
binary: dash-agent
|
||||||
|
main: ./cmd/dash-agent
|
||||||
|
env: [CGO_ENABLED=0]
|
||||||
|
goos: [linux]
|
||||||
|
goarch: [amd64, arm64, arm]
|
||||||
|
goarm: ["7"]
|
||||||
|
ldflags: ["-s", "-w", "-X main.version={{.Version}}"]
|
||||||
|
|
||||||
|
archives:
|
||||||
|
- id: dash
|
||||||
|
formats: [tar.gz]
|
||||||
|
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}"
|
||||||
|
format_overrides:
|
||||||
|
- goos: windows
|
||||||
|
formats: [zip]
|
||||||
|
files: [README.md, LICENSE]
|
||||||
|
|
||||||
|
checksum:
|
||||||
|
name_template: "checksums.txt"
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
use: github
|
||||||
|
sort: asc
|
||||||
|
filters:
|
||||||
|
exclude:
|
||||||
|
- "^docs:"
|
||||||
|
- "^chore:"
|
||||||
|
- "^test:"
|
||||||
|
|
||||||
|
release:
|
||||||
|
footer: |
|
||||||
|
Docker: `docker pull ghcr.io/dvorinka/dash:{{ .Tag }}`
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to Dash. Format follows
|
||||||
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
|
||||||
|
[Semantic Versioning](https://semver.org/).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.7.0] — 2026-09-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Incidents with open/ack/resolve/close lifecycle and per-incident update
|
||||||
|
timeline; monitors auto-open and auto-resolve incidents on transitions
|
||||||
|
- Maintenance windows that suppress monitor alerts
|
||||||
|
- Public status pages at `/status/:slug` (unauthenticated, per-page monitor
|
||||||
|
and system selection)
|
||||||
|
- SVG badges: `/api/badge/monitor/:id.svg`, `/api/badge/domain/:id.svg`
|
||||||
|
- Prometheus metrics at `/api/metrics`
|
||||||
|
- CSV bulk import (`/api/import/csv`) and monitors/domains/systems in JSON
|
||||||
|
export
|
||||||
|
- Generic webhook notifications (`notify_webhook` setting): monitor up/down
|
||||||
|
and domain events, Slack/Discord/ntfy-compatible payload, `/api/notify/test`
|
||||||
|
|
||||||
|
## [0.6.0] — 2026-09-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- System monitoring: `systems` + `system_stats`, bearer-token push ingest
|
||||||
|
(`/api/systems/ingest`), offline detection
|
||||||
|
- `dash-agent` binary: Linux collector for CPU, memory, swap, disk, network,
|
||||||
|
load, uptime, temperatures, and Docker container state
|
||||||
|
- Systems list and detail pages with Recharts graphs; system board widget
|
||||||
|
|
||||||
|
## [0.5.0] — 2026-09-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Domain watch: RDAP + TCP-WHOIS lookup, WHOIS parsing, DNS records
|
||||||
|
(NS/MX/TXT/A/AAAA), TLS certificate details, host geo, DNS/email/hosting/
|
||||||
|
certificate provider detection
|
||||||
|
- Daily refresh scheduler, manual refresh, check history
|
||||||
|
- Domain list and detail pages, expiry and SSL countdowns, domain board widget
|
||||||
|
|
||||||
|
## [0.4.0] — 2026-09-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Uptime monitors: HTTP, TCP, ping, DNS, keyword, JSON-path, and push types;
|
||||||
|
interval/timeout/retries config, heartbeat history, 24h/30d uptime stats
|
||||||
|
- Monitor scheduler, check-now endpoint, push ingest (`/api/push/:token`)
|
||||||
|
- Monitor list and detail pages, monitor board widget, service-to-monitor
|
||||||
|
creation checkbox
|
||||||
|
- Importers: Homepage `services.yml`, Homarr JSON, Dashy `conf.yml`
|
||||||
|
|
||||||
|
## [0.3.0] — 2026-09-17
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Index and Console board renderers (four total), command palette (⌘K),
|
||||||
|
icon CDN suggestions
|
||||||
|
|
||||||
|
## [0.2.0] — 2026-09-17
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Widget layer: Go fetcher registry, config-field metadata, 30s TTL cache
|
||||||
|
- Pi-hole, AdGuard Home, Immich, and clock widgets; per-widget config UI
|
||||||
|
|
||||||
|
## [0.1.0] — 2026-09-17
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Board MVP: sections, items, multi-URL services, drag-drop, collapse state
|
||||||
|
- Bento and Cards renderers, dark/light themes, icon upload + letter fallback
|
||||||
|
- Server-side status pings with up/down chips
|
||||||
|
- Single Go binary serving the embedded React UI; SQLite via goose migrations;
|
||||||
|
Docker image on distroless
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/Dvorinka/Dash/compare/v0.7.0...HEAD
|
||||||
|
[0.7.0]: https://github.com/Dvorinka/Dash/compare/v0.6.0...v0.7.0
|
||||||
|
[0.6.0]: https://github.com/Dvorinka/Dash/compare/v0.5.0...v0.6.0
|
||||||
|
[0.5.0]: https://github.com/Dvorinka/Dash/compare/v0.4.0...v0.5.0
|
||||||
|
[0.4.0]: https://github.com/Dvorinka/Dash/compare/v0.3.0...v0.4.0
|
||||||
|
[0.3.0]: https://github.com/Dvorinka/Dash/compare/v0.2.0...v0.3.0
|
||||||
|
[0.2.0]: https://github.com/Dvorinka/Dash/compare/v0.1.0...v0.2.0
|
||||||
|
[0.1.0]: https://github.com/Dvorinka/Dash/releases/tag/v0.1.0
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Code of Conduct
|
||||||
|
|
||||||
|
This project follows the [Contributor Covenant](https://www.contributor-covenant.org/),
|
||||||
|
version 2.1.
|
||||||
|
|
||||||
|
## Our pledge
|
||||||
|
|
||||||
|
We pledge to make participation in this project a harassment-free experience
|
||||||
|
for everyone, regardless of age, body size, visible or invisible disability,
|
||||||
|
ethnicity, sex characteristics, gender identity and expression, level of
|
||||||
|
experience, education, socio-economic status, nationality, personal appearance,
|
||||||
|
race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment:
|
||||||
|
|
||||||
|
- Demonstrating empathy and kindness toward other people
|
||||||
|
- Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
- Giving and gracefully accepting constructive feedback
|
||||||
|
- Accepting responsibility and apologizing to those affected by our mistakes
|
||||||
|
- Focusing on what is best for the community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior:
|
||||||
|
|
||||||
|
- The use of sexualized language or imagery, and sexual attention or advances
|
||||||
|
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
- Public or private harassment
|
||||||
|
- Publishing others' private information without explicit permission
|
||||||
|
- Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of unacceptable behavior may be reported to the maintainer via
|
||||||
|
GitHub (@Dvorinka). All complaints will be reviewed and investigated promptly
|
||||||
|
and fairly. The full enforcement guidelines are at
|
||||||
|
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
Thanks for considering a contribution. Dash is a small, deliberate codebase —
|
||||||
|
read [AGENTS.md](AGENTS.md) and [ROADMAP.md](ROADMAP.md) first; the roadmap is
|
||||||
|
canonical for what gets built and when.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Requires Go 1.24+ and Node 20+.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
npm run generate # regenerate the TS client from openapi.yaml
|
||||||
|
npm run dev # vite dev server on :5173, proxies /api to :8080
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd apps/backend
|
||||||
|
DASH_DEV=1 go run ./cmd/dash # API on :8080
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules of the house
|
||||||
|
|
||||||
|
- `openapi.yaml` is the API contract. Change it first, then
|
||||||
|
`npm run generate`. Never hand-write API types in the frontend.
|
||||||
|
- All schema changes go through goose migrations in
|
||||||
|
`apps/backend/internal/db/migrations` — `NNNN_name.sql` with `-- +goose Up`
|
||||||
|
and `-- +goose Down`.
|
||||||
|
- Board renderers share state, drag-drop, and popover primitives. Never
|
||||||
|
reimplement them per renderer.
|
||||||
|
- No auth, no YAML-as-source-of-truth, no external database. These are
|
||||||
|
non-goals for v1.
|
||||||
|
- Standard library before dependencies; existing helpers before new ones.
|
||||||
|
|
||||||
|
## Before pushing
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run typecheck
|
||||||
|
npm run lint
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
cd apps/backend
|
||||||
|
go build ./... && go vet ./... && go test ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
Plus a real smoke test of whatever you changed. All gates green or no push.
|
||||||
|
|
||||||
|
## Commits and PRs
|
||||||
|
|
||||||
|
- Conventional-ish subjects: `feat:`, `fix:`, `docs:`, `refactor:` — keep them
|
||||||
|
short and describe the why.
|
||||||
|
- Small, reviewable PRs. Fill in the template; a screenshot of UI changes is
|
||||||
|
worth more than a paragraph.
|
||||||
|
|
||||||
|
## Reporting bugs / requesting features
|
||||||
|
|
||||||
|
Use the issue templates. For security issues, see
|
||||||
|
[SECURITY.md](SECURITY.md) — do not open a public issue.
|
||||||
@@ -3,21 +3,69 @@
|
|||||||
A self-hosted homelab dashboard. CasaOS-grade ease of use, Vercel-grade design.
|
A self-hosted homelab dashboard. CasaOS-grade ease of use, Vercel-grade design.
|
||||||
Single container, UI-managed, no YAML, no auth.
|
Single container, UI-managed, no YAML, no auth.
|
||||||
|
|
||||||
**Status:** pre-release. See [ROADMAP.md](ROADMAP.md).
|

|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Service board** — sections, drag-and-drop, multi-URL services, icon upload,
|
||||||
|
four board renderers (Bento, Cards, Index, Console), dark/light themes
|
||||||
|
- **Uptime monitors** — HTTP, TCP, ping, DNS, keyword, JSON-path, and push
|
||||||
|
checks on a schedule, with heartbeat history and uptime stats
|
||||||
|
- **Domain watch** — WHOIS/RDAP, DNS records, TLS certificate expiry,
|
||||||
|
registrar and provider detection
|
||||||
|
- **System monitoring** — lightweight `dash-agent` pushes CPU, memory, disk,
|
||||||
|
network, load, temperatures, and Docker container state
|
||||||
|
- **Incidents & status pages** — manual and automatic incidents, maintenance
|
||||||
|
windows, public `/status/:slug` pages, SVG badges
|
||||||
|
- **Alerts** — generic webhook notifications (Slack, Discord, ntfy compatible)
|
||||||
|
- **Observability** — Prometheus `/api/metrics`, JSON export, CSV bulk import,
|
||||||
|
importers for Homepage, Homarr, and Dashy configs
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker run -v ./data:/data -p 3000:3000 dash
|
docker run -d -v ./data:/data -p 3000:3000 ghcr.io/dvorinka/dash:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
or
|
or with compose:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
dash:
|
||||||
|
image: ghcr.io/dvorinka/dash:latest
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
restart: unless-stopped
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open http://localhost:3000. Everything is configured from the UI.
|
||||||
|
|
||||||
|
## System agent
|
||||||
|
|
||||||
|
Monitor a Linux host: create a system in the UI, copy the token, then:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker compose up
|
dash-agent -url http://dash:3000 -token <token>
|
||||||
```
|
```
|
||||||
|
|
||||||
Then open http://localhost:3000.
|
One static binary, no dependencies. See
|
||||||
|
[docs/installation.md](docs/installation.md) for systemd and Docker setups.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
|  |  |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
|
- [Installation](docs/installation.md)
|
||||||
|
- [Configuration](docs/configuration.md)
|
||||||
|
- [Widget development](docs/widgets.md)
|
||||||
|
- [Roadmap](ROADMAP.md) · [Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md)
|
||||||
|
|
||||||
## Develop
|
## Develop
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -175,7 +175,7 @@ Beszel-style server monitoring, push-based.
|
|||||||
- [x] Schema: `systems` (name, token, host, os/arch, last_seen, status, `latest` JSON snapshot), `system_stats` (system_id, ts, payload JSON — one column, no sub-field queries; 7d retention prune)
|
- [x] Schema: `systems` (name, token, host, os/arch, last_seen, status, `latest` JSON snapshot), `system_stats` (system_id, ts, payload JSON — one column, no sub-field queries; 7d retention prune)
|
||||||
- [x] Ingest: `POST /api/systems/ingest` with per-system bearer token; offline when silent > max(3×interval, 30s); `system.up`/`system.down` webhook events
|
- [x] Ingest: `POST /api/systems/ingest` with per-system bearer token; offline when silent > max(3×interval, 30s); `system.up`/`system.down` webhook events
|
||||||
- [x] `cmd/dash-agent`: Linux collector — `/proc` (cpu/mem/net/load/uptime), `/sys` hwmon temps, statfs disk, docker.sock container list; POST every `-interval` (default 10s); `-once` debug mode
|
- [x] `cmd/dash-agent`: Linux collector — `/proc` (cpu/mem/net/load/uptime), `/sys` hwmon temps, statfs disk, docker.sock container list; POST every `-interval` (default 10s); `-once` debug mode
|
||||||
- [ ] Agent packaging: systemd unit + Dockerfile + release binaries (Phase 4)
|
- [x] Agent packaging: systemd unit (`packaging/dash-agent.service`) + `infra/Dockerfile.agent` + goreleaser binaries (Phase 4)
|
||||||
- [x] Systems page (status cards, cpu/mem/disk bars, uptime) + detail (recharts: cpu%, mem%, net rx/tx, load; temps + containers tables)
|
- [x] Systems page (status cards, cpu/mem/disk bars, uptime) + detail (recharts: cpu%, mem%, net rx/tx, load; temps + containers tables)
|
||||||
- [x] Board widget `system` (cpu/mem/disk mini bars)
|
- [x] Board widget `system` (cpu/mem/disk mini bars)
|
||||||
- [x] Not ported: the beszel agent protocol (SSH/WS into PocketBase) — our agent is push-JSON. SMART/ZFS/GPU metrics + per-container cpu/mem deferred to demand.
|
- [x] Not ported: the beszel agent protocol (SSH/WS into PocketBase) — our agent is push-JSON. SMART/ZFS/GPU metrics + per-container cpu/mem deferred to demand.
|
||||||
@@ -192,11 +192,11 @@ Beszel-style server monitoring, push-based.
|
|||||||
|
|
||||||
## Phase 4 — Open-source launch → v1.0.0
|
## Phase 4 — Open-source launch → v1.0.0
|
||||||
|
|
||||||
- [ ] Docs: install, configuration, widget development guide (`docs/`)
|
- [x] Docs: install, configuration, widget development guide (`docs/`)
|
||||||
- [ ] Importers: Homepage `services.yml`, Homarr JSON, Dashy `conf.yml` → sections/items/urls
|
- [x] Importers: Homepage `services.yml`, Homarr JSON, Dashy `conf.yml` → sections/items/urls
|
||||||
- [ ] `README.md` hero: screenshots (4 themes), demo GIF, `docker run` one-liner
|
- [x] `README.md` hero: screenshots, `docker run` one-liner (demo GIF deferred — pngs suffice for v1.0.0)
|
||||||
- [ ] goreleaser → binaries; GHCR image on tag; semver + `CHANGELOG.md`
|
- [x] goreleaser → binaries; GHCR image on tag; semver + `CHANGELOG.md`
|
||||||
- [ ] `CONTRIBUTING.md`, issue/PR templates, code of conduct, security policy
|
- [x] `CONTRIBUTING.md`, issue/PR templates, code of conduct, security policy
|
||||||
- [ ] i18n scaffolding (en default)
|
- [ ] i18n scaffolding (en default)
|
||||||
- [ ] Public repo flip + announcement assets
|
- [ ] Public repo flip + announcement assets
|
||||||
|
|
||||||
|
|||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported versions
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
|---|---|
|
||||||
|
| latest release | yes |
|
||||||
|
| older releases | no |
|
||||||
|
|
||||||
|
Dash is pre-1.0; only the latest release receives fixes.
|
||||||
|
|
||||||
|
## Reporting a vulnerability
|
||||||
|
|
||||||
|
Use GitHub's private vulnerability reporting:
|
||||||
|
**Security → Advisories → Report a vulnerability** on this repository.
|
||||||
|
|
||||||
|
Do not open a public issue for security problems.
|
||||||
|
|
||||||
|
Please include:
|
||||||
|
|
||||||
|
- affected version/commit
|
||||||
|
- steps to reproduce or a proof of concept
|
||||||
|
- impact assessment
|
||||||
|
|
||||||
|
You will get an acknowledgement within a few days. If the report is accepted,
|
||||||
|
a fix ships in the next release and you are credited in the advisory (unless
|
||||||
|
you prefer otherwise).
|
||||||
|
|
||||||
|
## Scope notes
|
||||||
|
|
||||||
|
Dash is designed for trusted private networks — it ships **without
|
||||||
|
authentication** by design. Exposure to the public internet is a deployment
|
||||||
|
choice outside the project's control, but issues that remain exploitable on a
|
||||||
|
LAN (SSRF in checkers, injection, token leakage, unsafe schemes) are in scope.
|
||||||
@@ -25,6 +25,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// version is stamped by goreleaser via -X main.version.
|
||||||
|
var version = "dev"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
url := flag.String("url", os.Getenv("DASH_URL"), "dash server base URL")
|
url := flag.String("url", os.Getenv("DASH_URL"), "dash server base URL")
|
||||||
token := flag.String("token", os.Getenv("DASH_TOKEN"), "system token")
|
token := flag.String("token", os.Getenv("DASH_TOKEN"), "system token")
|
||||||
@@ -52,7 +55,7 @@ func main() {
|
|||||||
if *url == "" || *token == "" {
|
if *url == "" || *token == "" {
|
||||||
log.Fatal("dash-agent: -url and -token (or DASH_URL/DASH_TOKEN) required")
|
log.Fatal("dash-agent: -url and -token (or DASH_URL/DASH_TOKEN) required")
|
||||||
}
|
}
|
||||||
log.Printf("dash-agent: pushing to %s every %ds", *url, *interval)
|
log.Printf("dash-agent %s: pushing to %s every %ds", version, *url, *interval)
|
||||||
for {
|
for {
|
||||||
s := c.sample(*interval)
|
s := c.sample(*interval)
|
||||||
if err := push(*url, *token, s); err != nil {
|
if err := push(*url, *token, s); err != nil {
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ import (
|
|||||||
"github.com/tdvorak/dash/internal/web"
|
"github.com/tdvorak/dash/internal/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// version is stamped by goreleaser via -X main.version.
|
||||||
|
var version = "dev"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
dev := os.Getenv("DASH_DEV") == "1"
|
dev := os.Getenv("DASH_DEV") == "1"
|
||||||
|
|
||||||
@@ -55,7 +58,7 @@ func main() {
|
|||||||
srv := &http.Server{Addr: addr, Handler: router, ReadHeaderTimeout: 10 * time.Second}
|
srv := &http.Server{Addr: addr, Handler: router, ReadHeaderTimeout: 10 * time.Second}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
logger.Info("listening", zap.String("addr", addr), zap.String("data", dataDir))
|
logger.Info("listening", zap.String("addr", addr), zap.String("data", dataDir), zap.String("version", version))
|
||||||
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
logger.Fatal("serve", zap.Error(err))
|
logger.Fatal("serve", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# Minimal dash-agent image. A containerized agent only sees its own /proc —
|
||||||
|
# run with --pid=host and -v /proc:/host/proc for host metrics, or prefer the
|
||||||
|
# systemd unit in packaging/.
|
||||||
|
FROM golang:1.27-alpine AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY apps/backend/go.mod apps/backend/go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY apps/backend ./
|
||||||
|
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/dash-agent ./cmd/dash-agent
|
||||||
|
|
||||||
|
FROM gcr.io/distroless/static-debian12
|
||||||
|
COPY --from=build /out/dash-agent /dash-agent
|
||||||
|
ENTRYPOINT ["/dash-agent"]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Dash system monitoring agent
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
EnvironmentFile=/etc/dash/agent.env
|
||||||
|
ExecStart=/usr/local/bin/dash-agent
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
# Reads /proc, /sys, and (if present) the Docker socket.
|
||||||
|
ProtectHome=true
|
||||||
|
NoNewPrivileges=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Reference in new issue
Block a user