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.
Files changed (122) hide show
  1. package/auth.ts +14 -0
  2. package/dataConnectors/baseConnector.ts +107 -0
  3. package/dataConnectors/mongo.ts +6 -12
  4. package/dataConnectors/postgres.ts +17 -37
  5. package/dataConnectors/sqlite.ts +44 -55
  6. package/dist/auth.js +7 -0
  7. package/dist/dataConnectors/baseConnector.js +90 -0
  8. package/dist/dataConnectors/mongo.js +6 -12
  9. package/dist/dataConnectors/postgres.js +12 -33
  10. package/dist/dataConnectors/sqlite.js +67 -83
  11. package/dist/index.js +125 -66
  12. package/dist/modules/codeInjector.js +29 -15
  13. package/dist/plugins/AuditLogPlugin/index.js +2 -17
  14. package/dist/plugins/S3UploadPlugin/custom/s3uploader.vue +120 -0
  15. package/dist/plugins/S3UploadPlugin/index.js +103 -0
  16. package/dist/plugins/S3UploadPlugin/package-lock.json +431 -0
  17. package/dist/plugins/S3UploadPlugin/package.json +15 -0
  18. package/dist/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsConfirmation.vue +152 -0
  19. package/dist/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsSetup.vue +188 -0
  20. package/dist/plugins/TwoFactorsAuthPlugin/index.js +150 -0
  21. package/dist/plugins/TwoFactorsAuthPlugin/types.js +1 -0
  22. package/dist/spa/index.html +1 -1
  23. package/dist/spa/package-lock.json +111 -0
  24. package/dist/spa/package.json +1 -0
  25. package/dist/spa/src/App.vue +178 -133
  26. package/dist/spa/src/components/MenuLink.vue +3 -3
  27. package/dist/spa/src/components/ResourceForm.vue +96 -96
  28. package/dist/spa/src/components/ResourceListTable.vue +69 -84
  29. package/dist/spa/src/components/ValueRenderer.vue +2 -3
  30. package/dist/spa/src/composables/useStores.ts +24 -11
  31. package/dist/spa/src/main.ts +1 -1
  32. package/dist/spa/src/router/index.ts +0 -1
  33. package/dist/spa/src/utils.ts +1 -1
  34. package/dist/spa/src/views/CreateView.vue +1 -9
  35. package/dist/spa/src/views/EditView.vue +1 -9
  36. package/dist/spa/src/views/ListView.vue +12 -3
  37. package/dist/spa/src/views/LoginView.vue +6 -1
  38. package/dist/spa/src/views/ResourceParent.vue +1 -2
  39. package/dist/spa/src/views/ShowView.vue +6 -14
  40. package/dist/spa/tailwind.config.js +3 -1
  41. package/index.ts +103 -36
  42. package/modules/codeInjector.ts +38 -27
  43. package/package.json +2 -1
  44. package/plugins/AuditLogPlugin/index.ts +4 -20
  45. package/plugins/S3UploadPlugin/custom/s3uploader.vue +120 -0
  46. package/plugins/S3UploadPlugin/index.ts +112 -0
  47. package/plugins/S3UploadPlugin/package-lock.json +431 -0
  48. package/plugins/S3UploadPlugin/package.json +15 -0
  49. package/plugins/S3UploadPlugin/types.ts +76 -0
  50. package/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsConfirmation.vue +152 -0
  51. package/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsOtp.vue +0 -0
  52. package/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsSetup.vue +188 -0
  53. package/plugins/TwoFactorsAuthPlugin/index.ts +150 -0
  54. package/plugins/TwoFactorsAuthPlugin/types.ts +10 -0
  55. package/servers/express.ts +1 -0
  56. package/spa/src/App.vue +154 -121
  57. package/spa/src/components/ResourceForm.vue +91 -91
  58. package/spa/src/router/index.ts +0 -1
  59. package/spa/src/utils.ts +1 -1
  60. package/spa/src/views/CreateView.vue +0 -1
  61. package/spa/src/views/LoginView.vue +4 -1
  62. package/types/AdminForthConfig.ts +166 -5
  63. package/dist/plugins/AuditLog/index.js +0 -13
  64. package/dist/spa/spa/.eslintrc.cjs +0 -14
  65. package/dist/spa/spa/.vscode/extensions.json +0 -6
  66. package/dist/spa/spa/README.md +0 -39
  67. package/dist/spa/spa/env.d.ts +0 -1
  68. package/dist/spa/spa/index.html +0 -23
  69. package/dist/spa/spa/package-lock.json +0 -4470
  70. package/dist/spa/spa/package.json +0 -48
  71. package/dist/spa/spa/postcss.config.js +0 -6
  72. package/dist/spa/spa/public/assets/favicon.png +0 -0
  73. package/dist/spa/spa/src/App.vue +0 -283
  74. package/dist/spa/spa/src/assets/logo.svg +0 -19
  75. package/dist/spa/spa/src/components/AcceptModal.vue +0 -45
  76. package/dist/spa/spa/src/components/Breadcrumbs.vue +0 -40
  77. package/dist/spa/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  78. package/dist/spa/spa/src/components/CustomDatePicker.vue +0 -174
  79. package/dist/spa/spa/src/components/CustomDateRangePicker.vue +0 -218
  80. package/dist/spa/spa/src/components/CustomRangePicker.vue +0 -148
  81. package/dist/spa/spa/src/components/Dropdown.vue +0 -168
  82. package/dist/spa/spa/src/components/Filters.vue +0 -211
  83. package/dist/spa/spa/src/components/HelloWorld.vue +0 -17
  84. package/dist/spa/spa/src/components/MenuLink.vue +0 -24
  85. package/dist/spa/spa/src/components/ResourceForm.vue +0 -265
  86. package/dist/spa/spa/src/components/ResourceListTable.vue +0 -404
  87. package/dist/spa/spa/src/components/SingleSkeletLoader.vue +0 -13
  88. package/dist/spa/spa/src/components/Toast.vue +0 -66
  89. package/dist/spa/spa/src/components/ValueRenderer.vue +0 -59
  90. package/dist/spa/spa/src/components/icons/IconCalendar.vue +0 -5
  91. package/dist/spa/spa/src/components/icons/IconCommunity.vue +0 -7
  92. package/dist/spa/spa/src/components/icons/IconDocumentation.vue +0 -7
  93. package/dist/spa/spa/src/components/icons/IconEcosystem.vue +0 -7
  94. package/dist/spa/spa/src/components/icons/IconSupport.vue +0 -7
  95. package/dist/spa/spa/src/components/icons/IconTime.vue +0 -5
  96. package/dist/spa/spa/src/components/icons/IconTooling.vue +0 -19
  97. package/dist/spa/spa/src/composables/useFrontendApi.ts +0 -26
  98. package/dist/spa/spa/src/composables/useStores.ts +0 -126
  99. package/dist/spa/spa/src/index.scss +0 -26
  100. package/dist/spa/spa/src/main.ts +0 -18
  101. package/dist/spa/spa/src/router/index.ts +0 -64
  102. package/dist/spa/spa/src/spa_types/core.ts +0 -51
  103. package/dist/spa/spa/src/stores/core.ts +0 -144
  104. package/dist/spa/spa/src/stores/filters.ts +0 -22
  105. package/dist/spa/spa/src/stores/modal.ts +0 -48
  106. package/dist/spa/spa/src/stores/toast.ts +0 -15
  107. package/dist/spa/spa/src/stores/user.ts +0 -54
  108. package/dist/spa/spa/src/utils.ts +0 -101
  109. package/dist/spa/spa/src/views/CreateView.vue +0 -156
  110. package/dist/spa/spa/src/views/EditView.vue +0 -157
  111. package/dist/spa/spa/src/views/ListView.vue +0 -266
  112. package/dist/spa/spa/src/views/LoginView.vue +0 -136
  113. package/dist/spa/spa/src/views/ResourceParent.vue +0 -17
  114. package/dist/spa/spa/src/views/ShowView.vue +0 -180
  115. package/dist/spa/spa/tailwind.config.js +0 -17
  116. package/dist/spa/spa/tsconfig.app.json +0 -14
  117. package/dist/spa/spa/tsconfig.json +0 -11
  118. package/dist/spa/spa/tsconfig.node.json +0 -19
  119. package/dist/spa/spa/vite.config.ts +0 -49
  120. /package/dist/plugins/{AuditLog → S3UploadPlugin}/types.js +0 -0
  121. /package/dist/{spa/spa/src/assets/base.css → plugins/TwoFactorsAuthPlugin/custom/TwoFactorsOtp.vue} +0 -0
  122. /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, } from './types/AdminForthConfig.js';
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
- for (const value of [res.hooks.show, res.hooks.list]) {
292
- if (value) {
293
- if (value.beforeDatasourceRequest) {
294
- if (!Array.isArray(value.beforeDatasourceRequest)) {
295
- value.beforeDatasourceRequest = [value.beforeDatasourceRequest];
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
- for (const value of [res.hooks.create, res.hooks.edit, res.hooks.delete]) {
306
- if (value) {
307
- if (value.beforeSave) {
308
- if (!Array.isArray(value.beforeSave)) {
309
- value.beforeSave = [value.beforeSave];
310
- }
311
- }
312
- if (value.afterSave) {
313
- if (!Array.isArray(value.afterSave)) {
314
- value.afterSave = [value.afterSave];
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
- this.auth.setAuthCookie({ response, username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name] });
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 { ok: true };
585
- }),
637
+ return toReturn;
638
+ })
586
639
  });
587
640
  server.endpoint({
588
641
  method: 'POST',
589
642
  path: '/check_auth',
590
- handler: (_d) => __awaiter(this, [_d], void 0, function* ({ adminUser }) {
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: (_e) => __awaiter(this, [_e], void 0, function* ({ response }) {
599
- this.auth.removeAuthCookie({ response });
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: (_f) => __awaiter(this, [_f], void 0, function* ({ body }) {
608
- var _g;
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: (_g = this.config.customization) === null || _g === void 0 ? void 0 : _g.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: (_h) => __awaiter(this, [_h], void 0, function* ({ input, adminUser, cookies }) {
628
- var _j, _k;
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: (_j = this.config.customization) === null || _j === void 0 ? void 0 : _j.title,
691
- emptyFieldPlaceholder: (_k = this.config.customization) === null || _k === void 0 ? void 0 : _k.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: (_l) => __awaiter(this, [_l], void 0, function* ({ body, adminUser }) {
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: (_m) => __awaiter(this, [_m], void 0, function* ({ body, adminUser }) {
753
- var _o, _p, _q, _r;
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((_p = (_o = resource.hooks) === null || _o === void 0 ? void 0 : _o[source]) === null || _p === void 0 ? void 0 : _p.beforeDatasourceRequest)) {
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((_r = (_q = resource.hooks) === null || _q === void 0 ? void 0 : _q[source]) === null || _r === void 0 ? void 0 : _r.afterDatasourceResponse)) {
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: (_s) => __awaiter(this, [_s], void 0, function* ({ body, adminUser }) {
867
- var _t, _u, _v, _w;
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((_u = (_t = columnConfig.foreignResource.hooks) === null || _t === void 0 ? void 0 : _t.dropdownList) === null || _u === void 0 ? void 0 : _u.beforeDatasourceRequest)) {
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((_w = (_v = columnConfig.foreignResource.hooks) === null || _v === void 0 ? void 0 : _v.dropdownList) === null || _w === void 0 ? void 0 : _w.afterDatasourceResponse)) {
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: (_x) => __awaiter(this, [_x], void 0, function* ({ body }) {
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: (_y) => __awaiter(this, [_y], void 0, function* ({ body, adminUser }) {
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
- yield this.createResourceRecord({ resource, record: body['record'], adminUser });
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: (_z) => __awaiter(this, [_z], void 0, function* ({ body, adminUser }) {
982
- var _0, _1, _2, _3;
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((_1 = (_0 = resource.hooks) === null || _0 === void 0 ? void 0 : _0.edit) === null || _1 === void 0 ? void 0 : _1.beforeSave)) {
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, record, newValues });
1084
+ yield connector.updateRecord({ resource, recordId, newValues });
1026
1085
  }
1027
1086
  // execute hook if needed
1028
- for (const hook of listify((_3 = (_2 = resource.hooks) === null || _2 === void 0 ? void 0 : _2.edit) === null || _3 === void 0 ? void 0 : _3.afterSave)) {
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: (_4) => __awaiter(this, [_4], void 0, function* ({ body, adminUser }) {
1046
- var _5, _6, _7, _8;
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((_6 = (_5 = resource.hooks) === null || _5 === void 0 ? void 0 : _5.delete) === null || _6 === void 0 ? void 0 : _6.beforeSave)) {
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((_8 = (_7 = resource.hooks) === null || _7 === void 0 ? void 0 : _7.delete) === null || _8 === void 0 ? void 0 : _8.afterSave)) {
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: (_9) => __awaiter(this, [_9], void 0, function* ({ body }) {
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
- 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`;
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
- 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`;
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
- 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`;
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
- const defaultHooks = {
97
- create: { afterSave: [] },
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>