adminforth 1.1.64 → 1.1.66
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/dataConnectors/mongo.ts +7 -13
- package/dataConnectors/postgres.ts +2 -2
- package/dataConnectors/sqlite.ts +2 -2
- package/dist/dataConnectors/mongo.js +1 -9
- package/dist/index.js +1 -3
- package/dist/plugins/AuditLogPlugin/custom/AuditLogView.vue +7 -6
- package/dist/plugins/AuditLogPlugin/index.js +18 -13
- 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 +4 -5
- package/package.json +1 -1
- package/plugins/AuditLogPlugin/custom/AuditLogView.vue +7 -6
- package/plugins/AuditLogPlugin/index.ts +23 -15
- 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 +16 -3
- 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) => {
|
|
@@ -725,10 +726,8 @@ class AdminForth implements AdminForthClass {
|
|
|
725
726
|
let userFullName = ''
|
|
726
727
|
if (adminUser.isRoot) {
|
|
727
728
|
username = this.config.rootUser.username;
|
|
728
|
-
console.log(username, 'this is the root')
|
|
729
729
|
} else {
|
|
730
730
|
const dbUser = adminUser.dbUser;
|
|
731
|
-
console.log(dbUser,'this is the db user',adminUser,'this is the admin user')
|
|
732
731
|
username = dbUser[this.config.auth.usernameField];
|
|
733
732
|
userFullName =dbUser[this.config.auth.userFullNameField];
|
|
734
733
|
}
|
|
@@ -1172,7 +1171,7 @@ class AdminForth implements AdminForthClass {
|
|
|
1172
1171
|
|
|
1173
1172
|
// execute hook if needed
|
|
1174
1173
|
for (const hook of listify(resource.hooks?.edit?.afterSave as AfterSaveFunction[])) {
|
|
1175
|
-
const resp = await hook({ resource, record, adminUser });
|
|
1174
|
+
const resp = await hook({ resource, record, adminUser, oldRecord });
|
|
1176
1175
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
1177
1176
|
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
1178
1177
|
}
|
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="props.record[props.meta.resourceColumns.resourceDataColumnName].oldRecord"
|
|
18
|
+
:current="props.record[props.meta.resourceColumns.resourceDataColumnName].newRecord"
|
|
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,26 @@ 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
|
+
}
|
|
39
|
+
// console.log('newData', newData)
|
|
30
40
|
const record = {
|
|
31
41
|
[this.options.resourceColumns.resourceIdColumnName]: resource.resourceId,
|
|
32
42
|
[this.options.resourceColumns.resourceActionColumnName]: action,
|
|
33
|
-
[this.options.resourceColumns.resourceDataColumnName]:
|
|
43
|
+
[this.options.resourceColumns.resourceDataColumnName]: newData,
|
|
34
44
|
[this.options.resourceColumns.resourceUserIdColumnName]: user.pk,
|
|
35
45
|
[this.options.resourceColumns.resourceRecordIdColumnName]: recordId,
|
|
36
46
|
[this.options.resourceColumns.resourceCreatedColumnName]: new Date()
|
|
37
47
|
}
|
|
38
48
|
const auditLogResource = this.adminforth.config.resources.find((r) => r.resourceId === this.auditLogResource);
|
|
39
|
-
console.log('rec', record);
|
|
40
49
|
await this.adminforth.createResourceRecord({ resource: auditLogResource, record, adminUser: user});
|
|
41
50
|
return {ok: true};
|
|
42
51
|
}
|
|
@@ -60,9 +69,9 @@ export default class AuditLogPlugin extends AdminForthPlugin {
|
|
|
60
69
|
// type: AdminForthDataTypes.STRING
|
|
61
70
|
}
|
|
62
71
|
}
|
|
63
|
-
|
|
72
|
+
|
|
64
73
|
diffColumn.components = {
|
|
65
|
-
|
|
74
|
+
show: {
|
|
66
75
|
file: this.componentPath('AuditLogView.vue'),
|
|
67
76
|
meta: {
|
|
68
77
|
...this.options,
|
|
@@ -70,14 +79,13 @@ export default class AuditLogPlugin extends AdminForthPlugin {
|
|
|
70
79
|
}
|
|
71
80
|
}
|
|
72
81
|
}
|
|
73
|
-
resource.columns.push(diffColumn);
|
|
74
82
|
return;
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
const defaultHooks = {
|
|
78
|
-
create: {
|
|
79
|
-
edit: {
|
|
80
|
-
delete: {
|
|
86
|
+
create: { afterSave: [] },
|
|
87
|
+
edit: { afterSave: [] },
|
|
88
|
+
delete: { afterSave: [] }
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
if ( !resource.hooks ) {
|
|
@@ -87,11 +95,11 @@ export default class AuditLogPlugin extends AdminForthPlugin {
|
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
Object.keys(resource.hooks).forEach((hook) => {
|
|
90
|
-
if(!Array.isArray(resource.hooks[hook].
|
|
91
|
-
resource.hooks[hook].
|
|
98
|
+
if(!Array.isArray(resource.hooks[hook].afterSave)){
|
|
99
|
+
resource.hooks[hook].afterSave = [resource.hooks[hook].afterSave]
|
|
92
100
|
}
|
|
93
|
-
resource.hooks[hook].
|
|
94
|
-
return await this.createLogRecord(resource, hook as AllowedActionsEnum, record, adminUser)
|
|
101
|
+
resource.hooks[hook].afterSave.push(async ({resource, record, adminUser, oldRecord}) => {
|
|
102
|
+
return await this.createLogRecord(resource, hook as AllowedActionsEnum, record, adminUser, oldRecord)
|
|
95
103
|
})
|
|
96
104
|
})
|
|
97
105
|
})
|
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,7 +95,7 @@ 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>;
|
|
@@ -534,11 +545,11 @@ export type AdminForthResource = {
|
|
|
534
545
|
},
|
|
535
546
|
options?: {
|
|
536
547
|
bulkActions?: Array<{
|
|
548
|
+
id?: string,
|
|
537
549
|
label: string,
|
|
538
550
|
state: string,
|
|
539
551
|
icon?: string,
|
|
540
552
|
action: Function,
|
|
541
|
-
id?: string,
|
|
542
553
|
confirm?: string,
|
|
543
554
|
}>,
|
|
544
555
|
allowedActions?: AllowedActions,
|
|
@@ -715,7 +726,9 @@ export type AdminForthConfig = {
|
|
|
715
726
|
* If you want use custom DataSource which is not supported by AdminForth yet, you can define it's class here
|
|
716
727
|
*
|
|
717
728
|
*/
|
|
718
|
-
databaseConnectors?:
|
|
729
|
+
databaseConnectors?: {
|
|
730
|
+
[key: string]: AdminForthDataSourceConnectorConstructor,
|
|
731
|
+
},
|
|
719
732
|
|
|
720
733
|
/**
|
|
721
734
|
* 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
|
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
|
-
import { AllowedActionsEnum } from "../../types/AdminForthConfig.js";
|
|
11
|
-
import AdminForthPlugin from "../base.js";
|
|
12
|
-
class AccessControlPlugin extends AdminForthPlugin {
|
|
13
|
-
constructor(options) {
|
|
14
|
-
super(options, import.meta.url);
|
|
15
|
-
this.options = options;
|
|
16
|
-
}
|
|
17
|
-
modifyResourceConfig(adminforth, resourceConfig) {
|
|
18
|
-
super.modifyResourceConfig(adminforth, resourceConfig);
|
|
19
|
-
this.adminforth = adminforth;
|
|
20
|
-
if (!resourceConfig.hooks) {
|
|
21
|
-
resourceConfig.hooks = {};
|
|
22
|
-
}
|
|
23
|
-
const checkAccess = (adminUser, action, meta) => __awaiter(this, void 0, void 0, function* () {
|
|
24
|
-
const hasAccessOrError = yield this.options.hasAccess(adminUser, action, meta);
|
|
25
|
-
if (hasAccessOrError === true) {
|
|
26
|
-
return { ok: true };
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
return { ok: false, error: hasAccessOrError || AccessControlPlugin.defaultError };
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
const bindHookCheck = (action, hookName) => {
|
|
33
|
-
if (!resourceConfig.hooks[action]) {
|
|
34
|
-
resourceConfig.hooks[action] = {};
|
|
35
|
-
}
|
|
36
|
-
if (!resourceConfig.hooks[action][hookName]) {
|
|
37
|
-
resourceConfig.hooks[action][hookName] = [];
|
|
38
|
-
}
|
|
39
|
-
if (hookName === 'beforeDatasourceRequest') {
|
|
40
|
-
resourceConfig.hooks[action][hookName].unshift((_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, query }) {
|
|
41
|
-
return checkAccess(adminUser, action, { query });
|
|
42
|
-
}));
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
resourceConfig.hooks[action][hookName].unshift((_b) => __awaiter(this, [_b], void 0, function* ({ adminUser, record }) {
|
|
46
|
-
return checkAccess(adminUser, action, { record });
|
|
47
|
-
}));
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
// List check
|
|
51
|
-
bindHookCheck(AllowedActionsEnum.list, 'beforeDatasourceRequest');
|
|
52
|
-
// Show check
|
|
53
|
-
bindHookCheck(AllowedActionsEnum.show, 'beforeDatasourceRequest');
|
|
54
|
-
// Edit check
|
|
55
|
-
bindHookCheck(AllowedActionsEnum.edit, 'beforeDatasourceRequest');
|
|
56
|
-
// create check
|
|
57
|
-
bindHookCheck(AllowedActionsEnum.create, 'beforeSave');
|
|
58
|
-
// edit check
|
|
59
|
-
bindHookCheck(AllowedActionsEnum.edit, 'beforeSave');
|
|
60
|
-
// delete check
|
|
61
|
-
bindHookCheck(AllowedActionsEnum.delete, 'beforeSave');
|
|
62
|
-
console.log('resourceConfig', resourceConfig);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
AccessControlPlugin.defaultError = 'Sorry, you do not have access to this resource.';
|
|
66
|
-
export default AccessControlPlugin;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
Binary file
|
|
Binary file
|