Help us shape the future of Go! Take the 2026 Go Developer Survey

admin

package
v0.20.0 Latest Latest
Warning

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

Go to latest
Published: Oct 17, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package admin is an admin panel generated from ORM models: paginated lists with search, sort and filters, detail pages, create/edit forms built from z schemas, deletes with confirmation, and an audit log of every change. The UI is server-rendered html/template with htmx, all embedded (no CDN, no inline scripts or styles, so it runs under a strict CSP).

Access fails closed: every route requires an authenticated user (the guard in Config), the panel ability (default "admin") and the policy ability for the action (viewAny, view, create, update, delete) on the model type. A missing ability or policy denies.

panel, err := admin.New(admin.Config{DB: db, Guard: acc.Web, Authorizer: gate})
if err != nil { return err }
if err := panel.Register(admin.NewResource[models.Post](requests.PostSchema).Search("title")); err != nil {
	return err
}
return panel.Mount(r)

Index

Examples

Constants

View Source
const (
	// AbilityAdmin is the default gate ability required for every page.
	AbilityAdmin = "admin"
	// AbilityAudit is the gate ability required to read the audit log.
	AbilityAudit = "viewAuditLogs"

	AbilityViewAny = "viewAny"
	AbilityView    = "view"
	AbilityCreate  = "create"
	AbilityUpdate  = "update"
	AbilityDelete  = "delete"
)

Abilities checked by the panel. Policy abilities are checked on the model type ((*T)(nil)) or on the record (*T).

View Source
const (
	ActionCreate = "create"
	ActionUpdate = "update"
	ActionDelete = "delete"
)

Audit actions.

View Source
const (
	InputText     = "text"
	InputTextarea = "textarea"
	InputNumber   = "number"
	InputEmail    = "email"
	InputURL      = "url"
	InputPassword = "password"
	InputCheckbox = "checkbox"
	InputSelect   = "select"
	InputDate     = "date"
	InputDateTime = "datetime-local"
)

Input types a form field can be rendered with (Resource.Input).

View Source
const AuditTable = "admin_audit_logs"

AuditTable is the table holding the admin audit log.

View Source
const FlashKey = "admin.success"

FlashKey is the session flash key of the panel's success messages.

Variables

This section is empty.

Functions

func Migration

func Migration(id string) schema.Migration

Migration creates the audit log table; register it with a timestamped id (vukem install:admin does):

admin.Migration("20261016120000_create_admin_audit_logs_table")

func Templates

func Templates() fs.FS

Templates returns the embedded templates, to copy one into Config.Views and change it.

Types

type AnyResource

type AnyResource interface {
	// contains filtered or unexported methods
}

AnyResource is a Resource of any model, as accepted by Panel.Register. Only Resource implements it.

type AuditLog

type AuditLog struct {
	ID int64 `db:"id"`
	// ActorID is the AuthID of the user who made the change.
	ActorID string `db:"actor_id"`
	// Resource is the resource slug ("posts").
	Resource string `db:"resource"`
	// RecordID is the primary key of the changed record.
	RecordID string `db:"record_id"`
	// Action is ActionCreate, ActionUpdate or ActionDelete.
	Action string `db:"action"`
	// Changes maps changed columns to their values before and after.
	// Sensitive columns only record that they changed.
	Changes   orm.JSON[map[string]Change] `db:"changes"`
	CreatedAt time.Time                   `db:"created_at"`
}

AuditLog is one change made through the admin panel.

func (AuditLog) Table

func (AuditLog) Table() string

Table implements orm.Tabler.

type Change

type Change struct {
	Before   any  `json:"before,omitempty"`
	After    any  `json:"after,omitempty"`
	Redacted bool `json:"redacted,omitempty"`
}

Change is the before/after value of one column. Redacted changes (to sensitive columns) carry no values.

type Config

type Config struct {
	// DB stores the resources and the audit log (required).
	DB *database.DB
	// Guard authenticates admins (required), e.g. the "web" session guard.
	// Guests are redirected to the route named "login" (401 for JSON).
	Guard auth.Checker
	// Authorizer decides the panel and policy abilities, usually an
	// authz.Gate. Nil uses the router's Services.Authorizer; with neither,
	// everything is denied.
	Authorizer web.Authorizer
	// Prefix is the URL prefix (default "/admin").
	Prefix string
	// Ability is the gate ability required for every page (default
	// "admin").
	Ability string
	// Title is shown in the header (default "Admin").
	Title string
	// Views overrides embedded templates by path: layouts/admin.html,
	// index.html, show.html, form.html, delete.html, dashboard.html,
	// audit.html.
	Views fs.FS
}

Config configures a Panel.

type Panel

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

Panel is an admin panel; build it with New, add resources with Register and serve it with Mount.

func New

func New(cfg Config) (*Panel, error)

New validates cfg and returns an empty panel.

Example

The panel needs a database, a guard and an authorizer granting the "admin" ability plus a policy per model.

mount := func(r *router.Router, db *database.DB, guard *auth.SessionGuard[User]) error {
	gate := authz.New(func(ctx context.Context) (*User, error) {
		u, err := auth.UserFrom[User](ctx, guard.Name())
		return &u, err
	})
	gate.Define(admin.AbilityAdmin, func(_ context.Context, u *User) bool { return u.Role == "admin" })

	if err := gate.Register(authz.For[*Post](adminPolicy[*Post]{})); err != nil {
		return err
	}

	panel, err := admin.New(admin.Config{DB: db, Guard: guard, Authorizer: gate})
	if err != nil {
		return err
	}

	posts := admin.NewResource[Post](postSchema).
		Search("title").
		Filter("status", "draft", "live").
		Column("Excerpt", func(p Post) string { return strings.SplitN(p.Body, "\n", 2)[0] })

	if err := panel.Register(posts); err != nil {
		return err
	}

	return panel.Mount(r) // GET /admin, /admin/posts, ...
}

_ = mount

func (*Panel) Mount

func (p *Panel) Mount(r *router.Router) error

Mount registers the panel's routes on r. Every route requires the guard, the panel ability and same-origin (CSRF-checked) unsafe requests.

func (*Panel) Register

func (p *Panel) Register(resources ...AnyResource) error

Register validates and adds resources; configuration mistakes are returned here, at boot.

type Resource

type Resource[T any] struct {
	// contains filtered or unexported fields
}

Resource exposes model T in the admin panel: a paginated list with search, sort and filters, a detail page, and create/edit forms generated from a z schema. Options chain and return the resource; mistakes (unknown columns, sensitive columns in lists) are reported by Panel.Register, at boot.

func NewResource

func NewResource[T, I any](schema z.StructSchema[I]) *Resource[T]

NewResource returns a resource for model T whose forms are generated from schema: each schema key is a form field written to the model column with the same name (or the snake_case of the input field name). Only schema keys are ever written (mass-assignment protection); the primary key and timestamps cannot be schema keys. T is given explicitly and I is inferred:

admin.NewResource[models.Post](requests.PostSchema)

func (*Resource[T]) BeforeSave

func (r *Resource[T]) BeforeSave(fn func(ctx context.Context, rec *T) error) *Resource[T]

BeforeSave runs fn on the record before every create and update (after validation, inside the transaction), e.g. to hash a password or set an owner. Returning an error aborts the write.

func (*Resource[T]) Column

func (r *Resource[T]) Column(label string, fn func(T) string) *Resource[T]

Column adds a computed column to the list; fn's result is HTML-escaped.

func (*Resource[T]) Filter

func (r *Resource[T]) Filter(col string, options ...string) *Resource[T]

Filter adds an exact-match filter on col; with options it renders a select, otherwise a text box. Boolean columns get yes/no options.

func (*Resource[T]) Hidden

func (r *Resource[T]) Hidden(cols ...string) *Resource[T]

Hidden hides columns from the list, the detail page and the forms; hidden schema keys are never written.

func (*Resource[T]) Input

func (r *Resource[T]) Input(key, kind string) *Resource[T]

Input overrides the form control of a schema key (one of the Input* constants).

func (*Resource[T]) Label

func (r *Resource[T]) Label(col, label string) *Resource[T]

Label sets the label of a column.

func (*Resource[T]) Labels

func (r *Resource[T]) Labels(singular, plural string) *Resource[T]

Labels sets the singular and plural names shown in the UI.

func (*Resource[T]) List

func (r *Resource[T]) List(cols ...string) *Resource[T]

List sets the columns of the list, in order (default: every column that is not hidden or sensitive).

func (*Resource[T]) PerPage

func (r *Resource[T]) PerPage(n int) *Resource[T]

PerPage sets the page size of the list (default 20, at most 200).

func (*Resource[T]) ReadOnly

func (r *Resource[T]) ReadOnly(cols ...string) *Resource[T]

ReadOnly shows columns in the edit form without letting the admin change them: read-only schema keys keep the stored value and are never written.

func (*Resource[T]) Search

func (r *Resource[T]) Search(cols ...string) *Resource[T]

Search enables the search box over the given string columns (case-insensitive LIKE with bound parameters).

func (*Resource[T]) Sensitive

func (r *Resource[T]) Sensitive(cols ...string) *Resource[T]

Sensitive marks columns holding secrets (password hashes, tokens) beyond the ones detected by name or json:"-": they are never displayed, listed, searched, sorted, filtered or written to the audit log. A sensitive schema key renders as an empty password field and keeps the stored value when left blank on edit.

func (*Resource[T]) Slug

func (r *Resource[T]) Slug(slug string) *Resource[T]

Slug sets the URL segment (default: the table name in kebab-case).

func (*Resource[T]) Sortable

func (r *Resource[T]) Sortable(cols ...string) *Resource[T]

Sortable sets the columns the list may be sorted by (default: the listed columns). Any other sort parameter is ignored.

Jump to

Keyboard shortcuts

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