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
@@ -1,13 +1,8 @@
1
1
  <template>
2
2
 
3
3
 
4
- <div class="flex items-center w-full p-4 text-gray-500 rounded-lg shadow-lg dark:text-gray-400 dark:bg-gray-800 bg-white"
4
+ <div class="flex items-center w-full p-4 rounded-lg shadow-lg dark:text-darkToastText dark:bg-darkToastBackground bg-lightToastBackground text-lightToastText"
5
5
  role="alert"
6
- :class="
7
- {
8
- 'danger': 'bg-red-100',
9
- }[toast.variant]
10
- "
11
6
  >
12
7
  <div v-if="toast.variant == 'info'" class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-lightPrimary dark:text-darkPrimary bg-lightPrimaryOpacity rounded-lg dark:bg-blue-800 dark:text-blue-200">
13
8
  <svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20">
@@ -35,8 +30,19 @@
35
30
  </div>
36
31
 
37
32
  <div class="ms-3 text-sm font-normal max-w-xs pr-2" v-if="toast.messageHtml" v-html="toast.messageHtml"></div>
38
- <div class="ms-3 text-sm font-normal max-w-xs pr-2" v-else>{{toast.message}}</div>
39
- <button @click="closeToast" type="button" class="ms-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex items-center justify-center h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700" >
33
+ <div class="ms-3 text-sm font-normal max-w-xs pr-2" v-else>
34
+ <div class="flex flex-col items-center justify-center">
35
+ {{toast.message}}
36
+ <div class="flex justify-center mt-2 gap-2">
37
+ <div v-for="button in toast.buttons" class="rounded-md bg-lightButtonsBackground hover:bg-lightButtonsHover text-lightButtonsText dark:bg-darkPrimary dark:hover:bg-darkButtonsBackground dark:text-darkButtonsText">
38
+ <button @click="onButtonClick(button.value)" class="px-2 py-1 rounded hover:bg-black/5 dark:hover:bg-white/10">
39
+ {{ button.label }}
40
+ </button>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ <button @click="closeToast" type="button" class="ms-auto -mx-1.5 -my-1.5 bg-lightToastCloseIconBackground text-lightToastCloseIcon hover:text-lightToastCloseIconHover rounded-lg focus:ring-2 focus:ring-lightToastCloseIconFocusRing p-1.5 hover:bg-lightToastCloseIconBackgroundHover inline-flex items-center justify-center h-8 w-8 dark:text-darkToastCloseIcon dark:hover:text-darkToastCloseIconHover dark:bg-darkToastCloseIconBackground dark:hover:bg-darkToastCloseIconBackgroundHover dark:focus:ring-darkToastCloseIconFocusRing" >
40
46
  <svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
41
47
  <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"/>
42
48
  </svg>
@@ -58,16 +64,28 @@ const props = defineProps<{
58
64
  variant: string;
59
65
  id: string;
60
66
  timeout?: number|'unlimited';
67
+ buttons?: { value: any; label: string }[];
61
68
  }
62
69
  }>();
63
70
  function closeToast() {
71
+ // resolve with undefined on close (X button)
72
+ toastStore.resolveToast(props.toast.id);
73
+ emit('close');
74
+ }
75
+
76
+ function onButtonClick(value: any) {
77
+ toastStore.resolveToast(props.toast.id, value);
64
78
  emit('close');
65
79
  }
66
80
 
67
81
  onMounted(() => {
68
82
  if (props.toast.timeout === 'unlimited') return;
69
83
  else {
70
- setTimeout(() => {emit('close');}, (props.toast.timeout || 10) * 1e3 );
84
+ setTimeout(() => {
85
+ // resolve with undefined on auto-timeout
86
+ toastStore.resolveToast(props.toast.id);
87
+ emit('close');
88
+ }, (props.toast.timeout || 10) * 1e3 );
71
89
  }
72
90
  });
73
91
 
@@ -12,13 +12,40 @@
12
12
  >
13
13
  <RouterLink
14
14
  class="font-medium text-lightSidebarText dark:text-darkSidebarText hover:brightness-110 whitespace-nowrap"
15
- :to="{ name: 'resource-show', params: { primaryKey: foreignResource.pk, resourceId: column.foreignResource.resourceId || column.foreignResource.polymorphicResources.find((pr) => pr.whenValue === record[column.foreignResource.polymorphicOn]).resourceId } }"
15
+ :to="{
16
+ name: 'resource-show',
17
+ params: {
18
+ primaryKey: foreignResource.pk,
19
+ resourceId: column.foreignResource
20
+ ? (
21
+ column.foreignResource.resourceId
22
+ || column.foreignResource.polymorphicResources?.find(
23
+ (pr: any) => pr.whenValue === record[column.foreignResource?.polymorphicOn!]
24
+ )?.resourceId
25
+ )
26
+ : undefined
27
+ }
28
+ }"
16
29
  >
17
30
  {{ foreignResource.label }}
18
31
  </RouterLink>
19
32
  </span>
20
33
  <RouterLink v-else-if="record[column.name]" class="font-medium text-lightPrimary dark:text-darkPrimary hover:brightness-110 whitespace-nowrap"
21
- :to="{ name: 'resource-show', params: { primaryKey: record[column.name].pk, resourceId: column.foreignResource.resourceId || column.foreignResource.polymorphicResources.find((pr) => pr.whenValue === record[column.foreignResource.polymorphicOn]).resourceId } }">
34
+ :to="{
35
+ name: 'resource-show',
36
+ params: {
37
+ primaryKey: record[column.name].pk,
38
+ resourceId: column.foreignResource
39
+ ? (
40
+ column.foreignResource.resourceId
41
+ || column.foreignResource.polymorphicResources?.find(
42
+ (pr: any) => pr.whenValue === record[column.foreignResource?.polymorphicOn!]
43
+ )?.resourceId
44
+ )
45
+ : undefined
46
+ }
47
+ }"
48
+ >
22
49
  {{ record[column.name].label }}
23
50
  </RouterLink>
24
51
  <div v-else>
@@ -27,8 +54,8 @@
27
54
  </span>
28
55
 
29
56
  <span v-else-if="column.type === 'boolean'">
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>
57
+ <span v-if="record[column.name] === true" class="af-true-value-icon 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>
58
+ <span v-else-if="record[column.name] === false" class="af-false-value-icon 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
59
  <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
60
  </span>
34
61
  <span
@@ -38,14 +65,14 @@
38
65
  <template v-for="(arrayItem, arrayItemIndex) in record[column.name]">
39
66
  <span
40
67
  v-if="column.isArray.itemType === 'boolean' && arrayItem"
41
- :key="`${column.name}-${arrayItemIndex}`"
42
- 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">
68
+ :key="`${column.name}-${arrayItemIndex}-true`"
69
+ class="af-true-value-icon 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">
43
70
  {{ $t('Yes') }}
44
71
  </span>
45
72
  <span
46
73
  v-else-if="column.isArray.itemType === 'boolean'"
47
- :key="`${column.name}-${arrayItemIndex}`"
48
- 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">
74
+ :key="`${column.name}-${arrayItemIndex}-false`"
75
+ class="af-false-value-icon 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">
49
76
  {{ $t('No') }}
50
77
  </span>
51
78
  <span
@@ -53,30 +80,30 @@
53
80
  :key="`${column.name}-${arrayItemIndex}`"
54
81
  class="rounded-md m-0.5 bg-lightAnnouncementBG dark:bg-darkAnnouncementBG text-lightAnnouncementText dark:text-darkAnnouncementText py-0.5 px-2.5 text-sm"
55
82
  >
56
- {{ checkEmptyValues(getArrayItemDisplayValue(arrayItem, column), route.meta.type) }}
83
+ {{ checkEmptyValues(getArrayItemDisplayValue(arrayItem, column), route.meta.type as "show" | "list") }}
57
84
  </span>
58
85
  </template>
59
86
  </span>
60
87
  <span v-else-if="column.enum">
61
- {{ checkEmptyValues(column.enum.find(e => e.value === record[column.name])?.label || record[column.name], route.meta.type) }}
88
+ {{ checkEmptyValues(column.enum.find(e => e.value === record[column.name])?.label || record[column.name], route.meta.type as "show" | "list") }}
62
89
  </span>
63
90
  <span v-else-if="column.type === 'datetime'" class="whitespace-nowrap">
64
- {{ checkEmptyValues(formatDateTime(record[column.name]), route.meta.type) }}
91
+ {{ checkEmptyValues(formatDateTime(record[column.name]), route.meta.type as "show" | "list") }}
65
92
  </span>
66
93
  <span v-else-if="column.type === 'date'" class="whitespace-nowrap">
67
- {{ checkEmptyValues(formatDate(record[column.name]), route.meta.type) }}
94
+ {{ checkEmptyValues(formatDate(record[column.name]), route.meta.type as "show" | "list") }}
68
95
  </span>
69
96
  <span v-else-if="column.type === 'time'" class="whitespace-nowrap">
70
- {{ checkEmptyValues(formatTime(record[column.name]), route.meta.type) }}
97
+ {{ checkEmptyValues(formatTime(record[column.name]), route.meta.type as "show" | "list") }}
71
98
  </span>
72
99
  <span v-else-if="column.type === 'decimal'">
73
- {{ checkEmptyValues(record[column.name] && parseFloat(record[column.name]), route.meta.type) }}
100
+ {{ checkEmptyValues(record[column.name] && parseFloat(record[column.name]), route.meta.type as "show" | "list") }}
74
101
  </span>
75
102
  <span v-else-if="column.type === 'json'">
76
103
  <JsonViewer class="min-w-[6rem]" :value="record[column.name]" :expandDepth="column.extra?.jsonCollapsedLevel" copyable sort :theme="coreStore.theme"/>
77
104
  </span>
78
105
  <span v-else>
79
- {{ checkEmptyValues(record[column.name],route.meta.type) }}
106
+ {{ checkEmptyValues(record[column.name], route.meta.type as "show" | "list") }}
80
107
  </span>
81
108
  </div>
82
109
  </template>
@@ -122,7 +149,7 @@ function formatTime(time: string) {
122
149
  return dayjs(`0000-00-00 ${time}`).format(coreStore.config?.timeFormat || 'HH:mm:ss');
123
150
  }
124
151
 
125
- function getArrayItemDisplayValue(value, column) {
152
+ function getArrayItemDisplayValue(value: any, column: AdminForthResourceColumnCommon) {
126
153
  if (column.isArray?.itemType === 'datetime') {
127
154
  return formatDateTime(value);
128
155
  } else if (column.isArray?.itemType === 'date') {
@@ -0,0 +1,34 @@
1
+ <template>
2
+ <Toggle
3
+ :disabled="readonly"
4
+ @update:modelValue="$emit('update:value', $event)"
5
+ :modelValue="valueFromRecord"
6
+ >
7
+ <p>{{text}}</p>
8
+ </Toggle>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import Toggle from '@/afcl/Toggle.vue';
13
+ import type {
14
+ AdminForthResourceColumnCommon,
15
+ AdminForthResourceCommon,
16
+ AdminUser,
17
+ } from "@/types/Common";
18
+
19
+ const props = defineProps<{
20
+ value: boolean,
21
+ text: string,
22
+ column: AdminForthResourceColumnCommon,
23
+ record: any,
24
+ meta: any,
25
+ resource: AdminForthResourceCommon,
26
+ adminUser: AdminUser,
27
+ readonly: boolean
28
+ }>();
29
+ console.log(JSON.stringify(props));
30
+ console.log("Current mode:", props.meta?.mode)
31
+ defineEmits(['update:value']);
32
+ const valueFromRecord = props.record[props.column.name]
33
+ const editReadOnly = props.column.editReadonly;
34
+ </script>
@@ -3,7 +3,7 @@ import { createApp } from 'vue';
3
3
 
4
4
 
5
5
  // taken from here https://vue-i18n.intlify.dev/guide/essentials/pluralization.html#custom-pluralization
6
- function slavicPluralRule(choice, choicesLength, orgRule) {
6
+ function slavicPluralRule(choice: number, choicesLength: number, orgRule: any) {
7
7
  if (choice === 0) {
8
8
  return 0
9
9
  }
@@ -0,0 +1,5 @@
1
+ declare module '*.vue' {
2
+ import type { DefineComponent } from 'vue';
3
+ const component: DefineComponent<{}, {}, any>;
4
+ export default component;
5
+ }
@@ -1,4 +1,4 @@
1
- import type { AdminForthResource, AdminForthResourceColumn } from '../types/AdminForthConfig';
1
+ import type { AdminForthResource, AdminForthResourceColumn } from '../types/Back.js';
2
2
 
3
3
  export type resourceById = {
4
4
  [key: string]: AdminForthResource;
@@ -21,6 +21,7 @@ export type ResourceColumns = {
21
21
 
22
22
  export type CoreConfig = {
23
23
  brandName: string,
24
+ singleTheme?: 'light' | 'dark',
24
25
  brandLogo: string,
25
26
  title: string,
26
27
  datesFormat: string,
@@ -33,12 +34,18 @@ export type CoreConfig = {
33
34
  passwordHashField: string,
34
35
  loginBackgroundImage: string,
35
36
  loginBackgroundPosition: string,
37
+ removeBackgroundBlendMode: boolean,
36
38
  userFullnameField: string,
37
39
  },
38
40
  emptyFieldPlaceholder?: {
39
41
  show?: string,
40
42
  list?: string,
41
43
  } | string,
44
+
45
+ customHeadItems?: {
46
+ tagName: string;
47
+ attributes: { [key: string]: string | boolean };
48
+ }[],
42
49
  }
43
50
 
44
51
 
@@ -118,7 +118,7 @@ export const useCoreStore = defineStore('core', () => {
118
118
  item.badge = badge;
119
119
  }
120
120
  });
121
-
121
+ websocket.unsubscribeAll();
122
122
  subscribeToMenuBadges();
123
123
 
124
124
  }
@@ -29,7 +29,12 @@ export const useModalStore = defineStore('modal', () => {
29
29
  onCancelFunction.value = func;
30
30
  }
31
31
  function setModalContent(content: ModalContentType) {
32
- modalContent.value = content;
32
+ modalContent.value = {
33
+ title: content.title || 'title',
34
+ content: content.content || 'content',
35
+ acceptText: content.acceptText || 'acceptText',
36
+ cancelText: content.cancelText || 'cancelText',
37
+ };
33
38
  }
34
39
  function resetmodalState() {
35
40
  isOpened.value = false;
@@ -12,19 +12,38 @@ export const useToastStore = defineStore('toast', () => {
12
12
  watch(route, () => {
13
13
  // on route change clear all toasts older then 5 seconds
14
14
  const now = +new Date();
15
- toasts.value = toasts.value.filter((t) => now - t.createdAt < 5000);
15
+ toasts.value = toasts.value.filter((t) => t?.timeout === 'unlimited' || now - t.createdAt < 5000);
16
16
  });
17
17
 
18
- const addToast = (toast: { message: string; variant: string }) => {
18
+ const addToast = (toast: {
19
+ message?: string;
20
+ messageHtml?: string;
21
+ variant: string;
22
+ timeout?: number | 'unlimited';
23
+ buttons?: { value: any; label: string }[];
24
+ onResolve?: (value?: any) => void;
25
+ }): string => {
19
26
  const toastId = uuid();
20
27
  toasts.value.push({
21
28
  ...toast,
22
29
  id: toastId,
23
30
  createdAt: +new Date(),
24
31
  });
32
+ return toastId;
25
33
  };
26
34
  const removeToast = (toast: { id: string }) => {
27
35
  toasts.value = toasts.value.filter((t) => t.id !== toast.id);
28
36
  };
29
- return { toasts, addToast, removeToast };
37
+
38
+ const resolveToast = (toastId: string, value?: any) => {
39
+ const t = toasts.value.find((x) => x.id === toastId);
40
+ try {
41
+ t?.onResolve?.(value);
42
+ } catch {
43
+ // no-op
44
+ }
45
+ toasts.value = toasts.value.filter((x) => x.id !== toastId);
46
+ };
47
+
48
+ return { toasts, addToast, removeToast, resolveToast };
30
49
  });
@@ -1,4 +1,4 @@
1
- import type { Express } from 'express';
1
+ import type { Express, Request } from 'express';
2
2
  import type { Writable } from 'stream';
3
3
 
4
4
  import { ActionCheckSource, AdminForthFilterOperators, AdminForthSortDirections, AllowedActionsEnum,
@@ -8,12 +8,12 @@ import { ActionCheckSource, AdminForthFilterOperators, AdminForthSortDirections,
8
8
  type AdminForthBulkActionCommon,
9
9
  type AdminForthForeignResourceCommon,
10
10
  type AdminForthResourceColumnCommon,
11
- AdminForthResourceInputCommon,
12
- AdminForthComponentDeclarationFull,
13
- AdminForthConfigMenuItem,
14
- AnnouncementBadgeResponse,
11
+ type AdminForthResourceInputCommon,
12
+ type AdminForthComponentDeclarationFull,
13
+ type AdminForthConfigMenuItem,
14
+ type AnnouncementBadgeResponse,
15
15
  AdminForthResourcePages,
16
- AdminForthResourceColumnInputCommon,
16
+ type AdminForthResourceColumnInputCommon,
17
17
  } from './Common.js';
18
18
 
19
19
  export interface ICodeInjector {
@@ -92,19 +92,36 @@ export interface IExpressHttpServer extends IHttpServer {
92
92
  * Adds adminUser to request object if user is authorized. Drops request with 401 status if user is not authorized.
93
93
  * @param callable : Function which will be called if user is authorized.
94
94
  *
95
- * Example:
96
95
  *
96
+ * @example
97
97
  * ```ts
98
- * expressApp.get('/myApi', authorize((req, res) => \{
98
+ * expressApp.get('/myApi', authorize((req, res) => {
99
99
  * console.log('User is authorized', req.adminUser);
100
- * res.json(\{ message: 'Hello World' \});
101
- * \}));
102
- * ``
100
+ * res.json({ message: 'Hello World' });
101
+ * }));
102
+ * ```
103
103
  *
104
- */
104
+ */
105
105
  authorize(callable: Function): void;
106
106
  }
107
107
 
108
+ export interface ITranslateFunction {
109
+ (
110
+ msg: string,
111
+ category: string,
112
+ params: any,
113
+ pluralizationNumber?: number
114
+ ): Promise<string>;
115
+ }
116
+
117
+ // Omit <Request, 'param'> is used to remove 'param' method from Request type for correct docs generation
118
+ export interface IAdminUserExpressRequest extends Omit<Request, 'protocol' | 'param' | 'unshift'> {
119
+ adminUser: AdminUser;
120
+ }
121
+
122
+ export interface ITranslateExpressRequest extends Omit<Request, 'protocol' | 'param' | 'unshift'> {
123
+ tr: ITranslateFunction;
124
+ }
108
125
 
109
126
  export interface IAdminForthSingleFilter {
110
127
  field?: string;
@@ -113,7 +130,9 @@ export interface IAdminForthSingleFilter {
113
130
  | AdminForthFilterOperators.LTE | AdminForthFilterOperators.LIKE | AdminForthFilterOperators.ILIKE
114
131
  | AdminForthFilterOperators.IN | AdminForthFilterOperators.NIN;
115
132
  value?: any;
133
+ rightField?: string;
116
134
  insecureRawSQL?: string;
135
+ insecureRawNoSQL?: any;
117
136
  }
118
137
  export interface IAdminForthAndOrFilter {
119
138
  operator: AdminForthFilterOperators.AND | AdminForthFilterOperators.OR;
@@ -336,7 +355,7 @@ export interface IAdminForth {
336
355
 
337
356
  createResourceRecord(
338
357
  params: { resource: AdminForthResource, record: any, adminUser: AdminUser, extra?: HttpExtra }
339
- ): Promise<{ error?: string, createdRecord?: any }>;
358
+ ): Promise<{ error?: string, createdRecord?: any, newRecordId?: any }>;
340
359
 
341
360
  updateResourceRecord(
342
361
  params: { resource: AdminForthResource, recordId: any, record: any, oldRecord: any, adminUser: AdminUser, extra?: HttpExtra }
@@ -474,7 +493,7 @@ export type BeforeDataSourceRequestFunction = (params: {
474
493
  requestUrl: string,
475
494
  },
476
495
  adminforth: IAdminForth,
477
- }) => Promise<{ok: boolean, error?: string}>;
496
+ }) => Promise<{ok: boolean, error?: string, newRecordId?: string}>;
478
497
 
479
498
  /**
480
499
  * Modify response to change how data is returned after fetching from database.
@@ -525,7 +544,7 @@ export type BeforeEditSaveFunction = (params: {
525
544
  oldRecord: any,
526
545
  adminforth: IAdminForth,
527
546
  extra?: HttpExtra,
528
- }) => Promise<{ok: boolean, error?: string}>;
547
+ }) => Promise<{ok: boolean, error?: string | null}>;
529
548
 
530
549
 
531
550
 
@@ -535,7 +554,7 @@ export type BeforeCreateSaveFunction = (params: {
535
554
  record: any,
536
555
  adminforth: IAdminForth,
537
556
  extra?: HttpExtra,
538
- }) => Promise<{ok: boolean, error?: string}>;
557
+ }) => Promise<{ok: boolean, error?: string | null, newRecordId?: string}>;
539
558
 
540
559
  export type AfterCreateSaveFunction = (params: {
541
560
  resource: AdminForthResource,
@@ -619,12 +638,23 @@ interface AdminForthInputConfigCustomization {
619
638
  */
620
639
  brandName?: string,
621
640
 
641
+ /**
642
+ * Whether to use single theme for the app
643
+ */
644
+ singleTheme?: 'light' | 'dark',
645
+
622
646
  /**
623
647
  * Whether to show brand name in sidebar
624
648
  * default is true
625
649
  */
626
650
  showBrandNameInSidebar?: boolean,
627
651
 
652
+ /**
653
+ * Whether to show brand logo in sidebar
654
+ * default is true
655
+ */
656
+ showBrandLogoInSidebar?: boolean,
657
+
628
658
  /**
629
659
  * Path to your app logo
630
660
  *
@@ -757,8 +787,19 @@ interface AdminForthInputConfigCustomization {
757
787
  userMenu?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
758
788
  header?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
759
789
  sidebar?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
790
+ sidebarTop?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
760
791
  everyPageBottom?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
761
792
  }
793
+
794
+ /**
795
+ * Allows adding custom elements (e.g., <link>, <script>, <meta>) to the <head> of the HTML document.
796
+ * Each item must include a tag name and a set of attributes.
797
+ */
798
+ customHeadItems?: {
799
+ tagName: string;
800
+ attributes: Record<string, string | boolean>;
801
+ }[];
802
+
762
803
  }
763
804
 
764
805
  export interface AdminForthActionInput {
@@ -935,6 +976,13 @@ export interface AdminForthInputConfig {
935
976
  */
936
977
  loginBackgroundPosition?: 'over' | '1/2' | '1/3' | '2/3' | '3/4' | '2/5' | '3/5',
937
978
 
979
+ /**
980
+ * If true, background blend mode will be removed from login background image when position is 'over'
981
+ *
982
+ * Default: false
983
+ */
984
+ removeBackgroundBlendMode?: boolean,
985
+
938
986
  /**
939
987
  * Function or functions which will be called before user try to login.
940
988
  * Each function will resive User object as an argument
@@ -956,7 +1004,7 @@ export interface AdminForthInputConfig {
956
1004
  /**
957
1005
  * Any prompt to show users on login. Supports HTML.
958
1006
  */
959
- loginPromptHTML?: string,
1007
+ loginPromptHTML?: string | (() => string | void | undefined | Promise<string | void | undefined>) | undefined
960
1008
 
961
1009
  /**
962
1010
  * Remember me days for "Remember Me" checkbox on login page.
@@ -1062,8 +1110,15 @@ export interface AdminForthConfigCustomization extends Omit<AdminForthInputConfi
1062
1110
  userMenu: Array<AdminForthComponentDeclarationFull>,
1063
1111
  header: Array<AdminForthComponentDeclarationFull>,
1064
1112
  sidebar: Array<AdminForthComponentDeclarationFull>,
1113
+ sidebarTop: Array<AdminForthComponentDeclarationFull>,
1065
1114
  everyPageBottom: Array<AdminForthComponentDeclarationFull>,
1066
1115
  },
1116
+
1117
+ customHeadItems?: {
1118
+ tagName: string;
1119
+ attributes: Record<string, string | boolean>;
1120
+ }[];
1121
+
1067
1122
  }
1068
1123
 
1069
1124
  export interface AdminForthConfig extends Omit<AdminForthInputConfig, 'customization' | 'resources'> {
@@ -1111,6 +1166,21 @@ export class Filters {
1111
1166
  static LIKE(field: string, value: any): IAdminForthSingleFilter {
1112
1167
  return { field, operator: AdminForthFilterOperators.LIKE, value };
1113
1168
  }
1169
+ static ILIKE(field: string, value: any): IAdminForthSingleFilter {
1170
+ return { field, operator: AdminForthFilterOperators.ILIKE, value };
1171
+ }
1172
+ static GT_FIELD(leftField: string, rightField: string): IAdminForthSingleFilter {
1173
+ return { field: leftField, operator: AdminForthFilterOperators.GT, rightField };
1174
+ }
1175
+ static GTE_FIELD(leftField: string, rightField: string): IAdminForthSingleFilter {
1176
+ return { field: leftField, operator: AdminForthFilterOperators.GTE, rightField };
1177
+ }
1178
+ static LT_FIELD(leftField: string, rightField: string): IAdminForthSingleFilter {
1179
+ return { field: leftField, operator: AdminForthFilterOperators.LT, rightField };
1180
+ }
1181
+ static LTE_FIELD(leftField: string, rightField: string): IAdminForthSingleFilter {
1182
+ return { field: leftField, operator: AdminForthFilterOperators.LTE, rightField };
1183
+ }
1114
1184
  static AND(
1115
1185
  ...args: (IAdminForthSingleFilter | IAdminForthAndOrFilter | Array<IAdminForthSingleFilter | IAdminForthAndOrFilter>)[]
1116
1186
  ): IAdminForthAndOrFilter {
@@ -1318,9 +1388,13 @@ export interface AdminForthResource extends Omit<AdminForthResourceInput, 'optio
1318
1388
  },
1319
1389
  create?: {
1320
1390
  /**
1391
+ * Should return `ok: true` to continue saving pipeline and allow creating record in database, and `ok: false` to interrupt pipeline and prevent record creation.
1392
+ * If you need to show error on UI, set `error: \<error message\>` in response.
1393
+ *
1321
1394
  * Typical use-cases:
1322
- * - Validate record before saving to database and interrupt execution if validation failed (`allowedActions.create` should be preferred in most cases)
1323
- * - fill-in adminUser as creator of record
1395
+ * - Create record by custom code (return `{ ok: false, newRecordId: <id of created record from custom code> }`)
1396
+ * - 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
1397
+ * - fill-in adminUser as creator of record (set `record.<some field> = x; return \{ ok: true \}`)
1324
1398
  * - Attach additional data to record before saving to database (mostly fillOnCreate should be used instead)
1325
1399
  */
1326
1400
  beforeSave?: Array<BeforeCreateSaveFunction>,
@@ -1471,15 +1545,27 @@ export type ShowInInput = ShowInModernInput | ShowInLegacyInput;
1471
1545
  export type ShowIn = {
1472
1546
  [key in AdminForthResourcePages]: AllowedActionValue
1473
1547
  }
1548
+ export type BackendOnlyInput =
1549
+ | boolean
1550
+ | ((p: {
1551
+ adminUser: AdminUser;
1552
+ resource: AdminForthResource;
1553
+ meta: any;
1554
+ source: ActionCheckSource;
1555
+ adminforth: IAdminForth;
1556
+ }) => boolean | Promise<boolean>);
1557
+
1474
1558
 
1475
- export interface AdminForthResourceColumnInput extends Omit<AdminForthResourceColumnInputCommon, 'showIn'> {
1559
+ export interface AdminForthResourceColumnInput extends Omit<AdminForthResourceColumnInputCommon, 'showIn' | 'backendOnly'> {
1476
1560
  showIn?: ShowInInput,
1477
1561
  foreignResource?: AdminForthForeignResource,
1562
+ backendOnly?: BackendOnlyInput;
1478
1563
  }
1479
1564
 
1480
- export interface AdminForthResourceColumn extends Omit<AdminForthResourceColumnCommon, 'showIn'> {
1565
+ export interface AdminForthResourceColumn extends Omit<AdminForthResourceColumnCommon, 'showIn' | 'backendOnly'> {
1481
1566
  showIn?: ShowIn,
1482
1567
  foreignResource?: AdminForthForeignResource,
1568
+ backendOnly?: BackendOnlyInput;
1483
1569
  }
1484
1570
 
1485
1571
  export interface IWebSocketClient {