Dashboard
A native developer dashboard inspired by Laravel Telescope, Horizon, and Grafana, but built specifically for Breeze. Ships as a self-contained module, adds zero runtime overhead when disabled, and exposes one function for wiring.
package main
import (
"runtime"
"github.com/nelthaarion/breeze/v2"
"github.com/nelthaarion/breeze/v2/dashboard"
)
func main() {
router := breeze.NewRouter()
pool := breeze.NewWorkerPool(runtime.NumCPU())
app := breeze.New(router, pool)
coll := dashboard.Install(app, router, dashboard.DefaultConfig())
router.Use(coll.Middleware())
router.Handle(breeze.GET, "/api/users", listUsers)
app.Run(3000, true)
} Visit http://localhost:3000/dashboard (default credentials admin / admin).
14 pages
Monitor โ Overview (RPS, latency, memory, goroutines, four live charts at 1 Hz over WebSocket), Live Requests (filterable, slow requests >500ms highlighted), the Developer Timeline (a hierarchical per-request profiler: Request Received โ Middleware โ Auth โ Cache โ ORM Query โ Controller โ Serialization โ Response), and Performance (Go runtime metrics with four live charts).
Develop โ Routes Explorer (every route, with documentation joined from
the OpenAPI registry โ a route absent from that registry is also invisible
to every client generator and every agent reading the spec), the API
Explorer (curl/Go/JS/Python/C#/PHP snippet generation, request forms
derived from a route's RouteDoc), the Database Browser (optional inline
CRUD), and the ORM Query Monitor.
System โ Cache, Queue, Scheduler monitors, Logs (five tabs), Health checks, and Video (opt-in, appears once a video bus is attached).
The API Explorer's SSRF guard
The explorer's target is this service only: it sends its request to 127.0.0.1 on the port the application is listening on, a relative path
resolves against that, and an absolute URL is accepted only when it names
the same origin. Redirects are returned rather than followed. This is a hard
constraint, not a convenience โ a request the server makes comes from
inside the deployment, and an unrestricted URL field would let anyone
reaching this endpoint read the cloud metadata service or any internal
hostname the container can resolve. Basic Auth is not a sufficient gate,
because it is disabled entirely when Username or Password is empty.
Configuration
dashboard:
enabled: true
timeline: true
queries: true
metrics: true
requests: true
base_path: "/dashboard"
username: "admin"
password: "s3cret"
max_requests: 1000
max_queries: 500
max_logs: 1000
max_timeline_entries: 256
slow_query_ms: 100
slow_request_ms: 500
masked_headers:
- authorization
- cookie
- x-api-key | Field | Default | Description |
|---|---|---|
enabled | true | master switch โ false short-circuits before any work |
timeline | true | per-request timeline profiler |
queries | true | ORM/SQL query capture |
metrics | true | Go runtime metrics |
requests | true | live request tracking |
base_path | /dashboard | URL prefix for the SPA and API |
username / password | admin / admin | HTTP Basic Auth; both must be non-empty to enforce |
max_requests | 1000 | rolling window for live requests |
max_queries | 500 | rolling window for the query monitor |
max_logs | 1000 | per-tab log buffer size |
max_timeline_entries | 256 | cap on timeline steps per request |
slow_query_ms | 100 | slow-query highlight threshold |
slow_request_ms | 500 | slow-request highlight threshold |
masked_headers | sensible defaults | header names redacted in the inspector |
DBWriter (optional CRUD)
The Database Browser is read-only by default:
coll.SetDBInspector(store) // existing read-only interface
coll.SetDBWriter(store) // enables writes
cfg.AllowWrites = true // must also be explicitly enabled type DBWriter interface {
InsertRow(table string, values map[string]any) (map[string]any, error)
UpdateRow(table string, pk map[string]any, values map[string]any) error
DeleteRow(table string, pk map[string]any) error
} DBWriter is a separate interface from DBInspector so existing read-only
integrations are unaffected โ both Config.AllowWrites and a configured DBWriter are required; either one alone leaves the browser read-only.
Real-time updates
One WebSocket connection at /dashboard/ws for all live updates: a snapshot message (full overview metrics, every second) and an event message (a single live record, pushed the moment it is recorded). No
polling; the SPA reconnects automatically with a 2-second backoff.
Security
HTTP Basic Auth with constant-time password comparison (SHA-256 + subtle.ConstantTimeCompare). Authorization, Cookie, API-Key, Token, and
Password headers are masked in the request inspector. When enabled: false,
the middleware returns immediately after ctx.Next() โ no allocations, no
locks.
Pushing application data
coll.PushQuery("SELECT * FROM users WHERE id = $1", []any{42}, 850, 1, "models/user.go", 42, nil)
coll.PushLog("app", "user 42 logged in", "auth/handler.go:88")
coll.PushQueueJob(dashboard.QueueJob{ID: "job-1", Queue: "emails", State: "pending"})
coll.PushTask(dashboard.SchedulerTask{Name: "cleanup-sessions", Cron: "0 */5 * * * *"})
coll.RegisterHealthCheck("database", func() (string, string) {
if err := db.Ping(); err != nil {
return "red", err.Error()
}
return "green", "reachable"
})
coll.SetDBInspector(myORMAdapter)
defer coll.AttachVideo(events.Default)() Implementing a DBInspector
type DBInspector interface {
Tables() ([]TableInfo, error)
TableData(name string, page, pageSize int, search string) (TableData, error)
} Typically introspects information_schema.columns (Postgres/MySQL) or sqlite_master + PRAGMA table_info (SQLite).
Design decisions
- Zero-overhead fast path โ when disabled, the middleware returns after
ctx.Next()with no allocations, locks, or map writes. - Ring buffers, not channels โ bounds memory and avoids backpressure on the hot path.
- Single WebSocket โ the hub drops messages for slow clients rather than blocking the request path.
- Self-contained SPA โ one HTML response with inlined CSS/JS, no CDN, no asset pipeline.
- Canvas charts โ no Chart.js, no D3, no npm install.
Run the bundled example:
go run ./cmd/dashboard-example