feat(10.1-01): serve controller JS/CSS and run registered toolbar actions

- boardwalk exports ContentType and SetSecurityHeaders
- pact.AdminClientAssets files are read and hashed at boot and served by exact
  key under {prefix}/assets/{vendor}/{plugin}/ with nosniff, CSP, CORP,
  no-cache and an ETag; a miss falls through to the SPA
- list and form schemas carry assets URLs with a ?v= hash
- toolbar.buttons resolves create, delete and registered actions after decode;
  toolbarActions is permission-filtered per admin
- POST .../toolbar/{action} behind requireAjax and action permissions
This commit is contained in:
Jakub Zych
2026-09-28 23:41:17 +02:00
parent f9281949a6
commit 8b1cb244de
23 changed files with 772 additions and 64 deletions

View File

@@ -1237,6 +1237,95 @@ export interface paths {
patch?: never;
trace?: never;
};
"/{vendor}/{plugin}/{controller}/toolbar/{action}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Run a toolbar action
* @description Runs a controller-registered action that the list's toolbar.buttons declares. The body must be {}: a toolbar action takes no record ids or values, and its fill is always empty.
*/
post: {
parameters: {
query?: never;
header?: never;
path: {
/** @description Vendor */
vendor: string;
/** @description Plugin */
plugin: string;
/** @description Controller */
controller: string;
/** @description Action name */
action: string;
};
cookie?: never;
};
/** @description Empty object */
requestBody: {
content: {
"application/json": components["schemas"]["cabana.AdminActionRequest"];
};
};
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["cabana.Envelope-cabana_AdminActionResult"];
};
};
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["cabana.ErrorEnvelope"];
};
};
/** @description Forbidden */
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["cabana.ErrorEnvelope"];
};
};
/** @description Not Found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["cabana.ErrorEnvelope"];
};
};
/** @description Unprocessable Entity */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["cabana.ErrorEnvelope"];
};
};
};
};
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/{vendor}/{plugin}/{controller}/widgets/{field}": {
parameters: {
query?: never;
@@ -1953,6 +2042,10 @@ export interface components {
"cabana.BulkResult": {
deleted: number;
};
"cabana.ControllerAssets": {
scripts: string[];
styles: string[];
};
"cabana.Envelope-array_cabana_FilterOption": {
data: components["schemas"]["cabana.FilterOption"][];
meta: components["schemas"]["cabana.SuccessMeta"];
@@ -2076,6 +2169,11 @@ export interface components {
update: components["schemas"]["cabana.FormRedirect"];
};
"cabana.FormView": {
/**
* @description Assets are the controller's plugin JS and CSS URLs; a settings form
* carries empty lists.
*/
assets: components["schemas"]["cabana.ControllerAssets"];
fields: components["schemas"]["cabana.FormField"][];
/** @description Messages is the form's copy resolved in the request locale (D-13). */
messages: components["schemas"]["cabana.FormMessages"];
@@ -2141,6 +2239,8 @@ export interface components {
total: number;
};
"cabana.ListSchema": {
/** @description Assets are the controller's plugin JS and CSS URLs (pact.AdminClientAssets). */
assets: components["schemas"]["cabana.ControllerAssets"];
bulkActions: components["schemas"]["cabana.BulkAction"][];
columns: components["schemas"]["cabana.ListColumn"][];
defaultSort?: components["schemas"]["cabana.ListSort"];
@@ -2164,6 +2264,11 @@ export interface components {
showSetup: boolean;
showSorting: boolean;
title?: string;
/**
* @description ToolbarActions are the controller-registered toolbar.buttons entries
* the requesting admin may run, in declared order, with their labels.
*/
toolbarActions: components["schemas"]["cabana.ToolbarAction"][];
toolbarButtons: string[];
};
"cabana.ListSort": {
@@ -2261,6 +2366,10 @@ export interface components {
per_page?: number;
total?: number;
};
"cabana.ToolbarAction": {
label: string;
name: string;
};
"cabana.fieldContext": string | string[];
"cabana.jsonScalar": (string | number | boolean) | null;
};