adminforth 1.1.73 → 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 (122) hide show
  1. package/auth.ts +14 -0
  2. package/dataConnectors/baseConnector.ts +107 -0
  3. package/dataConnectors/mongo.ts +6 -12
  4. package/dataConnectors/postgres.ts +17 -37
  5. package/dataConnectors/sqlite.ts +44 -55
  6. package/dist/auth.js +7 -0
  7. package/dist/dataConnectors/baseConnector.js +90 -0
  8. package/dist/dataConnectors/mongo.js +6 -12
  9. package/dist/dataConnectors/postgres.js +12 -33
  10. package/dist/dataConnectors/sqlite.js +67 -83
  11. package/dist/index.js +125 -66
  12. package/dist/modules/codeInjector.js +29 -15
  13. package/dist/plugins/AuditLogPlugin/index.js +2 -17
  14. package/dist/plugins/S3UploadPlugin/custom/s3uploader.vue +120 -0
  15. package/dist/plugins/S3UploadPlugin/index.js +103 -0
  16. package/dist/plugins/S3UploadPlugin/package-lock.json +431 -0
  17. package/dist/plugins/S3UploadPlugin/package.json +15 -0
  18. package/dist/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsConfirmation.vue +152 -0
  19. package/dist/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsSetup.vue +188 -0
  20. package/dist/plugins/TwoFactorsAuthPlugin/index.js +150 -0
  21. package/dist/plugins/TwoFactorsAuthPlugin/types.js +1 -0
  22. package/dist/spa/index.html +1 -1
  23. package/dist/spa/package-lock.json +111 -0
  24. package/dist/spa/package.json +1 -0
  25. package/dist/spa/src/App.vue +178 -133
  26. package/dist/spa/src/components/MenuLink.vue +3 -3
  27. package/dist/spa/src/components/ResourceForm.vue +96 -96
  28. package/dist/spa/src/components/ResourceListTable.vue +69 -84
  29. package/dist/spa/src/components/ValueRenderer.vue +2 -3
  30. package/dist/spa/src/composables/useStores.ts +24 -11
  31. package/dist/spa/src/main.ts +1 -1
  32. package/dist/spa/src/router/index.ts +0 -1
  33. package/dist/spa/src/utils.ts +1 -1
  34. package/dist/spa/src/views/CreateView.vue +1 -9
  35. package/dist/spa/src/views/EditView.vue +1 -9
  36. package/dist/spa/src/views/ListView.vue +12 -3
  37. package/dist/spa/src/views/LoginView.vue +6 -1
  38. package/dist/spa/src/views/ResourceParent.vue +1 -2
  39. package/dist/spa/src/views/ShowView.vue +6 -14
  40. package/dist/spa/tailwind.config.js +3 -1
  41. package/index.ts +103 -36
  42. package/modules/codeInjector.ts +38 -27
  43. package/package.json +2 -1
  44. package/plugins/AuditLogPlugin/index.ts +4 -20
  45. package/plugins/S3UploadPlugin/custom/s3uploader.vue +120 -0
  46. package/plugins/S3UploadPlugin/index.ts +112 -0
  47. package/plugins/S3UploadPlugin/package-lock.json +431 -0
  48. package/plugins/S3UploadPlugin/package.json +15 -0
  49. package/plugins/S3UploadPlugin/types.ts +76 -0
  50. package/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsConfirmation.vue +152 -0
  51. package/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsOtp.vue +0 -0
  52. package/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsSetup.vue +188 -0
  53. package/plugins/TwoFactorsAuthPlugin/index.ts +150 -0
  54. package/plugins/TwoFactorsAuthPlugin/types.ts +10 -0
  55. package/servers/express.ts +1 -0
  56. package/spa/src/App.vue +154 -121
  57. package/spa/src/components/ResourceForm.vue +91 -91
  58. package/spa/src/router/index.ts +0 -1
  59. package/spa/src/utils.ts +1 -1
  60. package/spa/src/views/CreateView.vue +0 -1
  61. package/spa/src/views/LoginView.vue +4 -1
  62. package/types/AdminForthConfig.ts +166 -5
  63. package/dist/plugins/AuditLog/index.js +0 -13
  64. package/dist/spa/spa/.eslintrc.cjs +0 -14
  65. package/dist/spa/spa/.vscode/extensions.json +0 -6
  66. package/dist/spa/spa/README.md +0 -39
  67. package/dist/spa/spa/env.d.ts +0 -1
  68. package/dist/spa/spa/index.html +0 -23
  69. package/dist/spa/spa/package-lock.json +0 -4470
  70. package/dist/spa/spa/package.json +0 -48
  71. package/dist/spa/spa/postcss.config.js +0 -6
  72. package/dist/spa/spa/public/assets/favicon.png +0 -0
  73. package/dist/spa/spa/src/App.vue +0 -283
  74. package/dist/spa/spa/src/assets/logo.svg +0 -19
  75. package/dist/spa/spa/src/components/AcceptModal.vue +0 -45
  76. package/dist/spa/spa/src/components/Breadcrumbs.vue +0 -40
  77. package/dist/spa/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  78. package/dist/spa/spa/src/components/CustomDatePicker.vue +0 -174
  79. package/dist/spa/spa/src/components/CustomDateRangePicker.vue +0 -218
  80. package/dist/spa/spa/src/components/CustomRangePicker.vue +0 -148
  81. package/dist/spa/spa/src/components/Dropdown.vue +0 -168
  82. package/dist/spa/spa/src/components/Filters.vue +0 -211
  83. package/dist/spa/spa/src/components/HelloWorld.vue +0 -17
  84. package/dist/spa/spa/src/components/MenuLink.vue +0 -24
  85. package/dist/spa/spa/src/components/ResourceForm.vue +0 -265
  86. package/dist/spa/spa/src/components/ResourceListTable.vue +0 -404
  87. package/dist/spa/spa/src/components/SingleSkeletLoader.vue +0 -13
  88. package/dist/spa/spa/src/components/Toast.vue +0 -66
  89. package/dist/spa/spa/src/components/ValueRenderer.vue +0 -59
  90. package/dist/spa/spa/src/components/icons/IconCalendar.vue +0 -5
  91. package/dist/spa/spa/src/components/icons/IconCommunity.vue +0 -7
  92. package/dist/spa/spa/src/components/icons/IconDocumentation.vue +0 -7
  93. package/dist/spa/spa/src/components/icons/IconEcosystem.vue +0 -7
  94. package/dist/spa/spa/src/components/icons/IconSupport.vue +0 -7
  95. package/dist/spa/spa/src/components/icons/IconTime.vue +0 -5
  96. package/dist/spa/spa/src/components/icons/IconTooling.vue +0 -19
  97. package/dist/spa/spa/src/composables/useFrontendApi.ts +0 -26
  98. package/dist/spa/spa/src/composables/useStores.ts +0 -126
  99. package/dist/spa/spa/src/index.scss +0 -26
  100. package/dist/spa/spa/src/main.ts +0 -18
  101. package/dist/spa/spa/src/router/index.ts +0 -64
  102. package/dist/spa/spa/src/spa_types/core.ts +0 -51
  103. package/dist/spa/spa/src/stores/core.ts +0 -144
  104. package/dist/spa/spa/src/stores/filters.ts +0 -22
  105. package/dist/spa/spa/src/stores/modal.ts +0 -48
  106. package/dist/spa/spa/src/stores/toast.ts +0 -15
  107. package/dist/spa/spa/src/stores/user.ts +0 -54
  108. package/dist/spa/spa/src/utils.ts +0 -101
  109. package/dist/spa/spa/src/views/CreateView.vue +0 -156
  110. package/dist/spa/spa/src/views/EditView.vue +0 -157
  111. package/dist/spa/spa/src/views/ListView.vue +0 -266
  112. package/dist/spa/spa/src/views/LoginView.vue +0 -136
  113. package/dist/spa/spa/src/views/ResourceParent.vue +0 -17
  114. package/dist/spa/spa/src/views/ShowView.vue +0 -180
  115. package/dist/spa/spa/tailwind.config.js +0 -17
  116. package/dist/spa/spa/tsconfig.app.json +0 -14
  117. package/dist/spa/spa/tsconfig.json +0 -11
  118. package/dist/spa/spa/tsconfig.node.json +0 -19
  119. package/dist/spa/spa/vite.config.ts +0 -49
  120. /package/dist/plugins/{AuditLog → S3UploadPlugin}/types.js +0 -0
  121. /package/dist/{spa/spa/src/assets/base.css → plugins/TwoFactorsAuthPlugin/custom/TwoFactorsOtp.vue} +0 -0
  122. /package/dist/spa/{spa/src → src}/components/SkeleteLoader.vue +0 -0
@@ -2,11 +2,11 @@
2
2
  <div class="rounded-default">
3
3
 
4
4
  <div
5
- class="relative shadow-resourse-form-shadow sm:rounded-lg dark:shadow-2xl dark:shadow-black rounded-default"
5
+ class="relative shadow-resourseFormShadow sm:rounded-lg dark:shadow-2xl dark:shadow-black rounded-default"
6
6
  >
7
7
  <form autocomplete="off" @submit.prevent>
8
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-form-view-heading dark:bg-gray-700 dark:text-gray-400">
9
+ <thead class="text-xs text-gray-700 uppercase bg-lightormHeading dark:bg-gray-700 dark:text-gray-400">
10
10
  <tr>
11
11
  <th scope="col" class="px-6 py-3">
12
12
  Field
@@ -18,18 +18,8 @@
18
18
  </thead>
19
19
  <tbody>
20
20
  <tr v-for="column, i in editableColumns" :key="column.name"
21
- class="bg-form-view-bg dark:bg-gray-800 border-b dark:border-gray-700"
21
+ class="bg-ligftForm dark:bg-gray-800 border-b dark:border-gray-700"
22
22
  >
23
- <!-- if column is in customComponentsPerColumn, use this component. If not, use this code -->
24
- <template v-if="customComponentsPerColumn[column.name]">
25
- <component
26
- :is="customComponentsPerColumn[column.name]"
27
- :column="column"
28
- :value="currentValues[column.name]"
29
- @update:value="setCurrentValue(column.name, $event)"
30
- />
31
- </template>
32
- <template v-else>
33
23
  <td class="px-6 py-4 whitespace-nowrap "> <!--align-top-->
34
24
  {{ column.label }}
35
25
  <span :data-tooltip-target="`tooltip-show-${i}`" class="relative inline-block">
@@ -44,88 +34,99 @@
44
34
  </div>
45
35
  </td>
46
36
  <td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap relative">
47
- <Dropdown
48
- single
49
- v-if="column.foreignResource"
50
- :options="columnOptions[column.name] || []"
51
- :placeholder = "columnOptions[column.name]?.length || 'There are no options available'"
52
- :modelValue="currentValues[column.name]"
53
- @update:modelValue="setCurrentValue(column.name, $event)"
54
- ></Dropdown>
55
- <Dropdown
56
- single
57
- v-else-if="column.enum"
58
- :options="column.enum"
59
- :modelValue="currentValues[column.name]"
60
- @update:modelValue="setCurrentValue(column.name, $event)"
61
- />
62
- <Dropdown
63
- single
64
- v-else-if="column.type === 'boolean'"
65
- :options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
66
- :modelValue="currentValues[column.name]"
67
- @update:modelValue="setCurrentValue(column.name, $event)"
68
- />
69
- <input
70
- v-else-if="['integer'].includes(column.type)"
71
- type="number"
72
- step="1"
73
- 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"
74
- placeholder="0"
75
- :value="currentValues[column.name]"
76
- @input="setCurrentValue(column.name, $event.target.value)"
77
- >
78
- <CustomDatePicker
79
- v-else-if="['datetime'].includes(column.type)"
80
- :column="column"
81
- :valueStart="currentValues[column.name]"
82
- auto-hide
83
- @update:valueStart="setCurrentValue(column.name, $event)"
84
- />
85
- <input
86
- v-else-if="['decimal', 'float'].includes(column.type)"
87
- type="number"
88
- step="0.1"
89
- 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"
90
- placeholder="0.0"
91
- :value="currentValues[column.name]"
92
- @input="setCurrentValue(column.name, $event.target.value)"
93
- />
94
- <textarea
95
- v-else-if="['text'].includes(column.type)"
96
- 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"
97
- placeholder="Text"
98
- :value="currentValues[column.name]"
99
- @input="setCurrentValue(column.name, $event.target.value)"
100
- >
101
- </textarea>
102
- <input
103
- v-else
104
- :type="!column.masked || unmasked[column.name] ? 'text' : 'password'"
105
- 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"
106
- placeholder="Text"
107
- :value="currentValues[column.name]"
108
- @input="setCurrentValue(column.name, $event.target.value)"
109
- autocomplete="false"
110
- data-lpignore="true"
111
- readonly
112
- onfocus="this.removeAttribute('readonly');"
113
- >
114
37
 
115
- <button
116
- v-if="column.masked"
117
- type="button"
118
- @click="unmasked[column.name] = !unmasked[column.name]"
119
- class="h-6 absolute inset-y-2 top-6 right-6 flex items-center pr-2 z-index-100 focus:outline-none"
120
- >
121
- <IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
122
- <IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
123
- </button>
124
- <div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
38
+ <template v-if="column?.components?.[props.source]?.file">
39
+ <component
40
+ :is="getCustomComponent(column.components[props.source])"
41
+ :column="column"
42
+ :value="currentValues[column.name]"
43
+ @update:value="setCurrentValue(column.name, $event)"
44
+ :meta="column.components[props.source].meta"
45
+ />
46
+ </template>
47
+ <template v-else>
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
+ >
125
115
 
126
- <div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-gray-400 dark:text-gray-500">{{ column.editingNote[mode] }}</div>
127
- </td>
128
- </template>
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
+ </template>
129
+ </td>
129
130
  </tr>
130
131
 
131
132
  </tbody>
@@ -141,7 +142,7 @@
141
142
  import CustomDatePicker from "@/components/CustomDatePicker.vue";
142
143
  import Dropdown from '@/components/Dropdown.vue';
143
144
  import { useCoreStore } from '@/stores/core';
144
- import { callAdminForthApi } from '@/utils';
145
+ import { callAdminForthApi, getCustomComponent } from '@/utils';
145
146
  import { IconExclamationCircleSolid, IconEyeSlashSolid, IconEyeSolid } from '@iconify-prerendered/vue-flowbite';
146
147
  import { computedAsync } from '@vueuse/core';
147
148
  import { initFlowbite } from 'flowbite';
@@ -155,10 +156,9 @@ const props = defineProps({
155
156
  resource: Object,
156
157
  record: Object,
157
158
  validating: Boolean,
158
- customComponentsPerColumn: Object,
159
+ source: String,
159
160
  });
160
161
 
161
-
162
162
  const unmasked = ref({});
163
163
 
164
164
  const mode = computed(() => props.record && Object.keys(props.record).length ? 'edit' : 'create');
@@ -1,42 +1,29 @@
1
1
  <template>
2
2
  <!-- table -->
3
- <div class="relative overflow-x-auto shadow-list-table-shadow dark:shadow-black overflow-y-hidden rounded-default">
3
+ <div class="relative overflow-x-auto shadow-listTableShadow dark:shadow-black overflow-y-hidden rounded-default">
4
4
 
5
5
  <!-- skelet loader -->
6
6
  <div role="status" v-if="!resource || !resource.columns"
7
7
  class="max-w p-4 space-y-4 divide-y divide-gray-200 rounded shadow animate-pulse dark:divide-gray-700 md:p-6 dark:border-gray-700">
8
- <div class="flex items-center justify-between h-16">
9
- <div>
10
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
11
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
12
- </div>
13
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
14
- </div>
15
-
16
- <div class="flex items-center justify-between h-16 pt-4" v-for="i in new Array(10)">
17
- <div>
18
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
19
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
20
- </div>
21
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
22
- </div>
23
- <span class="sr-only">Loading...</span>
8
+
24
9
  </div>
25
10
 
26
11
  <table v-else class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 rounded-default">
27
- <thead class="text-xs text-view-table-heading-text bg-list-view-table-heading dark:bg-gray-700 dark:text-gray-400">
12
+ <thead class="text-xs text-view-table-heading-text bg-lightListTableHeading dark:bg-darkListTableHeading dark:text-gray-400">
28
13
  <tr>
29
14
  <th scope="col" class="p-4">
30
15
  <div v-if="rows && rows.length" class="flex items-center">
31
- <input id="checkbox-all-search" type="checkbox" :checked="allFromThisPageChecked" @change="selectAll()"
32
- class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
16
+ <input id="checkbox-all-search" type="checkbox" :checked="allFromThisPageChecked" @change="selectAll()"
17
+ class="w-4 h-4 cursor-pointer text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
33
18
  <label for="checkbox-all-search" class="sr-only">checkbox</label>
34
19
  </div>
35
20
  </th>
36
21
 
37
22
  <th v-for="c in columnsListed" scope="col" class="px-6 py-3">
23
+
38
24
  <div @click="() => c.sortable && onSortButtonClick(c.name)" class="flex items-center " :class="{'cursor-pointer':c.sortable}">
39
25
  {{ c.label }}
26
+
40
27
 
41
28
  <div v-if="c.sortable"
42
29
  :style="{ 'color':ascArr.includes(c.name)?'green':descArr.includes(c.name)?'red':'currentColor'}">
@@ -52,6 +39,12 @@
52
39
  d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 1.086Zm6.852 1.952H8.574a2.072 2.072 0 0 0-1.847 1.087 1.9 1.9 0 0 0 .11 1.985l3.426 5.05a2.123 2.123 0 0 0 3.472 0l3.427-5.05a1.9 1.9 0 0 0 .11-1.985 2.074 2.074 0 0 0-1.846-1.087Z"/>
53
40
  </svg>
54
41
  </div>
42
+ <span
43
+ class="bg-red-100 text-red-800 text-xs font-medium me-1 px-1 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400"
44
+ v-if="sort.findIndex((s) => s.field === c.name) !== -1">
45
+ {{ sort.findIndex((s) => s.field === c.name) + 1 }}
46
+ </span>
47
+
55
48
  </div>
56
49
  </th>
57
50
 
@@ -61,51 +54,8 @@
61
54
  </tr>
62
55
  </thead>
63
56
  <tbody>
64
- <tr v-if="!rows" class="bg-list-view-table-bg border-b dark:bg-gray-800 dark:border-gray-700">
65
- <td :colspan="resource?.columns.length + 2">
66
-
67
- <div role="status"
68
- class="max-w p-4 space-y-4 divide-y divide-gray-200 rounded animate-pulse dark:divide-gray-700 md:p-6 dark:border-gray-700">
69
- <div class="flex items-center justify-between">
70
- <div>
71
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
72
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
73
- </div>
74
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
75
- </div>
76
- <div class="flex items-center justify-between pt-4">
77
- <div>
78
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
79
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
80
- </div>
81
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
82
- </div>
83
- <div class="flex items-center justify-between pt-4">
84
- <div>
85
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
86
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
87
- </div>
88
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
89
- </div>
90
- <div class="flex items-center justify-between pt-4">
91
- <div>
92
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
93
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
94
- </div>
95
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
96
- </div>
97
- <div class="flex items-center justify-between pt-4">
98
- <div>
99
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
100
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
101
- </div>
102
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
103
- </div>
104
- <span class="sr-only">Loading...</span>
105
- </div>
106
- </td>
107
- </tr>
108
- <tr v-else-if="rows.length === 0" class="bg-list-view-table-bg border-b dark:bg-gray-800 dark:border-gray-700">
57
+ <SkeleteLoader v-if="!rows" :columns="3" :rows="resource?.columns.length + 2"/>
58
+ <tr v-else-if="rows.length === 0" class="bg-lightListTable border-b dark:bg-darkListTable dark:border-darkListTableBorder">
109
59
  <td :colspan="resource?.columns.length + 2">
110
60
 
111
61
  <div id="toast-simple"
@@ -118,16 +68,17 @@
118
68
  </td>
119
69
  </tr>
120
70
 
121
- <tr v-else v-for="(row, rowI) in rows" :key="row.id"
122
- class="bg-list-view-table-bg border-b dark:bg-gray-800 border-list-view-border-color dark:border-gray-700 hover:bg-list-view-table-row-hover dark:hover:bg-gray-600">
123
- <td class="w-4 p-4">
124
- <div class="flex items center">
71
+ <tr @click="onClick($event,row)" v-else v-for="(row, rowI) in rows" :key="row.id"
72
+ class="bg-lightListTable border-b dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover cursor-pointer">
73
+ <td class="w-4 p-4 cursor-default" @click="(e)=>{e.stopPropagation()}">
74
+ <div class="flex items center ">
125
75
  <input
76
+ @click="(e)=>{e.stopPropagation()}"
126
77
  id="checkbox-table-search-1"
127
78
  type="checkbox"
128
79
  :checked="checkboxesInternal.includes(row.id)"
129
80
  @change="(e)=>{addToCheckedValues(row.id)}"
130
- class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
81
+ class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600 cursor-pointer">
131
82
  <label for="checkbox-table-search-1" class="sr-only">checkbox</label>
132
83
  </div>
133
84
  </td>
@@ -135,14 +86,14 @@
135
86
  <!-- if c.name in listComponentsPerColumn, render it. If not, render ValueRenderer -->
136
87
  <component
137
88
  :is="c?.components?.list ? getCustomComponent(c.components.list) : ValueRenderer"
138
- :meta="c?.components?.list.meta"
89
+ :meta="c?.components?.list?.meta"
139
90
  :column="c"
140
91
  :record="row"
141
92
  :adminUser="coreStore.adminUser"
142
93
  :resource="resource"
143
94
  />
144
95
  </td>
145
- <td class=" items-center px-6 py-4">
96
+ <td class=" items-center px-6 py-4 cursor-default" @click="(e)=>{e.stopPropagation()}">
146
97
  <div class="flex">
147
98
  <RouterLink
148
99
  v-if="resource.options?.allowedActions.show"
@@ -278,6 +229,7 @@ import ValueRenderer from '@/components/ValueRenderer.vue';
278
229
  import { getCustomComponent } from '@/utils';
279
230
  import { useCoreStore } from '@/stores/core';
280
231
  import { showSuccesTost } from '@/composables/useFrontendApi';
232
+ import SkeleteLoader from '@/components/SkeleteLoader.vue';
281
233
 
282
234
  import {
283
235
  IconInboxOutline,
@@ -291,6 +243,7 @@ IconFilterOutline,
291
243
  IconPenSolid,
292
244
  IconTrashBinSolid
293
245
  } from '@iconify-prerendered/vue-flowbite';
246
+ import router from '@/router';
294
247
 
295
248
  const coreStore = useCoreStore();
296
249
 
@@ -299,7 +252,8 @@ const props = defineProps([
299
252
  'rows',
300
253
  'totalRows',
301
254
  'pageSize',
302
- 'checkboxes'
255
+ 'checkboxes',
256
+ 'sort'
303
257
  ])
304
258
 
305
259
  // emits, update page
@@ -335,6 +289,10 @@ watch(() => props.checkboxes, (newCheckboxes) => {
335
289
  checkboxesInternal.value = newCheckboxes;
336
290
  });
337
291
 
292
+ watch(() => props.sort, (newSort) => {
293
+ sort.value = newSort;
294
+ });
295
+
338
296
  function addToCheckedValues(id) {
339
297
  if (checkboxesInternal.value.includes(id)) {
340
298
  checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== id);
@@ -347,15 +305,18 @@ function addToCheckedValues(id) {
347
305
  const columnsListed = computed(() => props.resource?.columns?.filter(c => c.showIn.includes('list')));
348
306
 
349
307
  async function selectAll(value) {
350
- props.rows.forEach((r) => {
351
- if (!checkboxesInternal.value.includes(r.id)) {
352
- checkboxesInternal.value.push(r.id)
353
- } else {
354
- checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== r.id)
355
- }
356
- });
357
- checkboxesInternal.value = [ ...checkboxesInternal.value ]
358
- // checkboxes.value = rows.value.map((v) => v.id);
308
+ if (!allFromThisPageChecked.value) {
309
+ props.rows.forEach((r) => {
310
+ if (!checkboxesInternal.value.includes(r.id)) {
311
+ checkboxesInternal.value.push(r.id)
312
+ }
313
+ });
314
+ }
315
+ else {
316
+ props.rows.forEach((r) => {
317
+ checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== r.id);
318
+ });
319
+ }
359
320
  }
360
321
 
361
322
  const totalPages = computed(() => Math.ceil(props.totalRows / props.pageSize));
@@ -365,7 +326,7 @@ const totalPages = computed(() => Math.ceil(props.totalRows / props.pageSize));
365
326
 
366
327
  const allFromThisPageChecked = computed(() => {
367
328
  if (!props.rows) return false;
368
- return props.rows.every((r) => props.checkboxes.includes(r.id));
329
+ return props.rows.every((r) => checkboxesInternal.value.includes(r.id));
369
330
  });
370
331
  const ascArr = computed(() => sort.value.filter((s) => s.direction === 'asc').map((s) => s.field));
371
332
  const descArr = computed(() => sort.value.filter((s) => s.direction === 'desc').map((s) => s.field));
@@ -374,7 +335,7 @@ const descArr = computed(() => sort.value.filter((s) => s.direction === 'desc').
374
335
  function onSortButtonClick(field) {
375
336
  const sortIndex = sort.value.findIndex((s) => s.field === field);
376
337
  if (sortIndex === -1) {
377
- sort.value = [{field, direction: 'asc'}, ...sort.value];
338
+ sort.value = [...sort.value,{field, direction: 'asc'}];
378
339
  } else {
379
340
  const sortField = sort.value[sortIndex];
380
341
  if (sortField.direction === 'asc') {
@@ -385,6 +346,30 @@ function onSortButtonClick(field) {
385
346
  }
386
347
  }
387
348
 
349
+
350
+ const clickTarget = ref(null);
351
+ async function onClick(e,row) {
352
+ if(clickTarget.value === e.target) return;
353
+ clickTarget.value = e.target;
354
+ await new Promise((resolve) => setTimeout(resolve, 300));
355
+ if (window.getSelection().toString()) return;
356
+ else {router.push({
357
+ name: 'resource-show',
358
+ params: {
359
+ resourceId: props.resource.resourceId,
360
+ primaryKey: row._primaryKeyValue,
361
+ },
362
+ })}
363
+ }
364
+
365
+
366
+
367
+
368
+
369
+
370
+
371
+
372
+
388
373
  async function deleteRecord(row) {
389
374
  const data = await window.adminforth.confirm({
390
375
  message: 'Are you sure you want to delete this item?',
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div>
3
3
 
4
- <span v-if="column.foreignResource">
4
+ <span @click="(e)=>{e.stopPropagation()}" v-if="column.foreignResource">
5
5
  <RouterLink v-if="record[column.name]" class="font-medium text-blue-600 dark:text-blue-500 hover:brightness-110"
6
6
  :to="{ name: 'resource-show', params: { resourceId: column.foreignResource.resourceId, primaryKey: record[column.name].pk } }">
7
7
  {{ record[column.name].label }}
@@ -18,9 +18,8 @@
18
18
  <span v-else-if="column.enum">
19
19
  {{ checkEmptyValues(column.enum.find(e => e.value === record[column.name])?.label,route.meta.type) }}
20
20
  </span>
21
- <span v-else-if="column.type === 'datetime'">
21
+ <span v-else-if="column.type === 'datetime'" class="whitespace-nowrap">
22
22
  {{ checkEmptyValues(formatDate(record[column.name]),route.meta.type) }}
23
-
24
23
  </span>
25
24
  <span v-else>
26
25
  {{ checkEmptyValues(record[column.name],route.meta.type) }}
@@ -1,4 +1,5 @@
1
- import type { FrontendAPIInterface, ConfirmParams, AlertParams, FilterParams,Operator } from '../types/FrontendAPI';
1
+ import type { FrontendAPIInterface, ConfirmParams, AlertParams, } from '../types/FrontendAPI';
2
+ import type { AdminForthFilterOperators } from '@/types/AdminForthConfig';
2
3
  import { useToastStore } from '../stores/toast';
3
4
  import { useModalStore } from '../stores/modal';
4
5
  import { useCoreStore } from '@/stores/core';
@@ -6,9 +7,20 @@ import { useFiltersStore } from '@/stores/filters';
6
7
  import router from '@/router'
7
8
  import type { AdminForthResourceColumn } from '@/types/AdminForthConfig';
8
9
 
9
-
10
-
11
-
10
+ type FilterParams = {
11
+ /**
12
+ * Field of resource to filter
13
+ */
14
+ field: string;
15
+ /**
16
+ * Operator of filter
17
+ */
18
+ operator: AdminForthFilterOperators;
19
+ /**
20
+ * Value of filter
21
+ */
22
+ value: string | number | boolean ;
23
+ }
12
24
 
13
25
  declare global {
14
26
  interface Window {
@@ -23,7 +35,6 @@ declare global {
23
35
  }
24
36
 
25
37
  export class FrontendAPI implements FrontendAPIInterface {
26
- private validOperators: Operator[] = ['lte', 'gte', 'in','ilike'];
27
38
  private toastStore:any
28
39
  private modalStore:any
29
40
  private filtersStore:any
@@ -70,12 +81,8 @@ export class FrontendAPI implements FrontendAPIInterface {
70
81
  const filterField = this.coreStore.resourceColumnsWithFilters.find((col: AdminForthResourceColumn) => col.name === filter.field)
71
82
  if(!filterField){
72
83
  throw new Error(`Field ${filter.field} is not available for filtering`)
73
- }
74
- if(filterField) {
75
- if(!this.validOperators.includes(filter.operator)){
76
- throw new Error(`Operator ${filter.operator} is not valid`)
77
- }
78
84
  }
85
+
79
86
  }
80
87
  return true
81
88
  }
@@ -103,7 +110,13 @@ export class FrontendAPI implements FrontendAPIInterface {
103
110
  if(index === -1) {
104
111
  this.filtersStore.setFilter(filter)
105
112
  } else {
106
- this.filtersStore.setFilters([...this.filtersStore.filters.slice(0, index), filter, ...this.filtersStore.filters.slice(index + 1)])
113
+ const filters = [...this.filtersStore.filters];
114
+ if (filter.value === undefined) {
115
+ filters.splice(index, 1);
116
+ } else {
117
+ filters[index] = filter;
118
+ }
119
+ this.filtersStore.setFilters(filters);
107
120
  }
108
121
  }
109
122
  }
@@ -6,7 +6,7 @@ import { createPinia } from 'pinia'
6
6
  import App from './App.vue'
7
7
  import router from './router'
8
8
 
9
- const app = createApp(App)
9
+ export const app = createApp(App)
10
10
  /* IMPORTANT:ADMINFORTH COMPONENT REGISTRATIONS */
11
11
 
12
12
 
@@ -12,7 +12,6 @@ const router = createRouter({
12
12
  component: () => import('@/views/LoginView.vue'),
13
13
  meta: { title: 'login' },
14
14
  beforeEnter: async (to, from, next) => {
15
- const userStore = useUserStore()
16
15
  if(localStorage.getItem('isAuthorized') === 'true'){
17
16
  next({name: 'home'})
18
17
  } else {
@@ -16,7 +16,7 @@ export async function callApi({path, method, body=undefined} ) {
16
16
  };
17
17
  const fullPath = `${import.meta.env.VITE_ADMINFORTH_PUBLIC_PATH || ''}${path}`;
18
18
  const r = await fetch(fullPath, options);
19
- if (r.status == 401) {
19
+ if (r.status == 401 ) {
20
20
  useUserStore().unauthorize();
21
21
  router.push({ name: 'login' });
22
22
  return null;
@@ -50,7 +50,7 @@
50
50
  @update:record="onUpdateRecord"
51
51
  @update:isValid="isValid = $event"
52
52
  :validating="validating"
53
- :customComponentsPerColumn="createComponentsPerColumn"
53
+ :source="'create'"
54
54
  >
55
55
  </ResourceForm>
56
56
 
@@ -91,7 +91,6 @@ const route = useRoute();
91
91
  const router = useRouter();
92
92
 
93
93
  const record = ref({});
94
- let createComponentsPerColumn = {};
95
94
 
96
95
  const coreStore = useCoreStore();
97
96
 
@@ -115,12 +114,6 @@ onMounted(async () => {
115
114
  await coreStore.fetchResourceFull({
116
115
  resourceId: route.params.resourceId
117
116
  });
118
- createComponentsPerColumn = coreStore.resource.columns.reduce((acc, column) => {
119
- if (column.components?.create) {
120
- acc[column.name] = getCustomComponent(column.components.create);
121
- }
122
- return acc;
123
- }, {});
124
117
  loading.value = false;
125
118
  checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'create');
126
119
  });
@@ -143,7 +136,6 @@ async function saveRecord() {
143
136
  });
144
137
  if (response.error) {
145
138
  showErrorTost(response.error);
146
-
147
139
  }
148
140
  saving.value = false;
149
141
  if (route.query.returnTo) {