adminforth 1.2.95 → 1.2.97

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 (115) hide show
  1. package/dataConnectors/baseConnector.ts +26 -14
  2. package/dataConnectors/clickhouse.ts +57 -37
  3. package/dataConnectors/mongo.ts +45 -14
  4. package/dataConnectors/postgres.ts +53 -36
  5. package/dataConnectors/sqlite.ts +37 -35
  6. package/dist/dataConnectors/baseConnector.js +16 -7
  7. package/dist/dataConnectors/clickhouse.js +58 -48
  8. package/dist/dataConnectors/mongo.js +29 -12
  9. package/dist/dataConnectors/postgres.js +62 -52
  10. package/dist/dataConnectors/sqlite.js +52 -45
  11. package/dist/index.js +5 -1
  12. package/dist/modules/operationalResource.js +88 -0
  13. package/dist/modules/restApi.js +27 -29
  14. package/dist/spa/index.html +4 -4
  15. package/dist/spa/package-lock.json +421 -6
  16. package/dist/spa/package.json +10 -2
  17. package/dist/spa/src/App.vue +236 -94
  18. package/dist/spa/src/assets/base.css +2 -0
  19. package/dist/spa/src/assets/logo.svg +19 -1
  20. package/dist/spa/src/components/AcceptModal.vue +3 -10
  21. package/dist/spa/src/components/Breadcrumbs.vue +3 -2
  22. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
  23. package/dist/spa/src/components/CustomDatePicker.vue +25 -10
  24. package/dist/spa/src/components/CustomDateRangePicker.vue +14 -5
  25. package/dist/spa/src/components/Dropdown.vue +19 -5
  26. package/dist/spa/src/components/Filters.vue +103 -38
  27. package/dist/spa/src/components/MenuLink.vue +3 -3
  28. package/dist/spa/src/components/ResourceForm.vue +205 -116
  29. package/dist/spa/{spa/src → src}/components/ResourceListTable.vue +6 -3
  30. package/dist/spa/{spa/src → src}/components/Toast.vue +9 -3
  31. package/dist/spa/src/components/ValueRenderer.vue +30 -8
  32. package/dist/spa/src/index.scss +2 -1
  33. package/dist/spa/src/main.ts +2 -2
  34. package/dist/spa/src/router/index.ts +29 -19
  35. package/dist/spa/src/stores/core.ts +65 -59
  36. package/dist/spa/src/stores/modal.ts +13 -3
  37. package/dist/spa/src/types/AdminForthConfig.ts +1477 -0
  38. package/dist/spa/src/types/FrontendAPI.ts +121 -0
  39. package/dist/spa/src/utils.ts +69 -10
  40. package/dist/spa/src/views/CreateView.vue +69 -16
  41. package/dist/spa/src/views/EditView.vue +68 -14
  42. package/dist/spa/src/views/ListView.vue +100 -371
  43. package/dist/spa/src/views/LoginView.vue +67 -29
  44. package/dist/spa/src/views/ResourceParent.vue +1 -2
  45. package/dist/spa/src/views/ShowView.vue +116 -25
  46. package/dist/spa/tailwind.config.js +8 -3
  47. package/dist/spa/vite.config.ts +13 -0
  48. package/dist/types/AdminForthConfig.js +37 -0
  49. package/index.ts +12 -3
  50. package/modules/operationalResource.ts +97 -0
  51. package/modules/restApi.ts +31 -31
  52. package/package.json +1 -1
  53. package/types/AdminForthConfig.ts +91 -8
  54. package/dist/spa/.vscode/extensions.json +0 -6
  55. package/dist/spa/public/favicon.ico +0 -0
  56. package/dist/spa/spa/.eslintrc.cjs +0 -14
  57. package/dist/spa/spa/.vscode/extensions.json +0 -6
  58. package/dist/spa/spa/README.md +0 -39
  59. package/dist/spa/spa/env.d.ts +0 -1
  60. package/dist/spa/spa/index.html +0 -23
  61. package/dist/spa/spa/package-lock.json +0 -4573
  62. package/dist/spa/spa/package.json +0 -49
  63. package/dist/spa/spa/postcss.config.js +0 -6
  64. package/dist/spa/spa/public/favicon.ico +0 -0
  65. package/dist/spa/spa/src/App.vue +0 -314
  66. package/dist/spa/spa/src/assets/base.css +0 -0
  67. package/dist/spa/spa/src/assets/logo.svg +0 -19
  68. package/dist/spa/spa/src/components/AcceptModal.vue +0 -45
  69. package/dist/spa/spa/src/components/Breadcrumbs.vue +0 -40
  70. package/dist/spa/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  71. package/dist/spa/spa/src/components/CustomDatePicker.vue +0 -174
  72. package/dist/spa/spa/src/components/CustomDateRangePicker.vue +0 -218
  73. package/dist/spa/spa/src/components/Dropdown.vue +0 -168
  74. package/dist/spa/spa/src/components/Filters.vue +0 -222
  75. package/dist/spa/spa/src/components/HelloWorld.vue +0 -17
  76. package/dist/spa/spa/src/components/MenuLink.vue +0 -24
  77. package/dist/spa/spa/src/components/ResourceForm.vue +0 -289
  78. package/dist/spa/spa/src/components/SingleSkeletLoader.vue +0 -13
  79. package/dist/spa/spa/src/components/ValueRenderer.vue +0 -66
  80. package/dist/spa/spa/src/components/icons/IconCalendar.vue +0 -5
  81. package/dist/spa/spa/src/components/icons/IconCommunity.vue +0 -7
  82. package/dist/spa/spa/src/components/icons/IconDocumentation.vue +0 -7
  83. package/dist/spa/spa/src/components/icons/IconEcosystem.vue +0 -7
  84. package/dist/spa/spa/src/components/icons/IconSupport.vue +0 -7
  85. package/dist/spa/spa/src/components/icons/IconTime.vue +0 -5
  86. package/dist/spa/spa/src/components/icons/IconTooling.vue +0 -19
  87. package/dist/spa/spa/src/index.scss +0 -27
  88. package/dist/spa/spa/src/main.ts +0 -18
  89. package/dist/spa/spa/src/router/index.ts +0 -63
  90. package/dist/spa/spa/src/stores/core.ts +0 -144
  91. package/dist/spa/spa/src/stores/modal.ts +0 -48
  92. package/dist/spa/spa/src/utils.ts +0 -103
  93. package/dist/spa/spa/src/views/CreateView.vue +0 -156
  94. package/dist/spa/spa/src/views/EditView.vue +0 -157
  95. package/dist/spa/spa/src/views/HomeView.vue +0 -8
  96. package/dist/spa/spa/src/views/ListView.vue +0 -258
  97. package/dist/spa/spa/src/views/LoginView.vue +0 -143
  98. package/dist/spa/spa/src/views/ResourceParent.vue +0 -17
  99. package/dist/spa/spa/src/views/ShowView.vue +0 -184
  100. package/dist/spa/spa/tailwind.config.js +0 -17
  101. package/dist/spa/spa/tsconfig.app.json +0 -14
  102. package/dist/spa/spa/tsconfig.json +0 -11
  103. package/dist/spa/spa/tsconfig.node.json +0 -19
  104. package/dist/spa/spa/vite.config.ts +0 -56
  105. package/dist/spa/src/views/HomeView.vue +0 -8
  106. package/dist/types.js +0 -30
  107. /package/dist/spa/{spa/public → public}/assets/favicon.png +0 -0
  108. /package/dist/spa/{spa/src → src}/components/CustomRangePicker.vue +0 -0
  109. /package/dist/spa/{spa/src → src}/components/SkeleteLoader.vue +0 -0
  110. /package/dist/spa/{spa/src → src}/composables/useFrontendApi.ts +0 -0
  111. /package/dist/spa/{spa/src → src}/composables/useStores.ts +0 -0
  112. /package/dist/spa/{spa/src → src}/spa_types/core.ts +0 -0
  113. /package/dist/spa/{spa/src → src}/stores/filters.ts +0 -0
  114. /package/dist/spa/{spa/src → src}/stores/toast.ts +0 -0
  115. /package/dist/spa/{spa/src → src}/stores/user.ts +0 -0
@@ -1,17 +1,18 @@
1
1
  <template>
2
2
  <div class="relative flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-800"
3
3
  :style="coreStore.config?.loginBackgroundImage ? {
4
- 'background-image': 'url(' + coreStore.config?.loginBackgroundImage + ')',
4
+ 'background-image': 'url(' + loadFile(coreStore.config?.loginBackgroundImage) + ')',
5
5
  'background-size': 'cover',
6
6
  'background-position': 'center',
7
7
  'background-blend-mode': 'darken'
8
8
  }: {}"
9
9
  >
10
- <!-- Main modal -->
10
+
11
+ <!-- Main modal -->
11
12
  <div id="authentication-modal" tabindex="-1" class=" overflow-y-auto overflow-x-hidden z-50 min-w-[400px] justify-center items-center md:inset-0 h-[calc(100%-1rem)] max-h-full">
12
13
  <div class="relative p-4 w-full max-w-md max-h-full">
13
14
  <!-- Modal content -->
14
- <div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
15
+ <div class="relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black" >
15
16
  <!-- Modal header -->
16
17
  <div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
17
18
  <h3 class="text-xl font-semibold text-gray-900 dark:text-white">
@@ -21,17 +22,23 @@
21
22
  </div>
22
23
  <!-- Modal body -->
23
24
  <div class="p-4 md:p-5">
24
- <form class="space-y-4" @submit.prevent>
25
+ <form class="space-y-4" @submit.prevent>
25
26
  <div>
26
27
  <label for="username" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your {{ coreStore.config?.usernameFieldName?.toLowerCase() }}</label>
27
- <input type="username" name="username" id="username" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" placeholder="name@company.com" required />
28
+ <input type="username" name="username" id="username"
29
+ ref="usernameInput"
30
+ @keydown.enter="passwordInput.focus()"
31
+ class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" placeholder="name@company.com" required />
28
32
  </div>
29
33
  <div class="relative">
30
34
  <label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your password</label>
31
- <input :type="!showPw ? 'password': 'text'" name="password" id="password" placeholder="••••••••" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" required />
35
+ <input
36
+ ref="passwordInput"
37
+ @keydown.enter="login"
38
+ :type="!showPw ? 'password': 'text'" name="password" id="password" placeholder="••••••••" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" required />
32
39
  <button type="button" @click="showPw = !showPw" class="absolute top-12 right-3 -translate-y-1/2 text-gray-400 dark:text-gray-300">
33
- <IconEyeSolid class="w-5 h-5" v-if="!showPw" />
34
- <IconEyeSlashSolid class="w-5 h-5" v-else />
40
+ <IconEyeSolid class="w-5 h-5" v-if="!showPw" />
41
+ <IconEyeSlashSolid class="w-5 h-5" v-else />
35
42
  </button>
36
43
  </div>
37
44
  <!-- <div class="flex justify-between">
@@ -54,6 +61,17 @@
54
61
  </div>
55
62
  </div>
56
63
 
64
+
65
+ <div v-if="coreStore.config?.loginPromptHTML"
66
+ class="flex items-center p-4 mb-4 text-sm text-gray-800 rounded-lg bg-gray-50 dark:bg-gray-800 dark:text-gray-400" role="alert"
67
+ >
68
+ <svg class="flex-shrink-0 inline w-4 h-4 me-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
69
+ <path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
70
+ </svg>
71
+ <span class="sr-only">Info</span>
72
+ <div v-html="coreStore.config?.loginPromptHTML"></div>
73
+ </div>
74
+
57
75
  <button
58
76
  @click="login"
59
77
  type="submit" class="flex items-center justify-center gap-1 w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
@@ -78,44 +96,64 @@
78
96
 
79
97
  <script setup>
80
98
 
81
- import { onMounted, ref } from 'vue';
99
+ import { onMounted, ref, watchEffect } from 'vue';
82
100
  import { useCoreStore } from '@/stores/core';
101
+ import { useUserStore } from '@/stores/user';
83
102
  import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
84
- import { callAdminForthApi } from '@/utils';
103
+ import { callAdminForthApi, loadFile } from '@/utils';
85
104
  import { useRouter } from 'vue-router';
86
105
 
106
+ const passwordInput = ref(null);
107
+ const usernameInput = ref(null);
108
+
87
109
  const router = useRouter();
88
110
  const inProgress = ref(false);
89
111
 
90
112
  const coreStore = useCoreStore();
113
+ const user = useUserStore();
114
+
91
115
 
92
116
  const showPw = ref(false);
93
117
 
94
118
  const error = ref(null);
95
119
 
96
- onMounted(() => {
97
- coreStore.getPublicConfig()
120
+ onMounted(async () => {
121
+ await coreStore.getPublicConfig();
122
+ if (coreStore.config?.demoCredentials) {
123
+ console.log('Setting demo credentials');
124
+ const [username, password] = coreStore.config.demoCredentials.split(':');
125
+ usernameInput.value.value = username;
126
+ passwordInput.value.value = password;
127
+ }
128
+ usernameInput.value.focus();
98
129
  });
99
130
 
131
+
100
132
  async function login() {
101
- inProgress.value = true;
102
- const resp = await callAdminForthApi({
103
- path: '/login',
104
- method: 'POST',
105
- body: {
106
- username: document.getElementById('username').value,
107
- password: document.getElementById('password').value,
108
- }
109
- });
110
- inProgress.value = false;
111
- if (resp.error) {
112
- error.value = resp.error;
113
- } else {
114
- error.value = null;
115
- router.push('/');
116
- await coreStore.fetchMenuAndResource();
133
+ const username = usernameInput.value.value;
134
+ const password = passwordInput.value.value;
135
+
136
+ if (!username || !password) {
137
+ return;
138
+ }
139
+ inProgress.value = true;
140
+ const resp = await callAdminForthApi({
141
+ path: '/login',
142
+ method: 'POST',
143
+ body: {
144
+ username,
145
+ password,
117
146
  }
118
-
147
+ });
148
+ inProgress.value = false;
149
+ if (resp.error) {
150
+ error.value = resp.error;
151
+ } else if (resp.redirectTo) {
152
+ router.push(resp.redirectTo);
153
+ } else {
154
+ error.value = null;
155
+ await user.finishLogin();
156
+ }
119
157
  }
120
158
 
121
159
 
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div>
2
+ <div :key="`${$route?.params.resourceId}---${$route?.params.primaryKey}`" class="p-4">
3
3
  <RouterView/>
4
4
  </div>
5
5
  </template>
@@ -11,7 +11,6 @@
11
11
 
12
12
 
13
13
  import { useCoreStore } from '@/stores/core';
14
- import { onMounted } from 'vue';
15
14
 
16
15
  const coreStore = useCoreStore()
17
16
 
@@ -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>
@@ -26,35 +43,69 @@
26
43
  <span class="sr-only">Loading...</span>
27
44
  </div>
28
45
  <div
29
- v-else
30
- class="relative overflow-x-auto shadow-md sm:rounded-lg"
46
+ v-else-if="coreStore.record"
47
+ class="relative overflow-x-auto rounded-default shadow-resourseFormShadow "
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-lightFormHeading dark:bg-gray-700 dark:text-gray-400">
34
51
  <tr>
35
52
  <th scope="col" class="px-6 py-3">
36
53
  Field
37
54
  </th>
38
- <th scope="col" class="px-6 py-3 w-4/6">
55
+ <th scope="col" class="px-6 py-3 w-5/6">
39
56
  Value
40
57
  </th>
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-lightForm bg-darkForm 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="column?.components?.show"
79
+ :is="getCustomComponent(column?.components?.show)"
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
- </table>
57
- </div>
93
+ </table>
94
+ </div>
95
+
96
+ <div v-else class="text-center text-gray-500 dark:text-gray-400 mt-10">
97
+ Ooops. Record not found
98
+ </div>
99
+
100
+ <component
101
+ v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.bottom || []"
102
+ :is="getCustomComponent(c)"
103
+ :meta="c.meta"
104
+ :column="column"
105
+ :record="coreStore.record"
106
+ :resource="coreStore.resource"
107
+ :adminUser="coreStore.adminUser"
108
+ />
58
109
 
59
110
 
60
111
  </div>
@@ -63,31 +114,71 @@
63
114
 
64
115
  <script setup>
65
116
 
66
- import { ref, computed, onMounted } from 'vue';
67
- import { useRoute } from 'vue-router';
68
- import { callAdminForthApi } from '@/utils';
69
117
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
70
- import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
71
- import { useCoreStore } from '@/stores/core';
118
+
72
119
  import ValueRenderer from '@/components/ValueRenderer.vue';
120
+ import { useCoreStore } from '@/stores/core';
121
+ import { useModalStore } from '@/stores/modal';
122
+ import { getCustomComponent, checkAcessByAllowedActions } from '@/utils';
123
+ import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
124
+ import { onMounted, ref } from 'vue';
125
+ import { useRoute,useRouter } from 'vue-router';
126
+ import {callAdminForthApi} from '@/utils';
127
+ import { showSuccesTost, showErrorTost } from '@/composables/useFrontendApi';
73
128
 
74
129
 
75
130
  const item = ref(null);
76
131
  const route = useRoute();
77
- const loading = ref(false);
132
+ const router = useRouter();
133
+ const loading = ref(true);
134
+
135
+ console.log(route.params,'showWiev');
136
+
78
137
 
79
138
  const coreStore = useCoreStore();
139
+ const modalStore = useModalStore();
80
140
 
81
141
  onMounted(async () => {
82
142
  loading.value = true;
83
- await coreStore.fetchColumns({
143
+ await coreStore.fetchResourceFull({
84
144
  resourceId: route.params.resourceId
85
145
  });
86
146
  await coreStore.fetchRecord({
87
147
  resourceId: route.params.resourceId,
88
148
  primaryKey: route.params.primaryKey,
89
149
  });
150
+ checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'show');
90
151
  loading.value = false;
91
152
  });
92
153
 
93
- </script>
154
+ async function deleteRecord(row) {
155
+ const data = await window.adminforth.confirm({
156
+ message: 'Are you sure you want to delete this item?',
157
+ yes: 'Delete',
158
+ no: 'Cancel',
159
+ });
160
+ if (data) {
161
+ try {
162
+ const res = await callAdminForthApi({
163
+ path: '/delete_record',
164
+ method: 'POST',
165
+ body: {
166
+ resourceId: route.params.resourceId,
167
+ primaryKey: route.params.primaryKey,
168
+ }});
169
+ if (!res.error){
170
+ router.push({ name: 'resource-list', params: { resourceId: route.params.resourceId } });
171
+ showSuccesTost('Record deleted successfully')
172
+ } else {
173
+ showErrorTost(res.error)
174
+ }
175
+
176
+ } catch (e) {
177
+ console.error(e);
178
+ };
179
+ }
180
+
181
+
182
+ }
183
+
184
+ </script>
@@ -1,12 +1,17 @@
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
- require('flowbite/plugin'),
12
+ require('flowbite/plugin')({
13
+ charts: true,
14
+ }),
10
15
  ],
11
16
  }
12
17
 
@@ -2,6 +2,7 @@ import { fileURLToPath, URL } from 'node:url'
2
2
 
3
3
  import { defineConfig } from 'vite'
4
4
  import vue from '@vitejs/plugin-vue'
5
+ import { clear, error } from 'node:console';
5
6
 
6
7
 
7
8
  const customLogger = {
@@ -11,6 +12,12 @@ const customLogger = {
11
12
  console.log(msg);
12
13
  }
13
14
  },
15
+ warnOnce(msg: string) {
16
+ console.warn('warn once', msg);
17
+ if (!this.hasWarned) {
18
+ this.hasWarned = true;
19
+ }
20
+ },
14
21
  warn(msg: string) {
15
22
  console.warn(msg);
16
23
  },
@@ -20,7 +27,13 @@ const customLogger = {
20
27
  clear() {
21
28
  console.clear();
22
29
  },
30
+ clearScreen() {
31
+ console.clear();
32
+ },
23
33
  hasWarned: false,
34
+ hasErrorLogged: (error: any): boolean => {
35
+ return false;
36
+ }
24
37
  };
25
38
 
26
39
  // https://vitejs.dev/config/
@@ -53,6 +53,43 @@ export var AdminForthResourcePages;
53
53
  AdminForthResourcePages["create"] = "create";
54
54
  AdminForthResourcePages["filter"] = "filter";
55
55
  })(AdminForthResourcePages || (AdminForthResourcePages = {}));
56
+ export class Filters {
57
+ static EQ(field, value) {
58
+ return { field, operator: AdminForthFilterOperators.EQ, value };
59
+ }
60
+ static NEQ(field, value) {
61
+ return { field, operator: AdminForthFilterOperators.NE, value };
62
+ }
63
+ static GT(field, value) {
64
+ return { field, operator: AdminForthFilterOperators.GT, value };
65
+ }
66
+ static GTE(field, value) {
67
+ return { field, operator: AdminForthFilterOperators.GTE, value };
68
+ }
69
+ static LT(field, value) {
70
+ return { field, operator: AdminForthFilterOperators.LT, value };
71
+ }
72
+ static LTE(field, value) {
73
+ return { field, operator: AdminForthFilterOperators.LTE, value };
74
+ }
75
+ static IN(field, value) {
76
+ return { field, operator: AdminForthFilterOperators.IN, value };
77
+ }
78
+ static NOT_IN(field, value) {
79
+ return { field, operator: AdminForthFilterOperators.NIN, value };
80
+ }
81
+ static LIKE(field, value) {
82
+ return { field, operator: AdminForthFilterOperators.LIKE, value };
83
+ }
84
+ }
85
+ export class Sorts {
86
+ static ASC(field) {
87
+ return { field, direction: AdminForthSortDirections.asc };
88
+ }
89
+ static DESC(field) {
90
+ return { field, direction: AdminForthSortDirections.desc };
91
+ }
92
+ }
56
93
  export var AllowedActionsEnum;
57
94
  (function (AllowedActionsEnum) {
58
95
  AllowedActionsEnum["show"] = "show";
package/index.ts CHANGED
@@ -10,7 +10,9 @@ import {
10
10
  type AdminForthConfig,
11
11
  type IAdminForth,
12
12
  type IConfigValidator,
13
- AdminForthFilterOperators, AdminForthDataTypes, AdminForthResourcePages, IHttpServer,
13
+ IOperationalResource,
14
+ AdminForthFilterOperators,
15
+ AdminForthDataTypes, AdminForthResourcePages, IHttpServer,
14
16
  BeforeSaveFunction,
15
17
  AfterSaveFunction,
16
18
  AdminUser,
@@ -18,11 +20,12 @@ import {
18
20
  } from './types/AdminForthConfig.js';
19
21
  import AdminForthPlugin from './basePlugin.js';
20
22
  import ConfigValidator from './modules/configValidator.js';
21
- import AdminForthRestAPI from './modules/restApi.js';
23
+ import AdminForthRestAPI, { interpretResource } from './modules/restApi.js';
22
24
  import ClickhouseConnector from './dataConnectors/clickhouse.js';
23
25
 
24
26
  // exports
25
27
  export * from './types/AdminForthConfig.js';
28
+ export { interpretResource };
26
29
  export { AdminForthPlugin };
27
30
 
28
31
 
@@ -49,7 +52,9 @@ class AdminForth implements IAdminForth {
49
52
  activatedPlugins: Array<AdminForthPlugin>;
50
53
  configValidator: IConfigValidator;
51
54
  restApi: AdminForthRestAPI;
52
-
55
+ resourceInstances: {
56
+ [resourceId: string]: IOperationalResource,
57
+ }
53
58
  baseUrlSlashed: string;
54
59
 
55
60
  statuses: {
@@ -247,6 +252,10 @@ class AdminForth implements IAdminForth {
247
252
  return { ok: true };
248
253
  }
249
254
 
255
+ resource(resourceId: string) {
256
+ return this.config.resources.find((res) => res.resourceId === resourceId);
257
+ }
258
+
250
259
  setupEndpoints(server: IHttpServer) {
251
260
  this.restApi.registerEndpoints(server);
252
261
  }
@@ -0,0 +1,97 @@
1
+ import { IAdminForthFilter, IAdminForthSort, IOperationalResource, IAdminForthDataSourceConnectorBase, AdminForthResource, IAdminForth } from '../types/AdminForthConfig.js';
2
+
3
+
4
+ // export interface IOperationalResource {
5
+ // get: (filters: IAdminForthFilter | IAdminForthFilter[]) => Promise<any[]>;
6
+
7
+ // list: (filters: IAdminForthFilter | IAdminForthFilter[], limit: number, offset: number, sort: IAdminForthSort | IAdminForthSort[]) => Promise<any[]>;
8
+
9
+ // count: (filters: IAdminForthFilter | IAdminForthFilter[]) => Promise<number>;
10
+
11
+ // create: (record: any) => Promise<any>;
12
+
13
+ // update: (primaryKey: any, record: any) => Promise<any>;
14
+
15
+ // delete: (primaryKey: any) => Promise<boolean>;
16
+
17
+ // deleteMany: (primaryKeys: any[]) => Promise<boolean>;
18
+ // }
19
+
20
+
21
+ // async getData({ resource, limit, offset, sort, filters }: {
22
+ // resource: AdminForthResource,
23
+ // limit: number,
24
+ // offset: number,
25
+ // sort: { field: string, direction: AdminForthSortDirections }[],
26
+ // filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
27
+ // }): Promise<{ data: any[], total: number }> {
28
+
29
+ function filtersIfFilter(filter: IAdminForthFilter | IAdminForthFilter[]): IAdminForthFilter[] {
30
+ return (typeof filter === 'object' ? [filter] : filter) as IAdminForthFilter[];
31
+ }
32
+
33
+ function sortsIfSort(sort: IAdminForthSort | IAdminForthSort[]): IAdminForthSort[] {
34
+ return (typeof sort === 'object' ? [sort] : sort) as IAdminForthSort[];
35
+ }
36
+
37
+ export class OperationalResource implements IOperationalResource {
38
+ dataConnector: IAdminForthDataSourceConnectorBase;
39
+ resourceConfig: AdminForthResource;
40
+
41
+ constructor(dataConnector: IAdminForthDataSourceConnectorBase, resourceConfig: AdminForthResource) {
42
+ this.dataConnector = dataConnector;
43
+ this.resourceConfig = resourceConfig;
44
+ }
45
+
46
+ async get(filter: IAdminForthFilter | IAdminForthFilter[]): Promise<any[]> {
47
+ return await this.dataConnector.getData({
48
+ resource: this.resourceConfig,
49
+ filters: filtersIfFilter(filter),
50
+ limit: 1,
51
+ offset: 0,
52
+ sort: [],
53
+ })[0];
54
+ }
55
+
56
+ async list(
57
+ filter: IAdminForthFilter | IAdminForthFilter[],
58
+ limit: number,
59
+ offset: number,
60
+ sort: IAdminForthSort | IAdminForthSort[]
61
+ ): Promise<any[]> {
62
+ const { data } = await this.dataConnector.getData({
63
+ resource: this.resourceConfig,
64
+ filters: filtersIfFilter(filter),
65
+ limit,
66
+ offset,
67
+ sort: sortsIfSort(sort),
68
+ getTotals: false,
69
+ });
70
+ return data;
71
+ }
72
+
73
+ async count(filter: IAdminForthFilter | IAdminForthFilter[]): Promise<number> {
74
+ return await this.dataConnector.getCount({
75
+ resource: this.resourceConfig,
76
+ filters: filtersIfFilter(filter),
77
+ });
78
+ }
79
+
80
+ async create(record: any): Promise<any> {
81
+ return await this.dataConnector.createRecord({ resource: this.resourceConfig, record });
82
+ }
83
+
84
+ async update(primaryKey: any, record: any): Promise<any> {
85
+ return await this.dataConnector.updateRecord({
86
+ resource: this.resourceConfig,
87
+ recordId: primaryKey,
88
+ newValues: record
89
+ });
90
+ }
91
+
92
+ async delete(primaryKey: any): Promise<boolean> {
93
+ return await this.dataConnector.deleteRecord({ resource: this.resourceConfig, recordId: primaryKey });
94
+ }
95
+
96
+
97
+ }