adminui

package module
v4.8.0 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2026 License: MIT Imports: 24 Imported by: 0

README

adminui — Admin Dashboard for cqrs-htmx

A ready-made, good-looking Admin Dashboard for apps built on cqrs-htmx/usermgmt. Mount it in one call and get a complete HTMX-driven UI: a dashboard with live metrics, user management, tenant management, tenant members, and an audit log.

  • One-call mount behind your existing session middleware.
  • Two scopes: a global Super Admin panel, or a per-tenant Tenant Admin panel.
  • Modern look out of the box — a self-contained stylesheet with automatic light/dark theming and a configurable accent color. No Tailwind, no build step.
  • HTMX interactivity — live search, inline actions, toast notifications.
  • templ-powered — type-safe HTML components. The generated Go is committed, so consumers never run a code generator.

Quick start

import (
    "net/http"

    "github.com/larsartmann/cqrs-htmx/adminui/v4"
    "github.com/larsartmann/cqrs-htmx/usermgmt/v4"
)

func main() {
    svc, _ := usermgmt.NewService(usermgmt.ServiceConfig{AuditLog: usermgmt.NewAuditLog()})

    panel, _ := adminui.New(adminui.Config{
        Service:     svc,
        Title:       "Acme Admin",
        AccentColor: "#0ea5e9",
        LogoutURL:   "/logout",
    })

    mux := http.NewServeMux()
    // Sit the panel behind your session middleware so *usermgmt.User is in context.
    mux.Handle("/admin/", usermgmt.NewSessionMiddleware(svc, "session")(panel.Handler()))
    // panel.Mount(mux, "/admin/") is the no-middleware shorthand.
    http.ListenAndServe(":8080", mux)
}

Open /admin/ — done.

How auth works

The panel is auth-agnostic: it reads the authenticated *usermgmt.User from the request context (placed there by usermgmt.NewSessionMiddleware or your own middleware). Requests without a user get 401; users that fail Config.Authorizer get 403.

Security: like authentication, CSRF protection is the consumer's responsibility. The panel issues state-changing POSTs (delete user, create/suspend/delete tenant). Wrap it with httputil.CSRFMiddleware (or your own) in production. The showcase demo omits it for simplicity.

The default authorizer checks roles:

Mode Default check
ModeSuperAdmin super_admin or admin in the global (*) domain
ModeTenantAdmin admin or owner within Config.TenantID

Override with your own Config.Authorizer, or use the helpers RequireAnyRole and RequireAuthenticated.

Configuration

Field Purpose Default
Service The backing *usermgmt.Service. Required.
Title Brand text in the sidebar / tab. "Admin"
BasePath URL prefix the panel is mounted under. "/admin"
Mode ModeSuperAdmin or ModeTenantAdmin. SuperAdmin
TenantID Scopes a tenant-admin panel.
AccentColor Highlight color (any CSS color). indigo
Authorizer Access-control function. role-based
LogoutURL "Sign out" link target. Empty hides the link.

What you get

  • Dashboard — user/tenant/audit counts + recent activity.
  • Users — searchable list, per-user detail (credentials, MFA, roles across tenants), delete.
  • Tenants — list, create, suspend, reactivate, delete, and view members.
  • Members — add a user by email + role, or remove a member, on any tenant (super-admin) or your scoped tenant (tenant-admin).
  • Audit log — the recorded user/tenant events.

Run the demo

nix run .#build-admin-demo        # build the showcase binary
# or:
cd examples/admin-demo && go run .

Then open http://localhost:8097/ — it signs you in as the demo admin and shows the panel with seeded data.

Project layout

adminui/
├── config.go / authz.go    # Config, modes, authorization
├── handler.go              # Handler, Mount(), routing, auth guard
├── render.go               # page/partial render + toasts + redirects
├── assets/                 # embedded CSS + JS; reuses root's htmx.js
├── *.templ / *_templ.go    # templ components (generated files committed)
├── handler_*.go            # per-section HTTP handlers
└── *_test.go               # render + handler tests

The panel is a leaf module: it depends on cqrs-htmx/v4 (root) and cqrs-htmx/usermgmt/v4, and nothing depends on it.

Documentation

Overview

templ: version: v0.3.1020

templ: version: v0.3.1020

templ: version: v0.3.1020

Package adminui provides a ready-made, good-looking Admin Dashboard for applications built on github.com/larsartmann/cqrs-htmx/usermgmt/v4.

It renders a complete HTMX-driven management UI — dashboard, users, tenants, tenant members, and an audit log — backed by a *usermgmt.Service. Consumers mount it with a single call:

svc, _ := usermgmt.NewService(config)
panel := adminui.New(adminui.Config{Service: svc})
panel.Mount(mux, "/admin")

The panel is intended to sit behind the consumer's session middleware (e.g. usermgmt.NewSessionMiddleware) so that *identitymodel.User is present in the request context. Access is gated by Config.Authorizer.

Two scopes

  • Super Admin (default): a global view of every user, tenant, and audit event. Best for platform operators.
  • Tenant Admin: a scoped view limited to a single tenant (Config.TenantID). Best for per-customer admin sub-panels. Only the dashboard, members, and audit sections are shown.

Design

All markup is authored in templ and compiled to Go (the generated _templ.go files are committed, so consumers never run the templ generator). A modern embedded stylesheet (assets/admin-tw.css) provides the look, with automatic light/dark theming. No JavaScript framework — just HTMX, Tailwind v4, and a binary.

templ: version: v0.3.1020

templ: version: v0.3.1020

templ: version: v0.3.1020

templ: version: v0.3.1020

Index

Constants

View Source
const DefaultAccentColor = "#4f46e5"

DefaultAccentColor is the indigo used for buttons, links, and highlights when Config.AccentColor is empty.

View Source
const MaxListRows = 200

MaxListRows is the maximum number of rows rendered in a single list page. It bounds memory and response size for large datasets. The UI shows a "showing N of M" note when a list is truncated. (Real server-side pagination is a future enhancement — it needs paginated read-model query methods.)

Variables

This section is empty.

Functions

func Layout

func Layout(p pageData) templ.Component

func RequireAnyRole

func RequireAnyRole(
	service *usermgmt.Service,
	domain string,
	roles ...identitymodel.Role,
) func(*identitymodel.User) error

RequireAnyRole returns an authorizer that grants access when the user holds any of the given roles in domain (use "*" for a global check, or a tenant ID for a scoped check). A nil or unauthenticated user is always denied.

func RequireAuthenticated

func RequireAuthenticated() func(*identitymodel.User) error

RequireAuthenticated returns an authorizer that grants access to any authenticated user, regardless of role. Use for low-trust panels or as a building block combined with additional checks.

Types

type Config

type Config struct {
	// Service backs the panel. Required.
	Service *usermgmt.Service

	// Title is shown in the sidebar and the browser tab. Default "Admin".
	Title string

	// BasePath is the URL prefix the panel is mounted under, without a trailing
	// slash (e.g. "/admin"). Used for every internal link. Default "/admin".
	BasePath string

	// Mode selects the panel scope. Default [ModeSuperAdmin].
	Mode Mode

	// TenantID scopes a [ModeTenantAdmin] panel to one tenant. Ignored in
	// [ModeSuperAdmin] mode. Required when Mode == [ModeTenantAdmin].
	TenantID identitymodel.TenantID

	// AccentColor overrides the highlight color (any CSS color). Default
	// [DefaultAccentColor].
	AccentColor string

	// Authorizer decides whether the authenticated user may use the panel.
	// Return a non-nil error to deny access (HTTP 403). When nil, a default
	// role-based authorizer is used — see [defaultAuthorizer]. Override this to
	// match your own role model.
	Authorizer func(user *identitymodel.User) error

	// LogoutURL is the destination of the "Sign out" link. Empty hides the link.
	LogoutURL string

	// SSEURL is the Server-Sent Events endpoint URL. When set, the panel
	// layout includes a data-sse-url attribute and renders the global sync
	// indicator (.sync-bar). Empty disables honest UI sync tracking.
	SSEURL string

	// NonceFunc returns a per-request CSP nonce for inline scripts (used by
	// ToastContainer and GlobalErrorHandling). Return "" if CSP is not active.
	// When nil, the nonce is read from the request context via
	// httputil.NonceFromRequest, which works automatically when the consumer
	// adds httputil.Nonce middleware (included in [Handler.Middleware]).
	// Set this only to override the default behavior with a custom nonce source.
	NonceFunc func(*http.Request) string
}

Config configures an admin panel. Only Config.Service is required; every other field has a sensible default applied by New.

type Handler

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

Handler is a mounted admin panel. Build it with New and register it on a router with Handler.Mount or Handler.Handler.

The panel expects the consumer's session middleware to have placed the authenticated *identitymodel.User in the request context (see usermgmt.NewSessionMiddleware). Requests without an authenticated user, or users that fail Config.Authorizer, receive 401/403.

func New

func New(config Config) (*Handler, error)

New builds an admin panel from config, applying defaults to empty fields and validating the result. It returns an error only for invalid configuration (e.g. a nil Service).

func (*Handler) Config added in v4.8.0

func (h *Handler) Config() Config

Config returns the resolved configuration (defaults applied) behind the panel. Read-only snapshot for inspection and tests.

func (*Handler) Handler

func (h *Handler) Handler() http.Handler

Handler returns an http.Handler serving the whole panel at root-relative paths. Mount it under a prefix with http.StripPrefix, or use Handler.Mount.

func (*Handler) Middleware

func (h *Handler) Middleware() func(http.Handler) http.Handler

Middleware returns the standard middleware chain the panel recommends: security headers (X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy), per-request CSP nonce, and panic recovery.

It delegates to cqrshtmx.RecommendedSecurityMiddleware so that the panel has the same security posture as dashboardui.

Wrap it around the panel — and compose your session and CSRF middleware:

panel.Mount(mux, "/admin/")
mux.Use(sessionMW, csrfMW, panel.Middleware()) // pseudo: chain as you prefer

This is optional: the panel works without it, but recovery + security headers are recommended for any production deployment.

func (*Handler) Mount

func (h *Handler) Mount(mux *http.ServeMux, pattern string)

Mount registers the panel on mux at pattern (e.g. "/admin/"). A trailing slash is required by the standard mux for prefix matching. Use "/" to host the panel at the site root.

The pattern is registered without a method, so it conflicts with a method-specific "GET /" catch-all on the same mux. Register any site-root index as "GET /{$}" or "/" (no method) to avoid a ServeMux panic.

type Mode

type Mode int

Mode controls the scope of the admin panel.

const (
	// ModeSuperAdmin shows a global view: all users, tenants, and audit events.
	// Intended for platform operators.
	ModeSuperAdmin Mode = iota
	// ModeTenantAdmin shows a view scoped to a single tenant: dashboard,
	// members, and audit. Requires [Config.TenantID].
	ModeTenantAdmin
)

Jump to

Keyboard shortcuts

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