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.
- package/auth.ts +17 -3
- package/dist/auth.js +32 -18
- package/dist/index.js +99 -58
- package/dist/modules/codeInjector.js +0 -1
- package/dist/modules/utils.js +2 -12
- package/dist/plugins/AccessControl/index.js +66 -0
- package/dist/plugins/AccessControl/types.js +1 -0
- package/dist/plugins/ForeignInlineListPlugin/index.js +1 -1
- package/dist/servers/express.js +2 -2
- package/dist/spa/spa/src/App.vue +7 -3
- package/dist/spa/spa/src/components/Toast.vue +5 -4
- package/dist/spa/spa/src/components/ValueRenderer.vue +1 -1
- package/dist/spa/spa/src/composables/useStores.ts +2 -1
- package/dist/spa/spa/src/router/index.ts +13 -1
- package/dist/spa/spa/src/stores/core.ts +13 -11
- package/dist/spa/spa/src/stores/user.ts +50 -0
- package/dist/spa/spa/src/utils.ts +8 -3
- package/dist/spa/spa/src/views/CreateView.vue +7 -0
- package/dist/spa/spa/src/views/EditView.vue +8 -1
- package/dist/spa/spa/src/views/ListView.vue +10 -1
- package/dist/types/AdminForthConfig.js +9 -0
- package/dist/types/FrontendAPI.js +4 -4
- package/index.ts +91 -42
- package/modules/codeInjector.ts +0 -1
- package/modules/utils.ts +2 -10
- package/package.json +1 -1
- package/plugins/AccessControl/index.ts +83 -0
- package/plugins/AccessControl/types.ts +14 -0
- package/plugins/ForeignInlineListPlugin/custom/InlineList.vue +11 -0
- package/plugins/ForeignInlineListPlugin/index.ts +2 -1
- package/servers/express.ts +2 -2
- package/spa/src/App.vue +7 -3
- package/spa/src/components/Toast.vue +5 -4
- package/spa/src/components/ValueRenderer.vue +1 -1
- package/spa/src/composables/useStores.ts +2 -1
- package/spa/src/router/index.ts +13 -1
- package/spa/src/stores/core.ts +13 -11
- package/spa/src/stores/user.ts +50 -0
- package/spa/src/utils.ts +8 -3
- package/spa/src/views/CreateView.vue +7 -0
- package/spa/src/views/EditView.vue +8 -1
- package/spa/src/views/ListView.vue +10 -1
- package/types/AdminForthConfig.ts +73 -21
- 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
|
}
|
package/servers/express.ts
CHANGED
|
@@ -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
|
-
|
|
225
|
-
|
|
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
|
-
|
|
49
|
+
timeout?: number|'unlimited';}
|
|
50
50
|
}>();
|
|
51
51
|
function closeToast() {
|
|
52
52
|
emit('close');
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
onMounted(() => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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>
|
package/spa/src/router/index.ts
CHANGED
|
@@ -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
|
package/spa/src/stores/core.ts
CHANGED
|
@@ -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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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?:
|
|
460
|
-
afterDatasourceResponse?:
|
|
504
|
+
beforeDatasourceRequest?: BeforeDataSourceRequestFunction | Array<BeforeDataSourceRequestFunction>,
|
|
505
|
+
afterDatasourceResponse?: AfterDataSourceResponseFunction | Array<AfterDataSourceResponseFunction>,
|
|
461
506
|
},
|
|
462
507
|
list?: {
|
|
463
|
-
beforeDatasourceRequest?:
|
|
464
|
-
afterDatasourceResponse?:
|
|
508
|
+
beforeDatasourceRequest?: BeforeDataSourceRequestFunction | Array<BeforeDataSourceRequestFunction>,
|
|
509
|
+
afterDatasourceResponse?: AfterDataSourceResponseFunction | Array<AfterDataSourceResponseFunction>,
|
|
465
510
|
},
|
|
466
511
|
create?: {
|
|
467
|
-
beforeSave?:
|
|
468
|
-
afterSave?:
|
|
512
|
+
beforeSave?: BeforeSaveFunction | Array<BeforeSaveFunction>,
|
|
513
|
+
afterSave?: AfterSaveFunction | Array<AfterSaveFunction>,
|
|
469
514
|
},
|
|
470
515
|
edit?: {
|
|
471
|
-
beforeSave?:
|
|
472
|
-
afterSave?:
|
|
516
|
+
beforeSave?: BeforeSaveFunction | Array<BeforeSaveFunction>,
|
|
517
|
+
afterSave?: AfterSaveFunction | Array<AfterSaveFunction>,
|
|
473
518
|
},
|
|
474
519
|
delete?: {
|
|
475
|
-
beforeSave?:
|
|
476
|
-
afterSave?:
|
|
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
|
-
|
|
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?:
|
|
964
|
-
afterDatasourceResponse?:
|
|
1015
|
+
beforeDatasourceRequest?: BeforeDataSourceRequestFunction | Array<BeforeDataSourceRequestFunction>,
|
|
1016
|
+
afterDatasourceResponse?: AfterDataSourceResponseFunction | Array<AfterDataSourceResponseFunction>,
|
|
965
1017
|
},
|
|
966
1018
|
},
|
|
967
1019
|
}
|
package/types/FrontendAPI.ts
CHANGED
|
@@ -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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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' ;
|