dashboard

package
v0.2.5 Latest Latest
Warning

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

Go to latest
Published: Jun 19, 2026 License: AGPL-3.0 Imports: 20 Imported by: 0

Documentation

Overview

Package dashboard implements the HTTP dashboard server, probe loop, and pulse-sample ring for the Pilot Protocol registry.

Thread safety: all exported Handler methods are safe for concurrent use.

Index

Constants

View Source
const ProbeRetention = 30 * 24 * time.Hour

ProbeRetention is the maximum age of probe downtime intervals kept in memory and persisted to snapshots.

Variables

This section is empty.

Functions

func WriteBucketedStats

func WriteBucketedStats(ring []StatsSample, idxPtr *int, sample StatsSample, bucketSecs int64)

WriteBucketedStats writes a sample into a fixed ring buffer with time-bucket dedup: if the most recently written slot holds a sample in the same bucket, it is overwritten in place instead of advancing the index. This prevents duplicate entries (e.g. two samples on the same UTC day after a restart).

Types

type AuditEntrySnapshot added in v0.2.5

type AuditEntrySnapshot struct {
	Timestamp string `json:"timestamp"`
	Action    string `json:"action"`
	NetworkID uint16 `json:"network_id,omitempty"`
	NodeID    uint32 `json:"node_id,omitempty"`
	Details   string `json:"details,omitempty"`
	Hash      string `json:"hash,omitempty"`
}

AuditEntrySnapshot is the JSON-friendly shape returned by /api/admin/audit/recent. Lifted out of the server package so the dashboard doesn't import it directly. Fields mirror audit.Entry so the wire format is stable across releases.

type BackupEntry added in v0.2.5

type BackupEntry struct {
	Name      string `json:"name"`
	SizeBytes int64  `json:"size_bytes"`
	ModUnix   int64  `json:"mod_unix"`
}

BackupEntry is the wire shape returned per file by /api/admin/backups.

type BeaconStatsProvider

type BeaconStatsProvider interface {
	RelayForwarded() uint64
	RelayDropped() uint64
	RelayNotFound() uint64
}

BeaconStatsProvider exposes relay counters the dashboard surfaces in /api/stats. Implemented by pkg/beacon.Server.

type BreakerListEntry added in v0.2.5

type BreakerListEntry struct {
	Name         string     `json:"name"`
	State        string     `json:"state"`
	Reason       string     `json:"reason,omitempty"`
	UpdatedAt    *time.Time `json:"updated_at,omitempty"`
	AllowedTotal int64      `json:"allowed_total"`
	DeniedTotal  int64      `json:"denied_total"`
	LastDeniedAt *time.Time `json:"last_denied_at,omitempty"`
}

BreakerListEntry is one row in the /api/breakers response. Marshalled as-is to JSON, so field tags pin the public wire format. Counters are monotonic since process start; LastDeniedAt is nil when DeniedTotal is 0.

type Callbacks

type Callbacks struct {
	// GetDashboardToken returns the current dashboard token.
	GetDashboardToken func() string
	// GetAdminToken returns the current admin token.
	GetAdminToken func() string
	// BuildStatsPayload assembles the core /api/stats JSON payload (without
	// probe state and maintenance banner, which are owned by Handler).
	// The returned map is JSON-safe; callers must not mutate it.
	BuildStatsPayload func(authenticated bool) map[string]interface{}
	// GetNodes returns a snapshot of registered nodes for /api/nodes.
	GetNodes func() []NodeSnapshot
	// RequestCount returns the current total request count.
	RequestCount func() int64
	// StartTime returns when the server started.
	StartTime func() time.Time
	// NodeCount returns the number of registered nodes currently in the
	// in-memory map. Decays toward OnlineCount as the reaper deletes
	// stale entries (see server_lifecycle.reapStaleNodes).
	NodeCount func() int
	// OnlineCount returns the number of nodes online at or after threshold.
	OnlineCount func(threshold time.Time) int
	// TotalEverRegistered returns the cumulative count of node IDs ever
	// allocated by this registry (monotonic across reaps, persisted
	// across restarts via the snapshot's next_node counter). Surfaced on
	// /api/public-stats as total_nodes so it stays distinct from
	// active_nodes (which post-reap is identical to NodeCount).
	TotalEverRegistered func() int64
	// BuildInfo returns the build-time identity of the running binary
	// (version, git_commit, build_time, binary_sha256, go_version) so
	// /api/public-stats can publish a verifiable record of what code is
	// actually running. Returns nil when build info wasn't registered;
	// the handler then omits the build_info field entirely.
	BuildInfo func() map[string]string
	// StaleThreshold returns the configured stale-node threshold.
	StaleThreshold func() time.Duration
	// TriggerSnapshot triggers a snapshot save.
	TriggerSnapshot func() error
	// UpdateGauges updates Prometheus gauges before /metrics scrape.
	UpdateGauges func()
	// WriteMetrics writes Prometheus metrics to w.
	WriteMetrics func(w io.Writer)
	// ListenerAddr returns the registry TCP listener address for probeRegistry.
	// Returns "" when no listener is bound.
	ListenerAddr func() string
	// BeaconAddr returns the beacon UDP address for probeBeacon. "" = disabled.
	BeaconAddr func() string
	// ReadyCh returns the channel closed when the server is ready.
	ReadyCh func() <-chan struct{}
	// Done returns the channel closed on server shutdown.
	Done func() <-chan struct{}
	// Save triggers a debounced snapshot after probe state changes.
	Save func()
	// BreakerAllow consults the Server's breaker manager for the named
	// switch. Returns (allow, reason). Allowed=true means the gated path
	// should proceed; allowed=false means it should return 503 to the
	// caller with reason surfaced. Optional — when nil, every name is
	// implicitly allowed (default-Closed semantics, same as a missing
	// breakers.json).
	BreakerAllow func(name string) (allowed bool, reason string)
	// BreakerList returns the live snapshot for /api/breakers — one
	// entry per known name with state, reason, counters, last-denied
	// timestamp. Required for the read endpoint; nil disables it.
	BreakerList func() []BreakerListEntry
	// BreakerSet upserts one breaker by name, returning an error when
	// the state value is malformed. Reason is operator free-text. Used
	// by PUT /api/breakers/<name>. Writes to breakers.json on disk so
	// the change survives restart; the file watcher's next poll is a
	// no-op due to the matching mtime.
	BreakerSet func(name, state, reason string) error
	// BreakerDelete removes one breaker by name. Counters are
	// preserved so post-incident review still shows traffic that
	// flowed through the gate. Persists the change to breakers.json.
	BreakerDelete func(name string) error
	// HealthSnapshot returns a flat map of operator-facing health
	// signals: snapshot save telemetry, WAL size, snapshot.write
	// breaker state, replica subscriber count. Backs /api/health.
	// Optional — when nil, /api/health 503s.
	HealthSnapshot func() map[string]any
	// AuditRecent returns the most recent N audit-log entries from the
	// in-memory ring buffer (caller-supplied bound, server-clamped to a
	// safe maximum). Optional — when nil, /api/admin/audit/recent
	// returns an empty list.
	AuditRecent func(n int) []AuditEntrySnapshot

	// WhitelistGet returns the on-disk rate-limit whitelist as raw JSON
	// bytes (the exact contents of /var/lib/pilot/rate-limit-whitelist.json).
	// Optional — when nil, GET /api/admin/whitelist returns 404. When the
	// file does not exist on disk, the callback should return ([]byte("[]"),
	// nil) so the editor sees an empty list rather than an error.
	WhitelistGet func() ([]byte, error)
	// WhitelistSet atomically replaces the whitelist file with the given
	// JSON bytes. The on-disk watcher (cmd/rendezvous/whitelist_watcher.go)
	// picks the change up on its next 2 s poll, so the limiter is updated
	// without a restart. Optional — when nil, PUT /api/admin/whitelist
	// returns 405.
	WhitelistSet func(data []byte) error

	// GetLogLevel returns the current slog level ("debug", "info", "warn",
	// "error"). Optional — when nil, GET /api/admin/runtime/log-level
	// returns the static string "unknown".
	GetLogLevel func() string
	// SetLogLevel re-installs the default slog handler at the requested
	// level. The level string must be one of debug/info/warn/error
	// (case-insensitive); anything else returns an error and the previous
	// handler is left in place. Optional — when nil, PUT
	// /api/admin/runtime/log-level returns 405.
	SetLogLevel func(level string) error

	// ReplicationStatus returns a snapshot of the registry's replication
	// state — current term, standby flag, replica subscriber count, and
	// last replica push timestamp. Optional — when nil,
	// GET /api/admin/replication-status returns 404.
	ReplicationStatus func() ReplicationSnapshot
	// BackupsList returns metadata for snapshot backup files. Used to power
	// the dashboard's backup browser. Optional — when nil,
	// GET /api/admin/backups returns 404.
	BackupsList func() ([]BackupEntry, error)

	// MembersList returns a snapshot of the membership for the given
	// network — one entry per node with role, hostname (if registered),
	// and last-seen unix timestamp. Backs the GET
	// /api/admin/networks/{id}/members endpoint, which the dashboard's
	// operator-facing member browser consumes. Optional — when nil, the
	// endpoint returns 404 so a registry running without membership
	// admin wiring still serves the rest of the dashboard cleanly.
	// Errors are returned to the client as 500; protocol.ErrNetworkNotFound
	// surfaces as 404.
	MembersList func(netID uint16) ([]MemberSnapshot, error)
	// NetworksList returns a snapshot of every known network with both
	// the numeric ID and the human-readable name, plus per-role counts
	// and the enterprise / policy-set flags. Powers the dashboard's
	// network picker — without this the per-metric rollup (keyed by
	// label = name) doesn't know which numeric ID the management
	// endpoints (kick / set-role) need.
	// Backs GET /api/admin/networks. Optional; nil → endpoint 404.
	NetworksList func() []NetworkSnapshot
	// MemberKick removes the given node from the network's member list
	// and role map. The reason string is the operator's audit note (why
	// the action was taken) and is required — endpoints reject empty
	// reasons with 400 so post-incident review always has a paper trail.
	// Backs DELETE /api/admin/networks/{id}/members/{nodeID}?reason=...
	// Optional — when nil, the endpoint returns 404. Errors surface as
	// 400 (cannot kick owner, unknown member) or 500 (generic failures);
	// protocol.ErrNetworkNotFound surfaces as 404.
	MemberKick func(netID uint16, nodeID uint32, reason string) error
	// MemberRole updates the per-network role for the given node. Role
	// must be "admin" or "member" (case-insensitive); anything else
	// returns 400. Cannot demote the network owner — returns an error
	// the handler surfaces as 400. Reason is the operator's audit note.
	// Backs PUT /api/admin/networks/{id}/members/{nodeID}/role.
	// Optional — when nil, the endpoint returns 404. The role-validation
	// step is split between the dashboard handler (string accept set)
	// and the server callback (owner-demotion check) so the wire format
	// is policed at the edge.
	MemberRole func(netID uint16, nodeID uint32, role, reason string) error
}

Callbacks bundles the side-effect functions Handler calls on the parent server. All functions must be safe for concurrent use.

type DashboardStats

type DashboardStats struct {
	TotalNodes        int                    `json:"total_nodes"`
	ActiveNodes       int                    `json:"active_nodes"`
	TotalTrustLinks   int                    `json:"-"`
	TotalRequests     int64                  `json:"total_requests"`
	RelayForwarded    uint64                 `json:"relay_forwarded,omitempty"`
	RelayDropped      uint64                 `json:"relay_dropped,omitempty"`
	RelayNotFound     uint64                 `json:"relay_not_found,omitempty"`
	ReqPerDay         int64                  `json:"req_per_day"`
	UptimeSecs        int64                  `json:"uptime_secs"`
	Versions          map[string]int         `json:"versions,omitempty"`
	Networks          []NetworkStats         `json:"networks,omitempty"` // only populated with dashboard token
	Hourly            []StatsSample          `json:"hourly,omitempty"`
	Daily             []StatsSample          `json:"daily,omitempty"`
	RestartEvents     []int64                `json:"restart_events,omitempty"`
	DowntimeIntervals [][2]int64             `json:"downtime_intervals,omitempty"`
	Probes            map[string]*ProbeState `json:"probes,omitempty"`
	ReleaseBanner     *ReleaseBanner         `json:"release_banner,omitempty"`
}

DashboardStats is the public-safe data returned by the dashboard API.

type Handler

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

Handler owns the dashboard HTTP mux, probe state, pulse ring, and the maintenance banner. It is wired into the parent server via Callbacks.

func NewHandler

func NewHandler(cb Callbacks) *Handler

NewHandler creates a ready-to-use dashboard Handler backed by cb.

func (*Handler) GetProbeStates

func (h *Handler) GetProbeStates() map[string]*ProbeState

GetProbeStates returns a deep-copy snapshot of all probe states.

func (*Handler) GetPulseSamples

func (h *Handler) GetPulseSamples() []PulseSample

GetPulseSamples returns the ordered pulse samples from the ring buffer.

func (*Handler) MaintenanceBanner

func (h *Handler) MaintenanceBanner() string

MaintenanceBanner returns the currently-set maintenance notice (or "").

func (*Handler) ProbeLoop

func (h *Handler) ProbeLoop()

ProbeLoop runs the 4 component probes at a steady cadence. Must be called in a separate goroutine (typically by the parent server).

func (*Handler) PulseLoop

func (h *Handler) PulseLoop()

PulseLoop records one request-count sample per second into the ring. Must be called in a separate goroutine.

func (*Handler) RecentThroughput added in v0.2.5

func (h *Handler) RecentThroughput(window int) float64

RecentThroughput returns smoothed requests-per-second over the last `window` pulse samples (1 sample/sec). Returns 0 when there aren't yet two samples to differentiate or the span is zero.

func (*Handler) Serve

func (h *Handler) Serve(addr string) error

Serve starts an HTTP server serving the dashboard UI and stats API.

func (*Handler) SetBackupsList added in v0.2.5

func (h *Handler) SetBackupsList(fn func() ([]BackupEntry, error))

SetBackupsList wires the GET /api/admin/backups endpoint. The closure owns the directory path; the dashboard package only enumerates what it returns.

func (*Handler) SetBannerPath

func (h *Handler) SetBannerPath(path string)

SetBannerPath wires a disk-persistence path for the maintenance banner. If the file already exists, its contents become the in-memory banner.

func (*Handler) SetHTTPProbeAddr

func (h *Handler) SetHTTPProbeAddr(addr string)

SetHTTPProbeAddr records the dashboard HTTP listen address so the probe loop knows where to dial for the dashboard + metrics probes.

func (*Handler) SetLogLevelCallbacks added in v0.2.5

func (h *Handler) SetLogLevelCallbacks(get func() string, set func(string) error)

SetLogLevelCallbacks wires the GET/PUT /api/admin/runtime/log-level endpoints after construction. The level lives in cmd/rendezvous/main.go (alongside logging.Setup) so cannot be set inside NewWithStore.

func (*Handler) SetMaintenanceBanner

func (h *Handler) SetMaintenanceBanner(msg string)

SetMaintenanceBanner sets a free-form notice rendered on the dashboard. Empty string clears it. If a bannerPath has been configured, the new value is atomically written to disk.

func (*Handler) SetProbeStates

func (h *Handler) SetProbeStates(states map[string]*ProbeState)

SetProbeStates replaces the probe state map (used during snapshot restore).

func (*Handler) SetReplicationStatus added in v0.2.5

func (h *Handler) SetReplicationStatus(fn func() ReplicationSnapshot)

SetReplicationStatus wires the GET /api/admin/replication-status endpoint.

func (*Handler) SetWhitelistCallbacks added in v0.2.5

func (h *Handler) SetWhitelistCallbacks(get func() ([]byte, error), set func([]byte) error)

SetWhitelistCallbacks wires the GET/PUT /api/admin/whitelist endpoints after construction. The Handler is created early in NewWithStore (before main.go knows the whitelist path), so the cmd-level package installs the callbacks once both pieces exist. Either argument may be nil to leave that direction disabled.

func (*Handler) StatsCollectorLoop

func (h *Handler) StatsCollectorLoop(
	readyCh <-chan struct{},
	done <-chan struct{},
	sampleFn func() StatsSampleResult,
	recordFn func(StatsSampleResult, bool),
)

StatsCollectorLoop runs in the background and samples stats for history charts. Hourly samples are taken every hour; daily samples every 24 hours.

readyCh is closed when the parent server is ready to serve (load complete). done is closed on server shutdown. sampleFn returns a fresh StatsSampleResult under whatever locking the caller requires. recordFn persists the sample into the parent server's ring buffers; it must acquire any required server locks internally.

type MemberSnapshot added in v0.2.5

type MemberSnapshot struct {
	NodeID       uint32 `json:"node_id"`
	Role         string `json:"role"`
	Hostname     string `json:"hostname,omitempty"`
	LastSeenUnix int64  `json:"last_seen_unix,omitempty"`
}

MemberSnapshot is one row in the GET /api/admin/networks/{id}/members response. Hostname and LastSeenUnix are omitted when zero so the wire payload stays compact for members that have never registered a hostname or whose last_seen is unknown.

type NetHistoryRing

type NetHistoryRing struct {
	Samples []NetworkSampleEntry // length == Size; heap-allocated per ring
	Size    int                  // ring capacity (24 for hourly, 30 for daily)
	Idx     int                  // next write index
}

NetHistoryRing is a fixed-size ring buffer for per-network history samples.

func NewNetHistoryRing

func NewNetHistoryRing(size int) *NetHistoryRing

NewNetHistoryRing allocates a new ring of the given capacity.

func (*NetHistoryRing) Read

func (r *NetHistoryRing) Read() []NetworkSampleEntry

Read returns all non-zero entries in chronological order.

func (*NetHistoryRing) Write

func (r *NetHistoryRing) Write(e NetworkSampleEntry)

Write appends a new entry, overwriting the oldest on wraparound.

func (*NetHistoryRing) WriteBucketed

func (r *NetHistoryRing) WriteBucketed(e NetworkSampleEntry, bucketSecs int64)

WriteBucketed overwrites the most recent entry if its timestamp falls in the same time bucket (e.g. 86400s for per-day dedup); otherwise behaves like Write. Guarantees at most one entry per bucket across restarts.

type NetworkSampleEntry

type NetworkSampleEntry struct {
	Timestamp int64  `json:"ts"`
	ID        uint16 `json:"id"`
	Name      string `json:"name"`
	Members   int    `json:"members"`
	Online    int    `json:"online"`
	Requests  int64  `json:"requests"`
}

NetworkSampleEntry holds per-network stats within a time-series sample.

type NetworkSnapshot added in v0.2.5

type NetworkSnapshot struct {
	ID           uint16 `json:"id"`
	Name         string `json:"name"`
	MembersCount int    `json:"members_count"`
	AdminsCount  int    `json:"admins_count"`
	OwnersCount  int    `json:"owners_count"`
	Enterprise   bool   `json:"enterprise"`
	PolicySet    bool   `json:"policy_set"`
}

NetworkSnapshot is one row in the GET /api/admin/networks response. Powers the dashboard's network picker: the labeled /metrics rollup is keyed by network NAME but the management endpoints (kick / set role) take numeric IDs, so this endpoint surfaces both alongside the per-network role counts and the enterprise / policy-set flags.

type NetworkStats

type NetworkStats struct {
	ID         uint16               `json:"id"`
	Name       string               `json:"name"`
	Members    int                  `json:"members"`
	Online     int                  `json:"online"`
	Requests   int64                `json:"requests"`
	TrustLinks int                  `json:"-"`
	Hourly     []NetworkSampleEntry `json:"hourly,omitempty"`
	Daily      []NetworkSampleEntry `json:"daily,omitempty"`
}

NetworkStats holds per-network statistics for the authenticated dashboard view.

type NodeSnapshot

type NodeSnapshot struct {
	ID       uint32
	Hostname string
	LastSeen time.Time
}

NodeSnapshot is a minimal node record for the /api/nodes endpoint.

type ProbeState

type ProbeState struct {
	LastSuccess       int64      `json:"last_success,omitempty"`       // millis
	DowntimeIntervals [][2]int64 `json:"downtime_intervals,omitempty"` // pruned to 30d
	CurrentDownStart  int64      `json:"current_down_start,omitempty"` // 0 when up
}

ProbeState holds the health history for a single named probe.

type PulseSample

type PulseSample struct {
	Ts    int64 `json:"ts"`
	Total int64 `json:"total"`
}

PulseSample is one entry in the server-side 1/sec request-count ring.

type ReleaseBanner

type ReleaseBanner struct {
	Version     string `json:"version"`
	PublishedAt int64  `json:"published_at"`
}

ReleaseBanner is the public-safe snapshot of the latest GitHub release. Surfaced on the dashboard so users see why peers may be briefly unreachable while the fleet upgrades.

type ReplicationSnapshot added in v0.2.5

type ReplicationSnapshot struct {
	Term            uint64 `json:"term"`
	IsStandby       bool   `json:"is_standby"`
	Subscribers     int    `json:"subscribers"`
	LastPushUnix    int64  `json:"last_push_unix,omitempty"`
	LastPushAgeSecs int64  `json:"last_push_age_secs,omitempty"`
}

ReplicationSnapshot is the wire shape returned by /api/admin/replication-status.

type StatsSample

type StatsSample struct {
	Timestamp     int64 `json:"ts"`
	TotalNodes    int   `json:"total_nodes"`
	OnlineNodes   int   `json:"online_nodes"`
	TrustLinks    int   `json:"trust_links,omitempty"`
	TotalRequests int64 `json:"total_requests"`
}

StatsSample is a single time-series data point for dashboard history charts.

type StatsSampleResult

type StatsSampleResult struct {
	Global   StatsSample
	Networks map[uint16]NetworkSampleEntry
}

StatsSampleResult holds both global and per-network samples from a single snapshot. It is produced by the server's sampleStats callback and consumed by recordSample.

Jump to

Keyboard shortcuts

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