adminforth 1.13.0-next.4 → 1.13.0-next.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/createApp/templates/adminuser.ts.hbs +9 -7
- package/commands/createApp/templates/index.ts.hbs +0 -1
- package/commands/createPlugin/templates/package.json.hbs +1 -1
- package/dist/dataConnectors/baseConnector.d.ts +10 -14
- package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
- package/dist/dataConnectors/baseConnector.js +76 -30
- package/dist/dataConnectors/baseConnector.js.map +1 -1
- package/dist/dataConnectors/clickhouse.d.ts +14 -23
- package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
- package/dist/dataConnectors/clickhouse.js +68 -27
- package/dist/dataConnectors/clickhouse.js.map +1 -1
- package/dist/dataConnectors/mongo.d.ts +12 -16
- package/dist/dataConnectors/mongo.d.ts.map +1 -1
- package/dist/dataConnectors/mongo.js +15 -11
- package/dist/dataConnectors/mongo.js.map +1 -1
- package/dist/dataConnectors/mysql.d.ts +8 -13
- package/dist/dataConnectors/mysql.d.ts.map +1 -1
- package/dist/dataConnectors/mysql.js +45 -26
- package/dist/dataConnectors/mysql.js.map +1 -1
- package/dist/dataConnectors/postgres.d.ts +8 -13
- package/dist/dataConnectors/postgres.d.ts.map +1 -1
- package/dist/dataConnectors/postgres.js +45 -28
- package/dist/dataConnectors/postgres.js.map +1 -1
- package/dist/dataConnectors/sqlite.d.ts +7 -4
- package/dist/dataConnectors/sqlite.d.ts.map +1 -1
- package/dist/dataConnectors/sqlite.js +48 -22
- package/dist/dataConnectors/sqlite.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -7
- package/dist/index.js.map +1 -1
- package/dist/modules/codeInjector.d.ts.map +1 -1
- package/dist/modules/codeInjector.js +8 -1
- package/dist/modules/codeInjector.js.map +1 -1
- package/dist/modules/configValidator.d.ts +5 -0
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +88 -7
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/operationalResource.d.ts +4 -4
- package/dist/modules/operationalResource.d.ts.map +1 -1
- package/dist/modules/operationalResource.js +16 -2
- package/dist/modules/operationalResource.js.map +1 -1
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +182 -56
- package/dist/modules/restApi.js.map +1 -1
- package/dist/spa/src/afcl/Dialog.vue +13 -0
- package/dist/spa/src/afcl/Select.vue +27 -2
- package/dist/spa/src/components/ColumnValueInput.vue +12 -5
- package/dist/spa/src/components/ColumnValueInputWrapper.vue +77 -0
- package/dist/spa/src/components/Filters.vue +16 -8
- package/dist/spa/src/components/GroupsTable.vue +15 -50
- package/dist/spa/src/components/ResourceForm.vue +19 -6
- package/dist/spa/src/components/ResourceListTable.vue +87 -7
- package/dist/spa/src/components/ShowTable.vue +11 -3
- package/dist/spa/src/components/SkeleteLoader.vue +11 -3
- package/dist/spa/src/components/ThreeDotsMenu.vue +75 -7
- package/dist/spa/src/components/ValueRenderer.vue +18 -2
- package/dist/spa/src/types/Back.ts +88 -20
- package/dist/spa/src/types/Common.ts +26 -2
- package/dist/spa/src/views/EditView.vue +7 -3
- package/dist/spa/src/views/ListView.vue +5 -2
- package/dist/spa/src/views/ShowView.vue +76 -1
- package/dist/types/Back.d.ts +80 -20
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js +6 -0
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +26 -4
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js +3 -0
- package/dist/types/Common.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template >
|
|
2
|
-
<template v-if="threeDotsDropdownItems?.length">
|
|
2
|
+
<template v-if="threeDotsDropdownItems?.length || customActions?.length">
|
|
3
3
|
<button
|
|
4
4
|
data-dropdown-toggle="listThreeDotsDropdown"
|
|
5
5
|
class="flex items-center py-2 px-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
|
|
@@ -23,6 +23,18 @@
|
|
|
23
23
|
/>
|
|
24
24
|
</a>
|
|
25
25
|
</li>
|
|
26
|
+
<li v-for="action in customActions" :key="action.id">
|
|
27
|
+
<a href="#" @click.prevent="handleActionClick(action)" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
|
28
|
+
<div class="flex items-center gap-2">
|
|
29
|
+
<component
|
|
30
|
+
v-if="action.icon"
|
|
31
|
+
:is="getIcon(action.icon)"
|
|
32
|
+
class="w-4 h-4 text-lightPrimary dark:text-darkPrimary"
|
|
33
|
+
/>
|
|
34
|
+
{{ action.name }}
|
|
35
|
+
</div>
|
|
36
|
+
</a>
|
|
37
|
+
</li>
|
|
26
38
|
</ul>
|
|
27
39
|
</div>
|
|
28
40
|
</template>
|
|
@@ -30,14 +42,70 @@
|
|
|
30
42
|
|
|
31
43
|
|
|
32
44
|
<script setup lang="ts">
|
|
45
|
+
import { getCustomComponent, getIcon } from '@/utils';
|
|
46
|
+
import { useCoreStore } from '@/stores/core';
|
|
47
|
+
import adminforth from '@/adminforth';
|
|
48
|
+
import { callAdminForthApi } from '@/utils';
|
|
49
|
+
import { useRoute, useRouter } from 'vue-router';
|
|
50
|
+
|
|
51
|
+
const route = useRoute();
|
|
52
|
+
const coreStore = useCoreStore();
|
|
53
|
+
const router = useRouter();
|
|
33
54
|
|
|
34
|
-
|
|
35
|
-
|
|
55
|
+
const props = defineProps({
|
|
56
|
+
threeDotsDropdownItems: Array,
|
|
57
|
+
customActions: Array
|
|
58
|
+
});
|
|
36
59
|
|
|
37
|
-
|
|
60
|
+
async function handleActionClick(action) {
|
|
61
|
+
adminforth.list.closeThreeDotsDropdown();
|
|
62
|
+
|
|
63
|
+
const actionId = action.id;
|
|
64
|
+
const data = await callAdminForthApi({
|
|
65
|
+
path: '/start_custom_action',
|
|
66
|
+
method: 'POST',
|
|
67
|
+
body: {
|
|
68
|
+
resourceId: route.params.resourceId,
|
|
69
|
+
actionId: actionId,
|
|
70
|
+
recordId: route.params.primaryKey
|
|
71
|
+
}
|
|
72
|
+
});
|
|
38
73
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
+
}
|
|
88
|
+
|
|
89
|
+
if (data?.ok) {
|
|
90
|
+
await coreStore.fetchRecord({
|
|
91
|
+
resourceId: route.params.resourceId,
|
|
92
|
+
primaryKey: route.params.primaryKey,
|
|
93
|
+
source: 'show',
|
|
94
|
+
});
|
|
42
95
|
|
|
96
|
+
if (data.successMessage) {
|
|
97
|
+
adminforth.alert({
|
|
98
|
+
message: data.successMessage,
|
|
99
|
+
variant: 'success'
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (data?.error) {
|
|
105
|
+
adminforth.alert({
|
|
106
|
+
message: data.error,
|
|
107
|
+
variant: 'danger'
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
43
111
|
</script>
|
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
<span
|
|
4
|
-
|
|
3
|
+
<span
|
|
4
|
+
v-if="column.foreignResource"
|
|
5
|
+
:class="{'flex flex-wrap': column.isArray?.enabled}"
|
|
6
|
+
@click="(e)=>{e.stopPropagation()}"
|
|
7
|
+
>
|
|
8
|
+
<span
|
|
9
|
+
v-if="record[column.name] && column.isArray?.enabled"
|
|
10
|
+
v-for="foreignResource in record[column.name]"
|
|
11
|
+
class="rounded-md m-0.5 bg-lightAnnouncementBG dark:bg-darkAnnouncementBG text-lightAnnouncementText dark:text-darkAnnouncementText py-0.5 px-2.5 text-sm"
|
|
12
|
+
>
|
|
13
|
+
<RouterLink
|
|
14
|
+
class="font-medium text-lightSidebarText dark:text-darkSidebarText hover:brightness-110 whitespace-nowrap"
|
|
15
|
+
:to="{ name: 'resource-show', params: { primaryKey: foreignResource.pk, resourceId: column.foreignResource.resourceId || column.foreignResource.polymorphicResources.find((pr) => pr.whenValue === record[column.foreignResource.polymorphicOn]).resourceId } }"
|
|
16
|
+
>
|
|
17
|
+
{{ foreignResource.label }}
|
|
18
|
+
</RouterLink>
|
|
19
|
+
</span>
|
|
20
|
+
<RouterLink v-else-if="record[column.name]" class="font-medium text-lightPrimary dark:text-darkPrimary hover:brightness-110 whitespace-nowrap"
|
|
5
21
|
:to="{ name: 'resource-show', params: { primaryKey: record[column.name].pk, resourceId: column.foreignResource.resourceId || column.foreignResource.polymorphicResources.find((pr) => pr.whenValue === record[column.foreignResource.polymorphicOn]).resourceId } }">
|
|
6
22
|
{{ record[column.name].label }}
|
|
7
23
|
</RouterLink>
|
|
@@ -106,11 +106,18 @@ export interface IExpressHttpServer extends IHttpServer {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
|
|
109
|
-
export interface
|
|
109
|
+
export interface IAdminForthSingleFilter {
|
|
110
110
|
field: string;
|
|
111
|
-
operator: AdminForthFilterOperators
|
|
111
|
+
operator: AdminForthFilterOperators.EQ | AdminForthFilterOperators.NE
|
|
112
|
+
| AdminForthFilterOperators.GT | AdminForthFilterOperators.LT | AdminForthFilterOperators.GTE
|
|
113
|
+
| AdminForthFilterOperators.LTE | AdminForthFilterOperators.LIKE | AdminForthFilterOperators.ILIKE
|
|
114
|
+
| AdminForthFilterOperators.IN | AdminForthFilterOperators.NIN;
|
|
112
115
|
value: any;
|
|
113
116
|
}
|
|
117
|
+
export interface IAdminForthAndOrFilter {
|
|
118
|
+
operator: AdminForthFilterOperators.AND | AdminForthFilterOperators.OR;
|
|
119
|
+
subFilters: Array<IAdminForthAndOrFilter | IAdminForthSingleFilter>
|
|
120
|
+
}
|
|
114
121
|
|
|
115
122
|
export interface IAdminForthSort {
|
|
116
123
|
field: string,
|
|
@@ -185,7 +192,7 @@ export interface IAdminForthDataSourceConnector {
|
|
|
185
192
|
limit: number,
|
|
186
193
|
offset: number,
|
|
187
194
|
sort: IAdminForthSort[],
|
|
188
|
-
filters:
|
|
195
|
+
filters: IAdminForthAndOrFilter,
|
|
189
196
|
}): Promise<Array<any>>;
|
|
190
197
|
|
|
191
198
|
/**
|
|
@@ -193,7 +200,7 @@ export interface IAdminForthDataSourceConnector {
|
|
|
193
200
|
*/
|
|
194
201
|
getCount({ resource, filters }: {
|
|
195
202
|
resource: AdminForthResource,
|
|
196
|
-
filters:
|
|
203
|
+
filters: IAdminForthAndOrFilter,
|
|
197
204
|
}): Promise<number>;
|
|
198
205
|
|
|
199
206
|
/**
|
|
@@ -206,9 +213,9 @@ export interface IAdminForthDataSourceConnector {
|
|
|
206
213
|
|
|
207
214
|
|
|
208
215
|
/**
|
|
209
|
-
* Used to create record in database.
|
|
216
|
+
* Used to create record in database. Should return value of primary key column of created record.
|
|
210
217
|
*/
|
|
211
|
-
createRecordOriginalValues({ resource, record }: { resource: AdminForthResource, record: any }): Promise<
|
|
218
|
+
createRecordOriginalValues({ resource, record }: { resource: AdminForthResource, record: any }): Promise<string>;
|
|
212
219
|
|
|
213
220
|
/**
|
|
214
221
|
* Update record in database. newValues might have not all fields in record, but only changed ones.
|
|
@@ -235,7 +242,7 @@ export interface IAdminForthDataSourceConnectorBase extends IAdminForthDataSourc
|
|
|
235
242
|
limit: number,
|
|
236
243
|
offset: number,
|
|
237
244
|
sort: IAdminForthSort[],
|
|
238
|
-
filters:
|
|
245
|
+
filters: IAdminForthAndOrFilter,
|
|
239
246
|
getTotals?: boolean,
|
|
240
247
|
}): Promise<{ data: Array<any>, total: number }>;
|
|
241
248
|
|
|
@@ -720,6 +727,33 @@ interface AdminForthInputConfigCustomization {
|
|
|
720
727
|
}
|
|
721
728
|
}
|
|
722
729
|
|
|
730
|
+
export interface AdminForthActionInput {
|
|
731
|
+
name: string;
|
|
732
|
+
showIn?: {
|
|
733
|
+
list?: boolean,
|
|
734
|
+
showButton?: boolean,
|
|
735
|
+
showThreeDotsMenu?: boolean,
|
|
736
|
+
};
|
|
737
|
+
allowed?: (params: {
|
|
738
|
+
adminUser: AdminUser;
|
|
739
|
+
standardAllowedActions: AllowedActions;
|
|
740
|
+
}) => boolean;
|
|
741
|
+
url?: string;
|
|
742
|
+
action?: (params: {
|
|
743
|
+
adminforth: IAdminForth;
|
|
744
|
+
resource: AdminForthResource;
|
|
745
|
+
recordId: string;
|
|
746
|
+
adminUser: AdminUser;
|
|
747
|
+
extra?: HttpExtra;
|
|
748
|
+
tr: Function;
|
|
749
|
+
}) => Promise<{
|
|
750
|
+
ok: boolean;
|
|
751
|
+
error?: string;
|
|
752
|
+
message?: string;
|
|
753
|
+
}>;
|
|
754
|
+
icon?: string;
|
|
755
|
+
id?: string;
|
|
756
|
+
}
|
|
723
757
|
|
|
724
758
|
export interface AdminForthResourceInput extends Omit<AdminForthResourceInputCommon, 'columns' | 'hooks' | 'options'> {
|
|
725
759
|
|
|
@@ -1012,36 +1046,42 @@ export interface AdminForthConfig extends Omit<AdminForthInputConfig, 'customiza
|
|
|
1012
1046
|
// return { field: field, operator: 'eq', value: value }. They should be exported with Filters namespace so I can import Filters from this file
|
|
1013
1047
|
// and use Filters.EQ(field, value) in my code
|
|
1014
1048
|
|
|
1015
|
-
export type FDataFilter = (field: string, value: any) =>
|
|
1049
|
+
export type FDataFilter = (field: string, value: any) => IAdminForthSingleFilter;
|
|
1016
1050
|
|
|
1017
1051
|
export class Filters {
|
|
1018
|
-
static EQ(field: string, value: any):
|
|
1052
|
+
static EQ(field: string, value: any): IAdminForthSingleFilter {
|
|
1019
1053
|
return { field, operator: AdminForthFilterOperators.EQ, value };
|
|
1020
1054
|
}
|
|
1021
|
-
static NEQ(field: string, value: any):
|
|
1055
|
+
static NEQ(field: string, value: any): IAdminForthSingleFilter {
|
|
1022
1056
|
return { field, operator: AdminForthFilterOperators.NE, value };
|
|
1023
1057
|
}
|
|
1024
|
-
static GT(field: string, value: any):
|
|
1058
|
+
static GT(field: string, value: any): IAdminForthSingleFilter {
|
|
1025
1059
|
return { field, operator: AdminForthFilterOperators.GT, value };
|
|
1026
1060
|
}
|
|
1027
|
-
static GTE(field: string, value: any):
|
|
1061
|
+
static GTE(field: string, value: any): IAdminForthSingleFilter {
|
|
1028
1062
|
return { field, operator: AdminForthFilterOperators.GTE, value };
|
|
1029
1063
|
}
|
|
1030
|
-
static LT(field: string, value: any):
|
|
1064
|
+
static LT(field: string, value: any): IAdminForthSingleFilter {
|
|
1031
1065
|
return { field, operator: AdminForthFilterOperators.LT, value };
|
|
1032
1066
|
}
|
|
1033
|
-
static LTE(field: string, value: any):
|
|
1067
|
+
static LTE(field: string, value: any): IAdminForthSingleFilter {
|
|
1034
1068
|
return { field, operator: AdminForthFilterOperators.LTE, value };
|
|
1035
1069
|
}
|
|
1036
|
-
static IN(field: string, value: any):
|
|
1070
|
+
static IN(field: string, value: any): IAdminForthSingleFilter {
|
|
1037
1071
|
return { field, operator: AdminForthFilterOperators.IN, value };
|
|
1038
1072
|
}
|
|
1039
|
-
static NOT_IN(field: string, value: any):
|
|
1073
|
+
static NOT_IN(field: string, value: any): IAdminForthSingleFilter {
|
|
1040
1074
|
return { field, operator: AdminForthFilterOperators.NIN, value };
|
|
1041
1075
|
}
|
|
1042
|
-
static LIKE(field: string, value: any):
|
|
1076
|
+
static LIKE(field: string, value: any): IAdminForthSingleFilter {
|
|
1043
1077
|
return { field, operator: AdminForthFilterOperators.LIKE, value };
|
|
1044
1078
|
}
|
|
1079
|
+
static AND(subFilters: Array<IAdminForthSingleFilter | IAdminForthAndOrFilter>): IAdminForthAndOrFilter {
|
|
1080
|
+
return { operator: AdminForthFilterOperators.AND, subFilters };
|
|
1081
|
+
}
|
|
1082
|
+
static OR(subFilters: Array<IAdminForthSingleFilter | IAdminForthAndOrFilter>): IAdminForthAndOrFilter {
|
|
1083
|
+
return { operator: AdminForthFilterOperators.OR, subFilters };
|
|
1084
|
+
}
|
|
1045
1085
|
}
|
|
1046
1086
|
|
|
1047
1087
|
export type FDataSort = (field: string, direction: AdminForthSortDirections) => IAdminForthSort;
|
|
@@ -1056,11 +1096,11 @@ export class Sorts {
|
|
|
1056
1096
|
}
|
|
1057
1097
|
|
|
1058
1098
|
export interface IOperationalResource {
|
|
1059
|
-
get: (filter:
|
|
1099
|
+
get: (filter: IAdminForthSingleFilter | IAdminForthAndOrFilter | Array<IAdminForthSingleFilter | IAdminForthAndOrFilter>) => Promise<any | null>;
|
|
1060
1100
|
|
|
1061
|
-
list: (filter:
|
|
1101
|
+
list: (filter: IAdminForthSingleFilter | IAdminForthAndOrFilter | Array<IAdminForthSingleFilter | IAdminForthAndOrFilter>, limit?: number, offset?: number, sort?: IAdminForthSort | IAdminForthSort[]) => Promise<any[]>;
|
|
1062
1102
|
|
|
1063
|
-
count: (filter:
|
|
1103
|
+
count: (filter: IAdminForthSingleFilter | IAdminForthAndOrFilter | Array<IAdminForthSingleFilter | IAdminForthAndOrFilter> | undefined) => Promise<number>;
|
|
1064
1104
|
|
|
1065
1105
|
create: (record: any) => Promise<{ ok: boolean; createdRecord: any; error?: string; }>;
|
|
1066
1106
|
|
|
@@ -1141,10 +1181,38 @@ export interface ResourceOptionsInput extends Omit<AdminForthResourceCommon['opt
|
|
|
1141
1181
|
*
|
|
1142
1182
|
*/
|
|
1143
1183
|
allowedActions?: AllowedActionsInput,
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* Array of actions which will be displayed in the resource.
|
|
1187
|
+
*
|
|
1188
|
+
* Example:
|
|
1189
|
+
*
|
|
1190
|
+
* ```ts
|
|
1191
|
+
* actions: [
|
|
1192
|
+
* {
|
|
1193
|
+
* name: 'Auto submit',
|
|
1194
|
+
* allowed: ({ adminUser, standardAllowedActions }) => {
|
|
1195
|
+
* return adminUser.dbUser.role === 'superadmin';
|
|
1196
|
+
* },
|
|
1197
|
+
* action: ({ adminUser, resource, recordId, adminforth, extra, tr }) => {
|
|
1198
|
+
* console.log("auto submit", recordId, adminUser);
|
|
1199
|
+
* return { ok: true, successMessage: "Auto submitted" };
|
|
1200
|
+
* },
|
|
1201
|
+
* showIn: {
|
|
1202
|
+
* list: true,
|
|
1203
|
+
* showButton: true,
|
|
1204
|
+
* showThreeDotsMenu: true,
|
|
1205
|
+
* },
|
|
1206
|
+
* },
|
|
1207
|
+
* ]
|
|
1208
|
+
* ```
|
|
1209
|
+
*/
|
|
1210
|
+
actions?: Array<AdminForthActionInput>,
|
|
1144
1211
|
};
|
|
1145
1212
|
|
|
1146
1213
|
export interface ResourceOptions extends Omit<ResourceOptionsInput, 'allowedActions'> {
|
|
1147
1214
|
allowedActions: AllowedActions,
|
|
1215
|
+
actions?: Array<AdminForthActionInput>,
|
|
1148
1216
|
}
|
|
1149
1217
|
|
|
1150
1218
|
/**
|
|
@@ -28,6 +28,8 @@ export enum AdminForthFilterOperators {
|
|
|
28
28
|
ILIKE = 'ilike',
|
|
29
29
|
IN = 'in',
|
|
30
30
|
NIN = 'nin',
|
|
31
|
+
AND = 'and',
|
|
32
|
+
OR = 'or',
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
export enum AdminForthSortDirections {
|
|
@@ -50,6 +52,7 @@ export enum ActionCheckSource {
|
|
|
50
52
|
CreateRequest = 'createRequest',
|
|
51
53
|
DeleteRequest = 'deleteRequest',
|
|
52
54
|
BulkActionRequest = 'bulkActionRequest',
|
|
55
|
+
CustomActionRequest = 'customActionRequest',
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
export enum AllowedActionsEnum {
|
|
@@ -91,7 +94,14 @@ export interface AdminForthBulkActionCommon {
|
|
|
91
94
|
* Label for action button which will be displayed in the list view
|
|
92
95
|
*/
|
|
93
96
|
label: string,
|
|
94
|
-
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Bulk Action button state 'danger'|success|'active',
|
|
100
|
+
* * 'danger' - red button
|
|
101
|
+
* * 'success' - green button
|
|
102
|
+
* * 'active' - blue button
|
|
103
|
+
**/
|
|
104
|
+
state?: 'danger' | 'success' | 'active';
|
|
95
105
|
|
|
96
106
|
/**
|
|
97
107
|
* Icon for action button which will be displayed in the list view
|
|
@@ -357,18 +367,22 @@ export interface AdminForthResourceInputCommon {
|
|
|
357
367
|
fieldGroups?: {
|
|
358
368
|
groupName: string;
|
|
359
369
|
columns: string[];
|
|
370
|
+
noTitle?: boolean;
|
|
360
371
|
}[];
|
|
361
372
|
createFieldGroups?: {
|
|
362
373
|
groupName: string;
|
|
363
374
|
columns: string[];
|
|
375
|
+
noTitle?: boolean;
|
|
364
376
|
}[];
|
|
365
377
|
editFieldGroups?: {
|
|
366
378
|
groupName: string;
|
|
367
379
|
columns: string[];
|
|
380
|
+
noTitle?: boolean;
|
|
368
381
|
}[];
|
|
369
382
|
showFieldGroups?: {
|
|
370
383
|
groupName: string;
|
|
371
384
|
columns: string[];
|
|
385
|
+
noTitle?: boolean;
|
|
372
386
|
}[];
|
|
373
387
|
|
|
374
388
|
/**
|
|
@@ -440,6 +454,7 @@ export interface AdminForthResourceInputCommon {
|
|
|
440
454
|
bottom?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
441
455
|
threeDotsDropdownItems?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
442
456
|
customActionIcons?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
457
|
+
tableBodyStart?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
443
458
|
},
|
|
444
459
|
|
|
445
460
|
/**
|
|
@@ -551,6 +566,11 @@ export interface AdminForthForeignResourceCommon {
|
|
|
551
566
|
unsetLabel?: string,
|
|
552
567
|
}
|
|
553
568
|
|
|
569
|
+
export type FillOnCreateFunction = (params: {
|
|
570
|
+
initialRecord: any,
|
|
571
|
+
adminUser: AdminUser,
|
|
572
|
+
}) => any;
|
|
573
|
+
|
|
554
574
|
/**
|
|
555
575
|
* Column describes one field in the table or collection in database.
|
|
556
576
|
*/
|
|
@@ -681,7 +701,7 @@ export interface AdminForthResourceColumnInputCommon {
|
|
|
681
701
|
/**
|
|
682
702
|
* Called on the backend when the record is saved to a database. Value returned by `fillOnCreate` will be saved to the database.
|
|
683
703
|
*/
|
|
684
|
-
fillOnCreate?:
|
|
704
|
+
fillOnCreate?: FillOnCreateFunction,
|
|
685
705
|
|
|
686
706
|
/**
|
|
687
707
|
* Single value that will be substituted in create form. User can change it before saving the record.
|
|
@@ -805,6 +825,10 @@ export interface AdminForthResourceColumnInputCommon {
|
|
|
805
825
|
* If false - will force EQ operator for filter instead of ILIKE.
|
|
806
826
|
*/
|
|
807
827
|
substringSearch?: boolean,
|
|
828
|
+
/**
|
|
829
|
+
* Boolean value that determines what select input type to display on filter page.
|
|
830
|
+
*/
|
|
831
|
+
multiselect?: boolean,
|
|
808
832
|
},
|
|
809
833
|
|
|
810
834
|
/**
|
|
@@ -107,7 +107,11 @@ const editableRecord = computed(() => {
|
|
|
107
107
|
}
|
|
108
108
|
coreStore.resource.columns.forEach(column => {
|
|
109
109
|
if (column.foreignResource) {
|
|
110
|
-
|
|
110
|
+
if (column.isArray?.enabled) {
|
|
111
|
+
newRecord[column.name] = newRecord[column.name]?.map(fr => fr.pk);
|
|
112
|
+
} else {
|
|
113
|
+
newRecord[column.name] = newRecord[column.name]?.pk;
|
|
114
|
+
}
|
|
111
115
|
}
|
|
112
116
|
});
|
|
113
117
|
return newRecord;
|
|
@@ -145,7 +149,7 @@ async function saveRecord() {
|
|
|
145
149
|
|
|
146
150
|
const column = coreStore.resource.columns.find((c) => c.name === key);
|
|
147
151
|
if (column?.foreignResource) {
|
|
148
|
-
columnIsUpdated = record.value[key] !== coreStore.record[key]
|
|
152
|
+
columnIsUpdated = record.value[key] !== coreStore.record[key]?.pk;
|
|
149
153
|
}
|
|
150
154
|
|
|
151
155
|
if (columnIsUpdated) {
|
|
@@ -172,7 +176,7 @@ async function saveRecord() {
|
|
|
172
176
|
});
|
|
173
177
|
}
|
|
174
178
|
saving.value = false;
|
|
175
|
-
router.push({ name: 'resource-show', params: { resourceId: route.params.resourceId, primaryKey:
|
|
179
|
+
router.push({ name: 'resource-show', params: { resourceId: route.params.resourceId, primaryKey: resp.recordId } });
|
|
176
180
|
}
|
|
177
181
|
|
|
178
182
|
</script>
|
|
@@ -38,8 +38,10 @@
|
|
|
38
38
|
:key="action.id"
|
|
39
39
|
@click="startBulkAction(action.id)"
|
|
40
40
|
class="flex gap-1 items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-default border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
|
41
|
-
:class="{
|
|
42
|
-
|
|
41
|
+
:class="{
|
|
42
|
+
'bg-red-100 text-red-800 border-red-400 dark:bg-red-700 dark:text-red-400 dark:border-red-400':action.state==='danger',
|
|
43
|
+
'bg-green-100 text-green-800 border-green-400 dark:bg-green-700 dark:text-green-400 dark:border-green-400':action.state==='success',
|
|
44
|
+
'bg-lightPrimaryOpacity text-lightPrimary border-blue-400 dark:bg-blue-700 dark:text-blue-400 dark:border-blue-400':action.state==='active',
|
|
43
45
|
}"
|
|
44
46
|
>
|
|
45
47
|
<component
|
|
@@ -114,6 +116,7 @@
|
|
|
114
116
|
:totalRows="totalRows"
|
|
115
117
|
:checkboxes="checkboxes"
|
|
116
118
|
:customActionsInjection="coreStore.resourceOptions?.pageInjections?.list?.customActionIcons"
|
|
119
|
+
:tableBodyStartInjection="coreStore.resourceOptions?.pageInjections?.list?.tableBodyStart"
|
|
117
120
|
/>
|
|
118
121
|
|
|
119
122
|
<component
|
|
@@ -10,6 +10,22 @@
|
|
|
10
10
|
:adminUser="coreStore.adminUser"
|
|
11
11
|
/>
|
|
12
12
|
<BreadcrumbsWithButtons>
|
|
13
|
+
<template v-if="coreStore.resource?.options?.actions">
|
|
14
|
+
<button
|
|
15
|
+
v-for="action in coreStore.resource.options.actions.filter(a => a.showIn?.showButton)"
|
|
16
|
+
:key="action.id"
|
|
17
|
+
@click="startCustomAction(action.id)"
|
|
18
|
+
:disabled="actionLoadingStates[action.id]"
|
|
19
|
+
class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-default border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
|
20
|
+
>
|
|
21
|
+
<component
|
|
22
|
+
v-if="action.icon"
|
|
23
|
+
:is="getIcon(action.icon)"
|
|
24
|
+
class="w-4 h-4 me-2 text-lightPrimary dark:text-darkPrimary"
|
|
25
|
+
/>
|
|
26
|
+
{{ action.name }}
|
|
27
|
+
</button>
|
|
28
|
+
</template>
|
|
13
29
|
<RouterLink v-if="coreStore.resource?.options?.allowedActions?.create"
|
|
14
30
|
:to="{ name: 'resource-create', params: { resourceId: $route.params.resourceId } }"
|
|
15
31
|
class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
|
|
@@ -34,6 +50,7 @@
|
|
|
34
50
|
|
|
35
51
|
<ThreeDotsMenu
|
|
36
52
|
:threeDotsDropdownItems="coreStore.resourceOptions?.pageInjections?.show?.threeDotsDropdownItems"
|
|
53
|
+
:customActions="customActions"
|
|
37
54
|
></ThreeDotsMenu>
|
|
38
55
|
</BreadcrumbsWithButtons>
|
|
39
56
|
|
|
@@ -66,11 +83,12 @@
|
|
|
66
83
|
:record="coreStore.record"
|
|
67
84
|
/>
|
|
68
85
|
</div>
|
|
69
|
-
<template v-else>
|
|
86
|
+
<template v-else>
|
|
70
87
|
<template v-for="group in groups" :key="group.groupName">
|
|
71
88
|
<ShowTable
|
|
72
89
|
:columns="group.columns"
|
|
73
90
|
:groupName="group.groupName"
|
|
91
|
+
:noTitle="group.noTitle"
|
|
74
92
|
:resource="coreStore.resource"
|
|
75
93
|
:record="coreStore.record"
|
|
76
94
|
/>
|
|
@@ -121,6 +139,7 @@ import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
|
|
|
121
139
|
import ShowTable from '@/components/ShowTable.vue';
|
|
122
140
|
import adminforth from "@/adminforth";
|
|
123
141
|
import { useI18n } from 'vue-i18n';
|
|
142
|
+
import { getIcon } from '@/utils';
|
|
124
143
|
|
|
125
144
|
const route = useRoute();
|
|
126
145
|
const router = useRouter();
|
|
@@ -128,6 +147,12 @@ const loading = ref(true);
|
|
|
128
147
|
const { t } = useI18n();
|
|
129
148
|
const coreStore = useCoreStore();
|
|
130
149
|
|
|
150
|
+
const actionLoadingStates = ref({});
|
|
151
|
+
|
|
152
|
+
const customActions = computed(() => {
|
|
153
|
+
return coreStore.resource?.options?.actions?.filter(a => a.showIn?.showThreeDotsMenu) || [];
|
|
154
|
+
});
|
|
155
|
+
|
|
131
156
|
onMounted(async () => {
|
|
132
157
|
loading.value = true;
|
|
133
158
|
await coreStore.fetchResourceFull({
|
|
@@ -206,4 +231,54 @@ async function deleteRecord(row) {
|
|
|
206
231
|
|
|
207
232
|
}
|
|
208
233
|
|
|
234
|
+
async function startCustomAction(actionId) {
|
|
235
|
+
actionLoadingStates.value[actionId] = true;
|
|
236
|
+
|
|
237
|
+
const data = await callAdminForthApi({
|
|
238
|
+
path: '/start_custom_action',
|
|
239
|
+
method: 'POST',
|
|
240
|
+
body: {
|
|
241
|
+
resourceId: route.params.resourceId,
|
|
242
|
+
actionId: actionId,
|
|
243
|
+
recordId: route.params.primaryKey
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
actionLoadingStates.value[actionId] = false;
|
|
248
|
+
|
|
249
|
+
if (data?.redirectUrl) {
|
|
250
|
+
// Check if the URL should open in a new tab
|
|
251
|
+
if (data.redirectUrl.includes('target=_blank')) {
|
|
252
|
+
window.open(data.redirectUrl.replace('&target=_blank', '').replace('?target=_blank', ''), '_blank');
|
|
253
|
+
} else {
|
|
254
|
+
// Navigate within the app
|
|
255
|
+
if (data.redirectUrl.startsWith('http')) {
|
|
256
|
+
window.location.href = data.redirectUrl;
|
|
257
|
+
} else {
|
|
258
|
+
router.push(data.redirectUrl);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (data?.ok) {
|
|
265
|
+
await coreStore.fetchRecord({
|
|
266
|
+
resourceId: route.params.resourceId,
|
|
267
|
+
primaryKey: route.params.primaryKey,
|
|
268
|
+
source: 'show',
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
if (data.successMessage) {
|
|
272
|
+
adminforth.alert({
|
|
273
|
+
message: data.successMessage,
|
|
274
|
+
variant: 'success'
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (data?.error) {
|
|
280
|
+
showErrorTost(data.error);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
209
284
|
</script>
|