Skip to content

Panels

Updated View as Markdown

A panel is an independently routed admin surface. definePanel(id) defaults to /<id> and returns an immutable builder: keep the returned value from every chain. You can create multiple panel modules for different domains, guards or audiences.

import { definePanel, nav } from '@rikology/pupitre'

export default definePanel('staff')
  .path('/staff')
  .domain('staff.example.com')
  .guard('web')
  .brand({ name: 'Operations' })
  .timezone('Asia/Jakarta')
  .navigation([nav.group('Content').items(['posts'])])
  .notifications({ interval: 30_000 })
  .globalSearch({ keyBinding: 'mod+k' })

Complete builder surface

Method Purpose
path(string), domain(string) URL prefix and optional host restriction
guard(string) Host authentication guard
middleware(array) Panel middleware, in declaration order
resources(arrayOrRecord), pages(arrayOrRecord) Register definitions/builders or discovered modules
brand(object) Name, logos and favicon; exact fields in panel reference
theme(object) Validated theme
timezone(string) Panel display/parse timezone
locale('en') English, the supported locale
breadcrumbs(boolean) Disable or enable generated breadcrumbs
login(enabled?), registration(enabled?) Built-in sign-in and sign-up
passwordReset(enabled?), emailVerification(enabled?) Mail-backed reset/verification
`mfa({ required?, issuer? } false)`
passkeys(enabled?) WebAuthn login/profile management
`profile(boolean { avatar? })`
`social(string[] { providers, registration? })`
navigation(entries) Explicit nav.group() / nav.item() tree
userMenu(items) Host-defined account menu links/actions
`tenancy(options false)`
`notifications({ interval? } false)`
`globalSearch({ keyBinding? } false)`
widgets(arrayOrRecord) Dashboard widgets
filters(definitions) Dashboard filter definitions
plugins(plugin[]) Ordered plugin list; repeated calls replace the list
toDefinition(), toJSON() Inspect the definition or deterministic serialized representation
strictAuthorization(enabled?) Require resource/related-model policies at boot and deny missing policies at runtime
environmentIndicator(options?) Opt-in environment badge and top strip; hidden in production by default

Auth methods that take an optional boolean enable their feature when called without arguments. Built-in auth is disabled initially. Notifications are disabled initially; global search is enabled but only resources declaring searchable fields participate.

Environment indicator

Call .environmentIndicator() to show the Adonis application environment name outside production. The shared pupitre.environment prop is omitted when hidden. Options are { visible?: boolean | ((ctx) => boolean | Promise<boolean>), label?: string, color?: 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'gray' }; color defaults to warning. Explicit visibility or an async callback overrides the production default.

definePanel('admin').environmentIndicator({
  label: 'Staging',
  color: 'info',
  visible: async (ctx) => ctx.auth.user?.email === 'admin@example.com',
})

The badge includes text and an accessible environment title; the thin shell-top strip is decorative. It works with expanded/collapsed sidebars and top navigation.

Without explicit navigation, resources and pages contribute their metadata. Resource .navigation({ group, sort, badge }) and page .navigation(...) control placement; badges are server callbacks. Bouncer viewAny, page canAccess, and visibility checks filter entries. Nested resources do not get top-level navigation entries.

Use nav.group(label).items([...]) for ordered groups and nav.item(label) for host links. The generated navigation reference lists exact link, icon, sort, visibility and collapsibility signatures. userMenu() items support server visibility, sorting, methods and external destinations.

Plugins append their navigation contributions after host contributions; equal group labels merge. See plugin ordering before combining explicit and derived navigation.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close