adminforth 1.1.74 → 1.1.75
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 +14 -0
- package/dataConnectors/postgres.ts +1 -1
- package/dist/auth.js +7 -0
- package/dist/index.js +117 -65
- package/dist/modules/codeInjector.js +29 -15
- package/dist/plugins/AuditLogPlugin/index.js +2 -17
- package/dist/plugins/S3UploadPlugin/custom/s3uploader.vue +120 -0
- package/dist/plugins/S3UploadPlugin/index.js +103 -0
- package/dist/plugins/S3UploadPlugin/package-lock.json +431 -0
- package/dist/plugins/S3UploadPlugin/package.json +15 -0
- package/dist/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsConfirmation.vue +152 -0
- package/dist/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsSetup.vue +188 -0
- package/dist/plugins/TwoFactorsAuthPlugin/index.js +150 -0
- package/dist/plugins/TwoFactorsAuthPlugin/types.js +1 -0
- package/dist/spa/index.html +1 -1
- package/dist/spa/package-lock.json +111 -0
- package/dist/spa/package.json +1 -0
- package/dist/spa/src/App.vue +178 -133
- package/dist/spa/src/components/MenuLink.vue +3 -3
- package/dist/spa/src/components/ResourceForm.vue +96 -96
- package/dist/spa/src/components/ResourceListTable.vue +69 -84
- package/dist/spa/src/components/ValueRenderer.vue +2 -3
- package/dist/spa/src/composables/useStores.ts +24 -11
- package/dist/spa/src/main.ts +1 -1
- package/dist/spa/src/router/index.ts +0 -1
- package/dist/spa/src/utils.ts +1 -1
- package/dist/spa/src/views/CreateView.vue +1 -9
- package/dist/spa/src/views/EditView.vue +1 -9
- package/dist/spa/src/views/ListView.vue +12 -3
- package/dist/spa/src/views/LoginView.vue +6 -1
- package/dist/spa/src/views/ResourceParent.vue +1 -2
- package/dist/spa/src/views/ShowView.vue +6 -14
- package/dist/spa/tailwind.config.js +3 -1
- package/index.ts +94 -35
- package/modules/codeInjector.ts +38 -27
- package/package.json +2 -1
- package/plugins/AuditLogPlugin/index.ts +4 -20
- package/plugins/S3UploadPlugin/custom/s3uploader.vue +120 -0
- package/plugins/S3UploadPlugin/index.ts +112 -0
- package/plugins/S3UploadPlugin/package-lock.json +431 -0
- package/plugins/S3UploadPlugin/package.json +15 -0
- package/plugins/S3UploadPlugin/types.ts +76 -0
- package/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsConfirmation.vue +152 -0
- package/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsOtp.vue +0 -0
- package/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsSetup.vue +188 -0
- package/plugins/TwoFactorsAuthPlugin/index.ts +150 -0
- package/plugins/TwoFactorsAuthPlugin/types.ts +10 -0
- package/servers/express.ts +1 -0
- package/spa/src/App.vue +154 -121
- package/spa/src/components/ResourceForm.vue +91 -91
- package/spa/src/router/index.ts +0 -1
- package/spa/src/utils.ts +1 -1
- package/spa/src/views/CreateView.vue +0 -1
- package/spa/src/views/LoginView.vue +4 -1
- package/types/AdminForthConfig.ts +24 -3
- package/dist/plugins/AuditLog/index.js +0 -13
- package/dist/spa/spa/.eslintrc.cjs +0 -14
- package/dist/spa/spa/.vscode/extensions.json +0 -6
- package/dist/spa/spa/README.md +0 -39
- package/dist/spa/spa/env.d.ts +0 -1
- package/dist/spa/spa/index.html +0 -23
- package/dist/spa/spa/package-lock.json +0 -4470
- package/dist/spa/spa/package.json +0 -48
- package/dist/spa/spa/postcss.config.js +0 -6
- package/dist/spa/spa/public/assets/favicon.png +0 -0
- package/dist/spa/spa/src/App.vue +0 -283
- package/dist/spa/spa/src/assets/logo.svg +0 -19
- package/dist/spa/spa/src/components/AcceptModal.vue +0 -45
- package/dist/spa/spa/src/components/Breadcrumbs.vue +0 -40
- package/dist/spa/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
- package/dist/spa/spa/src/components/CustomDatePicker.vue +0 -174
- package/dist/spa/spa/src/components/CustomDateRangePicker.vue +0 -218
- package/dist/spa/spa/src/components/CustomRangePicker.vue +0 -148
- package/dist/spa/spa/src/components/Dropdown.vue +0 -168
- package/dist/spa/spa/src/components/Filters.vue +0 -211
- package/dist/spa/spa/src/components/HelloWorld.vue +0 -17
- package/dist/spa/spa/src/components/MenuLink.vue +0 -24
- package/dist/spa/spa/src/components/ResourceForm.vue +0 -265
- package/dist/spa/spa/src/components/ResourceListTable.vue +0 -404
- package/dist/spa/spa/src/components/SingleSkeletLoader.vue +0 -13
- package/dist/spa/spa/src/components/Toast.vue +0 -66
- package/dist/spa/spa/src/components/ValueRenderer.vue +0 -59
- package/dist/spa/spa/src/components/icons/IconCalendar.vue +0 -5
- package/dist/spa/spa/src/components/icons/IconCommunity.vue +0 -7
- package/dist/spa/spa/src/components/icons/IconDocumentation.vue +0 -7
- package/dist/spa/spa/src/components/icons/IconEcosystem.vue +0 -7
- package/dist/spa/spa/src/components/icons/IconSupport.vue +0 -7
- package/dist/spa/spa/src/components/icons/IconTime.vue +0 -5
- package/dist/spa/spa/src/components/icons/IconTooling.vue +0 -19
- package/dist/spa/spa/src/composables/useFrontendApi.ts +0 -26
- package/dist/spa/spa/src/composables/useStores.ts +0 -126
- package/dist/spa/spa/src/index.scss +0 -26
- package/dist/spa/spa/src/main.ts +0 -18
- package/dist/spa/spa/src/router/index.ts +0 -64
- package/dist/spa/spa/src/spa_types/core.ts +0 -51
- package/dist/spa/spa/src/stores/core.ts +0 -144
- package/dist/spa/spa/src/stores/filters.ts +0 -22
- package/dist/spa/spa/src/stores/modal.ts +0 -48
- package/dist/spa/spa/src/stores/toast.ts +0 -15
- package/dist/spa/spa/src/stores/user.ts +0 -54
- package/dist/spa/spa/src/utils.ts +0 -101
- package/dist/spa/spa/src/views/CreateView.vue +0 -156
- package/dist/spa/spa/src/views/EditView.vue +0 -157
- package/dist/spa/spa/src/views/ListView.vue +0 -266
- package/dist/spa/spa/src/views/LoginView.vue +0 -136
- package/dist/spa/spa/src/views/ResourceParent.vue +0 -17
- package/dist/spa/spa/src/views/ShowView.vue +0 -180
- package/dist/spa/spa/tailwind.config.js +0 -17
- package/dist/spa/spa/tsconfig.app.json +0 -14
- package/dist/spa/spa/tsconfig.json +0 -11
- package/dist/spa/spa/tsconfig.node.json +0 -19
- package/dist/spa/spa/vite.config.ts +0 -49
- /package/dist/plugins/{AuditLog → S3UploadPlugin}/types.js +0 -0
- /package/dist/{spa/spa/src/assets/base.css → plugins/TwoFactorsAuthPlugin/custom/TwoFactorsOtp.vue} +0 -0
- /package/dist/spa/{spa/src → src}/components/SkeleteLoader.vue +0 -0
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
@update:record="onUpdateRecord"
|
|
47
47
|
@update:isValid="isValid = $event"
|
|
48
48
|
:validating="validating"
|
|
49
|
-
:
|
|
49
|
+
:source="'edit'"
|
|
50
50
|
>
|
|
51
51
|
</ResourceForm>
|
|
52
52
|
|
|
@@ -88,7 +88,6 @@ const loading = ref(false);
|
|
|
88
88
|
const saving = ref(false);
|
|
89
89
|
|
|
90
90
|
const record = ref({});
|
|
91
|
-
let editComponentsPerColumn = {};
|
|
92
91
|
|
|
93
92
|
async function onUpdateRecord(newRecord) {
|
|
94
93
|
record.value = newRecord;
|
|
@@ -119,13 +118,6 @@ onMounted(async () => {
|
|
|
119
118
|
primaryKey: route.params.primaryKey,
|
|
120
119
|
});
|
|
121
120
|
checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'edit');
|
|
122
|
-
|
|
123
|
-
editComponentsPerColumn = coreStore.resource.columns.reduce((acc, column) => {
|
|
124
|
-
if (column.components?.edit) {
|
|
125
|
-
acc[column.name] = getCustomComponent(column.components.edit);
|
|
126
|
-
}
|
|
127
|
-
return acc;
|
|
128
|
-
}, {});
|
|
129
121
|
loading.value = false;
|
|
130
122
|
});
|
|
131
123
|
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
v-if="checkboxes.length"
|
|
23
23
|
data-tooltip-target="tooltip-remove-all"
|
|
24
24
|
data-tooltip-placement="bottom"
|
|
25
|
-
class="flex gap-1 items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded 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-
|
|
25
|
+
class="flex gap-1 items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded 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-darkListTable dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
|
|
26
26
|
>
|
|
27
27
|
<IconBanOutline class="w-5 h-5 "/>
|
|
28
28
|
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
class="bg-red-100 text-red-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400"
|
|
71
71
|
v-if="filtersStore.filters.length">
|
|
72
72
|
{{ filtersStore.filters.length }}
|
|
73
|
-
|
|
73
|
+
</span>
|
|
74
74
|
</button>
|
|
75
75
|
</BreadcrumbsWithButtons>
|
|
76
76
|
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
@update:sort="sort = $event"
|
|
90
90
|
@update:checkboxes="checkboxes = $event"
|
|
91
91
|
@update:records="getList"
|
|
92
|
+
:sort="sort"
|
|
92
93
|
:pageSize="pageSize"
|
|
93
94
|
:totalRows="totalRows"
|
|
94
95
|
:checkboxes="checkboxes"
|
|
@@ -229,6 +230,15 @@ async function init() {
|
|
|
229
230
|
resourceId: route.params.resourceId
|
|
230
231
|
});
|
|
231
232
|
|
|
233
|
+
if (coreStore.resource.options?.defaultSort) {
|
|
234
|
+
sort.value = [{
|
|
235
|
+
field: coreStore.resource.options.defaultSort.columnName,
|
|
236
|
+
direction: coreStore.resource.options.defaultSort.direction
|
|
237
|
+
}];
|
|
238
|
+
} else {
|
|
239
|
+
sort.value = [];
|
|
240
|
+
}
|
|
241
|
+
|
|
232
242
|
await getList();
|
|
233
243
|
columnsMinMax.value = await callAdminForthApi({
|
|
234
244
|
path: '/get_min_max_for_columns',
|
|
@@ -250,7 +260,6 @@ onMounted(async () => {
|
|
|
250
260
|
watch(() => route.params.resourceId, async () => {
|
|
251
261
|
filtersStore.setFilters([]);
|
|
252
262
|
checkboxes.value = [];
|
|
253
|
-
sort.value = [];
|
|
254
263
|
await init();
|
|
255
264
|
});
|
|
256
265
|
|
|
@@ -103,6 +103,8 @@ onMounted(() => {
|
|
|
103
103
|
coreStore.getPublicConfig()
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
+
|
|
107
|
+
|
|
106
108
|
async function login() {
|
|
107
109
|
inProgress.value = true;
|
|
108
110
|
const resp = await callAdminForthApi({
|
|
@@ -116,7 +118,10 @@ async function login() {
|
|
|
116
118
|
inProgress.value = false;
|
|
117
119
|
if (resp.error) {
|
|
118
120
|
error.value = resp.error;
|
|
119
|
-
} else {
|
|
121
|
+
} else if (resp.redirectTo) {
|
|
122
|
+
router.push(resp.redirectTo);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
120
125
|
error.value = null;
|
|
121
126
|
user.authorize()
|
|
122
127
|
router.push('/');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :key="`${$route?.params.resourceId}---${$route?.params.primaryKey}`">
|
|
2
|
+
<div :key="`${$route?.params.resourceId}---${$route?.params.primaryKey}`" class="p-4">
|
|
3
3
|
<RouterView/>
|
|
4
4
|
</div>
|
|
5
5
|
</template>
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
import { useCoreStore } from '@/stores/core';
|
|
14
|
-
import { onMounted } from 'vue';
|
|
15
14
|
|
|
16
15
|
const coreStore = useCoreStore()
|
|
17
16
|
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
</div>
|
|
45
45
|
<div
|
|
46
46
|
v-else
|
|
47
|
-
class="relative overflow-x-auto shadow-
|
|
47
|
+
class="relative overflow-x-auto shadow-resourseFormShadow "
|
|
48
48
|
>
|
|
49
49
|
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 rounded-default">
|
|
50
|
-
<thead class="text-xs text-gray-700 uppercase bg-
|
|
50
|
+
<thead class="text-xs text-gray-700 uppercase bg-lightormHeading dark:bg-gray-700 dark:text-gray-400">
|
|
51
51
|
<tr>
|
|
52
52
|
<th scope="col" class="px-6 py-3">
|
|
53
53
|
Field
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
</thead>
|
|
60
60
|
<tbody>
|
|
61
61
|
<tr v-for="column in coreStore.resource?.columns.filter(c => c.showIn.includes('show'))" :key="column.name"
|
|
62
|
-
class="bg-
|
|
62
|
+
class="bg-lightForm bg-darkForm odd:dark:bg-gray-900 even:dark:bg-gray-800 border-b dark:border-gray-700"
|
|
63
63
|
>
|
|
64
64
|
<component
|
|
65
65
|
v-if="column.components?.showRow"
|
|
@@ -75,8 +75,8 @@
|
|
|
75
75
|
</td>
|
|
76
76
|
<td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap">
|
|
77
77
|
<component
|
|
78
|
-
v-if="
|
|
79
|
-
:is="
|
|
78
|
+
v-if="column?.components?.show"
|
|
79
|
+
:is="getCustomComponent(column?.components?.show)"
|
|
80
80
|
:resource="coreStore.resource"
|
|
81
81
|
:meta="column.components.show.meta"
|
|
82
82
|
:column="column"
|
|
@@ -126,8 +126,7 @@ import {showSuccesTost} from '@/composables/useFrontendApi';
|
|
|
126
126
|
const item = ref(null);
|
|
127
127
|
const route = useRoute();
|
|
128
128
|
const router = useRouter();
|
|
129
|
-
const loading = ref(
|
|
130
|
-
let showComponentsPerColumn = {};
|
|
129
|
+
const loading = ref(true);
|
|
131
130
|
|
|
132
131
|
console.log(route.params,'showWiev');
|
|
133
132
|
|
|
@@ -145,13 +144,6 @@ onMounted(async () => {
|
|
|
145
144
|
primaryKey: route.params.primaryKey,
|
|
146
145
|
});
|
|
147
146
|
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);
|
|
152
|
-
}
|
|
153
|
-
return acc;
|
|
154
|
-
}, {});
|
|
155
147
|
loading.value = false;
|
|
156
148
|
});
|
|
157
149
|
|
package/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { AdminForthConfig, AdminForthClass, AdminForthComponentDeclaration, Admi
|
|
|
20
20
|
AllowedActions,
|
|
21
21
|
ActionCheckSource,
|
|
22
22
|
AdminForthDataSourceConnector,
|
|
23
|
+
BeforeLoginConfirmationFunction
|
|
23
24
|
} from './types/AdminForthConfig.js';
|
|
24
25
|
import path from 'path';
|
|
25
26
|
import AdminForthPlugin from './plugins/base.js';
|
|
@@ -159,7 +160,25 @@ class AdminForth implements AdminForthClass {
|
|
|
159
160
|
this.config.customization = {};
|
|
160
161
|
}
|
|
161
162
|
|
|
163
|
+
if (!this.config.customization.customComponentsDir) {
|
|
164
|
+
this.config.customization.customComponentsDir = './custom';
|
|
165
|
+
}
|
|
162
166
|
|
|
167
|
+
try {
|
|
168
|
+
// check customComponentsDir exists
|
|
169
|
+
fs.accessSync(this.config.customization.customComponentsDir, fs.constants.R_OK);
|
|
170
|
+
} catch (e) {
|
|
171
|
+
this.config.customization.customComponentsDir = undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (this.config.customization.customPages) {
|
|
175
|
+
this.config.customization.customPages.forEach((page, i) => {
|
|
176
|
+
// validate component if it's not plugin injection
|
|
177
|
+
if (this.codeInjector.allComponentNames.hasOwnProperty(page.component as PropertyKey)) {
|
|
178
|
+
const validatedPage = this.validateComponent(page.component, errors, true);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
163
182
|
if (!this.config.baseUrl) {
|
|
164
183
|
this.config.baseUrl = '';
|
|
165
184
|
}
|
|
@@ -342,35 +361,44 @@ class AdminForth implements AdminForthClass {
|
|
|
342
361
|
}
|
|
343
362
|
|
|
344
363
|
// transform all hooks Functions to array of functions
|
|
345
|
-
if (res.hooks) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
364
|
+
if (!res.hooks) {
|
|
365
|
+
res.hooks = {};
|
|
366
|
+
}
|
|
367
|
+
for (const hookName of ['show', 'list']) {
|
|
368
|
+
if (!res.hooks[hookName]) {
|
|
369
|
+
res.hooks[hookName] = {};
|
|
370
|
+
}
|
|
371
|
+
if (!res.hooks[hookName].beforeDatasourceRequest) {
|
|
372
|
+
res.hooks[hookName].beforeDatasourceRequest = [];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (!Array.isArray(res.hooks[hookName].beforeDatasourceRequest)) {
|
|
376
|
+
res.hooks[hookName].beforeDatasourceRequest = [res.hooks[hookName].beforeDatasourceRequest];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (!res.hooks[hookName].afterDatasourceResponse) {
|
|
380
|
+
res.hooks[hookName].afterDatasourceResponse = [];
|
|
359
381
|
}
|
|
360
|
-
for (const value of [res.hooks.create, res.hooks.edit, res.hooks.delete]) {
|
|
361
|
-
if (value) {
|
|
362
382
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
383
|
+
if (!Array.isArray(res.hooks[hookName].afterDatasourceResponse)) {
|
|
384
|
+
res.hooks[hookName].afterDatasourceResponse = [res.hooks[hookName].afterDatasourceResponse];
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
for (const hookName of ['create', 'edit', 'delete']) {
|
|
388
|
+
if (!res.hooks[hookName]) {
|
|
389
|
+
res.hooks[hookName] = {};
|
|
390
|
+
}
|
|
391
|
+
if (!res.hooks[hookName].beforeSave) {
|
|
392
|
+
res.hooks[hookName].beforeSave = [];
|
|
393
|
+
}
|
|
394
|
+
if (!Array.isArray(res.hooks[hookName].beforeSave)) {
|
|
395
|
+
res.hooks[hookName].beforeSave = [res.hooks[hookName].beforeSave];
|
|
396
|
+
}
|
|
397
|
+
if (!res.hooks[hookName].afterSave) {
|
|
398
|
+
res.hooks[hookName].afterSave = [];
|
|
399
|
+
}
|
|
400
|
+
if (!Array.isArray(res.hooks[hookName].afterSave)) {
|
|
401
|
+
res.hooks[hookName].afterSave = [res.hooks[hookName].afterSave];
|
|
374
402
|
}
|
|
375
403
|
}
|
|
376
404
|
});
|
|
@@ -474,6 +502,10 @@ class AdminForth implements AdminForthClass {
|
|
|
474
502
|
column.required = { create: column.required, edit: column.required };
|
|
475
503
|
}
|
|
476
504
|
|
|
505
|
+
if (!column.required) {
|
|
506
|
+
column.required = { create: false, edit: false };
|
|
507
|
+
}
|
|
508
|
+
|
|
477
509
|
// same for editingNote
|
|
478
510
|
if (typeof column.editingNote === 'string') {
|
|
479
511
|
column.editingNote = { create: column.editingNote, edit: column.editingNote };
|
|
@@ -605,6 +637,7 @@ class AdminForth implements AdminForthClass {
|
|
|
605
637
|
await connector.createRecord({ resource, record });
|
|
606
638
|
// execute hook if needed
|
|
607
639
|
for (const hook of listify(resource.hooks?.create?.afterSave as AfterSaveFunction[])) {
|
|
640
|
+
console.log('Hook afterSave', hook);
|
|
608
641
|
const resp = await hook({ resource, record, adminUser });
|
|
609
642
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
610
643
|
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -622,11 +655,16 @@ class AdminForth implements AdminForthClass {
|
|
|
622
655
|
method: 'POST',
|
|
623
656
|
path: '/login',
|
|
624
657
|
handler: async ({ body, response }) => {
|
|
658
|
+
|
|
625
659
|
const INVALID_MESSAGE = 'Invalid username or password';
|
|
626
660
|
const { username, password } = body;
|
|
661
|
+
let adminUser: AdminUser;
|
|
662
|
+
let toReturn: { ok: boolean, redirectTo?: string, allowedLogin:boolean } = { ok: true, allowedLogin:true};
|
|
663
|
+
|
|
627
664
|
let token;
|
|
628
665
|
if (username === this.config.rootUser.username && password === this.config.rootUser.password) {
|
|
629
666
|
this.auth.setAuthCookie({ response, username, pk: null });
|
|
667
|
+
adminUser = { isRoot: true, dbUser: null, pk: null, username: this.config.rootUser.username};
|
|
630
668
|
} else {
|
|
631
669
|
// get resource from db
|
|
632
670
|
if (!this.config.auth) {
|
|
@@ -661,17 +699,39 @@ class AdminForth implements AdminForthClass {
|
|
|
661
699
|
}
|
|
662
700
|
|
|
663
701
|
const passwordHash = userRecord[this.config.auth.passwordHashField];
|
|
664
|
-
console.log('User record', userRecord, passwordHash) // why does it has no hash?
|
|
665
702
|
const valid = await AdminForthAuth.verifyPassword(password, passwordHash);
|
|
666
703
|
if (valid) {
|
|
667
|
-
|
|
704
|
+
adminUser = {
|
|
705
|
+
isRoot: false, dbUser: userRecord,
|
|
706
|
+
pk: userRecord[userResource.columns.find((col) => col.primaryKey).name],
|
|
707
|
+
username
|
|
708
|
+
};
|
|
709
|
+
const beforeLoginConfirmation = this.config.auth.beforeLoginConfirmation as BeforeLoginConfirmationFunction[];
|
|
710
|
+
if (beforeLoginConfirmation.length){
|
|
711
|
+
for (const hook of beforeLoginConfirmation) {
|
|
712
|
+
const resp = await hook({ userRecord:adminUser});
|
|
713
|
+
if (resp?.body?.setCookie){
|
|
714
|
+
resp?.body?.setCookie.forEach((cookie) => {
|
|
715
|
+
this.auth.setCustomCookie({response,payload:cookie})});
|
|
716
|
+
}
|
|
717
|
+
if (resp?.body?.redirectTo) {
|
|
718
|
+
toReturn = {ok:resp.ok, redirectTo:resp?.body?.redirectTo, allowedLogin:resp?.body?.allowedLogin};
|
|
719
|
+
break;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (toReturn.allowedLogin){
|
|
724
|
+
this.auth.setAuthCookie({ response, username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name] });
|
|
725
|
+
}
|
|
668
726
|
} else {
|
|
669
727
|
return { error: INVALID_MESSAGE };
|
|
670
728
|
}
|
|
729
|
+
|
|
671
730
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
}
|
|
731
|
+
|
|
732
|
+
return toReturn;
|
|
733
|
+
}
|
|
734
|
+
});
|
|
675
735
|
|
|
676
736
|
server.endpoint({
|
|
677
737
|
method: 'POST',
|
|
@@ -686,7 +746,7 @@ class AdminForth implements AdminForthClass {
|
|
|
686
746
|
method: 'POST',
|
|
687
747
|
path: '/logout',
|
|
688
748
|
handler: async ({ response }) => {
|
|
689
|
-
this.auth.removeAuthCookie(
|
|
749
|
+
this.auth.removeAuthCookie( response );
|
|
690
750
|
return { ok: true };
|
|
691
751
|
},
|
|
692
752
|
})
|
|
@@ -763,7 +823,6 @@ class AdminForth implements AdminForthClass {
|
|
|
763
823
|
}
|
|
764
824
|
newMenu.push(newMenuItem)
|
|
765
825
|
}
|
|
766
|
-
console.log(userData,'this is the user data')
|
|
767
826
|
|
|
768
827
|
return {
|
|
769
828
|
user: userData,
|
|
@@ -1170,7 +1229,7 @@ class AdminForth implements AdminForthClass {
|
|
|
1170
1229
|
}
|
|
1171
1230
|
|
|
1172
1231
|
if (Object.keys(newValues).length > 0) {
|
|
1173
|
-
await connector.updateRecord({ resource, recordId,
|
|
1232
|
+
await connector.updateRecord({ resource, recordId, newValues});
|
|
1174
1233
|
}
|
|
1175
1234
|
|
|
1176
1235
|
// execute hook if needed
|
package/modules/codeInjector.ts
CHANGED
|
@@ -118,51 +118,62 @@ class CodeInjector implements CodeInjectorType {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
if (item.component) {
|
|
121
|
-
if(Object.keys(item).includes('isStaticRoute')){
|
|
122
|
-
if(!item.isStaticRoute){
|
|
123
|
-
routes += `{
|
|
124
|
-
path: '${item.path}',
|
|
125
|
-
name: '${item.path}',
|
|
126
|
-
component: () => import('${item.component}'),
|
|
127
|
-
meta: { title: '${item?.meta?.title || item.path.replace('/', '')}'}
|
|
128
|
-
},\n`} else {
|
|
121
|
+
if(Object.keys(item).includes('isStaticRoute')) {
|
|
122
|
+
if(!item.isStaticRoute) {
|
|
129
123
|
routes += `{
|
|
130
124
|
path: '${item.path}',
|
|
131
125
|
name: '${item.path}',
|
|
132
|
-
component: ${
|
|
133
|
-
meta: { title: '${item?.meta?.title|| item.path.replace('/', '')}'}
|
|
126
|
+
component: () => import('${item.component}'),
|
|
127
|
+
meta: { title: '${item?.meta?.title || item.path.replace('/', '')}'}
|
|
134
128
|
},\n`
|
|
135
|
-
|
|
136
|
-
routerComponents += `import ${componentName} from '${item.component}';\n`;
|
|
137
|
-
}
|
|
138
|
-
} else{
|
|
139
|
-
if(item.homepage){
|
|
129
|
+
} else {
|
|
140
130
|
routes += `{
|
|
141
131
|
path: '${item.path}',
|
|
142
132
|
name: '${item.path}',
|
|
143
133
|
component: ${getComponentNameFromPath(item.component)},
|
|
144
|
-
meta: { title: '${item?.meta?.title
|
|
134
|
+
meta: { title: '${item?.meta?.title|| item.path.replace('/', '')}'}
|
|
145
135
|
},\n`
|
|
146
136
|
const componentName = `${getComponentNameFromPath(item.component)}`;
|
|
147
|
-
routerComponents += `import ${componentName} from '${item.component}';\n`;
|
|
148
|
-
else {
|
|
149
|
-
routes += `{
|
|
150
|
-
path: '${item.path}',
|
|
151
|
-
name: '${item.path}',
|
|
152
|
-
component: () => import('${item.component}'),
|
|
153
|
-
meta: { title: '${item?.meta?.title || item.path.replace('/', '')}'}
|
|
154
|
-
},\n`
|
|
155
|
-
|
|
137
|
+
routerComponents += `import ${componentName} from '${item.component}';\n`;
|
|
156
138
|
}
|
|
157
|
-
|
|
139
|
+
} else {
|
|
140
|
+
if (item.homepage) {
|
|
141
|
+
routes += `{
|
|
142
|
+
path: '${item.path}',
|
|
143
|
+
name: '${item.path}',
|
|
144
|
+
component: ${getComponentNameFromPath(item.component)},
|
|
145
|
+
meta: { title: '${item?.meta?.title || item.path.replace('/', '')}'}
|
|
146
|
+
},\n`
|
|
147
|
+
const componentName = `${getComponentNameFromPath(item.component)}`;
|
|
148
|
+
routerComponents += `import ${componentName} from '${item.component}';\n`;
|
|
149
|
+
} else {
|
|
150
|
+
routes += `{
|
|
151
|
+
path: '${item.path}',
|
|
152
|
+
name: '${item.path}',
|
|
153
|
+
component: () => import('${item.component}'),
|
|
154
|
+
meta: { title: '${item?.meta?.title || item.path.replace('/', '')}'}
|
|
155
|
+
},\n`
|
|
156
|
+
}
|
|
158
157
|
}
|
|
159
|
-
|
|
160
158
|
}
|
|
161
159
|
if (item.children) {
|
|
162
160
|
collectAssetsFromMenu(item.children);
|
|
163
161
|
}
|
|
164
162
|
});
|
|
165
163
|
};
|
|
164
|
+
const registerCustomPages = (config) => {
|
|
165
|
+
if (config.customization.customPages) {
|
|
166
|
+
config.customization.customPages.forEach((page) => {
|
|
167
|
+
routes += `{
|
|
168
|
+
path: '${page.path}',
|
|
169
|
+
name: '${page.path}',
|
|
170
|
+
component: () => import('${page?.component?.file || page.component}'),
|
|
171
|
+
meta: { title: '${page.meta?.title || page.path.replace('/', '')}',customLayout:${page?.component?.meta?.customLayout }}
|
|
172
|
+
},`})
|
|
173
|
+
|
|
174
|
+
}}
|
|
175
|
+
|
|
176
|
+
registerCustomPages(this.adminforth.config);
|
|
166
177
|
collectAssetsFromMenu(this.adminforth.config.menu);
|
|
167
178
|
|
|
168
179
|
if (filesUpdated) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adminforth",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.75",
|
|
4
4
|
"description": "OpenSource Vue3 powered forth-generation admin panel",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"fs-extra": "^11.2.0",
|
|
25
25
|
"jsonwebtoken": "^9.0.2",
|
|
26
26
|
"mongodb": "6.6",
|
|
27
|
+
"node-2fa": "^2.0.3",
|
|
27
28
|
"node-fetch": "^3.3.2",
|
|
28
29
|
"pg": "^8.11.5",
|
|
29
30
|
"request": "^2.88.2",
|
|
@@ -34,9 +34,9 @@ export default class AuditLogPlugin extends AdminForthPlugin {
|
|
|
34
34
|
|
|
35
35
|
const newRecord = action == AllowedActionsEnum.delete ? {} : (await connector.getRecordByPrimaryKey(resource, recordId)) || {};
|
|
36
36
|
if (action !== AllowedActionsEnum.delete) {
|
|
37
|
-
|
|
37
|
+
oldRecord = oldRecord ? JSON.parse(JSON.stringify(oldRecord)) : {};
|
|
38
38
|
} else {
|
|
39
|
-
|
|
39
|
+
oldRecord = data
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
if (action !== AllowedActionsEnum.delete) {
|
|
@@ -114,24 +114,8 @@ export default class AuditLogPlugin extends AdminForthPlugin {
|
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
edit: { afterSave: [] },
|
|
120
|
-
delete: { beforeSave: [] }
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if ( !resource.hooks ) {
|
|
124
|
-
resource.hooks = defaultHooks;
|
|
125
|
-
} else {
|
|
126
|
-
resource.hooks = {...defaultHooks, ...resource.hooks}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
Object.keys(resource.hooks).forEach((hook) => {
|
|
130
|
-
const hookToUse = hook == AllowedActionsEnum.delete ? 'beforeSave' : 'afterSave';
|
|
131
|
-
if(!Array.isArray(resource.hooks[hook][hookToUse])){
|
|
132
|
-
resource.hooks[hook][hookToUse] = [resource.hooks[hook][hookToUse]]
|
|
133
|
-
}
|
|
134
|
-
resource.hooks[hook][hookToUse].push(async ({resource, record, adminUser, oldRecord}) => {
|
|
117
|
+
['edit', 'create', 'delete'].forEach((hook) => {
|
|
118
|
+
resource.hooks[hook].afterSave.push(async ({resource, record, adminUser, oldRecord}) => {
|
|
135
119
|
return await this.createLogRecord(resource, hook as AllowedActionsEnum, record, adminUser, oldRecord)
|
|
136
120
|
})
|
|
137
121
|
})
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex items-center justify-center w-full">
|
|
3
|
+
<label for="dropzone-file" class="flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-gray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600">
|
|
4
|
+
<div class="flex flex-col items-center justify-center pt-5 pb-6">
|
|
5
|
+
<img v-if="imgPreview" :src="imgPreview" class="w-100 mt-4 rounded-lg h-40 object-contain" />
|
|
6
|
+
|
|
7
|
+
<svg v-else class="w-8 h-8 mb-4 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16">
|
|
8
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"/>
|
|
9
|
+
</svg>
|
|
10
|
+
<p class="mb-2 text-sm text-gray-500 dark:text-gray-400"><span class="font-semibold">Click to upload</span> or drag and drop</p>
|
|
11
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">
|
|
12
|
+
{{ allowedExtensionsLabel }} {{ meta.maxFileSize ? `(up to ${maxFileSizeHumanized})` : '' }}
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<div class="w-full bg-gray-200 rounded-full dark:bg-gray-700 mt-2 mb-2" v-if="progress > 0">
|
|
16
|
+
<div class="bg-blue-600 text-xs font-medium text-blue-100 text-center p-0.5 leading-none rounded-full"
|
|
17
|
+
:style="{width: `${progress}%`}">{{ progress }}%
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<input id="dropzone-file" type="file" class="hidden" @change="onFileChange" />
|
|
22
|
+
</label>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup>
|
|
28
|
+
import { computed, ref } from 'vue'
|
|
29
|
+
|
|
30
|
+
const props = defineProps({
|
|
31
|
+
meta: String
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const imgPreview = ref(null);
|
|
35
|
+
const progress = ref(0);
|
|
36
|
+
|
|
37
|
+
const allowedExtensionsLabel = computed(() => {
|
|
38
|
+
const allowedExtensions = props.meta.allowedExtensions || []
|
|
39
|
+
if (allowedExtensions.length === 0) {
|
|
40
|
+
return 'Any file type'
|
|
41
|
+
}
|
|
42
|
+
// make upper case and write in format EXT1, EXT2 or EXT3
|
|
43
|
+
let label = allowedExtensions.map(ext => ext.toUpperCase()).join(', ');
|
|
44
|
+
// last comma to 'or'
|
|
45
|
+
label = label.replace(/,([^,]*)$/, ' or$1')
|
|
46
|
+
return label
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const maxFileSizeHumanized = computed(() => {
|
|
50
|
+
let maxFileSize = props.meta.maxFileSize || 0
|
|
51
|
+
if (maxFileSize === 0) {
|
|
52
|
+
return ''
|
|
53
|
+
}
|
|
54
|
+
// if maxFileSize is in bytes, convert to KB, MB, GB, TB
|
|
55
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
56
|
+
let i = 0
|
|
57
|
+
while (maxFileSize >= 1024 && i < units.length - 1) {
|
|
58
|
+
maxFileSize /= 1024
|
|
59
|
+
i++
|
|
60
|
+
}
|
|
61
|
+
return `${maxFileSize.toFixed(2)} ${units[i]}`
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const onFileChange = (e) => {
|
|
65
|
+
const file = e.target.files[0]
|
|
66
|
+
if (!file) {
|
|
67
|
+
imgPreview.value = null;
|
|
68
|
+
progress.value = 0;
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
console.log('File selected:', file);
|
|
72
|
+
|
|
73
|
+
// get filename, extension, size, mimeType
|
|
74
|
+
const { name, size, type } = file;
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
const extension = name.split('.').pop();
|
|
78
|
+
console.log('File details:', { name, extension, size, type });
|
|
79
|
+
// validate file extension
|
|
80
|
+
const allowedExtensions = props.meta.allowedExtensions || []
|
|
81
|
+
if (allowedExtensions.length > 0 && !allowedExtensions.includes(extension)) {
|
|
82
|
+
window.adminforth.alert({
|
|
83
|
+
message: `Sorry but the file type ${extension} is not allowed. Please upload a file with one of the following extensions: ${allowedExtensionsLabel.value}`,
|
|
84
|
+
variant: 'danger'
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// validate file size
|
|
90
|
+
if (props.meta.maxFileSize && size > props.meta.maxFileSize) {
|
|
91
|
+
window.adminforth.alert({
|
|
92
|
+
message: `Sorry but the file size is too large. Please upload a file with a maximum size of ${maxFileSizeHumanized.value}`,
|
|
93
|
+
variant: 'danger'
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// supports preview
|
|
98
|
+
if (type.startsWith('image/')) {
|
|
99
|
+
const reader = new FileReader();
|
|
100
|
+
reader.onload = (e) => {
|
|
101
|
+
imgPreview.value = e.target.result;
|
|
102
|
+
}
|
|
103
|
+
reader.readAsDataURL(file);
|
|
104
|
+
}
|
|
105
|
+
setInterval(() => {
|
|
106
|
+
progress.value += 1;
|
|
107
|
+
}, 20);
|
|
108
|
+
|
|
109
|
+
await callAdminForthApi({
|
|
110
|
+
path: `/plugin/${props.meta.pluginInstanceId}/get_s3_upload_url`,
|
|
111
|
+
method: 'POST',
|
|
112
|
+
body: {
|
|
113
|
+
originalFilename: name,
|
|
114
|
+
contentType, size, originalExtension
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
</script>
|