admin

package
v1.15.0 Latest Latest
Warning

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

Go to latest
Published: Nov 12, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package admin generates screens to manage an app's data from Go structs: a list with search, filters, sorting and pages for each kind of record, and a form to create, edit and delete one, checked against the struct's validate tags.

Describe each kind of record with a struct, and keep it in a Table, or in a Store of your own:

type Link struct {
	ID      int64     `admin:"key"`
	Title   string    `validate:"required,max=200" admin:"list,search"`
	URL     string    `validate:"required,url" admin:"list,search"`
	Read    bool      `admin:"list,filter"`
	AddedAt time.Time `admin:"list,readonly"`
}

links, err := admin.NewTable[Link](db, "links", "")
panel := admin.New(admin.Options{Authorize: onlyAdmins},
	admin.Resource("links", links),
)

Add the panel to the app with kit.WithPlugins, and give it a route, such as src/routes/admin/path.rest:

// page.server.go
func Load(c *kit.Ctx) (admin.Page, error) { return admin.Load(c) }

var Actions = admin.Actions

<!-- +page.svelte -->
<script lang="ts">
	import { Admin } from '@ternkit/tern/ui';
	let { data, form } = $props();
</script>
<Admin {data} {form} />

The admin tag takes key, list, search, filter, readonly, textarea, date and ref=resource; "-" leaves a field out. Fields take their label, help and rules from the label, description and validate tags, as forms do.

Index

Constants

This section is empty.

Variables

View Source
var Actions = kit.Actions{
	"save":   save,
	"delete": remove,
}

Actions are the panel's route's actions: save and delete.

View Source
var ErrNotFound = errors.New("admin: not found")

ErrNotFound is what a Store returns for a record that doesn't exist.

Functions

This section is empty.

Types

type Admin

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

Admin is a panel of screens. It's a kit.Plugin.

func New

func New(opts Options, resources ...Res) *Admin

New returns a panel with these resources, shown in this order.

func (*Admin) Name

func (a *Admin) Name() string

Name implements kit.Plugin.

func (*Admin) Setup

func (a *Admin) Setup(s *kit.Setup) error

Setup implements kit.Plugin.

type Field

type Field struct {
	Name        string `json:"name"`
	Label       string `json:"label"`
	Description string `json:"description"`
	// Input is the control: text, textarea, number, checkbox, date,
	// datetime-local, email, url, password or select.
	Input    string   `json:"input"`
	Options  []string `json:"options"`
	List     bool     `json:"list"`
	Filter   bool     `json:"filter"`
	ReadOnly bool     `json:"readonly"`
	// Ref is the resource this field holds a key of, linked from the list.
	Ref string `json:"ref"`
	// Rules are the field's validate rules, for the browser to check.
	Rules formrules.Field `json:"rules"`
}

Field is a field on screen.

type Info

type Info struct {
	Name     string  `json:"name"`
	Label    string  `json:"label"`
	Singular string  `json:"singular"`
	Href     string  `json:"href"`
	Key      string  `json:"key"`
	Fields   []Field `json:"fields"`
	Search   bool    `json:"search"`
	Create   bool    `json:"create"`
	Edit     bool    `json:"edit"`
	Delete   bool    `json:"delete"`
}

Info describes the resource on screen.

type List

type List struct {
	Rows    []Row             `json:"rows"`
	Total   int               `json:"total"`
	Page    int               `json:"page"`
	Pages   int               `json:"pages"`
	PerPage int               `json:"perPage"`
	Search  string            `json:"search"`
	Sort    string            `json:"sort"`
	Desc    bool              `json:"desc"`
	Filters map[string]string `json:"filters"`
}

List is a page of records.

type Nav struct {
	Name  string `json:"name"`
	Label string `json:"label"`
	Href  string `json:"href"`
}

Nav is a resource in the menu.

type Option

type Option func(*resource)

An Option changes how a resource is shown.

func AfterDelete

func AfterDelete(fn func(ctx context.Context, key string) error) Option

AfterDelete runs fn after a record is deleted, with its key.

func AfterSave

func AfterSave(fn func(ctx context.Context, key string) error) Option

AfterSave runs fn after a record is created or saved, with its key, such as to update a search index or a cache. An error fails the request, though the record stays saved.

func Label

func Label(plural, singular string) Option

Label names the resource in the menu and its screens, such as Label("People", "Person"). The default comes from its name.

func NoCreate

func NoCreate() Option

NoCreate leaves out creating records.

func NoDelete

func NoDelete() Option

NoDelete leaves out deleting records.

func ReadOnly

func ReadOnly() Option

ReadOnly shows the records without a way to create, edit or delete them.

type Options

type Options struct {
	// Title is the panel's name, shown above its menu. Default: "Admin".
	Title string
	// Path is where its route is. Default: "/admin".
	Path string
	// Authorize runs before every screen and action, and returns an error,
	// such as a redirect to sign in or kit.Error(403, ""), for anyone who
	// may not use the panel. It's required: a panel without it would let
	// anyone change everything.
	Authorize func(c *kit.Ctx) error
	// PerPage is how many records a list shows at a time. Default: 25.
	PerPage int
}

Options configure a panel.

type Page

type Page struct {
	Title     string `json:"title"`
	Base      string `json:"base"`
	Resources []Nav  `json:"resources"`
	// View is "home", "list" or "form".
	View     string  `json:"view"`
	Resource *Info   `json:"resource"`
	List     *List   `json:"list"`
	Record   *Record `json:"record"`
	Counts   []int   `json:"counts"`
	Notice   string  `json:"notice"`
}

Page is what a panel's route loads.

func Load

func Load(c *kit.Ctx) (Page, error)

Load is the panel's route's load: it returns the screen for its path.

type Query

type Query struct {
	// Search is text to look for in the searchable fields, or "".
	Search string
	// Filters maps columns to the values they must equal, as forms post them.
	Filters map[string]string
	// Sort is the column to sort by, or "" for the store's own order.
	Sort string
	Desc bool
	// Offset and Limit page the results.
	Offset, Limit int
}

Query is what a list asks a Store for.

type Record

type Record struct {
	// Key is "" for a new record.
	Key    string            `json:"key"`
	Values map[string]string `json:"values"`
}

Record is the record being created or edited.

type Res

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

Res is a resource for New; make one with Resource.

func Resource

func Resource[T any](name string, store Store[T], opts ...Option) Res

Resource is a kind of record, named for its address, such as "links", kept in store.

type Row

type Row struct {
	Key    string            `json:"key"`
	Values map[string]string `json:"values"`
}

Row is a record in a list: its key, and each listed field's value, as a form would post it.

type Store

type Store[T any] interface {
	// List returns a page of the records that match, and how many match in all.
	List(ctx context.Context, q Query) (records []T, total int, err error)
	// Get returns the record with this key, or ErrNotFound.
	Get(ctx context.Context, key string) (T, error)
	// Create adds a record and returns its key.
	Create(ctx context.Context, record T) (key string, err error)
	// Update replaces the record with this key. columns are the ones the
	// form edited.
	Update(ctx context.Context, key string, record T, columns []string) error
	// Delete removes the record with this key.
	Delete(ctx context.Context, key string) error
}

Store keeps the records of one kind. Table is a Store for a SQL table; implement it for data kept elsewhere, such as behind an API.

type Table

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

Table is a Store for a SQL table, in SQLite or PostgreSQL, whose columns are T's fields: each field's db tag, else its name in snake_case.

func NewTable

func NewTable[T any](db *sql.DB, table, order string) (*Table[T], error)

NewTable returns a Store for table. order is the list's order when nobody chose one, such as "id DESC"; "" is the key, newest first.

func (*Table[T]) Create

func (t *Table[T]) Create(ctx context.Context, rec T) (string, error)

Create implements Store. Read-only fields are left to the database's defaults, and so is a numeric key that is zero.

func (*Table[T]) Delete

func (t *Table[T]) Delete(ctx context.Context, key string) error

Delete implements Store.

func (*Table[T]) Get

func (t *Table[T]) Get(ctx context.Context, key string) (T, error)

Get implements Store.

func (*Table[T]) List

func (t *Table[T]) List(ctx context.Context, q Query) ([]T, int, error)

List implements Store.

func (*Table[T]) Update

func (t *Table[T]) Update(ctx context.Context, key string, rec T, columns []string) error

Update implements Store.

Jump to

Keyboard shortcuts

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