Features
Dashboard capabilities including micro-frontend shell, contributor system, authentication, bridge, SSE, search, and security
ForgeUI Dashboard Shell
The dashboard is a ForgeUI-powered micro-frontend shell served from the Forge HTTP router. It uses a layout hierarchy for page structure:
| Layout | Purpose |
|---|---|
root | HTML shell with head, scripts, sidebar, topbar. Detects HTMX partial requests and skips the outer shell. |
dashboard | Default layout. Inherits root, adds breadcrumbs, page title, and content area. |
base | Minimal layout with sidebar and topbar but no breadcrumbs. |
full | Full-width layout without sidebar (e.g., for standalone pages). |
settings | Settings layout with sidebar navigation for settings pages. |
auth | Centered card layout for login/register pages. No sidebar or topbar. |
Navigation between pages uses HTMX partial requests -- clicking a sidebar link fetches only the page content (not the full HTML shell), swaps the #content div, and updates the browser URL. This gives a SPA-like experience without client-side JavaScript routing.
Alpine.js manages client-side state (sidebar collapse, theme toggle, SSE connection status, notification count) and is available in all layouts.
Contributor System
The dashboard uses a contributor-based architecture. Extensions register pages, widgets, and settings via a manifest, and the shell merges everything into a unified UI.
Local Contributors
Local contributors run in-process and render gomponents.Node values directly:
type LocalContributor interface {
Manifest() *contributor.Manifest
RenderPage(ctx context.Context, route string, params Params) (g.Node, error)
RenderWidget(ctx context.Context, widgetID string) (g.Node, error)
RenderSettings(ctx context.Context, settingID string) (g.Node, error)
}Register a local contributor:
dashExt.RegisterContributor(&MyContributor{})Remote Contributors
Remote contributors are separate HTTP services that expose fragment endpoints:
GET <baseURL>/_forge/dashboard/manifest-- JSON manifestGET <baseURL>/_forge/dashboard/pages/*-- HTML page fragmentsGET <baseURL>/_forge/dashboard/widgets/:id-- HTML widget fragments
The dashboard proxies requests via the fragment proxy, which includes an LRU cache with TTL and stale-while-revalidate fallback.
Manifest
Each contributor provides a manifest describing its capabilities:
&contributor.Manifest{
Name: "users",
DisplayName: "User Management",
Icon: "users",
Version: "1.0.0",
Nav: []contributor.NavItem{
{Label: "Users", Path: "/", Icon: "users", Group: "Identity", Priority: 0},
{Label: "Roles", Path: "/roles", Icon: "shield", Group: "Identity", Priority: 1},
},
Widgets: []contributor.WidgetDescriptor{
{ID: "active-users", Title: "Active Users", Size: "sm", RefreshSec: 60},
},
Settings: []contributor.SettingsDescriptor{
{ID: "user-defaults", Title: "User Defaults", Description: "Default settings for new users"},
},
}NavItem entries become sidebar links grouped by Group and sorted by Priority. The sidebar is constructed automatically from all contributor manifests.
See the Contributors guide for full details.
Built-in Pages
Four pages are provided by the built-in core contributor:
| Page | Path | Description |
|---|---|---|
| Overview | / | Application health, extension count, uptime, key metrics |
| Health | /health | Per-extension health checks with status indicators |
| Metrics | /metrics | Counters, gauges, and histograms from the Forge metrics system |
| Services | /services | Registered services with status and configuration |
Widgets
Contributors register widgets via WidgetDescriptor. Widgets are rendered as HTMX fragments that auto-refresh at the interval specified by RefreshSec:
Widgets: []contributor.WidgetDescriptor{
{ID: "active-users", Title: "Active Users", Size: "sm", RefreshSec: 60, Group: "Identity"},
}Widget sizes: sm, md, lg, xl. The overview page displays widgets from all contributors.
Real-Time Updates
The dashboard uses Server-Sent Events (SSE) for live updates. When EnableRealtime is true, the SSE broker broadcasts events to all connected browsers:
broker := ext.SSEBroker()
// Broadcast a custom event
broker.BroadcastJSON("deployment", map[string]any{
"service": "api",
"version": "2.1.0",
})The SSE endpoint is at {basePath}/sse. The broker sends keep-alive pings at the configured interval (default 15s).
Event types include metric updates, health state changes, and contributor health transitions (e.g., when a remote contributor becomes unreachable).
The dashboard uses SSE (Server-Sent Events), not WebSocket. SSE is simpler, works over standard HTTP, and is sufficient for server-to-client push.
Go-JS Bridge
The Go-JS bridge lets the dashboard UI call Go functions directly from the browser via Alpine.js magic helpers:
// From Alpine.js in the browser
const overview = await $go('dashboard.getOverview')
const detail = await $go('dashboard.getServiceDetail', { name: 'api' })8 built-in functions are registered automatically. Extensions can register custom functions:
dashExt.RegisterBridgeFunction("myapp.getData", handler,
bridge.WithDescription("Get application data"),
bridge.WithFunctionCache(10 * time.Second),
)Bridge endpoints: POST {basePath}/bridge/call for single calls, GET {basePath}/bridge/stream/ for streaming responses.
See the Bridge guide for full details.
Federated Search
When EnableSearch is true, the dashboard provides a cross-contributor search API. Contributors implementing the SearchableContributor interface are searched in parallel:
type SearchableContributor interface {
DashboardContributor
Search(ctx context.Context, query string, limit int) ([]SearchResult, error)
}The search API is at GET {basePath}/api/search?q=query. Results include the contributor name, matched item, and a link to the relevant page.
Settings Aggregation
When EnableSettings is true, contributor settings are merged into a unified settings page at {basePath}/settings. Each contributor's SettingsDescriptor entries appear as form sections that can be edited and submitted.
Data Export
Export dashboard data in three formats when EnableExport is true:
| Endpoint | Format | Description |
|---|---|---|
GET /export/json | JSON | Full snapshot of overview, health, metrics, and services |
GET /export/csv | CSV | Metrics as rows with name, type, value, and timestamp |
GET /export/prometheus | Prometheus | Text exposition format for external scraping |
Configurable export formats:
dashboard.WithExportFormats([]string{"json", "csv", "prometheus"})Service Discovery
When EnableDiscovery is true and a discovery service is configured, the dashboard automatically discovers remote contributors tagged with forge-dashboard-contributor:
dashExt.SetDiscoveryService(discoveryExt.Service())Discovered services have their manifests fetched and are registered as remote contributors. The discovery integration polls at the configured interval (default 60s).
Authentication & Authorization
The dashboard supports optional authentication with three page access levels:
| Level | Behavior |
|---|---|
public | Always accessible, no login required |
protected | Requires authentication; unauthenticated users are redirected to the login page |
partial | Always accessible but can render differently based on auth state |
Enabling Auth
dashExt := dashboard.NewExtension(
dashboard.WithEnableAuth(true),
dashboard.WithDefaultAccess("protected"),
dashboard.WithLoginPath("/auth/login"),
dashboard.WithLogoutPath("/auth/logout"),
)AuthChecker
The AuthChecker interface is the primary abstraction for validating requests. Implement it to integrate with your auth system (JWT, sessions, OAuth, etc.):
import dashauth "github.com/xraph/forge/extensions/dashboard/auth"
type MyChecker struct{ /* your auth service */ }
func (c *MyChecker) CheckAuth(ctx context.Context, r *http.Request) (*dashauth.UserInfo, error) {
// Return nil (not an error) when unauthenticated.
// Return *UserInfo when authenticated.
// Return an error only for infrastructure failures.
}Wire it after registration:
typedDash := dashExt.(*dashboard.Extension)
typedDash.SetAuthChecker(&MyChecker{})UserInfo
UserInfo represents an authenticated user. It is decoupled from any auth provider:
type UserInfo struct {
Subject string // unique user ID
DisplayName string // display name
Email string // email address
AvatarURL string // avatar image URL
Roles []string // user roles
Scopes []string // OAuth2 scopes
ProviderName string // which auth provider
Claims map[string]any // additional claims
Metadata map[string]any // provider metadata
}Helper methods: Authenticated(), HasRole(role), HasScope(scope), HasAnyRole(roles...), Initials().
Auth Page Provider
Auth extensions provide login/register/logout pages by implementing AuthPageProvider:
type AuthPageProvider interface {
AuthPages() []AuthPageDescriptor
RenderAuthPage(ctx *router.PageContext, pageType AuthPageType) (g.Node, error)
HandleAuthAction(ctx *router.PageContext, pageType AuthPageType) (redirectURL string, errNode g.Node, err error)
}Supported page types: login, logout, register, forgot-password, reset-password, callback, profile.
Auth pages use the auth layout (centered card, no sidebar) and are always publicly accessible so unauthenticated users can reach the login form.
Per-Page Access Levels
Contributors can set access levels on individual NavItem entries:
Nav: []contributor.NavItem{
{Label: "Public Stats", Path: "/stats", Access: "public"},
{Label: "Admin Panel", Path: "/admin", Access: "protected"},
{Label: "Dashboard", Path: "/", Access: "partial"},
}Empty Access falls back to the dashboard DefaultAccess config.
Reading User Info
Page handlers and contributors can read the authenticated user from the request context:
user := dashauth.UserFromContext(ctx)
if user.Authenticated() {
// Personalize content for the user
}HTMX-Aware Redirects
When an HTMX partial request hits a protected page without authentication, the middleware returns a 401 status with an HX-Redirect header. The AuthRedirectScript (automatically injected when auth is enabled) reads this header and performs a full-page redirect to the login page.
Middleware Stack
The auth system uses two middleware layers:
ForgeMiddleware-- runs on the forge.Router catch-all, callsAuthChecker.CheckAuth(), and stores theUserInfoin the request context. Never blocks requests.PageMiddleware-- runs per ForgeUI page, enforces the access level. Redirects to the login page forprotectedpages when the user is unauthenticated.
Additional middleware helpers: RequireRole(role) and RequireScope(scope) for fine-grained authorization.
The dashboard auth system is intentionally decoupled from the extensions/auth package. The dashauth package defines its own AuthChecker interface and UserInfo type so the dashboard has no hard dependency on any specific auth provider.
Security
Content-Security-Policy
When EnableCSP is true (default), the dashboard adds CSP headers to prevent XSS and other injection attacks.
CSRF Protection
When EnableCSRF is true (default), form submissions and bridge calls are protected with CSRF tokens.
HTML Sanitization
Remote contributor HTML fragments are sanitized before embedding to prevent XSS from untrusted remote services.
Theming
Three theme modes are supported:
dashboard.WithTheme("auto") // Follows system preference (prefers-color-scheme)
dashboard.WithTheme("light") // Always light mode
dashboard.WithTheme("dark") // Always dark modeCustom CSS can be injected:
dashboard.WithCustomCSS(".my-class { color: red; }")Error Recovery
The recovery manager implements a circuit-breaker pattern for remote contributors. If a remote service becomes unreachable, the recovery manager tracks its health state transitions (healthy -> degraded -> unhealthy) and broadcasts state changes via SSE for real-time UI updates.
How is this guide?