Admin¶
Admin panel coordinator that discovers and mounts admin views from other apps.
Package: github.com/oliverandrich/burrow/contrib/admin
Requires: an app implementing burrow.AdminAuth (e.g., contrib/auth)
JavaScript required¶
The admin contrib mandates JavaScript. The bundled contrib/auth is WebAuthn-only (passkeys cannot work without JS), and every admin page sits behind RequireAuth + RequireStaff, so the no-JS form fallback never runs in practice. The admin layout reflects this:
- Nav uses
<a href>inside anhx-boostcontainer on<body>. Semantic HTML stays intact — screen readers, right-click-new-tab, bookmarks, and direct URL reloads all keep working — and htmx upgrades the click into a fragment swap. - Form submits use
hx-postwithhx-target="#main" hx-swap="innerHTML". Validation errors re-render the form fragment into#main; success useshtmx.SmartRedirect(which setsHX-Redirectto drive a full navigation). - Destructive actions (delete, deactivate) use
<button hx-post>, not link-shaped triggers — they shouldn't be reachable via bookmark or prefetch. - CSRF is carried via
csrfHxHeaderson<body>(auto-injectedX-CSRF-Tokenfor every htmx request) plus a hiddengorilla.csrf.Tokeninput in each form for belt-and-suspenders.
Downstream HasAdmin apps should follow the same conventions. See contrib/auth/templates/admin_user_form.html and contrib/auth/admin_handlers.go (adminUpdateUser) for the validation-error / success pair.
Setup¶
srv := burrow.NewServer(
session.New(),
auth.New[auth.EmptyProfile](),
admin.New(),
staticApp, // staticfiles.New(myStaticFS) — returns (*App, error)
// ... other apps
)
admin.New() uses built-in defaults for the layout template and dashboard renderer. Use options to override:
The admin app discovers auth middleware via the AdminAuth interface and admin views via the HasAdmin interface. Any app that implements HasAdmin gets its routes mounted under /admin with auth protection. The admin app does not import contrib/auth directly — any app implementing AdminAuth can provide the middleware.
Default Layout¶
The built-in default layout renders a full admin HTML page with Tailwind v4 styling, a top navbar (brand on the left, user info on the right), and htmx for hx-boost-powered navigation. The dashboard at /admin/ is the primary navigation surface — each registered admin section appears as a card.
Each admin page renders its own breadcrumb (<nav><ul class="breadcrumb">…<li aria-current="page">…</ul></nav>) for back-navigation. There is no persistent sidebar — the cards-on-dashboard pattern keeps the chrome minimal and works the same on desktop and mobile.
Static assets are served via the staticfiles app using content-hashed URLs.
Dependencies: staticfiles, htmx, messages, csrf, auth — all five must be registered. The layout uses {{ csrfToken }}, {{ csrfHxHeaders }}, {{ currentUser }}, and admin's Configure needs an AdminAuth provider (supplied by contrib/auth).
Building Admin Views¶
Apps provide admin views by implementing the HasAdmin interface and writing handlers directly using burrow.Handle and burrow.Render. The admin coordinator handles layout, dashboard cards, and auth middleware — your app only needs to define routes and templates.
func (a *App) AdminRoutes(r chi.Router) {
r.Get("/notes", burrow.Handle(a.adminListNotes))
r.Get("/notes/{id}", burrow.Handle(a.adminEditNote))
r.Post("/notes/{id}", burrow.Handle(a.adminUpdateNote))
r.Delete("/notes/{id}", burrow.Handle(a.adminDeleteNote))
}
func (a *App) AdminNavItems() []burrow.NavItem {
return []burrow.NavItem{
{Label: "Notes", URL: "/admin/notes", Icon: "notes/icon_journal_text", Position: 20},
}
}
Admin handlers follow the same patterns as regular handlers — use Den queries for data access, burrow.Render for template rendering, and htmx.SmartRedirect for redirects from htmx requests. The admin contrib ships a shared admin/pagination template (handlers pass RawQuery: r.URL.RawQuery so filters/search persist across pages). See the contrib/auth and contrib/jobs source code for complete examples of admin views with search, filters, pagination, and inline forms.
Routes¶
The admin app creates the /admin route group with auth.RequireAuth() and auth.RequireStaff() — any logged-in user with the staff or admin role passes the frame gate. Apps that ship admin-only routes wrap them inside their own AdminRoutes with auth.RequireAdmin():
func (a *App) AdminRoutes(r chi.Router) {
// Open to any staff member.
r.Get("/posts", burrow.Handle(a.adminListPosts))
r.Get("/drafts/{id}", burrow.Handle(a.adminEditDraft))
// Admin-only group.
r.Group(func(r chi.Router) {
r.Use(auth.RequireAdmin())
r.Get("/settings", burrow.Handle(a.adminSettings))
})
}
The dashboard at GET /admin/ lists every nav item contributed via AdminNavItems(), filtered per request: NavItem.AdminOnly: true hides the entry from non-admin staff. Groups whose items all become hidden disappear from the dashboard too.
CLI Commands¶
The CLI subcommands for user management (set-role, create-invite) are contributed by the auth app via HasCLICommands, not by the admin app itself. See Auth docs for details.
To wire up CLI commands from all apps, add them to your cli.Command via srv.CLICommands():
cmd := &cli.Command{
Name: "myapp",
Flags: srv.Flags(nil),
Action: srv.Run,
Commands: srv.CLICommands(),
}
srv.CLICommands() wraps each subcommand's Action so the framework's boot lifecycle (database open, Configure() on every app) runs before the subcommand fires. The raw srv.Registry().AllCLICommands() skips that step and is wrong for contrib subcommands that depend on configured state (e.g. auth set-role).
HasAdmin Interface¶
Apps contribute admin views by implementing HasAdmin:
The admin app collects all HasAdmin implementations and mounts their routes under /admin with auth.RequireAuth() + auth.RequireStaff() — the frame is staff-gated. Routes that must stay admin-only are the contributing app's responsibility: wrap them in a sub-group with auth.RequireAdmin() and tag the matching AdminNavItems() entries with AdminOnly: true so non-admin staff don't see them in the dashboard.
Interfaces Implemented¶
| Interface | Description |
|---|---|
burrow.App |
Required: Name() |
HasRoutes |
Creates /admin group and delegates to HasAdmin apps |
HasTemplates |
Contributes admin layout and page templates |
HasFuncMap |
Contributes admin icon template functions |
HasTranslations |
Contributes English and German translations for admin UI |
HasStaticFiles |
Ships admin.css (admin-shell polish: action-row alignment, dashboard card link styling, form-footer flex) |
HasDependencies |
Requires staticfiles, htmx, messages, csrf, auth |