admin

package
v0.1.99 Latest Latest
Warning

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

Go to latest
Published: Oct 10, 2026 License: Apache-2.0 Imports: 40 Imported by: 0

Documentation

Overview

Package admin serves the app's admin pages at /admin: an overview with a setup checklist, users, the credentials of the sign-in, mail, language-model and storage providers (saved sealed with the master key and applied without a restart) plus the app's own (Options.Sections), token usage, the mail outbox, jobs and stored files. Mount it from routes.go:

admin.Mount(r, admin.Options{})

The first account and the users ADMIN_USERS lists (ids or emails, comma separated) get in, unless Options.Allow decides otherwise. The pages are server-rendered HTML on Tabler (https://tabler.io, MIT), light and dark, with their stylesheet, script and icons served from the binary. An app themes them with admin/theme.css (the --admin-* variables, or any Tabler variable) and replaces the frame with admin/layout.html.

Index

Constants

View Source
const EnvAdminUsers = "ADMIN_USERS"

EnvAdminUsers lists who may open the admin pages.

Variables

This section is empty.

Functions

func Mount

func Mount(r *router.Router, opt Options)

Mount registers the pages on r at Options.Path.

func TemplateFS

func TemplateFS() fs.FS

templateFS exposes the embedded templates, for an app that wants to start its layout.html from the pack's.

Types

type Action

type Action func(r *http.Request) (string, error)

Action handles one of a page's forms. It reads r.Form (parsed) and the request's context, and returns the message the page shows next.

type Audit

type Audit struct {
	// Path is the action's address under the admin pages, without the
	// prefix: "/users/<subject>/disable", "/settings", "/orders/refund",
	// "/orders/export".
	Path string
	// Form is what the form sent, secrets redacted: a secret setting's
	// value, every credential value, and any field named like a password,
	// secret, token, key or code read "[redacted]"; long values are cut
	// to 500 characters; an uploaded file is "file: name (bytes)".
	Form url.Values
	// Message is the page's confirmation; Error its alert. One is set for
	// a form; neither for a download, whose Status says how it went.
	Message, Error string
	// Download reports a download; Status is its HTTP status.
	Download bool
	Status   int
	// Request is the request (the admin, the client address).
	Request *http.Request
}

Audit is one admin action, as Options.OnAudit sees it: a form sent on any page (the pack's own and the app's) or a download.

type Field

type Field struct {
	// Name is the environment name (MAIL_API_KEY).
	Name  string
	Label string
	// Kind is "text", "number", "secret" (never shown again once saved),
	// "select" (one of Options) or "multi" (any of Options, saved
	// comma-separated).
	Kind    string
	Options []string
	// Labels name options for people ("Cloudflare R2" for r2).
	Labels map[string]string
	// Dev lists the options meant for development and tests (log, fake,
	// local); the page flags a section that runs on one.
	Dev []string
	// Default is what the pack uses when the setting is empty.
	Default     string
	Help        string
	Placeholder string
	// Placeholders vary the placeholder with the selector's value
	// (the default model of each provider).
	Placeholders map[string]string
	// Group gathers fields under a heading within the section (Google,
	// SMTP server).
	Group string
	// For lists the values of the section's selector under which the
	// field applies (mailgun, sendgrid); empty means always. The empty
	// value ("") names the state where nothing is chosen, for a selector
	// whose empty value works (Section.None).
	For []string
	// Advanced folds the field away under "Advanced" (a proxy address, a
	// token limit): rarely needed, never required.
	Advanced bool
}

Field is one setting.

type Handler

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

Handler serves the pages.

func New

func New(opt Options) *Handler

New builds the handler without mounting it.

func (*Handler) ServeHTTP

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler: the stylesheets, the script and the theme are public (a sign-in page may use them), everything else is behind Auth and Allow.

type Link struct {
	// For is the selector value it belongs to.
	For, Title, URL string
}

Link is a pointer to a provider's console, shown next to the section's fields while that provider is chosen.

type Options

type Options struct {
	// Path is where the pages live; "/admin" by default.
	Path string
	// Dir holds the app's theme on disk: Dir/theme.css replaces the
	// variables, Dir/layout.html the frame. "admin" by default. Both are
	// read from Templates first, so an app that embeds them runs from the
	// binary alone.
	Dir string
	// Title heads every page; the app's name is a good one. "Admin" by
	// default.
	Title string
	// Auth protects the pages; auth.Require() by default. Tests pass a
	// no-op.
	Auth middleware.Middleware
	// Allow decides whether the signed-in user is an admin. By default
	// the first user ever to sign in is one, and so is anyone whose id
	// or email claim ADMIN_USERS lists (in .env, the credentials, or
	// saved from the Overview page).
	Allow func(ctx context.Context) bool
	// OnAudit runs after every admin action, for a staff audit log: each
	// form sent on any page (the pack's own and the app's, with its
	// outcome) and each download. Audit.Form has the secrets redacted. It
	// cannot refuse anything and runs on the request: insert a row.
	OnAudit func(ctx context.Context, a Audit)
	// FirstUser returns the id of the app's first account, an admin by
	// default; the auth pack's FirstSubject unless set. Empty means no
	// such rule.
	FirstUser func(ctx context.Context) string
	// NoFirstUserAdmin turns that rule off: only listed users get in.
	NoFirstUserAdmin bool
	// WorkspaceOwner is the auth.Teams owner role the Workspaces page
	// lists; "owner" when empty.
	WorkspaceOwner string
	// FoldGroups folds the sidebar's labelled groups (Services and the
	// app's Page.Group sections) but the one holding the page shown; a
	// click opens one. For an app with many pages, so the list fits.
	FoldGroups bool
	// CredentialsDir is where config/credentials.yml.enc lives when no
	// db pack holds the runtime values; "." by default.
	CredentialsDir string
	// Sections add the app's own settings to the Credentials page after
	// the packs': API keys of the services the app calls, feature
	// switches. Each field is an environment name the app reads with
	// pkg/env, saved sealed like the packs' and applied through
	// lidza.Reconfigure.
	Sections []Section
	// Pages add the app's own admin pages (orders to refund, reports,
	// moderation queues) to the sidebar, rendered in the same frame. See
	// Page.
	Pages []Page
	// Templates holds the app's page templates, theme.css and
	// layout.html, usually the app's admin/ folder embedded
	// (//go:embed admin, then lidza.Sub(files, "admin")) so they ship in
	// the binary. A file it lacks is read from Dir on disk; the theme and
	// the frame then fall back to the pack's own.
	Templates fs.FS
}

Options configure Mount.

type Page

type Page struct {
	// Name is the sidebar entry and the page's title ("Orders").
	Name string
	// Path is the page's address under the admin pages ("orders" serves
	// /admin/orders); it may not be one of the built-in pages'.
	Path string
	// Icon is one of the vendored Tabler icons (assets/icons.txt in the
	// admin pack); "layout-dashboard" when empty.
	Icon string
	// Group is the sidebar section the page is listed in ("Billing").
	// Pages with the same Group are listed together, the groups in the
	// order their first page appears in Options.Pages; pages without one
	// are under "App", with the app's settings.
	Group string
	// Template names the page's file in Options.Templates (else in
	// Options.Dir on disk). It defines "content" and reads .Data.
	Template string
	// Data loads what the page shows, from the request's context (db.From,
	// the packs). An error is shown on the page instead of the content.
	Data func(r *http.Request) (any, error)
	// Actions are the page's forms: a POST to <Path>/<name> runs
	// Actions[name] and redirects back to the page with its message, or
	// with its error as an alert. A form that posts the frame's .Back as
	// "back" (<input type="hidden" name="back" value="{{$.Back}}">)
	// returns to the view it was sent from: the same page with the same
	// query (?order=1001), never another address.
	Actions map[string]Action
	// MaxUpload, when above zero, lets the page's forms send files
	// (enctype="multipart/form-data"): the body is limited to MaxUpload
	// bytes and an action reads r.MultipartForm.File; a larger body is
	// refused with an alert. Zero refuses file forms.
	MaxUpload int64
	// Downloads answer GET <Path>/<name> behind the same gate as the
	// page: an attachment, an export. The handler writes the whole
	// response; set Content-Type, Content-Disposition and
	// X-Content-Type-Options: nosniff for anything a user uploaded.
	Downloads map[string]http.HandlerFunc
}

Page is an admin page the app adds: an entry in the sidebar under the app's heading, served at Options.Path plus Page.Path behind the same admin gate, and rendered inside the pack's frame, so it has the sidebar, the light and dark themes, Tabler's classes, the icons and the template functions (icon, since, bytes, num, dict, initials) of the built-in pages.

admin.Mount(r, admin.Options{
	Templates: lidza.Sub(adminFS, "admin"), // //go:embed admin
	Pages: []admin.Page{{
		Name: "Orders", Path: "orders", Icon: "inbox", Template: "orders.html",
		Data: func(r *http.Request) (any, error) { return queries.New(db.From(r.Context())).RecentOrders(r.Context()) },
		Actions: map[string]admin.Action{"refund": refundOrder},
	}},
})

type Section

type Section struct {
	// Key names the section in the page's URL (?section=mail).
	Key string
	// Page is the pack page the section sits on (mail, llm, storage,
	// users); an app's sections (empty Page) are on the Settings page.
	Page string
	// Links point at the providers' consoles, per selector value.
	Links []Link
	// Title and Description head the section; Icon is a Tabler icon
	// name among the vendored ones (assets/icons.txt).
	Title, Description, Icon string
	// Selector names the field whose value decides which other fields
	// apply (MAIL_PROVIDER, AUTH_PROVIDERS); see Field.For.
	Selector string
	Fields   []Field
	// Enabled reports whether the section applies to the running app;
	// nil means always.
	Enabled func(ctx context.Context) bool
	// None, when set, makes an empty selector a working state with this
	// status text (sign-in with email and password alone), instead of
	// "Not set up".
	None string
}

Section is one group of settings on the Credentials page: a pack's provider and keys, or the app's own (Options.Sections). Every field is an environment name the app reads through pkg/env; the page saves it sealed with the master key and asks the packs to reload.

Jump to

Keyboard shortcuts

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