Documentation
¶
Overview ¶
Package admin provides an auto-generated administration panel for Nucleus, similar to Django's contrib.admin. It exposes a REST API for CRUD operations on registered models and serves an embedded SPA frontend.
Index ¶
- Constants
- func EnsureBootstrapAdminUsersSchema(ctx context.Context, sqlDB *sql.DB, system string) error
- func NormalizePrefix(raw string) string
- func ParseImportData(reader io.Reader, format string) ([]map[string]interface{}, error)
- func ValidateImportConfig(cfg ImportConfig, src datasource.DataSource) error
- type AdminAuth
- type AuditEntry
- type BootstrapAdminConfig
- type BootstrapAdminResult
- type DatabaseAdminAuth
- func (a *DatabaseAdminAuth) Authenticate(r *http.Request) (*auth.User, error)
- func (a *DatabaseAdminAuth) Authorize(user *auth.User, _ string, _ string) bool
- func (a *DatabaseAdminAuth) LoginHandler() http.Handler
- func (a *DatabaseAdminAuth) WithAuthChain(chain *auth.Chain) *DatabaseAdminAuth
- func (a *DatabaseAdminAuth) WithSystem(system string) *DatabaseAdminAuth
- func (a *DatabaseAdminAuth) WithTitle(title string) *DatabaseAdminAuth
- type DatabaseRuntimeInfo
- type DjangoFixtureRecord
- type DumpdataConfig
- type ExportConfig
- type ExportFormat
- type ExportResult
- type ImportConfig
- type ImportError
- type ImportReport
- type LoaddataConfig
- type Panel
- func (p *Panel) Close(ctx context.Context) error
- func (p *Panel) ConsumeEventBus(eb nucleus.EventBus) func()
- func (p *Panel) ConsumeObservability(bus *observability.Bus) func()
- func (p *Panel) Dumpdata(ctx context.Context, cfg DumpdataConfig) (ExportResult, error)
- func (p *Panel) EnableLiveClusterRelay() error
- func (p *Panel) ExecuteImport(ctx context.Context, cfg ImportConfig, records []map[string]interface{}) (*ImportReport, error)
- func (p *Panel) FeatureFlag(name string) (enabled bool, ok bool)
- func (p *Panel) Handler() *router.Mux
- func (p *Panel) ImportFromFile(ctx context.Context, storageKey string, cfg ImportConfig) (*ImportReport, error)
- func (p *Panel) LiveTrafficMiddleware() func(http.Handler) http.Handler
- func (p *Panel) Loaddata(ctx context.Context, cfg LoaddataConfig) (*ImportReport, error)
- func (p *Panel) SetFeatureFlag(name string, enabled bool)
- func (p *Panel) SetSignalBus(bus *signals.Bus)
- type PanelConfig
- type TenantContext
Constants ¶
const DefaultPrefix = "/admin"
const DefaultTitle = "Orbit"
DefaultTitle is the product name shown in the UI when no Title is configured. The panel is Orbit — it used to introduce itself as "Nucleus Admin", which left the product invisible in its own interface (OH-2/PR-ORB-01).
Variables ¶
This section is empty.
Functions ¶
func EnsureBootstrapAdminUsersSchema ¶
EnsureBootstrapAdminUsersSchema guarantees the admin users table exists WITHOUT inserting any user. orbit's mount calls it unconditionally, so the schema exists even when BootstrapPassword is empty and the operator provisions the admin account another way (e.g. `nucleus createuser`, whose own error message tells them to "start the app once to create the schema" — advice that used to be false, because the schema was only created when a bootstrap password was set, leaving the secure-default onboarding with no way in).
func NormalizePrefix ¶
NormalizePrefix canonicalizes the admin mount path.
func ParseImportData ¶
ParseImportData parses uploaded data and returns a slice of record maps.
func ValidateImportConfig ¶
func ValidateImportConfig(cfg ImportConfig, src datasource.DataSource) error
ValidateImportConfig checks import configuration.
Types ¶
type AdminAuth ¶
type AdminAuth interface {
Authenticate(r *http.Request) (*auth.User, error)
Authorize(user *auth.User, model string, action string) bool
LoginHandler() http.Handler
}
AdminAuth is the interface for admin panel authentication and authorization.
type AuditEntry ¶
type AuditEntry struct {
ID uint `json:"id"`
UserID string `json:"user_id"`
Username string `json:"username"`
Action string `json:"action"`
ModelName string `json:"model_name"` // Model or surface affected (e.g. "User", "rbac", "feature_flag")
RecordID string `json:"record_id"` // ID of the affected record (or key / name of the affected object)
OldValue map[string]any `json:"old_value"` // Previous state (updates, deletes, removals) — redacted
NewValue map[string]any `json:"new_value"` // New state or outcome (creates, updates, management actions) — redacted
IP string `json:"ip"`
UserAgent string `json:"user_agent"`
CreatedAt time.Time `json:"created_at"`
}
AuditEntry represents a single audit log record.
Every mutating handler of the panel records its own entry (see audit_coverage_test.go for the route-by-route contract). Action is the verb: Data Studio uses create/update/delete/bulk_delete/bulk_export and schema.update; the management surfaces use dotted names (rbac.policy.add, flag.set, migration.apply, cache.flush, export.create, import.execute, live.exclude.add, audit.clear, ...); the session surfaces use login, login.failed, login.locked, logout and session.terminate.
type BootstrapAdminConfig ¶
type BootstrapAdminConfig struct {
Username string
Email string
Password string
// System is the database dialect of the target connection, as
// reported by `db.DB.System()` ("sqlite", "postgresql", "mysql",
// "mssql", "oracle"). It selects the dialect-appropriate
// CREATE-TABLE form for the admin users table. An empty System
// falls back to the portable `CREATE TABLE IF NOT EXISTS` form
// (accepted by SQLite, PostgreSQL, and MySQL) — preserving the
// pre-dialect-aware behaviour for any caller that does not set it.
System string
}
BootstrapAdminConfig defines how the framework should initialize the first admin account when the admin users table is empty.
type BootstrapAdminResult ¶
type BootstrapAdminResult struct {
Created bool
Username string
Password string
PasswordGenerated bool
}
BootstrapAdminResult reports whether a bootstrap admin account was created. Password is only populated when generated by the framework.
func EnsureBootstrapAdminUser ¶
func EnsureBootstrapAdminUser(ctx context.Context, sqlDB *sql.DB, cfg BootstrapAdminConfig) (BootstrapAdminResult, error)
EnsureBootstrapAdminUser guarantees the admin users table exists and creates one default superuser if the table is currently empty.
type DatabaseAdminAuth ¶
type DatabaseAdminAuth struct {
// contains filtered or unexported fields
}
DatabaseAdminAuth is the default admin auth provider wired by pkg/app. Behavior: - Admin is always protected: login is required.
func NewDatabaseAdminAuth ¶
func NewDatabaseAdminAuth(sqlDB *sql.DB, session *auth.SessionManager, prefix string) *DatabaseAdminAuth
func (*DatabaseAdminAuth) Authenticate ¶
Authenticate returns an authenticated admin user from server-side session.
func (*DatabaseAdminAuth) Authorize ¶
Authorize currently allows all actions for authenticated admin users.
func (*DatabaseAdminAuth) LoginHandler ¶
func (a *DatabaseAdminAuth) LoginHandler() http.Handler
LoginHandler renders the login page (GET) and validates credentials (POST).
func (*DatabaseAdminAuth) WithAuthChain ¶
func (a *DatabaseAdminAuth) WithAuthChain(chain *auth.Chain) *DatabaseAdminAuth
NewDatabaseAdminAuth creates a DB-backed AdminAuth provider that validates credentials against nucleus_admin_users (same table used by createuser). WithAuthChain delegates credential verification to the application's declared authentication chain (auth_backends), so an operator who configured a corporate directory gets directory login in the admin panel without orbit shipping an LDAP client.
It delegates AUTHENTICATION only. Authorization stays here: a directory user who is not in the admin table is refused. Skipping that would turn an LDAP integration into a privilege escalation — every employee in the company would become an administrator of this panel.
func (*DatabaseAdminAuth) WithSystem ¶
func (a *DatabaseAdminAuth) WithSystem(system string) *DatabaseAdminAuth
WithSystem names the SQL dialect of the admin database so that the user lookups Authenticate and the login run on every request are bounded queries (WHERE id = ? / WHERE LOWER(username) = LOWER(?) OR ...) instead of a full read of the admin table followed by a linear scan. orbit.go passes db.DB.System() of the auth database. A caller that leaves it unset keeps the full-read path, which needs no placeholder style.
func (*DatabaseAdminAuth) WithTitle ¶
func (a *DatabaseAdminAuth) WithTitle(title string) *DatabaseAdminAuth
WithTitle sets the heading the login page renders — the panel's configured Title. Returns the receiver for chaining, like WithAuthChain.
type DatabaseRuntimeInfo ¶
type DatabaseRuntimeInfo struct {
Alias string `json:"alias"`
Engine string `json:"engine"`
Dialect string `json:"dialect"`
IsDefault bool `json:"is_default"`
}
DatabaseRuntimeInfo describes one configured DB alias for admin observability.
type DjangoFixtureRecord ¶
type DjangoFixtureRecord struct {
Model string `json:"model"`
PK interface{} `json:"pk"`
Fields map[string]interface{} `json:"fields"`
}
DjangoFixtureRecord represents a single record in Django-style fixture format. Format: {"model": "app.ModelName", "pk": 1, "fields": {...}}
type DumpdataConfig ¶
type DumpdataConfig struct {
Models []string `json:"models"` // Models to export (empty = all)
Database string `json:"database"` // Source database alias
TenantID string `json:"tenant_id"` // Tenant scope (empty = all)
}
DumpdataConfig configures the dumpdata operation.
type ExportConfig ¶
type ExportConfig struct {
Models []string `json:"models"` // Models to export (empty = all registered)
Database string `json:"database"` // Source database alias
TenantID string `json:"tenant_id"` // Tenant scope (empty = all)
Format ExportFormat `json:"format"` // csv | json | sql
Filters map[string]string `json:"filters"` // Additional filters (model.field=value)
}
ExportConfig defines the scope and format of an export operation.
type ExportFormat ¶
type ExportFormat string
ExportFormat defines supported export formats.
const ( ExportFormatCSV ExportFormat = "csv" ExportFormatJSON ExportFormat = "json" ExportFormatSQL ExportFormat = "sql" )
type ExportResult ¶
type ExportResult struct {
ID string `json:"id"`
Status string `json:"status"` // completed, processing, failed
Format string `json:"format"`
Tenant string `json:"tenant,omitempty"` // tenant the export was confined to ("" = every tenant)
Filename string `json:"filename"`
StorageKey string `json:"storage_key"` // Key in storage for download
Size int64 `json:"size"`
Records int `json:"records"`
Error string `json:"error,omitempty"`
CreatedAt time.Time `json:"created_at"`
URL string `json:"url,omitempty"` // Download URL when available
}
ExportResult holds the result of an export operation.
type ImportConfig ¶
type ImportConfig struct {
Database string `json:"database"` // Target database alias
TenantID string `json:"tenant_id"` // Target tenant (for tenant field injection)
Model string `json:"model"` // Target model
Format string `json:"format"` // csv | json
OnConflict string `json:"on_conflict"` // skip | update | error
DryRun bool `json:"dry_run"` // Validate only, no changes
BatchSize int `json:"batch_size"` // Records per batch (default 100)
}
ImportConfig defines the target and behavior of an import.
type ImportError ¶
type ImportError struct {
Row int `json:"row"`
Field string `json:"field,omitempty"`
Message string `json:"message"`
}
ImportError describes a single row import error.
func ValidateImportData ¶
func ValidateImportData(mi datasource.ModelInfo, records []map[string]interface{}, tenantID string) []ImportError
ValidateImportData validates records against model schema without importing.
type ImportReport ¶
type ImportReport struct {
Total int `json:"total"`
Imported int `json:"imported"`
Skipped int `json:"skipped"`
Updated int `json:"updated"`
Failed int `json:"failed"`
Errors []ImportError `json:"errors"`
DryRun bool `json:"dry_run"`
}
ImportReport summarizes import results.
type LoaddataConfig ¶
type LoaddataConfig struct {
StorageKey string `json:"key"` // Storage key of the fixture file
OnConflict string `json:"on_conflict"` // "skip" (default) or "update"
Database string `json:"database"` // Target database alias
TenantID string `json:"tenant_id"` // Tenant ID for auto-injection
}
LoaddataConfig configures the loaddata operation.
type Panel ¶
type Panel struct {
// contains filtered or unexported fields
}
Panel is the admin panel instance that provides CRUD UI for registered models.
func NewPanel ¶
func NewPanel(src datasource.DataSource, logger *slog.Logger, cfg PanelConfig) *Panel
NewPanel creates a new admin panel. Data Studio reads and writes records through the neutral datasource contract (ADR-001); the Nucleus-backed adapter is built by the caller (orbit.go) from the same Runtime accessors and passed in as src. The panel no longer imports the model registry for record access — only the optional cfg.SchemaRegistry, for the field-metadata editor.
func (*Panel) ConsumeEventBus ¶
ConsumeEventBus wires the live SQL and HTTP feeds to the framework's first-party EventBus (nucleus.Runtime.Observability()) — the orbit counterpart to ConsumeObservability, which takes the experimental *observability.Bus directly (SQL only). It subscribes to SQL and HTTP events, drains them into the live ring buffers + stream, and returns a stop function (also invoked from Close) that cancels both subscriptions.
The HTTP lane is what makes host-application traffic reach the in-process panel: the framework's app-level HTTP middleware emits an HTTPEvent for every request, so the panel no longer depends on the host mounting LiveTrafficMiddleware (an internal type the module API cannot expose). Events whose path falls under the admin prefix are skipped here — the panel's own middleware lane records those (and only those), so a request is never recorded twice; live_exclude_patterns apply the same way they do in the middleware and the cluster relay ingest.
The EventBus hands back detached value events and owns the underlying bus's pooled-event Release discipline internally, so these drains are plain (no Release, no buffered-event drain on cancel — cancel() closes the channels). Safe no-op when p, p.live, or eb is nil.
func (*Panel) ConsumeObservability ¶
func (p *Panel) ConsumeObservability(bus *observability.Bus) func()
ConsumeObservability wires the live view's SQL feed to the application observability bus. The bus receives EVERY model.CRUD query across the whole application — the framework's default SQL observer (installed in pkg/app) emits to it — not just the queries issued by the admin's own Data Studio CRUDs. This is the "bus becomes the single SQL feed" step: application queries (REST resources, app-side CRUD) now surface in the live view, which previously only saw the admin panel's own browsing.
It marks the panel as bus-connected so getCRUD skips the now-redundant per-CRUD observer (avoiding double-recording), starts a goroutine draining the subscription into the SQL ring buffer + live stream, and returns a stop function (also invoked from Close). Safe no-op when p, p.live, or bus is nil.
func (*Panel) Dumpdata ¶
func (p *Panel) Dumpdata(ctx context.Context, cfg DumpdataConfig) (ExportResult, error)
Dumpdata exports registered models to a Django-compatible JSON fixture file. Each record is serialized as {"model": "AppName.ModelName", "pk": <id>, "fields": {...}}.
func (*Panel) EnableLiveClusterRelay ¶
EnableLiveClusterRelay enables cluster-aware live telemetry distribution. It is optional and safe to call multiple times.
func (*Panel) ExecuteImport ¶
func (p *Panel) ExecuteImport(ctx context.Context, cfg ImportConfig, records []map[string]interface{}) (*ImportReport, error)
ExecuteImport imports validated records into the database.
func (*Panel) FeatureFlag ¶
FeatureFlag returns one in-memory feature flag value.
func (*Panel) Handler ¶
Handler returns a *router.Mux that can be mounted on the application router.
func (*Panel) ImportFromFile ¶
func (p *Panel) ImportFromFile(ctx context.Context, storageKey string, cfg ImportConfig) (*ImportReport, error)
ImportFromFile handles the complete import flow: read file → parse → validate → import.
func (*Panel) LiveTrafficMiddleware ¶
LiveTrafficMiddleware returns non-blocking runtime observation middleware that records requests into the live feed. In a framework-wired deployment it is NOT the lane host-application traffic arrives on: ConsumeEventBus drains the framework's HTTP events (emitted by the app-level middleware), so mounting this at app level is unnecessary there — the panel keeps it on its own SPA branch, where it also records session activity (which the bus event does not carry). It remains useful for hand-wired panels that have no nucleus.EventBus to consume.
func (*Panel) Loaddata ¶
func (p *Panel) Loaddata(ctx context.Context, cfg LoaddataConfig) (*ImportReport, error)
Loaddata imports data from a Django-compatible JSON fixture file. It auto-detects models from the "model" field, skips unknown models, and handles conflicts based on the OnConflict setting.
func (*Panel) SetFeatureFlag ¶
SetFeatureFlag upserts one in-memory feature flag value.
func (*Panel) SetSignalBus ¶
SetSignalBus sets the signal bus for CRUD operations.
type PanelConfig ¶
type PanelConfig struct {
Prefix string // URL prefix (default "/admin")
Title string // Site title shown in the UI
Environment string
OTLPEndpoint string // optional OTLP endpoint configured by the host app
RedisURL string // optional Redis URL for background jobs runtime snapshot
TaskInspector tasks.Inspector // optional configured queue inspector
LiveExcludePatterns []string // optional path patterns excluded from live HTTP capture
LiveClusterEnabled bool // when true, publish/subscribe live telemetry through Redis
LiveClusterRedisURL string // optional Redis URL for live cluster relay (falls back to RedisURL)
LiveClusterChannel string // optional pub/sub channel (default nucleus:admin:live:v1)
LiveClusterNodeID string // optional explicit node id (defaults to runtime identity)
LiveClusterToken string // optional shared token to reject untrusted relay messages
TraceURLTemplate string // optional trace explorer URL template (supports {trace_id})
Databases []DatabaseRuntimeInfo
DatabaseHandles map[string]*db.DB // optional alias->db handle mapping for runtime stats
EnvironmentSnapshot []string // optional env snapshot (defaults to os.Environ at startup)
FeatureFlags map[string]bool // optional initial in-memory feature flags
MailDriver string
MailFrom string
SMTPHost string
Auth AdminAuth
Session *auth.SessionManager // optional session manager for admin telemetry
SessionStore string // configured session store label (memory|sql|redis)
SessionRuntime auth.SessionRuntimeIdentity
// Multi-tenant configuration. When enabled, Data Studio is confined to
// the tenant of each request — list, get, create, update, delete, bulk,
// CSV export, exports (and their job list, status and download),
// imports and fixtures — resolved by TenantResolver (the host
// application's request scope) and falling back to MultiTenantDefault.
// A request that resolves no tenant is refused (403) unless its operator
// may switch tenants; it does not fall back to every tenant. Only a
// superuser, or a subject granted the tenant_switch RBAC action on
// admin:*, may switch the request to another tenant or to all of them
// with ?tenant=; every switch is audited. The confinement trusts the
// resolver: a tenant read from a header the client controls is the
// client's choice, not the host's.
MultiTenantEnabled bool // whether multi-tenant mode is active
MultiTenantDefault string // default tenant ID when none resolved
MultiTenantAutoFilter bool // confine Data Studio to the request's tenant (default true when multi-tenant enabled)
MultiTenantField string // override tenant field name (empty = auto-detect from model)
MultiTenantIDs []string // known tenant IDs for the selector UI (empty = discover from scope)
// TenantResolver returns the tenant the host application resolved for the
// request (nucleus resolves it from the subdomain or a header before the
// panel runs). Nil means no host resolution: the default tenant applies.
TenantResolver func(*http.Request) (tenant string, ok bool)
MultiSiteEnabled bool // whether multi-site mode is active
MultiSiteDefault string // default site name
MultiSiteNames []string // known site names for the selector UI
// RBAC configuration
RBACEnforcer *authz.Enforcer // optional Casbin enforcer for fine-grained authorization
// Audit logging configuration
AuditEnabled bool // whether audit logging is enabled
AuditMaxSize int // max audit entries in memory (default 10000)
// Migrations path
MigrationsPath string // path to migration files directory
// Storage for exports/imports
Store storage.Store
// SchemaRegistry is an optional Nucleus model registry used ONLY by the
// runtime field-metadata editor (handleUpdateFieldMeta), which mutates model
// metadata and has no equivalent in the neutral datasource contract. Data
// Studio CRUD does not use it — it speaks datasource.DataSource (ADR-001).
SchemaRegistry *model.Registry
}
PanelConfig configures the admin panel.
type TenantContext ¶
type TenantContext struct {
Enabled bool // Whether multi-tenant is enabled
TenantID string // Current tenant ID (empty = global/all tenants)
TenantField string // The column name for tenant isolation
AutoFilter bool // Whether Data Studio is confined to TenantID
Overridden bool // TenantID came from ?tenant= (superuser / tenant_switch)
}
TenantContext holds the tenant resolution of one admin request. It is built by tenantContextMiddleware from, in order of precedence, the ?tenant= override (gated and audited), the tenant the host resolved for the request (PanelConfig.TenantResolver) and the configured default.
Source Files
¶
- actions.go
- audit.go
- bootstrap_admin.go
- datastudio.go
- default_auth.go
- exporters.go
- fixtures.go
- flags.go
- handlers.go
- hardening.go
- importers.go
- live.go
- live_cluster.go
- live_eventbus.go
- live_observ.go
- management.go
- management_migrations.go
- p2_features.go
- panel.go
- prefix.go
- rbac.go
- runtime_cache.go
- runtime_email.go
- runtime_storage.go
- sessions.go
- system.go
- tenant.go
- ui_fallback.go