adminforth 2.7.18 → 2.8.0

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 (107) hide show
  1. package/commands/createApp/templates/index.ts.hbs +2 -1
  2. package/commands/createCustomComponent/main.js +1 -0
  3. package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +38 -0
  4. package/commands/createPlugin/templates/custom/tsconfig.json.hbs +2 -5
  5. package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
  6. package/dist/dataConnectors/baseConnector.js +33 -15
  7. package/dist/dataConnectors/baseConnector.js.map +1 -1
  8. package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
  9. package/dist/dataConnectors/clickhouse.js +15 -0
  10. package/dist/dataConnectors/clickhouse.js.map +1 -1
  11. package/dist/dataConnectors/mongo.d.ts.map +1 -1
  12. package/dist/dataConnectors/mongo.js +30 -1
  13. package/dist/dataConnectors/mongo.js.map +1 -1
  14. package/dist/dataConnectors/mysql.d.ts.map +1 -1
  15. package/dist/dataConnectors/mysql.js +11 -0
  16. package/dist/dataConnectors/mysql.js.map +1 -1
  17. package/dist/dataConnectors/postgres.d.ts.map +1 -1
  18. package/dist/dataConnectors/postgres.js +11 -0
  19. package/dist/dataConnectors/postgres.js.map +1 -1
  20. package/dist/dataConnectors/sqlite.d.ts.map +1 -1
  21. package/dist/dataConnectors/sqlite.js +11 -0
  22. package/dist/dataConnectors/sqlite.js.map +1 -1
  23. package/dist/modules/codeInjector.d.ts +1 -0
  24. package/dist/modules/codeInjector.d.ts.map +1 -1
  25. package/dist/modules/codeInjector.js +4 -0
  26. package/dist/modules/codeInjector.js.map +1 -1
  27. package/dist/modules/configValidator.d.ts.map +1 -1
  28. package/dist/modules/configValidator.js +6 -2
  29. package/dist/modules/configValidator.js.map +1 -1
  30. package/dist/modules/restApi.d.ts.map +1 -1
  31. package/dist/modules/restApi.js +2 -0
  32. package/dist/modules/restApi.js.map +1 -1
  33. package/dist/modules/styles.d.ts +8 -1
  34. package/dist/modules/styles.d.ts.map +1 -1
  35. package/dist/modules/styles.js +9 -2
  36. package/dist/modules/styles.js.map +1 -1
  37. package/dist/spa/src/App.vue +12 -4
  38. package/dist/spa/src/adminforth.ts +31 -11
  39. package/dist/spa/src/afcl/BarChart.vue +2 -2
  40. package/dist/spa/src/afcl/Checkbox.vue +2 -2
  41. package/dist/spa/src/afcl/Dialog.vue +38 -21
  42. package/dist/spa/src/afcl/Dropzone.vue +2 -2
  43. package/dist/spa/src/afcl/Input.vue +1 -1
  44. package/dist/spa/src/afcl/LinkButton.vue +1 -1
  45. package/dist/spa/src/afcl/PieChart.vue +5 -5
  46. package/dist/spa/src/afcl/Select.vue +17 -12
  47. package/dist/spa/src/afcl/Table.vue +202 -72
  48. package/dist/spa/src/afcl/Textarea.vue +31 -0
  49. package/dist/spa/src/afcl/Toggle.vue +2 -2
  50. package/dist/spa/src/afcl/Tooltip.vue +0 -1
  51. package/dist/spa/src/afcl/index.ts +1 -1
  52. package/dist/spa/src/components/AcceptModal.vue +1 -1
  53. package/dist/spa/src/components/ColumnValueInput.vue +11 -11
  54. package/dist/spa/src/components/ColumnValueInputWrapper.vue +2 -2
  55. package/dist/spa/src/components/CustomRangePicker.vue +5 -5
  56. package/dist/spa/src/components/ErrorMessage.vue +21 -0
  57. package/dist/spa/src/components/Filters.vue +7 -6
  58. package/dist/spa/src/components/GroupsTable.vue +2 -1
  59. package/dist/spa/src/components/MenuLink.vue +3 -3
  60. package/dist/spa/src/components/ResourceForm.vue +35 -27
  61. package/dist/spa/src/components/ResourceListTable.vue +42 -42
  62. package/dist/spa/src/components/ResourceListTableVirtual.vue +41 -41
  63. package/dist/spa/src/components/ShowTable.vue +3 -3
  64. package/dist/spa/src/components/SkeleteLoader.vue +2 -2
  65. package/dist/spa/src/components/ThreeDotsMenu.vue +69 -10
  66. package/dist/spa/src/components/Toast.vue +25 -2
  67. package/dist/spa/src/components/ValueRenderer.vue +39 -12
  68. package/dist/spa/src/i18n.ts +1 -1
  69. package/dist/spa/src/shims-vue.d.ts +5 -0
  70. package/dist/spa/src/spa_types/core.ts +1 -1
  71. package/dist/spa/src/stores/modal.ts +6 -1
  72. package/dist/spa/src/stores/toast.ts +22 -3
  73. package/dist/spa/src/types/Back.ts +31 -6
  74. package/dist/spa/src/types/Common.ts +33 -24
  75. package/dist/spa/src/types/FrontendAPI.ts +21 -5
  76. package/dist/spa/src/types/adapters/EmailAdapter.ts +2 -4
  77. package/dist/spa/src/types/adapters/ImageVisionAdapter.ts +30 -0
  78. package/dist/spa/src/types/adapters/index.ts +1 -0
  79. package/dist/spa/src/utils.ts +8 -7
  80. package/dist/spa/src/views/CreateView.vue +15 -16
  81. package/dist/spa/src/views/EditView.vue +23 -17
  82. package/dist/spa/src/views/ListView.vue +116 -66
  83. package/dist/spa/src/views/LoginView.vue +2 -9
  84. package/dist/spa/src/views/ResourceParent.vue +1 -1
  85. package/dist/spa/src/views/ShowView.vue +59 -39
  86. package/dist/spa/src/websocket.ts +6 -1
  87. package/dist/spa/tsconfig.app.json +1 -1
  88. package/dist/spa/vite.config.ts +45 -2
  89. package/dist/types/Back.d.ts +16 -1
  90. package/dist/types/Back.d.ts.map +1 -1
  91. package/dist/types/Back.js +15 -0
  92. package/dist/types/Back.js.map +1 -1
  93. package/dist/types/Common.d.ts +27 -22
  94. package/dist/types/Common.d.ts.map +1 -1
  95. package/dist/types/Common.js.map +1 -1
  96. package/dist/types/FrontendAPI.d.ts +21 -3
  97. package/dist/types/FrontendAPI.d.ts.map +1 -1
  98. package/dist/types/FrontendAPI.js.map +1 -1
  99. package/dist/types/adapters/EmailAdapter.d.ts +2 -3
  100. package/dist/types/adapters/EmailAdapter.d.ts.map +1 -1
  101. package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
  102. package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
  103. package/dist/types/adapters/ImageVisionAdapter.js +2 -0
  104. package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
  105. package/dist/types/adapters/index.d.ts +1 -0
  106. package/dist/types/adapters/index.d.ts.map +1 -1
  107. package/package.json +2 -1
@@ -3,7 +3,8 @@
3
3
  <Teleport to="body">
4
4
  <Filters
5
5
  :columns="coreStore.resource?.columns"
6
- :columnsMinMax="columnsMinMax" :show="filtersShow"
6
+ :columnsMinMax="columnsMinMax"
7
+ :show="filtersShow"
7
8
  @hide="filtersShow = false"
8
9
  />
9
10
  </Teleport>
@@ -11,12 +12,19 @@
11
12
  <component
12
13
  v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.beforeBreadcrumbs || []"
13
14
  :is="getCustomComponent(c)"
14
- :meta="c.meta"
15
+ :meta="(c as AdminForthComponentDeclarationFull).meta"
15
16
  :resource="coreStore.resource"
16
17
  :adminUser="coreStore.adminUser"
17
18
  />
18
19
 
19
20
  <BreadcrumbsWithButtons>
21
+ <component
22
+ v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.beforeActionButtons || []"
23
+ :is="getCustomComponent(c)"
24
+ :meta="(c as AdminForthComponentDeclarationFull).meta"
25
+ :resource="coreStore.resource"
26
+ :adminUser="coreStore.adminUser"
27
+ />
20
28
  <button
21
29
  @click="()=>{checkboxes = []}"
22
30
  v-if="checkboxes.length"
@@ -31,47 +39,47 @@
31
39
  </Tooltip>
32
40
  </button>
33
41
 
34
- <button
35
- v-if="checkboxes.length"
42
+ <div
43
+ v-if="checkboxes.length"
36
44
  v-for="(action,i) in coreStore.resource?.options?.bulkActions"
37
- :key="action.id"
38
- @click="startBulkAction(action.id)"
39
- class="flex gap-1 items-center py-1 px-3 text-sm font-medium text-lightListViewButtonText focus:outline-none bg-lightListViewButtonBackground rounded-default border border-lightListViewButtonBorder hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightListViewButtonFocusRing dark:focus:ring-darkListViewButtonFocusRing dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:border-darkListViewButtonBorder dark:hover:text-darkListViewButtonTextHover dark:hover:bg-darkListViewButtonBackgroundHover"
40
- :class="action.buttonCustomCssClass || ''"
41
45
  >
42
- <component
43
- v-if="action.icon && !bulkActionLoadingStates[action.id]"
44
- :is="getIcon(action.icon)"
45
- class="w-5 h-5 transition duration-75 group-hover:text-gray-900 dark:group-hover:text-white"></component>
46
- <div v-if="bulkActionLoadingStates[action.id]">
47
- <svg
48
- aria-hidden="true"
49
- class="w-5 h-5 animate-spin"
50
- :class="{
51
- 'text-gray-200 dark:text-gray-500 fill-gray-500 dark:fill-gray-300': action.state !== 'danger',
52
- 'text-red-200 dark:text-red-800 fill-red-600 dark:fill-red-500': action.state === 'danger'
53
- }"
54
- viewBox="0 0 100 101"
55
- fill="none"
56
- xmlns="http://www.w3.org/2000/svg"
57
- >
58
- <path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
59
- <path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
60
- </svg>
61
- <span class="sr-only">Loading...</span>
62
- </div>
63
- {{ `${action.label} (${checkboxes.length})` }}
64
- <div v-if="action.badge" class="text-white bg-gradient-to-r from-purple-500 via-purple-600 to-purple-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-purple-300 dark:focus:ring-purple-800
65
- font-medium rounded-sm text-xs px-1 ml-1 text-center ">
66
- {{ action.badge }}
67
- </div>
68
- </button>
46
+ <button
47
+ v-if="!action.showInThreeDotsDropdown"
48
+ :key="action.id"
49
+ @click="startBulkAction(action.id!)"
50
+ class="flex gap-1 items-center py-1 px-3 text-sm font-medium text-lightListViewButtonText focus:outline-none bg-lightListViewButtonBackground rounded-default border border-lightListViewButtonBorder hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightListViewButtonFocusRing dark:focus:ring-darkListViewButtonFocusRing dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:border-darkListViewButtonBorder dark:hover:text-darkListViewButtonTextHover dark:hover:bg-darkListViewButtonBackgroundHover"
51
+ :class="action.buttonCustomCssClass || ''"
52
+ >
53
+ <component
54
+ v-if="action.icon && !bulkActionLoadingStates[action.id!]"
55
+ :is="getIcon(action.icon)"
56
+ class="w-5 h-5 transition duration-75 group-hover:text-gray-900 dark:group-hover:text-white"></component>
57
+ <div v-if="bulkActionLoadingStates[action.id!]">
58
+ <svg
59
+ aria-hidden="true"
60
+ class="w-5 h-5 animate-spin text-gray-200 dark:text-gray-500 fill-gray-500 dark:fill-gray-300"
61
+ viewBox="0 0 100 101"
62
+ fill="none"
63
+ xmlns="http://www.w3.org/2000/svg"
64
+ >
65
+ <path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
66
+ <path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
67
+ </svg>
68
+ <span class="sr-only">Loading...</span>
69
+ </div>
70
+ {{ `${action.label} (${checkboxes.length})` }}
71
+ <div v-if="action.badge" class="text-white bg-gradient-to-r from-purple-500 via-purple-600 to-purple-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-purple-300 dark:focus:ring-purple-800
72
+ font-medium rounded-sm text-xs px-1 ml-1 text-center ">
73
+ {{ action.badge }}
74
+ </div>
75
+ </button>
76
+ </div>
69
77
 
70
78
  <RouterLink v-if="coreStore.resource?.options?.allowedActions?.create"
71
79
  :to="{ name: 'resource-create', params: { resourceId: $route.params.resourceId } }"
72
- class="af-create-button flex items-center py-1 px-3 text-sm font-medium text-lightListViewButtonText focus:outline-none bg-lightListViewButtonBackground rounded border border-lightListViewButtonBorder hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightListViewButtonFocusRing dark:focus:ring-darkListViewButtonFocusRing dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:border-darkListViewButtonBorder dark:hover:text-darkListViewButtonTextHover dark:hover:bg-darkListViewButtonBackgroundHover rounded-default"
80
+ class="af-create-button flex items-center py-1 px-3 text-sm font-medium text-lightListViewButtonText focus:outline-none bg-lightListViewButtonBackground rounded border border-lightListViewButtonBorder hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightListViewButtonFocusRing dark:focus:ring-darkListViewButtonFocusRing dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:border-darkListViewButtonBorder dark:hover:text-darkListViewButtonTextHover dark:hover:bg-darkListViewButtonBackgroundHover rounded-default gap-1"
73
81
  >
74
- <IconPlusOutline class="w-4 h-4 me-2"/>
82
+ <IconPlusOutline class="w-4 h-4"/>
75
83
  {{ $t('Create') }}
76
84
  </RouterLink>
77
85
 
@@ -80,7 +88,7 @@
80
88
  @click="()=>{filtersShow = !filtersShow}"
81
89
  v-if="coreStore.resource?.options?.allowedActions?.filter"
82
90
  >
83
- <IconFilterOutline class="w-4 h-4 me-2"/>
91
+ <IconFilterOutline class="w-4 h-4"/>
84
92
  {{ $t('Filter') }}
85
93
  <span
86
94
  class="bg-red-100 text-red-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400"
@@ -90,14 +98,19 @@
90
98
  </button>
91
99
 
92
100
  <ThreeDotsMenu
93
- :threeDotsDropdownItems="coreStore.resourceOptions?.pageInjections?.list?.threeDotsDropdownItems"
94
- ></ThreeDotsMenu>
101
+ :threeDotsDropdownItems="(coreStore.resourceOptions?.pageInjections?.list?.threeDotsDropdownItems as [])"
102
+ :bulkActions="coreStore.resource?.options?.bulkActions"
103
+ :checkboxes="checkboxes"
104
+ @startBulkAction="startBulkAction"
105
+ :updateList="getList"
106
+ :clearCheckboxes="clearCheckboxes"
107
+ ></ThreeDotsMenu>
95
108
  </BreadcrumbsWithButtons>
96
109
 
97
110
  <component
98
111
  v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.afterBreadcrumbs || []"
99
112
  :is="getCustomComponent(c)"
100
- :meta="c.meta"
113
+ :meta="(c as AdminForthComponentDeclarationFull).meta"
101
114
  :resource="coreStore.resource"
102
115
  :adminUser="coreStore.adminUser"
103
116
  />
@@ -114,12 +127,23 @@
114
127
  :pageSize="pageSize"
115
128
  :totalRows="totalRows"
116
129
  :checkboxes="checkboxes"
117
- :customActionsInjection="coreStore.resourceOptions?.pageInjections?.list?.customActionIcons"
118
- :tableBodyStartInjection="coreStore.resourceOptions?.pageInjections?.list?.tableBodyStart"
130
+ :customActionsInjection="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.customActionIcons)
131
+ ? coreStore.resourceOptions.pageInjections.list.customActionIcons
132
+ : coreStore.resourceOptions?.pageInjections?.list?.customActionIcons
133
+ ? [coreStore.resourceOptions.pageInjections.list.customActionIcons]
134
+ : []
135
+ "
136
+ :tableBodyStartInjection="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.tableBodyStart)
137
+ ? coreStore.resourceOptions.pageInjections.list.tableBodyStart
138
+ : coreStore.resourceOptions?.pageInjections?.list?.tableBodyStart
139
+ ? [coreStore.resourceOptions.pageInjections.list.tableBodyStart]
140
+ : []
141
+ "
119
142
  :container-height="1100"
120
143
  :item-height="52.5"
121
144
  :buffer-size="listBufferSize"
122
145
  />
146
+
123
147
  <ResourceListTable
124
148
  v-else
125
149
  :resource="coreStore.resource"
@@ -133,14 +157,24 @@
133
157
  :pageSize="pageSize"
134
158
  :totalRows="totalRows"
135
159
  :checkboxes="checkboxes"
136
- :customActionsInjection="coreStore.resourceOptions?.pageInjections?.list?.customActionIcons"
137
- :tableBodyStartInjection="coreStore.resourceOptions?.pageInjections?.list?.tableBodyStart"
160
+ :customActionsInjection="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.customActionIcons)
161
+ ? coreStore.resourceOptions.pageInjections.list.customActionIcons
162
+ : coreStore.resourceOptions?.pageInjections?.list?.customActionIcons
163
+ ? [coreStore.resourceOptions.pageInjections.list.customActionIcons]
164
+ : []
165
+ "
166
+ :tableBodyStartInjection="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.tableBodyStart)
167
+ ? coreStore.resourceOptions.pageInjections.list.tableBodyStart
168
+ : coreStore.resourceOptions?.pageInjections?.list?.tableBodyStart
169
+ ? [coreStore.resourceOptions.pageInjections.list.tableBodyStart]
170
+ : []
171
+ "
138
172
  />
139
173
 
140
174
  <component
141
175
  v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.bottom || []"
142
176
  :is="getCustomComponent(c)"
143
- :meta="c.meta"
177
+ :meta="(c as AdminForthComponentDeclarationFull).meta"
144
178
  :resource="coreStore.resource"
145
179
  :adminUser="coreStore.adminUser"
146
180
  />
@@ -161,6 +195,8 @@ import { showErrorTost } from '@/composables/useFrontendApi'
161
195
  import { getCustomComponent, initThreeDotsDropdown } from '@/utils';
162
196
  import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
163
197
  import { Tooltip } from '@/afcl'
198
+ import type { AdminForthComponentDeclarationFull } from '@/types/Common';
199
+
164
200
 
165
201
  import {
166
202
  IconBanOutline,
@@ -180,7 +216,7 @@ const route = useRoute();
180
216
 
181
217
  const page = ref(1);
182
218
  const columnsMinMax = ref({});
183
- const sort = ref([]);
219
+ const sort = ref();
184
220
 
185
221
  watch(() => sort, async (to, from) => {
186
222
  // in store sort might be needed for plugins
@@ -224,11 +260,11 @@ async function getList() {
224
260
  totalRows.value = 0;
225
261
  return {error: data.error};
226
262
  }
227
- rows.value = data.data?.map(row => {
228
- if (coreStore.resource.columns.find(c => c.primaryKey).foreignResource) {
229
- row._primaryKeyValue = row[coreStore.resource.columns.find(c => c.primaryKey).name].pk;
230
- } else {
231
- row._primaryKeyValue = row[coreStore.resource.columns.find(c => c.primaryKey).name];
263
+ rows.value = data.data?.map((row: any) => {
264
+ if (coreStore.resource?.columns?.find(c => c.primaryKey)?.foreignResource) {
265
+ row._primaryKeyValue = row[coreStore.resource.columns.find(c => c.primaryKey)!.name].pk;
266
+ } else if (coreStore.resource) {
267
+ row._primaryKeyValue = row[coreStore.resource.columns.find(c => c.primaryKey)!.name];
232
268
  }
233
269
  return row;
234
270
  });
@@ -240,6 +276,10 @@ async function getList() {
240
276
  return {}
241
277
  }
242
278
 
279
+ function clearCheckboxes() {
280
+ checkboxes.value = [];
281
+ }
282
+
243
283
  async function refreshExistingList(pk?: any) {
244
284
  const currentData = rows.value;
245
285
  if (!currentData) {
@@ -292,9 +332,9 @@ async function refreshExistingList(pk?: any) {
292
332
  }
293
333
 
294
334
 
295
- async function startBulkAction(actionId) {
296
- const action = coreStore.resource.options.bulkActions.find(a => a.id === actionId);
297
- if (action.confirm) {
335
+ async function startBulkAction(actionId: string) {
336
+ const action = coreStore.resource?.options?.bulkActions?.find(a => a.id === actionId);
337
+ if (action?.confirm) {
298
338
  const confirmed = await adminforth.confirm({
299
339
  message: action.confirm,
300
340
  });
@@ -334,10 +374,10 @@ async function startBulkAction(actionId) {
334
374
 
335
375
 
336
376
  class SortQuerySerializer {
337
- static serialize(sort) {
377
+ static serialize(sort: {field: string, direction: 'asc' | 'desc'}[]) {
338
378
  return sort.map(s => `${s.field}__${s.direction}`).join(',');
339
379
  }
340
- static deserialize(str) {
380
+ static deserialize(str: string) {
341
381
  return str.split(',').map(s => {
342
382
  const [field, direction] = s.split('__');
343
383
  return { field, direction };
@@ -350,7 +390,7 @@ let listAutorefresher: any = null;
350
390
  async function init() {
351
391
 
352
392
  await coreStore.fetchResourceFull({
353
- resourceId: route.params.resourceId
393
+ resourceId: route.params.resourceId as string
354
394
  });
355
395
  isPageLoaded.value = true;
356
396
  // !!! clear filters should be in same tick with sort assignment so that watch can catch it as one change
@@ -361,7 +401,7 @@ async function init() {
361
401
  return {
362
402
  field,
363
403
  operator,
364
- value: JSON.parse(decodeURIComponent(route.query[k]))
404
+ value: JSON.parse(decodeURIComponent(route.query[k] as string))
365
405
  }
366
406
  });
367
407
  if (filters.length) {
@@ -371,8 +411,8 @@ async function init() {
371
411
  }
372
412
 
373
413
  if (route.query.sort) {
374
- sort.value = SortQuerySerializer.deserialize(route.query.sort);
375
- } else if (coreStore.resource.options?.defaultSort) {
414
+ sort.value = SortQuerySerializer.deserialize(route.query.sort as string);
415
+ } else if (coreStore?.resource?.options?.defaultSort) {
376
416
  sort.value = [{
377
417
  field: coreStore.resource.options.defaultSort.columnName,
378
418
  direction: coreStore.resource.options.defaultSort.direction
@@ -382,7 +422,7 @@ async function init() {
382
422
  }
383
423
  // page init should be also in same tick
384
424
  if (route.query.page) {
385
- page.value = parseInt(route.query.page);
425
+ page.value = parseInt(route.query.page as string);
386
426
  }
387
427
 
388
428
  // getList(); - Not needed here, watch will trigger it
@@ -411,8 +451,18 @@ watch([page, sort, () => filtersStore.filters], async () => {
411
451
  }, { deep: true });
412
452
 
413
453
  adminforth.list.refresh = async () => {
414
- return await getList();
415
- }
454
+ const result = await getList();
455
+
456
+ if (!result) {
457
+ return {};
458
+ }
459
+
460
+ if ('error' in result && result.error != null) {
461
+ return { error: String(result.error) };
462
+ }
463
+
464
+ return {};
465
+ };
416
466
 
417
467
  adminforth.list.silentRefresh = async () => {
418
468
  return await refreshExistingList();
@@ -432,10 +482,10 @@ watch(() => filtersStore.filters, async (to, from) => {
432
482
  page.value = 1;
433
483
  checkboxes.value = []; // TODO: not sure absolutely needed here
434
484
  // update query param for each filter as filter_<column_name>=value
435
- const query = {};
485
+ const query: Record<string, string | undefined> = {};
436
486
  const currentQ = currentQuery();
437
487
  filtersStore.filters.forEach(f => {
438
- if (f.value) {
488
+ if (f.value !== undefined && f.value !== null && f.value !== '') {
439
489
  query[`filter__${f.field}__${f.operator}`] = encodeURIComponent(JSON.stringify(f.value));
440
490
  }
441
491
  });
@@ -92,15 +92,7 @@
92
92
  :meta="c.meta"
93
93
  />
94
94
 
95
- <div v-if="error" class="af-login-modal-error 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">
96
- <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">
97
- <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"/>
98
- </svg>
99
- <span class="sr-only">{{ $t('Info') }}</span>
100
- <div>
101
- {{ error }}
102
- </div>
103
- </div>
95
+ <ErrorMessage :error="error" />
104
96
 
105
97
  <div v-if="loginPromptHTML"
106
98
  class="flex items-center p-4 mb-4 text-sm text-lightLoginViewPromptText rounded-lg bg-lightLoginViewPromptBackground dark:bg-darkLoginViewPromptBackground dark:text-darkLoginViewPromptText" role="alert"
@@ -136,6 +128,7 @@ import { callAdminForthApi, loadFile } from '@/utils';
136
128
  import { useRoute, useRouter } from 'vue-router';
137
129
  import { Button, Checkbox, Input } from '@/afcl';
138
130
  import { useI18n } from 'vue-i18n';
131
+ import ErrorMessage from '@/components/ErrorMessage.vue';
139
132
 
140
133
  const { t } = useI18n();
141
134
 
@@ -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) {
@@ -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="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"
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 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"
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 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"
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,