GisilaStudio
All symbols are exported from package:gisila_studio/gisila_studio.dart. Database and
TableMeta come from gisila_orm (package:gisila_orm/gisila.dart).
GisilaStudio
Section titled “GisilaStudio”The admin interface. Holds the database, the auth strategy and the registered models, and builds the Shelf handler.
class GisilaStudio { GisilaStudio({ required Database db, String title = 'Gisila Studio', StudioAuth? auth, String? username, String? password, String? secret, bool allowUnauthenticated = false, bool? allowInsecure, });
final String title; final bool allowUnauthenticated; final bool allowInsecure;
void register<T>(TableMeta<T> meta, {...}); Handler handler({required String prefix});}When to use: create one per admin site at startup, register your models, then mount
handler(). You can create several (for example with different titles and model sets) and
mount them at different prefixes.
| Parameter | Type | Default | Description |
|---|---|---|---|
db |
Database |
required | gisila_orm database the studio queries. |
title |
String |
'Gisila Studio' |
Shown in the header, the browser tab and log messages. |
auth |
StudioAuth? |
null |
Full auth strategy. When set, username, password and secret are ignored. |
username |
String? |
STUDIO_USERNAME env var |
Admin username for the built-in strategies. |
password |
String? |
STUDIO_PASSWORD env var |
Admin password. |
secret |
String? |
STUDIO_SECRET env var |
With it, username/password build a JwtStudioAuth; without it, a SessionStudioAuth. |
allowUnauthenticated |
bool |
false |
Serve with no login when no auth is configured. Anyone who can reach the studio gets full read/write access. Local development only. |
allowInsecure |
bool? |
GISILA_ALLOW_INSECURE == '1' |
Serve even when weaknessReason() objects to the credentials. Logs a warning. |
If no auth is configured (and allowUnauthenticated is false), or the credentials are weak (and
allowInsecure is false), handler() returns a handler that answers every request with 503
and a “Studio is disabled” page, and logs the reason to stderr. See
Security model.
Example
import 'dart:io';
import 'package:gisila_orm/gisila.dart';import 'package:gisila_studio/gisila_studio.dart';
final db = await Database.connect(await DatabaseConfig.fromFile('database.yaml'));
final studio = GisilaStudio( db: db, title: 'Blog Admin', username: Platform.environment['STUDIO_USERNAME'], password: Platform.environment['STUDIO_PASSWORD'], secret: Platform.environment['STUDIO_SECRET'],);See also: Add an admin to an app, Secure the studio.
register
Section titled “register”Adds a model to the admin.
void register<T>( TableMeta<T> meta, { String? displayName, String? displayNamePlural, List<String>? listDisplay, List<String> searchFields = const [], List<String> readonlyFields = const [], List<String> excludeFields = const [], List<String>? passwordFields, List<String> autoNowOnUpdateFields = const [], Map<String, StudioSaveHook> onSave = const {}, int pageSize = 25, List<String> ordering = const [], String? group,});When to use: once per table you want in the admin, before calling handler(). Models
registered after handler() is called are not included in that handler.
Column names in every list (and the keys of onSave) may be the snake_case database column
(first_name) or the camelCase Dart field (firstName). A camelCase name whose snake_case form
is a real column is rewritten to it; anything else is kept as is.
| Parameter | Type | Default | Description |
|---|---|---|---|
meta |
TableMeta<T> |
required | Generated metadata, e.g. UserTable.metadata. |
displayName |
String? |
Table name title-cased (blog_post → Blog Post) |
Singular label. |
displayNamePlural |
String? |
displayName + 's' |
Plural label, used in the sidebar and list title. |
listDisplay |
List<String>? |
First 6 columns not in passwordFields |
Columns in the list view, in order. |
searchFields |
List<String> |
[] |
Columns matched with ILIKE '%q%' (cast to text) by the list search. When empty, a search shows all rows. Names that are not real columns are skipped. |
readonlyFields |
List<String> |
[] |
Shown on forms as text; never written. |
excludeFields |
List<String> |
[] |
Left off forms entirely and never written. Still usable in listDisplay. |
passwordFields |
List<String>? |
Columns whose name contains password, passwd or secret |
Masked, never pre-filled, kept when blank on edit, hidden from the default list and delete summary. Pass const [] to opt out. |
autoNowOnUpdateFields |
List<String> |
[] |
Set to the current UTC time on every add and edit. Usually also in readonlyFields. |
onSave |
Map<String, StudioSaveHook> |
{} |
Per-column transforms run before writing. See StudioSaveHook. |
pageSize |
int |
25 |
Rows per list page. |
ordering |
List<String> |
[] (primary key ascending) |
List order; prefix a column with - for descending. Not validated: an unknown column makes the list query fail. |
group |
String? |
null (shown under “General”) |
Sidebar section label. |
register logs a warning to stderr for each editable password column without an onSave hook,
because the studio would store what is typed.
Example
studio.register<User>( UserTable.metadata, displayName: 'User', displayNamePlural: 'Users', listDisplay: ['id', 'email', 'isActive', 'dateJoined'], searchFields: ['email', 'firstName', 'lastName'], readonlyFields: ['id', 'dateJoined', 'updatedAt'], autoNowOnUpdateFields: ['updatedAt'], onSave: {'password': (v) => hashPassword(v as String)}, ordering: ['-dateJoined'], group: 'Accounts',);See also: Customize lists and forms, ModelAdmin.
handler
Section titled “handler”Builds the Shelf Handler for the admin.
Handler handler({required String prefix});When to use: once, after all register calls, to mount the studio. prefix must be the
path the handler is mounted at, because every link, redirect and the cookie Path are built from
it. A trailing slash is removed. Pass '' when the handler is served at the root.
The auth check (see the constructor) happens here, so a misconfigured studio is reported at startup.
Example
import 'package:shelf/shelf_io.dart' as io;import 'package:shelf_router/shelf_router.dart';
// Mounted under /adminfinal router = Router()..mount('/admin', studio.handler(prefix: '/admin'));await io.serve(router.call, '0.0.0.0', 8080);
// Standalone on its own portawait io.serve(studio.handler(prefix: ''), 'localhost', 8081);Routes served are listed in How the studio works.