adminforth 2.4.0-next.15 → 2.4.0-next.151

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 (161) hide show
  1. package/commands/callTsProxy.js +14 -4
  2. package/commands/cli.js +12 -4
  3. package/commands/createApp/templates/custom/tsconfig.json.hbs +2 -3
  4. package/commands/createApp/templates/index.ts.hbs +10 -2
  5. package/commands/createApp/templates/package.json.hbs +1 -1
  6. package/commands/createApp/utils.js +27 -2
  7. package/commands/createCustomComponent/configLoader.js +3 -0
  8. package/commands/createCustomComponent/main.js +1 -0
  9. package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +38 -0
  10. package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
  11. package/dist/dataConnectors/baseConnector.js +46 -15
  12. package/dist/dataConnectors/baseConnector.js.map +1 -1
  13. package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
  14. package/dist/dataConnectors/clickhouse.js +15 -0
  15. package/dist/dataConnectors/clickhouse.js.map +1 -1
  16. package/dist/dataConnectors/mongo.d.ts.map +1 -1
  17. package/dist/dataConnectors/mongo.js +44 -15
  18. package/dist/dataConnectors/mongo.js.map +1 -1
  19. package/dist/dataConnectors/mysql.d.ts.map +1 -1
  20. package/dist/dataConnectors/mysql.js +11 -0
  21. package/dist/dataConnectors/mysql.js.map +1 -1
  22. package/dist/dataConnectors/postgres.d.ts.map +1 -1
  23. package/dist/dataConnectors/postgres.js +11 -0
  24. package/dist/dataConnectors/postgres.js.map +1 -1
  25. package/dist/dataConnectors/sqlite.d.ts.map +1 -1
  26. package/dist/dataConnectors/sqlite.js +11 -0
  27. package/dist/dataConnectors/sqlite.js.map +1 -1
  28. package/dist/index.d.ts +2 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +20 -9
  31. package/dist/index.js.map +1 -1
  32. package/dist/modules/codeInjector.d.ts.map +1 -1
  33. package/dist/modules/codeInjector.js +22 -5
  34. package/dist/modules/codeInjector.js.map +1 -1
  35. package/dist/modules/configValidator.d.ts.map +1 -1
  36. package/dist/modules/configValidator.js +54 -3
  37. package/dist/modules/configValidator.js.map +1 -1
  38. package/dist/modules/restApi.d.ts.map +1 -1
  39. package/dist/modules/restApi.js +147 -25
  40. package/dist/modules/restApi.js.map +1 -1
  41. package/dist/modules/styles.d.ts +457 -13
  42. package/dist/modules/styles.d.ts.map +1 -1
  43. package/dist/modules/styles.js +513 -31
  44. package/dist/modules/styles.js.map +1 -1
  45. package/dist/modules/utils.d.ts +1 -0
  46. package/dist/modules/utils.d.ts.map +1 -1
  47. package/dist/modules/utils.js +9 -0
  48. package/dist/modules/utils.js.map +1 -1
  49. package/dist/spa/index.html +1 -1
  50. package/dist/spa/src/App.vue +33 -15
  51. package/dist/spa/src/adminforth.ts +30 -10
  52. package/dist/spa/src/afcl/BarChart.vue +2 -2
  53. package/dist/spa/src/afcl/Button.vue +6 -6
  54. package/dist/spa/src/afcl/Checkbox.vue +21 -13
  55. package/dist/spa/src/afcl/CountryFlag.vue +4 -1
  56. package/dist/spa/src/{components/CustomDatePicker.vue → afcl/DatePicker.vue} +95 -9
  57. package/dist/spa/src/afcl/Dialog.vue +44 -27
  58. package/dist/spa/src/afcl/Dropzone.vue +12 -12
  59. package/dist/spa/src/afcl/Input.vue +6 -6
  60. package/dist/spa/src/afcl/JsonViewer.vue +25 -0
  61. package/dist/spa/src/afcl/LinkButton.vue +1 -1
  62. package/dist/spa/src/afcl/PieChart.vue +5 -5
  63. package/dist/spa/src/afcl/ProgressBar.vue +7 -7
  64. package/dist/spa/src/afcl/Select.vue +68 -34
  65. package/dist/spa/src/afcl/Skeleton.vue +6 -6
  66. package/dist/spa/src/afcl/Table.vue +199 -71
  67. package/dist/spa/src/afcl/Textarea.vue +31 -0
  68. package/dist/spa/src/afcl/Toggle.vue +32 -0
  69. package/dist/spa/src/afcl/Tooltip.vue +1 -2
  70. package/dist/spa/src/afcl/VerticalTabs.vue +3 -3
  71. package/dist/spa/src/afcl/index.ts +4 -3
  72. package/dist/spa/src/components/AcceptModal.vue +7 -7
  73. package/dist/spa/src/components/Breadcrumbs.vue +5 -5
  74. package/dist/spa/src/components/ColumnValueInput.vue +38 -18
  75. package/dist/spa/src/components/ColumnValueInputWrapper.vue +4 -3
  76. package/dist/spa/src/components/CustomDateRangePicker.vue +9 -8
  77. package/dist/spa/src/components/CustomRangePicker.vue +37 -8
  78. package/dist/spa/src/components/ErrorMessage.vue +21 -0
  79. package/dist/spa/src/components/Filters.vue +83 -37
  80. package/dist/spa/src/components/GroupsTable.vue +9 -8
  81. package/dist/spa/src/components/MenuLink.vue +3 -3
  82. package/dist/spa/src/components/ResourceForm.vue +94 -51
  83. package/dist/spa/src/components/ResourceListTable.vue +78 -80
  84. package/dist/spa/src/components/ResourceListTableVirtual.vue +70 -72
  85. package/dist/spa/src/components/ShowTable.vue +17 -12
  86. package/dist/spa/src/components/SingleSkeletLoader.vue +6 -6
  87. package/dist/spa/src/components/SkeleteLoader.vue +3 -3
  88. package/dist/spa/src/components/ThreeDotsMenu.vue +73 -14
  89. package/dist/spa/src/components/Toast.vue +27 -9
  90. package/dist/spa/src/components/ValueRenderer.vue +43 -16
  91. package/dist/spa/src/controls/BoolToggle.vue +34 -0
  92. package/dist/spa/src/i18n.ts +1 -1
  93. package/dist/spa/src/shims-vue.d.ts +5 -0
  94. package/dist/spa/src/spa_types/core.ts +8 -1
  95. package/dist/spa/src/stores/core.ts +1 -1
  96. package/dist/spa/src/stores/modal.ts +6 -1
  97. package/dist/spa/src/stores/toast.ts +22 -3
  98. package/dist/spa/src/types/Back.ts +107 -21
  99. package/dist/spa/src/types/Common.ts +45 -31
  100. package/dist/spa/src/types/FrontendAPI.ts +15 -2
  101. package/dist/spa/src/types/adapters/CompletionAdapter.ts +25 -0
  102. package/dist/spa/src/types/adapters/EmailAdapter.ts +27 -0
  103. package/dist/spa/src/types/adapters/ImageGenerationAdapter.ts +50 -0
  104. package/dist/spa/src/types/adapters/ImageVisionAdapter.ts +30 -0
  105. package/dist/spa/src/types/adapters/OAuth2Adapter.ts +34 -0
  106. package/dist/spa/src/types/adapters/StorageAdapter.ts +73 -0
  107. package/dist/spa/src/types/adapters/index.ts +6 -0
  108. package/dist/spa/src/utils.ts +217 -7
  109. package/dist/spa/src/views/CreateView.vue +18 -19
  110. package/dist/spa/src/views/EditView.vue +25 -19
  111. package/dist/spa/src/views/ListView.vue +124 -79
  112. package/dist/spa/src/views/LoginView.vue +36 -44
  113. package/dist/spa/src/views/ResourceParent.vue +2 -2
  114. package/dist/spa/src/views/ShowView.vue +59 -39
  115. package/dist/spa/src/websocket.ts +6 -1
  116. package/dist/spa/tsconfig.app.json +1 -1
  117. package/dist/spa/vite.config.ts +45 -2
  118. package/dist/types/Back.d.ts +75 -14
  119. package/dist/types/Back.d.ts.map +1 -1
  120. package/dist/types/Back.js +15 -0
  121. package/dist/types/Back.js.map +1 -1
  122. package/dist/types/Common.d.ts +38 -28
  123. package/dist/types/Common.d.ts.map +1 -1
  124. package/dist/types/Common.js.map +1 -1
  125. package/dist/types/FrontendAPI.d.ts +15 -1
  126. package/dist/types/FrontendAPI.d.ts.map +1 -1
  127. package/dist/types/FrontendAPI.js.map +1 -1
  128. package/dist/types/adapters/CompletionAdapter.d.ts +20 -0
  129. package/dist/types/adapters/CompletionAdapter.d.ts.map +1 -0
  130. package/dist/types/adapters/CompletionAdapter.js +2 -0
  131. package/dist/types/adapters/CompletionAdapter.js.map +1 -0
  132. package/dist/types/adapters/EmailAdapter.d.ts +20 -0
  133. package/dist/types/adapters/EmailAdapter.d.ts.map +1 -0
  134. package/dist/types/adapters/EmailAdapter.js +2 -0
  135. package/dist/types/adapters/EmailAdapter.js.map +1 -0
  136. package/dist/types/adapters/ImageGenerationAdapter.d.ts +37 -0
  137. package/dist/types/adapters/ImageGenerationAdapter.d.ts.map +1 -0
  138. package/dist/types/adapters/ImageGenerationAdapter.js +2 -0
  139. package/dist/types/adapters/ImageGenerationAdapter.js.map +1 -0
  140. package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
  141. package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
  142. package/dist/types/adapters/ImageVisionAdapter.js +2 -0
  143. package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
  144. package/dist/types/adapters/OAuth2Adapter.d.ts +32 -0
  145. package/dist/types/adapters/OAuth2Adapter.d.ts.map +1 -0
  146. package/dist/types/adapters/OAuth2Adapter.js +2 -0
  147. package/dist/types/adapters/OAuth2Adapter.js.map +1 -0
  148. package/dist/types/adapters/StorageAdapter.d.ts +63 -0
  149. package/dist/types/adapters/StorageAdapter.d.ts.map +1 -0
  150. package/dist/types/adapters/StorageAdapter.js +2 -0
  151. package/dist/types/adapters/StorageAdapter.js.map +1 -0
  152. package/dist/types/adapters/index.d.ts +7 -0
  153. package/dist/types/adapters/index.d.ts.map +1 -0
  154. package/dist/types/adapters/index.js +2 -0
  155. package/dist/types/adapters/index.js.map +1 -0
  156. package/package.json +3 -2
  157. package/dist/spa/src/types/Adapters.ts +0 -213
  158. package/dist/types/Adapters.d.ts +0 -168
  159. package/dist/types/Adapters.d.ts.map +0 -1
  160. package/dist/types/Adapters.js +0 -2
  161. package/dist/types/Adapters.js.map +0 -1
@@ -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,
@@ -1,18 +1,60 @@
1
1
  import { fileURLToPath, URL } from 'node:url'
2
-
3
2
  import { defineConfig } from 'vite'
4
3
  import vue from '@vitejs/plugin-vue'
5
4
  import portfinder from 'portfinder';
5
+ import { Plugin } from 'vite';
6
+ import tailwindcss from 'tailwindcss';
7
+
6
8
 
7
9
  /**
8
10
  * Find the next available port after a specified port.
9
11
  * @param {number} startPort - The starting port to check.
10
12
  * @returns {Promise<number>} - A promise that resolves with the next available port.
11
13
  */
12
- async function getNextAvailablePort(startPort) {
14
+ async function getNextAvailablePort(startPort: number | undefined) {
13
15
  return await portfinder.getPortPromise({ port: startPort });
14
16
  };
15
17
 
18
+ function ignoreTailwindErrors(): Plugin {
19
+ return {
20
+ name: 'ignore-tailwind-errors',
21
+ configureServer(server) {
22
+ server.middlewares.use((req, res, next) => {
23
+ const originalWrite = res.write;
24
+ res.write = function(chunk) {
25
+ if (typeof chunk === 'string' && chunk.includes('tailwind')) {
26
+ return true;
27
+ }
28
+ return originalWrite.call(this, chunk);
29
+ };
30
+ next();
31
+ });
32
+ },
33
+ config(config, { command }) {
34
+ if (command === 'build') {
35
+ // Override PostCSS config for build
36
+ config.css = config.css || {};
37
+ config.css.postcss = {
38
+ plugins: [
39
+ {
40
+ postcssPlugin: 'ignore-tailwind-errors',
41
+ Once(root, helpers) {
42
+ try {
43
+ return tailwindcss()(root, helpers);
44
+ } catch (error: any) {
45
+ console.warn('TailwindCSS warning ignored:', error.message);
46
+ return;
47
+ }
48
+ }
49
+ }
50
+ ]
51
+ };
52
+ }
53
+ }
54
+ };
55
+ }
56
+
57
+
16
58
  const appPort = await getNextAvailablePort(5173);
17
59
  const hmrPort = await getNextAvailablePort(5273);
18
60
  console.log(`SPA port: ${appPort}. HMR port: ${hmrPort}`);
@@ -27,6 +69,7 @@ export default defineConfig({
27
69
  },
28
70
  },
29
71
  plugins: [
72
+ //ignoreTailwindErrors(),
30
73
  vue(),
31
74
  ],
32
75
  resolve: {
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
- import type { Express } from 'express';
2
+ import type { Express, Request } from 'express';
3
3
  import type { Writable } from 'stream';
4
- import { ActionCheckSource, AdminForthFilterOperators, AdminForthSortDirections, AllowedActionsEnum, type AdminForthComponentDeclaration, type AdminUser, type AllowedActionsResolved, type AdminForthBulkActionCommon, type AdminForthForeignResourceCommon, type AdminForthResourceColumnCommon, AdminForthResourceInputCommon, AdminForthComponentDeclarationFull, AdminForthConfigMenuItem, AnnouncementBadgeResponse, AdminForthResourcePages, AdminForthResourceColumnInputCommon } from './Common.js';
4
+ import { ActionCheckSource, AdminForthFilterOperators, AdminForthSortDirections, AllowedActionsEnum, type AdminForthComponentDeclaration, type AdminUser, type AllowedActionsResolved, type AdminForthBulkActionCommon, type AdminForthForeignResourceCommon, type AdminForthResourceColumnCommon, type AdminForthResourceInputCommon, type AdminForthComponentDeclarationFull, type AdminForthConfigMenuItem, type AnnouncementBadgeResponse, AdminForthResourcePages, type AdminForthResourceColumnInputCommon } from './Common.js';
5
5
  export interface ICodeInjector {
6
6
  srcFoldersToSync: Object;
7
7
  allComponentNames: Object;
@@ -62,23 +62,34 @@ export interface IExpressHttpServer extends IHttpServer {
62
62
  * Adds adminUser to request object if user is authorized. Drops request with 401 status if user is not authorized.
63
63
  * @param callable : Function which will be called if user is authorized.
64
64
  *
65
- * Example:
66
65
  *
66
+ * @example
67
67
  * ```ts
68
- * expressApp.get('/myApi', authorize((req, res) => \{
68
+ * expressApp.get('/myApi', authorize((req, res) => {
69
69
  * console.log('User is authorized', req.adminUser);
70
- * res.json(\{ message: 'Hello World' \});
71
- * \}));
72
- * ``
70
+ * res.json({ message: 'Hello World' });
71
+ * }));
72
+ * ```
73
73
  *
74
74
  */
75
75
  authorize(callable: Function): void;
76
76
  }
77
+ export interface ITranslateFunction {
78
+ (msg: string, category: string, params: any, pluralizationNumber?: number): Promise<string>;
79
+ }
80
+ export interface IAdminUserExpressRequest extends Omit<Request, 'protocol' | 'param' | 'unshift'> {
81
+ adminUser: AdminUser;
82
+ }
83
+ export interface ITranslateExpressRequest extends Omit<Request, 'protocol' | 'param' | 'unshift'> {
84
+ tr: ITranslateFunction;
85
+ }
77
86
  export interface IAdminForthSingleFilter {
78
87
  field?: string;
79
88
  operator?: AdminForthFilterOperators.EQ | AdminForthFilterOperators.NE | AdminForthFilterOperators.GT | AdminForthFilterOperators.LT | AdminForthFilterOperators.GTE | AdminForthFilterOperators.LTE | AdminForthFilterOperators.LIKE | AdminForthFilterOperators.ILIKE | AdminForthFilterOperators.IN | AdminForthFilterOperators.NIN;
80
89
  value?: any;
90
+ rightField?: string;
81
91
  insecureRawSQL?: string;
92
+ insecureRawNoSQL?: any;
82
93
  }
83
94
  export interface IAdminForthAndOrFilter {
84
95
  operator: AdminForthFilterOperators.AND | AdminForthFilterOperators.OR;
@@ -315,6 +326,7 @@ export interface IAdminForth {
315
326
  }): Promise<{
316
327
  error?: string;
317
328
  createdRecord?: any;
329
+ newRecordId?: any;
318
330
  }>;
319
331
  updateResourceRecord(params: {
320
332
  resource: AdminForthResource;
@@ -451,6 +463,7 @@ export type BeforeDataSourceRequestFunction = (params: {
451
463
  }) => Promise<{
452
464
  ok: boolean;
453
465
  error?: string;
466
+ newRecordId?: string;
454
467
  }>;
455
468
  /**
456
469
  * Modify response to change how data is returned after fetching from database.
@@ -509,7 +522,7 @@ export type BeforeEditSaveFunction = (params: {
509
522
  extra?: HttpExtra;
510
523
  }) => Promise<{
511
524
  ok: boolean;
512
- error?: string;
525
+ error?: string | null;
513
526
  }>;
514
527
  export type BeforeCreateSaveFunction = (params: {
515
528
  resource: AdminForthResource;
@@ -519,7 +532,8 @@ export type BeforeCreateSaveFunction = (params: {
519
532
  extra?: HttpExtra;
520
533
  }) => Promise<{
521
534
  ok: boolean;
522
- error?: string;
535
+ error?: string | null;
536
+ newRecordId?: string;
523
537
  }>;
524
538
  export type AfterCreateSaveFunction = (params: {
525
539
  resource: AdminForthResource;
@@ -602,11 +616,20 @@ interface AdminForthInputConfigCustomization {
602
616
  * Your app name
603
617
  */
604
618
  brandName?: string;
619
+ /**
620
+ * Whether to use single theme for the app
621
+ */
622
+ singleTheme?: 'light' | 'dark';
605
623
  /**
606
624
  * Whether to show brand name in sidebar
607
625
  * default is true
608
626
  */
609
627
  showBrandNameInSidebar?: boolean;
628
+ /**
629
+ * Whether to show brand logo in sidebar
630
+ * default is true
631
+ */
632
+ showBrandLogoInSidebar?: boolean;
610
633
  /**
611
634
  * Path to your app logo
612
635
  *
@@ -727,8 +750,17 @@ interface AdminForthInputConfigCustomization {
727
750
  userMenu?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>;
728
751
  header?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>;
729
752
  sidebar?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>;
753
+ sidebarTop?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>;
730
754
  everyPageBottom?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>;
731
755
  };
756
+ /**
757
+ * Allows adding custom elements (e.g., <link>, <script>, <meta>) to the <head> of the HTML document.
758
+ * Each item must include a tag name and a set of attributes.
759
+ */
760
+ customHeadItems?: {
761
+ tagName: string;
762
+ attributes: Record<string, string | boolean>;
763
+ }[];
732
764
  }
733
765
  export interface AdminForthActionInput {
734
766
  name: string;
@@ -885,6 +917,12 @@ export interface AdminForthInputConfig {
885
917
  * Default: '1/2'
886
918
  */
887
919
  loginBackgroundPosition?: 'over' | '1/2' | '1/3' | '2/3' | '3/4' | '2/5' | '3/5';
920
+ /**
921
+ * If true, background blend mode will be removed from login background image when position is 'over'
922
+ *
923
+ * Default: false
924
+ */
925
+ removeBackgroundBlendMode?: boolean;
888
926
  /**
889
927
  * Function or functions which will be called before user try to login.
890
928
  * Each function will resive User object as an argument
@@ -903,7 +941,7 @@ export interface AdminForthInputConfig {
903
941
  /**
904
942
  * Any prompt to show users on login. Supports HTML.
905
943
  */
906
- loginPromptHTML?: string;
944
+ loginPromptHTML?: string | (() => string | void | undefined | Promise<string | void | undefined>) | undefined;
907
945
  /**
908
946
  * Remember me days for "Remember Me" checkbox on login page.
909
947
  * If not set or set to null/0/undefined, "Remember Me" checkbox will not be displayed.
@@ -991,8 +1029,13 @@ export interface AdminForthConfigCustomization extends Omit<AdminForthInputConfi
991
1029
  userMenu: Array<AdminForthComponentDeclarationFull>;
992
1030
  header: Array<AdminForthComponentDeclarationFull>;
993
1031
  sidebar: Array<AdminForthComponentDeclarationFull>;
1032
+ sidebarTop: Array<AdminForthComponentDeclarationFull>;
994
1033
  everyPageBottom: Array<AdminForthComponentDeclarationFull>;
995
1034
  };
1035
+ customHeadItems?: {
1036
+ tagName: string;
1037
+ attributes: Record<string, string | boolean>;
1038
+ }[];
996
1039
  }
997
1040
  export interface AdminForthConfig extends Omit<AdminForthInputConfig, 'customization' | 'resources'> {
998
1041
  baseUrl: string;
@@ -1011,6 +1054,11 @@ export declare class Filters {
1011
1054
  static IN(field: string, value: any): IAdminForthSingleFilter;
1012
1055
  static NOT_IN(field: string, value: any): IAdminForthSingleFilter;
1013
1056
  static LIKE(field: string, value: any): IAdminForthSingleFilter;
1057
+ static ILIKE(field: string, value: any): IAdminForthSingleFilter;
1058
+ static GT_FIELD(leftField: string, rightField: string): IAdminForthSingleFilter;
1059
+ static GTE_FIELD(leftField: string, rightField: string): IAdminForthSingleFilter;
1060
+ static LT_FIELD(leftField: string, rightField: string): IAdminForthSingleFilter;
1061
+ static LTE_FIELD(leftField: string, rightField: string): IAdminForthSingleFilter;
1014
1062
  static AND(...args: (IAdminForthSingleFilter | IAdminForthAndOrFilter | Array<IAdminForthSingleFilter | IAdminForthAndOrFilter>)[]): IAdminForthAndOrFilter;
1015
1063
  static OR(...args: (IAdminForthSingleFilter | IAdminForthAndOrFilter | Array<IAdminForthSingleFilter | IAdminForthAndOrFilter>)[]): IAdminForthAndOrFilter;
1016
1064
  }
@@ -1166,9 +1214,13 @@ export interface AdminForthResource extends Omit<AdminForthResourceInput, 'optio
1166
1214
  };
1167
1215
  create?: {
1168
1216
  /**
1217
+ * Should return `ok: true` to continue saving pipeline and allow creating record in database, and `ok: false` to interrupt pipeline and prevent record creation.
1218
+ * If you need to show error on UI, set `error: \<error message\>` in response.
1219
+ *
1169
1220
  * Typical use-cases:
1170
- * - Validate record before saving to database and interrupt execution if validation failed (`allowedActions.create` should be preferred in most cases)
1171
- * - fill-in adminUser as creator of record
1221
+ * - Create record by custom code (return `{ ok: false, newRecordId: <id of created record from custom code> }`)
1222
+ * - Validate record before saving to database and interrupt execution if validation failed (return `{ ok: false, error: <validation error> }`), though `allowedActions.create` should be preferred in most cases
1223
+ * - fill-in adminUser as creator of record (set `record.<some field> = x; return \{ ok: true \}`)
1172
1224
  * - Attach additional data to record before saving to database (mostly fillOnCreate should be used instead)
1173
1225
  */
1174
1226
  beforeSave?: Array<BeforeCreateSaveFunction>;
@@ -1306,13 +1358,22 @@ export type ShowInInput = ShowInModernInput | ShowInLegacyInput;
1306
1358
  export type ShowIn = {
1307
1359
  [key in AdminForthResourcePages]: AllowedActionValue;
1308
1360
  };
1309
- export interface AdminForthResourceColumnInput extends Omit<AdminForthResourceColumnInputCommon, 'showIn'> {
1361
+ export type BackendOnlyInput = boolean | ((p: {
1362
+ adminUser: AdminUser;
1363
+ resource: AdminForthResource;
1364
+ meta: any;
1365
+ source: ActionCheckSource;
1366
+ adminforth: IAdminForth;
1367
+ }) => boolean | Promise<boolean>);
1368
+ export interface AdminForthResourceColumnInput extends Omit<AdminForthResourceColumnInputCommon, 'showIn' | 'backendOnly'> {
1310
1369
  showIn?: ShowInInput;
1311
1370
  foreignResource?: AdminForthForeignResource;
1371
+ backendOnly?: BackendOnlyInput;
1312
1372
  }
1313
- export interface AdminForthResourceColumn extends Omit<AdminForthResourceColumnCommon, 'showIn'> {
1373
+ export interface AdminForthResourceColumn extends Omit<AdminForthResourceColumnCommon, 'showIn' | 'backendOnly'> {
1314
1374
  showIn?: ShowIn;
1315
1375
  foreignResource?: AdminForthForeignResource;
1376
+ backendOnly?: BackendOnlyInput;
1316
1377
  }
1317
1378
  export interface IWebSocketClient {
1318
1379
  id: string;