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:
@@ -170,6 +170,27 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"cabana.ControllerAssets": {
|
||||
"properties": {
|
||||
"scripts": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"styles": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"scripts",
|
||||
"styles"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"cabana.Envelope-array_cabana_FilterOption": {
|
||||
"properties": {
|
||||
"data": {
|
||||
@@ -609,6 +630,14 @@
|
||||
},
|
||||
"cabana.FormView": {
|
||||
"properties": {
|
||||
"assets": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/cabana.ControllerAssets"
|
||||
}
|
||||
],
|
||||
"description": "Assets are the controller's plugin JS and CSS URLs; a settings form\ncarries empty lists."
|
||||
},
|
||||
"fields": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/cabana.FormField"
|
||||
@@ -642,6 +671,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"assets",
|
||||
"fields",
|
||||
"messages",
|
||||
"meta",
|
||||
@@ -838,6 +868,14 @@
|
||||
},
|
||||
"cabana.ListSchema": {
|
||||
"properties": {
|
||||
"assets": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/cabana.ControllerAssets"
|
||||
}
|
||||
],
|
||||
"description": "Assets are the controller's plugin JS and CSS URLs (pact.AdminClientAssets)."
|
||||
},
|
||||
"bulkActions": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/cabana.BulkAction"
|
||||
@@ -915,6 +953,13 @@
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"toolbarActions": {
|
||||
"description": "ToolbarActions are the controller-registered toolbar.buttons entries\nthe requesting admin may run, in declared order, with their labels.",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/cabana.ToolbarAction"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"toolbarButtons": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
@@ -923,6 +968,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"assets",
|
||||
"bulkActions",
|
||||
"columns",
|
||||
"filters",
|
||||
@@ -935,6 +981,7 @@
|
||||
"showSearch",
|
||||
"showSetup",
|
||||
"showSorting",
|
||||
"toolbarActions",
|
||||
"toolbarButtons"
|
||||
],
|
||||
"type": "object"
|
||||
@@ -1277,6 +1324,21 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"cabana.ToolbarAction": {
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"label",
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"cabana.fieldContext": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -2730,6 +2792,121 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/{vendor}/{plugin}/{controller}/toolbar/{action}": {
|
||||
"post": {
|
||||
"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.",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Vendor",
|
||||
"in": "path",
|
||||
"name": "vendor",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Plugin",
|
||||
"in": "path",
|
||||
"name": "plugin",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Controller",
|
||||
"in": "path",
|
||||
"name": "controller",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Action name",
|
||||
"in": "path",
|
||||
"name": "action",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/cabana.AdminActionRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Empty object",
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/cabana.Envelope-cabana_AdminActionResult"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "OK"
|
||||
},
|
||||
"401": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Not Found"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Unprocessable Entity"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"BackendBearer": []
|
||||
}
|
||||
],
|
||||
"summary": "Run a toolbar action",
|
||||
"tags": [
|
||||
"admin"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/{vendor}/{plugin}/{controller}/widgets/{field}": {
|
||||
"post": {
|
||||
"description": "Runs the controller action a `type: widget` field declares. The record is loaded through the controller's form scope (404 when out of scope); only the field's fill keys with scalar values reach the action and the response.",
|
||||
|
||||
109
admin/src/api/schema.d.ts
vendored
109
admin/src/api/schema.d.ts
vendored
@@ -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;
|
||||
};
|
||||
|
||||
4
admin/tests/fixtures/settings.json
vendored
4
admin/tests/fixtures/settings.json
vendored
@@ -75,6 +75,10 @@
|
||||
"meta": {
|
||||
"locale": "en"
|
||||
},
|
||||
"assets": {
|
||||
"scripts": [],
|
||||
"styles": []
|
||||
},
|
||||
"redirects": {
|
||||
"create": {
|
||||
"redirect": "",
|
||||
|
||||
@@ -143,6 +143,10 @@
|
||||
"meta": {
|
||||
"locale": "en"
|
||||
},
|
||||
"assets": {
|
||||
"scripts": [],
|
||||
"styles": []
|
||||
},
|
||||
"redirects": {
|
||||
"create": {
|
||||
"redirect": "acme/demo/widgets/update/:id",
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
"searchTerm": "",
|
||||
"recordUrl": "acme/demo/widgets/update/:id",
|
||||
"toolbarButtons": ["create", "delete"],
|
||||
"toolbarActions": [],
|
||||
"assets": { "scripts": [], "styles": [] },
|
||||
"columns": [
|
||||
{ "key": "name", "label": "Name", "searchable": true, "sortable": true },
|
||||
{ "key": "code", "label": "Code", "searchable": true, "sortable": true },
|
||||
|
||||
Reference in New Issue
Block a user