adminforth 2.4.0-next.2 → 2.4.0-next.200

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 (197) hide show
  1. package/commands/callTsProxy.js +14 -4
  2. package/commands/cli.js +12 -4
  3. package/commands/createApp/templates/.env.local.hbs +2 -2
  4. package/commands/createApp/templates/custom/tsconfig.json.hbs +2 -3
  5. package/commands/createApp/templates/index.ts.hbs +11 -6
  6. package/commands/createApp/templates/package.json.hbs +1 -1
  7. package/commands/createApp/utils.js +40 -7
  8. package/commands/createCustomComponent/configLoader.js +3 -0
  9. package/commands/createCustomComponent/configUpdater.js +25 -21
  10. package/commands/createCustomComponent/fileGenerator.js +1 -1
  11. package/commands/createCustomComponent/main.js +3 -1
  12. package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +38 -0
  13. package/commands/createCustomComponent/templates/login/beforeLogin.vue.hbs +18 -0
  14. package/commands/createPlugin/templates/custom/tsconfig.json.hbs +2 -5
  15. package/commands/createPlugin/templates/package.json.hbs +1 -1
  16. package/dist/auth.d.ts +9 -1
  17. package/dist/auth.d.ts.map +1 -1
  18. package/dist/auth.js +15 -2
  19. package/dist/auth.js.map +1 -1
  20. package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
  21. package/dist/dataConnectors/baseConnector.js +46 -15
  22. package/dist/dataConnectors/baseConnector.js.map +1 -1
  23. package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
  24. package/dist/dataConnectors/clickhouse.js +15 -0
  25. package/dist/dataConnectors/clickhouse.js.map +1 -1
  26. package/dist/dataConnectors/mongo.d.ts.map +1 -1
  27. package/dist/dataConnectors/mongo.js +44 -15
  28. package/dist/dataConnectors/mongo.js.map +1 -1
  29. package/dist/dataConnectors/mysql.d.ts.map +1 -1
  30. package/dist/dataConnectors/mysql.js +11 -0
  31. package/dist/dataConnectors/mysql.js.map +1 -1
  32. package/dist/dataConnectors/postgres.d.ts.map +1 -1
  33. package/dist/dataConnectors/postgres.js +11 -0
  34. package/dist/dataConnectors/postgres.js.map +1 -1
  35. package/dist/dataConnectors/sqlite.d.ts.map +1 -1
  36. package/dist/dataConnectors/sqlite.js +11 -0
  37. package/dist/dataConnectors/sqlite.js.map +1 -1
  38. package/dist/index.d.ts +2 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +20 -9
  41. package/dist/index.js.map +1 -1
  42. package/dist/modules/codeInjector.d.ts +2 -0
  43. package/dist/modules/codeInjector.d.ts.map +1 -1
  44. package/dist/modules/codeInjector.js +55 -12
  45. package/dist/modules/codeInjector.js.map +1 -1
  46. package/dist/modules/configValidator.d.ts.map +1 -1
  47. package/dist/modules/configValidator.js +76 -8
  48. package/dist/modules/configValidator.js.map +1 -1
  49. package/dist/modules/restApi.d.ts.map +1 -1
  50. package/dist/modules/restApi.js +149 -25
  51. package/dist/modules/restApi.js.map +1 -1
  52. package/dist/modules/styles.d.ts +485 -13
  53. package/dist/modules/styles.d.ts.map +1 -1
  54. package/dist/modules/styles.js +541 -31
  55. package/dist/modules/styles.js.map +1 -1
  56. package/dist/modules/utils.d.ts +2 -0
  57. package/dist/modules/utils.d.ts.map +1 -1
  58. package/dist/modules/utils.js +16 -0
  59. package/dist/modules/utils.js.map +1 -1
  60. package/dist/servers/express.d.ts.map +1 -1
  61. package/dist/servers/express.js +14 -0
  62. package/dist/servers/express.js.map +1 -1
  63. package/dist/spa/index.html +1 -1
  64. package/dist/spa/package-lock.json +5 -4
  65. package/dist/spa/package.json +1 -1
  66. package/dist/spa/src/App.vue +54 -169
  67. package/dist/spa/src/adminforth.ts +42 -18
  68. package/dist/spa/src/afcl/BarChart.vue +2 -2
  69. package/dist/spa/src/afcl/Button.vue +6 -6
  70. package/dist/spa/src/afcl/ButtonGroup.vue +91 -0
  71. package/dist/spa/src/afcl/Card.vue +25 -0
  72. package/dist/spa/src/afcl/Checkbox.vue +21 -13
  73. package/dist/spa/src/afcl/CountryFlag.vue +4 -1
  74. package/dist/spa/src/{components/CustomDatePicker.vue → afcl/DatePicker.vue} +95 -9
  75. package/dist/spa/src/afcl/Dialog.vue +44 -27
  76. package/dist/spa/src/afcl/Dropzone.vue +15 -13
  77. package/dist/spa/src/afcl/Input.vue +9 -7
  78. package/dist/spa/src/afcl/JsonViewer.vue +25 -0
  79. package/dist/spa/src/afcl/Link.vue +1 -1
  80. package/dist/spa/src/afcl/LinkButton.vue +3 -3
  81. package/dist/spa/src/afcl/PieChart.vue +5 -5
  82. package/dist/spa/src/afcl/ProgressBar.vue +7 -7
  83. package/dist/spa/src/afcl/Select.vue +68 -34
  84. package/dist/spa/src/afcl/Skeleton.vue +6 -6
  85. package/dist/spa/src/afcl/Table.vue +213 -74
  86. package/dist/spa/src/afcl/Textarea.vue +31 -0
  87. package/dist/spa/src/afcl/Toggle.vue +32 -0
  88. package/dist/spa/src/afcl/Tooltip.vue +2 -3
  89. package/dist/spa/src/afcl/VerticalTabs.vue +16 -7
  90. package/dist/spa/src/afcl/index.ts +6 -3
  91. package/dist/spa/src/components/AcceptModal.vue +7 -7
  92. package/dist/spa/src/components/Breadcrumbs.vue +5 -5
  93. package/dist/spa/src/components/ColumnValueInput.vue +38 -18
  94. package/dist/spa/src/components/ColumnValueInputWrapper.vue +4 -3
  95. package/dist/spa/src/components/CustomDateRangePicker.vue +9 -8
  96. package/dist/spa/src/components/CustomRangePicker.vue +37 -8
  97. package/dist/spa/src/components/ErrorMessage.vue +21 -0
  98. package/dist/spa/src/components/Filters.vue +85 -39
  99. package/dist/spa/src/components/GroupsTable.vue +9 -8
  100. package/dist/spa/src/components/MenuLink.vue +90 -23
  101. package/dist/spa/src/components/ResourceForm.vue +94 -51
  102. package/dist/spa/src/components/ResourceListTable.vue +78 -80
  103. package/dist/spa/src/components/ResourceListTableVirtual.vue +70 -72
  104. package/dist/spa/src/components/ShowTable.vue +17 -12
  105. package/dist/spa/src/components/Sidebar.vue +443 -0
  106. package/dist/spa/src/components/SingleSkeletLoader.vue +6 -6
  107. package/dist/spa/src/components/SkeleteLoader.vue +3 -3
  108. package/dist/spa/src/components/ThreeDotsMenu.vue +73 -14
  109. package/dist/spa/src/components/Toast.vue +27 -9
  110. package/dist/spa/src/components/UserMenuSettingsButton.vue +68 -0
  111. package/dist/spa/src/components/ValueRenderer.vue +43 -16
  112. package/dist/spa/src/controls/BoolToggle.vue +34 -0
  113. package/dist/spa/src/i18n.ts +1 -1
  114. package/dist/spa/src/renderers/CompactField.vue +1 -1
  115. package/dist/spa/src/renderers/CompactUUID.vue +1 -1
  116. package/dist/spa/src/router/index.ts +8 -0
  117. package/dist/spa/src/shims-vue.d.ts +5 -0
  118. package/dist/spa/src/spa_types/core.ts +13 -1
  119. package/dist/spa/src/stores/core.ts +1 -1
  120. package/dist/spa/src/stores/filters.ts +29 -2
  121. package/dist/spa/src/stores/modal.ts +6 -1
  122. package/dist/spa/src/stores/toast.ts +22 -3
  123. package/dist/spa/src/types/Back.ts +139 -22
  124. package/dist/spa/src/types/Common.ts +68 -32
  125. package/dist/spa/src/types/FrontendAPI.ts +31 -5
  126. package/dist/spa/src/types/adapters/CaptchaAdapter.ts +34 -0
  127. package/dist/spa/src/types/adapters/CompletionAdapter.ts +25 -0
  128. package/dist/spa/src/types/adapters/EmailAdapter.ts +27 -0
  129. package/dist/spa/src/types/adapters/ImageGenerationAdapter.ts +50 -0
  130. package/dist/spa/src/types/adapters/ImageVisionAdapter.ts +30 -0
  131. package/dist/spa/src/types/adapters/KeyValueAdapter.ts +16 -0
  132. package/dist/spa/src/types/adapters/OAuth2Adapter.ts +34 -0
  133. package/dist/spa/src/types/adapters/StorageAdapter.ts +73 -0
  134. package/dist/spa/src/types/adapters/index.ts +8 -0
  135. package/dist/spa/src/utils.ts +219 -8
  136. package/dist/spa/src/views/CreateView.vue +18 -19
  137. package/dist/spa/src/views/EditView.vue +25 -19
  138. package/dist/spa/src/views/ListView.vue +139 -86
  139. package/dist/spa/src/views/LoginView.vue +65 -55
  140. package/dist/spa/src/views/ResourceParent.vue +2 -2
  141. package/dist/spa/src/views/SettingsView.vue +121 -0
  142. package/dist/spa/src/views/ShowView.vue +59 -39
  143. package/dist/spa/src/websocket.ts +6 -1
  144. package/dist/spa/tsconfig.app.json +1 -1
  145. package/dist/spa/vite.config.ts +45 -2
  146. package/dist/types/Back.d.ts +117 -14
  147. package/dist/types/Back.d.ts.map +1 -1
  148. package/dist/types/Back.js +15 -0
  149. package/dist/types/Back.js.map +1 -1
  150. package/dist/types/Common.d.ts +60 -29
  151. package/dist/types/Common.d.ts.map +1 -1
  152. package/dist/types/Common.js.map +1 -1
  153. package/dist/types/FrontendAPI.d.ts +31 -3
  154. package/dist/types/FrontendAPI.d.ts.map +1 -1
  155. package/dist/types/FrontendAPI.js.map +1 -1
  156. package/dist/types/adapters/CaptchaAdapter.d.ts +30 -0
  157. package/dist/types/adapters/CaptchaAdapter.d.ts.map +1 -0
  158. package/dist/types/adapters/CaptchaAdapter.js +5 -0
  159. package/dist/types/adapters/CaptchaAdapter.js.map +1 -0
  160. package/dist/types/adapters/CompletionAdapter.d.ts +20 -0
  161. package/dist/types/adapters/CompletionAdapter.d.ts.map +1 -0
  162. package/dist/types/adapters/CompletionAdapter.js +2 -0
  163. package/dist/types/adapters/CompletionAdapter.js.map +1 -0
  164. package/dist/types/adapters/EmailAdapter.d.ts +20 -0
  165. package/dist/types/adapters/EmailAdapter.d.ts.map +1 -0
  166. package/dist/types/adapters/EmailAdapter.js +2 -0
  167. package/dist/types/adapters/EmailAdapter.js.map +1 -0
  168. package/dist/types/adapters/ImageGenerationAdapter.d.ts +37 -0
  169. package/dist/types/adapters/ImageGenerationAdapter.d.ts.map +1 -0
  170. package/dist/types/adapters/ImageGenerationAdapter.js +2 -0
  171. package/dist/types/adapters/ImageGenerationAdapter.js.map +1 -0
  172. package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
  173. package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
  174. package/dist/types/adapters/ImageVisionAdapter.js +2 -0
  175. package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
  176. package/dist/types/adapters/KeyValueAdapter.d.ts +10 -0
  177. package/dist/types/adapters/KeyValueAdapter.d.ts.map +1 -0
  178. package/dist/types/adapters/KeyValueAdapter.js +2 -0
  179. package/dist/types/adapters/KeyValueAdapter.js.map +1 -0
  180. package/dist/types/adapters/OAuth2Adapter.d.ts +32 -0
  181. package/dist/types/adapters/OAuth2Adapter.d.ts.map +1 -0
  182. package/dist/types/adapters/OAuth2Adapter.js +2 -0
  183. package/dist/types/adapters/OAuth2Adapter.js.map +1 -0
  184. package/dist/types/adapters/StorageAdapter.d.ts +63 -0
  185. package/dist/types/adapters/StorageAdapter.d.ts.map +1 -0
  186. package/dist/types/adapters/StorageAdapter.js +2 -0
  187. package/dist/types/adapters/StorageAdapter.js.map +1 -0
  188. package/dist/types/adapters/index.d.ts +9 -0
  189. package/dist/types/adapters/index.d.ts.map +1 -0
  190. package/dist/types/adapters/index.js +2 -0
  191. package/dist/types/adapters/index.js.map +1 -0
  192. package/package.json +3 -2
  193. package/dist/spa/src/types/Adapters.ts +0 -213
  194. package/dist/types/Adapters.d.ts +0 -168
  195. package/dist/types/Adapters.d.ts.map +0 -1
  196. package/dist/types/Adapters.js +0 -2
  197. package/dist/types/Adapters.js.map +0 -1
@@ -1,10 +1,10 @@
1
1
  <template>
2
- <div class="relative flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-800 relative w-screen h-screen"
2
+ <div class="relative flex items-center justify-center min-h-screen bg-lightHtml dark:bg-darkHtml w-screen h-screen"
3
3
  :style="coreStore.config?.loginBackgroundImage && backgroundPosition === 'over' ? {
4
4
  'background-image': 'url(' + loadFile(coreStore.config?.loginBackgroundImage) + ')',
5
5
  'background-size': 'cover',
6
6
  'background-position': 'center',
7
- 'background-blend-mode': 'darken'
7
+ 'background-blend-mode': coreStore.config?.removeBackgroundBlendMode ? 'normal' : 'darken'
8
8
  }: {}"
9
9
  >
10
10
 
@@ -23,44 +23,57 @@
23
23
 
24
24
  <!-- Main modal -->
25
25
  <div id="authentication-modal" tabindex="-1"
26
- class="overflow-y-auto flex flex-grow
26
+ class="af-login-modal overflow-y-auto flex flex-grow
27
27
  overflow-x-hidden z-50 min-w-[350px] justify-center items-center md:inset-0 h-[calc(100%-1rem)] max-h-full">
28
28
  <div class="relative p-4 w-full max-h-full max-w-[400px]">
29
29
  <!-- Modal content -->
30
- <div class="relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black" >
30
+ <div class="af-login-modal-content relative bg-lightLoginViewBackground rounded-lg shadow dark:bg-darkLoginViewBackground dark:shadow-black" >
31
31
  <!-- Modal header -->
32
- <div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
33
- <h3 class="text-xl font-semibold text-gray-900 dark:text-white">
32
+ <div class="af-login-modal-header flex items-center justify-between flex-col p-4 md:p-5 border-b rounded-t dark:border-gray-600">
33
+
34
+ <template v-if="coreStore?.config?.loginPageInjections?.panelHeader.length > 0">
35
+ <component
36
+ v-for="(c, index) in coreStore?.config?.loginPageInjections?.panelHeader || []"
37
+ :key="index"
38
+ :is="getCustomComponent(c)"
39
+ :meta="c.meta"
40
+ />
41
+ </template>
42
+ <h3 v-else class="text-xl font-semibold text-lightLoginViewText dark:text-darkLoginViewTextColor">
34
43
  {{ $t('Sign in to') }} {{ coreStore.config?.brandName }}
35
44
  </h3>
36
45
  </div>
37
46
  <!-- Modal body -->
38
- <div class="p-4 md:p-5">
47
+ <div class="af-login-modal-body p-4 md:p-5">
39
48
  <form class="space-y-4" @submit.prevent>
40
49
  <div>
41
- <label for="username" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{ $t('Your') }} {{ coreStore.config?.usernameFieldName?.toLowerCase() }}</label>
42
- <input
50
+ <label for="username" class="block mb-2 text-sm font-medium text-lightLoginViewText dark:text-darkLoginViewTextColor">{{ $t('Your') }} {{ coreStore.config?.usernameFieldName?.toLowerCase() }}</label>
51
+ <Input
52
+ v-model="username"
43
53
  autocomplete="username"
44
54
  type="username"
45
55
  name="username"
46
56
  id="username"
47
57
  ref="usernameInput"
48
- oninput="setCustomValidity('')"
49
58
  @keydown.enter="passwordInput.focus()"
50
- 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 />
59
+ class="w-full"
60
+ placeholder="name@company.com" required />
51
61
  </div>
52
- <div class="relative">
53
- <label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{ $t('Your password') }}</label>
54
- <input
62
+ <div class="">
63
+ <label for="password" class="block mb-2 text-sm font-medium text-lightLoginViewText dark:text-darkLoginViewTextColor">{{ $t('Your password') }}</label>
64
+ <Input
65
+ v-model="password"
55
66
  ref="passwordInput"
56
67
  autocomplete="current-password"
57
- oninput="setCustomValidity('')"
58
68
  @keydown.enter="login"
59
- :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 />
60
- <button type="button" @click="showPw = !showPw" class="absolute top-12 right-3 -translate-y-1/2 text-gray-400 dark:text-gray-300">
61
- <IconEyeSolid class="w-5 h-5" v-if="!showPw" />
62
- <IconEyeSlashSolid class="w-5 h-5" v-else />
63
- </button>
69
+ :type="!showPw ? 'password': 'text'" name="password" id="password" placeholder="••••••••" class="w-full" required>
70
+ <template #rightIcon>
71
+ <button type="button" @click="showPw = !showPw" class="text-lightLoginViewSubTextColor dark:text-darkLoginViewSubTextColor">
72
+ <IconEyeSolid class="w-5 h-5" v-if="!showPw" />
73
+ <IconEyeSlashSolid class="w-5 h-5" v-else />
74
+ </button>
75
+ </template>
76
+ </Input>
64
77
  </div>
65
78
 
66
79
  <div v-if="coreStore.config.rememberMeDays"
@@ -77,28 +90,21 @@
77
90
  v-for="c in coreStore?.config?.loginPageInjections?.underInputs || []"
78
91
  :is="getCustomComponent(c)"
79
92
  :meta="c.meta"
93
+ @update:disableLoginButton="setDisableLoginButton($event)"
80
94
  />
81
95
 
82
- <div v-if="error" class="flex items-center p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
83
- <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">
84
- <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"/>
85
- </svg>
86
- <span class="sr-only">{{ $t('Info') }}</span>
87
- <div>
88
- {{ error }}
89
- </div>
90
- </div>
91
-
92
- <div v-if="coreStore.config?.loginPromptHTML"
93
- 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"
96
+ <ErrorMessage :error="error" />
97
+
98
+ <div v-if="loginPromptHTML"
99
+ class="flex items-center p-4 mb-4 text-sm text-lightLoginViewPromptText rounded-lg bg-lightLoginViewPromptBackground dark:bg-darkLoginViewPromptBackground dark:text-darkLoginViewPromptText" role="alert"
94
100
  >
95
101
  <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">
96
102
  <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"/>
97
103
  </svg>
98
104
  <span class="sr-only">{{ $t('Info') }}</span>
99
- <div v-html="coreStore.config?.loginPromptHTML"></div>
105
+ <div v-html="loginPromptHTML"></div>
100
106
  </div>
101
- <Button @click="login" :loader="inProgress" :disabled="inProgress" class="w-full">
107
+ <Button @click="login" :loader="inProgress" :disabled="inProgress || disableLoginButton" class="w-full">
102
108
  {{ $t('Login to your account') }}
103
109
  </Button>
104
110
  </form>
@@ -112,7 +118,7 @@
112
118
  </template>
113
119
 
114
120
 
115
- <script setup>
121
+ <script setup lang="ts">
116
122
 
117
123
  import { getCustomComponent } from '@/utils';
118
124
  import { onBeforeMount, onMounted, ref, computed } from 'vue';
@@ -121,30 +127,43 @@ import { useUserStore } from '@/stores/user';
121
127
  import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
122
128
  import { callAdminForthApi, loadFile } from '@/utils';
123
129
  import { useRoute, useRouter } from 'vue-router';
124
- import { Button, Checkbox } from '@/afcl';
130
+ import { Button, Checkbox, Input } from '@/afcl';
125
131
  import { useI18n } from 'vue-i18n';
132
+ import ErrorMessage from '@/components/ErrorMessage.vue';
126
133
 
127
134
  const { t } = useI18n();
128
135
 
129
136
  const passwordInput = ref(null);
130
137
  const usernameInput = ref(null);
131
138
  const rememberMeValue= ref(false);
139
+ const username = ref('');
140
+ const password = ref('');
132
141
 
133
142
  const route = useRoute();
134
143
  const router = useRouter();
135
144
  const inProgress = ref(false);
136
-
145
+ const loginPromptHTML = ref()
137
146
  const coreStore = useCoreStore();
138
147
  const user = useUserStore();
139
148
 
140
149
  const showPw = ref(false);
141
150
 
142
151
  const error = ref(null);
152
+ const disableLoginButton = ref(false);
143
153
 
144
154
  const backgroundPosition = computed(() => {
145
155
  return coreStore.config?.loginBackgroundPosition || '1/2';
146
156
  });
147
157
 
158
+
159
+ async function getLoginFormConfig() {
160
+ const response = await callAdminForthApi({
161
+ path: '/get_login_form_config',
162
+ method: 'GET',
163
+ });
164
+ loginPromptHTML.value = response.loginPromptHTML;
165
+ }
166
+
148
167
  onBeforeMount(() => {
149
168
  if (localStorage.getItem('isAuthorized') === 'true') {
150
169
  // if route has next param, redirect
@@ -158,29 +177,17 @@ onBeforeMount(() => {
158
177
  })
159
178
 
160
179
  onMounted(async () => {
180
+ getLoginFormConfig();
161
181
  if (coreStore.config?.demoCredentials) {
162
- const [username, password] = coreStore.config.demoCredentials.split(':');
163
- usernameInput.value.value = username;
164
- passwordInput.value.value = password;
182
+ const [demoUsername, demoPassword] = coreStore.config.demoCredentials.split(':');
183
+ username.value = demoUsername;
184
+ password.value = demoPassword;
165
185
  }
166
186
  usernameInput.value.focus();
167
187
  });
168
188
 
169
189
 
170
190
  async function login() {
171
-
172
- const username = usernameInput.value.value;
173
- const password = passwordInput.value.value;
174
-
175
- if (!username) {
176
- usernameInput.value.setCustomValidity(t('Please fill out this field.'));
177
- return;
178
- }
179
- if (!password) {
180
- passwordInput.value.setCustomValidity(t('Please fill out this field.'));
181
- return;
182
- }
183
-
184
191
  if (inProgress.value) {
185
192
  return;
186
193
  }
@@ -189,8 +196,8 @@ async function login() {
189
196
  path: '/login',
190
197
  method: 'POST',
191
198
  body: {
192
- username,
193
- password,
199
+ username: username.value,
200
+ password: password.value,
194
201
  rememberMe: rememberMeValue.value,
195
202
  }
196
203
  });
@@ -206,5 +213,8 @@ async function login() {
206
213
 
207
214
  }
208
215
 
216
+ function setDisableLoginButton(value: boolean) {
217
+ disableLoginButton.value = value;
218
+ }
209
219
 
210
220
  </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div :key="`${$route?.params.resourceId}---${$route?.params.primaryKey}`" class="p-4 flex"
2
+ <div :key="`${$route?.params.resourceId}---${$route?.params.primaryKey}`" class="af-resource-parent p-4 flex"
3
3
  :class="limitHeightToPage ? 'h-[calc(100dvh-3.5rem)]': undefined"
4
4
  >
5
5
  <RouterView/>
@@ -33,7 +33,7 @@ const limitHeightToPage = computed(() => {
33
33
  }
34
34
  const listPageInjects = coreStore.resource.options.pageInjections.list;
35
35
 
36
- for (const pi of [listPageInjects.beforeBreadcrumbs, listPageInjects.afterBreadcrumbs, listPageInjects.bottom]) {
36
+ for (const pi of [listPageInjects.beforeBreadcrumbs, listPageInjects.beforeActionButtons, listPageInjects.afterBreadcrumbs, listPageInjects.bottom]) {
37
37
  if (pi) {
38
38
  for (const piItem of pi) {
39
39
  if (!piItem.meta?.thinEnoughToShrinkTable) {
@@ -0,0 +1,121 @@
1
+ <template>
2
+ <div class="mt-20 h-full w-full" :class="{ 'hidden': initialTabSet === false }">
3
+ <div v-if="!coreStore?.config?.settingPages || coreStore?.config?.settingPages.length === 0">
4
+ <p>No setting pages configured or still loading...</p>
5
+ </div>
6
+ <VerticalTabs v-else ref="VerticalTabsRef" v-model:active-tab="activeTab" @update:active-tab="setURL({slug: $event, pageLabel: ''})">
7
+ <template v-for="(c,i) in coreStore?.config?.settingPages" :key="`tab:${settingPageSlotName(c,i)}`" v-slot:['tab:'+c.slug]>
8
+ <div class="flex items-center justify-center whitespace-nowrap w-full px-4 gap-2" @click="setURL(c)">
9
+ <component v-if="c.icon" :is="getIcon(c.icon)" class="w-5 h-5 group-hover:text-lightSidebarIconsHover transition duration-75 dark:group-hover:text-darkSidebarIconsHover dark:text-darkSidebarIcons" ></component>
10
+ {{ c.pageLabel }}
11
+ </div>
12
+ </template>
13
+
14
+ <template v-for="(c,i) in coreStore?.config?.settingPages" :key="`${settingPageSlotName(c,i)}-content`" v-slot:[c.slug]>
15
+ <component
16
+ :is="getCustomComponent({file: c.component || ''})"
17
+ :resource="coreStore.resource"
18
+ :adminUser="coreStore.adminUser"
19
+ />
20
+ </template>
21
+ </VerticalTabs>
22
+ </div>
23
+ </template>
24
+
25
+ <script setup lang="ts">
26
+ import { ref, onMounted, watch } from 'vue';
27
+ import { useRouter } from 'vue-router';
28
+ import { useCoreStore } from '@/stores/core';
29
+ import { getCustomComponent, getIcon } from '@/utils';
30
+ import { Dropdown } from 'flowbite';
31
+ import adminforth from '@/adminforth';
32
+ import { VerticalTabs } from '@/afcl'
33
+ import { useRoute } from 'vue-router'
34
+
35
+ const route = useRoute()
36
+ const coreStore = useCoreStore();
37
+ const router = useRouter();
38
+
39
+ const routerIsReady = ref(false);
40
+ const loginRedirectCheckIsReady = ref(false);
41
+ const dropdownUserButton = ref<HTMLElement | null>(null);
42
+ const VerticalTabsRef = ref();
43
+ const activeTab = ref('');
44
+ const initialTabSet = ref(false);
45
+
46
+ watch(() => route?.params?.page, (val) => {
47
+ handleURLChange(val as string | null);
48
+ });
49
+
50
+ async function initRouter() {
51
+ await router.isReady();
52
+ routerIsReady.value = true;
53
+ }
54
+
55
+ watch(dropdownUserButton, (el) => {
56
+ if (el) {
57
+ const dd = new Dropdown(
58
+ document.querySelector('#dropdown-user') as HTMLElement,
59
+ document.querySelector('[data-dropdown-toggle="dropdown-user"]') as HTMLElement,
60
+ );
61
+ adminforth.closeUserMenuDropdown = () => dd.hide();
62
+ }
63
+ });
64
+
65
+ onMounted(async () => {
66
+ if (coreStore.adminUser) {
67
+ await loadMenu();
68
+ loginRedirectCheckIsReady.value = true;
69
+ const routeParamsPage = route?.params?.page;
70
+ if (!routeParamsPage) {
71
+ if (coreStore.config?.settingPages?.[0]) {
72
+ setURL(coreStore.config.settingPages[0]);
73
+ }
74
+ } else {
75
+ handleURLChange(routeParamsPage as string | null);
76
+ }
77
+ }
78
+ });
79
+
80
+ async function loadMenu() {
81
+ await initRouter();
82
+ await coreStore.fetchMenuAndResource();
83
+ }
84
+
85
+ function slugifyString(str: string): string {
86
+ return str
87
+ .toString()
88
+ .toLowerCase()
89
+ .replace(/\s+/g, '-')
90
+ .replace(/[^a-z0-9-_]/g, '-');
91
+ }
92
+
93
+ function setURL(item: {
94
+ pageLabel: string;
95
+ slug?: string | undefined;
96
+ }) {
97
+ router.replace({
98
+ name: 'settings',
99
+ params: { page: item?.slug }
100
+ });
101
+ }
102
+
103
+ function handleURLChange(val: string | null) {
104
+ let isParamInTabs;
105
+ for (const c of coreStore?.config?.settingPages || []) {
106
+ if (c.slug ? c.slug === val : slugifyString(c.pageLabel) === val) {
107
+ isParamInTabs = true;
108
+ break;
109
+ }
110
+ }
111
+ if (isParamInTabs) {
112
+ VerticalTabsRef.value.setActiveTab(val);
113
+ activeTab.value = val as string;
114
+ if (!initialTabSet.value) initialTabSet.value = true;
115
+ } else {
116
+ if (coreStore.config?.settingPages?.[0]) {
117
+ setURL(coreStore.config.settingPages[0]);
118
+ }
119
+ }
120
+ }
121
+ </script>
@@ -4,7 +4,7 @@
4
4
  v-if="!loading"
5
5
  v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.beforeBreadcrumbs || []"
6
6
  :is="getCustomComponent(c)"
7
- :meta="c.meta"
7
+ :meta="(c as AdminForthComponentDeclarationFull).meta"
8
8
  :record="coreStore.record"
9
9
  :resource="coreStore.resource"
10
10
  :adminUser="coreStore.adminUser"
@@ -15,7 +15,7 @@
15
15
  v-for="action in coreStore.resource.options.actions.filter(a => a.showIn?.showButton)"
16
16
  :key="action.id"
17
17
  @click="startCustomAction(action.id)"
18
- :disabled="actionLoadingStates[action.id]"
18
+ :disabled="actionLoadingStates[action.id!]"
19
19
  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-lightPrimary 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"
20
20
  >
21
21
  <component
@@ -28,28 +28,28 @@
28
28
  </template>
29
29
  <RouterLink v-if="coreStore.resource?.options?.allowedActions?.create"
30
30
  :to="{ name: 'resource-create', params: { resourceId: $route.params.resourceId } }"
31
- 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-lightPrimary 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"
31
+ class="af-add-new-button flex items-center py-1 px-3 text-sm font-medium text-lightShowViewButtonText focus:outline-none bg-lightShowViewButtonBackground rounded border border-lightShowViewButtonBorder hover:bg-lightShowViewButtonBackgroundHover hover:text-lightShowViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightShowViewButtonFocusRing dark:focus:ring-darkShowViewButtonFocusRing dark:bg-darkShowViewButtonBackground dark:text-darkShowViewButtonText dark:border-darkShowViewButtonBorder dark:hover:text-darkShowViewButtonTextHover dark:hover:bg-darkShowViewButtonBackgroundHover rounded-default gap-1"
32
32
  >
33
- <IconPlusOutline class="w-4 h-4 me-2"/>
33
+ <IconPlusOutline class="w-4 h-4"/>
34
34
  {{ $t('Add new') }}
35
35
  </RouterLink>
36
36
 
37
37
  <RouterLink v-if="coreStore?.resourceOptions?.allowedActions?.edit" :to="{ name: 'resource-edit', params: { resourceId: $route.params.resourceId, primaryKey: $route.params.primaryKey } }"
38
- 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-lightPrimary 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"
38
+ class="flex items-center af-edit-button py-1 px-3 text-sm font-medium text-lightShowViewButtonText focus:outline-none bg-lightShowViewButtonBackground rounded-default border border-lightShowViewButtonBorder hover:bg-lightShowViewButtonBackgroundHover hover:text-lightShowViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightShowViewButtonFocusRing dark:focus:ring-darkShowViewButtonFocusRing dark:bg-darkShowViewButtonBackground dark:text-darkShowViewButtonText dark:border-darkShowViewButtonBorder dark:hover:text-darkShowViewButtonTextHover dark:hover:bg-darkShowViewButtonBackgroundHover gap-1"
39
39
  >
40
40
  <IconPenSolid class="w-4 h-4" />
41
41
  {{ $t('Edit') }}
42
42
  </RouterLink>
43
43
 
44
44
  <button v-if="coreStore?.resourceOptions?.allowedActions?.delete" @click="deleteRecord"
45
- 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"
45
+ class="flex items-center af-delete-button py-1 px-3 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-lightShowViewButtonBackground border border-lightShowViewButtonBorder hover:bg-lightShowViewButtonBackgroundHover hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-lightShowViewButtonFocusRing dark:focus:ring-darkShowViewButtonFocusRing dark:bg-darkShowViewButtonBackground dark:text-red-500 dark:border-darkShowViewButtonBorder dark:hover:text-darkShowViewButtonTextHover dark:hover:bg-darkShowViewButtonBackgroundHover gap-1"
46
46
  >
47
47
  <IconTrashBinSolid class="w-4 h-4" />
48
48
  {{ $t('Delete') }}
49
49
  </button>
50
50
 
51
51
  <ThreeDotsMenu
52
- :threeDotsDropdownItems="coreStore.resourceOptions?.pageInjections?.show?.threeDotsDropdownItems"
52
+ :threeDotsDropdownItems="(coreStore.resourceOptions?.pageInjections?.show?.threeDotsDropdownItems as [])"
53
53
  :customActions="customActions"
54
54
  ></ThreeDotsMenu>
55
55
  </BreadcrumbsWithButtons>
@@ -57,7 +57,7 @@
57
57
  <component
58
58
  v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.afterBreadcrumbs || []"
59
59
  :is="getCustomComponent(c)"
60
- :meta="c.meta"
60
+ :meta="(c as AdminForthComponentDeclarationFull).meta"
61
61
  :record="coreStore.record"
62
62
  :resource="coreStore.resource"
63
63
  :adminUser="coreStore.adminUser"
@@ -76,11 +76,11 @@
76
76
  v-else-if="coreStore.record"
77
77
  class="relative w-full flex flex-col gap-4"
78
78
  >
79
- <div v-if="!groups.length && allColumns.length">
79
+ <div v-if="!groups.length && allColumns?.length">
80
80
  <ShowTable
81
- :columns="allColumns"
82
81
  :resource="coreStore.resource"
83
82
  :record="coreStore.record"
83
+ :columns="allColumns as Array<{ name: string; label?: string; components?: any }>"
84
84
  />
85
85
  </div>
86
86
  <template v-else>
@@ -93,12 +93,12 @@
93
93
  :record="coreStore.record"
94
94
  />
95
95
  </template>
96
- <template v-if="otherColumns.length > 0">
96
+ <template v-if="otherColumns && otherColumns.length > 0">
97
97
  <ShowTable
98
- :columns="otherColumns"
99
98
  groupName="Other Fields"
100
99
  :resource="coreStore.resource"
101
100
  :record="coreStore.record"
101
+ :columns="otherColumns as Array<{ name: string; label?: string; components?: any }>"
102
102
  />
103
103
  </template>
104
104
  </template>
@@ -112,8 +112,7 @@
112
112
  v-if="!loading"
113
113
  v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.bottom || []"
114
114
  :is="getCustomComponent(c)"
115
- :meta="c.meta"
116
- :column="column"
115
+ :meta="(c as AdminForthComponentDeclarationFull).meta"
117
116
  :record="coreStore.record"
118
117
  :resource="coreStore.resource"
119
118
  :adminUser="coreStore.adminUser"
@@ -140,6 +139,7 @@ import ShowTable from '@/components/ShowTable.vue';
140
139
  import adminforth from "@/adminforth";
141
140
  import { useI18n } from 'vue-i18n';
142
141
  import { getIcon } from '@/utils';
142
+ import { type AdminForthComponentDeclarationFull, type AdminForthResourceColumnCommon, type FieldGroup } from '@/types/Common.js';
143
143
 
144
144
  const route = useRoute();
145
145
  const router = useRouter();
@@ -147,62 +147,65 @@ const loading = ref(true);
147
147
  const { t } = useI18n();
148
148
  const coreStore = useCoreStore();
149
149
 
150
- const actionLoadingStates = ref({});
150
+ const actionLoadingStates = ref<Record<string, boolean>>({});
151
151
 
152
152
  const customActions = computed(() => {
153
- return coreStore.resource?.options?.actions?.filter(a => a.showIn?.showThreeDotsMenu) || [];
153
+ return coreStore.resource?.options?.actions?.filter((a: any) => a.showIn?.showThreeDotsMenu) || [];
154
154
  });
155
155
 
156
156
  onMounted(async () => {
157
157
  loading.value = true;
158
158
  await coreStore.fetchResourceFull({
159
- resourceId: route.params.resourceId
159
+ resourceId: route.params.resourceId as string,
160
160
  });
161
161
  initThreeDotsDropdown();
162
162
  await coreStore.fetchRecord({
163
- resourceId: route.params.resourceId,
164
- primaryKey: route.params.primaryKey,
163
+ resourceId: route.params.resourceId as string,
164
+ primaryKey: route.params.primaryKey as string,
165
165
  source: 'show',
166
166
  });
167
- checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'show');
167
+ if(coreStore.resourceOptions){
168
+ checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'show');
169
+ }
168
170
  loading.value = false;
169
171
  });
170
172
 
171
173
  const groups = computed(() => {
172
174
  let fieldGroupType;
173
- if (coreStore.resource.options?.showFieldGroups) {
174
- fieldGroupType = coreStore.resource.options.showFieldGroups;
175
- } else if (coreStore.resource.options?.showFieldGroups === null) {
176
- fieldGroupType = [];
177
- } else {
178
- fieldGroupType = coreStore.resource.options?.fieldGroups;
175
+ if (coreStore.resource) {
176
+ if (coreStore.resource.options?.showFieldGroups) {
177
+ fieldGroupType = coreStore.resource.options.showFieldGroups;
178
+ } else if (coreStore.resource.options?.showFieldGroups === null) {
179
+ fieldGroupType = [];
180
+ } else {
181
+ fieldGroupType = coreStore.resource.options?.fieldGroups;
182
+ }
179
183
  }
184
+ const activeGroups: typeof fieldGroupType | [] = fieldGroupType ?? [];
180
185
 
181
- const activeGroups = fieldGroupType ?? [];
182
-
183
- return activeGroups.map(group => ({
186
+ return activeGroups.map((group: FieldGroup) => ({
184
187
  ...group,
185
- columns: coreStore.resource.columns.filter(
186
- col => group.columns.includes(col.name) && col.showIn.show
188
+ columns: coreStore.resource?.columns.filter(
189
+ col => group.columns.includes(col.name) && col.showIn?.show
187
190
  ),
188
191
  }));
189
192
  });
190
193
 
191
194
  const allColumns = computed(() => {
192
- return coreStore.resource.columns.filter(col => col.showIn.show);
195
+ return coreStore.resource?.columns.filter(col => col.showIn?.show);
193
196
  });
194
197
 
195
198
  const otherColumns = computed(() => {
196
199
  const groupedColumnNames = new Set(
197
- groups.value.flatMap(group => group.columns.map(col => col.name))
200
+ groups.value.flatMap(group => group.columns.map((col: AdminForthResourceColumnCommon) => col.name))
198
201
  );
199
202
 
200
- return coreStore.resource.columns.filter(
201
- col => !groupedColumnNames.has(col.name) && col.showIn.show
203
+ return coreStore.resource?.columns.filter(
204
+ col => !groupedColumnNames.has(col.name) && col.showIn?.show
202
205
  );
203
206
  });
204
207
 
205
- async function deleteRecord(row) {
208
+ async function deleteRecord() {
206
209
  const data = await adminforth.confirm({
207
210
  message: t('Are you sure you want to delete this item?'),
208
211
  yes: t('Delete'),
@@ -231,7 +234,7 @@ async function deleteRecord(row) {
231
234
 
232
235
  }
233
236
 
234
- async function startCustomAction(actionId) {
237
+ async function startCustomAction(actionId: string) {
235
238
  actionLoadingStates.value[actionId] = true;
236
239
 
237
240
  const data = await callAdminForthApi({
@@ -263,8 +266,8 @@ async function startCustomAction(actionId) {
263
266
 
264
267
  if (data?.ok) {
265
268
  await coreStore.fetchRecord({
266
- resourceId: route.params.resourceId,
267
- primaryKey: route.params.primaryKey,
269
+ resourceId: route.params.resourceId as string,
270
+ primaryKey: route.params.primaryKey as string,
268
271
  source: 'show',
269
272
  });
270
273
 
@@ -281,4 +284,21 @@ async function startCustomAction(actionId) {
281
284
  }
282
285
  }
283
286
 
287
+ adminforth.show.refresh = () => {
288
+ (async () => {
289
+ try {
290
+ loading.value = true;
291
+ await coreStore.fetchRecord({
292
+ resourceId: String(route.params.resourceId),
293
+ primaryKey: String(route.params.primaryKey),
294
+ source: 'show',
295
+ });
296
+ } catch (e) {
297
+ showErrorTost((e as Error).message);
298
+ } finally {
299
+ loading.value = false;
300
+ }
301
+ })();
302
+ }
303
+
284
304
  </script>
@@ -1,8 +1,13 @@
1
1
 
2
2
  const subscriptions: { [topic: string]: ((data: any) => void)[] } = {};
3
+
4
+ interface ExtendedWebSocket extends WebSocket {
5
+ connected?: boolean;
6
+ }
7
+
3
8
  const state: {
4
9
  status: 'connecting' | 'connected' | 'disconnected';
5
- ws: WebSocket | null;
10
+ ws: ExtendedWebSocket | null;
6
11
  } = {
7
12
  status: 'connecting',
8
13
  ws: null
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "extends": "@vue/tsconfig/tsconfig.dom.json",
3
- "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
3
+ "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "src/**/*.d.ts"],
4
4
  "exclude": ["src/**/__tests__/*"],
5
5
  "compilerOptions": {
6
6
  "composite": true,