dashboard

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 10, 2026 License: MIT Imports: 23 Imported by: 0

README

go-health-dashboard

Go Reference

A real-time health dashboard for go-health, powered by Datastar SSE. Drops into your mux with one call and gives you a live status page with green/yellow/red badges, severity grouping, and sub-second updates.

What It Does

  • Browser visits /health: sees a rich dashboard with status banners, service tables, and badges — updating in real-time via Datastar SSE.
  • Kubelet hits /readyz: gets the JSON readiness response from go-health.
  • Content negotiation on /health: browsers get the HTML dashboard, Accept: application/json gets JSON. Kubelet probes (/readyz, /healthz, /startupz) are JSON-only.

Why a Separate Repo?

go-health is a single-dependency library (samber/do only). Pulling in templ, templ-components, go-datastar, and go-sse as transitive dependencies would destroy that value proposition. The dashboard lives in its own module so consumers who only want JSON health probes pay zero dependency cost.

Quick Start

package main

import (
    "context"
    "net/http"
    "time"

    health "github.com/larsartmann/go-health"
    dashboard "github.com/larsartmann/go-health-dashboard"
    "github.com/samber/do/v2"
)

func main() {
    ctx := context.Background()
    injector := do.New()

    // Register your services with samber/do...
    // do.ProvideNamed(injector, "database", NewDatabase)

    probe := health.New(injector,
        health.WithVersion("1.2.3"),
        health.WithCriticalServices("database"),
        health.WithRefreshInterval(2*time.Second),
    )
    _ = probe.Start(ctx)
    defer probe.Shutdown()

    dash := dashboard.New(probe,
        dashboard.WithTitle("My Service"),
    )
    _ = dash.Start(ctx)
    defer dash.Shutdown()

    mux := http.NewServeMux()
    dash.RegisterRoutes(mux)

    http.ListenAndServe(":8080", mux)
}

Open http://localhost:8080/health in a browser. Done.

Routes

Path Method Content-Type What It Does
/health GET text/html or application/json HTML dashboard (default) or JSON health response (Accept: application/json). JSON returns 503 when critical services fail
/health/sse GET text/event-stream SSE endpoint (Datastar patch protocol)
/favicon.svg GET image/svg+xml SVG favicon (embedded green-heart icon)
/healthz GET application/json Liveness probe (always 200, no dependency checks)
/readyz GET application/json Readiness probe (503 when critical services fail)
/startupz GET application/json Startup probe (latched once all critical services pass)

Options

dash := dashboard.New(probe,
    dashboard.WithTitle("My Service"),                        // Page title
    dashboard.WithPushInterval(5*time.Second),                 // SSE push interval
    dashboard.WithPushMode(dashboard.PushOnChange),            // Only push on change (default)
    // dashboard.WithPushMode(dashboard.PushAlways),            // Push on every tick
    dashboard.WithNonce("abc123"),                             // CSP nonce for script tags
    dashboard.WithNonceExtractor(httputil.NonceFromRequest),   // Per-request nonce (takes precedence; v0.2.0)
    dashboard.WithCSSPath("/static/app.css"),                  // Compiled CSS (replaces Tailwind CDN)
    dashboard.WithHeartbeatInterval(30*time.Second),           // SSE keepalive interval (default 15s)
    dashboard.WithMaxSSEConnections(100),                      // Max concurrent SSE clients (0 = unlimited)
    dashboard.WithRetryInterval(2*time.Second),                // SSE reconnection delay (browser retry field)
    dashboard.WithBasePath("/admin"),                          // Prefix all routes for sub-path mounting
    dashboard.WithRoutes(dashboard.Routes{
        Dashboard: "/status",
        SSE:       "/status/sse",
        Readiness: "/ready",
        // ...
    }),
)

How Real-Time Works

The dashboard uses Datastar for real-time DOM updates:

  1. The HTML page loads the Datastar SDK via a <script> tag
  2. A datastar.LiveRegion div wraps the health content with data-init="@get('/health/sse')"
  3. The Datastar SDK opens an SSE connection to /health/sse
  4. A background pusher goroutine reads probe.CachedResponse() at the configured interval
  5. On each update, the pusher renders the content as a Datastar element patch and broadcasts it
  6. The Datastar SDK applies the patch, replacing the inner HTML of the LiveRegion

By default, PushOnChange mode only sends updates when the health status actually changes — minimizing SSE traffic for NOC monitors that stay connected for long periods.

Build

Requires GOEXPERIMENT=jsonv2 (the go-sse dependency uses encoding/json/v2).

# Using Nix (recommended)
nix run .#build
nix run .#test
nix run .#lint

# Manual
GOEXPERIMENT=jsonv2 go build ./...
GOEXPERIMENT=jsonv2 go test ./...

Run the Example

GOEXPERIMENT=jsonv2 go run ./example
# Open http://localhost:8080/health

The example includes mock services: one always healthy, one flapping (alternates pass/fail every 15s), and one always failing. Watch the dashboard update live.

Status Mapping

go-health Status Badge Color Alert Banner
pass Green (success) "All Systems Operational"
warn Yellow (warning) "Degraded — Non-Critical Issues"
fail Red (error) "Unhealthy — Critical Failures"

Dependencies

Dependency Purpose
go-health Health-check Response, Probe, CachedResponse
templ-components LiveRegion, SDKScript, Alert, Table, Badge, Card
go-datastar Datastar SSE patch protocol (ElementsFromTempl)
go-sse SSE transport (Broadcaster, Stream)

Dark Mode

The dashboard respects the user's OS dark-mode preference and includes a toggle button for manual switching. The preference is persisted in localStorage.

License

MIT

Documentation

Overview

Package dashboard renders a real-time, browser-friendly health dashboard from a github.com/larsartmann/go-health Probe. It composes go-health (health checking), github.com/larsartmann/templ-components (UI rendering), and github.com/larsartmann/go-datastar (SSE patch protocol) into a single drop-in handler.

The dashboard lives at /health and uses Datastar SSE for real-time updates. It serves HTML by default but returns JSON when the client sends Accept: application/json. Kubernetes probe endpoints (/healthz, /readyz, /startupz) are wired separately as JSON-only.

Quick Start

probe := health.New(injector, health.WithVersion("1.2.3"))
_ = probe.Start(ctx)

dash := dashboard.New(probe,
    dashboard.WithTitle("My Service"),
)
_ = dash.Start(ctx)
defer dash.Shutdown()

mux := http.NewServeMux()
dash.RegisterRoutes(mux)
http.ListenAndServe(":8080", mux)

Browser visits http://localhost:8080/health and sees a live status dashboard that updates in real-time via SSE. Kubelet hits http://localhost:8080/readyz and gets the JSON readiness response.

templ: version: v0.3.1020

Index

Constants

View Source
const Version = "0.2.0"

Version is the current package version.

Variables

View Source
var ErrPusherNotActive = errors.New("dashboard: SSE pusher is not active")

ErrPusherNotActive is returned by HealthCheck when the SSE pusher has not been started or has been shut down.

Functions

func View

func View(data viewModel) templ.Component

View renders the full HTML dashboard page: Base shell (no HTMX), Datastar SDK in head, header, StatCards, and a LiveRegion that auto-connects to the SSE endpoint for real-time health updates.

Types

type Config

type Config struct {
	Title             string
	PushInterval      time.Duration
	PushMode          PushMode
	Routes            Routes
	Nonce             string
	NonceExtractor    func(*http.Request) string
	CSSPath           string
	DatastarSrc       string
	HeartbeatInterval time.Duration
	MaxSSEConnections int
	RetryInterval     time.Duration
}

Config holds construction-only configuration for a Dashboard. It is populated by Option functions and consumed by New.

type Dashboard

type Dashboard struct {
	// contains filtered or unexported fields
}

Dashboard renders a browser-friendly health dashboard from a go-health Probe using Datastar SSE for real-time updates.

The dashboard lives at a dedicated HTML route (default /health). It serves HTML by default but returns JSON when the client sends Accept: application/json. Kubernetes probe endpoints (/healthz, /readyz, /startupz) are wired separately as JSON-only.

Dashboard is safe for concurrent use by multiple goroutines.

func New

func New(probe *health.Probe, opts ...Option) *Dashboard

New creates a Dashboard wired to the given Probe. The Probe provides health data (via CachedResponse) and JSON handlers (via ReadinessHandler, LivenessHandler, StartupHandler).

Default configuration:

  • Title: "Health Dashboard"
  • PushInterval: probe's RefreshInterval, or 2s if probe is live
  • PushMode: PushOnChange
  • Routes: DefaultRoutes()

func Register added in v0.3.0

func Register(injector do.Injector, probe *health.Probe, opts ...Option) *Dashboard

Register creates a Dashboard wired to the given Probe and registers it in the injector so it participates in container lifecycle cascades.

After registration:

  • do.Shutdown(injector) calls Dashboard.Shutdown(), closing the SSE broadcaster and releasing the pusher.
  • do.HealthCheck[*Dashboard](injector) calls Dashboard.HealthCheck(), reporting whether the SSE pusher is active.

The returned *Dashboard is the same instance stored in the container. Call Start before serving HTTP traffic and RegisterRoutes to wire routes.

Example:

injector := do.New()
probe := health.New(injector, health.WithCriticalServices("db"))
dash := dashboard.Register(injector, probe, dashboard.WithTitle("API"))
dash.Start(ctx)
dash.RegisterRoutes(mux)
// On shutdown: do.Shutdown(injector) cascades to dash.Shutdown().

func (*Dashboard) FaviconHandler

func (d *Dashboard) FaviconHandler() http.HandlerFunc

FaviconHandler returns an http.HandlerFunc that serves the dashboard favicon as an SVG image. Register it at your favicon route.

func (*Dashboard) Handler

func (d *Dashboard) Handler() http.HandlerFunc

Handler returns an http.HandlerFunc that serves the health dashboard with content negotiation based on the Accept header:

  • Accept: application/json → returns the probe's cached health response as JSON. HTTP status is 503 when any check is failing, 200 otherwise.
  • Any other Accept value (or none) → renders the full HTML dashboard page.

Register it at your dashboard route (e.g. /health).

func (*Dashboard) HealthCheck added in v0.3.0

func (d *Dashboard) HealthCheck(_ context.Context) error

HealthCheck reports whether the dashboard's real-time update mechanism is healthy. Returns an error when the SSE pusher has not been started or has been shut down.

This method satisfies do.HealthcheckerWithContext, enabling the dashboard to participate in container-wide health checks when registered in a samber/do injector.

func (*Dashboard) RegisterRoutes

func (d *Dashboard) RegisterRoutes(mux *http.ServeMux)

RegisterRoutes registers all dashboard and probe endpoints on the given mux using the dashboard's configured routes (set via WithRoutes or WithBasePath, defaulting to DefaultRoutes).

This wires up:

  • Dashboard route (HTML page with Datastar SSE)
  • SSE route (Datastar patch stream)
  • Favicon route (SVG favicon)
  • Liveness, Readiness, Startup probe endpoints (JSON)

func (*Dashboard) SSEHandler

func (d *Dashboard) SSEHandler() http.HandlerFunc

SSEHandler returns an http.HandlerFunc that upgrades to an SSE connection and streams Datastar patches to the browser.

func (*Dashboard) Shutdown

func (d *Dashboard) Shutdown()

Shutdown stops the SSE pusher and closes all broadcaster connections. Safe to call multiple times.

func (*Dashboard) Start

func (d *Dashboard) Start(ctx context.Context) error

Start launches the SSE pusher goroutine that broadcasts health updates to connected clients. Call before serving HTTP traffic.

The ctx controls the lifetime of the pusher goroutine. Call Shutdown to stop it cleanly.

func (*Dashboard) SubscriberCount

func (d *Dashboard) SubscriberCount() int64

SubscriberCount returns the number of active SSE connections. Returns 0 when the pusher has not been started.

type Option

type Option func(*Config)

Option configures a Dashboard. Use the With* functions to create options.

func WithBasePath added in v0.3.0

func WithBasePath(prefix string) Option

WithBasePath prefixes all dashboard and probe routes with the given path. Use this when mounting the dashboard under a non-root path — for example WithBasePath("/admin") produces "/admin/health", "/admin/health/sse", etc.

The prefix is applied to whatever routes are currently configured. When combined with WithRoutes, call WithBasePath last so it prefixes the custom routes; calling WithRoutes after WithBasePath replaces the prefixed set.

func WithCSSPath

func WithCSSPath(path string) Option

WithCSSPath sets the URL path to a compiled CSS stylesheet. When set, the dashboard uses a <link> tag instead of the Tailwind Play CDN <script> tag. Use this in production to avoid the runtime overhead of the CDN.

func WithDatastarSrc added in v0.3.0

func WithDatastarSrc(src string) Option

WithDatastarSrc sets a self-hosted URL for the Datastar SDK script. When set, the dashboard renders <script src=...> pointing at this URL instead of the default jsdelivr CDN. Use this when the host application's Content-Security-Policy only allows 'self' scripts (e.g. the HTTP server serves a local copy of datastar.js).

func WithHeartbeatInterval

func WithHeartbeatInterval(d time.Duration) Option

WithHeartbeatInterval sets how often the SSE handler sends a comment-line keepalive to prevent proxy/load-balancer timeout. When zero (the default), the dashboard uses 15s.

func WithMaxSSEConnections

func WithMaxSSEConnections(n int) Option

WithMaxSSEConnections limits the number of concurrent SSE clients. When zero (the default), the number of connections is unlimited. Use this to prevent DoS via connection exhaustion.

func WithNonce

func WithNonce(nonce string) Option

WithNonce sets a fixed CSP nonce used in script and style tags. Required when the host application uses a strict Content-Security-Policy but cannot provide per-request nonces (e.g. because the dashboard is constructed once at startup). For stronger security, prefer WithNonceExtractor.

func WithNonceExtractor added in v0.2.0

func WithNonceExtractor(fn func(*http.Request) string) Option

WithNonceExtractor provides a function that extracts the CSP nonce from each incoming request. This enables per-request nonces (more secure than a fixed construction-time nonce) when the host application uses middleware such as httputil.Nonce that stores a unique nonce in the request context.

When set, the extractor takes precedence over WithNonce. If the extractor returns an empty string for a given request, the dashboard falls back to the fixed Nonce from WithNonce.

Example wiring with httputil:

dashboard.New(probe, dashboard.WithNonceExtractor(httputil.NonceFromRequest))

func WithPushInterval

func WithPushInterval(d time.Duration) Option

WithPushInterval sets the SSE push cadence. When zero (the default), the dashboard uses the probe's configured RefreshInterval, falling back to 2s when the probe is in live mode (interval == 0).

func WithPushMode

func WithPushMode(mode PushMode) Option

WithPushMode selects when the pusher sends updates: only on change (default) or on every tick.

func WithRetryInterval added in v0.3.0

func WithRetryInterval(d time.Duration) Option

WithRetryInterval sets the SSE retry field (in milliseconds) that tells the browser how long to wait before reconnecting after a disconnect. When zero (the default), the browser's built-in default (~3s) is used.

A shorter interval means faster recovery from transient network blips; a longer interval reduces server load when many clients reconnect at once. Negative values are treated as zero.

func WithRoutes

func WithRoutes(routes Routes) Option

WithRoutes overrides the default URL paths for dashboard and probe endpoints.

func WithTitle

func WithTitle(title string) Option

WithTitle sets the page title and heading displayed in the dashboard.

type PushMode

type PushMode string

PushMode controls when the SSE pusher sends updates to connected clients.

const (
	// PushOnChange broadcasts only when the overall status or any individual
	// check result changes (default). Minimises SSE traffic for NOC monitors
	// that stay connected for long periods.
	PushOnChange PushMode = "on-change"

	// PushAlways broadcasts on every tick, regardless of whether anything
	// changed. Use this when you want continuous confirmation that the
	// pusher is alive.
	PushAlways PushMode = "always"
)

type Routes

type Routes struct {
	Dashboard string // HTML dashboard page (default: /health)
	SSE       string // SSE push endpoint for real-time updates (default: /health/sse)
	Favicon   string // SVG favicon endpoint (default: /favicon.svg)
	Liveness  string // Kubernetes liveness probe — JSON (default: /healthz)
	Readiness string // Kubernetes readiness probe — JSON (default: /readyz)
	Startup   string // Kubernetes startup probe — JSON (default: /startupz)
}

Routes configures the URL paths for Dashboard.RegisterRoutes.

func DefaultRoutes

func DefaultRoutes() Routes

DefaultRoutes returns conventional paths for the dashboard and Kubernetes health probes. The HTML dashboard lives at /health; kubelet endpoints use the standard /healthz, /readyz, /startupz paths.

Directories

Path Synopsis
Command example demonstrates the go-health-dashboard with mock services.
Command example demonstrates the go-health-dashboard with mock services.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL