adminforth 2.4.0-next.4 → 2.4.0-next.41

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 (84) hide show
  1. package/commands/cli.js +12 -4
  2. package/commands/createApp/templates/custom/tsconfig.json.hbs +2 -3
  3. package/commands/createApp/templates/package.json.hbs +1 -1
  4. package/commands/createApp/utils.js +40 -7
  5. package/commands/createCustomComponent/configUpdater.js +25 -21
  6. package/commands/createCustomComponent/fileGenerator.js +1 -1
  7. package/commands/createCustomComponent/main.js +2 -1
  8. package/commands/createCustomComponent/templates/login/beforeLogin.vue.hbs +18 -0
  9. package/dist/dataConnectors/mongo.d.ts.map +1 -1
  10. package/dist/dataConnectors/mongo.js +1 -8
  11. package/dist/dataConnectors/mongo.js.map +1 -1
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/modules/codeInjector.js +5 -6
  17. package/dist/modules/codeInjector.js.map +1 -1
  18. package/dist/modules/configValidator.d.ts.map +1 -1
  19. package/dist/modules/configValidator.js +2 -1
  20. package/dist/modules/configValidator.js.map +1 -1
  21. package/dist/spa/src/App.vue +1 -1
  22. package/dist/spa/src/afcl/Button.vue +4 -4
  23. package/dist/spa/src/afcl/Checkbox.vue +1 -1
  24. package/dist/spa/src/afcl/Dropzone.vue +4 -2
  25. package/dist/spa/src/afcl/Input.vue +5 -3
  26. package/dist/spa/src/afcl/JsonViewer.vue +19 -0
  27. package/dist/spa/src/afcl/Link.vue +1 -1
  28. package/dist/spa/src/afcl/LinkButton.vue +1 -1
  29. package/dist/spa/src/afcl/Select.vue +1 -2
  30. package/dist/spa/src/afcl/Table.vue +8 -8
  31. package/dist/spa/src/afcl/Tooltip.vue +1 -1
  32. package/dist/spa/src/afcl/index.ts +1 -1
  33. package/dist/spa/src/components/AcceptModal.vue +4 -4
  34. package/dist/spa/src/components/Filters.vue +1 -1
  35. package/dist/spa/src/components/ResourceListTable.vue +13 -13
  36. package/dist/spa/src/components/ResourceListTableVirtual.vue +4 -4
  37. package/dist/spa/src/components/ValueRenderer.vue +1 -1
  38. package/dist/spa/src/types/Back.ts +3 -1
  39. package/dist/spa/src/types/Common.ts +1 -0
  40. package/dist/spa/src/types/adapters/CompletionAdapter.ts +25 -0
  41. package/dist/spa/src/types/adapters/EmailAdapter.ts +29 -0
  42. package/dist/spa/src/types/adapters/ImageGenerationAdapter.ts +50 -0
  43. package/dist/spa/src/types/adapters/OAuth2Adapter.ts +34 -0
  44. package/dist/spa/src/types/adapters/StorageAdapter.ts +73 -0
  45. package/dist/spa/src/types/adapters/index.ts +5 -0
  46. package/dist/spa/src/views/CreateView.vue +2 -2
  47. package/dist/spa/src/views/ListView.vue +2 -2
  48. package/dist/spa/src/views/LoginView.vue +40 -26
  49. package/dist/spa/src/views/ShowView.vue +3 -3
  50. package/dist/types/Back.d.ts +3 -1
  51. package/dist/types/Back.d.ts.map +1 -1
  52. package/dist/types/Back.js.map +1 -1
  53. package/dist/types/Common.d.ts +1 -0
  54. package/dist/types/Common.d.ts.map +1 -1
  55. package/dist/types/adapters/CompletionAdapter.d.ts +20 -0
  56. package/dist/types/adapters/CompletionAdapter.d.ts.map +1 -0
  57. package/dist/types/adapters/CompletionAdapter.js +2 -0
  58. package/dist/types/adapters/CompletionAdapter.js.map +1 -0
  59. package/dist/types/adapters/EmailAdapter.d.ts +21 -0
  60. package/dist/types/adapters/EmailAdapter.d.ts.map +1 -0
  61. package/dist/types/adapters/EmailAdapter.js +2 -0
  62. package/dist/types/adapters/EmailAdapter.js.map +1 -0
  63. package/dist/types/adapters/ImageGenerationAdapter.d.ts +37 -0
  64. package/dist/types/adapters/ImageGenerationAdapter.d.ts.map +1 -0
  65. package/dist/types/adapters/ImageGenerationAdapter.js +2 -0
  66. package/dist/types/adapters/ImageGenerationAdapter.js.map +1 -0
  67. package/dist/types/adapters/OAuth2Adapter.d.ts +32 -0
  68. package/dist/types/adapters/OAuth2Adapter.d.ts.map +1 -0
  69. package/dist/types/adapters/OAuth2Adapter.js +2 -0
  70. package/dist/types/adapters/OAuth2Adapter.js.map +1 -0
  71. package/dist/types/adapters/StorageAdapter.d.ts +63 -0
  72. package/dist/types/adapters/StorageAdapter.d.ts.map +1 -0
  73. package/dist/types/adapters/StorageAdapter.js +2 -0
  74. package/dist/types/adapters/StorageAdapter.js.map +1 -0
  75. package/dist/types/adapters/index.d.ts +6 -0
  76. package/dist/types/adapters/index.d.ts.map +1 -0
  77. package/dist/types/adapters/index.js +2 -0
  78. package/dist/types/adapters/index.js.map +1 -0
  79. package/package.json +2 -2
  80. package/dist/spa/src/types/Adapters.ts +0 -213
  81. package/dist/types/Adapters.d.ts +0 -168
  82. package/dist/types/Adapters.d.ts.map +0 -1
  83. package/dist/types/Adapters.js +0 -2
  84. package/dist/types/Adapters.js.map +0 -1
@@ -8,7 +8,7 @@ const modalStore = useModalStore();
8
8
  <Teleport to="body">
9
9
  <div v-if="modalStore.isOpened" class="bg-gray-900/50 dark:bg-gray-900/80 overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-full max-h-full">
10
10
  <div class="relative p-4 w-full max-w-md max-h-full top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 " >
11
- <div class="relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black">
11
+ <div class="afcl-confirmation-container relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black">
12
12
  <button type="button"@click="modalStore.togleModal" class="absolute top-3 end-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white" >
13
13
  <svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
14
14
  <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
@@ -19,11 +19,11 @@ const modalStore = useModalStore();
19
19
  <svg class="mx-auto mb-4 text-gray-400 w-12 h-12 dark:text-gray-200" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
20
20
  <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11V6m0 8h.01M19 10a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
21
21
  </svg>
22
- <h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">{{ modalStore?.modalContent?.content }}</h3>
23
- <button @click="()=>{ modalStore.onAcceptFunction(true);modalStore.togleModal()}" type="button" class="text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:focus:ring-red-800 font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center">
22
+ <h3 class="afcl-confirmation-title mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">{{ modalStore?.modalContent?.content }}</h3>
23
+ <button @click="()=>{ modalStore.onAcceptFunction(true);modalStore.togleModal()}" type="button" class="afcl-confirmation-accept-button text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:focus:ring-red-800 font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center">
24
24
  {{ modalStore?.modalContent?.acceptText }}
25
25
  </button>
26
- <button @click="()=>{modalStore.onAcceptFunction(false);modalStore.togleModal()}" type="button" class="py-2.5 px-5 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 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">{{ modalStore?.modalContent?.cancelText }}</button>
26
+ <button @click="()=>{modalStore.onAcceptFunction(false);modalStore.togleModal()}" type="button" class="afcl-confirmation-cancel-button py-2.5 px-5 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 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">{{ modalStore?.modalContent?.cancelText }}</button>
27
27
  </div>
28
28
  </div>
29
29
  </div>
@@ -2,7 +2,7 @@
2
2
  <!-- drawer component -->
3
3
  <div id="drawer-navigation"
4
4
 
5
- class="fixed right-0 z-50 p-4 overflow-y-auto transition-transform translate-x-full bg-white w-80 dark:bg-gray-800 shadow-xl dark:shadow-gray-900"
5
+ class="af-filters-sidebar fixed right-0 z-50 p-4 overflow-y-auto transition-transform translate-x-full bg-white w-80 dark:bg-gray-800 shadow-xl dark:shadow-gray-900"
6
6
 
7
7
  :class="show ? 'top-0 transform-none' : ''"
8
8
  tabindex="-1" aria-labelledby="drawer-navigation-label"
@@ -128,7 +128,7 @@
128
128
  }"
129
129
 
130
130
  >
131
- <IconEyeSolid class="w-5 h-5 me-2"/>
131
+ <IconEyeSolid class="af-show-icon w-5 h-5 me-2"/>
132
132
  </RouterLink>
133
133
 
134
134
  <template v-slot:tooltip>
@@ -147,7 +147,7 @@
147
147
  }
148
148
  }"
149
149
  >
150
- <IconPenSolid class="w-5 h-5 me-2"/>
150
+ <IconPenSolid class="af-edit-icon w-5 h-5 me-2"/>
151
151
  </RouterLink>
152
152
  <template v-slot:tooltip>
153
153
  {{ $t('Edit item') }}
@@ -159,7 +159,7 @@
159
159
  v-if="resource.options?.allowedActions.delete"
160
160
  @click="deleteRecord(row)"
161
161
  >
162
- <IconTrashBinSolid class="w-5 h-5 me-2"/>
162
+ <IconTrashBinSolid class="af-delete-icon w-5 h-5 me-2"/>
163
163
  </button>
164
164
 
165
165
  <template v-slot:tooltip>
@@ -199,14 +199,14 @@
199
199
  <!-- pagination
200
200
  totalRows in v-if is used to not hide page input during loading when user puts cursor into it and edit directly (rows gets null there during edit)
201
201
  -->
202
- <div class="flex flex-row items-center mt-4 xs:flex-row xs:justify-between xs:items-center gap-3"
203
- v-if="(rows || totalRows) && totalRows >= pageSize && totalRows > 0"
204
- >
202
+ <div class="af-pagination-container flex flex-row items-center mt-4 xs:flex-row xs:justify-between xs:items-center gap-3">
205
203
 
206
- <div class="inline-flex ">
204
+ <div class="af-pagination-buttons-container inline-flex "
205
+ v-if="(rows || totalRows) && totalRows >= pageSize && totalRows > 0"
206
+ >
207
207
  <!-- Buttons -->
208
208
  <button
209
- class="flex items-center py-1 px-3 gap-1 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 rounded-s 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 disabled:opacity-50"
209
+ class="af-pagination-prev-button flex items-center py-1 px-3 gap-1 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 rounded-s 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 disabled:opacity-50"
210
210
  @click="page--; pageInput = page.toString();" :disabled="page <= 1">
211
211
  <svg class="w-3.5 h-3.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
212
212
  viewBox="0 0 14 10">
@@ -218,14 +218,14 @@
218
218
  </span>
219
219
  </button>
220
220
  <button
221
- class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 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 disabled:opacity-50"
221
+ class="af-pagination-first-page-button flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 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 disabled:opacity-50"
222
222
  @click="page = 1; pageInput = page.toString();" :disabled="page <= 1">
223
223
  <!-- <IconChevronDoubleLeftOutline class="w-4 h-4" /> -->
224
224
  1
225
225
  </button>
226
226
  <div
227
227
  contenteditable="true"
228
- class="min-w-10 outline-none inline-block w-auto min-w-10 py-1.5 px-3 text-sm text-center text-gray-700 border border-gray-300 dark:border-gray-700 dark:text-gray-400 dark:bg-gray-800 z-10"
228
+ class="af-pagination-input min-w-10 outline-none inline-block w-auto min-w-10 py-1.5 px-3 text-sm text-center text-gray-700 border border-gray-300 dark:border-gray-700 dark:text-gray-400 dark:bg-gray-800 z-10"
229
229
  @keydown="onPageKeydown($event)"
230
230
  @input="onPageInput($event)"
231
231
  @blur="validatePageInput()"
@@ -234,14 +234,14 @@
234
234
  </div>
235
235
 
236
236
  <button
237
- class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-l-0 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 disabled:opacity-50"
237
+ class="af-pagination-last-page-button flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-l-0 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 disabled:opacity-50"
238
238
  @click="page = totalPages; pageInput = page.toString();" :disabled="page >= totalPages">
239
239
  {{ totalPages }}
240
240
 
241
241
  <!-- <IconChevronDoubleRightOutline class="w-4 h-4" /> -->
242
242
  </button>
243
243
  <button
244
- class="flex items-center py-1 px-3 gap-1 text-sm font-medium text-gray-900 focus:outline-none bg-white border-l-0 rounded-e 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 disabled:opacity-50"
244
+ class="af-pagination-next-button flex items-center py-1 px-3 gap-1 text-sm font-medium text-gray-900 focus:outline-none bg-white border-l-0 rounded-e 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 disabled:opacity-50"
245
245
  @click="page++; pageInput = page.toString();" :disabled="page >= totalPages">
246
246
  <span class="hidden sm:inline">{{ $t('Next') }}</span>
247
247
  <svg class="w-3.5 h-3.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
@@ -257,7 +257,7 @@
257
257
  <span v-if="((page || 1) - 1) * pageSize + 1 > totalRows">{{ $t('Wrong Page') }} </span>
258
258
  <template v-else>
259
259
 
260
- <span class="hidden sm:inline">
260
+ <span class="af-pagination-info hidden sm:inline">
261
261
  <i18n-t keypath="Showing {from} to {to} of {total} Entries" tag="p" >
262
262
  <template v-slot:from>
263
263
  <strong>{{ from }}</strong>
@@ -216,11 +216,11 @@
216
216
  <!-- pagination
217
217
  totalRows in v-if is used to not hide page input during loading when user puts cursor into it and edit directly (rows gets null there during edit)
218
218
  -->
219
- <div class="flex flex-row items-center mt-4 xs:flex-row xs:justify-between xs:items-center gap-3"
220
- v-if="(rows || totalRows) && totalRows >= pageSize && totalRows > 0"
221
- >
219
+ <div class="flex flex-row items-center mt-4 xs:flex-row xs:justify-between xs:items-center gap-3">
222
220
 
223
- <div class="inline-flex ">
221
+ <div class="inline-flex "
222
+ v-if="(rows || totalRows) && totalRows >= pageSize && totalRows > 0"
223
+ >
224
224
  <!-- Buttons -->
225
225
  <button
226
226
  class="flex items-center py-1 px-3 gap-1 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 rounded-s 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 disabled:opacity-50"
@@ -28,7 +28,7 @@
28
28
 
29
29
  <span v-else-if="column.type === 'boolean'">
30
30
  <span v-if="record[column.name] === true" class="bg-green-100 whitespace-nowrap text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400">{{ $t('Yes') }}</span>
31
- <span v-else-if="record[column.name] === false" class="bg-red-100 whitespace-nowrap text-red-800gg 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">{{ $t('No') }}</span>
31
+ <span v-else-if="record[column.name] === false" class="bg-red-100 whitespace-nowrap 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">{{ $t('No') }}</span>
32
32
  <span v-else class="bg-gray-100 whitespace-nowrap text-gray-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-gray-400 border border-gray-400">{{ $t('Unset') }}</span>
33
33
  </span>
34
34
  <span
@@ -99,7 +99,7 @@ export interface IExpressHttpServer extends IHttpServer {
99
99
  * console.log('User is authorized', req.adminUser);
100
100
  * res.json(\{ message: 'Hello World' \});
101
101
  * \}));
102
- * ``
102
+ * ```
103
103
  *
104
104
  */
105
105
  authorize(callable: Function): void;
@@ -747,6 +747,7 @@ interface AdminForthInputConfigCustomization {
747
747
  */
748
748
  loginPageInjections?: {
749
749
  underInputs?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
750
+ panelHeader?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
750
751
  }
751
752
 
752
753
  /**
@@ -1054,6 +1055,7 @@ export interface AdminForthConfigCustomization extends Omit<AdminForthInputConfi
1054
1055
 
1055
1056
  loginPageInjections: {
1056
1057
  underInputs: Array<AdminForthComponentDeclarationFull>,
1058
+ panelHeader: Array<AdminForthComponentDeclarationFull>,
1057
1059
  },
1058
1060
 
1059
1061
  globalInjections: {
@@ -1060,6 +1060,7 @@ export interface AdminForthConfigForFrontend {
1060
1060
  loginPromptHTML?: string,
1061
1061
  loginPageInjections: {
1062
1062
  underInputs: Array<AdminForthComponentDeclaration>,
1063
+ panelHeader: Array<AdminForthComponentDeclaration>,
1063
1064
  },
1064
1065
  rememberMeDays: number,
1065
1066
  showBrandNameInSidebar: boolean,
@@ -0,0 +1,25 @@
1
+ export interface CompletionAdapter {
2
+
3
+ /**
4
+ * This method is called to validate the configuration of the adapter
5
+ * and should throw a clear user-readbale error if the configuration is invalid.
6
+ */
7
+ validate(): void;
8
+
9
+ /**
10
+ * This method should return a text completion based on the provided content and stop sequence.
11
+ * @param content - The input text to complete
12
+ * @param stop - An array of stop sequences to indicate where to stop the completion
13
+ * @param maxTokens - The maximum number of tokens to generate
14
+ * @returns A promise that resolves to an object containing the completed text and other metadata
15
+ */
16
+ complete(
17
+ content: string,
18
+ stop: string[],
19
+ maxTokens: number,
20
+ ): Promise<{
21
+ content?: string;
22
+ finishReason?: string;
23
+ error?: string;
24
+ }>;
25
+ }
@@ -0,0 +1,29 @@
1
+ interface EmailAdapter {
2
+
3
+ /**
4
+ * This method is called to validate the configuration of the adapter
5
+ * and should throw a clear user-readbale error if the configuration is invalid.
6
+ */
7
+ validate(): Promise<void>;
8
+
9
+ /**
10
+ * This method should send an email using the adapter
11
+ * @param from - The sender's email address
12
+ * @param to - The recipient's email address
13
+ * @param text - The plain text version of the email
14
+ * @param html - The HTML version of the email
15
+ * @param subject - The subject of the email
16
+ */
17
+ sendEmail(
18
+ from: string,
19
+ to: string,
20
+ text: string,
21
+ html: string,
22
+ subject: string
23
+ ): Promise<{
24
+ error?: string;
25
+ ok?: boolean;
26
+ }>;
27
+ }
28
+
29
+ export { EmailAdapter };
@@ -0,0 +1,50 @@
1
+ export interface ImageGenerationAdapter {
2
+
3
+ /**
4
+ * This method is called to validate the configuration of the adapter
5
+ * and should throw a clear user-readbale error if the configuration is invalid.
6
+ */
7
+ validate(): void;
8
+
9
+ /**
10
+ * Return max number of images which model can generate in one request
11
+ */
12
+ outputImagesMaxCountSupported(): number;
13
+
14
+ /**
15
+ * Return the list of supported dimensions in format ["100x500", "200x200"]
16
+ */
17
+ outputDimensionsSupported(): string[];
18
+
19
+ /**
20
+ * Input file extension supported
21
+ */
22
+ inputFileExtensionSupported(): string[];
23
+
24
+ /**
25
+ * This method should generate an image based on the provided prompt and input files.
26
+ * @param prompt - The prompt to generate the image
27
+ * @param inputFiles - An array of input file paths (optional)
28
+ * @param n - The number of images to generate (default is 1)
29
+ * @param size - The size of the generated image (default is the lowest dimension supported)
30
+ * @returns A promise that resolves to an object containing the generated image URLs and any error message
31
+ */
32
+ generate({
33
+ prompt,
34
+ inputFiles,
35
+ n,
36
+ size,
37
+ }: {
38
+ prompt: string,
39
+ inputFiles: string[],
40
+
41
+ // default = lowest dimension supported
42
+ size?: string,
43
+
44
+ // one by default
45
+ n?: number
46
+ }): Promise<{
47
+ imageURLs?: string[];
48
+ error?: string;
49
+ }>;
50
+ }
@@ -0,0 +1,34 @@
1
+
2
+ /**
3
+ * This interface is used to implement OAuth2 authentication adapters.
4
+ */
5
+ export interface OAuth2Adapter {
6
+ /**
7
+ * This method should return navigatable URL to the OAuth2 provider authentication page.
8
+ */
9
+ getAuthUrl(): string;
10
+
11
+ /**
12
+ * This method should return the token from the OAuth2 provider using the provided code and redirect URI.
13
+ * @param code - The authorization code received from the OAuth2 provider
14
+ * @param redirect_uri - The redirect URI used in the authentication request
15
+ * @returns A promise that resolves to an object containing the email address of the authenticated user
16
+ */
17
+ getTokenFromCode(code: string, redirect_uri: string): Promise<{ email: string }>;
18
+
19
+ /**
20
+ * This method should return text (content) of SVG icon which will be used in the UI.
21
+ * Use official SVG icons with simplest possible conent, omit icons which have base64 encoded raster images inside.
22
+ */
23
+ getIcon(): string;
24
+
25
+ /**
26
+ * This method should return the text to be displayed on the button in the UI
27
+ */
28
+ getButtonText?(): string;
29
+
30
+ /**
31
+ * This method should return the name of the adapter
32
+ */
33
+ getName?(): string;
34
+ }
@@ -0,0 +1,73 @@
1
+
2
+ /**
3
+ * Each storage adapter should support two ways of storing files:
4
+ * - publically (public URL) - the file can be accessed by anyone by HTTP GET / HEAD request with plain URL
5
+ * - privately (presigned URL) - the file can be accessed by anyone by HTTP GET / HEAD request only with presigned URLs, limited by expiration time
6
+ *
7
+ */
8
+ export interface StorageAdapter {
9
+ /**
10
+ * This method should return the presigned URL for the given key capable of upload (adapter user will call PUT multipart form data to this URL within expiresIn seconds after link generation).
11
+ * By default file which will be uploaded on PUT should be marked for deletion. So if during 24h it is not marked for not deletion, it adapter should delete it forever.
12
+ * The PUT method should fail if the file already exists.
13
+ *
14
+ * Adapter user will always pass next parameters to the method:
15
+ * @param key - The key of the file to be uploaded e.g. "uploads/file.txt"
16
+ * @param expiresIn - The expiration time in seconds for the presigned URL
17
+ * @param contentType - The content type of the file to be uploaded, e.g. "image/png"
18
+ *
19
+ * @returns A promise that resolves to an object containing the upload URL and any extra parameters which should be sent with PUT multipart form data
20
+ */
21
+ getUploadSignedUrl(key: string, contentType: string, expiresIn?: number): Promise<{
22
+ uploadUrl: string;
23
+ uploadExtraParams?: Record<string, string>;
24
+ }>;
25
+
26
+ /**
27
+ * This method should return the URL for the given key capable of download (200 GET request with response body or 200 HEAD request without response body).
28
+ * If adapter configured to store objects publically, this method should return the public URL of the file.
29
+ * If adapter configured to no allow public storing of images, this method should return the presigned URL for the file.
30
+ *
31
+ * @param key - The key of the file to be downloaded e.g. "uploads/file.txt"
32
+ * @param expiresIn - The expiration time in seconds for the presigned URL
33
+ */
34
+ getDownloadUrl(key: string, expiresIn?: number): Promise<string>;
35
+
36
+ /**
37
+ * This method should mark the file for deletion.
38
+ * If file is marked for delation and exists more then 24h (since creation date) it should be deleted.
39
+ * This method should work even if the file does not exist yet (e.g. only presigned URL was generated).
40
+ * @param key - The key of the file to be uploaded e.g. "uploads/file.txt"
41
+ */
42
+ markKeyForDeletation(key: string): Promise<void>;
43
+
44
+
45
+ /**
46
+ * This method should mark the file to not be deleted.
47
+ * This method should be used to cancel the deletion of the file if it was marked for deletion.
48
+ * @param key - The key of the file to be uploaded e.g. "uploads/file.txt"
49
+ */
50
+ markKeyForNotDeletation(key: string): Promise<void>;
51
+
52
+
53
+ /**
54
+ * This method can start needed schedullers, cron jobs, etc. to clean up the storage.
55
+ * @param adapterUserUniqueRepresentation - The unique representation of the plugin instance which
56
+ * wil use this adapter. Might be handy if you need to distinguish between different instances of the same adapter.
57
+ */
58
+ setupLifecycle(adapterUserUniqueRepresentation: string): Promise<void>;
59
+
60
+ /**
61
+ * If adapter is configured to publically, this method should return true.
62
+ */
63
+ objectCanBeAccesedPublicly(): Promise<boolean>;
64
+
65
+ /**
66
+ * This method should return the key as a data URL (base64 encoded string).
67
+ * @param key - The key of the file to be converted to a data URL
68
+ * @returns A promise that resolves to a string containing the data URL
69
+ */
70
+ getKeyAsDataURL(key: string): Promise<string>;
71
+ }
72
+
73
+
@@ -0,0 +1,5 @@
1
+ export type { EmailAdapter } from './EmailAdapter.js';
2
+ export type { CompletionAdapter } from './CompletionAdapter.js';
3
+ export type { ImageGenerationAdapter } from './ImageGenerationAdapter.js';
4
+ export type { OAuth2Adapter } from './OAuth2Adapter.js';
5
+ export type { StorageAdapter } from './StorageAdapter.js';
@@ -13,14 +13,14 @@
13
13
  <BreadcrumbsWithButtons>
14
14
  <!-- save and cancle -->
15
15
  <button @click="$router.back()"
16
- class="flex items-center py-1 px-3 me-2 text-sm font-medium rounded-default 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"
16
+ class="af-cancel-button flex items-center py-1 px-3 me-2 text-sm font-medium rounded-default 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"
17
17
  >
18
18
  {{ $t('Cancel') }}
19
19
  </button>
20
20
 
21
21
  <button
22
22
  @click="saveRecord"
23
- class="flex items-center py-1 px-3 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-red-500 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
23
+ class="af-save-button flex items-center py-1 px-3 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-red-500 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
24
24
  :disabled="saving || (validating && !isValid)"
25
25
  >
26
26
  <svg v-if="saving"
@@ -74,14 +74,14 @@
74
74
 
75
75
  <RouterLink v-if="coreStore.resource?.options?.allowedActions?.create"
76
76
  :to="{ name: 'resource-create', params: { resourceId: $route.params.resourceId } }"
77
- 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"
77
+ class="af-create-button 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"
78
78
  >
79
79
  <IconPlusOutline class="w-4 h-4 me-2"/>
80
80
  {{ $t('Create') }}
81
81
  </RouterLink>
82
82
 
83
83
  <button
84
- class="flex gap-1 items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-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"
84
+ class="af-filter-button flex gap-1 items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-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"
85
85
  @click="()=>{filtersShow = !filtersShow}"
86
86
  v-if="coreStore.resource?.options?.allowedActions?.filter"
87
87
  >
@@ -23,23 +23,33 @@
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-white rounded-lg shadow dark:bg-gray-700 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-gray-900 dark:text-white">
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
50
  <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
51
+ <Input
52
+ v-model="username"
43
53
  autocomplete="username"
44
54
  type="username"
45
55
  name="username"
@@ -47,20 +57,25 @@
47
57
  ref="usernameInput"
48
58
  oninput="setCustomValidity('')"
49
59
  @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 />
60
+ class="w-full"
61
+ placeholder="name@company.com" required />
51
62
  </div>
52
- <div class="relative">
63
+ <div class="">
53
64
  <label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{ $t('Your password') }}</label>
54
- <input
65
+ <Input
66
+ v-model="password"
55
67
  ref="passwordInput"
56
68
  autocomplete="current-password"
57
69
  oninput="setCustomValidity('')"
58
70
  @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>
71
+ :type="!showPw ? 'password': 'text'" name="password" id="password" placeholder="••••••••" class="w-full" required>
72
+ <template #rightIcon>
73
+ <button type="button" @click="showPw = !showPw" class="text-gray-400 dark:text-gray-300">
74
+ <IconEyeSolid class="w-5 h-5" v-if="!showPw" />
75
+ <IconEyeSlashSolid class="w-5 h-5" v-else />
76
+ </button>
77
+ </template>
78
+ </Input>
64
79
  </div>
65
80
 
66
81
  <div v-if="coreStore.config.rememberMeDays"
@@ -79,7 +94,7 @@
79
94
  :meta="c.meta"
80
95
  />
81
96
 
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">
97
+ <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">
83
98
  <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
99
  <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
100
  </svg>
@@ -121,7 +136,7 @@ import { useUserStore } from '@/stores/user';
121
136
  import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
122
137
  import { callAdminForthApi, loadFile } from '@/utils';
123
138
  import { useRoute, useRouter } from 'vue-router';
124
- import { Button, Checkbox } from '@/afcl';
139
+ import { Button, Checkbox, Input } from '@/afcl';
125
140
  import { useI18n } from 'vue-i18n';
126
141
 
127
142
  const { t } = useI18n();
@@ -129,6 +144,8 @@ const { t } = useI18n();
129
144
  const passwordInput = ref(null);
130
145
  const usernameInput = ref(null);
131
146
  const rememberMeValue= ref(false);
147
+ const username = ref('');
148
+ const password = ref('');
132
149
 
133
150
  const route = useRoute();
134
151
  const router = useRouter();
@@ -159,9 +176,9 @@ onBeforeMount(() => {
159
176
 
160
177
  onMounted(async () => {
161
178
  if (coreStore.config?.demoCredentials) {
162
- const [username, password] = coreStore.config.demoCredentials.split(':');
163
- usernameInput.value.value = username;
164
- passwordInput.value.value = password;
179
+ const [demoUsername, demoPassword] = coreStore.config.demoCredentials.split(':');
180
+ username.value = demoUsername;
181
+ password.value = demoPassword;
165
182
  }
166
183
  usernameInput.value.focus();
167
184
  });
@@ -169,14 +186,11 @@ onMounted(async () => {
169
186
 
170
187
  async function login() {
171
188
 
172
- const username = usernameInput.value.value;
173
- const password = passwordInput.value.value;
174
-
175
- if (!username) {
189
+ if (!username.value) {
176
190
  usernameInput.value.setCustomValidity(t('Please fill out this field.'));
177
191
  return;
178
192
  }
179
- if (!password) {
193
+ if (!password.value) {
180
194
  passwordInput.value.setCustomValidity(t('Please fill out this field.'));
181
195
  return;
182
196
  }
@@ -189,8 +203,8 @@ async function login() {
189
203
  path: '/login',
190
204
  method: 'POST',
191
205
  body: {
192
- username,
193
- password,
206
+ username: username.value,
207
+ password: password.value,
194
208
  rememberMe: rememberMeValue.value,
195
209
  }
196
210
  });
@@ -28,21 +28,21 @@
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-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"
32
32
  >
33
33
  <IconPlusOutline class="w-4 h-4 me-2"/>
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-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"
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-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"
46
46
  >
47
47
  <IconTrashBinSolid class="w-4 h-4" />
48
48
  {{ $t('Delete') }}