adminforth 1.0.46 → 1.0.48

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.
@@ -138,7 +138,6 @@ class SQLiteConnector {
138
138
 
139
139
 
140
140
  getData({ resource, limit, offset, sort, filters }) {
141
- console.log('SQLITE getData', { resource, limit, offset, sort, filters });
142
141
  const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
143
142
  const tableName = resource.table;
144
143
 
@@ -148,7 +148,6 @@ class SQLiteConnector {
148
148
  return value;
149
149
  }
150
150
  getData({ resource, limit, offset, sort, filters }) {
151
- console.log('SQLITE getData', { resource, limit, offset, sort, filters });
152
151
  const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
153
152
  const tableName = resource.table;
154
153
  const where = filters.length ? `WHERE ${filters.map((f, i) => {
package/dist/index.js CHANGED
@@ -193,6 +193,15 @@ class AdminForth {
193
193
  if (item.component && !item.path) {
194
194
  errors.push(`Menu item with component must have path : ${JSON.stringify(item)}`);
195
195
  }
196
+ if (item.type === 'resource' && !item.resourceId) {
197
+ errors.push(`Menu item with type 'resource' must have resourceId : ${JSON.stringify(item)}`);
198
+ }
199
+ if (item.resourceId && !this.config.resources.find((res) => res.resourceId === item.resourceId)) {
200
+ errors.push(`Menu item with type 'resourceId' has resourceId which is not in resources: ${JSON.stringify(item)}`);
201
+ }
202
+ if (item.type === 'component' && !item.component) {
203
+ errors.push(`Menu item with type 'component' must have component : ${JSON.stringify(item)}`);
204
+ }
196
205
  // make sure component starts with @@
197
206
  if (item.component) {
198
207
  if (!item.component.startsWith('@@')) {
@@ -384,6 +393,7 @@ class AdminForth {
384
393
  method: 'GET',
385
394
  path: '/get_base_config',
386
395
  handler: (_e) => __awaiter(this, [_e], void 0, function* ({ input, adminUser, cookies }) {
396
+ var _f;
387
397
  const cookieParsed = this.auth.verify(cookies['adminforth_jwt']);
388
398
  let username = '';
389
399
  let userFullName = '';
@@ -424,6 +434,7 @@ class AdminForth {
424
434
  deleteConfirmation: this.config.deleteConfirmation,
425
435
  auth: this.config.auth,
426
436
  usernameField: this.config.auth.usernameField,
437
+ title: (_f = this.config) === null || _f === void 0 ? void 0 : _f.title,
427
438
  },
428
439
  adminUser,
429
440
  version: ADMINFORTH_VERSION,
@@ -433,7 +444,7 @@ class AdminForth {
433
444
  server.endpoint({
434
445
  method: 'POST',
435
446
  path: '/get_resource_columns',
436
- handler: (_f) => __awaiter(this, [_f], void 0, function* ({ body }) {
447
+ handler: (_g) => __awaiter(this, [_g], void 0, function* ({ body }) {
437
448
  const { resourceId } = body;
438
449
  if (!this.statuses.dbDiscover) {
439
450
  return { error: 'Database discovery not started' };
@@ -451,8 +462,8 @@ class AdminForth {
451
462
  server.endpoint({
452
463
  method: 'POST',
453
464
  path: '/get_resource_data',
454
- handler: (_g) => __awaiter(this, [_g], void 0, function* ({ body, adminUser }) {
455
- var _h, _j, _k, _l, _m, _o, _p, _q;
465
+ handler: (_h) => __awaiter(this, [_h], void 0, function* ({ body, adminUser }) {
466
+ var _j, _k, _l, _m, _o, _p, _q, _r;
456
467
  const { resourceId, source } = body;
457
468
  if (['show', 'list'].includes(source) === false) {
458
469
  return { error: 'Invalid source, should be list or show' };
@@ -467,8 +478,8 @@ class AdminForth {
467
478
  if (!resource) {
468
479
  return { error: `Resource ${resourceId} not found` };
469
480
  }
470
- if ((_j = (_h = resource.hooks) === null || _h === void 0 ? void 0 : _h[source]) === null || _j === void 0 ? void 0 : _j.beforeDatasourceRequest) {
471
- const resp = yield ((_l = (_k = resource.hooks) === null || _k === void 0 ? void 0 : _k[source]) === null || _l === void 0 ? void 0 : _l.beforeDatasourceRequest({ resource, query: body, adminUser }));
481
+ if ((_k = (_j = resource.hooks) === null || _j === void 0 ? void 0 : _j[source]) === null || _k === void 0 ? void 0 : _k.beforeDatasourceRequest) {
482
+ 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 }));
472
483
  if (!resp || (!resp.ok && !resp.error)) {
473
484
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
474
485
  }
@@ -533,12 +544,19 @@ class AdminForth {
533
544
  data.data.forEach((item) => {
534
545
  item[col.name] = targetDataMap[item[col.name]];
535
546
  });
547
+ data.data.forEach((item) => {
548
+ Object.keys(item).forEach((key) => {
549
+ if (!targetResource.columns.find((col) => col.name === key) || targetResource.columns.find((col) => col.name === key && col.backendOnly)) {
550
+ delete item[key];
551
+ }
552
+ });
553
+ });
536
554
  })));
537
555
  data.data.forEach((item) => {
538
556
  item._label = resource.itemLabel(item);
539
557
  });
540
- if ((_o = (_m = resource.hooks) === null || _m === void 0 ? void 0 : _m[source]) === null || _o === void 0 ? void 0 : _o.afterDatasourceRequest) {
541
- const resp = yield ((_q = (_p = resource.hooks) === null || _p === void 0 ? void 0 : _p[source]) === null || _q === void 0 ? void 0 : _q.afterDatasourceRequest({ resource, response: data.data, adminUser }));
558
+ if ((_p = (_o = resource.hooks) === null || _o === void 0 ? void 0 : _o[source]) === null || _p === void 0 ? void 0 : _p.afterDatasourceRequest) {
559
+ const resp = yield ((_r = (_q = resource.hooks) === null || _q === void 0 ? void 0 : _q[source]) === null || _r === void 0 ? void 0 : _r.afterDatasourceRequest({ resource, response: data.data, adminUser }));
542
560
  if (!resp || (!resp.ok && !resp.error)) {
543
561
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
544
562
  }
@@ -546,14 +564,22 @@ class AdminForth {
546
564
  return { error: resp.error };
547
565
  }
548
566
  }
567
+ // remove all columns which are not defined in resources, or defined but backendOnly
568
+ data.data.forEach((item) => {
569
+ Object.keys(item).forEach((key) => {
570
+ if (!resource.columns.find((col) => col.name === key) || resource.columns.find((col) => col.name === key && col.backendOnly)) {
571
+ delete item[key];
572
+ }
573
+ });
574
+ });
549
575
  return Object.assign(Object.assign({}, data), { options: resource === null || resource === void 0 ? void 0 : resource.options });
550
576
  }),
551
577
  });
552
578
  server.endpoint({
553
579
  method: 'POST',
554
580
  path: '/get_resource_foreign_data',
555
- handler: (_r) => __awaiter(this, [_r], void 0, function* ({ body, adminUser }) {
556
- var _s, _t, _u, _v;
581
+ handler: (_s) => __awaiter(this, [_s], void 0, function* ({ body, adminUser }) {
582
+ var _t, _u, _v, _w;
557
583
  const { resourceId, column } = body;
558
584
  if (!this.statuses.dbDiscover) {
559
585
  return { error: 'Database discovery not started' };
@@ -574,8 +600,8 @@ class AdminForth {
574
600
  }
575
601
  const targetResourceId = columnConfig.foreignResource.resourceId;
576
602
  const targetResource = this.config.resources.find((res) => res.resourceId == targetResourceId);
577
- if ((_s = columnConfig.foreignResource.hooks) === null || _s === void 0 ? void 0 : _s.beforeDatasourceRequest) {
578
- const resp = yield ((_t = column.foreignResource.hooks) === null || _t === void 0 ? void 0 : _t.beforeDatasourceRequest({ query: body, adminUser }));
603
+ if ((_t = columnConfig.foreignResource.hooks) === null || _t === void 0 ? void 0 : _t.beforeDatasourceRequest) {
604
+ const resp = yield ((_u = column.foreignResource.hooks) === null || _u === void 0 ? void 0 : _u.beforeDatasourceRequest({ query: body, adminUser }));
579
605
  if (!resp || (!resp.ok && !resp.error)) {
580
606
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
581
607
  }
@@ -603,8 +629,8 @@ class AdminForth {
603
629
  const response = {
604
630
  items
605
631
  };
606
- if ((_u = columnConfig.foreignResource.hooks) === null || _u === void 0 ? void 0 : _u.afterDatasourceResponse) {
607
- const resp = yield ((_v = column.foreignResource.hooks) === null || _v === void 0 ? void 0 : _v.afterDatasourceResponse({ response, adminUser }));
632
+ if ((_v = columnConfig.foreignResource.hooks) === null || _v === void 0 ? void 0 : _v.afterDatasourceResponse) {
633
+ const resp = yield ((_w = column.foreignResource.hooks) === null || _w === void 0 ? void 0 : _w.afterDatasourceResponse({ response, adminUser }));
608
634
  if (!resp || (!resp.ok && !resp.error)) {
609
635
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
610
636
  }
@@ -618,7 +644,7 @@ class AdminForth {
618
644
  server.endpoint({
619
645
  method: 'POST',
620
646
  path: '/get_min_max_for_columns',
621
- handler: (_w) => __awaiter(this, [_w], void 0, function* ({ body }) {
647
+ handler: (_x) => __awaiter(this, [_x], void 0, function* ({ body }) {
622
648
  const { resourceId } = body;
623
649
  if (!this.statuses.dbDiscover) {
624
650
  return { error: 'Database discovery not started' };
@@ -645,11 +671,10 @@ class AdminForth {
645
671
  }),
646
672
  });
647
673
  server.endpoint({
648
- noAuth: true, // TODO
649
674
  method: 'POST',
650
675
  path: '/create_record',
651
- handler: (_x) => __awaiter(this, [_x], void 0, function* ({ body, adminUser }) {
652
- var _y, _z, _0, _1, _2, _3, _4, _5, _6;
676
+ handler: (_y) => __awaiter(this, [_y], void 0, function* ({ body, adminUser }) {
677
+ var _z, _0, _1, _2, _3, _4, _5, _6, _7;
653
678
  console.log('create_record', body, this.config.resources);
654
679
  const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
655
680
  if (!resource) {
@@ -657,8 +682,8 @@ class AdminForth {
657
682
  }
658
683
  const record = body['record'];
659
684
  // execute hook if needed
660
- if ((_z = (_y = resource.hooks) === null || _y === void 0 ? void 0 : _y.create) === null || _z === void 0 ? void 0 : _z.beforeSave) {
661
- const resp = yield ((_1 = (_0 = resource.hooks) === null || _0 === void 0 ? void 0 : _0.create) === null || _1 === void 0 ? void 0 : _1.beforeSave({ resource, record, adminUser }));
685
+ if ((_0 = (_z = resource.hooks) === null || _z === void 0 ? void 0 : _z.create) === null || _0 === void 0 ? void 0 : _0.beforeSave) {
686
+ 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 }));
662
687
  if (!resp || (!resp.ok && !resp.error)) {
663
688
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
664
689
  }
@@ -674,7 +699,7 @@ class AdminForth {
674
699
  });
675
700
  }
676
701
  }
677
- if (((_2 = column.required) === null || _2 === void 0 ? void 0 : _2.create) && body['record'][column.name] === undefined) {
702
+ if (((_3 = column.required) === null || _3 === void 0 ? void 0 : _3.create) && body['record'][column.name] === undefined) {
678
703
  return { error: `Column '${column.name}' is required` };
679
704
  }
680
705
  if (column.isUnique) {
@@ -699,8 +724,8 @@ class AdminForth {
699
724
  const connector = this.connectors[resource.dataSource];
700
725
  yield connector.createRecord({ resource, record });
701
726
  // execute hook if needed
702
- if ((_4 = (_3 = resource.hooks) === null || _3 === void 0 ? void 0 : _3.create) === null || _4 === void 0 ? void 0 : _4.afterSave) {
703
- const resp = yield ((_6 = (_5 = resource.hooks) === null || _5 === void 0 ? void 0 : _5.create) === null || _6 === void 0 ? void 0 : _6.afterSave({ resource, record, adminUser }));
727
+ if ((_5 = (_4 = resource.hooks) === null || _4 === void 0 ? void 0 : _4.create) === null || _5 === void 0 ? void 0 : _5.afterSave) {
728
+ 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 }));
704
729
  if (!resp || (!resp.ok && !resp.error)) {
705
730
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
706
731
  }
@@ -714,11 +739,10 @@ class AdminForth {
714
739
  })
715
740
  });
716
741
  server.endpoint({
717
- noAuth: true, // TODO
718
742
  method: 'POST',
719
743
  path: '/update_record',
720
- handler: (_7) => __awaiter(this, [_7], void 0, function* ({ body, adminUser }) {
721
- var _8, _9, _10, _11, _12, _13, _14, _15;
744
+ handler: (_8) => __awaiter(this, [_8], void 0, function* ({ body, adminUser }) {
745
+ var _9, _10, _11, _12, _13, _14, _15, _16;
722
746
  const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
723
747
  if (!resource) {
724
748
  return { error: `Resource '${body['resourceId']}' not found` };
@@ -732,8 +756,8 @@ class AdminForth {
732
756
  }
733
757
  const record = body['record'];
734
758
  // execute hook if needed
735
- if ((_9 = (_8 = resource.hooks) === null || _8 === void 0 ? void 0 : _8.edit) === null || _9 === void 0 ? void 0 : _9.beforeSave) {
736
- const resp = yield ((_11 = (_10 = resource.hooks) === null || _10 === void 0 ? void 0 : _10.edit) === null || _11 === void 0 ? void 0 : _11.beforeSave({ resource, record, adminUser }));
759
+ if ((_10 = (_9 = resource.hooks) === null || _9 === void 0 ? void 0 : _9.edit) === null || _10 === void 0 ? void 0 : _10.beforeSave) {
760
+ 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 }));
737
761
  if (!resp || (!resp.ok && !resp.error)) {
738
762
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
739
763
  }
@@ -742,17 +766,24 @@ class AdminForth {
742
766
  }
743
767
  }
744
768
  const newValues = {};
745
- for (const col of resource.columns.filter((col) => !col.virtual)) {
746
- if (record[col.name] !== oldRecord[col.name]) {
747
- newValues[col.name] = connector.setFieldValue(col, record[col.name]);
769
+ for (const recordField in record) {
770
+ if (record[recordField] !== oldRecord[recordField]) {
771
+ const column = resource.columns.find((col) => col.name === recordField);
772
+ if (column) {
773
+ newValues[recordField] = connector.setFieldValue(column, record[recordField]);
774
+ }
775
+ else {
776
+ newValues[recordField] = record[recordField];
777
+ }
748
778
  }
749
779
  }
780
+ console.log('✅ newValues', newValues);
750
781
  if (Object.keys(newValues).length > 0) {
751
782
  yield connector.updateRecord({ resource, recordId, record, newValues });
752
783
  }
753
784
  // execute hook if needed
754
- if ((_13 = (_12 = resource.hooks) === null || _12 === void 0 ? void 0 : _12.edit) === null || _13 === void 0 ? void 0 : _13.afterSave) {
755
- const resp = yield ((_15 = (_14 = resource.hooks) === null || _14 === void 0 ? void 0 : _14.edit) === null || _15 === void 0 ? void 0 : _15.afterSave({ resource, record, adminUser }));
785
+ if ((_14 = (_13 = resource.hooks) === null || _13 === void 0 ? void 0 : _13.edit) === null || _14 === void 0 ? void 0 : _14.afterSave) {
786
+ 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 }));
756
787
  if (!resp || (!resp.ok && !resp.error)) {
757
788
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
758
789
  }
@@ -766,19 +797,18 @@ class AdminForth {
766
797
  })
767
798
  });
768
799
  server.endpoint({
769
- noAuth: true, // TODO
770
800
  method: 'POST',
771
801
  path: '/delete_record',
772
- handler: (_16) => __awaiter(this, [_16], void 0, function* ({ body, adminUser }) {
773
- var _17, _18, _19, _20, _21, _22, _23, _24;
802
+ handler: (_17) => __awaiter(this, [_17], void 0, function* ({ body, adminUser }) {
803
+ var _18, _19, _20, _21, _22, _23, _24, _25;
774
804
  const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
775
805
  const record = yield this.connectors[resource.dataSource].getRecordByPrimaryKey(resource, body['primaryKey']);
776
806
  if (!resource) {
777
807
  return { error: `Resource '${body['resourceId']}' not found` };
778
808
  }
779
809
  // execute hook if needed
780
- if ((_18 = (_17 = resource.hooks) === null || _17 === void 0 ? void 0 : _17.delete) === null || _18 === void 0 ? void 0 : _18.beforeSave) {
781
- const resp = yield ((_20 = (_19 = resource.hooks) === null || _19 === void 0 ? void 0 : _19.delete) === null || _20 === void 0 ? void 0 : _20.beforeSave({ resource, record, adminUser }));
810
+ if ((_19 = (_18 = resource.hooks) === null || _18 === void 0 ? void 0 : _18.delete) === null || _19 === void 0 ? void 0 : _19.beforeSave) {
811
+ 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 }));
782
812
  if (!resp || (!resp.ok && !resp.error)) {
783
813
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
784
814
  }
@@ -789,8 +819,8 @@ class AdminForth {
789
819
  const connector = this.connectors[resource.dataSource];
790
820
  yield connector.deleteRecord({ resource, recordId: body['primaryKey'] });
791
821
  // execute hook if needed
792
- if ((_22 = (_21 = resource.hooks) === null || _21 === void 0 ? void 0 : _21.delete) === null || _22 === void 0 ? void 0 : _22.afterSave) {
793
- const resp = yield ((_24 = (_23 = resource.hooks) === null || _23 === void 0 ? void 0 : _23.delete) === null || _24 === void 0 ? void 0 : _24.afterSave({ resource, record, adminUser }));
822
+ if ((_23 = (_22 = resource.hooks) === null || _22 === void 0 ? void 0 : _22.delete) === null || _23 === void 0 ? void 0 : _23.afterSave) {
823
+ 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 }));
794
824
  if (!resp || (!resp.ok && !resp.error)) {
795
825
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
796
826
  }
@@ -804,10 +834,9 @@ class AdminForth {
804
834
  })
805
835
  });
806
836
  server.endpoint({
807
- noAuth: true, // TODO
808
837
  method: 'POST',
809
838
  path: '/start_bulk_action',
810
- handler: (_25) => __awaiter(this, [_25], void 0, function* ({ body }) {
839
+ handler: (_26) => __awaiter(this, [_26], void 0, function* ({ body }) {
811
840
  const { resourceId, actionId, recordIds } = body;
812
841
  const resource = this.config.resources.find((res) => res.resourceId == resourceId);
813
842
  if (!resource) {
@@ -7,14 +7,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import fs from 'fs';
11
- import fsExtra from 'fs-extra';
12
- import filewatcher from 'filewatcher';
13
10
  import { exec, spawn } from 'child_process';
14
- import { promisify } from 'util';
15
- import path from 'path';
16
11
  import crypto from 'crypto';
12
+ import filewatcher from 'filewatcher';
13
+ import fs from 'fs';
14
+ import fsExtra from 'fs-extra';
17
15
  import os from 'os';
16
+ import path from 'path';
17
+ import { promisify } from 'util';
18
18
  import { ADMIN_FORTH_ABSOLUTE_PATH } from './utils.js';
19
19
  let TMP_DIR;
20
20
  try {
@@ -71,7 +71,7 @@ class CodeInjector {
71
71
  }
72
72
  prepareSources(_a) {
73
73
  return __awaiter(this, arguments, void 0, function* ({ filesUpdated, verbose = false }) {
74
- var _b, _c, _d, _e, _f, _g;
74
+ var _b, _c, _d, _e, _f, _g, _h;
75
75
  // check SPA_TMP_PATH exists and create if not
76
76
  try {
77
77
  yield fs.promises.access(CodeInjector.SPA_TMP_PATH, fs.constants.F_OK);
@@ -154,6 +154,25 @@ class CodeInjector {
154
154
  }).join('');
155
155
  return `import { ${PascalIconName} } from '@iconify-prerendered/vue-${collection}';`;
156
156
  }).join('\n');
157
+ // for each custom component generate import statement
158
+ const customComponentsDir = (_c = this.adminforth.config.customization) === null || _c === void 0 ? void 0 : _c.customComponentsDir;
159
+ let customComponentsImports = '';
160
+ if (customComponentsDir) {
161
+ // if file - return, if dir - go recursively
162
+ const customComponents = yield fs.promises.readdir(customComponentsDir);
163
+ for (const filePath of customComponents) {
164
+ fs.stat(`${customComponentsDir}/${filePath}`, (err, data) => {
165
+ if (err) {
166
+ console.log(err);
167
+ return;
168
+ }
169
+ if (data.isFile()) {
170
+ const componentName = filePath.split('.')[0].replace('/', '');
171
+ customComponentsImports += `import ${componentName} from '@/custom/${filePath}';\n`;
172
+ }
173
+ });
174
+ }
175
+ }
157
176
  // Generate Vue.component statements for each icon
158
177
  const iconComponents = uniqueIcons.map((icon) => {
159
178
  const [collection, iconName] = icon.split(':');
@@ -162,26 +181,44 @@ class CodeInjector {
162
181
  }).join('');
163
182
  return `app.component('${PascalIconName}', ${PascalIconName});`;
164
183
  }).join('\n');
184
+ // Generate Vue.component statements for each custom component
185
+ let customComponentsComponents = '';
186
+ if (customComponentsDir) {
187
+ const customComponents = yield fs.promises.readdir(customComponentsDir);
188
+ for (const filePath of customComponents) {
189
+ fs.stat(`${customComponentsDir}/${filePath}`, (err, data) => {
190
+ if (err) {
191
+ console.log('Custom components importing error: ', err);
192
+ return;
193
+ }
194
+ if (data.isFile()) {
195
+ const componentName = filePath.split('.')[0].replace('/', '');
196
+ customComponentsComponents += `app.component('${componentName}', ${componentName});\n`;
197
+ }
198
+ });
199
+ }
200
+ }
165
201
  let imports = iconImports + '\n';
166
- if ((_c = this.adminforth.config.customization) === null || _c === void 0 ? void 0 : _c.vueUsesFile) {
202
+ imports += customComponentsImports + '\n';
203
+ if ((_d = this.adminforth.config.customization) === null || _d === void 0 ? void 0 : _d.vueUsesFile) {
167
204
  imports += `import addCustomUses from '${this.adminforth.config.customization.vueUsesFile}';\n`;
168
205
  }
169
206
  // inject that code into spa_tmp/src/App.vue
170
207
  const appVuePath = path.join(CodeInjector.SPA_TMP_PATH, 'src', 'main.ts');
171
208
  let appVueContent = yield fs.promises.readFile(appVuePath, 'utf-8');
172
209
  appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH IMPORTS */', imports);
173
- appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH COMPONENT REGISTRATIONS */', iconComponents + '\n');
174
- if ((_d = this.adminforth.config.customization) === null || _d === void 0 ? void 0 : _d.vueUsesFile) {
210
+ appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH COMPONENT REGISTRATIONS */', iconComponents + '\n' + customComponentsComponents + '\n');
211
+ if ((_e = this.adminforth.config.customization) === null || _e === void 0 ? void 0 : _e.vueUsesFile) {
175
212
  appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH CUSTOM USES */', 'addCustomUses(app);');
176
213
  }
177
214
  yield fs.promises.writeFile(appVuePath, appVueContent);
178
215
  /* generate custom rotes */
179
216
  const homepageMenuItem = this.adminforth.config.menu.find((mi) => mi.homepage);
180
217
  let childrenHomePageMenuItem = this.adminforth.config.menu.find((mi) => { var _a; return mi.children && ((_a = mi.children) === null || _a === void 0 ? void 0 : _a.find((mi) => mi.homepage)); });
181
- let childrenHomepage = (_e = childrenHomePageMenuItem === null || childrenHomePageMenuItem === void 0 ? void 0 : childrenHomePageMenuItem.children) === null || _e === void 0 ? void 0 : _e.find((mi) => mi.homepage);
218
+ let childrenHomepage = (_f = childrenHomePageMenuItem === null || childrenHomePageMenuItem === void 0 ? void 0 : childrenHomePageMenuItem.children) === null || _f === void 0 ? void 0 : _f.find((mi) => mi.homepage);
182
219
  let homePagePath = (homepageMenuItem === null || homepageMenuItem === void 0 ? void 0 : homepageMenuItem.path) || `/resource/${childrenHomepage === null || childrenHomepage === void 0 ? void 0 : childrenHomepage.resourceId}`;
183
220
  if (!homePagePath) {
184
- homePagePath = ((_f = this.adminforth.config.menu.filter((mi) => mi.path)[0]) === null || _f === void 0 ? void 0 : _f.path) || `/resource/${(_g = this.adminforth.config.menu.filter((mi) => mi.children)[0]) === null || _g === void 0 ? void 0 : _g.resourceId}`;
221
+ homePagePath = ((_g = this.adminforth.config.menu.filter((mi) => mi.path)[0]) === null || _g === void 0 ? void 0 : _g.path) || `/resource/${(_h = this.adminforth.config.menu.filter((mi) => mi.children)[0]) === null || _h === void 0 ? void 0 : _h.resourceId}`;
185
222
  }
186
223
  routes += `{
187
224
  path: '/',
@@ -15,6 +15,7 @@
15
15
  "flowbite": "^2.3.0",
16
16
  "flowbite-datepicker": "^1.2.6",
17
17
  "pinia": "^2.1.7",
18
+ "unhead": "^1.9.12",
18
19
  "vue": "^3.4.21",
19
20
  "vue-router": "^4.3.0",
20
21
  "vue-slider-component": "^4.1.0-beta.7"
@@ -1149,6 +1150,41 @@
1149
1150
  "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
1150
1151
  "dev": true
1151
1152
  },
1153
+ "node_modules/@unhead/dom": {
1154
+ "version": "1.9.12",
1155
+ "resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-1.9.12.tgz",
1156
+ "integrity": "sha512-3MY1TbZmEjGNZapi3wvJW0vWNS2CLKHt7/m57sScDHCNvNBe1mTwrIOhtZFDgAndhml2EVQ68RMa0Vhum/M+cw==",
1157
+ "dependencies": {
1158
+ "@unhead/schema": "1.9.12",
1159
+ "@unhead/shared": "1.9.12"
1160
+ },
1161
+ "funding": {
1162
+ "url": "https://github.com/sponsors/harlan-zw"
1163
+ }
1164
+ },
1165
+ "node_modules/@unhead/schema": {
1166
+ "version": "1.9.12",
1167
+ "resolved": "https://registry.npmjs.org/@unhead/schema/-/schema-1.9.12.tgz",
1168
+ "integrity": "sha512-ue2FKyIZKsuZDpWJBMlBGwMm4s+vFeU3NUWsNt8Z+2JkOUIqO/VG43LxNgY1M595bOS71Gdxk+G9VtzfKJ5uEA==",
1169
+ "dependencies": {
1170
+ "hookable": "^5.5.3",
1171
+ "zhead": "^2.2.4"
1172
+ },
1173
+ "funding": {
1174
+ "url": "https://github.com/sponsors/harlan-zw"
1175
+ }
1176
+ },
1177
+ "node_modules/@unhead/shared": {
1178
+ "version": "1.9.12",
1179
+ "resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.9.12.tgz",
1180
+ "integrity": "sha512-72wlLXG3FP3sXUrwd42Uv8jYpHSg4R6IFJcsl+QisRjKM89JnjOFSw1DqWO4IOftW5xOxS4J5v7SQyJ4NJo7Bw==",
1181
+ "dependencies": {
1182
+ "@unhead/schema": "1.9.12"
1183
+ },
1184
+ "funding": {
1185
+ "url": "https://github.com/sponsors/harlan-zw"
1186
+ }
1187
+ },
1152
1188
  "node_modules/@vitejs/plugin-vue": {
1153
1189
  "version": "5.0.4",
1154
1190
  "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz",
@@ -2471,6 +2507,11 @@
2471
2507
  "he": "bin/he"
2472
2508
  }
2473
2509
  },
2510
+ "node_modules/hookable": {
2511
+ "version": "5.5.3",
2512
+ "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
2513
+ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="
2514
+ },
2474
2515
  "node_modules/ignore": {
2475
2516
  "version": "5.3.1",
2476
2517
  "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
@@ -3914,6 +3955,20 @@
3914
3955
  "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
3915
3956
  "dev": true
3916
3957
  },
3958
+ "node_modules/unhead": {
3959
+ "version": "1.9.12",
3960
+ "resolved": "https://registry.npmjs.org/unhead/-/unhead-1.9.12.tgz",
3961
+ "integrity": "sha512-s6VxcTV45hy8c/IioKQOonFnAO+kBOSpgDfqEHhnU0YVSQYaRPEp9pzW1qSPf0lx+bg9RKeOQyNNbSGGUP26aQ==",
3962
+ "dependencies": {
3963
+ "@unhead/dom": "1.9.12",
3964
+ "@unhead/schema": "1.9.12",
3965
+ "@unhead/shared": "1.9.12",
3966
+ "hookable": "^5.5.3"
3967
+ },
3968
+ "funding": {
3969
+ "url": "https://github.com/sponsors/harlan-zw"
3970
+ }
3971
+ },
3917
3972
  "node_modules/update-browserslist-db": {
3918
3973
  "version": "1.0.16",
3919
3974
  "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz",
@@ -4260,6 +4315,14 @@
4260
4315
  "funding": {
4261
4316
  "url": "https://github.com/sponsors/sindresorhus"
4262
4317
  }
4318
+ },
4319
+ "node_modules/zhead": {
4320
+ "version": "2.2.4",
4321
+ "resolved": "https://registry.npmjs.org/zhead/-/zhead-2.2.4.tgz",
4322
+ "integrity": "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==",
4323
+ "funding": {
4324
+ "url": "https://github.com/sponsors/harlan-zw"
4325
+ }
4263
4326
  }
4264
4327
  }
4265
4328
  }
@@ -19,6 +19,7 @@
19
19
  "flowbite": "^2.3.0",
20
20
  "flowbite-datepicker": "^1.2.6",
21
21
  "pinia": "^2.1.7",
22
+ "unhead": "^1.9.12",
22
23
  "vue": "^3.4.21",
23
24
  "vue-router": "^4.3.0",
24
25
  "vue-slider-component": "^4.1.0-beta.7"
@@ -64,6 +64,7 @@
64
64
  </div>
65
65
  </nav>
66
66
 
67
+
67
68
  <aside
68
69
  v-if="loggedIn"
69
70
 
@@ -132,10 +133,22 @@ import AcceptModal from './components/AcceptModal.vue';
132
133
  import MenuLink from './components/MenuLink.vue';
133
134
  import { useRoute, useRouter } from 'vue-router';
134
135
  import { getIcon } from '@/utils';
136
+ import { useHead } from 'unhead'
137
+ import { createHead } from 'unhead'
138
+ const coreStore = useCoreStore();
139
+
140
+
141
+ createHead()
142
+
143
+
144
+
145
+
135
146
 
136
147
 
137
148
  const route = useRoute();
138
149
  const router = useRouter();
150
+ const title = ref('');
151
+
139
152
 
140
153
  const routerIsReady = ref(false);
141
154
 
@@ -153,7 +166,6 @@ async function logout() {
153
166
  router.push({ name: 'login' });
154
167
  }
155
168
 
156
- const coreStore = useCoreStore();
157
169
 
158
170
 
159
171
 
@@ -167,6 +179,11 @@ onMounted(async () => {
167
179
  initRouter();
168
180
  initFlowbite();
169
181
  await coreStore.fetchMenuAndResource();
182
+ title.value = coreStore.config.title || 'Admin Forth';
183
+ useHead({
184
+ title: title.value,
185
+
186
+ })
170
187
  })
171
188
 
172
189
  </script>
@@ -29,9 +29,9 @@ export async function callAdminForthApi({ path, method, body=undefined }) {
29
29
  }
30
30
  }
31
31
 
32
-
33
-
34
-
32
+ export function getCustomComponent(name) {
33
+ return resolveComponent(name);
34
+ }
35
35
 
36
36
  export function getIcon(icon: string) {
37
37
  // icon format is "feather:icon-name". We need to get IconName in pascal case
@@ -45,9 +45,9 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
45
45
  import ResourceForm from '@/components/ResourceForm.vue';
46
46
  import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
47
47
  import { useCoreStore } from '@/stores/core';
48
- import { callAdminForthApi } from '@/utils';
48
+ import { callAdminForthApi, getCustomComponent } from '@/utils';
49
49
  import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
50
- import { defineAsyncComponent, onMounted, ref } from 'vue';
50
+ import { onMounted, ref } from 'vue';
51
51
  import { useRoute, useRouter } from 'vue-router';
52
52
 
53
53
  const isValid = ref(false);
@@ -75,12 +75,10 @@ onMounted(async () => {
75
75
  resourceId: route.params.resourceId
76
76
  });
77
77
  createComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
78
- if (column.component?.create) {
79
- const path = column.component.create.replace('@@', '../custom');
80
- let component = defineAsyncComponent(() => import(`${path}`))
81
- acc[column.name] = component;
82
- }
83
- return acc;
78
+ if (column.component?.show) {
79
+ acc[column.name] = getCustomComponent(column.component.create.replace('@@', '').replace('./custom/', '')).split('.')[0];
80
+ }
81
+ return acc;
84
82
  }, {});
85
83
  loading.value = false;
86
84
  });