feat(19.6.1-01): Administrators screen and cross-controller record actions
- pact.HasAdminActionExtensions / AdminActionExtension: plugins add record actions to any admin controller by ID, merged at boot before permission validation (unknown target, no preview, duplicate name stop the boot) - internal summercms.cabana pseudo-plugin, appended only when some plugin registers admin controllers: Administrators list and preview, settings link under System, backend.manage_users and backend.manage_roles with no role grants - backend::lang user, user.role and permissions copy (en, pl) - smoke tests: compile-time seam and settings, Postgres list/preview/action
This commit is contained in:
@@ -493,3 +493,40 @@ Each `pact.AdminRecordAction` has a `Name`, a `Label`, an optional `Confirm` tex
|
||||
- `Danger: true` marks an action that moves money out or cannot be undone, such as a refund. The record response serializes it as `danger` on the `cabana.RecordAction` entry (the key is absent for an ordinary action, so existing payloads are unchanged), and the admin's confirm dialog then shows its confirm button in the danger variant; the footer button itself stays the same. The flag changes nothing on the server: permissions and `Applies` are checked as for any action.
|
||||
|
||||
Record actions have their own namespace: a record action and a bulk action may share a name, such as `activate` here. `create` and `delete` are reserved. A name in `recordActions` that the controller does not register, a duplicate, or an action without a label stops the start-up.
|
||||
|
||||
## Adding record actions to another controller
|
||||
|
||||
A plugin can add record actions to an admin controller it does not own, such as the admin's own Administrators screen (`summercms.cabana.users`, `cabana.AdministratorsControllerID`) or another plugin's screen. The plugin, not a controller, implements `pact.HasAdminActionExtensions` and returns `pact.AdminActionExtension` entries, each naming the target `Controller` by ID and the `pact.AdminRecordAction` entries to add:
|
||||
|
||||
```go src=modules/cabana/example_action_extension_test.go#ProfilesPlugin.AdminActionExtensions
|
||||
// AdminActionExtensions adds an action to the Administrators screen.
|
||||
func (ProfilesPlugin) AdminActionExtensions() []pact.AdminActionExtension {
|
||||
return []pact.AdminActionExtension{{
|
||||
Controller: cabana.AdministratorsControllerID,
|
||||
RecordActions: []pact.AdminRecordAction{{
|
||||
Name: "acme_unlink_profile",
|
||||
Label: "acme.profiles::lang.unlink",
|
||||
Confirm: "acme.profiles::lang.unlink_confirm",
|
||||
Permissions: []string{"acme.profiles.manage_links"},
|
||||
Run: func(ctx context.Context, in pact.AdminRecordActionInput) (pact.AdminRecordActionResult, error) {
|
||||
tx, ok := cabana.TxFromContext(ctx) // the action's row-locked transaction
|
||||
if !ok {
|
||||
return pact.AdminRecordActionResult{}, errors.New("acme.profiles: no transaction")
|
||||
}
|
||||
err := tx.Where("backend_user_id = ?", in.RecordID).Delete(&ProfileLink{}).Error
|
||||
return pact.AdminRecordActionResult{Message: "acme.profiles::lang.unlinked"}, err
|
||||
},
|
||||
}},
|
||||
}}
|
||||
}
|
||||
```
|
||||
|
||||
The admin merges the contributions at boot, after the target's own record actions and in plugin activation order. From then on a contributed action is an ordinary record action of the target:
|
||||
|
||||
- It is offered on the target's preview screen, after the owner's actions, while its `Applies` reports true, and it runs through the same `POST /{controller}/{id}/actions/{action}` route: the record is loaded through the target's `pact.FormExtendQuery` scope with a row lock, and `Run` receives it in `pact.AdminRecordActionInput`.
|
||||
- Both the target controller's `RequiredPermissions` and the action's own `Permissions` apply, so an action on the Administrators screen needs `backend.manage_users` and its own code. Its `Permissions` must name codes some activated plugin registers.
|
||||
- A record action carries no input beyond the record.
|
||||
|
||||
The start-up stops on an unknown controller ID, a target whose `config_form.yaml` has no `preview` block (record actions are offered on the preview screen), a name the target already registers (its owner's, or an earlier contribution's), a reserved name (`create`, `delete`), a name that is not an identifier, an action without `Run`, or an empty label. Prefix contributed names with your plugin (`acme_...`) so they cannot collide with names the owner adds later.
|
||||
|
||||
Contributions are compiled only when the admin is active, that is when some plugin registers admin controllers; a plugin that only adds actions does not switch the admin on.
|
||||
|
||||
Reference in New Issue
Block a user