adminforth 1.1.31 → 1.1.33

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 (105) hide show
  1. package/dist/index.js +9 -0
  2. package/dist/modules/codeInjector.js +19 -7
  3. package/dist/modules/styles.js +2 -2
  4. package/dist/spa/index.html +4 -4
  5. package/dist/spa/package-lock.json +206 -5
  6. package/dist/spa/package.json +8 -2
  7. package/dist/spa/src/App.vue +171 -72
  8. package/dist/spa/src/assets/logo.svg +19 -1
  9. package/dist/spa/src/components/AcceptModal.vue +3 -10
  10. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
  11. package/dist/spa/src/components/CustomDatePicker.vue +19 -6
  12. package/dist/spa/src/components/CustomDateRangePicker.vue +14 -5
  13. package/dist/spa/src/components/Dropdown.vue +19 -5
  14. package/dist/spa/src/components/Filters.vue +91 -37
  15. package/dist/spa/src/components/MenuLink.vue +2 -2
  16. package/dist/spa/src/components/ResourceForm.vue +163 -103
  17. package/dist/spa/src/components/ValueRenderer.vue +22 -6
  18. package/dist/spa/src/main.ts +1 -1
  19. package/dist/spa/src/router/index.ts +30 -19
  20. package/dist/spa/src/stores/core.ts +65 -59
  21. package/dist/spa/src/stores/modal.ts +13 -3
  22. package/dist/spa/src/utils.ts +65 -8
  23. package/dist/spa/src/views/CreateView.vue +76 -16
  24. package/dist/spa/src/views/EditView.vue +76 -14
  25. package/dist/spa/src/views/ListView.vue +88 -360
  26. package/dist/spa/src/views/LoginView.vue +17 -5
  27. package/dist/spa/src/views/ResourceParent.vue +1 -1
  28. package/dist/spa/src/views/ShowView.vue +114 -19
  29. package/dist/spa/tailwind.config.js +5 -2
  30. package/dist/spa/vite.config.ts +6 -0
  31. package/index.ts +11 -0
  32. package/modules/codeInjector.ts +19 -1
  33. package/modules/styles.ts +2 -2
  34. package/package.json +1 -1
  35. package/spa/index.html +1 -1
  36. package/spa/src/App.vue +3 -2
  37. package/spa/src/components/ResourceForm.vue +1 -1
  38. package/types/AdminForthConfig.ts +15 -0
  39. package/dist/plugins/AccessControl/index.js +0 -13
  40. package/dist/plugins/AccessControl/types.js +0 -1
  41. package/dist/plugins/plugins/ForeignInlineListPlugin/custom/InlineList.vue +0 -248
  42. package/dist/spa/public/favicon.ico +0 -0
  43. package/dist/spa/spa/.eslintrc.cjs +0 -14
  44. package/dist/spa/spa/.vscode/extensions.json +0 -6
  45. package/dist/spa/spa/README.md +0 -39
  46. package/dist/spa/spa/env.d.ts +0 -1
  47. package/dist/spa/spa/index.html +0 -23
  48. package/dist/spa/spa/package-lock.json +0 -4359
  49. package/dist/spa/spa/package.json +0 -47
  50. package/dist/spa/spa/postcss.config.js +0 -6
  51. package/dist/spa/spa/public/favicon.ico +0 -0
  52. package/dist/spa/spa/src/App.vue +0 -270
  53. package/dist/spa/spa/src/assets/base.css +0 -0
  54. package/dist/spa/spa/src/assets/logo.svg +0 -19
  55. package/dist/spa/spa/src/components/AcceptModal.vue +0 -45
  56. package/dist/spa/spa/src/components/Breadcrumbs.vue +0 -40
  57. package/dist/spa/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  58. package/dist/spa/spa/src/components/CustomDatePicker.vue +0 -174
  59. package/dist/spa/spa/src/components/CustomDateRangePicker.vue +0 -218
  60. package/dist/spa/spa/src/components/Dropdown.vue +0 -168
  61. package/dist/spa/spa/src/components/Filters.vue +0 -211
  62. package/dist/spa/spa/src/components/HelloWorld.vue +0 -17
  63. package/dist/spa/spa/src/components/MenuLink.vue +0 -24
  64. package/dist/spa/spa/src/components/ResourceForm.vue +0 -265
  65. package/dist/spa/spa/src/components/ShowTableItem.vue +0 -14
  66. package/dist/spa/spa/src/components/SingleSkeletLoader.vue +0 -13
  67. package/dist/spa/spa/src/components/ValueRenderer.vue +0 -60
  68. package/dist/spa/spa/src/components/icons/IconCalendar.vue +0 -5
  69. package/dist/spa/spa/src/components/icons/IconCommunity.vue +0 -7
  70. package/dist/spa/spa/src/components/icons/IconDocumentation.vue +0 -7
  71. package/dist/spa/spa/src/components/icons/IconEcosystem.vue +0 -7
  72. package/dist/spa/spa/src/components/icons/IconSupport.vue +0 -7
  73. package/dist/spa/spa/src/components/icons/IconTime.vue +0 -5
  74. package/dist/spa/spa/src/components/icons/IconTooling.vue +0 -19
  75. package/dist/spa/spa/src/index.scss +0 -26
  76. package/dist/spa/spa/src/main.ts +0 -18
  77. package/dist/spa/spa/src/router/index.ts +0 -64
  78. package/dist/spa/spa/src/stores/core.ts +0 -144
  79. package/dist/spa/spa/src/stores/modal.ts +0 -48
  80. package/dist/spa/spa/src/utils.ts +0 -101
  81. package/dist/spa/spa/src/views/CreateView.vue +0 -163
  82. package/dist/spa/spa/src/views/EditView.vue +0 -165
  83. package/dist/spa/spa/src/views/HomeView.vue +0 -8
  84. package/dist/spa/spa/src/views/ListView.vue +0 -257
  85. package/dist/spa/spa/src/views/LoginView.vue +0 -134
  86. package/dist/spa/spa/src/views/ResourceParent.vue +0 -18
  87. package/dist/spa/spa/src/views/ShowView.vue +0 -188
  88. package/dist/spa/spa/tailwind.config.js +0 -15
  89. package/dist/spa/spa/tsconfig.app.json +0 -14
  90. package/dist/spa/spa/tsconfig.json +0 -11
  91. package/dist/spa/spa/tsconfig.node.json +0 -19
  92. package/dist/spa/spa/vite.config.ts +0 -49
  93. package/dist/spa/src/views/HomeView.vue +0 -8
  94. package/dist/types.js +0 -30
  95. /package/dist/spa/{spa/public → public/assets}/favicon.png +0 -0
  96. /package/dist/spa/{spa/src → src}/components/CustomRangePicker.vue +0 -0
  97. /package/dist/spa/{spa/src → src}/components/ResourceListTable.vue +0 -0
  98. /package/dist/spa/{spa/src → src}/components/Toast.vue +0 -0
  99. /package/dist/spa/{spa/src → src}/composables/useFrontendApi.ts +0 -0
  100. /package/dist/spa/{spa/src → src}/composables/useStores.ts +0 -0
  101. /package/dist/spa/{spa/src → src}/spa_types/core.ts +0 -0
  102. /package/dist/spa/{spa/src → src}/stores/filters.ts +0 -0
  103. /package/dist/spa/{spa/src → src}/stores/toast.ts +0 -0
  104. /package/dist/spa/{spa/src → src}/stores/user.ts +0 -0
  105. /package/spa/public/{favicon.png → 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>
@@ -27,10 +44,10 @@
27
44
  </div>
28
45
  <div
29
46
  v-else
30
- class="relative overflow-x-auto shadow-md sm:rounded-lg"
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">
33
- <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 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">
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">
36
53
  Field
@@ -41,19 +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"
45
- class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700"
61
+ <tr v-for="column in coreStore.resource?.columns.filter(c => c.showIn.includes('show'))" :key="column.name"
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
- <ValueRenderer :column="column" :row="coreStore.record" />
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
+ />
52
89
  </td>
90
+ </template>
53
91
  </tr>
54
-
55
92
  </tbody>
56
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
+ />
57
104
  </div>
58
105
 
59
106
 
@@ -63,31 +110,79 @@
63
110
 
64
111
  <script setup>
65
112
 
66
- import { ref, computed, onMounted } from 'vue';
67
- import { useRoute } from 'vue-router';
68
- import { callAdminForthApi } from '@/utils';
69
113
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
70
- import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
71
- import { useCoreStore } from '@/stores/core';
114
+
72
115
  import ValueRenderer from '@/components/ValueRenderer.vue';
116
+ import { useCoreStore } from '@/stores/core';
117
+ import { useModalStore } from '@/stores/modal';
118
+ import { getCustomComponent, checkAcessByAllowedActions } from '@/utils';
119
+ import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
120
+ import { onMounted, ref } from 'vue';
121
+ import { useRoute,useRouter } from 'vue-router';
122
+ import {callAdminForthApi} from '@/utils';
123
+ import {showSuccesTost} from '@/composables/useFrontendApi';
73
124
 
74
125
 
75
126
  const item = ref(null);
76
127
  const route = useRoute();
128
+ const router = useRouter();
77
129
  const loading = ref(false);
130
+ let showComponentsPerColumn = {};
131
+
132
+ console.log(route.params,'showWiev');
133
+
78
134
 
79
135
  const coreStore = useCoreStore();
136
+ const modalStore = useModalStore();
80
137
 
81
138
  onMounted(async () => {
82
139
  loading.value = true;
83
- await coreStore.fetchColumns({
140
+ await coreStore.fetchResourceFull({
84
141
  resourceId: route.params.resourceId
85
142
  });
86
143
  await coreStore.fetchRecord({
87
144
  resourceId: route.params.resourceId,
88
145
  primaryKey: route.params.primaryKey,
89
146
  });
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);
152
+ }
153
+ return acc;
154
+ }, {});
90
155
  loading.value = false;
91
156
  });
92
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
+
93
188
  </script>
@@ -1,9 +1,12 @@
1
1
  /** @type {import('tailwindcss').Config} */
2
2
  export default {
3
- content: ["./src/**/*.{vue, js}", "./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
- extend: {},
5
+ extend: {
6
+ /* IMPORTANT:ADMINFORTH TAILWIND STYLES */
7
+ }
6
8
  },
9
+
7
10
  darkMode: 'class',
8
11
  plugins: [
9
12
  require('flowbite/plugin'),
@@ -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
@@ -49,6 +49,8 @@ class AdminForth implements AdminForthClass {
49
49
  runningHotReload: boolean;
50
50
  activatedPlugins: Array<AdminForthPlugin>;
51
51
 
52
+ baseUrlSlashed: string;
53
+
52
54
  statuses: {
53
55
  dbDiscover?: 'running' | 'done',
54
56
  }
@@ -159,6 +161,11 @@ class AdminForth implements AdminForthClass {
159
161
  if (!this.config.baseUrl) {
160
162
  this.config.baseUrl = '';
161
163
  }
164
+ if (!this.config.baseUrl.endsWith('/')) {
165
+ this.baseUrlSlashed = this.config.baseUrl + '/';
166
+ } else {
167
+ this.baseUrlSlashed = this.config.baseUrl;
168
+ }
162
169
  if (!this.config?.customization.brandName) {
163
170
  this.config.customization.brandName = 'AdminForth';
164
171
  }
@@ -166,6 +173,10 @@ class AdminForth implements AdminForthClass {
166
173
  errors.push(...this.checkCustomFileExists(this.config.customization.brandLogo));
167
174
  }
168
175
 
176
+ if (!this.config.customization.favicon) {
177
+ errors.push(...this.checkCustomFileExists(this.config.customization.favicon));
178
+ }
179
+
169
180
  if (!this.config.customization.datesFormat) {
170
181
  this.config.customization.datesFormat = 'MMM D, YYYY HH:mm:ss';
171
182
  }
@@ -209,6 +209,18 @@ class CodeInjector implements CodeInjectorType {
209
209
  this.srcFoldersToSync[this.adminforth.config.customization.customComponentsDir] = './'
210
210
  }
211
211
 
212
+ // if this.adminforth.config.customization.favicon is set, copy it to assets
213
+ const customFav = this.adminforth.config.customization?.favicon;
214
+ if (customFav) {
215
+
216
+ const faviconPath = path.join(this.adminforth.config.customization?.customComponentsDir, customFav.replace('@@/', ''));
217
+ const dest = path.join(CodeInjector.SPA_TMP_PATH, 'public', 'assets', customFav.replace('@@/', ''));
218
+ // make sure all folders in dest exist
219
+ await fsExtra.ensureDir(path.dirname(dest));
220
+
221
+ await fsExtra.copy(faviconPath, dest);
222
+ }
223
+
212
224
  for (const [src, dest] of Object.entries(this.srcFoldersToSync)) {
213
225
  const to = path.join(CodeInjector.SPA_TMP_PATH, 'src', 'custom', dest);
214
226
  if (process.env.HEAVY_DEBUG) {
@@ -343,7 +355,6 @@ class CodeInjector implements CodeInjectorType {
343
355
  await fs.promises.writeFile(tailwindConfigPath, tailwindConfigContent);
344
356
  }
345
357
 
346
-
347
358
  const routerVuePath = path.join(CodeInjector.SPA_TMP_PATH, 'src', 'router', 'index.ts');
348
359
 
349
360
  let routerVueContent = await fs.promises.readFile(routerVuePath, 'utf-8');
@@ -353,6 +364,13 @@ class CodeInjector implements CodeInjectorType {
353
364
  const indexHtmlPath = path.join(CodeInjector.SPA_TMP_PATH, 'index.html');
354
365
  let indexHtmlContent = await fs.promises.readFile(indexHtmlPath, 'utf-8');
355
366
  indexHtmlContent = indexHtmlContent.replace('/* IMPORTANT:ADMINFORTH TITLE */', `${this.adminforth.config.customization.title || 'AdminForth'}`);
367
+
368
+ indexHtmlContent = indexHtmlContent.replace(
369
+ '/* IMPORTANT:ADMINFORTH FAVICON */',
370
+ this.adminforth.config.customization.favicon?.replace('@@/', `${this.adminforth.baseUrlSlashed}assets/`)
371
+ ||
372
+ `${this.adminforth.baseUrlSlashed}assets/favicon.png`
373
+ );
356
374
  await fs.promises.writeFile(indexHtmlPath, indexHtmlContent);
357
375
 
358
376
 
package/modules/styles.ts CHANGED
@@ -27,10 +27,10 @@ export const styles = () => `
27
27
 
28
28
  "list-view-bg": "#FFFFFF", // White background
29
29
  "list-view-table-bg": "#FFFFFF", // White table background
30
- "list-view-table-heading": "#F5F5F5", // Very light grey heading
30
+ "list-view-table-heading": "rgb(249 250 251)", // Very light grey heading
31
31
  "list-view-table-heading-text": "#333333", // Dark grey heading text
32
32
  "list-view-table-text": "#333333", // Dark grey text
33
- "list-view-table-row-hover": "#F0F0F0", // Very light grey row hover
33
+ "list-view-table-row-hover": "rgb(249 250 251)", // Very light grey row hover
34
34
  "list-view-bread-crumbs-text": "#666666", // Medium grey bread crumbs
35
35
  "list-view-border-color": "#DDDDDD", // Light grey border
36
36
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.1.31",
3
+ "version": "1.1.33",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/spa/index.html CHANGED
@@ -2,7 +2,7 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
- <link rel="icon" href="/favicon.png">
5
+ <link rel="icon" href="/* IMPORTANT:ADMINFORTH FAVICON */">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>/* IMPORTANT:ADMINFORTH TITLE */</title>
8
8
  <!--
package/spa/src/App.vue CHANGED
@@ -59,17 +59,18 @@
59
59
 
60
60
  v-if="loggedIn && routerIsReady && loginRedirectCheckIsReady"
61
61
 
62
- id="logo-sidebar" class="fixed border-none top-0 left-0 z-20 w-64 h-screen transition-transform bg-nav-menu-bg border-r border-nav-menu-border sm:translate-x-0 dark:bg-gray-800 dark:border-gray-700"
62
+ id="logo-sidebar" class="fixed border-none top-0 left-0 z-20 w-64 h-screen transition-transform border-r border-nav-menu-border sm:translate-x-0 dark:bg-gray-800 dark:border-gray-700"
63
63
  :class="{ '-translate-x-full': !sideBarOpen, 'transform-none': sideBarOpen }"
64
64
  aria-label="Sidebar"
65
65
  >
66
- <div class="h-full px-3 pb-4 overflow-y-auto bg-nav-menu-bg dark:bg-gray-800 border-r">
66
+ <div class="h-full px-3 pb-4 overflow-y-auto bg-white dark:bg-gray-800 border-r">
67
67
  <div class="flex ms-2 md:me-24 m-4 ">
68
68
  <img :src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')" :alt="`${ coreStore.config?.brandName } Logo`" class="h-8 me-3" />
69
69
  <span class="self-center text-header-text-size font-semibold sm:text-header-text-size whitespace-nowrap dark:text-header-text text-header-logo-color">
70
70
  {{ coreStore.config?.brandName }}
71
71
  </span>
72
72
  </div>
73
+
73
74
  <ul class="space-y-2 font-medium">
74
75
  <template v-for="(item, i) in coreStore.menu" :key="`menu-${i}`">
75
76
  <div v-if="item.type === 'divider'" class="border-t border-nav-menu-devider dark:border-gray-700"></div>
@@ -48,7 +48,7 @@
48
48
  single
49
49
  v-if="column.foreignResource"
50
50
  :options="columnOptions[column.name] || []"
51
- :placeholder = "columnOptions[column.name].length || 'There are no options available'"
51
+ :placeholder = "columnOptions[column.name]?.length || 'There are no options available'"
52
52
  :modelValue="currentValues[column.name]"
53
53
  @update:modelValue="setCurrentValue(column.name, $event)"
54
54
  ></Dropdown>
@@ -742,6 +742,21 @@ export type AdminForthConfig = {
742
742
  */
743
743
  brandLogo?: string,
744
744
 
745
+ /**
746
+ * Path to your app favicon
747
+ *
748
+ * Example:
749
+ * Place file `favicon.png` to `./custom` folder and set this option:
750
+ *
751
+ * ```ts
752
+ * favicon: '@@/favicon.png',
753
+ * ```
754
+ */
755
+ favicon?: string,
756
+
757
+ /**
758
+ favicon?: string,
759
+
745
760
  /**
746
761
  * DayJS format string for all dates in the app
747
762
  */
@@ -1,13 +0,0 @@
1
- import AdminForthPlugin from "../base.js";
2
- class AuditLogPlugin extends AdminForthPlugin {
3
- constructor(options) {
4
- super(options, import.meta.url);
5
- this.options = options;
6
- }
7
- modifyResourceConfig(adminforth, resourceConfig) {
8
- super.modifyResourceConfig(adminforth, resourceConfig);
9
- this.adminforth = adminforth;
10
- }
11
- }
12
- AuditLogPlugin.defaultError = 'Sorry, you do not have access to this resource.';
13
- export default AuditLogPlugin;
@@ -1 +0,0 @@
1
- export {};
@@ -1,248 +0,0 @@
1
- <template>
2
- <Teleport to="body">
3
- <!-- todo exclude foreign column-->
4
- <Filters
5
- v-if="listResource"
6
- :columns="listResource.columns.filter((c) => c.name !== listResourceRefColumn.name)"
7
- v-model:filters="filters"
8
- :columnsMinMax="columnsMinMax"
9
- :show="filtersShow"
10
- @hide="filtersShow = false"
11
- />
12
- </Teleport>
13
-
14
- <td colspan="2">
15
- <div class="flex items-center gap-1">
16
- <h4 v-if="listResource"
17
- class="px-6 py-4"
18
- >{{ listResource.label }} inline records</h4>
19
-
20
- <button
21
- @click="()=>{checkboxes = []}"
22
- v-if="checkboxes.length"
23
- data-tooltip-target="tooltip-remove-all"
24
- data-tooltip-placement="bottom"
25
- class="flex gap-1 items-center py-1 px-3 me-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"
26
- >
27
- <IconBanOutline class="w-5 h-5 "/>
28
- <div id="tooltip-remove-all" role="tooltip"
29
- class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
30
- Remove selection
31
- <div class="tooltip-arrow" data-popper-arrow></div>
32
- </div>
33
- </button>
34
-
35
- <button
36
- v-if="checkboxes.length"
37
- v-for="(action,i) in listResource?.options?.bulkActions"
38
- :key="action.id"
39
- @click="startBulkAction(action.id)"
40
- class="flex gap-1 items-center py-1 px-3 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"
41
- :class="{'bg-red-100 text-red-800 border-red-400 dark:bg-red-700 dark:text-red-400 dark:border-red-400':action.state==='danger', 'bg-green-100 text-green-800 border-green-400 dark:bg-green-700 dark:text-green-400 dark:border-green-400':action.state==='success',
42
- 'bg-blue-100 text-blue-800 border-blue-400 dark:bg-blue-700 dark:text-blue-400 dark:border-blue-400':action.state==='active',
43
- }"
44
- >
45
- <component
46
- v-if="action.icon"
47
- :is="getIcon(action.icon)"
48
- class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white"></component>
49
-
50
- {{ `${action.label} (${checkboxes.length})` }}
51
- </button>
52
-
53
- <RouterLink v-if="listResource?.options?.allowedActions?.create"
54
- :to="{
55
- name: 'resource-create',
56
- params: { resourceId: listResource.resourceId },
57
- query: {
58
- values: encodeURIComponent(JSON.stringify({[listResourceRefColumn.name]: props.record[selfPrimaryKeyColumn.name]})),
59
- returnTo: $route.fullPath,
60
- },
61
- }"
62
- class="flex items-center py-1 px-3 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 rounded-default"
63
- >
64
- <IconPlusOutline class="w-4 h-4 me-2"/>
65
- Create
66
- </RouterLink>
67
-
68
- <button
69
- class="flex gap-1 items-center py-1 px-3 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 rounded-default"
70
- @click="()=>{filtersShow = !filtersShow}"
71
- >
72
- <IconFilterOutline class="w-4 h-4 me-2"/>
73
- Filter
74
- <span
75
- class="bg-red-100 text-red-800 text-xs font-medium px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400"
76
- v-if="filters.length">
77
- {{ filters.length }}
78
- </span>
79
- </button>
80
-
81
- </div>
82
-
83
- <ResourceListTable
84
- :resource="listResource"
85
- :rows="rows"
86
- @update:page="page = $event"
87
- @update:sort="sort = $event"
88
- @update:checkboxes="checkboxes = $event"
89
- :pageSize="pageSize"
90
- :totalRows="totalRows"
91
- :checkboxes="checkboxes"
92
- />
93
-
94
- </td>
95
- </template>
96
-
97
- <script setup>
98
- import { callAdminForthApi } from '@/utils';
99
- import { ref, onMounted, watch, computed } from 'vue';
100
- import ResourceListTable from '@/components/ResourceListTable.vue';
101
- import Filters from '@/components/Filters.vue';
102
- import {
103
- IconBanOutline,
104
- IconFilterOutline,
105
- IconPlusOutline,
106
- } from '@iconify-prerendered/vue-flowbite';
107
-
108
- import { getIcon } from '@/utils';
109
-
110
- const props = defineProps(['column', 'record', 'meta', 'resource', 'adminUser']);
111
-
112
- const listResource = ref(null);
113
- const loading = ref(true);
114
-
115
- const page = ref(1);
116
- const sort = ref([]);
117
- const checkboxes = ref([]);
118
- const pageSize = computed(() => listResource.value?.options?.listPageSize || 10);
119
-
120
- const rows = ref(null);
121
- const totalRows = ref(0);
122
-
123
- const filters = ref([]);
124
- const filtersShow = ref(false);
125
- const columnsMinMax = ref(null);
126
-
127
- const listResourceRefColumn = computed(() => {
128
- if (!listResource.value) {
129
- return null;
130
- }
131
- return listResource.value.columns.find(c => c.foreignResource?.resourceId === props.resource.resourceId);
132
- });
133
-
134
- const selfPrimaryKeyColumn = computed(() => {
135
- return props.resource.columns.find(c => c.primaryKey);
136
- });
137
-
138
- const endFilters = computed(() => {
139
- if (!listResource.value) {
140
- return [];
141
- }
142
- // get name of the column that is foreign key
143
- const refColumn = listResourceRefColumn.value;
144
-
145
- const primaryKeyColumn = selfPrimaryKeyColumn.value;
146
-
147
- if (!refColumn) {
148
- window.adminforth.alert({
149
- message: `Column with foreignResource.resourceId which is equal to '${props.resource.resourceId}' not found in resource which is specified as foreighResourceId '${listResource.value.resourceId}'`,
150
- variant: 'danger',
151
- });
152
- return [];
153
- }
154
- return [
155
- ...filters.value,
156
- {
157
- field: refColumn.name,
158
- operator: 'eq',
159
- value: props.record[primaryKeyColumn.name],
160
- },
161
- ];
162
- });
163
-
164
- watch([page], async () => {
165
- await getList();
166
- });
167
-
168
- watch([sort], async () => {
169
- await getList();
170
- }, {deep: true});
171
-
172
- watch([filters], async () => {
173
- page.value = 1;
174
- checkboxes.value = [];
175
- await getList();
176
- }, {deep: true});
177
-
178
-
179
- async function startBulkAction(actionId) {
180
- const data = await callAdminForthApi({
181
- path: '/start_bulk_action',
182
- method: 'POST',
183
- body: {
184
- resourceId: listResource.value.resourceId,
185
- actionId: actionId,
186
- recordIds: checkboxes.value
187
- }
188
- });
189
- if (data?.status === 'success') {
190
- checkboxes.value = [];
191
- }
192
- await getList();
193
- }
194
-
195
- async function getList() {
196
- rows.value = null;
197
- console.log('getList', listResource.value)
198
- const data = await callAdminForthApi({
199
- path: '/get_resource_data',
200
- method: 'POST',
201
- body: {
202
- source: 'list',
203
- resourceId: listResource.value.resourceId,
204
- limit: pageSize.value,
205
- offset: (page.value - 1) * pageSize.value,
206
- filters: endFilters.value,
207
- sort: sort.value,
208
- }
209
- });
210
-
211
- if (data.error) {
212
- window.adminforth.alert({
213
- message: data.error,
214
- variant: 'danger',
215
- timeout: 'unlimited',
216
- });
217
- rows.value = [];
218
- totalRows.value = 0;
219
- return;
220
- }
221
-
222
- rows.value = data.data?.map(row => {
223
- row._primaryKeyValue = row[listResource.value.columns.find(c => c.primaryKey).name];
224
- return row;
225
- });
226
- totalRows.value = data.total;
227
- }
228
-
229
- onMounted( async () => {
230
- loading.value = true;
231
- const foreighResourceId = props.meta.foreignResourceId;
232
- listResource.value = (await callAdminForthApi({
233
- path: `/plugin/${props.meta.pluginInstanceId}/get_resource`,
234
- method: 'POST',
235
- body: {},
236
- })).resource;
237
- columnsMinMax.value = await callAdminForthApi({
238
- path: '/get_min_max_for_columns',
239
- method: 'POST',
240
- body: {
241
- resourceId: foreighResourceId,
242
- }
243
- });
244
- loading.value = false;
245
- await getList();
246
- });
247
-
248
- </script>
Binary file
@@ -1,14 +0,0 @@
1
- /* eslint-env node */
2
- require('@rushstack/eslint-patch/modern-module-resolution')
3
-
4
- module.exports = {
5
- root: true,
6
- 'extends': [
7
- 'plugin:vue/vue3-essential',
8
- 'eslint:recommended',
9
- '@vue/eslint-config-typescript'
10
- ],
11
- parserOptions: {
12
- ecmaVersion: 'latest'
13
- }
14
- }