adminforth 1.1.65 → 1.1.67
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 +19 -4
- package/dataConnectors/mongo.ts +7 -13
- package/dataConnectors/postgres.ts +2 -2
- package/dataConnectors/sqlite.ts +2 -2
- package/dist/auth.js +14 -4
- package/dist/dataConnectors/mongo.js +1 -9
- package/dist/index.js +4 -9
- package/dist/plugins/AuditLogPlugin/custom/AuditLogView.vue +7 -6
- package/dist/plugins/AuditLogPlugin/index.js +17 -13
- package/dist/servers/express.js +1 -1
- package/dist/spa/index.html +2 -2
- package/dist/spa/package-lock.json +17 -4
- package/dist/spa/package.json +2 -1
- package/dist/spa/spa/src/App.vue +24 -12
- package/dist/spa/spa/src/composables/useStores.ts +16 -4
- package/dist/spa/spa/src/main.ts +1 -1
- package/dist/spa/src/App.vue +125 -45
- package/dist/spa/src/assets/logo.svg +19 -1
- package/dist/spa/src/components/AcceptModal.vue +2 -9
- package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
- package/dist/spa/src/components/CustomDatePicker.vue +16 -3
- package/dist/spa/src/components/CustomDateRangePicker.vue +11 -2
- package/dist/spa/src/components/Dropdown.vue +6 -1
- package/dist/spa/src/components/Filters.vue +40 -19
- package/dist/spa/src/components/ResourceForm.vue +26 -24
- package/dist/spa/src/components/ResourceListTable.vue +419 -0
- package/dist/spa/src/components/Toast.vue +66 -0
- package/dist/spa/src/components/ValueRenderer.vue +14 -8
- package/dist/spa/src/composables/useFrontendApi.ts +26 -0
- package/dist/spa/src/composables/useStores.ts +113 -0
- package/dist/spa/src/main.ts +1 -1
- package/dist/spa/src/router/index.ts +30 -20
- package/dist/spa/src/spa_types/core.ts +51 -0
- package/dist/spa/src/stores/core.ts +48 -31
- package/dist/spa/src/stores/filters.ts +22 -0
- package/dist/spa/src/stores/modal.ts +13 -3
- package/dist/spa/src/stores/toast.ts +15 -0
- package/dist/spa/src/stores/user.ts +54 -0
- package/dist/spa/src/utils.ts +62 -7
- package/dist/spa/src/views/CreateView.vue +67 -15
- package/dist/spa/src/views/EditView.vue +45 -11
- package/dist/spa/src/views/ListView.vue +82 -366
- package/dist/spa/src/views/LoginView.vue +16 -4
- package/dist/spa/src/views/ShowView.vue +105 -21
- package/dist/spa/tailwind.config.js +1 -1
- package/dist/spa/vite.config.ts +6 -0
- package/index.ts +7 -12
- package/package.json +1 -1
- package/plugins/AuditLogPlugin/custom/AuditLogView.vue +7 -6
- package/plugins/AuditLogPlugin/index.ts +22 -15
- package/servers/express.ts +1 -1
- package/spa/src/App.vue +24 -12
- package/spa/src/composables/useStores.ts +16 -4
- package/spa/src/main.ts +1 -1
- package/types/AdminForthConfig.ts +18 -4
- package/types/FrontendAPI.ts +1 -15
- package/dist/plugins/AccessControl/index.js +0 -66
- package/dist/plugins/AccessControl/types.js +0 -1
- package/dist/spa/public/favicon.ico +0 -0
- package/dist/spa/spa/public/favicon.ico +0 -0
- package/dist/types.js +0 -30
- /package/dist/spa/{spa/public → public/assets}/favicon.png +0 -0
|
@@ -1,21 +1,38 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="relative">
|
|
3
|
+
<component
|
|
4
|
+
v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.beforeBreadcrumbs || []"
|
|
5
|
+
:is="getCustomComponent(c)"
|
|
6
|
+
:meta="c.meta"
|
|
7
|
+
:record="coreStore.record"
|
|
8
|
+
:resource="coreStore.resource"
|
|
9
|
+
:adminUser="coreStore.adminUser"
|
|
10
|
+
/>
|
|
3
11
|
<BreadcrumbsWithButtons>
|
|
4
|
-
<RouterLink :to="{ name: 'resource-edit', params: { resourceId: $route.params.resourceId, primaryKey: $route.params.primaryKey } }"
|
|
5
|
-
class="flex items-center py-1 px-3
|
|
12
|
+
<RouterLink v-if="coreStore?.resourceOptions?.allowedActions?.edit" :to="{ name: 'resource-edit', params: { resourceId: $route.params.resourceId, primaryKey: $route.params.primaryKey } }"
|
|
13
|
+
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-blue-700 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"
|
|
6
14
|
>
|
|
7
15
|
<IconPenSolid class="w-4 h-4" />
|
|
8
16
|
Edit
|
|
9
17
|
</RouterLink>
|
|
10
18
|
|
|
11
|
-
<button @click="deleteRecord"
|
|
12
|
-
class="flex items-center py-1 px-3
|
|
19
|
+
<button v-if="coreStore?.resourceOptions?.allowedActions?.delete" @click="deleteRecord"
|
|
20
|
+
class="flex items-center py-1 px-3 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-white border border-gray-300 hover:bg-gray-100 hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-red-500 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
|
13
21
|
>
|
|
14
22
|
<IconTrashBinSolid class="w-4 h-4" />
|
|
15
23
|
Delete
|
|
16
24
|
</button>
|
|
17
25
|
</BreadcrumbsWithButtons>
|
|
18
26
|
|
|
27
|
+
<component
|
|
28
|
+
v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.afterBreadcrumbs || []"
|
|
29
|
+
:is="getCustomComponent(c)"
|
|
30
|
+
:meta="c.meta"
|
|
31
|
+
:record="coreStore.record"
|
|
32
|
+
:resource="coreStore.resource"
|
|
33
|
+
:adminUser="coreStore.adminUser"
|
|
34
|
+
/>
|
|
35
|
+
|
|
19
36
|
<div v-if="loading" role="status" class="max-w-sm animate-pulse">
|
|
20
37
|
<div class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4"></div>
|
|
21
38
|
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px] mb-2.5"></div>
|
|
@@ -29,7 +46,7 @@
|
|
|
29
46
|
v-else
|
|
30
47
|
class="relative overflow-x-auto shadow-resourse-form-shadow "
|
|
31
48
|
>
|
|
32
|
-
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
|
49
|
+
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 rounded-default">
|
|
33
50
|
<thead class="text-xs text-gray-700 uppercase bg-form-view-heading dark:bg-gray-700 dark:text-gray-400">
|
|
34
51
|
<tr>
|
|
35
52
|
<th scope="col" class="px-6 py-3">
|
|
@@ -41,23 +58,49 @@
|
|
|
41
58
|
</tr>
|
|
42
59
|
</thead>
|
|
43
60
|
<tbody>
|
|
44
|
-
<tr v-for="column in coreStore.
|
|
61
|
+
<tr v-for="column in coreStore.resource?.columns.filter(c => c.showIn.includes('show'))" :key="column.name"
|
|
45
62
|
class="bg-form-view-bg odd:dark:bg-gray-900 even:dark:bg-gray-800 border-b dark:border-gray-700"
|
|
46
63
|
>
|
|
47
|
-
|
|
48
|
-
|
|
64
|
+
<component
|
|
65
|
+
v-if="column.components?.showRow"
|
|
66
|
+
:is="getCustomComponent(column.components.showRow)"
|
|
67
|
+
:meta="column.components.showRow.meta"
|
|
68
|
+
:column="column"
|
|
69
|
+
:resource="coreStore.resource"
|
|
70
|
+
:record="coreStore.record"
|
|
71
|
+
/>
|
|
72
|
+
<template v-else>
|
|
73
|
+
<td class="px-6 py-4 whitespace-nowrap "> <!--align-top-->
|
|
74
|
+
{{ column.label }}
|
|
49
75
|
</td>
|
|
50
76
|
<td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap">
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
77
|
+
<component
|
|
78
|
+
v-if="showComponentsPerColumn[column.name]"
|
|
79
|
+
:is="showComponentsPerColumn[column.name]"
|
|
80
|
+
:resource="coreStore.resource"
|
|
81
|
+
:meta="column.components.show.meta"
|
|
82
|
+
:column="column"
|
|
83
|
+
:record="coreStore.record"
|
|
84
|
+
/>
|
|
85
|
+
<ValueRenderer v-else
|
|
86
|
+
:column="column"
|
|
87
|
+
:record="coreStore.record"
|
|
88
|
+
/>
|
|
56
89
|
</td>
|
|
90
|
+
</template>
|
|
57
91
|
</tr>
|
|
58
|
-
|
|
59
92
|
</tbody>
|
|
60
93
|
</table>
|
|
94
|
+
|
|
95
|
+
<component
|
|
96
|
+
v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.bottom || []"
|
|
97
|
+
:is="getCustomComponent(c)"
|
|
98
|
+
:meta="c.meta"
|
|
99
|
+
:column="column"
|
|
100
|
+
:record="coreStore.record"
|
|
101
|
+
:resource="coreStore.resource"
|
|
102
|
+
:adminUser="coreStore.adminUser"
|
|
103
|
+
/>
|
|
61
104
|
</div>
|
|
62
105
|
|
|
63
106
|
|
|
@@ -68,37 +111,78 @@
|
|
|
68
111
|
<script setup>
|
|
69
112
|
|
|
70
113
|
import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
114
|
+
|
|
71
115
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
72
116
|
import { useCoreStore } from '@/stores/core';
|
|
73
|
-
import {
|
|
117
|
+
import { useModalStore } from '@/stores/modal';
|
|
118
|
+
import { getCustomComponent, checkAcessByAllowedActions } from '@/utils';
|
|
74
119
|
import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
|
|
75
120
|
import { onMounted, ref } from 'vue';
|
|
76
|
-
import { useRoute } from 'vue-router';
|
|
121
|
+
import { useRoute,useRouter } from 'vue-router';
|
|
122
|
+
import {callAdminForthApi} from '@/utils';
|
|
123
|
+
import {showSuccesTost} from '@/composables/useFrontendApi';
|
|
77
124
|
|
|
78
125
|
|
|
79
126
|
const item = ref(null);
|
|
80
127
|
const route = useRoute();
|
|
128
|
+
const router = useRouter();
|
|
81
129
|
const loading = ref(false);
|
|
82
130
|
let showComponentsPerColumn = {};
|
|
83
131
|
|
|
132
|
+
console.log(route.params,'showWiev');
|
|
133
|
+
|
|
134
|
+
|
|
84
135
|
const coreStore = useCoreStore();
|
|
136
|
+
const modalStore = useModalStore();
|
|
85
137
|
|
|
86
138
|
onMounted(async () => {
|
|
87
139
|
loading.value = true;
|
|
88
|
-
await coreStore.
|
|
140
|
+
await coreStore.fetchResourceFull({
|
|
89
141
|
resourceId: route.params.resourceId
|
|
90
142
|
});
|
|
91
143
|
await coreStore.fetchRecord({
|
|
92
144
|
resourceId: route.params.resourceId,
|
|
93
145
|
primaryKey: route.params.primaryKey,
|
|
94
146
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
147
|
+
checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'show');
|
|
148
|
+
|
|
149
|
+
showComponentsPerColumn = coreStore.resource.columns.reduce((acc, column) => {
|
|
150
|
+
if (column.components?.show) {
|
|
151
|
+
acc[column.name] = getCustomComponent(column.components.show);
|
|
98
152
|
}
|
|
99
153
|
return acc;
|
|
100
|
-
|
|
154
|
+
}, {});
|
|
101
155
|
loading.value = false;
|
|
102
156
|
});
|
|
103
157
|
|
|
158
|
+
async function deleteRecord(row) {
|
|
159
|
+
const data = await window.adminforth.confirm({
|
|
160
|
+
message: 'Are you sure you want to delete this item?',
|
|
161
|
+
yes: 'Delete',
|
|
162
|
+
no: 'Cancel',
|
|
163
|
+
});
|
|
164
|
+
if (data) {
|
|
165
|
+
try {
|
|
166
|
+
const res = await callAdminForthApi({
|
|
167
|
+
path: '/delete_record',
|
|
168
|
+
method: 'POST',
|
|
169
|
+
body: {
|
|
170
|
+
resourceId: route.params.resourceId,
|
|
171
|
+
primaryKey: route.params.primaryKey,
|
|
172
|
+
}});
|
|
173
|
+
if (!res.error){
|
|
174
|
+
router.push({ name: 'resource-list', params: { resourceId: route.params.resourceId } });
|
|
175
|
+
showSuccesTost('Record deleted successfully')
|
|
176
|
+
} else {
|
|
177
|
+
console.error(res.error)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
} catch (e) {
|
|
181
|
+
console.error(e);
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
|
|
104
188
|
</script>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @type {import('tailwindcss').Config} */
|
|
2
2
|
export default {
|
|
3
|
-
content: ["./src/**/*.{vue, js}","./src/*.{vue, js}", "./index.html", "./node_modules/flowbite/**/*.js"],
|
|
3
|
+
content: ["./src/**/*.{vue, js, ts, tsx}","./src/*.{vue, js, ts, tsx}", "./index.html", "./node_modules/flowbite/**/*.js"],
|
|
4
4
|
theme: {
|
|
5
5
|
extend: {
|
|
6
6
|
/* IMPORTANT:ADMINFORTH TAILWIND STYLES */
|
package/dist/spa/vite.config.ts
CHANGED
package/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { AdminForthConfig, AdminForthClass, AdminForthComponentDeclaration, Admi
|
|
|
19
19
|
AllowedActionsEnum,
|
|
20
20
|
AllowedActions,
|
|
21
21
|
ActionCheckSource,
|
|
22
|
+
AdminForthDataSourceConnector,
|
|
22
23
|
} from './types/AdminForthConfig.js';
|
|
23
24
|
import path from 'path';
|
|
24
25
|
import AdminForthPlugin from './plugins/base.js';
|
|
@@ -45,7 +46,7 @@ class AdminForth implements AdminForthClass {
|
|
|
45
46
|
express: ExpressServer;
|
|
46
47
|
auth: AdminForthAuth;
|
|
47
48
|
codeInjector: CodeInjector;
|
|
48
|
-
connectors
|
|
49
|
+
connectors;
|
|
49
50
|
connectorClasses: any;
|
|
50
51
|
runningHotReload: boolean;
|
|
51
52
|
activatedPlugins: Array<AdminForthPlugin>;
|
|
@@ -496,7 +497,7 @@ class AdminForth implements AdminForthClass {
|
|
|
496
497
|
if (!this.config.databaseConnectors[dbType]) {
|
|
497
498
|
throw new Error(`Database type ${dbType} is not supported, consider using databaseConnectors in AdminForth config`);
|
|
498
499
|
}
|
|
499
|
-
this.connectors[ds.id] = new this.config.databaseConnectors[dbType]({url: ds.url});
|
|
500
|
+
this.connectors[ds.id] = new this.config.databaseConnectors[dbType]({url: ds.url});
|
|
500
501
|
});
|
|
501
502
|
|
|
502
503
|
await Promise.all(this.config.resources.map(async (res) => {
|
|
@@ -625,7 +626,7 @@ class AdminForth implements AdminForthClass {
|
|
|
625
626
|
const { username, password } = body;
|
|
626
627
|
let token;
|
|
627
628
|
if (username === this.config.rootUser.username && password === this.config.rootUser.password) {
|
|
628
|
-
|
|
629
|
+
this.auth.setAuthCookie({ response, username, pk: null });
|
|
629
630
|
} else {
|
|
630
631
|
// get resource from db
|
|
631
632
|
if (!this.config.auth) {
|
|
@@ -663,15 +664,11 @@ class AdminForth implements AdminForthClass {
|
|
|
663
664
|
console.log('User record', userRecord, passwordHash) // why does it has no hash?
|
|
664
665
|
const valid = await AdminForthAuth.verifyPassword(password, passwordHash);
|
|
665
666
|
if (valid) {
|
|
666
|
-
|
|
667
|
-
username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name]
|
|
668
|
-
});
|
|
667
|
+
this.auth.setAuthCookie({ response, username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name] });
|
|
669
668
|
} else {
|
|
670
669
|
return { error: INVALID_MESSAGE };
|
|
671
670
|
}
|
|
672
671
|
}
|
|
673
|
-
|
|
674
|
-
response.setHeader('Set-Cookie', `adminforth_jwt=${token}; Path=${this.config.baseUrl || '/'}; HttpOnly; SameSite=Strict`);
|
|
675
672
|
return { ok: true };
|
|
676
673
|
},
|
|
677
674
|
});
|
|
@@ -689,7 +686,7 @@ class AdminForth implements AdminForthClass {
|
|
|
689
686
|
method: 'POST',
|
|
690
687
|
path: '/logout',
|
|
691
688
|
handler: async ({ response }) => {
|
|
692
|
-
|
|
689
|
+
this.auth.removeAuthCookie({ response });
|
|
693
690
|
return { ok: true };
|
|
694
691
|
},
|
|
695
692
|
})
|
|
@@ -725,10 +722,8 @@ class AdminForth implements AdminForthClass {
|
|
|
725
722
|
let userFullName = ''
|
|
726
723
|
if (adminUser.isRoot) {
|
|
727
724
|
username = this.config.rootUser.username;
|
|
728
|
-
console.log(username, 'this is the root')
|
|
729
725
|
} else {
|
|
730
726
|
const dbUser = adminUser.dbUser;
|
|
731
|
-
console.log(dbUser,'this is the db user',adminUser,'this is the admin user')
|
|
732
727
|
username = dbUser[this.config.auth.usernameField];
|
|
733
728
|
userFullName =dbUser[this.config.auth.userFullNameField];
|
|
734
729
|
}
|
|
@@ -1172,7 +1167,7 @@ class AdminForth implements AdminForthClass {
|
|
|
1172
1167
|
|
|
1173
1168
|
// execute hook if needed
|
|
1174
1169
|
for (const hook of listify(resource.hooks?.edit?.afterSave as AfterSaveFunction[])) {
|
|
1175
|
-
const resp = await hook({ resource, record, adminUser });
|
|
1170
|
+
const resp = await hook({ resource, record, adminUser, oldRecord });
|
|
1176
1171
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
1177
1172
|
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
1178
1173
|
}
|
package/package.json
CHANGED
|
@@ -2,19 +2,20 @@
|
|
|
2
2
|
import VueDiff from 'vue-diff';
|
|
3
3
|
import { ref } from 'vue';
|
|
4
4
|
import 'vue-diff/dist/index.css';
|
|
5
|
-
|
|
6
|
-
const props = defineProps(['prev', 'current', 'meta']);
|
|
5
|
+
import { app } from '@/main';
|
|
7
6
|
|
|
8
7
|
app.use(VueDiff);
|
|
8
|
+
|
|
9
|
+
const props = defineProps(['column', 'record', 'meta', 'resource', 'adminUser']);
|
|
9
10
|
</script>
|
|
10
11
|
|
|
11
12
|
<template>
|
|
12
13
|
<Diff
|
|
13
14
|
:mode="'split'"
|
|
14
15
|
:theme="'light'"
|
|
15
|
-
:language="'
|
|
16
|
-
:prev="props.
|
|
17
|
-
:current="props.
|
|
16
|
+
:language="'JSON'"
|
|
17
|
+
:prev="JSON.stringify(JSON.parse(props.record[props.meta.resourceColumns.resourceDataColumnName]).oldRecord, null, 2)"
|
|
18
|
+
:current="JSON.stringify(JSON.parse(props.record[props.meta.resourceColumns.resourceDataColumnName]).newRecord, null, 2)"
|
|
18
19
|
/>
|
|
19
|
-
|
|
20
|
+
</template>
|
|
20
21
|
|
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
AllowedActionsEnum,
|
|
5
5
|
BeforeSaveFunction,
|
|
6
6
|
AdminUser,
|
|
7
|
-
AdminForthDataTypes
|
|
7
|
+
AdminForthDataTypes,
|
|
8
|
+
AdminForthResourcePages,
|
|
9
|
+
AdminForthFilterOperators
|
|
8
10
|
} from "../../types/AdminForthConfig.js";
|
|
9
11
|
import AdminForthPlugin from "../base.js";
|
|
10
12
|
import { PluginOptions } from "./types.js";
|
|
@@ -24,19 +26,25 @@ export default class AuditLogPlugin extends AdminForthPlugin {
|
|
|
24
26
|
|
|
25
27
|
static defaultError = 'Sorry, you do not have access to this resource.'
|
|
26
28
|
|
|
27
|
-
createLogRecord = async (resource: AdminForthResource, action: AllowedActionsEnum, data: Object, user: AdminUser) => {
|
|
29
|
+
createLogRecord = async (resource: AdminForthResource, action: AllowedActionsEnum, data: Object, user: AdminUser, oldRecord: Object) => {
|
|
28
30
|
const recordIdFieldName = resource.columns.find((c) => c.primaryKey === true)?.name;
|
|
29
|
-
const recordId = data[recordIdFieldName];
|
|
31
|
+
const recordId = data?.[recordIdFieldName] || oldRecord?.[recordIdFieldName];
|
|
32
|
+
const connector = this.adminforth.connectors[resource.dataSource];
|
|
33
|
+
const newRecord = await connector.getRecordByPrimaryKey(resource, recordId);
|
|
34
|
+
|
|
35
|
+
let newData = {
|
|
36
|
+
'oldRecord': oldRecord || {},
|
|
37
|
+
'newRecord': newRecord
|
|
38
|
+
}
|
|
30
39
|
const record = {
|
|
31
40
|
[this.options.resourceColumns.resourceIdColumnName]: resource.resourceId,
|
|
32
41
|
[this.options.resourceColumns.resourceActionColumnName]: action,
|
|
33
|
-
[this.options.resourceColumns.resourceDataColumnName]:
|
|
42
|
+
[this.options.resourceColumns.resourceDataColumnName]: newData,
|
|
34
43
|
[this.options.resourceColumns.resourceUserIdColumnName]: user.pk,
|
|
35
44
|
[this.options.resourceColumns.resourceRecordIdColumnName]: recordId,
|
|
36
45
|
[this.options.resourceColumns.resourceCreatedColumnName]: new Date()
|
|
37
46
|
}
|
|
38
47
|
const auditLogResource = this.adminforth.config.resources.find((r) => r.resourceId === this.auditLogResource);
|
|
39
|
-
console.log('rec', record);
|
|
40
48
|
await this.adminforth.createResourceRecord({ resource: auditLogResource, record, adminUser: user});
|
|
41
49
|
return {ok: true};
|
|
42
50
|
}
|
|
@@ -60,9 +68,9 @@ export default class AuditLogPlugin extends AdminForthPlugin {
|
|
|
60
68
|
// type: AdminForthDataTypes.STRING
|
|
61
69
|
}
|
|
62
70
|
}
|
|
63
|
-
|
|
71
|
+
|
|
64
72
|
diffColumn.components = {
|
|
65
|
-
|
|
73
|
+
show: {
|
|
66
74
|
file: this.componentPath('AuditLogView.vue'),
|
|
67
75
|
meta: {
|
|
68
76
|
...this.options,
|
|
@@ -70,14 +78,13 @@ export default class AuditLogPlugin extends AdminForthPlugin {
|
|
|
70
78
|
}
|
|
71
79
|
}
|
|
72
80
|
}
|
|
73
|
-
resource.columns.push(diffColumn);
|
|
74
81
|
return;
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
const defaultHooks = {
|
|
78
|
-
create: {
|
|
79
|
-
edit: {
|
|
80
|
-
delete: {
|
|
85
|
+
create: { afterSave: [] },
|
|
86
|
+
edit: { afterSave: [] },
|
|
87
|
+
delete: { afterSave: [] }
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
if ( !resource.hooks ) {
|
|
@@ -87,11 +94,11 @@ export default class AuditLogPlugin extends AdminForthPlugin {
|
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
Object.keys(resource.hooks).forEach((hook) => {
|
|
90
|
-
if(!Array.isArray(resource.hooks[hook].
|
|
91
|
-
resource.hooks[hook].
|
|
97
|
+
if(!Array.isArray(resource.hooks[hook].afterSave)){
|
|
98
|
+
resource.hooks[hook].afterSave = [resource.hooks[hook].afterSave]
|
|
92
99
|
}
|
|
93
|
-
resource.hooks[hook].
|
|
94
|
-
return await this.createLogRecord(resource, hook as AllowedActionsEnum, record, adminUser)
|
|
100
|
+
resource.hooks[hook].afterSave.push(async ({resource, record, adminUser, oldRecord}) => {
|
|
101
|
+
return await this.createLogRecord(resource, hook as AllowedActionsEnum, record, adminUser, oldRecord)
|
|
95
102
|
})
|
|
96
103
|
})
|
|
97
104
|
})
|
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 = await this.adminforth.auth.verify(jwt);
|
|
160
|
+
const adminforthUser = await this.adminforth.auth.verify(jwt, 'auth');
|
|
161
161
|
if (!adminforthUser) {
|
|
162
162
|
res.status(401).send('Unauthorized by AdminForth');
|
|
163
163
|
} else {
|
package/spa/src/App.vue
CHANGED
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
</style>
|
|
164
164
|
|
|
165
165
|
<script setup lang="ts">
|
|
166
|
-
import { computed, onMounted, ref, watch, defineComponent } from 'vue';
|
|
166
|
+
import { computed, onMounted, ref, watch, defineComponent, onBeforeMount } from 'vue';
|
|
167
167
|
import { RouterLink, RouterView } from 'vue-router';
|
|
168
168
|
import { initFlowbite } from 'flowbite'
|
|
169
169
|
import './index.scss'
|
|
@@ -211,6 +211,8 @@ const theme = ref('light');
|
|
|
211
211
|
function toggleTheme() {
|
|
212
212
|
theme.value = theme.value === 'light' ? 'dark' : 'light';
|
|
213
213
|
document.documentElement.classList.toggle('dark');
|
|
214
|
+
window.localStorage.setItem('theme', theme.value);
|
|
215
|
+
|
|
214
216
|
}
|
|
215
217
|
|
|
216
218
|
function clickOnMenuItem (label: string) {
|
|
@@ -237,14 +239,8 @@ async function initRouter() {
|
|
|
237
239
|
}
|
|
238
240
|
|
|
239
241
|
async function loadMenu() {
|
|
240
|
-
await coreStore.fetchMenuAndResource()
|
|
242
|
+
await coreStore.fetchMenuAndResource();
|
|
241
243
|
loginRedirectCheckIsReady.value = true;
|
|
242
|
-
|
|
243
|
-
coreStore.menu.forEach((item, i) => {
|
|
244
|
-
if (item.open) {
|
|
245
|
-
opened.value.push(i);
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
244
|
}
|
|
249
245
|
|
|
250
246
|
|
|
@@ -256,16 +252,32 @@ watch(route, () => {
|
|
|
256
252
|
})
|
|
257
253
|
});
|
|
258
254
|
|
|
255
|
+
watch (()=>coreStore.menu, () => {
|
|
256
|
+
coreStore.menu.forEach((item, i) => {
|
|
257
|
+
if (item.open) {
|
|
258
|
+
opened.value.push(i);
|
|
259
|
+
};
|
|
260
|
+
});
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
watch([loggedIn, routerIsReady, loginRedirectCheckIsReady], ([l,r,lr]) => {
|
|
264
|
+
if (l && r && lr) {
|
|
265
|
+
setTimeout(() => {
|
|
266
|
+
initFlowbite();
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
|
|
259
271
|
// initialize components based on data attribute selectors
|
|
260
272
|
onMounted(async () => {
|
|
261
273
|
loadMenu(); // run this in async mode
|
|
262
274
|
// before init flowbite we have to wait router initialized because it affects dom(our v-ifs) and fetch menu
|
|
263
275
|
await initRouter()
|
|
264
|
-
|
|
265
|
-
initFlowbite();
|
|
266
|
-
});
|
|
267
|
-
|
|
276
|
+
})
|
|
268
277
|
|
|
278
|
+
onBeforeMount(()=>{
|
|
279
|
+
theme.value = window.localStorage.getItem('theme') || 'light';
|
|
280
|
+
document.documentElement.classList.toggle('dark', theme.value === 'dark');
|
|
269
281
|
})
|
|
270
282
|
|
|
271
283
|
</script>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { FrontendAPIInterface, ConfirmParams, AlertParams,
|
|
1
|
+
import type { FrontendAPIInterface, ConfirmParams, AlertParams, } from '../types/FrontendAPI';
|
|
2
|
+
import type { AdminForthFilterOperators } from '@/types/AdminForthConfig';
|
|
2
3
|
import { useToastStore } from '../stores/toast';
|
|
3
4
|
import { useModalStore } from '../stores/modal';
|
|
4
5
|
import { useCoreStore } from '@/stores/core';
|
|
@@ -6,9 +7,20 @@ import { useFiltersStore } from '@/stores/filters';
|
|
|
6
7
|
import router from '@/router'
|
|
7
8
|
import type { AdminForthResourceColumn } from '@/types/AdminForthConfig';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
type FilterParams = {
|
|
11
|
+
/**
|
|
12
|
+
* Field of resource to filter
|
|
13
|
+
*/
|
|
14
|
+
field: string;
|
|
15
|
+
/**
|
|
16
|
+
* Operator of filter
|
|
17
|
+
*/
|
|
18
|
+
operator: AdminForthFilterOperators;
|
|
19
|
+
/**
|
|
20
|
+
* Value of filter
|
|
21
|
+
*/
|
|
22
|
+
value: string | number | boolean ;
|
|
23
|
+
}
|
|
12
24
|
|
|
13
25
|
declare global {
|
|
14
26
|
interface Window {
|
package/spa/src/main.ts
CHANGED
|
@@ -77,6 +77,17 @@ export interface ExpressHttpServer extends GenericHttpServer {
|
|
|
77
77
|
authorize(callable: Function): void;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
export interface AdminForthDataSourceConnector {
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Function which will be called to fetch record from database.
|
|
84
|
+
*/
|
|
85
|
+
getRecordByPrimaryKey(resource: AdminForthResource, recordId: string): Promise<any>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface AdminForthDataSourceConnectorConstructor {
|
|
89
|
+
new ({ url }: { url: string }): AdminForthDataSourceConnector;
|
|
90
|
+
}
|
|
80
91
|
|
|
81
92
|
export interface AdminForthClass {
|
|
82
93
|
config: AdminForthConfig;
|
|
@@ -84,14 +95,15 @@ export interface AdminForthClass {
|
|
|
84
95
|
express: GenericHttpServer;
|
|
85
96
|
|
|
86
97
|
connectors: {
|
|
87
|
-
[key: string]:
|
|
98
|
+
[key: string]: AdminForthDataSourceConnector,
|
|
88
99
|
};
|
|
89
100
|
|
|
90
101
|
createResourceRecord(params: { resource: AdminForthResource, record: any, adminUser: AdminUser }): Promise<any>;
|
|
91
102
|
|
|
92
103
|
auth: {
|
|
104
|
+
verify(jwt : string, mustHaveType: string): Promise<any>;
|
|
93
105
|
|
|
94
|
-
|
|
106
|
+
issueJWT(payload: Object, type: string): string;
|
|
95
107
|
}
|
|
96
108
|
|
|
97
109
|
/**
|
|
@@ -534,11 +546,11 @@ export type AdminForthResource = {
|
|
|
534
546
|
},
|
|
535
547
|
options?: {
|
|
536
548
|
bulkActions?: Array<{
|
|
549
|
+
id?: string,
|
|
537
550
|
label: string,
|
|
538
551
|
state: string,
|
|
539
552
|
icon?: string,
|
|
540
553
|
action: Function,
|
|
541
|
-
id?: string,
|
|
542
554
|
confirm?: string,
|
|
543
555
|
}>,
|
|
544
556
|
allowedActions?: AllowedActions,
|
|
@@ -715,7 +727,9 @@ export type AdminForthConfig = {
|
|
|
715
727
|
* If you want use custom DataSource which is not supported by AdminForth yet, you can define it's class here
|
|
716
728
|
*
|
|
717
729
|
*/
|
|
718
|
-
databaseConnectors?:
|
|
730
|
+
databaseConnectors?: {
|
|
731
|
+
[key: string]: AdminForthDataSourceConnectorConstructor,
|
|
732
|
+
},
|
|
719
733
|
|
|
720
734
|
/**
|
|
721
735
|
* List of data sources which will be used to fetch data for resources.
|
package/types/FrontendAPI.ts
CHANGED
|
@@ -99,20 +99,7 @@ export type AlertParams = {
|
|
|
99
99
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Field of resource to filter
|
|
105
|
-
*/
|
|
106
|
-
field: string;
|
|
107
|
-
/**
|
|
108
|
-
* Operator of filter
|
|
109
|
-
*/
|
|
110
|
-
operator: Operator;
|
|
111
|
-
/**
|
|
112
|
-
* Value of filter
|
|
113
|
-
*/
|
|
114
|
-
value: string | number | boolean ;
|
|
115
|
-
}
|
|
102
|
+
|
|
116
103
|
|
|
117
104
|
export enum AlertVariant {
|
|
118
105
|
danger = 'danger',
|
|
@@ -121,7 +108,6 @@ export enum AlertVariant {
|
|
|
121
108
|
info = 'info'
|
|
122
109
|
}
|
|
123
110
|
|
|
124
|
-
export type Operator = 'in' | 'ilike' | 'gte' | 'lte' ;
|
|
125
111
|
|
|
126
112
|
|
|
127
113
|
|