adminforth 1.1.13 → 1.1.14

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.
Files changed (44) hide show
  1. package/auth.ts +17 -3
  2. package/dist/auth.js +32 -18
  3. package/dist/index.js +99 -58
  4. package/dist/modules/codeInjector.js +0 -1
  5. package/dist/modules/utils.js +2 -12
  6. package/dist/plugins/AccessControl/index.js +66 -0
  7. package/dist/plugins/AccessControl/types.js +1 -0
  8. package/dist/plugins/ForeignInlineListPlugin/index.js +1 -1
  9. package/dist/servers/express.js +2 -2
  10. package/dist/spa/spa/src/App.vue +7 -3
  11. package/dist/spa/spa/src/components/Toast.vue +5 -4
  12. package/dist/spa/spa/src/components/ValueRenderer.vue +1 -1
  13. package/dist/spa/spa/src/composables/useStores.ts +2 -1
  14. package/dist/spa/spa/src/router/index.ts +13 -1
  15. package/dist/spa/spa/src/stores/core.ts +13 -11
  16. package/dist/spa/spa/src/stores/user.ts +50 -0
  17. package/dist/spa/spa/src/utils.ts +8 -3
  18. package/dist/spa/spa/src/views/CreateView.vue +7 -0
  19. package/dist/spa/spa/src/views/EditView.vue +8 -1
  20. package/dist/spa/spa/src/views/ListView.vue +10 -1
  21. package/dist/types/AdminForthConfig.js +9 -0
  22. package/dist/types/FrontendAPI.js +4 -4
  23. package/index.ts +91 -42
  24. package/modules/codeInjector.ts +0 -1
  25. package/modules/utils.ts +2 -10
  26. package/package.json +1 -1
  27. package/plugins/AccessControl/index.ts +83 -0
  28. package/plugins/AccessControl/types.ts +14 -0
  29. package/plugins/ForeignInlineListPlugin/custom/InlineList.vue +11 -0
  30. package/plugins/ForeignInlineListPlugin/index.ts +2 -1
  31. package/servers/express.ts +2 -2
  32. package/spa/src/App.vue +7 -3
  33. package/spa/src/components/Toast.vue +5 -4
  34. package/spa/src/components/ValueRenderer.vue +1 -1
  35. package/spa/src/composables/useStores.ts +2 -1
  36. package/spa/src/router/index.ts +13 -1
  37. package/spa/src/stores/core.ts +13 -11
  38. package/spa/src/stores/user.ts +50 -0
  39. package/spa/src/utils.ts +8 -3
  40. package/spa/src/views/CreateView.vue +7 -0
  41. package/spa/src/views/EditView.vue +8 -1
  42. package/spa/src/views/ListView.vue +10 -1
  43. package/types/AdminForthConfig.ts +73 -21
  44. package/types/FrontendAPI.ts +11 -5
@@ -0,0 +1,83 @@
1
+ import {
2
+ AdminForthResource, AdminForthClass, BeforeDataSourceRequestFunction,
3
+ AllowedActionsEnum,
4
+ BeforeSaveFunction
5
+ } from "../../types/AdminForthConfig.js";
6
+ import AdminForthPlugin from "../base.js";
7
+ import { PluginOptions } from "./types.js";
8
+
9
+
10
+
11
+ export default class AccessControlPlugin extends AdminForthPlugin {
12
+
13
+ options: PluginOptions;
14
+ adminforth: AdminForthClass;
15
+
16
+ constructor(options: PluginOptions) {
17
+ super(options, import.meta.url);
18
+ this.options = options;
19
+ }
20
+
21
+ static defaultError = 'Sorry, you do not have access to this resource.'
22
+
23
+
24
+ modifyResourceConfig(adminforth: AdminForthClass, resourceConfig: AdminForthResource) {
25
+ super.modifyResourceConfig(adminforth, resourceConfig);
26
+ this.adminforth = adminforth;
27
+
28
+ if (!resourceConfig.hooks) {
29
+ resourceConfig.hooks = {};
30
+ }
31
+
32
+ const checkAccess = async (adminUser: any, action: AllowedActionsEnum, meta: any) => {
33
+ const hasAccessOrError = await this.options.hasAccess(adminUser, action, meta);
34
+ if (hasAccessOrError === true) {
35
+ return { ok: true };
36
+ } else {
37
+ return { ok: false, error: hasAccessOrError || AccessControlPlugin.defaultError };
38
+ }
39
+ }
40
+
41
+ const bindHookCheck = (action: AllowedActionsEnum, hookName: string) => {
42
+ if (!resourceConfig.hooks[action]) {
43
+ resourceConfig.hooks[action] = {};
44
+ }
45
+ if (!resourceConfig.hooks[action][hookName]) {
46
+ resourceConfig.hooks[action][hookName] = [];
47
+ }
48
+ if (hookName === 'beforeDatasourceRequest') {
49
+ (resourceConfig.hooks[action][hookName] as Array<BeforeDataSourceRequestFunction>).unshift(
50
+ async ({adminUser, query}: {resource: AdminForthResource, adminUser: any, query: any}) => {
51
+ return checkAccess(adminUser, action, { query });
52
+ }
53
+ );
54
+ } else {
55
+ (resourceConfig.hooks[action][hookName] as Array<BeforeSaveFunction>).unshift(
56
+ async ({adminUser, record}: {resource: AdminForthResource, adminUser: any, record: any}) => {
57
+ return checkAccess(adminUser, action, { record });
58
+ }
59
+ );
60
+ }
61
+ }
62
+
63
+ // List check
64
+ bindHookCheck(AllowedActionsEnum.list, 'beforeDatasourceRequest');
65
+
66
+ // Show check
67
+ bindHookCheck(AllowedActionsEnum.show, 'beforeDatasourceRequest');
68
+
69
+ // Edit check
70
+ bindHookCheck(AllowedActionsEnum.edit, 'beforeDatasourceRequest');
71
+
72
+ // create check
73
+ bindHookCheck(AllowedActionsEnum.create, 'beforeSave');
74
+
75
+ // edit check
76
+ bindHookCheck(AllowedActionsEnum.edit, 'beforeSave');
77
+
78
+ // delete check
79
+ bindHookCheck(AllowedActionsEnum.delete, 'beforeSave');
80
+
81
+ console.log('resourceConfig', resourceConfig);
82
+ }
83
+ }
@@ -0,0 +1,14 @@
1
+
2
+ import { AllowedActionsEnum, AdminUser } from '../../types/AdminForthConfig.js';
3
+
4
+
5
+ export type PluginOptions = {
6
+ /**
7
+ * Called to check if user has access to a page
8
+ * @param user - The user object
9
+ * @param page - The page to check access for
10
+ * @param meta - The meta object containing query for list/show or record for save/edit
11
+ * @returns true if user has access, false or a string with an error message
12
+ */
13
+ hasAccess: (user: AdminUser, page: AllowedActionsEnum, meta: any) => Promise<boolean | string>;
14
+ }
@@ -208,6 +208,17 @@ async function getList() {
208
208
  }
209
209
  });
210
210
 
211
+ if (data.error) {
212
+ window.adminforth.alert({
213
+ message: data.error,
214
+ variant: 'danger',
215
+ timeout: 'unlimited',
216
+ });
217
+ rows.value = [];
218
+ totalRows.value = 0;
219
+ return;
220
+ }
221
+
211
222
  rows.value = data.data?.map(row => {
212
223
  row._primaryKeyValue = row[listResource.value.columns.find(c => c.primaryKey).name];
213
224
  return row;
@@ -23,7 +23,8 @@ export default class ForeignInlineListPlugin extends AdminForthPlugin {
23
23
  return { error: `Resource ${this.options.foreignResourceId} not found` };
24
24
  }
25
25
  // exclude "plugins" key
26
- const resourceCopy = { ...resource, plugins: undefined };
26
+ const resourceCopy = JSON.parse(JSON.stringify({ ...resource, plugins: undefined }));
27
+
27
28
  if (this.options.modifyTableResourceConfig) {
28
29
  this.options.modifyTableResourceConfig(resourceCopy);
29
30
  }
@@ -157,7 +157,7 @@ class ExpressServer implements ExpressHttpServer {
157
157
  res.status(401).send('Unauthorized by AdminForth');
158
158
  return
159
159
  }
160
- const adminforthUser = this.adminforth.auth.verify(jwt);
160
+ const adminforthUser = await this.adminforth.auth.verify(jwt);
161
161
  if (!adminforthUser) {
162
162
  res.status(401).send('Unauthorized by AdminForth');
163
163
  } else {
@@ -201,7 +201,7 @@ class ExpressServer implements ExpressHttpServer {
201
201
  this.message = message;
202
202
  }
203
203
  };
204
- const input = { body, query, headers, cookies, response, _raw_express_req: req, _raw_express_res: res};
204
+ const input = { body, query, headers, cookies, adminUser, response, _raw_express_req: req, _raw_express_res: res};
205
205
 
206
206
  let output;
207
207
  try {
package/spa/src/App.vue CHANGED
@@ -64,7 +64,6 @@
64
64
  aria-label="Sidebar"
65
65
  >
66
66
  <div class="h-full px-3 pb-4 overflow-y-auto bg-nav-menu-bg dark:bg-gray-800 border-r">
67
-
68
67
  <div class="flex ms-2 md:me-24 m-4 ">
69
68
  <img :src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')" :alt="`${ coreStore.config?.brandName } Logo`" class="h-8 me-3" />
70
69
  <span class="self-center text-header-text-size font-semibold sm:text-header-text-size whitespace-nowrap dark:text-header-text text-header-logo-color">
@@ -168,6 +167,7 @@ import { RouterLink, RouterView } from 'vue-router';
168
167
  import { initFlowbite } from 'flowbite'
169
168
  import './index.scss'
170
169
  import { useCoreStore } from '@/stores/core';
170
+ import { useUserStore } from '@/stores/user';
171
171
  import {useModalStore} from '@/stores/modal';
172
172
  import { IconMoonSolid, IconSunSolid } from '@iconify-prerendered/vue-flowbite';
173
173
  import AcceptModal from './components/AcceptModal.vue';
@@ -184,6 +184,7 @@ import { FrontendAPI } from '@/composables/useStores'
184
184
  const coreStore = useCoreStore();
185
185
  const modalStore = useModalStore();
186
186
  const toastStore = useToastStore();
187
+ const userStore = useUserStore();
187
188
  const frontendApi = new FrontendAPI();
188
189
  frontendApi.init();
189
190
  const splitAtLast = (str: string, separator: string) => {
@@ -221,8 +222,9 @@ if (opened.value.includes(label)) {
221
222
  }
222
223
 
223
224
  async function logout() {
224
- await coreStore.logout();
225
- router.push({ name: 'login' });
225
+ userStore.unauthorize();
226
+ await userStore.logout();
227
+ router.push({ name: 'login' })
226
228
  }
227
229
 
228
230
 
@@ -244,6 +246,8 @@ async function loadMenu() {
244
246
  });
245
247
  }
246
248
 
249
+
250
+
247
251
  watch(route, () => {
248
252
  title.value = `${coreStore.config?.title || coreStore.config?.brandName || 'Adminforth'} | ${ Object.values(route.params)[0] || route.meta.title || ' '}`;
249
253
  useHead({
@@ -46,16 +46,17 @@ const props = defineProps<{
46
46
  toast:{message: string;
47
47
  variant: string;
48
48
  id: string;
49
- duration?: number;}
49
+ timeout?: number|'unlimited';}
50
50
  }>();
51
51
  function closeToast() {
52
52
  emit('close');
53
53
  }
54
54
 
55
55
  onMounted(() => {
56
- setTimeout(() => {
57
- emit('close');
58
- }, props.toast.duration || 5000 );
56
+ if (props.toast.timeout === 'unlimited') return;
57
+ else {
58
+ setTimeout(() => {emit('close');}, props.toast.timeout || 10000 );
59
+ }
59
60
  });
60
61
 
61
62
  </script>
@@ -41,7 +41,7 @@ import { useRoute, useRouter } from 'vue-router';
41
41
  import { useCoreStore } from '@/stores/core';
42
42
 
43
43
  const coreStore = useCoreStore();
44
- const route = useRoute();
44
+ const route = useRoute();
45
45
 
46
46
 
47
47
  dayjs.extend(utc);
@@ -56,7 +56,8 @@ export class FrontendAPI implements FrontendAPIInterface {
56
56
  alert(params: AlertParams): void {
57
57
  this.toastStore.addToast({
58
58
  message: params.message,
59
- variant: params.variant
59
+ variant: params.variant,
60
+ timeout: params.timeout
60
61
  })
61
62
  }
62
63
 
@@ -1,5 +1,6 @@
1
1
  import { createRouter, createWebHistory } from 'vue-router'
2
2
  import ResourceParent from '@/views/ResourceParent.vue'
3
+ import { useUserStore } from '@/stores/user'
3
4
  /* IMPORTANT:ADMINFORTH ROUTES IMPORTS */
4
5
 
5
6
  const router = createRouter({
@@ -9,7 +10,15 @@ const router = createRouter({
9
10
  path: '/login',
10
11
  name: 'login',
11
12
  component: () => import('@/views/LoginView.vue'),
12
- meta: { title: 'login' }
13
+ meta: { title: 'login' },
14
+ beforeEnter: async (to, from, next) => {
15
+ const userStore = useUserStore()
16
+ if (await userStore.checkAuth(true)) {
17
+ next({ name: 'home' })
18
+ } else {
19
+ next()
20
+ }
21
+ }
13
22
  },
14
23
  {
15
24
  path: '/resource/:resourceId',
@@ -49,4 +58,7 @@ const router = createRouter({
49
58
  })
50
59
 
51
60
 
61
+
62
+
63
+
52
64
  export default router
@@ -8,7 +8,7 @@ export const useCoreStore = defineStore('core', () => {
8
8
  const resourceById: Ref<Object> = ref({});
9
9
  const menu = ref([]);
10
10
  const config = ref({});
11
- const record = ref({});
11
+ const record: Ref<any | null> = ref({});
12
12
  const resource: Ref<AdminForthResource | null> = ref(null);
13
13
 
14
14
  const resourceColumnsWithFilters = computed(() => {
@@ -67,9 +67,17 @@ export const useCoreStore = defineStore('core', () => {
67
67
  }
68
68
  });
69
69
 
70
- console.log('📦 record', respData);
71
- record.value = respData.data[0];
72
- resourceOptions.value = respData.options;
70
+ if (respData.error) {
71
+ window.adminforth.alert({
72
+ message: respData.error,
73
+ variant: 'danger',
74
+ timeout: 'unlimited'
75
+ });
76
+ record.value = {};
77
+ } else {
78
+ record.value = respData.data[0];
79
+ }
80
+
73
81
  }
74
82
 
75
83
  async function fetchResourceFull({ resourceId }: { resourceId: string }) {
@@ -103,12 +111,7 @@ export const useCoreStore = defineStore('core', () => {
103
111
  config.value = {...config.value, ...res};
104
112
  }
105
113
 
106
- async function logout() {
107
- await callAdminForthApi({
108
- path: '/logout',
109
- method: 'POST',
110
- });
111
- }
114
+
112
115
 
113
116
  const username = computed(() => {
114
117
  const usernameField = config.value.usernameField;
@@ -134,7 +137,6 @@ export const useCoreStore = defineStore('core', () => {
134
137
  fetchResourceFull,
135
138
  resourceColumnsError,
136
139
  resourceOptions,
137
- logout,
138
140
  resource,
139
141
  adminUser,
140
142
  resourceColumnsWithFilters
@@ -0,0 +1,50 @@
1
+ import {ref} from 'vue';
2
+ import {defineStore} from 'pinia';
3
+ import { callAdminForthApi } from '@/utils';
4
+
5
+ export const useUserStore = defineStore('user', () => {
6
+ const isAuthorized = ref(false);
7
+
8
+ function authorize() {
9
+ isAuthorized.value = true;
10
+ }
11
+
12
+ function unauthorize() {
13
+ isAuthorized.value = false;
14
+ }
15
+
16
+ async function logout() {
17
+ await callAdminForthApi({
18
+ path: '/logout',
19
+ method: 'POST',
20
+ });
21
+ }
22
+
23
+ async function checkAuth( skipApiCall = false){
24
+ if(isAuthorized.value) return true;
25
+ else {
26
+ if(skipApiCall) return false;
27
+ const resp = await callAdminForthApi({
28
+ path: '/check_auth',
29
+ method: 'POST',
30
+ });
31
+ if (resp.status !== 401) {
32
+ authorize();
33
+ return true;
34
+ }
35
+ else {
36
+ unauthorize();
37
+ return false;}
38
+ }
39
+
40
+ }
41
+
42
+ return {
43
+ isAuthorized,
44
+ authorize,
45
+ unauthorize,
46
+ checkAuth,
47
+ logout
48
+ }
49
+
50
+ });
package/spa/src/utils.ts CHANGED
@@ -4,6 +4,7 @@ import type { CoreConfig } from './spa_types/core';
4
4
  import router from "./router";
5
5
  import { useRouter } from 'vue-router';
6
6
  import { useCoreStore } from './stores/core';
7
+ import { useUserStore } from './stores/user';
7
8
 
8
9
  export async function callApi({path, method, body=undefined} ) {
9
10
  const options = {
@@ -16,14 +17,18 @@ export async function callApi({path, method, body=undefined} ) {
16
17
  const fullPath = `${import.meta.env.VITE_ADMINFORTH_PUBLIC_PATH || ''}${path}`;
17
18
  const r = await fetch(fullPath, options);
18
19
  if (r.status == 401) {
19
- console.log('router', router);
20
- router.push({name: 'login'});
20
+ useUserStore().unauthorize();
21
+ router.push({ name: 'login' });
21
22
  return null;
22
23
  }
23
24
  return await r.json();
24
25
  }
25
26
 
26
- export async function callAdminForthApi({ path, method, body=undefined }) {
27
+ export async function callAdminForthApi({ path, method, body=undefined }: {
28
+ path: string,
29
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE',
30
+ body?: any
31
+ }) {
27
32
  try {
28
33
  return callApi({path: `/adminapi/v1${path}`, method, body} );
29
34
  } catch (e) {
@@ -137,6 +137,13 @@ async function saveRecord() {
137
137
  record: record.value,
138
138
  },
139
139
  });
140
+ if (response.error) {
141
+ window.adminforth.alert({
142
+ message: resp.error,
143
+ variant: 'danger',
144
+ timeout: 'unlimited',
145
+ })
146
+ }
140
147
  saving.value = false;
141
148
  if (route.query.returnTo) {
142
149
  router.push(route.query.returnTo);
@@ -145,7 +145,7 @@ async function saveRecord() {
145
145
  }
146
146
  }
147
147
 
148
- await callAdminForthApi({
148
+ const resp = await callAdminForthApi({
149
149
  method: 'POST',
150
150
  path: `/update_record`,
151
151
  body: {
@@ -154,6 +154,13 @@ async function saveRecord() {
154
154
  record: updates,
155
155
  },
156
156
  });
157
+ if (resp.error) {
158
+ window.adminforth.alert({
159
+ message: resp.error,
160
+ variant: 'danger',
161
+ timeout: 'unlimited',
162
+ })
163
+ }
157
164
  saving.value = false;
158
165
  router.push({ name: 'resource-show', params: { resourceId: route.params.resourceId, primaryKey: coreStore.record[coreStore.primaryKey] } });
159
166
  }
@@ -183,7 +183,16 @@ async function getList() {
183
183
  sort: sort.value,
184
184
  }
185
185
  });
186
-
186
+ if (data.error) {
187
+ window.adminforth.alert({
188
+ message: data.error,
189
+ variant: 'danger',
190
+ timeout: 'unlimited',
191
+ });
192
+ rows.value = [];
193
+ totalRows.value = 0;
194
+ return;
195
+ }
187
196
  rows.value = data.data?.map(row => {
188
197
  row._primaryKeyValue = row[coreStore.resource.columns.find(c => c.primaryKey).name];
189
198
  return row;
@@ -26,12 +26,31 @@ export interface GenericHttpServer {
26
26
  */
27
27
  endpoint(options: {
28
28
  method: string,
29
+ noAuth?: boolean,
29
30
  path: string,
30
- handler: Function,
31
+ handler: (body: any, adminUser: any, query: {[key: string]: string}, headers: {[key: string]: string}, cookies: {[key: string]: string}, response: {
32
+ setHeader: (key: string, value: string) => void,
33
+ setStatus: (code: number, message: string) => void,
34
+ }) => void,
31
35
  }): void;
32
36
 
33
37
  }
34
38
 
39
+ export type AdminUser = {
40
+ /**
41
+ * primaryKey field value of user in table which is defined by {@link AdminForthConfig.auth.resourceId}
42
+ * or null if it is user logged in as {@link AdminForthConfig.rootUser}
43
+ */
44
+ pk: string | null,
45
+
46
+ /**
47
+ * Username which takend from {@link AdminForthConfig.auth.usernameField} field in user resource {@link AdminForthConfig.auth.resourceId}
48
+ */
49
+ username: string,
50
+ isRoot: boolean,
51
+ dbUser: any,
52
+ }
53
+
35
54
  export interface ExpressHttpServer extends GenericHttpServer {
36
55
 
37
56
  /**
@@ -67,7 +86,7 @@ export interface AdminForthClass {
67
86
 
68
87
  auth: {
69
88
 
70
- verify(jwt : string): any;
89
+ verify(jwt : string): Promise<any>;
71
90
  }
72
91
 
73
92
  /**
@@ -75,7 +94,6 @@ export interface AdminForthClass {
75
94
  */
76
95
  runningHotReload: boolean;
77
96
 
78
-
79
97
  /**
80
98
  * Connects to databases defined in datasources and fetches described resource columns to find out data types and constraints.
81
99
  * You must call this method as soon as possible after AdminForth class is instantiated.
@@ -182,6 +200,7 @@ export enum AdminForthResourcePages {
182
200
  edit = 'edit',
183
201
  create = 'create',
184
202
  filter = 'filter',
203
+
185
204
  }
186
205
 
187
206
 
@@ -400,6 +419,32 @@ export type AdminForthResourceColumn = {
400
419
  masked?: boolean,
401
420
  }
402
421
 
422
+
423
+ /**
424
+ * Modify query to change how data is fetched from database.
425
+ * Return ok: false and error: string to stop execution and show error message to user. Return ok: true to continue execution.
426
+ */
427
+ export type BeforeDataSourceRequestFunction = (params: {resource: AdminForthResource, adminUser: AdminUser, query: any}) => Promise<{ok: boolean, error?: string}>;
428
+
429
+ /**
430
+ * Modify response to change how data is returned after fetching from database.
431
+ * Return ok: false and error: string to stop execution and show error message to user. Return ok: true to continue execution.
432
+ */
433
+ export type AfterDataSourceResponseFunction = (params: {resource: AdminForthResource, adminUser: AdminUser, response: any}) => Promise<{ok: boolean, error?: string}>;
434
+
435
+ /**
436
+ * Modify record to change how data is saved to database.
437
+ * Return ok: false and error: string to stop execution and show error message to user. Return ok: true to continue execution.
438
+ */
439
+ export type BeforeSaveFunction = (params:{resource: AdminForthResource, adminUser: AdminUser, record: any}) => Promise<{ok: boolean, error?: string}>;
440
+
441
+ /**
442
+ * Modify record to change how data is saved to database.
443
+ * Return ok: false and error: string to stop execution and show error message to user. Return ok: true to continue execution.
444
+ */
445
+ export type AfterSaveFunction = (params: {resource: AdminForthResource, adminUser: AdminUser, record: any}) => Promise<{ok: boolean, error?: string}>;
446
+
447
+
403
448
  /**
404
449
  * Resource describes one table or collection in database.
405
450
  * AdminForth generates set of pages for 'list', 'show', 'edit', 'create', 'filter' operations for each resource.
@@ -456,24 +501,24 @@ export type AdminForthResource = {
456
501
  plugins?: Array<AdminForthPluginType>,
457
502
  hooks?: {
458
503
  show?: {
459
- beforeDatasourceRequest?: Function | Array<Function>,
460
- afterDatasourceResponse?: Function | Array<Function>,
504
+ beforeDatasourceRequest?: BeforeDataSourceRequestFunction | Array<BeforeDataSourceRequestFunction>,
505
+ afterDatasourceResponse?: AfterDataSourceResponseFunction | Array<AfterDataSourceResponseFunction>,
461
506
  },
462
507
  list?: {
463
- beforeDatasourceRequest?: Function | Array<Function>,
464
- afterDatasourceResponse?: Function | Array<Function>,
508
+ beforeDatasourceRequest?: BeforeDataSourceRequestFunction | Array<BeforeDataSourceRequestFunction>,
509
+ afterDatasourceResponse?: AfterDataSourceResponseFunction | Array<AfterDataSourceResponseFunction>,
465
510
  },
466
511
  create?: {
467
- beforeSave?: Function | Array<Function>,
468
- afterSave?: Function | Array<Function>,
512
+ beforeSave?: BeforeSaveFunction | Array<BeforeSaveFunction>,
513
+ afterSave?: AfterSaveFunction | Array<AfterSaveFunction>,
469
514
  },
470
515
  edit?: {
471
- beforeSave?: Function | Array<Function>,
472
- afterSave?: Function | Array<Function>,
516
+ beforeSave?: BeforeSaveFunction | Array<BeforeSaveFunction>,
517
+ afterSave?: AfterSaveFunction | Array<AfterSaveFunction>,
473
518
  },
474
519
  delete?: {
475
- beforeSave?: Function | Array<Function>,
476
- afterSave?: Function | Array<Function>,
520
+ beforeSave?: BeforeSaveFunction | Array<BeforeSaveFunction>,
521
+ afterSave?: BeforeSaveFunction | Array<BeforeSaveFunction>,
477
522
  },
478
523
  },
479
524
  options?: {
@@ -770,14 +815,21 @@ export type AdminForthConfig = {
770
815
  deleteConfirmation?: boolean,
771
816
 
772
817
  styles?: Object,
773
- }
818
+ }
774
819
 
820
+
821
+ export enum AllowedActionsEnum {
822
+ show = 'show',
823
+ list = 'list',
824
+ edit = 'edit',
825
+ create = 'create',
826
+ delete = 'delete',
827
+ filter = 'filter',
828
+ }
829
+
830
+
775
831
  export type AllowedActions = {
776
- create?: boolean,
777
- edit?: boolean,
778
- show?: boolean,
779
- delete?: boolean,
780
- filter?: boolean,
832
+ [key in AllowedActionsEnum]?: boolean
781
833
  }
782
834
 
783
835
  export type ValidationObject = {
@@ -960,8 +1012,8 @@ export type AdminForthForeignResource = {
960
1012
  resourceId: string,
961
1013
  hooks?: {
962
1014
  dropdownList?: {
963
- beforeDatasourceRequest?: Function,
964
- afterDatasourceResponse?: Function,
1015
+ beforeDatasourceRequest?: BeforeDataSourceRequestFunction | Array<BeforeDataSourceRequestFunction>,
1016
+ afterDatasourceResponse?: AfterDataSourceResponseFunction | Array<AfterDataSourceResponseFunction>,
965
1017
  },
966
1018
  },
967
1019
  }
@@ -88,7 +88,13 @@ export type AlertParams = {
88
88
  /**
89
89
  * The variant of the alert
90
90
  */
91
- variant?: AlertVariant;
91
+ variant?: AlertVariant | keyof typeof AlertVariant;
92
+
93
+ /**
94
+ * The timeout of the alert
95
+ */
96
+ timeout?: number | 'unlimited';
97
+
92
98
  }
93
99
 
94
100
  export type FilterParams = {
@@ -107,10 +113,10 @@ export type FilterParams = {
107
113
  }
108
114
 
109
115
  export enum AlertVariant {
110
- Danger = 'danger',
111
- Success = 'success',
112
- Warning = 'warning',
113
- Info = 'info'
116
+ danger = 'danger',
117
+ success = 'success',
118
+ warning = 'warning',
119
+ info = 'info'
114
120
  }
115
121
 
116
122
  export type Operator = 'in' | 'ilike' | 'gte' | 'lte' ;