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:
@@ -61,6 +61,50 @@ func (s *service) widgetAction(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// toolbarAction serves POST .../{controller}/toolbar/{action} (D-12): a
|
||||
// registered action the list's toolbar.buttons declares. A toolbar action
|
||||
// carries no record id and no values, so it can never become an unscoped
|
||||
// record lookup; its answer's fill is always empty.
|
||||
func (s *service) toolbarAction(w http.ResponseWriter, r *http.Request) {
|
||||
s.protect(w, r, func(cc *CompiledController) {
|
||||
action, ok := toolbarActionOf(cc, r.PathValue("action"))
|
||||
if !ok {
|
||||
WriteError(w, http.StatusNotFound, "not_found", msgNotFound)
|
||||
return
|
||||
}
|
||||
if !s.allowAction(w, r, action) {
|
||||
return
|
||||
}
|
||||
in, err := decodeActionRequest(r)
|
||||
if err != nil {
|
||||
writeCRUDError(w, err)
|
||||
return
|
||||
}
|
||||
if in.RecordID != nil || in.Values != nil {
|
||||
writeCRUDError(w, &ValidationError{Details: map[string]any{"body": []string{"A toolbar action takes no record_id or values."}}})
|
||||
return
|
||||
}
|
||||
s.runAction(w, r, cc, action, pact.AdminActionInput{}, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// toolbarActionOf returns the registered action a list toolbar declares under
|
||||
// name; the built-in create and delete are not actions.
|
||||
func toolbarActionOf(cc *CompiledController, name string) (pact.AdminAction, bool) {
|
||||
if cc == nil || cc.List == nil || builtinToolbarActions[name] {
|
||||
return pact.AdminAction{}, false
|
||||
}
|
||||
declared := false
|
||||
for _, button := range cc.List.ToolbarButtons {
|
||||
declared = declared || button == name
|
||||
}
|
||||
if !declared {
|
||||
return pact.AdminAction{}, false
|
||||
}
|
||||
action, ok := cc.Actions[name]
|
||||
return action, ok
|
||||
}
|
||||
|
||||
// allowAction applies the action's own permissions on top of the controller's
|
||||
// (already checked by protect). A denial is logged and answered 403.
|
||||
func (s *service) allowAction(w http.ResponseWriter, r *http.Request, action pact.AdminAction) bool {
|
||||
|
||||
Reference in New Issue
Block a user