Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -360,3 +360,46 @@ Backend handler: read the payload via `extra`.
Notes:
- If you don’t emit a payload, the default behavior is used by the UI (e.g., in lists the current row context is used). When you do provide a payload, it will be forwarded to the backend as `extra` for your action handler.
- You can combine default context with your own payload by merging before emitting, for example: `emit('callAction', { ...row, asListed: true })` if your component has access to the row object.

## Start actions programmatically
You can execute resource actions manually using adminforth.runAction(). This is useful inside hooks, plugins, cron jobs, custom endpoints, or any backend automation.

```ts title="./resources/apartments.ts"
actions: [
{
//diff-add
id: 'testToggle listedAction',
name: 'Toggle listed',
icon: 'flowbite:eye-solid',
...
}
]
```
Then execute it from a hook for example:

```ts title="./resources/apartments.ts"
hooks: {
...
afterSave: async ({ record, adminUser, resource, adminforth }: { record: any, adminUser: AdminUser, resource: AdminForthResource, adminforth: any }) => {

await adminforth.runAction({
actionId: 'Toggle listed',
resourceId: resource.resourceId,
recordId: record.id,
adminUser,
});

return { ok: true };
},
},
```

runAction() automatically:
- finds the resource
- finds the action
- checks permissions via allowed
- executes the action handler
- passes full action context (recordId, adminUser, extra, etc.)

> ☝️ runAction() is not limited to hooks — you can call it anywhere you have access to the AdminForth instance.

81 changes: 80 additions & 1 deletion adminforth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
import {
AdminForthFilterOperators,
AdminForthDataTypes,
AdminUser,
AdminUser, ActionCheckSource
} from './types/Common.js';

import AdminForthPlugin from './basePlugin.js';
Expand Down Expand Up @@ -815,6 +815,85 @@ class AdminForth implements IAdminForth {
return { error: null };
}

async runAction({
resourceId,
actionId,
recordId,
adminUser,
extra = {},
response,
tr,
}: {
resourceId: string,
actionId: string,
recordId: string | number,
adminUser: AdminUser,
extra,
response?: any,
tr?: any,
}) {
const resource = this.config.resources.find(
(res) => res.resourceId === resourceId
);

if (!resource) {
return {
ok: false,
error: `Resource '${resourceId}' not found`,
};
}

const action = resource.options.actions?.find(
(act) => act.id === actionId
);

if (!action) {
return {
ok: false,
error: `Action '${actionId}' not found`,
};
}

if (!action.action) {
return {
ok: false,
error: `Action '${actionId}' has no action handler`,
};
}

if (typeof action.allowed === 'function') {
const { allowedActions } = await interpretResource(
adminUser,
resource,
{},
ActionCheckSource.CustomActionRequest,
this
);

const execAllowed = await action.allowed({
adminUser,
standardAllowedActions: allowedActions,
});

if (!execAllowed) {
return {
ok: false,
error: `Action '${actionId}' not allowed`,
};
}
}

return await action.action({
recordId: String(recordId),
adminUser,
resource,
adminforth: this,
response: response as any,
tr: tr as any,
extra,
});
}

resource(resourceId: string): IOperationalResource {
if (this.statuses.dbDiscover !== 'done') {
if (this.statuses.dbDiscover === 'running') {
Expand Down