adminforth 1.0.62 → 1.0.63

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/dist/index.js CHANGED
@@ -24,6 +24,7 @@ import { v1 as uuid } from 'uuid';
24
24
  import fs from 'fs';
25
25
  import { ADMINFORTH_VERSION } from './modules/utils.js';
26
26
  import { AdminForthFilterOperators, AdminForthTypes } from './types.js';
27
+ import { getFunctionList } from './modules/utils.js';
27
28
  const AVAILABLE_SHOW_IN = ['list', 'edit', 'create', 'filter', 'show'];
28
29
  const DEFAULT_ALLOWED_ACTIONS = { create: true, edit: true, show: true, delete: true };
29
30
  class AdminForth {
@@ -159,7 +160,7 @@ class AdminForth {
159
160
  errors.push(`Resource "${res.resourceId}" bulkActions must be an array`);
160
161
  bulkActions = [];
161
162
  }
162
- if ((_c = res.options) === null || _c === void 0 ? void 0 : _c.allowDelete) {
163
+ if (((_c = res.options) === null || _c === void 0 ? void 0 : _c.allowDelete) && !bulkActions.find((action) => action.label === 'Delete checked')) {
163
164
  bulkActions.push({
164
165
  label: `Delete checked`,
165
166
  state: 'danger',
@@ -389,6 +390,7 @@ class AdminForth {
389
390
  method: 'GET',
390
391
  path: '/get_public_config',
391
392
  handler: (_d) => __awaiter(this, [_d], void 0, function* ({ body }) {
393
+ var _e;
392
394
  // find resource
393
395
  if (!this.config.auth) {
394
396
  throw new Error('No config.auth defined');
@@ -400,14 +402,15 @@ class AdminForth {
400
402
  brandName: this.config.brandName,
401
403
  usernameFieldName: usernameColumn.label,
402
404
  loginBackgroundImage: this.config.auth.loginBackgroundImage,
405
+ title: (_e = this.config) === null || _e === void 0 ? void 0 : _e.title,
403
406
  };
404
407
  }),
405
408
  });
406
409
  server.endpoint({
407
410
  method: 'GET',
408
411
  path: '/get_base_config',
409
- handler: (_e) => __awaiter(this, [_e], void 0, function* ({ input, adminUser, cookies }) {
410
- var _f;
412
+ handler: (_f) => __awaiter(this, [_f], void 0, function* ({ input, adminUser, cookies }) {
413
+ var _g;
411
414
  const cookieParsed = this.auth.verify(cookies['adminforth_jwt']);
412
415
  let username = '';
413
416
  let userFullName = '';
@@ -448,7 +451,7 @@ class AdminForth {
448
451
  deleteConfirmation: this.config.deleteConfirmation,
449
452
  auth: this.config.auth,
450
453
  usernameField: this.config.auth.usernameField,
451
- title: (_f = this.config) === null || _f === void 0 ? void 0 : _f.title,
454
+ title: (_g = this.config) === null || _g === void 0 ? void 0 : _g.title,
452
455
  },
453
456
  adminUser,
454
457
  version: ADMINFORTH_VERSION,
@@ -458,7 +461,7 @@ class AdminForth {
458
461
  server.endpoint({
459
462
  method: 'POST',
460
463
  path: '/get_resource_columns',
461
- handler: (_g) => __awaiter(this, [_g], void 0, function* ({ body }) {
464
+ handler: (_h) => __awaiter(this, [_h], void 0, function* ({ body }) {
462
465
  const { resourceId } = body;
463
466
  if (!this.statuses.dbDiscover) {
464
467
  return { error: 'Database discovery not started' };
@@ -477,8 +480,8 @@ class AdminForth {
477
480
  server.endpoint({
478
481
  method: 'POST',
479
482
  path: '/get_resource_data',
480
- handler: (_h) => __awaiter(this, [_h], void 0, function* ({ body, adminUser }) {
481
- var _j, _k, _l, _m, _o, _p, _q, _r;
483
+ handler: (_j) => __awaiter(this, [_j], void 0, function* ({ body, adminUser }) {
484
+ var _k, _l, _m, _o;
482
485
  const { resourceId, source } = body;
483
486
  if (['show', 'list'].includes(source) === false) {
484
487
  return { error: 'Invalid source, should be list or show' };
@@ -493,8 +496,8 @@ class AdminForth {
493
496
  if (!resource) {
494
497
  return { error: `Resource ${resourceId} not found` };
495
498
  }
496
- if ((_k = (_j = resource.hooks) === null || _j === void 0 ? void 0 : _j[source]) === null || _k === void 0 ? void 0 : _k.beforeDatasourceRequest) {
497
- const resp = yield ((_m = (_l = resource.hooks) === null || _l === void 0 ? void 0 : _l[source]) === null || _m === void 0 ? void 0 : _m.beforeDatasourceRequest({ resource, query: body, adminUser }));
499
+ for (const hook of getFunctionList((_l = (_k = resource.hooks) === null || _k === void 0 ? void 0 : _k[source]) === null || _l === void 0 ? void 0 : _l.beforeDatasourceRequest)) {
500
+ const resp = yield hook({ resource, query: body, adminUser });
498
501
  if (!resp || (!resp.ok && !resp.error)) {
499
502
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
500
503
  }
@@ -563,8 +566,8 @@ class AdminForth {
563
566
  data.data.forEach((item) => {
564
567
  item._label = resource.itemLabel(item);
565
568
  });
566
- if ((_p = (_o = resource.hooks) === null || _o === void 0 ? void 0 : _o[source]) === null || _p === void 0 ? void 0 : _p.afterDatasourceResponse) {
567
- const resp = yield ((_r = (_q = resource.hooks) === null || _q === void 0 ? void 0 : _q[source]) === null || _r === void 0 ? void 0 : _r.afterDatasourceResponse({ resource, response: data.data, adminUser }));
569
+ for (const hook of getFunctionList((_o = (_m = resource.hooks) === null || _m === void 0 ? void 0 : _m[source]) === null || _o === void 0 ? void 0 : _o.afterDatasourceResponse)) {
570
+ const resp = yield hook({ resource, response: data.data, adminUser });
568
571
  if (!resp || (!resp.ok && !resp.error)) {
569
572
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
570
573
  }
@@ -586,8 +589,8 @@ class AdminForth {
586
589
  server.endpoint({
587
590
  method: 'POST',
588
591
  path: '/get_resource_foreign_data',
589
- handler: (_s) => __awaiter(this, [_s], void 0, function* ({ body, adminUser }) {
590
- var _t, _u, _v, _w;
592
+ handler: (_p) => __awaiter(this, [_p], void 0, function* ({ body, adminUser }) {
593
+ var _q, _r;
591
594
  const { resourceId, column } = body;
592
595
  if (!this.statuses.dbDiscover) {
593
596
  return { error: 'Database discovery not started' };
@@ -608,8 +611,8 @@ class AdminForth {
608
611
  }
609
612
  const targetResourceId = columnConfig.foreignResource.resourceId;
610
613
  const targetResource = this.config.resources.find((res) => res.resourceId == targetResourceId);
611
- if ((_t = columnConfig.foreignResource.hooks) === null || _t === void 0 ? void 0 : _t.beforeDatasourceRequest) {
612
- const resp = yield ((_u = column.foreignResource.hooks) === null || _u === void 0 ? void 0 : _u.beforeDatasourceRequest({ query: body, adminUser }));
614
+ for (const hook of getFunctionList((_q = columnConfig.foreignResource.hooks) === null || _q === void 0 ? void 0 : _q.beforeDatasourceRequest)) {
615
+ const resp = yield hook({ query: body, adminUser });
613
616
  if (!resp || (!resp.ok && !resp.error)) {
614
617
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
615
618
  }
@@ -637,8 +640,8 @@ class AdminForth {
637
640
  const response = {
638
641
  items
639
642
  };
640
- if ((_v = columnConfig.foreignResource.hooks) === null || _v === void 0 ? void 0 : _v.afterDatasourceResponse) {
641
- const resp = yield ((_w = column.foreignResource.hooks) === null || _w === void 0 ? void 0 : _w.afterDatasourceResponse({ response, adminUser }));
643
+ for (const hook of getFunctionList((_r = columnConfig.foreignResource.hooks) === null || _r === void 0 ? void 0 : _r.afterDatasourceResponse)) {
644
+ const resp = yield hook({ response, adminUser });
642
645
  if (!resp || (!resp.ok && !resp.error)) {
643
646
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
644
647
  }
@@ -652,7 +655,7 @@ class AdminForth {
652
655
  server.endpoint({
653
656
  method: 'POST',
654
657
  path: '/get_min_max_for_columns',
655
- handler: (_x) => __awaiter(this, [_x], void 0, function* ({ body }) {
658
+ handler: (_s) => __awaiter(this, [_s], void 0, function* ({ body }) {
656
659
  const { resourceId } = body;
657
660
  if (!this.statuses.dbDiscover) {
658
661
  return { error: 'Database discovery not started' };
@@ -681,8 +684,8 @@ class AdminForth {
681
684
  server.endpoint({
682
685
  method: 'POST',
683
686
  path: '/create_record',
684
- handler: (_y) => __awaiter(this, [_y], void 0, function* ({ body, adminUser }) {
685
- var _z, _0, _1, _2, _3, _4, _5, _6, _7;
687
+ handler: (_t) => __awaiter(this, [_t], void 0, function* ({ body, adminUser }) {
688
+ var _u, _v, _w, _x, _y;
686
689
  console.log('create_record', body, this.config.resources);
687
690
  const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
688
691
  if (!resource) {
@@ -690,8 +693,8 @@ class AdminForth {
690
693
  }
691
694
  const record = body['record'];
692
695
  // execute hook if needed
693
- if ((_0 = (_z = resource.hooks) === null || _z === void 0 ? void 0 : _z.create) === null || _0 === void 0 ? void 0 : _0.beforeSave) {
694
- const resp = yield ((_2 = (_1 = resource.hooks) === null || _1 === void 0 ? void 0 : _1.create) === null || _2 === void 0 ? void 0 : _2.beforeSave({ resource, record, adminUser }));
696
+ for (const hook of getFunctionList((_v = (_u = resource.hooks) === null || _u === void 0 ? void 0 : _u.create) === null || _v === void 0 ? void 0 : _v.beforeSave)) {
697
+ const resp = yield hook({ resource, record, adminUser });
695
698
  if (!resp || (!resp.ok && !resp.error)) {
696
699
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
697
700
  }
@@ -707,7 +710,7 @@ class AdminForth {
707
710
  });
708
711
  }
709
712
  }
710
- if (((_3 = column.required) === null || _3 === void 0 ? void 0 : _3.create) && body['record'][column.name] === undefined) {
713
+ if (((_w = column.required) === null || _w === void 0 ? void 0 : _w.create) && body['record'][column.name] === undefined) {
711
714
  return { error: `Column '${column.name}' is required` };
712
715
  }
713
716
  if (column.isUnique) {
@@ -732,8 +735,8 @@ class AdminForth {
732
735
  const connector = this.connectors[resource.dataSource];
733
736
  yield connector.createRecord({ resource, record });
734
737
  // execute hook if needed
735
- if ((_5 = (_4 = resource.hooks) === null || _4 === void 0 ? void 0 : _4.create) === null || _5 === void 0 ? void 0 : _5.afterSave) {
736
- const resp = yield ((_7 = (_6 = resource.hooks) === null || _6 === void 0 ? void 0 : _6.create) === null || _7 === void 0 ? void 0 : _7.afterSave({ resource, record, adminUser }));
738
+ for (const hook of getFunctionList((_y = (_x = resource.hooks) === null || _x === void 0 ? void 0 : _x.create) === null || _y === void 0 ? void 0 : _y.afterSave)) {
739
+ const resp = yield hook({ resource, record, adminUser });
737
740
  if (!resp || (!resp.ok && !resp.error)) {
738
741
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
739
742
  }
@@ -749,8 +752,8 @@ class AdminForth {
749
752
  server.endpoint({
750
753
  method: 'POST',
751
754
  path: '/update_record',
752
- handler: (_8) => __awaiter(this, [_8], void 0, function* ({ body, adminUser }) {
753
- var _9, _10, _11, _12, _13, _14, _15, _16;
755
+ handler: (_z) => __awaiter(this, [_z], void 0, function* ({ body, adminUser }) {
756
+ var _0, _1, _2, _3;
754
757
  const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
755
758
  if (!resource) {
756
759
  return { error: `Resource '${body['resourceId']}' not found` };
@@ -764,8 +767,8 @@ class AdminForth {
764
767
  }
765
768
  const record = body['record'];
766
769
  // execute hook if needed
767
- if ((_10 = (_9 = resource.hooks) === null || _9 === void 0 ? void 0 : _9.edit) === null || _10 === void 0 ? void 0 : _10.beforeSave) {
768
- const resp = yield ((_12 = (_11 = resource.hooks) === null || _11 === void 0 ? void 0 : _11.edit) === null || _12 === void 0 ? void 0 : _12.beforeSave({ resource, record, adminUser }));
770
+ for (const hook of getFunctionList((_1 = (_0 = resource.hooks) === null || _0 === void 0 ? void 0 : _0.edit) === null || _1 === void 0 ? void 0 : _1.beforeSave)) {
771
+ const resp = yield hook({ resource, record, adminUser });
769
772
  if (!resp || (!resp.ok && !resp.error)) {
770
773
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
771
774
  }
@@ -790,8 +793,8 @@ class AdminForth {
790
793
  yield connector.updateRecord({ resource, recordId, record, newValues });
791
794
  }
792
795
  // execute hook if needed
793
- if ((_14 = (_13 = resource.hooks) === null || _13 === void 0 ? void 0 : _13.edit) === null || _14 === void 0 ? void 0 : _14.afterSave) {
794
- const resp = yield ((_16 = (_15 = resource.hooks) === null || _15 === void 0 ? void 0 : _15.edit) === null || _16 === void 0 ? void 0 : _16.afterSave({ resource, record, adminUser }));
796
+ for (const hook of getFunctionList((_3 = (_2 = resource.hooks) === null || _2 === void 0 ? void 0 : _2.edit) === null || _3 === void 0 ? void 0 : _3.afterSave)) {
797
+ const resp = yield hook({ resource, record, adminUser });
795
798
  if (!resp || (!resp.ok && !resp.error)) {
796
799
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
797
800
  }
@@ -807,16 +810,16 @@ class AdminForth {
807
810
  server.endpoint({
808
811
  method: 'POST',
809
812
  path: '/delete_record',
810
- handler: (_17) => __awaiter(this, [_17], void 0, function* ({ body, adminUser }) {
811
- var _18, _19, _20, _21, _22, _23, _24, _25;
813
+ handler: (_4) => __awaiter(this, [_4], void 0, function* ({ body, adminUser }) {
814
+ var _5, _6, _7, _8;
812
815
  const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
813
816
  const record = yield this.connectors[resource.dataSource].getRecordByPrimaryKey(resource, body['primaryKey']);
814
817
  if (!resource) {
815
818
  return { error: `Resource '${body['resourceId']}' not found` };
816
819
  }
817
820
  // execute hook if needed
818
- if ((_19 = (_18 = resource.hooks) === null || _18 === void 0 ? void 0 : _18.delete) === null || _19 === void 0 ? void 0 : _19.beforeSave) {
819
- const resp = yield ((_21 = (_20 = resource.hooks) === null || _20 === void 0 ? void 0 : _20.delete) === null || _21 === void 0 ? void 0 : _21.beforeSave({ resource, record, adminUser }));
821
+ for (const hook of getFunctionList((_6 = (_5 = resource.hooks) === null || _5 === void 0 ? void 0 : _5.delete) === null || _6 === void 0 ? void 0 : _6.beforeSave)) {
822
+ const resp = yield hook({ resource, record, adminUser });
820
823
  if (!resp || (!resp.ok && !resp.error)) {
821
824
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
822
825
  }
@@ -827,8 +830,8 @@ class AdminForth {
827
830
  const connector = this.connectors[resource.dataSource];
828
831
  yield connector.deleteRecord({ resource, recordId: body['primaryKey'] });
829
832
  // execute hook if needed
830
- if ((_23 = (_22 = resource.hooks) === null || _22 === void 0 ? void 0 : _22.delete) === null || _23 === void 0 ? void 0 : _23.afterSave) {
831
- const resp = yield ((_25 = (_24 = resource.hooks) === null || _24 === void 0 ? void 0 : _24.delete) === null || _25 === void 0 ? void 0 : _25.afterSave({ resource, record, adminUser }));
833
+ for (const hook of getFunctionList((_8 = (_7 = resource.hooks) === null || _7 === void 0 ? void 0 : _7.delete) === null || _8 === void 0 ? void 0 : _8.afterSave)) {
834
+ const resp = yield hook({ resource, record, adminUser });
832
835
  if (!resp || (!resp.ok && !resp.error)) {
833
836
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
834
837
  }
@@ -844,7 +847,7 @@ class AdminForth {
844
847
  server.endpoint({
845
848
  method: 'POST',
846
849
  path: '/start_bulk_action',
847
- handler: (_26) => __awaiter(this, [_26], void 0, function* ({ body }) {
850
+ handler: (_9) => __awaiter(this, [_9], void 0, function* ({ body }) {
848
851
  const { resourceId, actionId, recordIds } = body;
849
852
  const resource = this.config.resources.find((res) => res.resourceId == resourceId);
850
853
  if (!resource) {
@@ -28,3 +28,16 @@ export const ADMINFORTH_VERSION = package_json.version;
28
28
  export function getComponentNameFromPath(filePath) {
29
29
  return filePath.replace(/@/g, '').replace(/\./g, '').replace(/\//g, '');
30
30
  }
31
+ export function getFunctionList(param) {
32
+ if (param) {
33
+ if (Array.isArray(param)) {
34
+ return param;
35
+ }
36
+ else {
37
+ return [param];
38
+ }
39
+ }
40
+ else {
41
+ return [];
42
+ }
43
+ }
@@ -17,7 +17,7 @@ export default class ForeignInlineListPlugin extends AdminForthPlugin {
17
17
  virtual: true,
18
18
  showIn: ['show'],
19
19
  component: {
20
- show: this.componentPath('InlineList.vue'),
20
+ showRow: this.componentPath('InlineList.vue'),
21
21
  },
22
22
  });
23
23
  }
@@ -113,7 +113,8 @@
113
113
  </div>
114
114
  <div v-else class="flex items-center justify-center h-screen">
115
115
  <div class="text-3xl text-gray-400 animate-bounce">
116
- ▶️
116
+ <svg aria-hidden="true" class="w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/><path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/></svg>
117
+ <span class="sr-only">Loading...</span>
117
118
  </div>
118
119
  </div>
119
120
  <AcceptModal />
@@ -191,10 +192,12 @@ async function initRouter() {
191
192
  // initialize components based on data attribute selectors
192
193
  onMounted(async () => {
193
194
  initRouter();
194
- initFlowbite();
195
+ setTimeout(() => {
196
+ initFlowbite();
197
+ }, 100);
195
198
  await coreStore.fetchMenuAndResource();
196
199
  loginRedirectCheckIsReady.value = true;
197
- title.value = coreStore.config.title || 'Admin Forth';
200
+ title.value = coreStore.config?.title || 'AdminForth';
198
201
  useHead({
199
202
  title: title.value,
200
203
  link: [
@@ -528,10 +528,7 @@ async function init() {
528
528
  }
529
529
 
530
530
  onMounted(async () => {
531
- if (!coreStore.flowBitIsInitialised){
532
- initFlowbite();
533
- coreStore.flowBitIsInitialised = true;
534
- }
531
+
535
532
 
536
533
  await init();
537
534
 
@@ -45,10 +45,28 @@
45
45
  class="bg-form-view-bg odd:dark:bg-gray-900 even:dark:bg-gray-800 border-b dark:border-gray-700"
46
46
  >
47
47
  <component
48
- :is="showComponentsPerColumn[column.name] || ShowTableItem"
48
+ v-if="showRowComponentsPerColumn[column.name]"
49
+ :is="showRowComponentsPerColumn[column.name]"
49
50
  :column="column"
50
51
  :row="coreStore.record"
51
52
  />
53
+ <template v-else>
54
+ <td class="px-6 py-4 whitespace-nowrap">
55
+ {{ column.label }}
56
+ </td>
57
+ <td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap">
58
+ <component
59
+ v-if="showComponentsPerColumn[column.name]"
60
+ :is="showComponentsPerColumn[column.name]"
61
+ :column="column"
62
+ :row="coreStore.record"
63
+ />
64
+ <ValueRenderer v-else
65
+ :column="column"
66
+ :row="coreStore.record"
67
+ />
68
+ </td>
69
+ </template>
52
70
  </tr>
53
71
  </tbody>
54
72
  </table>
@@ -76,6 +94,7 @@ const item = ref(null);
76
94
  const route = useRoute();
77
95
  const loading = ref(false);
78
96
  let showComponentsPerColumn = {};
97
+ let showRowComponentsPerColumn = {};
79
98
 
80
99
  const coreStore = useCoreStore();
81
100
 
@@ -93,7 +112,14 @@ onMounted(async () => {
93
112
  acc[column.name] = getCustomComponent(column.component.show);
94
113
  }
95
114
  return acc;
96
- }, {});
115
+ }, {});
116
+ showRowComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
117
+ if (column.component?.showRow) {
118
+ acc[column.name] = getCustomComponent(column.component.showRow);
119
+ }
120
+ return acc;
121
+ }, {});
122
+
97
123
  loading.value = false;
98
124
  });
99
125
 
@@ -1,6 +1,6 @@
1
1
  /** @type {import('tailwindcss').Config} */
2
2
  export default {
3
- content: ["./src/**/*.{vue, js}","./src/*.{vue, js}", "./index.html", "./node_modules/flowbite/**/*.js"],
3
+ content: ["./src/**/*.{vue, js, ts, tsx}","./src/*.{vue, js, ts, tsx}", "./index.html", "./node_modules/flowbite/**/*.js"],
4
4
  theme: {
5
5
  extend: {
6
6
  /* IMPORTANT:ADMINFORTH TAILWIND STYLES */
package/index.ts CHANGED
@@ -11,6 +11,7 @@ import fs from 'fs';
11
11
  import { ADMINFORTH_VERSION } from './modules/utils.js';
12
12
  import { AdminForthFilterOperators, AdminForthTypes, AdminForthTypesValues } from './types.js';
13
13
  import { AdminForthConfig } from './types/AdminForthConfig.js';
14
+ import { getFunctionList } from './modules/utils.js';
14
15
 
15
16
  const AVAILABLE_SHOW_IN = ['list', 'edit', 'create', 'filter', 'show'];
16
17
  const DEFAULT_ALLOWED_ACTIONS = {create: true, edit: true, show: true, delete: true};
@@ -196,7 +197,7 @@ class AdminForth {
196
197
  errors.push(`Resource "${res.resourceId}" bulkActions must be an array`);
197
198
  bulkActions = [];
198
199
  }
199
- if(res.options?.allowDelete){
200
+ if(res.options?.allowDelete && !bulkActions.find((action) => action.label === 'Delete checked')){
200
201
  bulkActions.push({
201
202
  label: `Delete checked`,
202
203
  state: 'danger',
@@ -471,6 +472,7 @@ class AdminForth {
471
472
  brandName: this.config.brandName,
472
473
  usernameFieldName: usernameColumn.label,
473
474
  loginBackgroundImage: this.config.auth.loginBackgroundImage,
475
+ title: this.config?.title,
474
476
  };
475
477
  },
476
478
  });
@@ -565,8 +567,8 @@ class AdminForth {
565
567
  return { error: `Resource ${resourceId} not found` };
566
568
  }
567
569
 
568
- if (resource.hooks?.[source]?.beforeDatasourceRequest) {
569
- const resp = await resource.hooks?.[source]?.beforeDatasourceRequest({ resource, query: body, adminUser });
570
+ for (const hook of getFunctionList(resource.hooks?.[source]?.beforeDatasourceRequest)) {
571
+ const resp = await hook({ resource, query: body, adminUser });
570
572
  if (!resp || (!resp.ok && !resp.error)) {
571
573
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
572
574
  }
@@ -645,8 +647,8 @@ class AdminForth {
645
647
  item._label = resource.itemLabel(item);
646
648
  })
647
649
 
648
- if (resource.hooks?.[source]?.afterDatasourceResponse) {
649
- const resp = await resource.hooks?.[source]?.afterDatasourceResponse({ resource, response: data.data, adminUser });
650
+ for (const hook of getFunctionList(resource.hooks?.[source]?.afterDatasourceResponse)) {
651
+ const resp = await hook({ resource, response: data.data, adminUser });
650
652
  if (!resp || (!resp.ok && !resp.error)) {
651
653
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
652
654
  }
@@ -695,8 +697,9 @@ class AdminForth {
695
697
  }
696
698
  const targetResourceId = columnConfig.foreignResource.resourceId;
697
699
  const targetResource = this.config.resources.find((res) => res.resourceId == targetResourceId);
698
- if (columnConfig.foreignResource.hooks?.beforeDatasourceRequest) {
699
- const resp = await column.foreignResource.hooks?.beforeDatasourceRequest({ query: body, adminUser });
700
+
701
+ for (const hook of getFunctionList(columnConfig.foreignResource.hooks?.beforeDatasourceRequest)) {
702
+ const resp = await hook({ query: body, adminUser });
700
703
  if (!resp || (!resp.ok && !resp.error)) {
701
704
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
702
705
  }
@@ -725,8 +728,9 @@ class AdminForth {
725
728
  const response = {
726
729
  items
727
730
  };
728
- if (columnConfig.foreignResource.hooks?.afterDatasourceResponse) {
729
- const resp = await column.foreignResource.hooks?.afterDatasourceResponse({ response, adminUser });
731
+
732
+ for (const hook of getFunctionList(columnConfig.foreignResource.hooks?.afterDatasourceResponse)) {
733
+ const resp = await hook({ response, adminUser });
730
734
  if (!resp || (!resp.ok && !resp.error)) {
731
735
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
732
736
  }
@@ -735,7 +739,7 @@ class AdminForth {
735
739
  return { error: resp.error };
736
740
  }
737
741
  }
738
-
742
+
739
743
  return response;
740
744
  },
741
745
  });
@@ -782,15 +786,15 @@ class AdminForth {
782
786
 
783
787
  const record = body['record'];
784
788
  // execute hook if needed
785
- if (resource.hooks?.create?.beforeSave) {
786
- const resp = await resource.hooks?.create?.beforeSave({ resource, record, adminUser });
787
- if (!resp || (!resp.ok && !resp.error)) {
788
- throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
789
- }
790
-
791
- if (resp.error) {
792
- return { error: resp.error };
793
- }
789
+ for (const hook of getFunctionList(resource.hooks?.create?.beforeSave)) {
790
+ const resp = await hook({ resource, record, adminUser });
791
+ if (!resp || (!resp.ok && !resp.error)) {
792
+ throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
793
+ }
794
+
795
+ if (resp.error) {
796
+ return { error: resp.error };
797
+ }
794
798
  }
795
799
 
796
800
  for (const column of resource.columns) {
@@ -828,15 +832,15 @@ class AdminForth {
828
832
  const connector = this.connectors[resource.dataSource];
829
833
  await connector.createRecord({ resource, record });
830
834
  // execute hook if needed
831
- if (resource.hooks?.create?.afterSave) {
832
- const resp = await resource.hooks?.create?.afterSave({ resource, record, adminUser });
833
- if (!resp || (!resp.ok && !resp.error)) {
834
- throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
835
- }
836
-
837
- if (resp.error) {
838
- return { error: resp.error };
839
- }
835
+ for (const hook of getFunctionList(resource.hooks?.create?.afterSave)) {
836
+ const resp = await hook({ resource, record, adminUser });
837
+ if (!resp || (!resp.ok && !resp.error)) {
838
+ throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
839
+ }
840
+
841
+ if (resp.error) {
842
+ return { error: resp.error };
843
+ }
840
844
  }
841
845
 
842
846
  return {
@@ -863,17 +867,16 @@ class AdminForth {
863
867
  const record = body['record'];
864
868
 
865
869
  // execute hook if needed
866
- if (resource.hooks?.edit?.beforeSave) {
867
- const resp = await resource.hooks?.edit?.beforeSave({ resource, record, adminUser });
868
- if (!resp || (!resp.ok && !resp.error)) {
869
- throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
870
- }
871
-
872
- if (resp.error) {
873
- return { error: resp.error };
874
- }
875
- }
870
+ for (const hook of getFunctionList(resource.hooks?.edit?.beforeSave)) {
871
+ const resp = await hook({ resource, record, adminUser });
872
+ if (!resp || (!resp.ok && !resp.error)) {
873
+ throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
874
+ }
876
875
 
876
+ if (resp.error) {
877
+ return { error: resp.error };
878
+ }
879
+ }
877
880
  const newValues = {};
878
881
 
879
882
  for (const recordField in record) {
@@ -893,15 +896,15 @@ class AdminForth {
893
896
  }
894
897
 
895
898
  // execute hook if needed
896
- if (resource.hooks?.edit?.afterSave) {
897
- const resp = await resource.hooks?.edit?.afterSave({ resource, record, adminUser });
898
- if (!resp || (!resp.ok && !resp.error)) {
899
- throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
900
- }
901
-
902
- if (resp.error) {
903
- return { error: resp.error };
904
- }
899
+ for (const hook of getFunctionList(resource.hooks?.edit?.afterSave)) {
900
+ const resp = await hook({ resource, record, adminUser });
901
+ if (!resp || (!resp.ok && !resp.error)) {
902
+ throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
903
+ }
904
+
905
+ if (resp.error) {
906
+ return { error: resp.error };
907
+ }
905
908
  }
906
909
 
907
910
  return {
@@ -920,30 +923,30 @@ class AdminForth {
920
923
  }
921
924
 
922
925
  // execute hook if needed
923
- if (resource.hooks?.delete?.beforeSave) {
924
- const resp = await resource.hooks?.delete?.beforeSave({ resource, record, adminUser });
925
- if (!resp || (!resp.ok && !resp.error)) {
926
- throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
927
- }
928
-
929
- if (resp.error) {
930
- return { error: resp.error };
931
- }
926
+ for (const hook of getFunctionList(resource.hooks?.delete?.beforeSave)) {
927
+ const resp = await hook({ resource, record, adminUser });
928
+ if (!resp || (!resp.ok && !resp.error)) {
929
+ throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
930
+ }
931
+
932
+ if (resp.error) {
933
+ return { error: resp.error };
934
+ }
932
935
  }
933
936
 
934
937
  const connector = this.connectors[resource.dataSource];
935
938
  await connector.deleteRecord({ resource, recordId: body['primaryKey']});
936
939
 
937
940
  // execute hook if needed
938
- if (resource.hooks?.delete?.afterSave) {
939
- const resp = await resource.hooks?.delete?.afterSave({ resource, record, adminUser });
940
- if (!resp || (!resp.ok && !resp.error)) {
941
- throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
942
- }
943
-
944
- if (resp.error) {
945
- return { error: resp.error };
946
- }
941
+ for (const hook of getFunctionList(resource.hooks?.delete?.afterSave)) {
942
+ const resp = await hook({ resource, record, adminUser });
943
+ if (!resp || (!resp.ok && !resp.error)) {
944
+ throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
945
+ }
946
+
947
+ if (resp.error) {
948
+ return { error: resp.error };
949
+ }
947
950
  }
948
951
  return {
949
952
  recordId: body['primaryKey']
package/modules/utils.ts CHANGED
@@ -14,7 +14,6 @@ export function guessLabelFromName(name) {
14
14
  }
15
15
  }
16
16
 
17
-
18
17
  export const currentFileDir = (metaUrl) => {
19
18
  const __filename = fileURLToPath(metaUrl);
20
19
  let __dirname = path.dirname(__filename);
@@ -33,4 +32,16 @@ export const ADMINFORTH_VERSION: string = package_json.version;
33
32
 
34
33
  export function getComponentNameFromPath(filePath) {
35
34
  return filePath.replace(/@/g, '').replace(/\./g, '').replace(/\//g, '');
35
+ }
36
+
37
+ export function getFunctionList(param?: Function | Array<Function>) {
38
+ if (param) {
39
+ if (Array.isArray(param)) {
40
+ return param;
41
+ } else {
42
+ return [param];
43
+ }
44
+ } else {
45
+ return [];
46
+ }
36
47
  }
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.0.62",
3
+ "version": "1.0.63",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "test": "echo \"Error: no test specified\" && exit 1",
9
9
  "build": "tsc",
10
- "rollout": "tsc && cp -r spa dist/spa && npm version patch && npm publish"
10
+ "rollout": "tsc && cp -r spa dist/spa && npm version patch && npm publish",
11
+ "docs": "typedoc --out docs index.ts"
11
12
  },
12
13
  "author": "devforth.io",
13
14
  "license": "ISC",
@@ -26,6 +27,7 @@
26
27
  },
27
28
  "devDependencies": {
28
29
  "@types/node": "^20.14.2",
30
+ "typedoc": "^0.25.13",
29
31
  "typescript": "^5.4.5"
30
32
  }
31
33
  }
@@ -32,7 +32,7 @@ export default class ForeignInlineListPlugin extends AdminForthPlugin {
32
32
  virtual: true,
33
33
  showIn: ['show'],
34
34
  component: {
35
- show: this.componentPath('InlineList.vue'),
35
+ showRow: this.componentPath('InlineList.vue'),
36
36
  },
37
37
  });
38
38
  }
package/spa/src/App.vue CHANGED
@@ -113,7 +113,8 @@
113
113
  </div>
114
114
  <div v-else class="flex items-center justify-center h-screen">
115
115
  <div class="text-3xl text-gray-400 animate-bounce">
116
- ▶️
116
+ <svg aria-hidden="true" class="w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/><path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/></svg>
117
+ <span class="sr-only">Loading...</span>
117
118
  </div>
118
119
  </div>
119
120
  <AcceptModal />
@@ -191,10 +192,12 @@ async function initRouter() {
191
192
  // initialize components based on data attribute selectors
192
193
  onMounted(async () => {
193
194
  initRouter();
194
- initFlowbite();
195
+ setTimeout(() => {
196
+ initFlowbite();
197
+ }, 100);
195
198
  await coreStore.fetchMenuAndResource();
196
199
  loginRedirectCheckIsReady.value = true;
197
- title.value = coreStore.config.title || 'Admin Forth';
200
+ title.value = coreStore.config?.title || 'AdminForth';
198
201
  useHead({
199
202
  title: title.value,
200
203
  link: [
@@ -528,10 +528,7 @@ async function init() {
528
528
  }
529
529
 
530
530
  onMounted(async () => {
531
- if (!coreStore.flowBitIsInitialised){
532
- initFlowbite();
533
- coreStore.flowBitIsInitialised = true;
534
- }
531
+
535
532
 
536
533
  await init();
537
534
 
@@ -45,10 +45,28 @@
45
45
  class="bg-form-view-bg odd:dark:bg-gray-900 even:dark:bg-gray-800 border-b dark:border-gray-700"
46
46
  >
47
47
  <component
48
- :is="showComponentsPerColumn[column.name] || ShowTableItem"
48
+ v-if="showRowComponentsPerColumn[column.name]"
49
+ :is="showRowComponentsPerColumn[column.name]"
49
50
  :column="column"
50
51
  :row="coreStore.record"
51
52
  />
53
+ <template v-else>
54
+ <td class="px-6 py-4 whitespace-nowrap">
55
+ {{ column.label }}
56
+ </td>
57
+ <td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap">
58
+ <component
59
+ v-if="showComponentsPerColumn[column.name]"
60
+ :is="showComponentsPerColumn[column.name]"
61
+ :column="column"
62
+ :row="coreStore.record"
63
+ />
64
+ <ValueRenderer v-else
65
+ :column="column"
66
+ :row="coreStore.record"
67
+ />
68
+ </td>
69
+ </template>
52
70
  </tr>
53
71
  </tbody>
54
72
  </table>
@@ -76,6 +94,7 @@ const item = ref(null);
76
94
  const route = useRoute();
77
95
  const loading = ref(false);
78
96
  let showComponentsPerColumn = {};
97
+ let showRowComponentsPerColumn = {};
79
98
 
80
99
  const coreStore = useCoreStore();
81
100
 
@@ -93,7 +112,14 @@ onMounted(async () => {
93
112
  acc[column.name] = getCustomComponent(column.component.show);
94
113
  }
95
114
  return acc;
96
- }, {});
115
+ }, {});
116
+ showRowComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
117
+ if (column.component?.showRow) {
118
+ acc[column.name] = getCustomComponent(column.component.showRow);
119
+ }
120
+ return acc;
121
+ }, {});
122
+
97
123
  loading.value = false;
98
124
  });
99
125
 
@@ -1,6 +1,6 @@
1
1
  /** @type {import('tailwindcss').Config} */
2
2
  export default {
3
- content: ["./src/**/*.{vue, js}","./src/*.{vue, js}", "./index.html", "./node_modules/flowbite/**/*.js"],
3
+ content: ["./src/**/*.{vue, js, ts, tsx}","./src/*.{vue, js, ts, tsx}", "./index.html", "./node_modules/flowbite/**/*.js"],
4
4
  theme: {
5
5
  extend: {
6
6
  /* IMPORTANT:ADMINFORTH TAILWIND STYLES */
@@ -49,24 +49,24 @@ export type AdminForthResource = {
49
49
  plugins?: Array<AdminForthPlugin>,
50
50
  hooks?: {
51
51
  show?: {
52
- beforeDatasourceRequest?: Function,
53
- afterDatasourceResponse?: Function,
52
+ beforeDatasourceRequest?: Function | Array<Function>,
53
+ afterDatasourceResponse?: Function | Array<Function>,
54
54
  },
55
55
  list?: {
56
- beforeDatasourceRequest?: Function,
57
- afterDatasourceResponse?: Function,
56
+ beforeDatasourceRequest?: Function | Array<Function>,
57
+ afterDatasourceResponse?: Function | Array<Function>,
58
58
  },
59
59
  create?: {
60
- beforeSave?: Function,
61
- afterSave?: Function,
60
+ beforeSave?: Function | Array<Function>,
61
+ afterSave?: Function | Array<Function>,
62
62
  },
63
63
  edit?: {
64
- beforeSave?: Function,
65
- afterSave?: Function,
64
+ beforeSave?: Function | Array<Function>,
65
+ afterSave?: Function | Array<Function>,
66
66
  },
67
67
  delete?: {
68
- beforeSave?: Function,
69
- afterSave?: Function,
68
+ beforeSave?: Function | Array<Function>,
69
+ afterSave?: Function | Array<Function>,
70
70
  },
71
71
  },
72
72
  options?: {
@@ -134,9 +134,11 @@ export type DataSource = {
134
134
  }
135
135
 
136
136
  export type AdminForthResourceColumnComponent = {
137
- show?: string,
137
+ show?: string, // rewrite value in show
138
+ showRow?: string, // rewrite full view table row (both <td> tags)
138
139
  create?: string,
139
140
  edit?: string,
141
+ list?: string,
140
142
  }
141
143
 
142
144
 
@@ -1,14 +0,0 @@
1
- <template>
2
- <td class="px-6 py-4 whitespace-nowrap">
3
- {{ column.label }}
4
- </td>
5
- <td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap">
6
- <ValueRenderer :column="column" :row="row" />
7
- </td>
8
- </template>
9
-
10
- <script setup>
11
- import ValueRenderer from '@/components/ValueRenderer.vue';
12
-
13
- defineProps(['column', 'row']);
14
- </script>