Skip to content

Commit 47ecc47

Browse files
committed
feat: enhance custom actions with URL redirection and validation
1 parent 8151b51 commit 47ecc47

File tree

7 files changed

+104
-6
lines changed

7 files changed

+104
-6
lines changed

adminforth/modules/configValidator.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,8 +342,12 @@ export default class ConfigValidator implements IConfigValidator {
342342
errors.push(`Resource "${res.resourceId}" has action without name`);
343343
}
344344

345-
if (!action.action) {
346-
errors.push(`Resource "${res.resourceId}" action "${action.name}" must have action function`);
345+
if (!action.action && !action.url) {
346+
errors.push(`Resource "${res.resourceId}" action "${action.name}" must have action or url`);
347+
}
348+
349+
if (action.action && action.url) {
350+
errors.push(`Resource "${res.resourceId}" action "${action.name}" cannot have both action and url`);
347351
}
348352

349353
// Generate ID if not present

adminforth/modules/restApi.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export async function interpretResource(
4949
[ActionCheckSource.CreateRequest]: ['create'],
5050
[ActionCheckSource.DisplayButtons]: ['show', 'edit', 'delete', 'create', 'filter'],
5151
[ActionCheckSource.BulkActionRequest]: ['show', 'edit', 'delete', 'create', 'filter'],
52+
[ActionCheckSource.CustomActionRequest]: ['show', 'edit', 'delete', 'create', 'filter'],
5253
}[source];
5354

5455
await Promise.all(
@@ -1058,13 +1059,33 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
10581059
if (!resource) {
10591060
return { error: await tr(`Resource {resourceId} not found`, 'errors', { resourceId }) };
10601061
}
1061-
console.log("resource", actionId);
1062+
const { allowedActions } = await interpretResource(
1063+
adminUser,
1064+
resource,
1065+
{ requestBody: body },
1066+
ActionCheckSource.CustomActionRequest,
1067+
this.adminforth
1068+
);
10621069
const action = resource.options.actions.find((act) => act.id == actionId);
10631070
if (!action) {
10641071
return { error: await tr(`Action {actionId} not found`, 'errors', { actionId }) };
10651072
}
1066-
1067-
const response = await action.action({ recordId, adminUser, resource, tr });
1073+
if (action.allowed) {
1074+
const execAllowed = await action.allowed({ adminUser, standardAllowedActions: allowedActions });
1075+
if (!execAllowed) {
1076+
return { error: await tr(`Action "{actionId}" not allowed`, 'errors', { actionId: action.name }) };
1077+
}
1078+
}
1079+
1080+
if (action.url) {
1081+
return {
1082+
actionId,
1083+
recordId,
1084+
resourceId,
1085+
redirectUrl: action.url
1086+
}
1087+
}
1088+
const response = await action.action({ recordId, adminUser, resource, tr, adminforth: this.adminforth });
10681089

10691090
return {
10701091
actionId,

adminforth/spa/src/components/ResourceListTable.vue

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,20 @@ async function startCustomAction(actionId, row) {
536536
537537
actionLoadingStates.value[actionId] = false;
538538
539+
if (data?.redirectUrl) {
540+
// Check if the URL should open in a new tab
541+
if (data.redirectUrl.includes('target=_blank')) {
542+
window.open(data.redirectUrl.replace('&target=_blank', '').replace('?target=_blank', ''), '_blank');
543+
} else {
544+
// Navigate within the app
545+
if (data.redirectUrl.startsWith('http')) {
546+
window.location.href = data.redirectUrl;
547+
} else {
548+
router.push(data.redirectUrl);
549+
}
550+
}
551+
return;
552+
}
539553
if (data?.ok) {
540554
emits('update:records', true);
541555

adminforth/spa/src/components/ThreeDotsMenu.vue

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,11 @@ import { getCustomComponent, getIcon } from '@/utils';
4646
import { useCoreStore } from '@/stores/core';
4747
import adminforth from '@/adminforth';
4848
import { callAdminForthApi } from '@/utils';
49-
import { useRoute } from 'vue-router';
49+
import { useRoute, useRouter } from 'vue-router';
5050
5151
const route = useRoute();
5252
const coreStore = useCoreStore();
53+
const router = useRouter();
5354
5455
const props = defineProps({
5556
threeDotsDropdownItems: Array,
@@ -69,6 +70,21 @@ async function handleActionClick(action) {
6970
recordId: route.params.primaryKey
7071
}
7172
});
73+
74+
if (data?.redirectUrl) {
75+
// Check if the URL should open in a new tab
76+
if (data.redirectUrl.includes('target=_blank')) {
77+
window.open(data.redirectUrl.replace('&target=_blank', '').replace('?target=_blank', ''), '_blank');
78+
} else {
79+
// Navigate within the app
80+
if (data.redirectUrl.startsWith('http')) {
81+
window.location.href = data.redirectUrl;
82+
} else {
83+
router.push(data.redirectUrl);
84+
}
85+
}
86+
return;
87+
}
7288
7389
if (data?.ok) {
7490
await coreStore.fetchRecord({

adminforth/spa/src/views/ShowView.vue

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,21 @@ async function startCustomAction(actionId) {
245245
246246
actionLoadingStates.value[actionId] = false;
247247
248+
if (data?.redirectUrl) {
249+
// Check if the URL should open in a new tab
250+
if (data.redirectUrl.includes('target=_blank')) {
251+
window.open(data.redirectUrl.replace('&target=_blank', '').replace('?target=_blank', ''), '_blank');
252+
} else {
253+
// Navigate within the app
254+
if (data.redirectUrl.startsWith('http')) {
255+
window.location.href = data.redirectUrl;
256+
} else {
257+
router.push(data.redirectUrl);
258+
}
259+
}
260+
return;
261+
}
262+
248263
if (data?.ok) {
249264
await coreStore.fetchRecord({
250265
resourceId: route.params.resourceId,

adminforth/types/Back.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,33 @@ interface AdminForthInputConfigCustomization {
720720
}
721721
}
722722

723+
export interface AdminForthActionInput {
724+
name: string;
725+
showIn?: {
726+
list?: boolean,
727+
showButton?: boolean,
728+
showThreeDotsMenu?: boolean,
729+
};
730+
allowed?: (params: {
731+
adminUser: AdminUser;
732+
standardAllowedActions: AllowedActionsEnum[];
733+
}) => boolean;
734+
url?: string;
735+
action?: (params: {
736+
adminforth: IAdminForth;
737+
resource: AdminForthResource;
738+
recordId: string;
739+
adminUser: AdminUser;
740+
extra?: HttpExtra;
741+
tr: Function;
742+
}) => Promise<{
743+
ok: boolean;
744+
error?: string;
745+
message?: string;
746+
}>;
747+
icon?: string;
748+
id?: string;
749+
}
723750

724751
export interface AdminForthResourceInput extends Omit<AdminForthResourceInputCommon, 'columns' | 'hooks' | 'options'> {
725752

adminforth/types/Common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export enum ActionCheckSource {
5050
CreateRequest = 'createRequest',
5151
DeleteRequest = 'deleteRequest',
5252
BulkActionRequest = 'bulkActionRequest',
53+
CustomActionRequest = 'customActionRequest',
5354
}
5455

5556
export enum AllowedActionsEnum {

0 commit comments

Comments
 (0)