adminforth 1.1.65 → 1.1.67

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 (62) hide show
  1. package/auth.ts +19 -4
  2. package/dataConnectors/mongo.ts +7 -13
  3. package/dataConnectors/postgres.ts +2 -2
  4. package/dataConnectors/sqlite.ts +2 -2
  5. package/dist/auth.js +14 -4
  6. package/dist/dataConnectors/mongo.js +1 -9
  7. package/dist/index.js +4 -9
  8. package/dist/plugins/AuditLogPlugin/custom/AuditLogView.vue +7 -6
  9. package/dist/plugins/AuditLogPlugin/index.js +17 -13
  10. package/dist/servers/express.js +1 -1
  11. package/dist/spa/index.html +2 -2
  12. package/dist/spa/package-lock.json +17 -4
  13. package/dist/spa/package.json +2 -1
  14. package/dist/spa/spa/src/App.vue +24 -12
  15. package/dist/spa/spa/src/composables/useStores.ts +16 -4
  16. package/dist/spa/spa/src/main.ts +1 -1
  17. package/dist/spa/src/App.vue +125 -45
  18. package/dist/spa/src/assets/logo.svg +19 -1
  19. package/dist/spa/src/components/AcceptModal.vue +2 -9
  20. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
  21. package/dist/spa/src/components/CustomDatePicker.vue +16 -3
  22. package/dist/spa/src/components/CustomDateRangePicker.vue +11 -2
  23. package/dist/spa/src/components/Dropdown.vue +6 -1
  24. package/dist/spa/src/components/Filters.vue +40 -19
  25. package/dist/spa/src/components/ResourceForm.vue +26 -24
  26. package/dist/spa/src/components/ResourceListTable.vue +419 -0
  27. package/dist/spa/src/components/Toast.vue +66 -0
  28. package/dist/spa/src/components/ValueRenderer.vue +14 -8
  29. package/dist/spa/src/composables/useFrontendApi.ts +26 -0
  30. package/dist/spa/src/composables/useStores.ts +113 -0
  31. package/dist/spa/src/main.ts +1 -1
  32. package/dist/spa/src/router/index.ts +30 -20
  33. package/dist/spa/src/spa_types/core.ts +51 -0
  34. package/dist/spa/src/stores/core.ts +48 -31
  35. package/dist/spa/src/stores/filters.ts +22 -0
  36. package/dist/spa/src/stores/modal.ts +13 -3
  37. package/dist/spa/src/stores/toast.ts +15 -0
  38. package/dist/spa/src/stores/user.ts +54 -0
  39. package/dist/spa/src/utils.ts +62 -7
  40. package/dist/spa/src/views/CreateView.vue +67 -15
  41. package/dist/spa/src/views/EditView.vue +45 -11
  42. package/dist/spa/src/views/ListView.vue +82 -366
  43. package/dist/spa/src/views/LoginView.vue +16 -4
  44. package/dist/spa/src/views/ShowView.vue +105 -21
  45. package/dist/spa/tailwind.config.js +1 -1
  46. package/dist/spa/vite.config.ts +6 -0
  47. package/index.ts +7 -12
  48. package/package.json +1 -1
  49. package/plugins/AuditLogPlugin/custom/AuditLogView.vue +7 -6
  50. package/plugins/AuditLogPlugin/index.ts +22 -15
  51. package/servers/express.ts +1 -1
  52. package/spa/src/App.vue +24 -12
  53. package/spa/src/composables/useStores.ts +16 -4
  54. package/spa/src/main.ts +1 -1
  55. package/types/AdminForthConfig.ts +18 -4
  56. package/types/FrontendAPI.ts +1 -15
  57. package/dist/plugins/AccessControl/index.js +0 -66
  58. package/dist/plugins/AccessControl/types.js +0 -1
  59. package/dist/spa/public/favicon.ico +0 -0
  60. package/dist/spa/spa/public/favicon.ico +0 -0
  61. package/dist/types.js +0 -30
  62. /package/dist/spa/{spa/public → public/assets}/favicon.png +0 -0
@@ -1,21 +1,38 @@
1
1
  <template>
2
2
  <div class="relative">
3
+ <component
4
+ v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.beforeBreadcrumbs || []"
5
+ :is="getCustomComponent(c)"
6
+ :meta="c.meta"
7
+ :record="coreStore.record"
8
+ :resource="coreStore.resource"
9
+ :adminUser="coreStore.adminUser"
10
+ />
3
11
  <BreadcrumbsWithButtons>
4
- <RouterLink :to="{ name: 'resource-edit', params: { resourceId: $route.params.resourceId, primaryKey: $route.params.primaryKey } }"
5
- class="flex items-center py-1 px-3 me-2 mb-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
12
+ <RouterLink v-if="coreStore?.resourceOptions?.allowedActions?.edit" :to="{ name: 'resource-edit', params: { resourceId: $route.params.resourceId, primaryKey: $route.params.primaryKey } }"
13
+ class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-default border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
6
14
  >
7
15
  <IconPenSolid class="w-4 h-4" />
8
16
  Edit
9
17
  </RouterLink>
10
18
 
11
- <button @click="deleteRecord"
12
- class="flex items-center py-1 px-3 mb-2 text-sm font-medium text-red-600 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-red-500 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
19
+ <button v-if="coreStore?.resourceOptions?.allowedActions?.delete" @click="deleteRecord"
20
+ class="flex items-center py-1 px-3 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-white border border-gray-300 hover:bg-gray-100 hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-red-500 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
13
21
  >
14
22
  <IconTrashBinSolid class="w-4 h-4" />
15
23
  Delete
16
24
  </button>
17
25
  </BreadcrumbsWithButtons>
18
26
 
27
+ <component
28
+ v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.afterBreadcrumbs || []"
29
+ :is="getCustomComponent(c)"
30
+ :meta="c.meta"
31
+ :record="coreStore.record"
32
+ :resource="coreStore.resource"
33
+ :adminUser="coreStore.adminUser"
34
+ />
35
+
19
36
  <div v-if="loading" role="status" class="max-w-sm animate-pulse">
20
37
  <div class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4"></div>
21
38
  <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px] mb-2.5"></div>
@@ -29,7 +46,7 @@
29
46
  v-else
30
47
  class="relative overflow-x-auto shadow-resourse-form-shadow "
31
48
  >
32
- <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
49
+ <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 rounded-default">
33
50
  <thead class="text-xs text-gray-700 uppercase bg-form-view-heading dark:bg-gray-700 dark:text-gray-400">
34
51
  <tr>
35
52
  <th scope="col" class="px-6 py-3">
@@ -41,23 +58,49 @@
41
58
  </tr>
42
59
  </thead>
43
60
  <tbody>
44
- <tr v-for="column in coreStore.resourceColumns?.filter(c => c.showIn.includes('show'))" :key="column.name"
61
+ <tr v-for="column in coreStore.resource?.columns.filter(c => c.showIn.includes('show'))" :key="column.name"
45
62
  class="bg-form-view-bg odd:dark:bg-gray-900 even:dark:bg-gray-800 border-b dark:border-gray-700"
46
63
  >
47
- <td class="px-6 py-4 whitespace-nowrap">
48
- {{ column.label }}
64
+ <component
65
+ v-if="column.components?.showRow"
66
+ :is="getCustomComponent(column.components.showRow)"
67
+ :meta="column.components.showRow.meta"
68
+ :column="column"
69
+ :resource="coreStore.resource"
70
+ :record="coreStore.record"
71
+ />
72
+ <template v-else>
73
+ <td class="px-6 py-4 whitespace-nowrap "> <!--align-top-->
74
+ {{ column.label }}
49
75
  </td>
50
76
  <td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap">
51
- <component
52
- :is="showComponentsPerColumn[column.name] || ValueRenderer"
53
- :column="column"
54
- :row="coreStore.record"
55
- />
77
+ <component
78
+ v-if="showComponentsPerColumn[column.name]"
79
+ :is="showComponentsPerColumn[column.name]"
80
+ :resource="coreStore.resource"
81
+ :meta="column.components.show.meta"
82
+ :column="column"
83
+ :record="coreStore.record"
84
+ />
85
+ <ValueRenderer v-else
86
+ :column="column"
87
+ :record="coreStore.record"
88
+ />
56
89
  </td>
90
+ </template>
57
91
  </tr>
58
-
59
92
  </tbody>
60
93
  </table>
94
+
95
+ <component
96
+ v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.bottom || []"
97
+ :is="getCustomComponent(c)"
98
+ :meta="c.meta"
99
+ :column="column"
100
+ :record="coreStore.record"
101
+ :resource="coreStore.resource"
102
+ :adminUser="coreStore.adminUser"
103
+ />
61
104
  </div>
62
105
 
63
106
 
@@ -68,37 +111,78 @@
68
111
  <script setup>
69
112
 
70
113
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
114
+
71
115
  import ValueRenderer from '@/components/ValueRenderer.vue';
72
116
  import { useCoreStore } from '@/stores/core';
73
- import { getCustomComponent } from '@/utils';
117
+ import { useModalStore } from '@/stores/modal';
118
+ import { getCustomComponent, checkAcessByAllowedActions } from '@/utils';
74
119
  import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
75
120
  import { onMounted, ref } from 'vue';
76
- import { useRoute } from 'vue-router';
121
+ import { useRoute,useRouter } from 'vue-router';
122
+ import {callAdminForthApi} from '@/utils';
123
+ import {showSuccesTost} from '@/composables/useFrontendApi';
77
124
 
78
125
 
79
126
  const item = ref(null);
80
127
  const route = useRoute();
128
+ const router = useRouter();
81
129
  const loading = ref(false);
82
130
  let showComponentsPerColumn = {};
83
131
 
132
+ console.log(route.params,'showWiev');
133
+
134
+
84
135
  const coreStore = useCoreStore();
136
+ const modalStore = useModalStore();
85
137
 
86
138
  onMounted(async () => {
87
139
  loading.value = true;
88
- await coreStore.fetchColumns({
140
+ await coreStore.fetchResourceFull({
89
141
  resourceId: route.params.resourceId
90
142
  });
91
143
  await coreStore.fetchRecord({
92
144
  resourceId: route.params.resourceId,
93
145
  primaryKey: route.params.primaryKey,
94
146
  });
95
- showComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
96
- if (column.component?.show) {
97
- acc[column.name] = getCustomComponent(column.component.show);
147
+ checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'show');
148
+
149
+ showComponentsPerColumn = coreStore.resource.columns.reduce((acc, column) => {
150
+ if (column.components?.show) {
151
+ acc[column.name] = getCustomComponent(column.components.show);
98
152
  }
99
153
  return acc;
100
- }, {});
154
+ }, {});
101
155
  loading.value = false;
102
156
  });
103
157
 
158
+ async function deleteRecord(row) {
159
+ const data = await window.adminforth.confirm({
160
+ message: 'Are you sure you want to delete this item?',
161
+ yes: 'Delete',
162
+ no: 'Cancel',
163
+ });
164
+ if (data) {
165
+ try {
166
+ const res = await callAdminForthApi({
167
+ path: '/delete_record',
168
+ method: 'POST',
169
+ body: {
170
+ resourceId: route.params.resourceId,
171
+ primaryKey: route.params.primaryKey,
172
+ }});
173
+ if (!res.error){
174
+ router.push({ name: 'resource-list', params: { resourceId: route.params.resourceId } });
175
+ showSuccesTost('Record deleted successfully')
176
+ } else {
177
+ console.error(res.error)
178
+ }
179
+
180
+ } catch (e) {
181
+ console.error(e);
182
+ };
183
+ }
184
+
185
+
186
+ }
187
+
104
188
  </script>
@@ -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 */
@@ -11,6 +11,12 @@ const customLogger = {
11
11
  console.log(msg);
12
12
  }
13
13
  },
14
+ warnOnce(msg: string) {
15
+ console.warn('warn once', msg);
16
+ if (!this.hasWarned) {
17
+ this.hasWarned = true;
18
+ }
19
+ },
14
20
  warn(msg: string) {
15
21
  console.warn(msg);
16
22
  },
package/index.ts CHANGED
@@ -19,6 +19,7 @@ import { AdminForthConfig, AdminForthClass, AdminForthComponentDeclaration, Admi
19
19
  AllowedActionsEnum,
20
20
  AllowedActions,
21
21
  ActionCheckSource,
22
+ AdminForthDataSourceConnector,
22
23
  } from './types/AdminForthConfig.js';
23
24
  import path from 'path';
24
25
  import AdminForthPlugin from './plugins/base.js';
@@ -45,7 +46,7 @@ class AdminForth implements AdminForthClass {
45
46
  express: ExpressServer;
46
47
  auth: AdminForthAuth;
47
48
  codeInjector: CodeInjector;
48
- connectors: any;
49
+ connectors;
49
50
  connectorClasses: any;
50
51
  runningHotReload: boolean;
51
52
  activatedPlugins: Array<AdminForthPlugin>;
@@ -496,7 +497,7 @@ class AdminForth implements AdminForthClass {
496
497
  if (!this.config.databaseConnectors[dbType]) {
497
498
  throw new Error(`Database type ${dbType} is not supported, consider using databaseConnectors in AdminForth config`);
498
499
  }
499
- this.connectors[ds.id] = new this.config.databaseConnectors[dbType]({url: ds.url});
500
+ this.connectors[ds.id] = new this.config.databaseConnectors[dbType]({url: ds.url});
500
501
  });
501
502
 
502
503
  await Promise.all(this.config.resources.map(async (res) => {
@@ -625,7 +626,7 @@ class AdminForth implements AdminForthClass {
625
626
  const { username, password } = body;
626
627
  let token;
627
628
  if (username === this.config.rootUser.username && password === this.config.rootUser.password) {
628
- token = this.auth.issueJWT({ username, pk: null });
629
+ this.auth.setAuthCookie({ response, username, pk: null });
629
630
  } else {
630
631
  // get resource from db
631
632
  if (!this.config.auth) {
@@ -663,15 +664,11 @@ class AdminForth implements AdminForthClass {
663
664
  console.log('User record', userRecord, passwordHash) // why does it has no hash?
664
665
  const valid = await AdminForthAuth.verifyPassword(password, passwordHash);
665
666
  if (valid) {
666
- token = this.auth.issueJWT({
667
- username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name]
668
- });
667
+ this.auth.setAuthCookie({ response, username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name] });
669
668
  } else {
670
669
  return { error: INVALID_MESSAGE };
671
670
  }
672
671
  }
673
-
674
- response.setHeader('Set-Cookie', `adminforth_jwt=${token}; Path=${this.config.baseUrl || '/'}; HttpOnly; SameSite=Strict`);
675
672
  return { ok: true };
676
673
  },
677
674
  });
@@ -689,7 +686,7 @@ class AdminForth implements AdminForthClass {
689
686
  method: 'POST',
690
687
  path: '/logout',
691
688
  handler: async ({ response }) => {
692
- response.setHeader('Set-Cookie', `adminforth_jwt=; Path=${this.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=Thu, 01 Jan 1970 00:00:00 GMT`);
689
+ this.auth.removeAuthCookie({ response });
693
690
  return { ok: true };
694
691
  },
695
692
  })
@@ -725,10 +722,8 @@ class AdminForth implements AdminForthClass {
725
722
  let userFullName = ''
726
723
  if (adminUser.isRoot) {
727
724
  username = this.config.rootUser.username;
728
- console.log(username, 'this is the root')
729
725
  } else {
730
726
  const dbUser = adminUser.dbUser;
731
- console.log(dbUser,'this is the db user',adminUser,'this is the admin user')
732
727
  username = dbUser[this.config.auth.usernameField];
733
728
  userFullName =dbUser[this.config.auth.userFullNameField];
734
729
  }
@@ -1172,7 +1167,7 @@ class AdminForth implements AdminForthClass {
1172
1167
 
1173
1168
  // execute hook if needed
1174
1169
  for (const hook of listify(resource.hooks?.edit?.afterSave as AfterSaveFunction[])) {
1175
- const resp = await hook({ resource, record, adminUser });
1170
+ const resp = await hook({ resource, record, adminUser, oldRecord });
1176
1171
  if (!resp || (!resp.ok && !resp.error)) {
1177
1172
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
1178
1173
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.1.65",
3
+ "version": "1.1.67",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -2,19 +2,20 @@
2
2
  import VueDiff from 'vue-diff';
3
3
  import { ref } from 'vue';
4
4
  import 'vue-diff/dist/index.css';
5
-
6
- const props = defineProps(['prev', 'current', 'meta']);
5
+ import { app } from '@/main';
7
6
 
8
7
  app.use(VueDiff);
8
+
9
+ const props = defineProps(['column', 'record', 'meta', 'resource', 'adminUser']);
9
10
  </script>
10
11
 
11
12
  <template>
12
13
  <Diff
13
14
  :mode="'split'"
14
15
  :theme="'light'"
15
- :language="'plaintext'"
16
- :prev="props.prev"
17
- :current="props.current"
16
+ :language="'JSON'"
17
+ :prev="JSON.stringify(JSON.parse(props.record[props.meta.resourceColumns.resourceDataColumnName]).oldRecord, null, 2)"
18
+ :current="JSON.stringify(JSON.parse(props.record[props.meta.resourceColumns.resourceDataColumnName]).newRecord, null, 2)"
18
19
  />
19
- </template>
20
+ </template>
20
21
 
@@ -4,7 +4,9 @@ import {
4
4
  AllowedActionsEnum,
5
5
  BeforeSaveFunction,
6
6
  AdminUser,
7
- AdminForthDataTypes
7
+ AdminForthDataTypes,
8
+ AdminForthResourcePages,
9
+ AdminForthFilterOperators
8
10
  } from "../../types/AdminForthConfig.js";
9
11
  import AdminForthPlugin from "../base.js";
10
12
  import { PluginOptions } from "./types.js";
@@ -24,19 +26,25 @@ export default class AuditLogPlugin extends AdminForthPlugin {
24
26
 
25
27
  static defaultError = 'Sorry, you do not have access to this resource.'
26
28
 
27
- createLogRecord = async (resource: AdminForthResource, action: AllowedActionsEnum, data: Object, user: AdminUser) => {
29
+ createLogRecord = async (resource: AdminForthResource, action: AllowedActionsEnum, data: Object, user: AdminUser, oldRecord: Object) => {
28
30
  const recordIdFieldName = resource.columns.find((c) => c.primaryKey === true)?.name;
29
- const recordId = data[recordIdFieldName];
31
+ const recordId = data?.[recordIdFieldName] || oldRecord?.[recordIdFieldName];
32
+ const connector = this.adminforth.connectors[resource.dataSource];
33
+ const newRecord = await connector.getRecordByPrimaryKey(resource, recordId);
34
+
35
+ let newData = {
36
+ 'oldRecord': oldRecord || {},
37
+ 'newRecord': newRecord
38
+ }
30
39
  const record = {
31
40
  [this.options.resourceColumns.resourceIdColumnName]: resource.resourceId,
32
41
  [this.options.resourceColumns.resourceActionColumnName]: action,
33
- [this.options.resourceColumns.resourceDataColumnName]: data,
42
+ [this.options.resourceColumns.resourceDataColumnName]: newData,
34
43
  [this.options.resourceColumns.resourceUserIdColumnName]: user.pk,
35
44
  [this.options.resourceColumns.resourceRecordIdColumnName]: recordId,
36
45
  [this.options.resourceColumns.resourceCreatedColumnName]: new Date()
37
46
  }
38
47
  const auditLogResource = this.adminforth.config.resources.find((r) => r.resourceId === this.auditLogResource);
39
- console.log('rec', record);
40
48
  await this.adminforth.createResourceRecord({ resource: auditLogResource, record, adminUser: user});
41
49
  return {ok: true};
42
50
  }
@@ -60,9 +68,9 @@ export default class AuditLogPlugin extends AdminForthPlugin {
60
68
  // type: AdminForthDataTypes.STRING
61
69
  }
62
70
  }
63
-
71
+
64
72
  diffColumn.components = {
65
- showRow: {
73
+ show: {
66
74
  file: this.componentPath('AuditLogView.vue'),
67
75
  meta: {
68
76
  ...this.options,
@@ -70,14 +78,13 @@ export default class AuditLogPlugin extends AdminForthPlugin {
70
78
  }
71
79
  }
72
80
  }
73
- resource.columns.push(diffColumn);
74
81
  return;
75
82
  }
76
83
 
77
84
  const defaultHooks = {
78
- create: { beforeSave: [] },
79
- edit: { beforeSave: [] },
80
- delete: { beforeSave: [] }
85
+ create: { afterSave: [] },
86
+ edit: { afterSave: [] },
87
+ delete: { afterSave: [] }
81
88
  }
82
89
 
83
90
  if ( !resource.hooks ) {
@@ -87,11 +94,11 @@ export default class AuditLogPlugin extends AdminForthPlugin {
87
94
  }
88
95
 
89
96
  Object.keys(resource.hooks).forEach((hook) => {
90
- if(!Array.isArray(resource.hooks[hook].beforeSave)){
91
- resource.hooks[hook].beforeSave = [resource.hooks[hook].beforeSave]
97
+ if(!Array.isArray(resource.hooks[hook].afterSave)){
98
+ resource.hooks[hook].afterSave = [resource.hooks[hook].afterSave]
92
99
  }
93
- resource.hooks[hook].beforeSave.push(async ({resource, record, adminUser}) => {
94
- return await this.createLogRecord(resource, hook as AllowedActionsEnum, record, adminUser)
100
+ resource.hooks[hook].afterSave.push(async ({resource, record, adminUser, oldRecord}) => {
101
+ return await this.createLogRecord(resource, hook as AllowedActionsEnum, record, adminUser, oldRecord)
95
102
  })
96
103
  })
97
104
  })
@@ -157,7 +157,7 @@ class ExpressServer implements ExpressHttpServer {
157
157
  res.status(401).send('Unauthorized by AdminForth');
158
158
  return
159
159
  }
160
- const adminforthUser = await this.adminforth.auth.verify(jwt);
160
+ const adminforthUser = await this.adminforth.auth.verify(jwt, 'auth');
161
161
  if (!adminforthUser) {
162
162
  res.status(401).send('Unauthorized by AdminForth');
163
163
  } else {
package/spa/src/App.vue CHANGED
@@ -163,7 +163,7 @@
163
163
  </style>
164
164
 
165
165
  <script setup lang="ts">
166
- import { computed, onMounted, ref, watch, defineComponent } from 'vue';
166
+ import { computed, onMounted, ref, watch, defineComponent, onBeforeMount } from 'vue';
167
167
  import { RouterLink, RouterView } from 'vue-router';
168
168
  import { initFlowbite } from 'flowbite'
169
169
  import './index.scss'
@@ -211,6 +211,8 @@ const theme = ref('light');
211
211
  function toggleTheme() {
212
212
  theme.value = theme.value === 'light' ? 'dark' : 'light';
213
213
  document.documentElement.classList.toggle('dark');
214
+ window.localStorage.setItem('theme', theme.value);
215
+
214
216
  }
215
217
 
216
218
  function clickOnMenuItem (label: string) {
@@ -237,14 +239,8 @@ async function initRouter() {
237
239
  }
238
240
 
239
241
  async function loadMenu() {
240
- await coreStore.fetchMenuAndResource()
242
+ await coreStore.fetchMenuAndResource();
241
243
  loginRedirectCheckIsReady.value = true;
242
-
243
- coreStore.menu.forEach((item, i) => {
244
- if (item.open) {
245
- opened.value.push(i);
246
- }
247
- });
248
244
  }
249
245
 
250
246
 
@@ -256,16 +252,32 @@ watch(route, () => {
256
252
  })
257
253
  });
258
254
 
255
+ watch (()=>coreStore.menu, () => {
256
+ coreStore.menu.forEach((item, i) => {
257
+ if (item.open) {
258
+ opened.value.push(i);
259
+ };
260
+ });
261
+ })
262
+
263
+ watch([loggedIn, routerIsReady, loginRedirectCheckIsReady], ([l,r,lr]) => {
264
+ if (l && r && lr) {
265
+ setTimeout(() => {
266
+ initFlowbite();
267
+ });
268
+ }
269
+ })
270
+
259
271
  // initialize components based on data attribute selectors
260
272
  onMounted(async () => {
261
273
  loadMenu(); // run this in async mode
262
274
  // before init flowbite we have to wait router initialized because it affects dom(our v-ifs) and fetch menu
263
275
  await initRouter()
264
- setTimeout(() => {
265
- initFlowbite();
266
- });
267
-
276
+ })
268
277
 
278
+ onBeforeMount(()=>{
279
+ theme.value = window.localStorage.getItem('theme') || 'light';
280
+ document.documentElement.classList.toggle('dark', theme.value === 'dark');
269
281
  })
270
282
 
271
283
  </script>
@@ -1,4 +1,5 @@
1
- import type { FrontendAPIInterface, ConfirmParams, AlertParams, FilterParams,Operator } from '../types/FrontendAPI';
1
+ import type { FrontendAPIInterface, ConfirmParams, AlertParams, } from '../types/FrontendAPI';
2
+ import type { AdminForthFilterOperators } from '@/types/AdminForthConfig';
2
3
  import { useToastStore } from '../stores/toast';
3
4
  import { useModalStore } from '../stores/modal';
4
5
  import { useCoreStore } from '@/stores/core';
@@ -6,9 +7,20 @@ import { useFiltersStore } from '@/stores/filters';
6
7
  import router from '@/router'
7
8
  import type { AdminForthResourceColumn } from '@/types/AdminForthConfig';
8
9
 
9
-
10
-
11
-
10
+ type FilterParams = {
11
+ /**
12
+ * Field of resource to filter
13
+ */
14
+ field: string;
15
+ /**
16
+ * Operator of filter
17
+ */
18
+ operator: AdminForthFilterOperators;
19
+ /**
20
+ * Value of filter
21
+ */
22
+ value: string | number | boolean ;
23
+ }
12
24
 
13
25
  declare global {
14
26
  interface Window {
package/spa/src/main.ts CHANGED
@@ -6,7 +6,7 @@ import { createPinia } from 'pinia'
6
6
  import App from './App.vue'
7
7
  import router from './router'
8
8
 
9
- const app = createApp(App)
9
+ export const app = createApp(App)
10
10
  /* IMPORTANT:ADMINFORTH COMPONENT REGISTRATIONS */
11
11
 
12
12
 
@@ -77,6 +77,17 @@ export interface ExpressHttpServer extends GenericHttpServer {
77
77
  authorize(callable: Function): void;
78
78
  }
79
79
 
80
+ export interface AdminForthDataSourceConnector {
81
+
82
+ /**
83
+ * Function which will be called to fetch record from database.
84
+ */
85
+ getRecordByPrimaryKey(resource: AdminForthResource, recordId: string): Promise<any>;
86
+ }
87
+
88
+ export interface AdminForthDataSourceConnectorConstructor {
89
+ new ({ url }: { url: string }): AdminForthDataSourceConnector;
90
+ }
80
91
 
81
92
  export interface AdminForthClass {
82
93
  config: AdminForthConfig;
@@ -84,14 +95,15 @@ export interface AdminForthClass {
84
95
  express: GenericHttpServer;
85
96
 
86
97
  connectors: {
87
- [key: string]: AdminForthDataSource
98
+ [key: string]: AdminForthDataSourceConnector,
88
99
  };
89
100
 
90
101
  createResourceRecord(params: { resource: AdminForthResource, record: any, adminUser: AdminUser }): Promise<any>;
91
102
 
92
103
  auth: {
104
+ verify(jwt : string, mustHaveType: string): Promise<any>;
93
105
 
94
- verify(jwt : string): Promise<any>;
106
+ issueJWT(payload: Object, type: string): string;
95
107
  }
96
108
 
97
109
  /**
@@ -534,11 +546,11 @@ export type AdminForthResource = {
534
546
  },
535
547
  options?: {
536
548
  bulkActions?: Array<{
549
+ id?: string,
537
550
  label: string,
538
551
  state: string,
539
552
  icon?: string,
540
553
  action: Function,
541
- id?: string,
542
554
  confirm?: string,
543
555
  }>,
544
556
  allowedActions?: AllowedActions,
@@ -715,7 +727,9 @@ export type AdminForthConfig = {
715
727
  * If you want use custom DataSource which is not supported by AdminForth yet, you can define it's class here
716
728
  *
717
729
  */
718
- databaseConnectors?: any, // TODO Define interface for database connector
730
+ databaseConnectors?: {
731
+ [key: string]: AdminForthDataSourceConnectorConstructor,
732
+ },
719
733
 
720
734
  /**
721
735
  * List of data sources which will be used to fetch data for resources.
@@ -99,20 +99,7 @@ export type AlertParams = {
99
99
 
100
100
  }
101
101
 
102
- export type FilterParams = {
103
- /**
104
- * Field of resource to filter
105
- */
106
- field: string;
107
- /**
108
- * Operator of filter
109
- */
110
- operator: Operator;
111
- /**
112
- * Value of filter
113
- */
114
- value: string | number | boolean ;
115
- }
102
+
116
103
 
117
104
  export enum AlertVariant {
118
105
  danger = 'danger',
@@ -121,7 +108,6 @@ export enum AlertVariant {
121
108
  info = 'info'
122
109
  }
123
110
 
124
- export type Operator = 'in' | 'ilike' | 'gte' | 'lte' ;
125
111
 
126
112
 
127
113