adminforth 1.1.73 → 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/baseConnector.ts +107 -0
- package/dataConnectors/mongo.ts +6 -12
- package/dataConnectors/postgres.ts +17 -37
- package/dataConnectors/sqlite.ts +44 -55
- package/dist/auth.js +7 -0
- package/dist/dataConnectors/baseConnector.js +90 -0
- package/dist/dataConnectors/mongo.js +6 -12
- package/dist/dataConnectors/postgres.js +12 -33
- package/dist/dataConnectors/sqlite.js +67 -83
- package/dist/index.js +125 -66
- 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 +103 -36
- 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 +166 -5
- 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
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import ExpressServer from './servers/express.js';
|
|
|
23
23
|
import { v1 as uuid } from 'uuid';
|
|
24
24
|
import fs from 'fs';
|
|
25
25
|
import { ADMINFORTH_VERSION, listify } from './modules/utils.js';
|
|
26
|
-
import { AdminForthFilterOperators, AdminForthDataTypes, AdminForthResourcePages, AllowedActionsEnum, ActionCheckSource
|
|
26
|
+
import { AdminForthFilterOperators, AdminForthDataTypes, AdminForthResourcePages, AllowedActionsEnum, ActionCheckSource } from './types/AdminForthConfig.js';
|
|
27
27
|
import path from 'path';
|
|
28
28
|
//get array from enum AdminForthResourcePages
|
|
29
29
|
class AdminForth {
|
|
@@ -120,6 +120,24 @@ class AdminForth {
|
|
|
120
120
|
if (!this.config.customization) {
|
|
121
121
|
this.config.customization = {};
|
|
122
122
|
}
|
|
123
|
+
if (!this.config.customization.customComponentsDir) {
|
|
124
|
+
this.config.customization.customComponentsDir = './custom';
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
// check customComponentsDir exists
|
|
128
|
+
fs.accessSync(this.config.customization.customComponentsDir, fs.constants.R_OK);
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
this.config.customization.customComponentsDir = undefined;
|
|
132
|
+
}
|
|
133
|
+
if (this.config.customization.customPages) {
|
|
134
|
+
this.config.customization.customPages.forEach((page, i) => {
|
|
135
|
+
// validate component if it's not plugin injection
|
|
136
|
+
if (this.codeInjector.allComponentNames.hasOwnProperty(page.component)) {
|
|
137
|
+
const validatedPage = this.validateComponent(page.component, errors, true);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
123
141
|
if (!this.config.baseUrl) {
|
|
124
142
|
this.config.baseUrl = '';
|
|
125
143
|
}
|
|
@@ -287,34 +305,41 @@ class AdminForth {
|
|
|
287
305
|
});
|
|
288
306
|
}
|
|
289
307
|
// transform all hooks Functions to array of functions
|
|
290
|
-
if (res.hooks) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
if (value.afterDatasourceResponse) {
|
|
299
|
-
if (!Array.isArray(value.afterDatasourceResponse)) {
|
|
300
|
-
value.afterDatasourceResponse = [value.afterDatasourceResponse];
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
308
|
+
if (!res.hooks) {
|
|
309
|
+
res.hooks = {};
|
|
310
|
+
}
|
|
311
|
+
for (const hookName of ['show', 'list']) {
|
|
312
|
+
if (!res.hooks[hookName]) {
|
|
313
|
+
res.hooks[hookName] = {};
|
|
304
314
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
315
|
+
if (!res.hooks[hookName].beforeDatasourceRequest) {
|
|
316
|
+
res.hooks[hookName].beforeDatasourceRequest = [];
|
|
317
|
+
}
|
|
318
|
+
if (!Array.isArray(res.hooks[hookName].beforeDatasourceRequest)) {
|
|
319
|
+
res.hooks[hookName].beforeDatasourceRequest = [res.hooks[hookName].beforeDatasourceRequest];
|
|
320
|
+
}
|
|
321
|
+
if (!res.hooks[hookName].afterDatasourceResponse) {
|
|
322
|
+
res.hooks[hookName].afterDatasourceResponse = [];
|
|
323
|
+
}
|
|
324
|
+
if (!Array.isArray(res.hooks[hookName].afterDatasourceResponse)) {
|
|
325
|
+
res.hooks[hookName].afterDatasourceResponse = [res.hooks[hookName].afterDatasourceResponse];
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
for (const hookName of ['create', 'edit', 'delete']) {
|
|
329
|
+
if (!res.hooks[hookName]) {
|
|
330
|
+
res.hooks[hookName] = {};
|
|
331
|
+
}
|
|
332
|
+
if (!res.hooks[hookName].beforeSave) {
|
|
333
|
+
res.hooks[hookName].beforeSave = [];
|
|
334
|
+
}
|
|
335
|
+
if (!Array.isArray(res.hooks[hookName].beforeSave)) {
|
|
336
|
+
res.hooks[hookName].beforeSave = [res.hooks[hookName].beforeSave];
|
|
337
|
+
}
|
|
338
|
+
if (!res.hooks[hookName].afterSave) {
|
|
339
|
+
res.hooks[hookName].afterSave = [];
|
|
340
|
+
}
|
|
341
|
+
if (!Array.isArray(res.hooks[hookName].afterSave)) {
|
|
342
|
+
res.hooks[hookName].afterSave = [res.hooks[hookName].afterSave];
|
|
318
343
|
}
|
|
319
344
|
}
|
|
320
345
|
});
|
|
@@ -397,6 +422,9 @@ class AdminForth {
|
|
|
397
422
|
if (typeof column.required === 'boolean') {
|
|
398
423
|
column.required = { create: column.required, edit: column.required };
|
|
399
424
|
}
|
|
425
|
+
if (!column.required) {
|
|
426
|
+
column.required = { create: false, edit: false };
|
|
427
|
+
}
|
|
400
428
|
// same for editingNote
|
|
401
429
|
if (typeof column.editingNote === 'string') {
|
|
402
430
|
column.editingNote = { create: column.editingNote, edit: column.editingNote };
|
|
@@ -520,6 +548,7 @@ class AdminForth {
|
|
|
520
548
|
yield connector.createRecord({ resource, record });
|
|
521
549
|
// execute hook if needed
|
|
522
550
|
for (const hook of listify((_g = (_f = resource.hooks) === null || _f === void 0 ? void 0 : _f.create) === null || _g === void 0 ? void 0 : _g.afterSave)) {
|
|
551
|
+
console.log('Hook afterSave', hook);
|
|
523
552
|
const resp = yield hook({ resource, record, adminUser });
|
|
524
553
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
525
554
|
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -536,12 +565,15 @@ class AdminForth {
|
|
|
536
565
|
method: 'POST',
|
|
537
566
|
path: '/login',
|
|
538
567
|
handler: (_b) => __awaiter(this, [_b], void 0, function* ({ body, response }) {
|
|
539
|
-
var _c;
|
|
568
|
+
var _c, _d, _e, _f, _g, _h;
|
|
540
569
|
const INVALID_MESSAGE = 'Invalid username or password';
|
|
541
570
|
const { username, password } = body;
|
|
571
|
+
let adminUser;
|
|
572
|
+
let toReturn = { ok: true, allowedLogin: true };
|
|
542
573
|
let token;
|
|
543
574
|
if (username === this.config.rootUser.username && password === this.config.rootUser.password) {
|
|
544
575
|
this.auth.setAuthCookie({ response, username, pk: null });
|
|
576
|
+
adminUser = { isRoot: true, dbUser: null, pk: null, username: this.config.rootUser.username };
|
|
545
577
|
}
|
|
546
578
|
else {
|
|
547
579
|
// get resource from db
|
|
@@ -572,22 +604,43 @@ class AdminForth {
|
|
|
572
604
|
return { error: 'User not found' };
|
|
573
605
|
}
|
|
574
606
|
const passwordHash = userRecord[this.config.auth.passwordHashField];
|
|
575
|
-
console.log('User record', userRecord, passwordHash); // why does it has no hash?
|
|
576
607
|
const valid = yield AdminForthAuth.verifyPassword(password, passwordHash);
|
|
577
608
|
if (valid) {
|
|
578
|
-
|
|
609
|
+
adminUser = {
|
|
610
|
+
isRoot: false, dbUser: userRecord,
|
|
611
|
+
pk: userRecord[userResource.columns.find((col) => col.primaryKey).name],
|
|
612
|
+
username
|
|
613
|
+
};
|
|
614
|
+
const beforeLoginConfirmation = this.config.auth.beforeLoginConfirmation;
|
|
615
|
+
if (beforeLoginConfirmation.length) {
|
|
616
|
+
for (const hook of beforeLoginConfirmation) {
|
|
617
|
+
const resp = yield hook({ userRecord: adminUser });
|
|
618
|
+
if ((_d = resp === null || resp === void 0 ? void 0 : resp.body) === null || _d === void 0 ? void 0 : _d.setCookie) {
|
|
619
|
+
(_e = resp === null || resp === void 0 ? void 0 : resp.body) === null || _e === void 0 ? void 0 : _e.setCookie.forEach((cookie) => {
|
|
620
|
+
this.auth.setCustomCookie({ response, payload: cookie });
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
if ((_f = resp === null || resp === void 0 ? void 0 : resp.body) === null || _f === void 0 ? void 0 : _f.redirectTo) {
|
|
624
|
+
toReturn = { ok: resp.ok, redirectTo: (_g = resp === null || resp === void 0 ? void 0 : resp.body) === null || _g === void 0 ? void 0 : _g.redirectTo, allowedLogin: (_h = resp === null || resp === void 0 ? void 0 : resp.body) === null || _h === void 0 ? void 0 : _h.allowedLogin };
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (toReturn.allowedLogin) {
|
|
630
|
+
this.auth.setAuthCookie({ response, username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name] });
|
|
631
|
+
}
|
|
579
632
|
}
|
|
580
633
|
else {
|
|
581
634
|
return { error: INVALID_MESSAGE };
|
|
582
635
|
}
|
|
583
636
|
}
|
|
584
|
-
return
|
|
585
|
-
})
|
|
637
|
+
return toReturn;
|
|
638
|
+
})
|
|
586
639
|
});
|
|
587
640
|
server.endpoint({
|
|
588
641
|
method: 'POST',
|
|
589
642
|
path: '/check_auth',
|
|
590
|
-
handler: (
|
|
643
|
+
handler: (_j) => __awaiter(this, [_j], void 0, function* ({ adminUser }) {
|
|
591
644
|
return { ok: true };
|
|
592
645
|
}),
|
|
593
646
|
});
|
|
@@ -595,8 +648,8 @@ class AdminForth {
|
|
|
595
648
|
noAuth: true,
|
|
596
649
|
method: 'POST',
|
|
597
650
|
path: '/logout',
|
|
598
|
-
handler: (
|
|
599
|
-
this.auth.removeAuthCookie(
|
|
651
|
+
handler: (_k) => __awaiter(this, [_k], void 0, function* ({ response }) {
|
|
652
|
+
this.auth.removeAuthCookie(response);
|
|
600
653
|
return { ok: true };
|
|
601
654
|
}),
|
|
602
655
|
});
|
|
@@ -604,8 +657,8 @@ class AdminForth {
|
|
|
604
657
|
noAuth: true,
|
|
605
658
|
method: 'GET',
|
|
606
659
|
path: '/get_public_config',
|
|
607
|
-
handler: (
|
|
608
|
-
var
|
|
660
|
+
handler: (_l) => __awaiter(this, [_l], void 0, function* ({ body }) {
|
|
661
|
+
var _m;
|
|
609
662
|
// find resource
|
|
610
663
|
if (!this.config.auth) {
|
|
611
664
|
throw new Error('No config.auth defined');
|
|
@@ -617,15 +670,15 @@ class AdminForth {
|
|
|
617
670
|
brandName: this.config.customization.brandName,
|
|
618
671
|
usernameFieldName: usernameColumn.label,
|
|
619
672
|
loginBackgroundImage: this.config.auth.loginBackgroundImage,
|
|
620
|
-
title: (
|
|
673
|
+
title: (_m = this.config.customization) === null || _m === void 0 ? void 0 : _m.title,
|
|
621
674
|
};
|
|
622
675
|
}),
|
|
623
676
|
});
|
|
624
677
|
server.endpoint({
|
|
625
678
|
method: 'GET',
|
|
626
679
|
path: '/get_base_config',
|
|
627
|
-
handler: (
|
|
628
|
-
var
|
|
680
|
+
handler: (_o) => __awaiter(this, [_o], void 0, function* ({ input, adminUser, cookies }) {
|
|
681
|
+
var _p, _q;
|
|
629
682
|
let username = '';
|
|
630
683
|
let userFullName = '';
|
|
631
684
|
if (adminUser.isRoot) {
|
|
@@ -672,7 +725,6 @@ class AdminForth {
|
|
|
672
725
|
}
|
|
673
726
|
newMenu.push(newMenuItem);
|
|
674
727
|
}
|
|
675
|
-
console.log(userData, 'this is the user data');
|
|
676
728
|
return {
|
|
677
729
|
user: userData,
|
|
678
730
|
resources: this.config.resources.map((res) => ({
|
|
@@ -687,8 +739,8 @@ class AdminForth {
|
|
|
687
739
|
deleteConfirmation: this.config.deleteConfirmation,
|
|
688
740
|
auth: this.config.auth,
|
|
689
741
|
usernameField: this.config.auth.usernameField,
|
|
690
|
-
title: (
|
|
691
|
-
emptyFieldPlaceholder: (
|
|
742
|
+
title: (_p = this.config.customization) === null || _p === void 0 ? void 0 : _p.title,
|
|
743
|
+
emptyFieldPlaceholder: (_q = this.config.customization) === null || _q === void 0 ? void 0 : _q.emptyFieldPlaceholder,
|
|
692
744
|
},
|
|
693
745
|
adminUser,
|
|
694
746
|
version: ADMINFORTH_VERSION,
|
|
@@ -727,7 +779,7 @@ class AdminForth {
|
|
|
727
779
|
server.endpoint({
|
|
728
780
|
method: 'POST',
|
|
729
781
|
path: '/get_resource',
|
|
730
|
-
handler: (
|
|
782
|
+
handler: (_r) => __awaiter(this, [_r], void 0, function* ({ body, adminUser }) {
|
|
731
783
|
const { resourceId } = body;
|
|
732
784
|
if (!this.statuses.dbDiscover) {
|
|
733
785
|
return { error: 'Database discovery not started' };
|
|
@@ -749,8 +801,8 @@ class AdminForth {
|
|
|
749
801
|
server.endpoint({
|
|
750
802
|
method: 'POST',
|
|
751
803
|
path: '/get_resource_data',
|
|
752
|
-
handler: (
|
|
753
|
-
var
|
|
804
|
+
handler: (_s) => __awaiter(this, [_s], void 0, function* ({ body, adminUser }) {
|
|
805
|
+
var _t, _u, _v, _w;
|
|
754
806
|
const { resourceId, source } = body;
|
|
755
807
|
if (['show', 'list'].includes(source) === false) {
|
|
756
808
|
return { error: 'Invalid source, should be list or show' };
|
|
@@ -770,7 +822,7 @@ class AdminForth {
|
|
|
770
822
|
if (!allowed) {
|
|
771
823
|
return { error };
|
|
772
824
|
}
|
|
773
|
-
for (const hook of listify((
|
|
825
|
+
for (const hook of listify((_u = (_t = resource.hooks) === null || _t === void 0 ? void 0 : _t[source]) === null || _u === void 0 ? void 0 : _u.beforeDatasourceRequest)) {
|
|
774
826
|
const resp = yield hook({ resource, query: body, adminUser });
|
|
775
827
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
776
828
|
throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -837,7 +889,7 @@ class AdminForth {
|
|
|
837
889
|
item[col.name] = targetDataMap[item[col.name]];
|
|
838
890
|
});
|
|
839
891
|
})));
|
|
840
|
-
for (const hook of listify((
|
|
892
|
+
for (const hook of listify((_w = (_v = resource.hooks) === null || _v === void 0 ? void 0 : _v[source]) === null || _w === void 0 ? void 0 : _w.afterDatasourceResponse)) {
|
|
841
893
|
const resp = yield hook({ resource, response: data.data, adminUser });
|
|
842
894
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
843
895
|
throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -863,8 +915,8 @@ class AdminForth {
|
|
|
863
915
|
server.endpoint({
|
|
864
916
|
method: 'POST',
|
|
865
917
|
path: '/get_resource_foreign_data',
|
|
866
|
-
handler: (
|
|
867
|
-
var
|
|
918
|
+
handler: (_x) => __awaiter(this, [_x], void 0, function* ({ body, adminUser }) {
|
|
919
|
+
var _y, _z, _0, _1;
|
|
868
920
|
const { resourceId, column } = body;
|
|
869
921
|
if (!this.statuses.dbDiscover) {
|
|
870
922
|
return { error: 'Database discovery not started' };
|
|
@@ -885,7 +937,7 @@ class AdminForth {
|
|
|
885
937
|
}
|
|
886
938
|
const targetResourceId = columnConfig.foreignResource.resourceId;
|
|
887
939
|
const targetResource = this.config.resources.find((res) => res.resourceId == targetResourceId);
|
|
888
|
-
for (const hook of listify((
|
|
940
|
+
for (const hook of listify((_z = (_y = columnConfig.foreignResource.hooks) === null || _y === void 0 ? void 0 : _y.dropdownList) === null || _z === void 0 ? void 0 : _z.beforeDatasourceRequest)) {
|
|
889
941
|
const resp = yield hook({ query: body, adminUser, resource: targetResource });
|
|
890
942
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
891
943
|
throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -914,7 +966,7 @@ class AdminForth {
|
|
|
914
966
|
const response = {
|
|
915
967
|
items
|
|
916
968
|
};
|
|
917
|
-
for (const hook of listify((
|
|
969
|
+
for (const hook of listify((_1 = (_0 = columnConfig.foreignResource.hooks) === null || _0 === void 0 ? void 0 : _0.dropdownList) === null || _1 === void 0 ? void 0 : _1.afterDatasourceResponse)) {
|
|
918
970
|
const resp = yield hook({ response, adminUser, resource: targetResource });
|
|
919
971
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
920
972
|
throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -929,7 +981,7 @@ class AdminForth {
|
|
|
929
981
|
server.endpoint({
|
|
930
982
|
method: 'POST',
|
|
931
983
|
path: '/get_min_max_for_columns',
|
|
932
|
-
handler: (
|
|
984
|
+
handler: (_2) => __awaiter(this, [_2], void 0, function* ({ body }) {
|
|
933
985
|
const { resourceId } = body;
|
|
934
986
|
if (!this.statuses.dbDiscover) {
|
|
935
987
|
return { error: 'Database discovery not started' };
|
|
@@ -958,7 +1010,7 @@ class AdminForth {
|
|
|
958
1010
|
server.endpoint({
|
|
959
1011
|
method: 'POST',
|
|
960
1012
|
path: '/create_record',
|
|
961
|
-
handler: (
|
|
1013
|
+
handler: (_3) => __awaiter(this, [_3], void 0, function* ({ body, adminUser }) {
|
|
962
1014
|
const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
963
1015
|
if (!resource) {
|
|
964
1016
|
return { error: `Resource '${body['resourceId']}' not found` };
|
|
@@ -968,7 +1020,14 @@ class AdminForth {
|
|
|
968
1020
|
if (!allowed) {
|
|
969
1021
|
return { error };
|
|
970
1022
|
}
|
|
971
|
-
|
|
1023
|
+
const { record } = body;
|
|
1024
|
+
// call setFieldValue for each column
|
|
1025
|
+
for (const column of resource.columns) {
|
|
1026
|
+
if (record[column.name] !== undefined) {
|
|
1027
|
+
record[column.name] = this.connectors[resource.dataSource].setFieldValue(column, record[column.name]);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
yield this.createResourceRecord({ resource, record, adminUser });
|
|
972
1031
|
const connector = this.connectors[resource.dataSource];
|
|
973
1032
|
return {
|
|
974
1033
|
newRecordId: body['record'][connector.getPrimaryKey(resource)]
|
|
@@ -978,8 +1037,8 @@ class AdminForth {
|
|
|
978
1037
|
server.endpoint({
|
|
979
1038
|
method: 'POST',
|
|
980
1039
|
path: '/update_record',
|
|
981
|
-
handler: (
|
|
982
|
-
var
|
|
1040
|
+
handler: (_4) => __awaiter(this, [_4], void 0, function* ({ body, adminUser }) {
|
|
1041
|
+
var _5, _6, _7, _8;
|
|
983
1042
|
const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
984
1043
|
if (!resource) {
|
|
985
1044
|
return { error: `Resource '${body['resourceId']}' not found` };
|
|
@@ -998,7 +1057,7 @@ class AdminForth {
|
|
|
998
1057
|
return { error };
|
|
999
1058
|
}
|
|
1000
1059
|
// execute hook if needed
|
|
1001
|
-
for (const hook of listify((
|
|
1060
|
+
for (const hook of listify((_6 = (_5 = resource.hooks) === null || _5 === void 0 ? void 0 : _5.edit) === null || _6 === void 0 ? void 0 : _6.beforeSave)) {
|
|
1002
1061
|
const resp = yield hook({ resource, record, adminUser });
|
|
1003
1062
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
1004
1063
|
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -1022,10 +1081,10 @@ class AdminForth {
|
|
|
1022
1081
|
}
|
|
1023
1082
|
}
|
|
1024
1083
|
if (Object.keys(newValues).length > 0) {
|
|
1025
|
-
yield connector.updateRecord({ resource, recordId,
|
|
1084
|
+
yield connector.updateRecord({ resource, recordId, newValues });
|
|
1026
1085
|
}
|
|
1027
1086
|
// execute hook if needed
|
|
1028
|
-
for (const hook of listify((
|
|
1087
|
+
for (const hook of listify((_8 = (_7 = resource.hooks) === null || _7 === void 0 ? void 0 : _7.edit) === null || _8 === void 0 ? void 0 : _8.afterSave)) {
|
|
1029
1088
|
const resp = yield hook({ resource, record, adminUser, oldRecord });
|
|
1030
1089
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
1031
1090
|
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -1042,8 +1101,8 @@ class AdminForth {
|
|
|
1042
1101
|
server.endpoint({
|
|
1043
1102
|
method: 'POST',
|
|
1044
1103
|
path: '/delete_record',
|
|
1045
|
-
handler: (
|
|
1046
|
-
var
|
|
1104
|
+
handler: (_9) => __awaiter(this, [_9], void 0, function* ({ body, adminUser }) {
|
|
1105
|
+
var _10, _11, _12, _13;
|
|
1047
1106
|
const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
1048
1107
|
const record = yield this.connectors[resource.dataSource].getRecordByPrimaryKey(resource, body['primaryKey']);
|
|
1049
1108
|
if (!resource) {
|
|
@@ -1061,7 +1120,7 @@ class AdminForth {
|
|
|
1061
1120
|
return { error };
|
|
1062
1121
|
}
|
|
1063
1122
|
// execute hook if needed
|
|
1064
|
-
for (const hook of listify((
|
|
1123
|
+
for (const hook of listify((_11 = (_10 = resource.hooks) === null || _10 === void 0 ? void 0 : _10.delete) === null || _11 === void 0 ? void 0 : _11.beforeSave)) {
|
|
1065
1124
|
const resp = yield hook({ resource, record, adminUser });
|
|
1066
1125
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
1067
1126
|
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -1073,7 +1132,7 @@ class AdminForth {
|
|
|
1073
1132
|
const connector = this.connectors[resource.dataSource];
|
|
1074
1133
|
yield connector.deleteRecord({ resource, recordId: body['primaryKey'] });
|
|
1075
1134
|
// execute hook if needed
|
|
1076
|
-
for (const hook of listify((
|
|
1135
|
+
for (const hook of listify((_13 = (_12 = resource.hooks) === null || _12 === void 0 ? void 0 : _12.delete) === null || _13 === void 0 ? void 0 : _13.afterSave)) {
|
|
1077
1136
|
const resp = yield hook({ resource, record, adminUser });
|
|
1078
1137
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
1079
1138
|
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -1090,7 +1149,7 @@ class AdminForth {
|
|
|
1090
1149
|
server.endpoint({
|
|
1091
1150
|
method: 'POST',
|
|
1092
1151
|
path: '/start_bulk_action',
|
|
1093
|
-
handler: (
|
|
1152
|
+
handler: (_14) => __awaiter(this, [_14], void 0, function* ({ body }) {
|
|
1094
1153
|
const { resourceId, actionId, recordIds } = body;
|
|
1095
1154
|
const resource = this.config.resources.find((res) => res.resourceId == resourceId);
|
|
1096
1155
|
if (!resource) {
|
|
@@ -107,11 +107,11 @@ class CodeInjector {
|
|
|
107
107
|
if (Object.keys(item).includes('isStaticRoute')) {
|
|
108
108
|
if (!item.isStaticRoute) {
|
|
109
109
|
routes += `{
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
path: '${item.path}',
|
|
111
|
+
name: '${item.path}',
|
|
112
|
+
component: () => import('${item.component}'),
|
|
113
|
+
meta: { title: '${((_a = item === null || item === void 0 ? void 0 : item.meta) === null || _a === void 0 ? void 0 : _a.title) || item.path.replace('/', '')}'}
|
|
114
|
+
},\n`;
|
|
115
115
|
}
|
|
116
116
|
else {
|
|
117
117
|
routes += `{
|
|
@@ -127,21 +127,21 @@ class CodeInjector {
|
|
|
127
127
|
else {
|
|
128
128
|
if (item.homepage) {
|
|
129
129
|
routes += `{
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
path: '${item.path}',
|
|
131
|
+
name: '${item.path}',
|
|
132
|
+
component: ${getComponentNameFromPath(item.component)},
|
|
133
|
+
meta: { title: '${((_c = item === null || item === void 0 ? void 0 : item.meta) === null || _c === void 0 ? void 0 : _c.title) || item.path.replace('/', '')}'}
|
|
134
|
+
},\n`;
|
|
135
135
|
const componentName = `${getComponentNameFromPath(item.component)}`;
|
|
136
136
|
routerComponents += `import ${componentName} from '${item.component}';\n`;
|
|
137
137
|
}
|
|
138
138
|
else {
|
|
139
139
|
routes += `{
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
path: '${item.path}',
|
|
141
|
+
name: '${item.path}',
|
|
142
|
+
component: () => import('${item.component}'),
|
|
143
|
+
meta: { title: '${((_d = item === null || item === void 0 ? void 0 : item.meta) === null || _d === void 0 ? void 0 : _d.title) || item.path.replace('/', '')}'}
|
|
144
|
+
},\n`;
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
}
|
|
@@ -150,6 +150,20 @@ class CodeInjector {
|
|
|
150
150
|
}
|
|
151
151
|
});
|
|
152
152
|
};
|
|
153
|
+
const registerCustomPages = (config) => {
|
|
154
|
+
if (config.customization.customPages) {
|
|
155
|
+
config.customization.customPages.forEach((page) => {
|
|
156
|
+
var _a, _b, _c, _d;
|
|
157
|
+
routes += `{
|
|
158
|
+
path: '${page.path}',
|
|
159
|
+
name: '${page.path}',
|
|
160
|
+
component: () => import('${((_a = page === null || page === void 0 ? void 0 : page.component) === null || _a === void 0 ? void 0 : _a.file) || page.component}'),
|
|
161
|
+
meta: { title: '${((_b = page.meta) === null || _b === void 0 ? void 0 : _b.title) || page.path.replace('/', '')}',customLayout:${(_d = (_c = page === null || page === void 0 ? void 0 : page.component) === null || _c === void 0 ? void 0 : _c.meta) === null || _d === void 0 ? void 0 : _d.customLayout}}
|
|
162
|
+
},`;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
registerCustomPages(this.adminforth.config);
|
|
153
167
|
collectAssetsFromMenu(this.adminforth.config.menu);
|
|
154
168
|
if (filesUpdated) {
|
|
155
169
|
// copy only updated files
|
|
@@ -93,23 +93,8 @@ class AuditLogPlugin extends AdminForthPlugin {
|
|
|
93
93
|
};
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
edit: { afterSave: [] },
|
|
99
|
-
delete: { beforeSave: [] }
|
|
100
|
-
};
|
|
101
|
-
if (!resource.hooks) {
|
|
102
|
-
resource.hooks = defaultHooks;
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
resource.hooks = Object.assign(Object.assign({}, defaultHooks), resource.hooks);
|
|
106
|
-
}
|
|
107
|
-
Object.keys(resource.hooks).forEach((hook) => {
|
|
108
|
-
const hookToUse = hook == AllowedActionsEnum.delete ? 'beforeSave' : 'afterSave';
|
|
109
|
-
if (!Array.isArray(resource.hooks[hook][hookToUse])) {
|
|
110
|
-
resource.hooks[hook][hookToUse] = [resource.hooks[hook][hookToUse]];
|
|
111
|
-
}
|
|
112
|
-
resource.hooks[hook][hookToUse].push((_a) => __awaiter(this, [_a], void 0, function* ({ resource, record, adminUser, oldRecord }) {
|
|
96
|
+
['edit', 'create', 'delete'].forEach((hook) => {
|
|
97
|
+
resource.hooks[hook].afterSave.push((_a) => __awaiter(this, [_a], void 0, function* ({ resource, record, adminUser, oldRecord }) {
|
|
113
98
|
return yield this.createLogRecord(resource, hook, record, adminUser, oldRecord);
|
|
114
99
|
}));
|
|
115
100
|
});
|
|
@@ -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>
|