adminforth 2.4.0-next.12 → 2.4.0-next.121

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 (145) 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 +8 -1
  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/dist/dataConnectors/baseConnector.d.ts.map +1 -1
  9. package/dist/dataConnectors/baseConnector.js +16 -3
  10. package/dist/dataConnectors/baseConnector.js.map +1 -1
  11. package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
  12. package/dist/dataConnectors/clickhouse.js +4 -0
  13. package/dist/dataConnectors/clickhouse.js.map +1 -1
  14. package/dist/dataConnectors/mongo.d.ts.map +1 -1
  15. package/dist/dataConnectors/mongo.js +14 -14
  16. package/dist/dataConnectors/mongo.js.map +1 -1
  17. package/dist/index.d.ts +2 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +20 -9
  20. package/dist/index.js.map +1 -1
  21. package/dist/modules/codeInjector.d.ts.map +1 -1
  22. package/dist/modules/codeInjector.js +22 -5
  23. package/dist/modules/codeInjector.js.map +1 -1
  24. package/dist/modules/configValidator.d.ts.map +1 -1
  25. package/dist/modules/configValidator.js +48 -1
  26. package/dist/modules/configValidator.js.map +1 -1
  27. package/dist/modules/restApi.d.ts.map +1 -1
  28. package/dist/modules/restApi.js +145 -25
  29. package/dist/modules/restApi.js.map +1 -1
  30. package/dist/modules/styles.d.ts +451 -13
  31. package/dist/modules/styles.d.ts.map +1 -1
  32. package/dist/modules/styles.js +507 -31
  33. package/dist/modules/styles.js.map +1 -1
  34. package/dist/modules/utils.d.ts +1 -0
  35. package/dist/modules/utils.d.ts.map +1 -1
  36. package/dist/modules/utils.js +9 -0
  37. package/dist/modules/utils.js.map +1 -1
  38. package/dist/spa/index.html +1 -1
  39. package/dist/spa/src/App.vue +25 -15
  40. package/dist/spa/src/adminforth.ts +10 -0
  41. package/dist/spa/src/afcl/Button.vue +6 -6
  42. package/dist/spa/src/afcl/Checkbox.vue +21 -13
  43. package/dist/spa/src/afcl/CountryFlag.vue +4 -1
  44. package/dist/spa/src/{components/CustomDatePicker.vue → afcl/DatePicker.vue} +95 -9
  45. package/dist/spa/src/afcl/Dialog.vue +43 -26
  46. package/dist/spa/src/afcl/Dropzone.vue +10 -10
  47. package/dist/spa/src/afcl/Input.vue +9 -7
  48. package/dist/spa/src/afcl/JsonViewer.vue +25 -0
  49. package/dist/spa/src/afcl/Link.vue +1 -1
  50. package/dist/spa/src/afcl/LinkButton.vue +1 -1
  51. package/dist/spa/src/afcl/ProgressBar.vue +7 -7
  52. package/dist/spa/src/afcl/Select.vue +60 -27
  53. package/dist/spa/src/afcl/Skeleton.vue +6 -6
  54. package/dist/spa/src/afcl/Table.vue +13 -13
  55. package/dist/spa/src/afcl/Textarea.vue +31 -0
  56. package/dist/spa/src/afcl/Toggle.vue +32 -0
  57. package/dist/spa/src/afcl/Tooltip.vue +2 -3
  58. package/dist/spa/src/afcl/VerticalTabs.vue +3 -3
  59. package/dist/spa/src/afcl/index.ts +4 -3
  60. package/dist/spa/src/components/AcceptModal.vue +7 -7
  61. package/dist/spa/src/components/Breadcrumbs.vue +5 -5
  62. package/dist/spa/src/components/ColumnValueInput.vue +37 -18
  63. package/dist/spa/src/components/ColumnValueInputWrapper.vue +4 -3
  64. package/dist/spa/src/components/CustomDateRangePicker.vue +9 -8
  65. package/dist/spa/src/components/CustomRangePicker.vue +37 -8
  66. package/dist/spa/src/components/Filters.vue +83 -37
  67. package/dist/spa/src/components/GroupsTable.vue +9 -8
  68. package/dist/spa/src/components/MenuLink.vue +3 -3
  69. package/dist/spa/src/components/ResourceForm.vue +94 -51
  70. package/dist/spa/src/components/ResourceListTable.vue +38 -40
  71. package/dist/spa/src/components/ResourceListTableVirtual.vue +31 -33
  72. package/dist/spa/src/components/ShowTable.vue +17 -12
  73. package/dist/spa/src/components/SingleSkeletLoader.vue +6 -6
  74. package/dist/spa/src/components/SkeleteLoader.vue +1 -1
  75. package/dist/spa/src/components/ThreeDotsMenu.vue +49 -7
  76. package/dist/spa/src/components/Toast.vue +2 -7
  77. package/dist/spa/src/components/ValueRenderer.vue +4 -4
  78. package/dist/spa/src/controls/BoolToggle.vue +34 -0
  79. package/dist/spa/src/i18n.ts +1 -1
  80. package/dist/spa/src/shims-vue.d.ts +5 -0
  81. package/dist/spa/src/spa_types/core.ts +7 -0
  82. package/dist/spa/src/stores/core.ts +1 -1
  83. package/dist/spa/src/types/Back.ts +82 -21
  84. package/dist/spa/src/types/Common.ts +25 -10
  85. package/dist/spa/src/types/FrontendAPI.ts +8 -0
  86. package/dist/spa/src/types/adapters/CompletionAdapter.ts +25 -0
  87. package/dist/spa/src/types/adapters/EmailAdapter.ts +27 -0
  88. package/dist/spa/src/types/adapters/ImageGenerationAdapter.ts +50 -0
  89. package/dist/spa/src/types/adapters/ImageVisionAdapter.ts +30 -0
  90. package/dist/spa/src/types/adapters/OAuth2Adapter.ts +34 -0
  91. package/dist/spa/src/types/adapters/StorageAdapter.ts +73 -0
  92. package/dist/spa/src/types/adapters/index.ts +6 -0
  93. package/dist/spa/src/utils.ts +217 -7
  94. package/dist/spa/src/views/CreateView.vue +21 -18
  95. package/dist/spa/src/views/EditView.vue +29 -19
  96. package/dist/spa/src/views/ListView.vue +60 -51
  97. package/dist/spa/src/views/LoginView.vue +50 -47
  98. package/dist/spa/src/views/ResourceParent.vue +1 -1
  99. package/dist/spa/src/views/ShowView.vue +34 -13
  100. package/dist/spa/src/websocket.ts +6 -1
  101. package/dist/spa/tsconfig.app.json +1 -1
  102. package/dist/spa/vite.config.ts +44 -1
  103. package/dist/types/Back.d.ts +61 -14
  104. package/dist/types/Back.d.ts.map +1 -1
  105. package/dist/types/Back.js.map +1 -1
  106. package/dist/types/Common.d.ts +23 -8
  107. package/dist/types/Common.d.ts.map +1 -1
  108. package/dist/types/Common.js.map +1 -1
  109. package/dist/types/FrontendAPI.d.ts +7 -0
  110. package/dist/types/FrontendAPI.d.ts.map +1 -1
  111. package/dist/types/FrontendAPI.js.map +1 -1
  112. package/dist/types/adapters/CompletionAdapter.d.ts +20 -0
  113. package/dist/types/adapters/CompletionAdapter.d.ts.map +1 -0
  114. package/dist/types/adapters/CompletionAdapter.js +2 -0
  115. package/dist/types/adapters/CompletionAdapter.js.map +1 -0
  116. package/dist/types/adapters/EmailAdapter.d.ts +20 -0
  117. package/dist/types/adapters/EmailAdapter.d.ts.map +1 -0
  118. package/dist/types/adapters/EmailAdapter.js +2 -0
  119. package/dist/types/adapters/EmailAdapter.js.map +1 -0
  120. package/dist/types/adapters/ImageGenerationAdapter.d.ts +37 -0
  121. package/dist/types/adapters/ImageGenerationAdapter.d.ts.map +1 -0
  122. package/dist/types/adapters/ImageGenerationAdapter.js +2 -0
  123. package/dist/types/adapters/ImageGenerationAdapter.js.map +1 -0
  124. package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
  125. package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
  126. package/dist/types/adapters/ImageVisionAdapter.js +2 -0
  127. package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
  128. package/dist/types/adapters/OAuth2Adapter.d.ts +32 -0
  129. package/dist/types/adapters/OAuth2Adapter.d.ts.map +1 -0
  130. package/dist/types/adapters/OAuth2Adapter.js +2 -0
  131. package/dist/types/adapters/OAuth2Adapter.js.map +1 -0
  132. package/dist/types/adapters/StorageAdapter.d.ts +63 -0
  133. package/dist/types/adapters/StorageAdapter.d.ts.map +1 -0
  134. package/dist/types/adapters/StorageAdapter.js +2 -0
  135. package/dist/types/adapters/StorageAdapter.js.map +1 -0
  136. package/dist/types/adapters/index.d.ts +7 -0
  137. package/dist/types/adapters/index.d.ts.map +1 -0
  138. package/dist/types/adapters/index.js +2 -0
  139. package/dist/types/adapters/index.js.map +1 -0
  140. package/package.json +2 -2
  141. package/dist/spa/src/types/Adapters.ts +0 -213
  142. package/dist/types/Adapters.d.ts +0 -168
  143. package/dist/types/Adapters.d.ts.map +0 -1
  144. package/dist/types/Adapters.js +0 -2
  145. package/dist/types/Adapters.js.map +0 -1
@@ -1,10 +1,12 @@
1
1
  <template>
2
- <div class="overflow-x-auto rounded-default shadow-resourseFormShadow dark:shadow-darkResourseFormShadow">
3
- <div v-if="groupName && !noTitle" class="text-md font-semibold px-6 py-3 flex flex-1 items-center dark:border-gray-600 text-gray-700 bg-lightFormHeading dark:bg-gray-700 dark:text-gray-400 rounded-t-lg">
2
+ <div class="overflow-x-auto shadow-resourseFormShadow dark:shadow-darkResourseFormShadow"
3
+ :class="{'rounded-default' : isRounded}"
4
+ >
5
+ <div v-if="groupName && !noTitle" class="text-md font-semibold px-6 py-3 flex flex-1 items-center text-lightShowTableHeadingText bg-lightShowTableHeadingBackground dark:bg-darkShowTableHeadingBackground dark:text-darkShowTableHeadingText rounded-t-lg">
4
6
  {{ groupName }}
5
7
  </div>
6
- <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 table-fixed">
7
- <thead v-if="!allColumnsHaveCustomComponent" class="text-gray-700 dark:text-gray-400 bg-lightFormHeading dark:bg-gray-700 block md:table-row-group">
8
+ <table class="w-full text-sm text-left rtl:text-right text-lightShowTableBodyText dark:text-darkShowTableBodyText table-fixed">
9
+ <thead v-if="!allColumnsHaveCustomComponent" class="text-lightShowTableUnderHeadingText dark:text-darkShowTableUnderHeadingText bg-lightShowTableUnderHeadingBackground dark:bg-darkShowTableUnderHeadingBackground dark:border-darkFormBorder block md:table-row-group">
8
10
  <tr>
9
11
  <th scope="col" class="px-6 py-3 text-xs uppercase hidden md:w-52 md:table-cell">
10
12
  {{ $t('Field') }}
@@ -18,8 +20,8 @@
18
20
  <tr
19
21
  v-for="column in columns"
20
22
  :key="column.name"
21
- class="bg-lightForm border-t border-gray-100
22
- dark:bg-gray-800 dark:border-gray-700 block md:table-row"
23
+ class="bg-lightShowTablesBodyBackground border-t border-lightShowTableBodyBorder
24
+ dark:bg-darkShowTablesBodyBackground dark:border-darkShowTableBodyBorder block md:table-row"
23
25
  >
24
26
  <component
25
27
  v-if="column.components?.showRow"
@@ -60,10 +62,11 @@
60
62
  import { getCustomComponent } from '@/utils';
61
63
  import { useCoreStore } from '@/stores/core';
62
64
  import { computed } from 'vue';
63
- const props = defineProps<{
65
+ import type { AdminForthResourceCommon } from '@/types/Common';
66
+ const props = withDefaults(defineProps<{
64
67
  columns: Array<{
65
68
  name: string;
66
- label: string;
69
+ label?: string;
67
70
  components?: {
68
71
  show?: {
69
72
  file: string;
@@ -75,13 +78,15 @@
75
78
  };
76
79
  };
77
80
  }>;
78
- source: string;
79
81
  groupName?: string | null;
80
82
  noTitle?: boolean;
81
- resource: Record<string, any>;
83
+ resource: AdminForthResourceCommon | null;
82
84
  record: Record<string, any>;
83
- }>();
84
-
85
+ isRounded?: boolean;
86
+ }>(), {
87
+ isRounded: true
88
+ });
89
+
85
90
  const coreStore = useCoreStore();
86
91
  const allColumnsHaveCustomComponent = computed(() => {
87
92
  return props.columns.every(column => {
@@ -2,12 +2,12 @@
2
2
  <div
3
3
  role="status" class="max-w-sm animate-pulse"
4
4
  >
5
- <div class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4"></div>
6
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px] mb-2.5"></div>
7
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"></div>
8
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[330px] mb-2.5"></div>
9
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[300px] mb-2.5"></div>
10
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px]"></div>
5
+ <div class="h-2.5 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor w-48 mb-4"></div>
6
+ <div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor max-w-[360px] mb-2.5"></div>
7
+ <div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor mb-2.5"></div>
8
+ <div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor max-w-[330px] mb-2.5"></div>
9
+ <div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor max-w-[300px] mb-2.5"></div>
10
+ <div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor max-w-[360px]"></div>
11
11
  <span class="sr-only">{{ $t('Loading...') }}</span>
12
12
  </div>
13
13
  </template>
@@ -12,7 +12,7 @@
12
12
  : '']"
13
13
  >
14
14
  <div role="status" class="max-w-sm animate-pulse">
15
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px]"></div>
15
+ <div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor max-w-[360px]"></div>
16
16
  </div>
17
17
  </td>
18
18
  </tr>
@@ -1,8 +1,8 @@
1
1
  <template >
2
- <template v-if="threeDotsDropdownItems?.length || customActions?.length">
2
+ <template v-if="threeDotsDropdownItems?.length || customActions?.length || (bulkActions?.some(action => action.showInThreeDotsDropdown))">
3
3
  <button
4
4
  data-dropdown-toggle="listThreeDotsDropdown"
5
- class="flex items-center py-2 px-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"
5
+ class="flex items-center py-2 px-2 text-sm font-medium text-lightThreeDotsMenuIconDots focus:outline-none bg-lightThreeDotsMenuIconBackground rounded border border-lightThreeDotsMenuIconBackgroundBorder hover:bg-lightThreeDotsMenuIconBackgroundHover hover:text-lightThreeDotsMenuIconDotsHover focus:z-10 focus:ring-4 focus:ring-lightThreeDotsMenuIconFocus dark:focus:ring-darkThreeDotsMenuIconFocus dark:bg-darkThreeDotsMenuIconBackground dark:text-darkThreeDotsMenuIconDots dark:border-darkThreeDotsMenuIconBackgroundBorder dark:hover:text-darkThreeDotsMenuIconDotsHover dark:hover:bg-darkThreeDotsMenuIconBackgroundHover rounded-default"
6
6
  >
7
7
  <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 4 15">
8
8
  <path d="M3.5 1.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm0 6.041a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm0 5.959a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z"/>
@@ -12,19 +12,28 @@
12
12
  <!-- Dropdown menu -->
13
13
  <div
14
14
  id="listThreeDotsDropdown"
15
- class="z-20 hidden bg-white divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-gray-700 dark:divide-gray-600">
16
- <ul class="py-2 text-sm text-gray-700 dark:text-gray-200" aria-labelledby="dropdownMenuIconButton">
15
+ class="z-20 hidden bg-lightThreeDotsMenuBodyBackground divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-darkThreeDotsMenuBodyBackground dark:divide-gray-600">
16
+ <ul class="py-2 text-sm text-lightThreeDotsMenuBodyText dark:text-darkThreeDotsMenuBodyText" aria-labelledby="dropdownMenuIconButton">
17
17
  <li v-for="item in threeDotsDropdownItems" :key="`dropdown-item-${item.label}`">
18
- <a href="#" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
18
+ <a href="#"
19
+ class="block px-4 py-2 hover:bg-lightThreeDotsMenuBodyBackgroundHover hover:text-lightThreeDotsMenuBodyTextHover dark:hover:bg-darkThreeDotsMenuBodyBackgroundHover dark:hover:text-darkThreeDotsMenuBodyTextHover"
20
+ :class="{
21
+ 'pointer-events-none': checkboxes && checkboxes.length === 0 && item.meta?.disabledWhenNoCheckboxes,
22
+ 'opacity-50': checkboxes && checkboxes.length === 0 && item.meta?.disabledWhenNoCheckboxes,
23
+ 'cursor-not-allowed': checkboxes && checkboxes.length === 0 && item.meta?.disabledWhenNoCheckboxes,
24
+ }">
19
25
  <component :is="getCustomComponent(item)"
20
26
  :meta="item.meta"
21
27
  :resource="coreStore.resource"
22
28
  :adminUser="coreStore.adminUser"
29
+ :checkboxes="checkboxes"
30
+ :updateList="props.updateList"
31
+ :clearCheckboxes="clearCheckboxes"
23
32
  />
24
33
  </a>
25
34
  </li>
26
35
  <li v-for="action in customActions" :key="action.id">
27
- <a href="#" @click.prevent="handleActionClick(action)" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
36
+ <a href="#" @click.prevent="handleActionClick(action)" class="block px-4 py-2 hover:text-lightThreeDotsMenuBodyTextHover hover:bg-lightThreeDotsMenuBodyBackgroundHover dark:hover:bg-darkThreeDotsMenuBodyBackgroundHover dark:hover:text-darkThreeDotsMenuBodyTextHover">
28
37
  <div class="flex items-center gap-2">
29
38
  <component
30
39
  v-if="action.icon"
@@ -35,6 +44,24 @@
35
44
  </div>
36
45
  </a>
37
46
  </li>
47
+ <li v-for="action in bulkActions.filter(a => a.showInThreeDotsDropdown)" :key="action.id">
48
+ <a href="#" @click.prevent="startBulkAction(action.id)"
49
+ class="block px-4 py-2 hover:text-lightThreeDotsMenuBodyTextHover hover:bg-lightThreeDotsMenuBodyBackgroundHover dark:hover:bg-darkThreeDotsMenuBodyBackgroundHover dark:hover:text-darkThreeDotsMenuBodyTextHover"
50
+ :class="{
51
+ 'pointer-events-none': !checkboxes.length,
52
+ 'opacity-50': !checkboxes.length,
53
+ 'cursor-not-allowed': !checkboxes.length
54
+ }">
55
+ <div class="flex items-center gap-2">
56
+ <component
57
+ v-if="action.icon"
58
+ :is="getIcon(action.icon)"
59
+ class="w-4 h-4 text-lightPrimary dark:text-darkPrimary"
60
+ />
61
+ {{ action.label }}
62
+ </div>
63
+ </a>
64
+ </li>
38
65
  </ul>
39
66
  </div>
40
67
  </template>
@@ -54,9 +81,19 @@ const router = useRouter();
54
81
 
55
82
  const props = defineProps({
56
83
  threeDotsDropdownItems: Array,
57
- customActions: Array
84
+ customActions: Array,
85
+ bulkActions: Array,
86
+ checkboxes: Array,
87
+ updateList: {
88
+ type: Function,
89
+ },
90
+ clearCheckboxes: {
91
+ type: Function
92
+ }
58
93
  });
59
94
 
95
+ const emit = defineEmits(['startBulkAction']);
96
+
60
97
  async function handleActionClick(action) {
61
98
  adminforth.list.closeThreeDotsDropdown();
62
99
 
@@ -108,4 +145,9 @@ async function handleActionClick(action) {
108
145
  });
109
146
  }
110
147
  }
148
+
149
+ function startBulkAction(actionId) {
150
+ adminforth.list.closeThreeDotsDropdown();
151
+ emit('startBulkAction', actionId);
152
+ }
111
153
  </script>
@@ -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">
@@ -36,7 +31,7 @@
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
33
  <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" >
34
+ <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
35
  <svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
41
36
  <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
37
  </svg>
@@ -27,8 +27,8 @@
27
27
  </span>
28
28
 
29
29
  <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>
30
+ <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>
31
+ <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
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
@@ -39,13 +39,13 @@
39
39
  <span
40
40
  v-if="column.isArray.itemType === 'boolean' && arrayItem"
41
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">
42
+ 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
43
  {{ $t('Yes') }}
44
44
  </span>
45
45
  <span
46
46
  v-else-if="column.isArray.itemType === 'boolean'"
47
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">
48
+ 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
49
  {{ $t('No') }}
50
50
  </span>
51
51
  <span
@@ -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
+ }
@@ -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
  }
@@ -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;
@@ -336,7 +353,7 @@ export interface IAdminForth {
336
353
 
337
354
  createResourceRecord(
338
355
  params: { resource: AdminForthResource, record: any, adminUser: AdminUser, extra?: HttpExtra }
339
- ): Promise<{ error?: string, createdRecord?: any }>;
356
+ ): Promise<{ error?: string, createdRecord?: any, newRecordId?: any }>;
340
357
 
341
358
  updateResourceRecord(
342
359
  params: { resource: AdminForthResource, recordId: any, record: any, oldRecord: any, adminUser: AdminUser, extra?: HttpExtra }
@@ -474,7 +491,7 @@ export type BeforeDataSourceRequestFunction = (params: {
474
491
  requestUrl: string,
475
492
  },
476
493
  adminforth: IAdminForth,
477
- }) => Promise<{ok: boolean, error?: string}>;
494
+ }) => Promise<{ok: boolean, error?: string, newRecordId?: string}>;
478
495
 
479
496
  /**
480
497
  * Modify response to change how data is returned after fetching from database.
@@ -525,7 +542,7 @@ export type BeforeEditSaveFunction = (params: {
525
542
  oldRecord: any,
526
543
  adminforth: IAdminForth,
527
544
  extra?: HttpExtra,
528
- }) => Promise<{ok: boolean, error?: string}>;
545
+ }) => Promise<{ok: boolean, error?: string | null}>;
529
546
 
530
547
 
531
548
 
@@ -535,7 +552,7 @@ export type BeforeCreateSaveFunction = (params: {
535
552
  record: any,
536
553
  adminforth: IAdminForth,
537
554
  extra?: HttpExtra,
538
- }) => Promise<{ok: boolean, error?: string}>;
555
+ }) => Promise<{ok: boolean, error?: string | null, newRecordId?: string}>;
539
556
 
540
557
  export type AfterCreateSaveFunction = (params: {
541
558
  resource: AdminForthResource,
@@ -619,6 +636,11 @@ interface AdminForthInputConfigCustomization {
619
636
  */
620
637
  brandName?: string,
621
638
 
639
+ /**
640
+ * Whether to use single theme for the app
641
+ */
642
+ singleTheme?: 'light' | 'dark',
643
+
622
644
  /**
623
645
  * Whether to show brand name in sidebar
624
646
  * default is true
@@ -759,6 +781,16 @@ interface AdminForthInputConfigCustomization {
759
781
  sidebar?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
760
782
  everyPageBottom?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
761
783
  }
784
+
785
+ /**
786
+ * Allows adding custom elements (e.g., <link>, <script>, <meta>) to the <head> of the HTML document.
787
+ * Each item must include a tag name and a set of attributes.
788
+ */
789
+ customHeadItems?: {
790
+ tagName: string;
791
+ attributes: Record<string, string | boolean>;
792
+ }[];
793
+
762
794
  }
763
795
 
764
796
  export interface AdminForthActionInput {
@@ -935,6 +967,13 @@ export interface AdminForthInputConfig {
935
967
  */
936
968
  loginBackgroundPosition?: 'over' | '1/2' | '1/3' | '2/3' | '3/4' | '2/5' | '3/5',
937
969
 
970
+ /**
971
+ * If true, background blend mode will be removed from login background image when position is 'over'
972
+ *
973
+ * Default: false
974
+ */
975
+ removeBackgroundBlendMode?: boolean,
976
+
938
977
  /**
939
978
  * Function or functions which will be called before user try to login.
940
979
  * Each function will resive User object as an argument
@@ -956,7 +995,7 @@ export interface AdminForthInputConfig {
956
995
  /**
957
996
  * Any prompt to show users on login. Supports HTML.
958
997
  */
959
- loginPromptHTML?: string,
998
+ loginPromptHTML?: string | (() => string | void | undefined | Promise<string | void | undefined>) | undefined
960
999
 
961
1000
  /**
962
1001
  * Remember me days for "Remember Me" checkbox on login page.
@@ -1064,6 +1103,12 @@ export interface AdminForthConfigCustomization extends Omit<AdminForthInputConfi
1064
1103
  sidebar: Array<AdminForthComponentDeclarationFull>,
1065
1104
  everyPageBottom: Array<AdminForthComponentDeclarationFull>,
1066
1105
  },
1106
+
1107
+ customHeadItems?: {
1108
+ tagName: string;
1109
+ attributes: Record<string, string | boolean>;
1110
+ }[];
1111
+
1067
1112
  }
1068
1113
 
1069
1114
  export interface AdminForthConfig extends Omit<AdminForthInputConfig, 'customization' | 'resources'> {
@@ -1318,9 +1363,13 @@ export interface AdminForthResource extends Omit<AdminForthResourceInput, 'optio
1318
1363
  },
1319
1364
  create?: {
1320
1365
  /**
1366
+ * Should return `ok: true` to continue saving pipeline and allow creating record in database, and `ok: false` to interrupt pipeline and prevent record creation.
1367
+ * If you need to show error on UI, set `error: \<error message\>` in response.
1368
+ *
1321
1369
  * 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
1370
+ * - Create record by custom code (return `{ ok: false, newRecordId: <id of created record from custom code> }`)
1371
+ * - 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
1372
+ * - fill-in adminUser as creator of record (set `record.<some field> = x; return \{ ok: true \}`)
1324
1373
  * - Attach additional data to record before saving to database (mostly fillOnCreate should be used instead)
1325
1374
  */
1326
1375
  beforeSave?: Array<BeforeCreateSaveFunction>,
@@ -1471,15 +1520,27 @@ export type ShowInInput = ShowInModernInput | ShowInLegacyInput;
1471
1520
  export type ShowIn = {
1472
1521
  [key in AdminForthResourcePages]: AllowedActionValue
1473
1522
  }
1523
+ export type BackendOnlyInput =
1524
+ | boolean
1525
+ | ((p: {
1526
+ adminUser: AdminUser;
1527
+ resource: AdminForthResource;
1528
+ meta: any;
1529
+ source: ActionCheckSource;
1530
+ adminforth: IAdminForth;
1531
+ }) => boolean | Promise<boolean>);
1532
+
1474
1533
 
1475
- export interface AdminForthResourceColumnInput extends Omit<AdminForthResourceColumnInputCommon, 'showIn'> {
1534
+ export interface AdminForthResourceColumnInput extends Omit<AdminForthResourceColumnInputCommon, 'showIn' | 'backendOnly'> {
1476
1535
  showIn?: ShowInInput,
1477
1536
  foreignResource?: AdminForthForeignResource,
1537
+ backendOnly?: BackendOnlyInput;
1478
1538
  }
1479
1539
 
1480
- export interface AdminForthResourceColumn extends Omit<AdminForthResourceColumnCommon, 'showIn'> {
1540
+ export interface AdminForthResourceColumn extends Omit<AdminForthResourceColumnCommon, 'showIn' | 'backendOnly'> {
1481
1541
  showIn?: ShowIn,
1482
1542
  foreignResource?: AdminForthForeignResource,
1543
+ backendOnly?: BackendOnlyInput;
1483
1544
  }
1484
1545
 
1485
1546
  export interface IWebSocketClient {
@@ -95,12 +95,9 @@ export interface AdminForthBulkActionCommon {
95
95
  label: string,
96
96
 
97
97
  /**
98
- * Bulk Action button state 'danger'|success|'active',
99
- * * 'danger' - red button
100
- * * 'success' - green button
101
- * * 'active' - blue button
98
+ * Add custom class
102
99
  **/
103
- state?: 'danger' | 'success' | 'active';
100
+ buttonCustomCssClass?: string;
104
101
 
105
102
  /**
106
103
  * Optional small badge for button which will be displayed in the list view
@@ -122,6 +119,10 @@ export interface AdminForthBulkActionCommon {
122
119
  */
123
120
  successMessage?: string,
124
121
 
122
+ /**
123
+ * Show in three dots dropdown menu in list view.
124
+ */
125
+ showInThreeDotsDropdown?: boolean,
125
126
  }
126
127
 
127
128
  export interface AdminForthFieldComponents {
@@ -584,6 +585,8 @@ export interface AdminForthForeignResourceCommon {
584
585
  polymorphicResources?: Array<AdminForthPolymorphicForeignResource>,
585
586
  polymorphicOn?: string,
586
587
  unsetLabel?: string,
588
+ searchableFields?: string | string[],
589
+ searchIsCaseSensitive?: boolean,
587
590
  }
588
591
 
589
592
  export type FillOnCreateFunction = (params: {
@@ -810,9 +813,6 @@ export interface AdminForthResourceColumnInputCommon {
810
813
  */
811
814
  minLength?: number,
812
815
 
813
- min?: number,
814
- max?: number,
815
-
816
816
  /**
817
817
  * Minimum value that can be entered in this field.
818
818
  */
@@ -878,6 +878,15 @@ export interface AdminForthResourceColumnCommon extends AdminForthResourceColumn
878
878
 
879
879
  editingNote?: { create?: string, edit?: string },
880
880
 
881
+ /**
882
+ * Minimal value stored in this field.
883
+ */
884
+ min?: number,
885
+
886
+ /**
887
+ * Maximum value stored in this field.
888
+ */
889
+ max?: number,
881
890
  }
882
891
 
883
892
  export enum AdminForthMenuTypes {
@@ -1055,9 +1064,10 @@ export interface AdminForthConfigForFrontend {
1055
1064
  usernameFieldName: string,
1056
1065
  loginBackgroundImage: string,
1057
1066
  loginBackgroundPosition: string,
1067
+ removeBackgroundBlendMode: boolean,
1058
1068
  title?: string,
1059
1069
  demoCredentials?: string,
1060
- loginPromptHTML?: string,
1070
+ loginPromptHTML?: string | (() => string | Promise<string> | void | Promise<void> | Promise<undefined>) | undefined
1061
1071
  loginPageInjections: {
1062
1072
  underInputs: Array<AdminForthComponentDeclaration>,
1063
1073
  panelHeader: Array<AdminForthComponentDeclaration>,
@@ -1065,6 +1075,7 @@ export interface AdminForthConfigForFrontend {
1065
1075
  rememberMeDays: number,
1066
1076
  showBrandNameInSidebar: boolean,
1067
1077
  brandLogo?: string,
1078
+ singleTheme?: 'light' | 'dark',
1068
1079
  datesFormat: string,
1069
1080
  timeFormat: string,
1070
1081
  auth: any,
@@ -1080,7 +1091,11 @@ export interface AdminForthConfigForFrontend {
1080
1091
  header: Array<AdminForthComponentDeclarationFull>,
1081
1092
  sidebar: Array<AdminForthComponentDeclarationFull>,
1082
1093
  everyPageBottom: Array<AdminForthComponentDeclarationFull>,
1083
- }
1094
+ },
1095
+ customHeadItems?: {
1096
+ tagName: string;
1097
+ attributes: Record<string, string | boolean>;
1098
+ }[],
1084
1099
  }
1085
1100
 
1086
1101
  export interface GetBaseConfigResponse {
@@ -121,6 +121,14 @@ export interface FrontendAPIInterface {
121
121
  clearFilters(): void;
122
122
  }
123
123
 
124
+ show: {
125
+ /**
126
+ * Full refresh the current record on the show page. Loader may be shown during fetching.
127
+ * Fire-and-forget; you don't need to await it.
128
+ */
129
+ refresh(): void;
130
+ }
131
+
124
132
  menu: {
125
133
  /**
126
134
  * Refreshes the badges in the menu, by recalling the badge function for each menu item