vizier 0.1.0
vizier: ^0.1.0 copied to clipboard
Resource-driven server-rendered admin panels for Maat applications.
Vizier #
Resource-driven, server-rendered admin panels for
Maat applications. Describe a model once as
an AdminResource and Vizier renders the list, create, edit and show pages,
enforces your authorization hooks on every request, and serves the whole panel
behind Anubis web sessions. No JavaScript build step: the assets ship inside
the package and vizier:install publishes them into your application.
class SlotAdminResource extends AdminResource<Slot> {
@override
ModelDefinition<Slot> get model => Slot.def;
@override
String get slug => 'slots';
@override
String get singularLabel => 'Slot';
@override
String get pluralLabel => 'Slots';
@override
String get titleAttribute => 'starts_at';
@override
AdminForm form(AdminForm form) => form.fields([
NumberField('resource_id', label: 'Resource id').integer().required(),
DateTimeField('starts_at').required(),
DateTimeField('ends_at').required(),
]);
@override
AdminTable<Slot> table(AdminTable<Slot> table) => table
.columns([
DateTimeColumn<Slot>('starts_at').sortable(),
DateTimeColumn<Slot>('ends_at'),
])
.defaultSort('starts_at');
@override
bool canCreate(Request request) => request.user<User>()?.isAdmin ?? false;
}
Installation #
dependencies:
vizier: ^0.1.0
Vizier needs the packages it builds on: anubis for web sessions and CSRF,
khnum_maat for views, and seshat_maat for models.
Register the service provider and the console commands, then publish the views and assets once:
providers: [VizierServiceProvider.new, AdminServiceProvider.new],
commands: [...vizierCommands()],
dart run bin/maat.dart vizier:install
A normal install never overwrites files your application already published;
pass --force after reviewing the differences to take newer defaults.
Defining a panel #
A panel is registered from a service provider that boots after
VizierServiceProvider, because the panel resolves the anubis.session
middleware by alias at registration time.
class AdminServiceProvider extends ServiceProvider {
AdminServiceProvider(super.app);
@override
void boot() {
Anubis.provider('users', (id) => User.query().find(int.parse(id)));
Vizier.panel(
Panel(
title: 'Booking Admin',
userProvider: 'users',
authenticate: (email, password) async {
final user = await User.query().where('email', email).first();
final valid = await Hash.checkAsync(password, user?.authPassword ?? '');
return user != null && valid && user.isStaff ? user : null;
},
resources: [BookingAdminResource(), SlotAdminResource()],
),
);
}
}
authenticate returns the user to sign in, or null to reject. The panel
mounts at /admin by default; pass path to move it.
Resources #
Every AdminResource<T> describes one model:
| Member | Purpose |
|---|---|
model, slug, singularLabel, pluralLabel, titleAttribute |
Identity and how a record is named. |
form(AdminForm) |
Fields for create and edit. |
table(AdminTable<T>) |
Columns, sorting, search and filters for the index page. |
navigationIcon, navigationOrder |
Placement in the sidebar. |
query() |
The base query; override to scope what the panel can see. |
beforeCreate, afterCreate, beforeUpdate, afterUpdate |
Hooks around writes. |
Fields: TextField, EmailField, PasswordField, TextareaField,
NumberField, BooleanField, SelectField, DateField, DateTimeField,
HiddenField.
Columns: TextColumn, BooleanColumn, DateColumn, DateTimeColumn, and
ComputedColumn for values derived from the record.
Authorization #
Nothing is writable by default. Each hook receives the current Request, so
the same rule that guards your API guards the panel:
@override
bool canViewAny(Request request) => true;
@override
bool canCreate(Request request) => _isAdmin(request);
@override
bool canUpdate(Request request, Slot record) => _isAdmin(request);
@override
bool canDelete(Request request, Slot record) => _isAdmin(request);
Actions #
Row and bulk actions run your own code and report back to the page:
@override
List<AdminAction<Booking>> actions() => [
AdminAction(
name: 'mark-no-show',
label: 'Mark no-show',
bulk: true,
icon: VizierIcon.check,
requiresConfirmation: true,
handler: (request, records, values) async {
for (final record in records) {
await bookings.markNoShow(record);
}
return ActionResult(message: '${records.length} bookings updated.');
},
),
];
@override
bool canRunAction(Request request, AdminAction<Booking> action, Booking record) =>
_isStaff(request);
An action may take a form to collect input before it runs; the values arrive
in values.
Pages, widgets and extensions #
VizierPagerenders a custom view at its ownslugand appears in the sidebar;dashboardreplaces the defaultDashboardPage.VizierWidgetrenders a view inside the dashboard.VizierExtensionbundles resources, pages, dashboard widgets and translations under a package name, so a third-party package can plug a whole section into a panel with one entry inextensions.
Localization #
Panels ship English and Arabic catalogs, and Arabic renders right-to-left.
defaultLocale, supportedLocales, rtlLocales, localeResolver and
translations on Panel control the rest; resources and actions accept
per-locale labels through singularLabelFor, pluralLabelFor and
localizedLabels.
Customizing the templates #
vizier:install publishes the Khnum templates to
resources/views/vendor/vizier/ and the CSS and JavaScript to
public/vendor/vizier/. Edit them freely; your application's copies win over
the package defaults.
Documentation #
The full guide lives in the Maat documentation.