Skip to content

GisilaStudio

All symbols are exported from package:gisila_studio/gisila_studio.dart. Database and TableMeta come from gisila_orm (package:gisila_orm/gisila.dart).

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.

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.

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 /admin
final router = Router()..mount('/admin', studio.handler(prefix: '/admin'));
await io.serve(router.call, '0.0.0.0', 8080);
// Standalone on its own port
await io.serve(studio.handler(prefix: ''), 'localhost', 8081);

Routes served are listed in How the studio works.