adminforth 1.1.64 → 1.1.66
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.
- package/dataConnectors/mongo.ts +7 -13
- package/dataConnectors/postgres.ts +2 -2
- package/dataConnectors/sqlite.ts +2 -2
- package/dist/dataConnectors/mongo.js +1 -9
- package/dist/index.js +1 -3
- package/dist/plugins/AuditLogPlugin/custom/AuditLogView.vue +7 -6
- package/dist/plugins/AuditLogPlugin/index.js +18 -13
- package/dist/spa/index.html +2 -2
- package/dist/spa/package-lock.json +17 -4
- package/dist/spa/package.json +2 -1
- package/dist/spa/spa/src/App.vue +24 -12
- package/dist/spa/spa/src/composables/useStores.ts +16 -4
- package/dist/spa/spa/src/main.ts +1 -1
- package/dist/spa/src/App.vue +125 -45
- package/dist/spa/src/assets/logo.svg +19 -1
- package/dist/spa/src/components/AcceptModal.vue +2 -9
- package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
- package/dist/spa/src/components/CustomDatePicker.vue +16 -3
- package/dist/spa/src/components/CustomDateRangePicker.vue +11 -2
- package/dist/spa/src/components/Dropdown.vue +6 -1
- package/dist/spa/src/components/Filters.vue +40 -19
- package/dist/spa/src/components/ResourceForm.vue +26 -24
- package/dist/spa/src/components/ResourceListTable.vue +419 -0
- package/dist/spa/src/components/Toast.vue +66 -0
- package/dist/spa/src/components/ValueRenderer.vue +14 -8
- package/dist/spa/src/composables/useFrontendApi.ts +26 -0
- package/dist/spa/src/composables/useStores.ts +113 -0
- package/dist/spa/src/main.ts +1 -1
- package/dist/spa/src/router/index.ts +30 -20
- package/dist/spa/src/spa_types/core.ts +51 -0
- package/dist/spa/src/stores/core.ts +48 -31
- package/dist/spa/src/stores/filters.ts +22 -0
- package/dist/spa/src/stores/modal.ts +13 -3
- package/dist/spa/src/stores/toast.ts +15 -0
- package/dist/spa/src/stores/user.ts +54 -0
- package/dist/spa/src/utils.ts +62 -7
- package/dist/spa/src/views/CreateView.vue +67 -15
- package/dist/spa/src/views/EditView.vue +45 -11
- package/dist/spa/src/views/ListView.vue +82 -366
- package/dist/spa/src/views/LoginView.vue +16 -4
- package/dist/spa/src/views/ShowView.vue +105 -21
- package/dist/spa/tailwind.config.js +1 -1
- package/dist/spa/vite.config.ts +6 -0
- package/index.ts +4 -5
- package/package.json +1 -1
- package/plugins/AuditLogPlugin/custom/AuditLogView.vue +7 -6
- package/plugins/AuditLogPlugin/index.ts +23 -15
- package/spa/src/App.vue +24 -12
- package/spa/src/composables/useStores.ts +16 -4
- package/spa/src/main.ts +1 -1
- package/types/AdminForthConfig.ts +16 -3
- package/types/FrontendAPI.ts +1 -15
- package/dist/plugins/AccessControl/index.js +0 -66
- package/dist/plugins/AccessControl/types.js +0 -1
- package/dist/spa/public/favicon.ico +0 -0
- package/dist/spa/spa/public/favicon.ico +0 -0
- package/dist/types.js +0 -30
- /package/dist/spa/{spa/public → public/assets}/favicon.png +0 -0
|
@@ -2,20 +2,27 @@
|
|
|
2
2
|
<div class="relative">
|
|
3
3
|
<Teleport to="body">
|
|
4
4
|
<Filters
|
|
5
|
-
:columns="coreStore.
|
|
6
|
-
v-model:filters="filters"
|
|
5
|
+
:columns="coreStore.resource?.columns"
|
|
7
6
|
:columnsMinMax="columnsMinMax" :show="filtersShow"
|
|
8
7
|
@hide="filtersShow = false"
|
|
9
8
|
/>
|
|
10
9
|
</Teleport>
|
|
11
10
|
|
|
11
|
+
<component
|
|
12
|
+
v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.beforeBreadcrumbs || []"
|
|
13
|
+
:is="getCustomComponent(c)"
|
|
14
|
+
:meta="c.meta"
|
|
15
|
+
:resource="coreStore.resource"
|
|
16
|
+
:adminUser="coreStore.adminUser"
|
|
17
|
+
/>
|
|
18
|
+
|
|
12
19
|
<BreadcrumbsWithButtons>
|
|
13
20
|
<button
|
|
14
21
|
@click="()=>{checkboxes = []}"
|
|
15
22
|
v-if="checkboxes.length"
|
|
16
23
|
data-tooltip-target="tooltip-remove-all"
|
|
17
24
|
data-tooltip-placement="bottom"
|
|
18
|
-
class="flex gap-1 items-center py-1 px-3 me-2
|
|
25
|
+
class="flex gap-1 items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-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 rounded-default"
|
|
19
26
|
>
|
|
20
27
|
<IconBanOutline class="w-5 h-5 "/>
|
|
21
28
|
|
|
@@ -27,9 +34,11 @@
|
|
|
27
34
|
</button>
|
|
28
35
|
|
|
29
36
|
<button
|
|
30
|
-
v-if="checkboxes.length"
|
|
37
|
+
v-if="checkboxes.length"
|
|
38
|
+
v-for="(action,i) in coreStore.resource?.options?.bulkActions"
|
|
39
|
+
:key="action.id"
|
|
31
40
|
@click="startBulkAction(action.id)"
|
|
32
|
-
class="flex gap-1 items-center py-1 px-3
|
|
41
|
+
class="flex gap-1 items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-default border border-gray-300 hover:bg-gray-100 hover:text-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"
|
|
33
42
|
:class="{'bg-red-100 text-red-800 border-red-400 dark:bg-red-700 dark:text-red-400 dark:border-red-400':action.state==='danger', 'bg-green-100 text-green-800 border-green-400 dark:bg-green-700 dark:text-green-400 dark:border-green-400':action.state==='success',
|
|
34
43
|
'bg-blue-100 text-blue-800 border-blue-400 dark:bg-blue-700 dark:text-blue-400 dark:border-blue-400':action.state==='active',
|
|
35
44
|
}"
|
|
@@ -42,383 +51,129 @@
|
|
|
42
51
|
{{ `${action.label} (${checkboxes.length})` }}
|
|
43
52
|
</button>
|
|
44
53
|
|
|
45
|
-
<RouterLink v-if="allowedActions
|
|
54
|
+
<RouterLink v-if="coreStore.resource?.options?.allowedActions?.create"
|
|
46
55
|
:to="{ name: 'resource-create', params: { resourceId: $route.params.resourceId } }"
|
|
47
|
-
class="flex items-center py-1 px-3
|
|
56
|
+
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 rounded-default"
|
|
48
57
|
>
|
|
49
58
|
<IconPlusOutline class="w-4 h-4 me-2"/>
|
|
50
59
|
Create
|
|
51
60
|
</RouterLink>
|
|
52
61
|
|
|
53
62
|
<button
|
|
54
|
-
class="flex gap-1 items-center py-1 px-3 me-2
|
|
63
|
+
class="flex gap-1 items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-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 rounded-default"
|
|
55
64
|
@click="()=>{filtersShow = !filtersShow}"
|
|
65
|
+
v-if="coreStore.resource?.options?.allowedActions?.filter"
|
|
56
66
|
>
|
|
57
67
|
<IconFilterOutline class="w-4 h-4 me-2"/>
|
|
58
68
|
Filter
|
|
59
69
|
<span
|
|
60
70
|
class="bg-red-100 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"
|
|
61
|
-
v-if="filters.length">
|
|
62
|
-
{{ filters.length }}
|
|
71
|
+
v-if="filtersStore.filters.length">
|
|
72
|
+
{{ filtersStore.filters.length }}
|
|
63
73
|
</span>
|
|
64
74
|
</button>
|
|
65
75
|
</BreadcrumbsWithButtons>
|
|
66
76
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
<component
|
|
78
|
+
v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.afterBreadcrumbs || []"
|
|
79
|
+
:is="getCustomComponent(c)"
|
|
80
|
+
:meta="c.meta"
|
|
81
|
+
:resource="coreStore.resource"
|
|
82
|
+
:adminUser="coreStore.adminUser"
|
|
83
|
+
/>
|
|
84
|
+
|
|
85
|
+
<ResourceListTable
|
|
86
|
+
:resource="coreStore.resource"
|
|
87
|
+
:rows="rows"
|
|
88
|
+
@update:page="page = $event"
|
|
89
|
+
@update:sort="sort = $event"
|
|
90
|
+
@update:checkboxes="checkboxes = $event"
|
|
91
|
+
@update:records="getList"
|
|
92
|
+
:pageSize="pageSize"
|
|
93
|
+
:totalRows="totalRows"
|
|
94
|
+
:checkboxes="checkboxes"
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
<component
|
|
98
|
+
v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.bottom || []"
|
|
99
|
+
:is="getCustomComponent(c)"
|
|
100
|
+
:meta="c.meta"
|
|
101
|
+
:resource="coreStore.resource"
|
|
102
|
+
:adminUser="coreStore.adminUser"
|
|
103
|
+
/>
|
|
81
104
|
|
|
82
|
-
<div class="flex items-center justify-between h-16 pt-4" v-for="i in new Array(10)">
|
|
83
|
-
<div>
|
|
84
|
-
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
|
|
85
|
-
<div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
|
|
86
|
-
</div>
|
|
87
|
-
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
|
|
88
|
-
</div>
|
|
89
|
-
<span class="sr-only">Loading...</span>
|
|
90
|
-
</div>
|
|
91
|
-
|
|
92
|
-
<table v-else class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
|
93
|
-
<thead class="text-xs text-view-table-heading-text bg-list-view-table-heading dark:bg-gray-700 dark:text-gray-400">
|
|
94
|
-
<tr>
|
|
95
|
-
<th scope="col" class="p-4">
|
|
96
|
-
<div v-if="rows && rows.length" class="flex items-center">
|
|
97
|
-
<input id="checkbox-all-search" type="checkbox" :checked="allFromThisPageChecked" @change="selectAll()"
|
|
98
|
-
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">
|
|
99
|
-
<label for="checkbox-all-search" class="sr-only">checkbox</label>
|
|
100
|
-
</div>
|
|
101
|
-
</th>
|
|
102
|
-
|
|
103
|
-
<th v-for="c in columnsListed" scope="col" class="px-6 py-3">
|
|
104
|
-
<div @click="() => c.sortable && onSortButtonClick(c.name)" class="flex items-center " :class="{'cursor-pointer':c.sortable}">
|
|
105
|
-
{{ c.label }}
|
|
106
|
-
|
|
107
|
-
<div v-if="c.sortable"
|
|
108
|
-
:style="{ 'color':ascArr.includes(c.name)?'green':descArr.includes(c.name)?'red':'currentColor'}">
|
|
109
|
-
<svg v-if="ascArr.includes(c.name) || descArr.includes(c.name)" class="w-3 h-3 ms-1.5"
|
|
110
|
-
:class="{'rotate-180':descArr.includes(c.name)}" viewBox="0 0 24 24">
|
|
111
|
-
<path
|
|
112
|
-
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"/>
|
|
113
|
-
</svg>
|
|
114
|
-
<svg v-else class="w-3 h-3 ms-1.5 opacity-30" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
|
|
115
|
-
fill='currentColor'
|
|
116
|
-
viewBox="0 0 24 24">
|
|
117
|
-
<path
|
|
118
|
-
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"/>
|
|
119
|
-
</svg>
|
|
120
|
-
</div>
|
|
121
|
-
</div>
|
|
122
|
-
</th>
|
|
123
|
-
|
|
124
|
-
<th scope="col" class="px-6 py-3">
|
|
125
|
-
Actions
|
|
126
|
-
</th>
|
|
127
|
-
</tr>
|
|
128
|
-
</thead>
|
|
129
|
-
<tbody>
|
|
130
|
-
<tr v-if="!rows" class="bg-list-view-table-bg border-b dark:bg-gray-800 dark:border-gray-700">
|
|
131
|
-
<td :colspan="coreStore.resourceColumns.length + 2">
|
|
132
|
-
|
|
133
|
-
<div role="status"
|
|
134
|
-
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">
|
|
135
|
-
<div class="flex items-center justify-between">
|
|
136
|
-
<div>
|
|
137
|
-
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
|
|
138
|
-
<div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
|
|
139
|
-
</div>
|
|
140
|
-
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
|
|
141
|
-
</div>
|
|
142
|
-
<div class="flex items-center justify-between pt-4">
|
|
143
|
-
<div>
|
|
144
|
-
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
|
|
145
|
-
<div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
|
|
146
|
-
</div>
|
|
147
|
-
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
|
|
148
|
-
</div>
|
|
149
|
-
<div class="flex items-center justify-between pt-4">
|
|
150
|
-
<div>
|
|
151
|
-
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
|
|
152
|
-
<div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
|
|
153
|
-
</div>
|
|
154
|
-
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
|
|
155
|
-
</div>
|
|
156
|
-
<div class="flex items-center justify-between pt-4">
|
|
157
|
-
<div>
|
|
158
|
-
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
|
|
159
|
-
<div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
|
|
160
|
-
</div>
|
|
161
|
-
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
|
|
162
|
-
</div>
|
|
163
|
-
<div class="flex items-center justify-between pt-4">
|
|
164
|
-
<div>
|
|
165
|
-
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
|
|
166
|
-
<div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
|
|
167
|
-
</div>
|
|
168
|
-
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
|
|
169
|
-
</div>
|
|
170
|
-
<span class="sr-only">Loading...</span>
|
|
171
|
-
</div>
|
|
172
|
-
</td>
|
|
173
|
-
</tr>
|
|
174
|
-
<tr v-else-if="rows.length === 0" class="bg-list-view-table-bg border-b dark:bg-gray-800 dark:border-gray-700">
|
|
175
|
-
<td :colspan="coreStore.resourceColumns.length + 2">
|
|
176
|
-
|
|
177
|
-
<div id="toast-simple"
|
|
178
|
-
class=" mx-auto my-5 flex items-center w-full max-w-xs p-4 space-x-4 rtl:space-x-reverse text-gray-500 bg-white divide-x rtl:divide-x-reverse divide-gray-200 dark:text-gray-400 dark:divide-gray-700 space-x dark:bg-gray-800"
|
|
179
|
-
role="alert">
|
|
180
|
-
<IconInboxOutline class="w-6 h-6 text-gray-500 dark:text-gray-400"/>
|
|
181
|
-
<div class="ps-4 text-sm font-normal">No items here yet</div>
|
|
182
|
-
</div>
|
|
183
|
-
|
|
184
|
-
</td>
|
|
185
|
-
</tr>
|
|
186
|
-
|
|
187
|
-
<tr v-else v-for="(row, rowI) in rows" :key="row.id"
|
|
188
|
-
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">
|
|
189
|
-
<td class="w-4 p-4">
|
|
190
|
-
<div class="flex items center">
|
|
191
|
-
<input
|
|
192
|
-
id="checkbox-table-search-1"
|
|
193
|
-
type="checkbox"
|
|
194
|
-
:checked="checkboxes.includes(row.id)"
|
|
195
|
-
@change="(e)=>{addToCheckedValues(row.id)}"
|
|
196
|
-
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">
|
|
197
|
-
<label for="checkbox-table-search-1" class="sr-only">checkbox</label>
|
|
198
|
-
</div>
|
|
199
|
-
</td>
|
|
200
|
-
<td v-for="c in columnsListed" class="px-6 py-4">
|
|
201
|
-
<!-- if c.name in listComponentsPerColumn, render it. If not, render ValueRenderer -->
|
|
202
|
-
<component
|
|
203
|
-
:is="listComponentsPerColumn[c.name] || ValueRenderer"
|
|
204
|
-
:column="c"
|
|
205
|
-
:row="row"
|
|
206
|
-
/>
|
|
207
|
-
</td>
|
|
208
|
-
<td class="flex items-center px-6 py-4">
|
|
209
|
-
|
|
210
|
-
<RouterLink
|
|
211
|
-
:to="{ name: 'resource-show', params: { resourceId: $route.params.resourceId, primaryKey: row._primaryKeyValue } }"
|
|
212
|
-
class="font-medium text-blue-600 dark:text-blue-500 hover:underline"
|
|
213
|
-
:data-tooltip-target="`tooltip-show-${rowI}`"
|
|
214
|
-
>
|
|
215
|
-
<IconEyeSolid class="w-5 h-5 me-2"/>
|
|
216
|
-
</RouterLink>
|
|
217
|
-
|
|
218
|
-
<div :id="`tooltip-show-${rowI}`"
|
|
219
|
-
role="tooltip"
|
|
220
|
-
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">
|
|
221
|
-
Show item
|
|
222
|
-
<div class="tooltip-arrow" data-popper-arrow></div>
|
|
223
|
-
</div>
|
|
224
|
-
|
|
225
|
-
<RouterLink v-if="allowedActions.edit"
|
|
226
|
-
:to="{ name: 'resource-edit', params: { resourceId: $route.params.resourceId, primaryKey: row._primaryKeyValue } }"
|
|
227
|
-
class="font-medium text-blue-600 dark:text-blue-500 hover:underline ms-3"
|
|
228
|
-
:data-tooltip-target="`tooltip-edit-${rowI}`"
|
|
229
|
-
>
|
|
230
|
-
<IconPenSolid class="w-5 h-5 me-2"/>
|
|
231
|
-
</RouterLink>
|
|
232
|
-
|
|
233
|
-
<div :id="`tooltip-edit-${rowI}`"
|
|
234
|
-
role="tooltip"
|
|
235
|
-
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">
|
|
236
|
-
Edit
|
|
237
|
-
<div class="tooltip-arrow" data-popper-arrow></div>
|
|
238
|
-
</div>
|
|
239
|
-
|
|
240
|
-
<button v-if="allowedActions.delete"
|
|
241
|
-
class="font-medium text-red-600 dark:text-red-500 hover:underline ms-3"
|
|
242
|
-
:data-tooltip-target="`tooltip-delete-${rowI}`"
|
|
243
|
-
@click="showDeleteModal(row)"
|
|
244
|
-
>
|
|
245
|
-
<IconTrashBinSolid class="w-5 h-5 me-2"/>
|
|
246
|
-
</button>
|
|
247
|
-
|
|
248
|
-
<div :id="`tooltip-delete-${rowI}`"
|
|
249
|
-
role="tooltip"
|
|
250
|
-
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">
|
|
251
|
-
Delete
|
|
252
|
-
<div class="tooltip-arrow" data-popper-arrow></div>
|
|
253
|
-
</div>
|
|
254
|
-
</td>
|
|
255
|
-
</tr>
|
|
256
|
-
</tbody>
|
|
257
|
-
</table>
|
|
258
|
-
</div>
|
|
259
|
-
<!-- pagination -->
|
|
260
|
-
<div class="flex flex-col items-center mt-4 xs:flex-row xs:justify-between xs:items-center"
|
|
261
|
-
v-if="rows && totalRows >= pageSize && totalRows > 0"
|
|
262
|
-
>
|
|
263
|
-
<!-- Help text -->
|
|
264
|
-
<span class="text-sm text-gray-700 dark:text-gray-400">
|
|
265
|
-
Showing <span class="font-semibold text-gray-900 dark:text-white">
|
|
266
|
-
{{ (page - 1) * pageSize + 1 }}
|
|
267
|
-
</span> to <span class="font-semibold text-gray-900 dark:text-white">
|
|
268
|
-
{{ Math.min(page * pageSize, totalRows) }}
|
|
269
|
-
</span> of <span class="font-semibold text-gray-900 dark:text-white">{{
|
|
270
|
-
totalRows
|
|
271
|
-
}}</span> Entries
|
|
272
|
-
</span>
|
|
273
|
-
<div class="inline-flex mt-2 xs:mt-0">
|
|
274
|
-
<!-- Buttons -->
|
|
275
|
-
<button
|
|
276
|
-
class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 rounded-s border border-gray-300 hover:bg-gray-100 hover:text-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"
|
|
277
|
-
@click="page--" :disabled="page <= 1">
|
|
278
|
-
<svg class="w-3.5 h-3.5 me-2 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
|
|
279
|
-
viewBox="0 0 14 10">
|
|
280
|
-
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
281
|
-
d="M13 5H1m0 0 4 4M1 5l4-4"/>
|
|
282
|
-
</svg>
|
|
283
|
-
Prev
|
|
284
|
-
</button>
|
|
285
|
-
<button
|
|
286
|
-
class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 border border-gray-300 hover:bg-gray-100 hover:text-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"
|
|
287
|
-
@click="page = 1" :disabled="page <= 1">
|
|
288
|
-
<!-- <IconChevronDoubleLeftOutline class="w-4 h-4" /> -->
|
|
289
|
-
1
|
|
290
|
-
</button>
|
|
291
|
-
<input type="text"
|
|
292
|
-
class="w-10 py-1.5 px-3 text-sm text-center text-gray-700 border border-gray-300 dark:border-gray-700 dark:text-gray-400 dark:bg-gray-800 z-10"
|
|
293
|
-
v-model="page"/>
|
|
294
|
-
|
|
295
|
-
<button
|
|
296
|
-
class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-l-0 border border-gray-300 hover:bg-gray-100 hover:text-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"
|
|
297
|
-
@click="page = totalPages" :disabled="page >= totalPages">
|
|
298
|
-
{{ totalPages }}
|
|
299
|
-
<!-- <IconChevronDoubleRightOutline class="w-4 h-4" /> -->
|
|
300
|
-
</button>
|
|
301
|
-
<button
|
|
302
|
-
class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-l-0 rounded-e border border-gray-300 hover:bg-gray-100 hover:text-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"
|
|
303
|
-
|
|
304
|
-
@click="page++" :disabled="page >= totalPages">
|
|
305
|
-
Next
|
|
306
|
-
<svg class="w-3.5 h-3.5 ms-2 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
|
|
307
|
-
viewBox="0 0 14 10">
|
|
308
|
-
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
309
|
-
d="M1 5h12m0 0L9 1m4 4L9 9"/>
|
|
310
|
-
</svg>
|
|
311
|
-
</button>
|
|
312
|
-
</div>
|
|
313
|
-
</div>
|
|
314
105
|
</div>
|
|
315
106
|
</template>
|
|
316
107
|
|
|
317
108
|
<script setup>
|
|
318
109
|
import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
110
|
+
import ResourceListTable from '@/components/ResourceListTable.vue';
|
|
319
111
|
import { useCoreStore } from '@/stores/core';
|
|
320
112
|
import { useModalStore } from '@/stores/modal';
|
|
113
|
+
import { useFiltersStore } from '@/stores/filters';
|
|
321
114
|
import { callAdminForthApi, getIcon } from '@/utils';
|
|
322
115
|
import { initFlowbite } from 'flowbite';
|
|
323
116
|
import { computed, onMounted, ref, watch } from 'vue';
|
|
324
117
|
import { useRoute } from 'vue-router';
|
|
118
|
+
import { showErrorTost } from '@/composables/useFrontendApi'
|
|
325
119
|
|
|
326
120
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
327
121
|
import { getCustomComponent } from '@/utils';
|
|
328
122
|
|
|
329
|
-
import {
|
|
330
|
-
IconInboxOutline,
|
|
331
|
-
IconPlusOutline
|
|
332
|
-
} from '@iconify-prerendered/vue-flowbite';
|
|
333
123
|
|
|
334
124
|
import {
|
|
335
|
-
IconBanOutline,
|
|
336
|
-
IconEyeSolid,
|
|
337
|
-
IconFilterOutline,
|
|
338
|
-
IconPenSolid,
|
|
339
|
-
IconTrashBinSolid
|
|
125
|
+
IconBanOutline,
|
|
126
|
+
IconEyeSolid,
|
|
127
|
+
IconFilterOutline,
|
|
128
|
+
IconPenSolid,
|
|
129
|
+
IconTrashBinSolid,
|
|
130
|
+
IconInboxOutline,
|
|
131
|
+
IconPlusOutline
|
|
340
132
|
} from '@iconify-prerendered/vue-flowbite';
|
|
341
133
|
|
|
342
134
|
import Filters from '@/components/Filters.vue';
|
|
343
135
|
|
|
344
136
|
const filtersShow = ref(false);
|
|
345
137
|
|
|
346
|
-
|
|
347
138
|
const coreStore = useCoreStore();
|
|
348
139
|
const modalStore = useModalStore();
|
|
140
|
+
const filtersStore = useFiltersStore();
|
|
349
141
|
|
|
350
142
|
const route = useRoute();
|
|
351
|
-
const checkboxes = ref([]);
|
|
352
143
|
|
|
353
144
|
const page = ref(1);
|
|
354
|
-
const filters =
|
|
145
|
+
const filters = filtersStore.filters;
|
|
355
146
|
const columnsMinMax = ref({});
|
|
356
147
|
const sort = ref([]);
|
|
357
|
-
const fetchStatus = ref({pending: false, error: null, success: false});
|
|
358
148
|
|
|
359
149
|
const rows = ref(null);
|
|
360
150
|
const totalRows = ref(0);
|
|
361
|
-
const
|
|
362
|
-
const allowedActions = ref({});
|
|
151
|
+
const checkboxes = ref([]);
|
|
363
152
|
|
|
364
153
|
const DEFAULT_PAGE_SIZE = 10;
|
|
365
154
|
|
|
366
|
-
const columnsListed = computed(() => coreStore.resourceColumns?.filter(c => c.showIn.includes('list')));
|
|
367
155
|
|
|
368
|
-
|
|
369
|
-
console.log('select all');
|
|
370
|
-
rows.value.forEach((r) => {
|
|
371
|
-
if (!checkboxes.value.includes(r.id)) {
|
|
372
|
-
checkboxes.value.push(r.id)
|
|
373
|
-
} else {
|
|
374
|
-
checkboxes.value = checkboxes.value.filter((item) => item !== r.id)
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
// checkboxes.value = rows.value.map((v) => v.id);
|
|
378
|
-
}
|
|
156
|
+
const pageSize = computed(() => coreStore.resource?.options?.listPageSize || DEFAULT_PAGE_SIZE);
|
|
379
157
|
|
|
380
|
-
const pageSize = computed(() => coreStore.resourceById[route.params.resourceId]?.pageSize || DEFAULT_PAGE_SIZE);
|
|
381
|
-
const totalPages = computed(() => Math.ceil(totalRows.value / pageSize.value));
|
|
382
|
-
let listComponentsPerColumn = {};
|
|
383
|
-
const allFromThisPageChecked = computed(() => {
|
|
384
|
-
if (!rows.value) return false;
|
|
385
|
-
return rows.value.every((r) => checkboxes.value.includes(r.id));
|
|
386
|
-
});
|
|
387
|
-
const ascArr = computed(() => sort.value.filter((s) => s.direction === 'asc').map((s) => s.field));
|
|
388
|
-
const descArr = computed(() => sort.value.filter((s) => s.direction === 'desc').map((s) => s.field));
|
|
389
158
|
|
|
390
159
|
watch([page], async () => {
|
|
391
|
-
await
|
|
160
|
+
await getList();
|
|
392
161
|
});
|
|
393
162
|
|
|
394
|
-
watch(
|
|
163
|
+
watch(()=>filtersStore.filters, async () => {
|
|
395
164
|
page.value = 1;
|
|
165
|
+
checkboxes.value = [];
|
|
166
|
+
|
|
396
167
|
await getList();
|
|
397
168
|
}, {deep: true});
|
|
398
169
|
|
|
399
170
|
watch([sort], async () => {
|
|
400
|
-
await
|
|
171
|
+
await getList();
|
|
401
172
|
}, {deep: true});
|
|
402
173
|
|
|
403
|
-
function onSortButtonClick(field) {
|
|
404
|
-
if (fetchStatus.value.pending) return;
|
|
405
|
-
const sortIndex = sort.value.findIndex((s) => s.field === field);
|
|
406
|
-
console.log('sortIndex', sortIndex);
|
|
407
|
-
if (sortIndex === -1) {
|
|
408
|
-
sort.value = [{field, direction: 'asc'}, ...sort.value];
|
|
409
|
-
} else {
|
|
410
|
-
const sortField = sort.value[sortIndex];
|
|
411
|
-
if (sortField.direction === 'asc') {
|
|
412
|
-
sort.value[sortIndex].direction = 'desc';
|
|
413
|
-
} else {
|
|
414
|
-
sort.value.splice(sortIndex, 1);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
174
|
|
|
419
175
|
async function getList() {
|
|
420
176
|
rows.value = null;
|
|
421
|
-
fetchStatus.value.pending = true;
|
|
422
177
|
const data = await callAdminForthApi({
|
|
423
178
|
path: '/get_resource_data',
|
|
424
179
|
method: 'POST',
|
|
@@ -427,64 +182,27 @@ async function getList() {
|
|
|
427
182
|
resourceId: route.params.resourceId,
|
|
428
183
|
limit: pageSize.value,
|
|
429
184
|
offset: (page.value - 1) * pageSize.value,
|
|
430
|
-
filters: filters
|
|
185
|
+
filters: filtersStore.filters,
|
|
431
186
|
sort: sort.value,
|
|
432
187
|
}
|
|
433
188
|
});
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
fetchStatus.value.pending = false;
|
|
189
|
+
if (data.error) {
|
|
190
|
+
showErrorTost(data.error);
|
|
191
|
+
rows.value = [];
|
|
192
|
+
totalRows.value = 0;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
442
195
|
rows.value = data.data?.map(row => {
|
|
443
|
-
row._primaryKeyValue = row[coreStore.
|
|
196
|
+
row._primaryKeyValue = row[coreStore.resource.columns.find(c => c.primaryKey).name];
|
|
444
197
|
return row;
|
|
445
198
|
});
|
|
446
199
|
totalRows.value = data.total;
|
|
447
|
-
allCheckedActions.value = data.options?.bulkActions || [];
|
|
448
|
-
allowedActions.value = data.options?.allowedActions;
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
setTimeout(() => {
|
|
452
|
-
initFlowbite();
|
|
453
|
-
console.log('initFlowbite');
|
|
454
|
-
});
|
|
455
200
|
}
|
|
456
201
|
|
|
457
202
|
|
|
458
|
-
function showDeleteModal(row) {
|
|
459
|
-
if (!coreStore.config?.deleteConfirmation) {
|
|
460
|
-
return deleteRecord(row);
|
|
461
|
-
}
|
|
462
|
-
modalStore.setModalContent({
|
|
463
|
-
content: 'Are you sure you want to delete this item?',
|
|
464
|
-
acceptText: 'Delete',
|
|
465
|
-
cancelText: 'Cancel',
|
|
466
|
-
});
|
|
467
|
-
modalStore.setOnAcceptFunction(() => {
|
|
468
|
-
return deleteRecord(row)
|
|
469
|
-
})
|
|
470
|
-
console.log('row', row);
|
|
471
|
-
modalStore.togleModal();
|
|
472
203
|
|
|
473
|
-
}
|
|
474
204
|
|
|
475
|
-
|
|
476
|
-
await callAdminForthApi({
|
|
477
|
-
path: '/delete_record',
|
|
478
|
-
method: 'POST',
|
|
479
|
-
body: {
|
|
480
|
-
resourceId: route.params.resourceId,
|
|
481
|
-
primaryKey: row._primaryKeyValue,
|
|
482
|
-
recordId: row.id
|
|
483
|
-
}
|
|
484
|
-
});
|
|
485
|
-
await getList();
|
|
486
|
-
modalStore.resetmodalState()
|
|
487
|
-
}
|
|
205
|
+
|
|
488
206
|
|
|
489
207
|
async function startBulkAction(actionId) {
|
|
490
208
|
const data = await callAdminForthApi({
|
|
@@ -503,16 +221,11 @@ async function startBulkAction(actionId) {
|
|
|
503
221
|
await getList();
|
|
504
222
|
}
|
|
505
223
|
|
|
506
|
-
|
|
507
|
-
if (checkboxes.value.includes(id)) {
|
|
508
|
-
checkboxes.value = checkboxes.value.filter((item) => item !== id);
|
|
509
|
-
} else {
|
|
510
|
-
checkboxes.value.push(id);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
224
|
+
|
|
513
225
|
|
|
514
226
|
async function init() {
|
|
515
|
-
|
|
227
|
+
|
|
228
|
+
await coreStore.fetchResourceFull({
|
|
516
229
|
resourceId: route.params.resourceId
|
|
517
230
|
});
|
|
518
231
|
|
|
@@ -527,12 +240,15 @@ async function init() {
|
|
|
527
240
|
}
|
|
528
241
|
|
|
529
242
|
onMounted(async () => {
|
|
243
|
+
initFlowbite();
|
|
244
|
+
|
|
530
245
|
await init();
|
|
246
|
+
|
|
531
247
|
});
|
|
532
248
|
|
|
533
249
|
// on route param change
|
|
534
250
|
watch(() => route.params.resourceId, async () => {
|
|
535
|
-
|
|
251
|
+
filtersStore.setFilters([]);
|
|
536
252
|
checkboxes.value = [];
|
|
537
253
|
sort.value = [];
|
|
538
254
|
await init();
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="relative flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-800"
|
|
3
3
|
:style="coreStore.config?.loginBackgroundImage ? {
|
|
4
|
-
'background-image': 'url(' + coreStore.config?.loginBackgroundImage + ')',
|
|
4
|
+
'background-image': 'url(' + loadFile(coreStore.config?.loginBackgroundImage) + ')',
|
|
5
5
|
'background-size': 'cover',
|
|
6
6
|
'background-position': 'center',
|
|
7
7
|
'background-blend-mode': 'darken'
|
|
8
8
|
}: {}"
|
|
9
9
|
>
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
<!-- Main modal -->
|
|
11
12
|
<div id="authentication-modal" tabindex="-1" class=" overflow-y-auto overflow-x-hidden z-50 min-w-[400px] justify-center items-center md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
|
12
13
|
<div class="relative p-4 w-full max-w-md max-h-full">
|
|
13
14
|
<!-- Modal content -->
|
|
@@ -78,16 +79,21 @@
|
|
|
78
79
|
|
|
79
80
|
<script setup>
|
|
80
81
|
|
|
81
|
-
import { onMounted, ref } from 'vue';
|
|
82
|
+
import { onMounted, ref, watchEffect } from 'vue';
|
|
82
83
|
import { useCoreStore } from '@/stores/core';
|
|
84
|
+
import { useUserStore } from '@/stores/user';
|
|
83
85
|
import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
|
|
84
|
-
import { callAdminForthApi } from '@/utils';
|
|
86
|
+
import { callAdminForthApi, loadFile } from '@/utils';
|
|
85
87
|
import { useRouter } from 'vue-router';
|
|
88
|
+
import { initFlowbite } from 'flowbite'
|
|
89
|
+
|
|
86
90
|
|
|
87
91
|
const router = useRouter();
|
|
88
92
|
const inProgress = ref(false);
|
|
89
93
|
|
|
90
94
|
const coreStore = useCoreStore();
|
|
95
|
+
const user = useUserStore();
|
|
96
|
+
|
|
91
97
|
|
|
92
98
|
const showPw = ref(false);
|
|
93
99
|
|
|
@@ -112,8 +118,14 @@ async function login() {
|
|
|
112
118
|
error.value = resp.error;
|
|
113
119
|
} else {
|
|
114
120
|
error.value = null;
|
|
121
|
+
user.authorize()
|
|
115
122
|
router.push('/');
|
|
123
|
+
await router.isReady();
|
|
116
124
|
await coreStore.fetchMenuAndResource();
|
|
125
|
+
setTimeout(() => {
|
|
126
|
+
initFlowbite();
|
|
127
|
+
});
|
|
128
|
+
|
|
117
129
|
}
|
|
118
130
|
|
|
119
131
|
}
|