adminforth 1.1.64 → 1.1.66

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 (58) hide show
  1. package/dataConnectors/mongo.ts +7 -13
  2. package/dataConnectors/postgres.ts +2 -2
  3. package/dataConnectors/sqlite.ts +2 -2
  4. package/dist/dataConnectors/mongo.js +1 -9
  5. package/dist/index.js +1 -3
  6. package/dist/plugins/AuditLogPlugin/custom/AuditLogView.vue +7 -6
  7. package/dist/plugins/AuditLogPlugin/index.js +18 -13
  8. package/dist/spa/index.html +2 -2
  9. package/dist/spa/package-lock.json +17 -4
  10. package/dist/spa/package.json +2 -1
  11. package/dist/spa/spa/src/App.vue +24 -12
  12. package/dist/spa/spa/src/composables/useStores.ts +16 -4
  13. package/dist/spa/spa/src/main.ts +1 -1
  14. package/dist/spa/src/App.vue +125 -45
  15. package/dist/spa/src/assets/logo.svg +19 -1
  16. package/dist/spa/src/components/AcceptModal.vue +2 -9
  17. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
  18. package/dist/spa/src/components/CustomDatePicker.vue +16 -3
  19. package/dist/spa/src/components/CustomDateRangePicker.vue +11 -2
  20. package/dist/spa/src/components/Dropdown.vue +6 -1
  21. package/dist/spa/src/components/Filters.vue +40 -19
  22. package/dist/spa/src/components/ResourceForm.vue +26 -24
  23. package/dist/spa/src/components/ResourceListTable.vue +419 -0
  24. package/dist/spa/src/components/Toast.vue +66 -0
  25. package/dist/spa/src/components/ValueRenderer.vue +14 -8
  26. package/dist/spa/src/composables/useFrontendApi.ts +26 -0
  27. package/dist/spa/src/composables/useStores.ts +113 -0
  28. package/dist/spa/src/main.ts +1 -1
  29. package/dist/spa/src/router/index.ts +30 -20
  30. package/dist/spa/src/spa_types/core.ts +51 -0
  31. package/dist/spa/src/stores/core.ts +48 -31
  32. package/dist/spa/src/stores/filters.ts +22 -0
  33. package/dist/spa/src/stores/modal.ts +13 -3
  34. package/dist/spa/src/stores/toast.ts +15 -0
  35. package/dist/spa/src/stores/user.ts +54 -0
  36. package/dist/spa/src/utils.ts +62 -7
  37. package/dist/spa/src/views/CreateView.vue +67 -15
  38. package/dist/spa/src/views/EditView.vue +45 -11
  39. package/dist/spa/src/views/ListView.vue +82 -366
  40. package/dist/spa/src/views/LoginView.vue +16 -4
  41. package/dist/spa/src/views/ShowView.vue +105 -21
  42. package/dist/spa/tailwind.config.js +1 -1
  43. package/dist/spa/vite.config.ts +6 -0
  44. package/index.ts +4 -5
  45. package/package.json +1 -1
  46. package/plugins/AuditLogPlugin/custom/AuditLogView.vue +7 -6
  47. package/plugins/AuditLogPlugin/index.ts +23 -15
  48. package/spa/src/App.vue +24 -12
  49. package/spa/src/composables/useStores.ts +16 -4
  50. package/spa/src/main.ts +1 -1
  51. package/types/AdminForthConfig.ts +16 -3
  52. package/types/FrontendAPI.ts +1 -15
  53. package/dist/plugins/AccessControl/index.js +0 -66
  54. package/dist/plugins/AccessControl/types.js +0 -1
  55. package/dist/spa/public/favicon.ico +0 -0
  56. package/dist/spa/spa/public/favicon.ico +0 -0
  57. package/dist/types.js +0 -30
  58. /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) => {
@@ -725,10 +726,8 @@ class AdminForth implements AdminForthClass {
725
726
  let userFullName = ''
726
727
  if (adminUser.isRoot) {
727
728
  username = this.config.rootUser.username;
728
- console.log(username, 'this is the root')
729
729
  } else {
730
730
  const dbUser = adminUser.dbUser;
731
- console.log(dbUser,'this is the db user',adminUser,'this is the admin user')
732
731
  username = dbUser[this.config.auth.usernameField];
733
732
  userFullName =dbUser[this.config.auth.userFullNameField];
734
733
  }
@@ -1172,7 +1171,7 @@ class AdminForth implements AdminForthClass {
1172
1171
 
1173
1172
  // execute hook if needed
1174
1173
  for (const hook of listify(resource.hooks?.edit?.afterSave as AfterSaveFunction[])) {
1175
- const resp = await hook({ resource, record, adminUser });
1174
+ const resp = await hook({ resource, record, adminUser, oldRecord });
1176
1175
  if (!resp || (!resp.ok && !resp.error)) {
1177
1176
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
1178
1177
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.1.64",
3
+ "version": "1.1.66",
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="props.record[props.meta.resourceColumns.resourceDataColumnName].oldRecord"
18
+ :current="props.record[props.meta.resourceColumns.resourceDataColumnName].newRecord"
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,26 @@ 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
+ }
39
+ // console.log('newData', newData)
30
40
  const record = {
31
41
  [this.options.resourceColumns.resourceIdColumnName]: resource.resourceId,
32
42
  [this.options.resourceColumns.resourceActionColumnName]: action,
33
- [this.options.resourceColumns.resourceDataColumnName]: data,
43
+ [this.options.resourceColumns.resourceDataColumnName]: newData,
34
44
  [this.options.resourceColumns.resourceUserIdColumnName]: user.pk,
35
45
  [this.options.resourceColumns.resourceRecordIdColumnName]: recordId,
36
46
  [this.options.resourceColumns.resourceCreatedColumnName]: new Date()
37
47
  }
38
48
  const auditLogResource = this.adminforth.config.resources.find((r) => r.resourceId === this.auditLogResource);
39
- console.log('rec', record);
40
49
  await this.adminforth.createResourceRecord({ resource: auditLogResource, record, adminUser: user});
41
50
  return {ok: true};
42
51
  }
@@ -60,9 +69,9 @@ export default class AuditLogPlugin extends AdminForthPlugin {
60
69
  // type: AdminForthDataTypes.STRING
61
70
  }
62
71
  }
63
-
72
+
64
73
  diffColumn.components = {
65
- showRow: {
74
+ show: {
66
75
  file: this.componentPath('AuditLogView.vue'),
67
76
  meta: {
68
77
  ...this.options,
@@ -70,14 +79,13 @@ export default class AuditLogPlugin extends AdminForthPlugin {
70
79
  }
71
80
  }
72
81
  }
73
- resource.columns.push(diffColumn);
74
82
  return;
75
83
  }
76
84
 
77
85
  const defaultHooks = {
78
- create: { beforeSave: [] },
79
- edit: { beforeSave: [] },
80
- delete: { beforeSave: [] }
86
+ create: { afterSave: [] },
87
+ edit: { afterSave: [] },
88
+ delete: { afterSave: [] }
81
89
  }
82
90
 
83
91
  if ( !resource.hooks ) {
@@ -87,11 +95,11 @@ export default class AuditLogPlugin extends AdminForthPlugin {
87
95
  }
88
96
 
89
97
  Object.keys(resource.hooks).forEach((hook) => {
90
- if(!Array.isArray(resource.hooks[hook].beforeSave)){
91
- resource.hooks[hook].beforeSave = [resource.hooks[hook].beforeSave]
98
+ if(!Array.isArray(resource.hooks[hook].afterSave)){
99
+ resource.hooks[hook].afterSave = [resource.hooks[hook].afterSave]
92
100
  }
93
- resource.hooks[hook].beforeSave.push(async ({resource, record, adminUser}) => {
94
- return await this.createLogRecord(resource, hook as AllowedActionsEnum, record, adminUser)
101
+ resource.hooks[hook].afterSave.push(async ({resource, record, adminUser, oldRecord}) => {
102
+ return await this.createLogRecord(resource, hook as AllowedActionsEnum, record, adminUser, oldRecord)
95
103
  })
96
104
  })
97
105
  })
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,7 +95,7 @@ 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>;
@@ -534,11 +545,11 @@ export type AdminForthResource = {
534
545
  },
535
546
  options?: {
536
547
  bulkActions?: Array<{
548
+ id?: string,
537
549
  label: string,
538
550
  state: string,
539
551
  icon?: string,
540
552
  action: Function,
541
- id?: string,
542
553
  confirm?: string,
543
554
  }>,
544
555
  allowedActions?: AllowedActions,
@@ -715,7 +726,9 @@ export type AdminForthConfig = {
715
726
  * If you want use custom DataSource which is not supported by AdminForth yet, you can define it's class here
716
727
  *
717
728
  */
718
- databaseConnectors?: any, // TODO Define interface for database connector
729
+ databaseConnectors?: {
730
+ [key: string]: AdminForthDataSourceConnectorConstructor,
731
+ },
719
732
 
720
733
  /**
721
734
  * 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
 
@@ -1,66 +0,0 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- import { AllowedActionsEnum } from "../../types/AdminForthConfig.js";
11
- import AdminForthPlugin from "../base.js";
12
- class AccessControlPlugin extends AdminForthPlugin {
13
- constructor(options) {
14
- super(options, import.meta.url);
15
- this.options = options;
16
- }
17
- modifyResourceConfig(adminforth, resourceConfig) {
18
- super.modifyResourceConfig(adminforth, resourceConfig);
19
- this.adminforth = adminforth;
20
- if (!resourceConfig.hooks) {
21
- resourceConfig.hooks = {};
22
- }
23
- const checkAccess = (adminUser, action, meta) => __awaiter(this, void 0, void 0, function* () {
24
- const hasAccessOrError = yield this.options.hasAccess(adminUser, action, meta);
25
- if (hasAccessOrError === true) {
26
- return { ok: true };
27
- }
28
- else {
29
- return { ok: false, error: hasAccessOrError || AccessControlPlugin.defaultError };
30
- }
31
- });
32
- const bindHookCheck = (action, hookName) => {
33
- if (!resourceConfig.hooks[action]) {
34
- resourceConfig.hooks[action] = {};
35
- }
36
- if (!resourceConfig.hooks[action][hookName]) {
37
- resourceConfig.hooks[action][hookName] = [];
38
- }
39
- if (hookName === 'beforeDatasourceRequest') {
40
- resourceConfig.hooks[action][hookName].unshift((_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, query }) {
41
- return checkAccess(adminUser, action, { query });
42
- }));
43
- }
44
- else {
45
- resourceConfig.hooks[action][hookName].unshift((_b) => __awaiter(this, [_b], void 0, function* ({ adminUser, record }) {
46
- return checkAccess(adminUser, action, { record });
47
- }));
48
- }
49
- };
50
- // List check
51
- bindHookCheck(AllowedActionsEnum.list, 'beforeDatasourceRequest');
52
- // Show check
53
- bindHookCheck(AllowedActionsEnum.show, 'beforeDatasourceRequest');
54
- // Edit check
55
- bindHookCheck(AllowedActionsEnum.edit, 'beforeDatasourceRequest');
56
- // create check
57
- bindHookCheck(AllowedActionsEnum.create, 'beforeSave');
58
- // edit check
59
- bindHookCheck(AllowedActionsEnum.edit, 'beforeSave');
60
- // delete check
61
- bindHookCheck(AllowedActionsEnum.delete, 'beforeSave');
62
- console.log('resourceConfig', resourceConfig);
63
- }
64
- }
65
- AccessControlPlugin.defaultError = 'Sorry, you do not have access to this resource.';
66
- export default AccessControlPlugin;
@@ -1 +0,0 @@
1
- export {};
Binary file
Binary file