Dashboard

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:

LayoutPurpose
rootHTML shell with head, scripts, sidebar, topbar. Detects HTMX partial requests and skips the outer shell.
dashboardDefault layout. Inherits root, adds breadcrumbs, page title, and content area.
baseMinimal layout with sidebar and topbar but no breadcrumbs.
fullFull-width layout without sidebar (e.g., for standalone pages).
settingsSettings layout with sidebar navigation for settings pages.
authCentered 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 manifest
  • GET <baseURL>/_forge/dashboard/pages/* -- HTML page fragments
  • GET <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:

PagePathDescription
Overview/Application health, extension count, uptime, key metrics
Health/healthPer-extension health checks with status indicators
Metrics/metricsCounters, gauges, and histograms from the Forge metrics system
Services/servicesRegistered 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.

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:

EndpointFormatDescription
GET /export/jsonJSONFull snapshot of overview, health, metrics, and services
GET /export/csvCSVMetrics as rows with name, type, value, and timestamp
GET /export/prometheusPrometheusText 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:

LevelBehavior
publicAlways accessible, no login required
protectedRequires authentication; unauthenticated users are redirected to the login page
partialAlways 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:

  1. ForgeMiddleware -- runs on the forge.Router catch-all, calls AuthChecker.CheckAuth(), and stores the UserInfo in the request context. Never blocks requests.
  2. PageMiddleware -- runs per ForgeUI page, enforces the access level. Redirects to the login page for protected pages 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 mode

Custom 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?

On this page