admin

package
v0.87.2 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: MIT Imports: 48 Imported by: 0

Documentation

Overview

Package admin is the back office for GoFastr apps: a dashboard, the entity screens, and the operations pages (jobs, audit log, roles, user roles, process modules), drawn in one shell through the app's UI host.

Every page renders through the host's pipeline (runtime.js, client navigation, the toast stack) and composes the kit; the battery ships no CSS. The entity screens are framework/entityui's list and record, read in process under the caller's own context plus the admin's per-call elevation (crud.WithElevation), and written through routes the battery mounts behind its own gate.

One default-deny gate covers every page and route: an authenticated caller holding Config.AdminRole ("admin"), or whatever Config.Authorize decides. A signed-out caller gets 401 (or a redirect to Config.LoginPath), a signed-in caller without the role 403. An embed grant and an access Decider's deny refuse the whole back office.

Every mutation records an audit row. A failed audit write is logged, never swallowed, because the mutation already took effect.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Battery

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

Battery is the framework Battery implementation.

func New

func New(cfg Config) *Battery

New constructs the Admin battery. Pass the result to framework.App.RegisterBattery.

func (*Battery) Init

func (b *Battery) Init(app *framework.App) error

Init implements framework.Battery. It refuses, naming what is missing, an app with no UI host, an exposed entity without Config.UI, an icon the kit does not register, and a page whose path or build is unusable; then it mounts the shell, the pages and the routes.

func (*Battery) Name

func (b *Battery) Name() string

Name implements framework.Battery.

func (*Battery) ReservedEmbedPrefixes added in v0.49.0

func (b *Battery) ReservedEmbedPrefixes() []string

ReservedEmbedPrefixes reports the prefix the battery mounted, so an embed grant can never reach the back office even when the app relocated it with Config.PathPrefix. See framework.EmbedReserving.

func (*Battery) SavedViews added in v0.87.0

func (b *Battery) SavedViews() entityui.SavedViewStore

SavedViews returns the saved views store, or nil when Config.SavedViews is off. Every method acts for the caller in ctx only; see entityui.SavedViewStore.

type BulkJobs added in v0.87.0

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

BulkJobs is entityui.JobRunner backed by a battery/queue backend. The queue payload carries only the job id: the confirmed selection lives in the host's snapshot store, and no roles or record ids ride along.

func NewBulkJobs added in v0.87.0

func NewBulkJobs(q handlerQueue, principal PrincipalFunc) (*BulkJobs, error)

NewBulkJobs builds the runner. principal is required: it is how the run re-authenticates the confirming user for every chunk, and a runner that cannot rebuild a creator's context must refuse to start rather than run bulk writes under nobody's identity.

func (*BulkJobs) Bind added in v0.87.0

func (j *BulkJobs) Bind(u *entityui.UI)

Bind registers the queue handler that runs queued bulk jobs through u, closing the NewBulkJobs → EntityUI → Config cycle. Call it once; the admin's Init does (Config.BulkJobs). A nil u is a no-op: Enqueue keeps answering errBulkJobsUnbound and ResumeBulkJobs hands the jobs to a later bound runner.

func (*BulkJobs) Enqueue added in v0.87.0

func (j *BulkJobs) Enqueue(ctx context.Context, job entityui.BulkJob) error

Enqueue implements entityui.JobRunner. It schedules the job and runs nothing inline; the queue's worker calls RunBulkJob, as often as it retries. Before Bind it answers an error and leaves the job unenqueued, so ResumeBulkJobs picks it up.

func (*BulkJobs) Principal added in v0.87.0

func (j *BulkJobs) Principal(ctx context.Context, job entityui.BulkJob) (context.Context, error)

Principal implements entityui.JobRunner: it hands principal the job's creator and tenant, the only identity the queue job carries, then runs the admin's gate on the rebuilt context, as a request to the admin's bulk route would: a creator who passes it now runs elevated, the way the admin's own screens write; one who no longer does runs as a plain caller and passes only their own roles' gates.

type Card added in v0.87.0

type Card struct {
	// Key names the card: a lowercase slug, unique among Cards.
	Key string

	// Title is the card's heading.
	Title string

	// Build draws the card body with the caller's own context. A panic or
	// an error draws a generic message in the card and logs.
	Build func(r *http.Request) (component.Component, error)

	// Poll, when positive, redraws the card on that interval (at least
	// five seconds; the runtime clamps shorter ones). Zero draws it once.
	Poll time.Duration
}

Card is one of the app's own dashboard cards.

type Config

type Config struct {
	// PathPrefix is the URL prefix every admin page and route mounts
	// under. Defaults to "/admin".
	PathPrefix string

	// Title is the product name in the sidebar and the page titles.
	// Defaults to the localized "Admin".
	Title string

	// Title's initial.
	Logo string

	// UI is the app's entity screens (App.EntityUI). Required when the
	// admin exposes any entity: the admin draws its lists and records
	// with it and points their writes at its own gated routes. The admin
	// reads every name it uses from the entities' Display config.
	UI *entityui.UI

	// Entities lists the entity names the admin exposes, under
	// <PathPrefix>/entities/<name>. Empty (the default) exposes none:
	// an admin dropped into an app names what it manages. Set AllEntities
	// for every entity with CRUD on. Naming an entity also exposes one
	// with CRUD off.
	Entities []string

	// AllEntities exposes every registered entity whose CRUD is on.
	// CRUD-off entities (battery/auth's users and sessions) stay hidden,
	// so it never exposes credential tables. Ignored when Entities is set.
	AllEntities bool

	// Themes are the page themes the account page's Appearance card
	// offers as its Look, a ui.ThemePicker beside the light and dark
	// toggle: each a theme registered with style.RegisterThemeOverride
	// (theme.Brutal is one). Empty offers the toggle alone.
	Themes []ui.ThemeChoice

	// Queue is the job queue the Jobs page and the dashboard's failed
	// jobs card read. Nil leaves both out.
	Queue queue.Browsable

	// DB holds the audit log the Audit log page and the dashboard's
	// recent activity read, and the ops pages write. Defaults to the app's
	// DB. Entity reads and writes always go through the app's own CRUD
	// handlers.
	DB *sql.DB

	// AuditTable is the audit log table. Defaults to "audit_log".
	AuditTable string

	// QueueListLimit is the Jobs page's rows per page; a pager under the
	// table reaches older jobs. Default 50.
	QueueListLimit int

	// AuditListLimit is the Audit log page's rows per page; a pager
	// under the table reaches older rows. Default 50.
	AuditListLimit int

	// SavedViews turns on per-user saved views over the admin's
	// database (Config.DB or the app's): one named filter/columns set
	// per user per entity, kept apart by owner and tenant. The store is
	// SavedViews(); the UI wiring is the host's (UI.WithSavedViews).
	SavedViews bool

	// SavedViewsTable is the saved views table. Defaults to
	// "admin_saved_views". Must be a lowercase identifier
	// ([a-z_][a-z0-9_]*).
	SavedViewsTable string

	// BulkJobs runs bulk actions over more records than one request may
	// touch, on a battery/queue backend. Build it with NewBulkJobs and
	// pass the same value to app.EntityUI's Extensions.Jobs; Init binds
	// it to Config.UI and hands back any job a crash left unenqueued.
	BulkJobs *BulkJobs

	// Authorize replaces the role check: it returns true to admit the
	// request. The embed refusal and a Decider's deny still run first.
	// Nil requires an authenticated user whose GetRoles() holds AdminRole.
	Authorize func(ctx context.Context) bool

	// AdminRole is the role the default check requires. Defaults to
	// "admin". Ignored when Authorize is set.
	AdminRole string

	// LoginPath, when set, sends a signed-out page request to
	// LoginPath?next=<path> instead of a 401. A signed-in caller without
	// the role still gets 403.
	LoginPath string

	// SignOutPath is where the account menu's Sign out posts. Defaults to
	// Auth's logout route when Auth is set; with neither, the menu has no
	// Sign out.
	SignOutPath string

	// Policy is the role policy the Roles page manages and the tier
	// checks read. With GrantStore the page grants and revokes.
	Policy *access.RolePolicy

	// GrantStore persists role grants. With Policy, Grant and Revoke
	// write both the live policy and the database. Wire it with
	// framework.NewGrantStore(db, policy), EnsureSchema and LoadInto.
	GrantStore *access.GrantStore

	// Auth backs the User roles page: its UserStore lists users and
	// updates their roles (EntityUserStore does both).
	Auth *auth.AuthManager

	// EffectiveRoles adds resolved roles to the User roles page. Direct
	// roles always show as "direct"; these are unioned with them and
	// labeled by their origin.
	EffectiveRoles func(ctx context.Context, userID string) []access.RoleWithOrigin

	// ProcessModules is the process-module supervisor the Modules page
	// manages (app.ProcessModules()). Nil leaves the page out.
	ProcessModules processModuleController

	// Pages are the app's own admin pages, drawn in the shell with
	// breadcrumbs and a palette entry.
	Pages []Page

	// Cards are the app's own dashboard cards.
	Cards []Card

	// Metrics are the figures in the strip at the top of the dashboard,
	// in order: counts and sums over exposed entities. The strip holds
	// six; with a Queue, the sixth is its Failed jobs figure.
	Metrics []Metric

	// Attention are the list views the dashboard's Needs attention panel
	// previews, beside the recent activity.
	Attention []Watch

	// DashboardNew names an exposed entity whose New button heads the
	// dashboard, the page's primary action ("New customer"). Empty draws
	// none; the entity cards keep their own New either way.
	DashboardNew string

	// Links are extra sidebar links, each in a nav group.
	Links []Link

	// Commands are extra palette entries that go to a URL.
	Commands []ui.PaletteCommand

	// Logger receives what a handler cannot tell its caller, notably an
	// audit row that failed to write after its mutation committed.
	// Defaults to slog.Default().
	Logger *slog.Logger
}

Config configures the Admin battery.

type Link struct {
	// Group is the nav group key; "" is the default group.
	Group string
	Label string
	// Href is a same-origin path.
	Href string
	// Icon is a registered kit icon name, or "".
	Icon string
}

Link is an extra sidebar link.

type Metric added in v0.87.0

type Metric struct {
	// Label names the figure.
	Label string

	// Entity is an exposed entity's name. A Detail's defaults to its
	// parent's.
	Entity string

	// Agg is "count" (or "") or "sum".
	Agg string

	// Field is the Int, Float or Decimal field a sum totals.
	Field string

	// Where narrows the rows, in the list's query DSL:
	// `status = "active"`. Empty takes every row.
	Where string

	// Format "money" prints a sum as currency.
	Format string

	// View is a key of the entity's Display.Views: the figure links to
	// that list view. Empty links to the whole list.
	View string

	// Icon is a registered kit icon name, or "".
	Icon string

	// Detail is a second, smaller figure under the value, its Label after
	// the number: "$1,240.00 outstanding" beside a count of past-due
	// invoices. A Detail takes no View, Icon or Detail of its own.
	Detail *Metric

	// Tone colours a Detail's line: ui.TrendUp good news, ui.TrendDown
	// bad, empty or ui.TrendFlat muted. Only a Detail takes one.
	Tone ui.TrendDirection
}

Metric is one figure in the strip at the top of the dashboard: a count or a sum over an exposed entity, computed by the database in the admin's read, the way the entity cards count.

type Page added in v0.87.0

type Page struct {
	// Path is the page's path under the admin, e.g. "/reports". It may
	// not take one of the admin's own paths.
	Path string

	// Title is the page title, its crumb and its nav and palette label.
	Title string

	// Nav places the page in the sidebar (Group, Order, Icon). Nil, or
	// Hide, leaves it out of the sidebar; the palette still lists it.
	Nav *entity.EntityNav

	// Access narrows who sees the page beyond the admin gate. Nil admits
	// every admin. A refused caller gets 403 and no nav entry. Like Build,
	// it runs with the caller's own context, never the admin's elevation.
	Access func(ctx context.Context) bool

	// Build draws the page body. It runs with the caller's own context:
	// the admin's elevation never reaches app code. A panic or an error
	// fails the page with a generic message and a log line.
	Build func(r *http.Request) (component.Component, error)
}

Page is one of the app's own admin pages, drawn in the shell behind the gate, with breadcrumbs and a palette entry.

type PrincipalFunc added in v0.87.0

type PrincipalFunc func(ctx context.Context, userID, tenant string) (context.Context, error)

PrincipalFunc rebuilds one user's request context: the user and their CURRENT roles, read fresh, and tenant, the run's own tenant, fixed at confirm. RunBulkJob calls it before every chunk; an error stops the run, so a creator who is gone, or whose roles no longer admit the action, runs nothing. An app that tracks tenant membership returns an error for a user no longer in tenant. See AuthPrincipal for battery/auth.

func AuthPrincipal added in v0.87.0

func AuthPrincipal(am *auth.AuthManager) PrincipalFunc

AuthPrincipal builds the PrincipalFunc for battery/auth: it loads the user by id through the manager's UserStore — a user who is gone is an error, so the run stops — installs them as the context's user with their CURRENT roles (as the user and via access.WithRoles, so both the User-reading gates and the permission-reading gates see them), and stamps the run's tenant as given: battery/auth has no tenant membership to check it against. The admin adds Config.Policy when the context has no policy of its own.

type Watch added in v0.87.0

type Watch struct {
	// Entity is an exposed entity's name.
	Entity string

	// View is a key of the entity's Display.Views: its Where picks the
	// rows and its Sort orders them.
	View string

	// Columns replaces the entity's list columns in the preview; empty
	// keeps them.
	Columns []string

	// Rows is how many rows the preview shows: 5 when zero, at most 20.
	Rows int
}

Watch is a list view whose rows need someone: past-due invoices, failed payments. The dashboard's Needs attention panel previews the first rows of every watched view that has any.

Jump to

Keyboard shortcuts

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