adminforth 1.1.74 → 1.1.75

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 (115) hide show
  1. package/auth.ts +14 -0
  2. package/dataConnectors/postgres.ts +1 -1
  3. package/dist/auth.js +7 -0
  4. package/dist/index.js +117 -65
  5. package/dist/modules/codeInjector.js +29 -15
  6. package/dist/plugins/AuditLogPlugin/index.js +2 -17
  7. package/dist/plugins/S3UploadPlugin/custom/s3uploader.vue +120 -0
  8. package/dist/plugins/S3UploadPlugin/index.js +103 -0
  9. package/dist/plugins/S3UploadPlugin/package-lock.json +431 -0
  10. package/dist/plugins/S3UploadPlugin/package.json +15 -0
  11. package/dist/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsConfirmation.vue +152 -0
  12. package/dist/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsSetup.vue +188 -0
  13. package/dist/plugins/TwoFactorsAuthPlugin/index.js +150 -0
  14. package/dist/plugins/TwoFactorsAuthPlugin/types.js +1 -0
  15. package/dist/spa/index.html +1 -1
  16. package/dist/spa/package-lock.json +111 -0
  17. package/dist/spa/package.json +1 -0
  18. package/dist/spa/src/App.vue +178 -133
  19. package/dist/spa/src/components/MenuLink.vue +3 -3
  20. package/dist/spa/src/components/ResourceForm.vue +96 -96
  21. package/dist/spa/src/components/ResourceListTable.vue +69 -84
  22. package/dist/spa/src/components/ValueRenderer.vue +2 -3
  23. package/dist/spa/src/composables/useStores.ts +24 -11
  24. package/dist/spa/src/main.ts +1 -1
  25. package/dist/spa/src/router/index.ts +0 -1
  26. package/dist/spa/src/utils.ts +1 -1
  27. package/dist/spa/src/views/CreateView.vue +1 -9
  28. package/dist/spa/src/views/EditView.vue +1 -9
  29. package/dist/spa/src/views/ListView.vue +12 -3
  30. package/dist/spa/src/views/LoginView.vue +6 -1
  31. package/dist/spa/src/views/ResourceParent.vue +1 -2
  32. package/dist/spa/src/views/ShowView.vue +6 -14
  33. package/dist/spa/tailwind.config.js +3 -1
  34. package/index.ts +94 -35
  35. package/modules/codeInjector.ts +38 -27
  36. package/package.json +2 -1
  37. package/plugins/AuditLogPlugin/index.ts +4 -20
  38. package/plugins/S3UploadPlugin/custom/s3uploader.vue +120 -0
  39. package/plugins/S3UploadPlugin/index.ts +112 -0
  40. package/plugins/S3UploadPlugin/package-lock.json +431 -0
  41. package/plugins/S3UploadPlugin/package.json +15 -0
  42. package/plugins/S3UploadPlugin/types.ts +76 -0
  43. package/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsConfirmation.vue +152 -0
  44. package/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsOtp.vue +0 -0
  45. package/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsSetup.vue +188 -0
  46. package/plugins/TwoFactorsAuthPlugin/index.ts +150 -0
  47. package/plugins/TwoFactorsAuthPlugin/types.ts +10 -0
  48. package/servers/express.ts +1 -0
  49. package/spa/src/App.vue +154 -121
  50. package/spa/src/components/ResourceForm.vue +91 -91
  51. package/spa/src/router/index.ts +0 -1
  52. package/spa/src/utils.ts +1 -1
  53. package/spa/src/views/CreateView.vue +0 -1
  54. package/spa/src/views/LoginView.vue +4 -1
  55. package/types/AdminForthConfig.ts +24 -3
  56. package/dist/plugins/AuditLog/index.js +0 -13
  57. package/dist/spa/spa/.eslintrc.cjs +0 -14
  58. package/dist/spa/spa/.vscode/extensions.json +0 -6
  59. package/dist/spa/spa/README.md +0 -39
  60. package/dist/spa/spa/env.d.ts +0 -1
  61. package/dist/spa/spa/index.html +0 -23
  62. package/dist/spa/spa/package-lock.json +0 -4470
  63. package/dist/spa/spa/package.json +0 -48
  64. package/dist/spa/spa/postcss.config.js +0 -6
  65. package/dist/spa/spa/public/assets/favicon.png +0 -0
  66. package/dist/spa/spa/src/App.vue +0 -283
  67. package/dist/spa/spa/src/assets/logo.svg +0 -19
  68. package/dist/spa/spa/src/components/AcceptModal.vue +0 -45
  69. package/dist/spa/spa/src/components/Breadcrumbs.vue +0 -40
  70. package/dist/spa/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  71. package/dist/spa/spa/src/components/CustomDatePicker.vue +0 -174
  72. package/dist/spa/spa/src/components/CustomDateRangePicker.vue +0 -218
  73. package/dist/spa/spa/src/components/CustomRangePicker.vue +0 -148
  74. package/dist/spa/spa/src/components/Dropdown.vue +0 -168
  75. package/dist/spa/spa/src/components/Filters.vue +0 -211
  76. package/dist/spa/spa/src/components/HelloWorld.vue +0 -17
  77. package/dist/spa/spa/src/components/MenuLink.vue +0 -24
  78. package/dist/spa/spa/src/components/ResourceForm.vue +0 -265
  79. package/dist/spa/spa/src/components/ResourceListTable.vue +0 -404
  80. package/dist/spa/spa/src/components/SingleSkeletLoader.vue +0 -13
  81. package/dist/spa/spa/src/components/Toast.vue +0 -66
  82. package/dist/spa/spa/src/components/ValueRenderer.vue +0 -59
  83. package/dist/spa/spa/src/components/icons/IconCalendar.vue +0 -5
  84. package/dist/spa/spa/src/components/icons/IconCommunity.vue +0 -7
  85. package/dist/spa/spa/src/components/icons/IconDocumentation.vue +0 -7
  86. package/dist/spa/spa/src/components/icons/IconEcosystem.vue +0 -7
  87. package/dist/spa/spa/src/components/icons/IconSupport.vue +0 -7
  88. package/dist/spa/spa/src/components/icons/IconTime.vue +0 -5
  89. package/dist/spa/spa/src/components/icons/IconTooling.vue +0 -19
  90. package/dist/spa/spa/src/composables/useFrontendApi.ts +0 -26
  91. package/dist/spa/spa/src/composables/useStores.ts +0 -126
  92. package/dist/spa/spa/src/index.scss +0 -26
  93. package/dist/spa/spa/src/main.ts +0 -18
  94. package/dist/spa/spa/src/router/index.ts +0 -64
  95. package/dist/spa/spa/src/spa_types/core.ts +0 -51
  96. package/dist/spa/spa/src/stores/core.ts +0 -144
  97. package/dist/spa/spa/src/stores/filters.ts +0 -22
  98. package/dist/spa/spa/src/stores/modal.ts +0 -48
  99. package/dist/spa/spa/src/stores/toast.ts +0 -15
  100. package/dist/spa/spa/src/stores/user.ts +0 -54
  101. package/dist/spa/spa/src/utils.ts +0 -101
  102. package/dist/spa/spa/src/views/CreateView.vue +0 -156
  103. package/dist/spa/spa/src/views/EditView.vue +0 -157
  104. package/dist/spa/spa/src/views/ListView.vue +0 -266
  105. package/dist/spa/spa/src/views/LoginView.vue +0 -136
  106. package/dist/spa/spa/src/views/ResourceParent.vue +0 -17
  107. package/dist/spa/spa/src/views/ShowView.vue +0 -180
  108. package/dist/spa/spa/tailwind.config.js +0 -17
  109. package/dist/spa/spa/tsconfig.app.json +0 -14
  110. package/dist/spa/spa/tsconfig.json +0 -11
  111. package/dist/spa/spa/tsconfig.node.json +0 -19
  112. package/dist/spa/spa/vite.config.ts +0 -49
  113. /package/dist/plugins/{AuditLog → S3UploadPlugin}/types.js +0 -0
  114. /package/dist/{spa/spa/src/assets/base.css → plugins/TwoFactorsAuthPlugin/custom/TwoFactorsOtp.vue} +0 -0
  115. /package/dist/spa/{spa/src → src}/components/SkeleteLoader.vue +0 -0
@@ -1,211 +0,0 @@
1
- <template>
2
- <!-- drawer component -->
3
- <div id="drawer-navigation"
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-black"
6
-
7
- :class="show ? 'top-0 transform-none' : ''"
8
- tabindex="-1" aria-labelledby="drawer-navigation-label"
9
- :style="{ height: `calc(100vh ` }"
10
- >
11
- <h5 id="drawer-navigation-label" class="text-base font-semibold text-gray-500 uppercase dark:text-gray-400">
12
- Filters
13
-
14
- <button type="button" @click="$emit('hide')" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 absolute end-2.5 inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white" >
15
- <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
16
- <span class="sr-only">Close menu</span>
17
- </button>
18
- </h5>
19
-
20
- <div class="py-4 ">
21
- <ul class="space-y-3 font-medium">
22
- <li v-for="c in columnsWithFilter" :key="c">
23
- <p class="dark:text-gray-400">{{ c.label }}</p>
24
-
25
- <Dropdown
26
- v-if="c.foreignResource"
27
- :options="columnOptions[c.name] || []"
28
- @update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
29
- :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
30
- />
31
- <Dropdown
32
- v-else-if="c.type === 'boolean'"
33
- :options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
34
- @update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
35
- :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
36
- />
37
-
38
- <Dropdown
39
- v-else-if="c.enum"
40
- :options="c.enum"
41
- :allowCustom="c.allowCustom"
42
- @update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
43
- :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
44
- />
45
-
46
- <input
47
- v-else-if="[ 'string', 'text' ].includes(c.type)"
48
- type="text" class="w-full py-1 px-2 border border-gray-300 rounded-md dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
49
- placeholder="Search"
50
- @input="setFilterItem({ column: c, operator: 'ilike', value: $event.target.value || undefined })"
51
- :value="getFilterItem({ column: c, operator: 'ilike' })"
52
- >
53
-
54
- <CustomDateRangePicker
55
- v-else-if="['datetime'].includes(c.type)"
56
- :column="c"
57
- :valueStart="filtersStore.filters.find(f => f.field === c.name && f.operator === 'gte')?.value || undefined"
58
- @update:valueStart="setFilterItem({ column: c, operator: 'gte', value: $event || undefined })"
59
- :valueEnd="filtersStore.filters.find(f => f.field === c.name && f.operator === 'lte')?.value || undefined"
60
- @update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
61
- />
62
-
63
- <input
64
- v-else-if="[ 'date', 'time' ].includes(c.type)"
65
- type="text" class="w-full py-1 px-2 border border-gray-300 rounded-md"
66
- placeholder="Search datetime"
67
- @input="setFilterItem({ column: c, operator: 'ilike', value: $event.target.value || undefined })"
68
- :value="getFilterItem({ column: c, operator: 'ilike' })"
69
- >
70
-
71
- <CustomRangePicker
72
- v-else-if="['integer', 'decimal', 'float'].includes(c.type) && c.allowMinMaxQuery"
73
- :min="c.min"
74
- :max="c.max"
75
- :valueStart="getFilterItem({ column: c, operator: 'gte' })"
76
- @update:valueStart="setFilterItem({ column: c, operator: 'gte', value: $event || undefined })"
77
- :valueEnd="getFilterItem({ column: c, operator: 'lte' })"
78
- @update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
79
- />
80
-
81
- <div v-else-if="['integer', 'decimal', 'float'].includes(c.type)" class="flex gap-2">
82
- <input
83
- type="number" aria-describedby="helper-text-explanation"
84
- 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-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
85
- placeholder="From"
86
- @input="setFilterItem({ column: c, operator: 'gte', value: $event.target.value || undefined })"
87
- :value="getFilterItem({ column: c, operator: 'gte' })"
88
- >
89
- <input
90
- type="number" aria-describedby="helper-text-explanation"
91
- 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-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
92
- placeholder="To"
93
- @input="setFilterItem({ column: c, operator: 'lte', value: $event.target.value || undefined})"
94
- :value="getFilterItem({ column: c, operator: 'lte' })"
95
- >
96
- </div>
97
-
98
- </li>
99
- </ul>
100
- </div>
101
-
102
- <div class="flex justify-end gap-2">
103
- <button
104
- :disabled="!filtersStore.filters.length"
105
- type="button"
106
- 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-blue-700 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 disabled:cursor-not-allowed"
107
- @click="clear">Clear all</button>
108
-
109
- </div>
110
- </div>
111
-
112
- <div v-if="show" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-30"
113
- @click="$emit('hide')">
114
- </div>
115
- </template>
116
-
117
- <script setup>
118
- import { watch, computed, ref, onMounted } from 'vue'
119
- import Dropdown from '@/components/Dropdown.vue';
120
- import CustomDateRangePicker from '@/components/CustomDateRangePicker.vue';
121
- import { callAdminForthApi } from '@/utils';
122
- import { useRouter } from 'vue-router';
123
- import { computedAsync } from '@vueuse/core'
124
- import CustomRangePicker from "@/components/CustomRangePicker.vue";
125
- import { useFiltersStore } from '@/stores/filters';
126
-
127
- const filtersStore = useFiltersStore();
128
-
129
-
130
- // props: columns
131
- // add support for v-model:filers
132
- const props = defineProps(['columns', 'filters', 'show', 'columnsMinMax']);
133
- const emits = defineEmits(['update:filters', 'hide']);
134
-
135
- const router = useRouter();
136
-
137
-
138
- const columnsWithFilter = computed(
139
- () => props.columns?.filter(column => column.showIn.includes('filter')) || []
140
- );
141
-
142
- const columnOptions = computedAsync(async () => {
143
- const ret = {};
144
- if (!props.columns) {
145
- return ret;
146
- }
147
- await Promise.all(
148
- Object.values(props.columns).map(async (column) => {
149
- if (column.foreignResource) {
150
- const list = await callAdminForthApi({
151
- method: 'POST',
152
- path: `/get_resource_foreign_data`,
153
- body: {
154
- resourceId: router.currentRoute.value.params.resourceId,
155
- column: column.name,
156
- limit: 1000,
157
- offset: 0,
158
- },
159
- });
160
- ret[column.name] = list.items;
161
- }
162
- })
163
- );
164
-
165
- return ret;
166
- }, {});
167
-
168
-
169
- // sync 'body' class 'overflow-hidden' with show prop show
170
- watch(() => props.show, (show) => {
171
- if (show) {
172
- document.body.classList.add('overflow-hidden');
173
- } else {
174
- document.body.classList.remove('overflow-hidden');
175
- }
176
- });
177
-
178
- // filters is a array of objects
179
- // {
180
- // field: 'name',
181
- // value: 'John',
182
- // operator: 'like'
183
- // }
184
-
185
- function setFilterItem({ column, operator, value }) {
186
-
187
- const index = filtersStore.filters.findIndex(f => f.field === column.name && f.operator === operator);
188
- if (value === undefined) {
189
- if (index !== -1) {
190
- filtersStore.filters.splice(index, 1);
191
- }
192
- emits('update:filters', [...filtersStore.filters]);
193
- return;
194
- } else {
195
- if (index === -1) {
196
- filtersStore.setFilter({ field: column.name, value, operator });
197
- } else {
198
- filtersStore.setFilters([...filtersStore.filters.slice(0, index), { field: column.name, value, operator }, ...filtersStore.filters.slice(index + 1)])
199
- }
200
- }
201
- emits('update:filters', [...filtersStore.filters]);
202
- }
203
-
204
- function getFilterItem({ column, operator }) {
205
- return filtersStore.filters.find(f => f.field === column.name && f.operator === operator)?.value || '';
206
- }
207
-
208
- async function clear() {
209
- filtersStore.clearFilters();
210
- }
211
- </script>
@@ -1,17 +0,0 @@
1
- <script setup lang="ts">
2
- defineProps<{
3
- msg: string
4
- }>()
5
- </script>
6
-
7
- <template>
8
- <div class="greetings">
9
- <h1 class="green">{{ msg }}</h1>
10
- <h3>
11
- You’ve successfully created a project with
12
- </h3>
13
- </div>
14
- </template>
15
-
16
- <style scoped>
17
- </style>
@@ -1,24 +0,0 @@
1
- <template>
2
- <RouterLink
3
- :to="{name: item.resourceId ? 'resource-list' : item.path, params: item.resourceId ? { resourceId: item.resourceId }: {}}"
4
- class="flex group items-center py-2 text-lightSidebarText dark:text-darkSidebarText rounded-default hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:hover:bg-darkSidebarItemHover dark:hover:text-darkSidebarTextHover active:bg-lightSidebarActive dark:active:bg-darkSidebarHover" role="menuitem"
5
- :class="{
6
- 'px-4': isChild,
7
- 'px-2': !isChild,
8
- 'bg-sidebarActive dark:bg-gray-700': item.resourceId ?
9
- ($route.params.resourceId === item.resourceId && $route.name === 'resource-list') :
10
- ($route.name === item.path)
11
- }"
12
- >
13
- <component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-lightSidebarIcons dark:text-darkSidebarIcons transition duration-75 group-hover:text-lightSidebarIconsHover dark:group-hover:text-darkSidebarIconsHover" ></component>
14
- <span class="ms-3">{{ item.label }}</span>
15
- </RouterLink>
16
- </template>
17
-
18
- <script setup lang="ts">
19
- import { getIcon } from '@/utils';
20
-
21
- const props = defineProps(['item', 'isChild']);
22
-
23
-
24
- </script>
@@ -1,265 +0,0 @@
1
- <template>
2
- <div class="rounded-default">
3
-
4
- <div
5
- class="relative shadow-resourseFormShadow sm:rounded-lg dark:shadow-2xl dark:shadow-black rounded-default"
6
- >
7
- <form autocomplete="off" @submit.prevent>
8
- <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 ">
9
- <thead class="text-xs text-gray-700 uppercase bg-lightormHeading dark:bg-gray-700 dark:text-gray-400">
10
- <tr>
11
- <th scope="col" class="px-6 py-3">
12
- Field
13
- </th>
14
- <th scope="col" class="px-6 py-3 w-4/6">
15
- Value
16
- </th>
17
- </tr>
18
- </thead>
19
- <tbody>
20
- <tr v-for="column, i in editableColumns" :key="column.name"
21
- class="bg-ligftForm dark:bg-gray-800 border-b dark:border-gray-700"
22
- >
23
- <!-- if column is in customComponentsPerColumn, use this component. If not, use this code -->
24
- <template v-if="column?.components?.[props.source]?.file">
25
- <component
26
- :is="getCustomComponent(column.components[props.source])"
27
- :column="column"
28
- :value="currentValues[column.name]"
29
- @update:value="setCurrentValue(column.name, $event)"
30
- :meta="column.components[props.source].meta"
31
- />
32
- </template>
33
- <template v-else>
34
- <td class="px-6 py-4 whitespace-nowrap "> <!--align-top-->
35
- {{ column.label }}
36
- <span :data-tooltip-target="`tooltip-show-${i}`" class="relative inline-block">
37
- <IconExclamationCircleSolid v-if="column.required[mode]" class="w-4 h-4"
38
- :class="(columnError(column) && validating) ? 'text-red-500 dark:text-red-400' : 'text-gray-400 dark:text-gray-500'"
39
- />
40
- </span>
41
- <div :id="`tooltip-show-${i}`"
42
- role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
43
- Required field
44
- <div class="tooltip-arrow" data-popper-arrow></div>
45
- </div>
46
- </td>
47
- <td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap relative">
48
- <Dropdown
49
- single
50
- v-if="column.foreignResource"
51
- :options="columnOptions[column.name] || []"
52
- :placeholder = "columnOptions[column.name]?.length ?'Select...': 'There are no options available'"
53
- :modelValue="currentValues[column.name]"
54
- @update:modelValue="setCurrentValue(column.name, $event)"
55
- ></Dropdown>
56
- <Dropdown
57
- single
58
- v-else-if="column.enum"
59
- :options="column.enum"
60
- :modelValue="currentValues[column.name]"
61
- @update:modelValue="setCurrentValue(column.name, $event)"
62
- />
63
- <Dropdown
64
- single
65
- v-else-if="column.type === 'boolean'"
66
- :options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
67
- :modelValue="currentValues[column.name]"
68
- @update:modelValue="setCurrentValue(column.name, $event)"
69
- />
70
- <input
71
- v-else-if="['integer'].includes(column.type)"
72
- type="number"
73
- step="1"
74
- 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-40 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
75
- placeholder="0"
76
- :value="currentValues[column.name]"
77
- @input="setCurrentValue(column.name, $event.target.value)"
78
- >
79
- <CustomDatePicker
80
- v-else-if="['datetime'].includes(column.type)"
81
- :column="column"
82
- :valueStart="currentValues[column.name]"
83
- auto-hide
84
- @update:valueStart="setCurrentValue(column.name, $event)"
85
- />
86
- <input
87
- v-else-if="['decimal', 'float'].includes(column.type)"
88
- type="number"
89
- step="0.1"
90
- 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-40 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
91
- placeholder="0.0"
92
- :value="currentValues[column.name]"
93
- @input="setCurrentValue(column.name, $event.target.value)"
94
- />
95
- <textarea
96
- v-else-if="['text'].includes(column.type)"
97
- 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-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
98
- placeholder="Text"
99
- :value="currentValues[column.name]"
100
- @input="setCurrentValue(column.name, $event.target.value)"
101
- >
102
- </textarea>
103
- <input
104
- v-else
105
- :type="!column.masked || unmasked[column.name] ? 'text' : 'password'"
106
- 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-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
107
- placeholder="Text"
108
- :value="currentValues[column.name]"
109
- @input="setCurrentValue(column.name, $event.target.value)"
110
- autocomplete="false"
111
- data-lpignore="true"
112
- readonly
113
- onfocus="this.removeAttribute('readonly');"
114
- >
115
-
116
- <button
117
- v-if="column.masked"
118
- type="button"
119
- @click="unmasked[column.name] = !unmasked[column.name]"
120
- class="h-6 absolute inset-y-2 top-6 right-6 flex items-center pr-2 z-index-100 focus:outline-none"
121
- >
122
- <IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
123
- <IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
124
- </button>
125
- <div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
126
-
127
- <div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-gray-400 dark:text-gray-500">{{ column.editingNote[mode] }}</div>
128
- </td>
129
- </template>
130
- </tr>
131
-
132
- </tbody>
133
- </table>
134
- </form>
135
- </div>
136
- </div>
137
-
138
- </template>
139
-
140
- <script setup>
141
-
142
- import CustomDatePicker from "@/components/CustomDatePicker.vue";
143
- import Dropdown from '@/components/Dropdown.vue';
144
- import { useCoreStore } from '@/stores/core';
145
- import { callAdminForthApi, getCustomComponent } from '@/utils';
146
- import { IconExclamationCircleSolid, IconEyeSlashSolid, IconEyeSolid } from '@iconify-prerendered/vue-flowbite';
147
- import { computedAsync } from '@vueuse/core';
148
- import { initFlowbite } from 'flowbite';
149
- import { computed, onMounted, ref, watch } from 'vue';
150
- import { useRouter } from 'vue-router';
151
-
152
- const router = useRouter();
153
-
154
- const props = defineProps({
155
- loading: Boolean,
156
- resource: Object,
157
- record: Object,
158
- validating: Boolean,
159
- source: String,
160
- });
161
-
162
- const unmasked = ref({});
163
-
164
- const mode = computed(() => props.record && Object.keys(props.record).length ? 'edit' : 'create');
165
-
166
- const emit = defineEmits(['update:record', 'update:isValid']);
167
-
168
- const currentValues = ref({});
169
-
170
-
171
- const columnError = (column) => {
172
- const val = computed(() => {
173
- if ( column.required[mode.value] && (currentValues.value[column.name] === undefined || currentValues.value[column.name] === null || currentValues.value[column.name] === '') ) {
174
- return 'This field is required';
175
- }
176
- if ( column.type === 'string' || column.type === 'text' ) {
177
- if ( column.maxLength && currentValues.value[column.name]?.length > column.maxLength ) {
178
- return `This field must be shorter than ${column.maxLength} characters`;
179
- }
180
- if ( column.minLength && currentValues.value[column.name]?.length < column.minLength ) {
181
- return `This field must be longer than ${column.minLength} characters`;
182
- }
183
- }
184
- if ( ['integer', 'decimal', 'float'].includes(column.type) ) {
185
- if ( column.minValue !== undefined && currentValues.value[column.name] < column.minValue ) {
186
- return `This field must be greater than ${column.minValue}`;
187
- }
188
- if ( column.maxValue !== undefined && currentValues.value[column.name] > column.maxValue ) {
189
- return `This field must be less than ${column.maxValue}`;
190
- }
191
- }
192
- if ( column.validation && column.validation.length ){
193
- const validationArray = column.validation;
194
- for (let i = 0; i < validationArray.length; i++) {
195
- if (validationArray[i].regExp) {
196
- const regExp = new RegExp(validationArray[i].regExp);
197
- let value = currentValues.value[column.name];
198
- if (value === undefined || value === null) {
199
- value = '';
200
- }
201
- if (!regExp.test(value)) {
202
- return validationArray[i].message;
203
- }
204
- }
205
- }
206
-
207
- }
208
- return null;
209
- });
210
- return val.value;
211
- };
212
-
213
-
214
- const setCurrentValue = (key, value) => {
215
- currentValues.value[key] = value;
216
-
217
- emit('update:record', currentValues.value);
218
- };
219
-
220
- onMounted(() => {
221
- Object.keys(props.record).forEach((key) => {
222
- currentValues.value[key] = props.record[key];
223
- });
224
- initFlowbite();
225
-
226
-
227
- });
228
-
229
- const columnOptions = computedAsync(async () => {
230
- return (await Promise.all(
231
- Object.values(props.resource.columns).map(async (column) => {
232
- if (column.foreignResource) {
233
- const list = await callAdminForthApi({
234
- method: 'POST',
235
- path: `/get_resource_foreign_data`,
236
- body: {
237
- resourceId: router.currentRoute.value.params.resourceId,
238
- column: column.name,
239
- limit: 1000,
240
- offset: 0,
241
- },
242
- });
243
- return { [column.name]: list.items };
244
- }
245
- })
246
- )).reduce((acc, val) => Object.assign(acc, val), {})
247
-
248
- }, {});
249
-
250
- const coreStore = useCoreStore();
251
-
252
- const editableColumns = computed(() => {
253
- const mode = props.record ? 'edit' : 'create';
254
- return props.resource?.columns?.filter(column => column.showIn.includes(mode));
255
- });
256
-
257
- const isValid = computed(() => {
258
- return editableColumns.value?.every(column => !columnError(column));
259
- });
260
-
261
- watch(() => isValid.value, (value) => {
262
- emit('update:isValid', value);
263
- });
264
-
265
- </script>