adminforth 1.1.22 → 1.1.23
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/dist/plugins/AccessControl/index.js +56 -3
- package/dist/spa/index.html +2 -2
- package/dist/spa/package-lock.json +189 -1
- package/dist/spa/package.json +6 -1
- package/dist/spa/spa/src/composables/useFrontendApi.ts +12 -0
- package/dist/spa/spa/src/views/ShowView.vue +22 -27
- package/dist/spa/src/App.vue +76 -57
- package/dist/spa/src/components/AcceptModal.vue +1 -1
- package/dist/spa/src/components/CustomDatePicker.vue +3 -3
- package/dist/spa/src/components/CustomDateRangePicker.vue +3 -3
- package/dist/spa/src/components/CustomRangePicker.vue +148 -0
- package/dist/spa/src/components/Dropdown.vue +13 -4
- package/dist/spa/src/components/Filters.vue +55 -22
- package/dist/spa/src/components/MenuLink.vue +2 -2
- package/dist/spa/src/components/ResourceForm.vue +157 -99
- package/dist/spa/src/components/ValueRenderer.vue +11 -1
- package/dist/spa/src/router/index.ts +3 -2
- package/dist/spa/src/stores/core.ts +22 -33
- package/dist/spa/src/utils.ts +5 -3
- package/dist/spa/src/views/CreateView.vue +15 -7
- package/dist/spa/src/views/EditView.vue +36 -8
- package/dist/spa/src/views/ListView.vue +35 -23
- package/dist/spa/src/views/LoginView.vue +1 -1
- package/dist/spa/src/views/ResourceParent.vue +1 -1
- package/dist/spa/src/views/ShowView.vue +20 -9
- package/dist/spa/tailwind.config.js +5 -2
- package/index.ts +0 -1
- package/package.json +1 -1
- package/spa/src/composables/useFrontendApi.ts +12 -0
- package/spa/src/views/ShowView.vue +22 -27
- package/dist/plugins/AuditLog/index.js +0 -66
- package/dist/plugins/AuditLog/types.js +0 -1
- package/dist/plugins/plugins/ForeignInlineListPlugin/custom/InlineList.vue +0 -248
- package/dist/spa/spa/src/components/ShowTableItem.vue +0 -14
- package/dist/spa/spa/src/views/HomeView.vue +0 -8
- package/dist/spa/src/views/HomeView.vue +0 -8
- package/plugins/AccessControl/index.ts +0 -29
- package/plugins/AccessControl/types.ts +0 -7
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
</BreadcrumbsWithButtons>
|
|
66
66
|
|
|
67
67
|
<!-- table -->
|
|
68
|
-
<div class="relative overflow-x-auto shadow-
|
|
68
|
+
<div class="relative overflow-x-auto shadow-list-table-shadow dark:shadow-black dark:s">
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
<!-- skelet loader -->
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
</div>
|
|
91
91
|
|
|
92
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-
|
|
93
|
+
<thead class="text-xs text-view-table-heading-text bg-list-view-table-heading dark:bg-gray-700 dark:text-gray-400">
|
|
94
94
|
<tr>
|
|
95
95
|
<th scope="col" class="p-4">
|
|
96
96
|
<div v-if="rows && rows.length" class="flex items-center">
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
</tr>
|
|
128
128
|
</thead>
|
|
129
129
|
<tbody>
|
|
130
|
-
<tr v-if="!rows" class="bg-
|
|
130
|
+
<tr v-if="!rows" class="bg-list-view-table-bg border-b dark:bg-gray-800 dark:border-gray-700">
|
|
131
131
|
<td :colspan="coreStore.resourceColumns.length + 2">
|
|
132
132
|
|
|
133
133
|
<div role="status"
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
</div>
|
|
172
172
|
</td>
|
|
173
173
|
</tr>
|
|
174
|
-
<tr v-else-if="rows.length === 0" class="bg-
|
|
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
175
|
<td :colspan="coreStore.resourceColumns.length + 2">
|
|
176
176
|
|
|
177
177
|
<div id="toast-simple"
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
</tr>
|
|
186
186
|
|
|
187
187
|
<tr v-else v-for="(row, rowI) in rows" :key="row.id"
|
|
188
|
-
class="bg-
|
|
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
189
|
<td class="w-4 p-4">
|
|
190
190
|
<div class="flex items center">
|
|
191
191
|
<input
|
|
@@ -198,7 +198,12 @@
|
|
|
198
198
|
</div>
|
|
199
199
|
</td>
|
|
200
200
|
<td v-for="c in columnsListed" class="px-6 py-4">
|
|
201
|
-
|
|
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
|
+
/>
|
|
202
207
|
</td>
|
|
203
208
|
<td class="flex items-center px-6 py-4">
|
|
204
209
|
|
|
@@ -310,29 +315,28 @@
|
|
|
310
315
|
</template>
|
|
311
316
|
|
|
312
317
|
<script setup>
|
|
313
|
-
import {ref, onMounted, watch, computed} from 'vue';
|
|
314
|
-
import {callAdminForthApi, getIcon} from '@/utils';
|
|
315
|
-
import {useRoute} from 'vue-router';
|
|
316
|
-
import {useCoreStore} from '@/stores/core';
|
|
317
|
-
import {useModalStore} from '@/stores/modal';
|
|
318
318
|
import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
319
|
-
import {
|
|
319
|
+
import { useCoreStore } from '@/stores/core';
|
|
320
|
+
import { useModalStore } from '@/stores/modal';
|
|
321
|
+
import { callAdminForthApi, getIcon } from '@/utils';
|
|
322
|
+
import { initFlowbite } from 'flowbite';
|
|
323
|
+
import { computed, onMounted, ref, watch } from 'vue';
|
|
324
|
+
import { useRoute } from 'vue-router';
|
|
320
325
|
|
|
321
326
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
327
|
+
import { getCustomComponent } from '@/utils';
|
|
322
328
|
|
|
323
329
|
import {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
IconInboxFullSolid,
|
|
327
|
-
IconInboxOutline,
|
|
328
|
-
IconPlusOutline
|
|
330
|
+
IconInboxOutline,
|
|
331
|
+
IconPlusOutline
|
|
329
332
|
} from '@iconify-prerendered/vue-flowbite';
|
|
330
333
|
|
|
331
334
|
import {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
335
|
+
IconBanOutline,
|
|
336
|
+
IconEyeSolid,
|
|
337
|
+
IconFilterOutline,
|
|
338
|
+
IconPenSolid,
|
|
339
|
+
IconTrashBinSolid
|
|
336
340
|
} from '@iconify-prerendered/vue-flowbite';
|
|
337
341
|
|
|
338
342
|
import Filters from '@/components/Filters.vue';
|
|
@@ -357,7 +361,6 @@ const totalRows = ref(0);
|
|
|
357
361
|
const allCheckedActions = ref([]);
|
|
358
362
|
const allowedActions = ref({});
|
|
359
363
|
|
|
360
|
-
|
|
361
364
|
const DEFAULT_PAGE_SIZE = 10;
|
|
362
365
|
|
|
363
366
|
const columnsListed = computed(() => coreStore.resourceColumns?.filter(c => c.showIn.includes('list')));
|
|
@@ -376,6 +379,7 @@ async function selectAll(value) {
|
|
|
376
379
|
|
|
377
380
|
const pageSize = computed(() => coreStore.resourceById[route.params.resourceId]?.pageSize || DEFAULT_PAGE_SIZE);
|
|
378
381
|
const totalPages = computed(() => Math.ceil(totalRows.value / pageSize.value));
|
|
382
|
+
let listComponentsPerColumn = {};
|
|
379
383
|
const allFromThisPageChecked = computed(() => {
|
|
380
384
|
if (!rows.value) return false;
|
|
381
385
|
return rows.value.every((r) => checkboxes.value.includes(r.id));
|
|
@@ -419,6 +423,7 @@ async function getList() {
|
|
|
419
423
|
path: '/get_resource_data',
|
|
420
424
|
method: 'POST',
|
|
421
425
|
body: {
|
|
426
|
+
source: 'list',
|
|
422
427
|
resourceId: route.params.resourceId,
|
|
423
428
|
limit: pageSize.value,
|
|
424
429
|
offset: (page.value - 1) * pageSize.value,
|
|
@@ -426,7 +431,14 @@ async function getList() {
|
|
|
426
431
|
sort: sort.value,
|
|
427
432
|
}
|
|
428
433
|
});
|
|
429
|
-
|
|
434
|
+
listComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
|
|
435
|
+
if (column.component?.list) {
|
|
436
|
+
acc[column.name] = getCustomComponent(column.component.list);
|
|
437
|
+
}
|
|
438
|
+
return acc;
|
|
439
|
+
}, {});
|
|
440
|
+
|
|
441
|
+
fetchStatus.value.pending = false;
|
|
430
442
|
rows.value = data.data?.map(row => {
|
|
431
443
|
row._primaryKeyValue = row[coreStore.resourceColumns.find(c => c.primaryKey).name];
|
|
432
444
|
return row;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<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
12
|
<div class="relative p-4 w-full max-w-md max-h-full">
|
|
13
13
|
<!-- Modal content -->
|
|
14
|
-
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
|
14
|
+
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black" >
|
|
15
15
|
<!-- Modal header -->
|
|
16
16
|
<div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
|
|
17
17
|
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
</div>
|
|
28
28
|
<div
|
|
29
29
|
v-else
|
|
30
|
-
class="relative overflow-x-auto shadow-
|
|
30
|
+
class="relative overflow-x-auto shadow-resourse-form-shadow "
|
|
31
31
|
>
|
|
32
32
|
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
|
33
|
-
<thead class="text-xs text-gray-700 uppercase bg-
|
|
33
|
+
<thead class="text-xs text-gray-700 uppercase bg-form-view-heading dark:bg-gray-700 dark:text-gray-400">
|
|
34
34
|
<tr>
|
|
35
35
|
<th scope="col" class="px-6 py-3">
|
|
36
36
|
Field
|
|
@@ -42,13 +42,17 @@
|
|
|
42
42
|
</thead>
|
|
43
43
|
<tbody>
|
|
44
44
|
<tr v-for="column in coreStore.resourceColumns?.filter(c => c.showIn.includes('show'))" :key="column.name"
|
|
45
|
-
class="
|
|
45
|
+
class="bg-form-view-bg odd:dark:bg-gray-900 even:dark:bg-gray-800 border-b dark:border-gray-700"
|
|
46
46
|
>
|
|
47
47
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
48
48
|
{{ column.label }}
|
|
49
49
|
</td>
|
|
50
50
|
<td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap">
|
|
51
|
-
<
|
|
51
|
+
<component
|
|
52
|
+
:is="showComponentsPerColumn[column.name] || ValueRenderer"
|
|
53
|
+
:column="column"
|
|
54
|
+
:row="coreStore.record"
|
|
55
|
+
/>
|
|
52
56
|
</td>
|
|
53
57
|
</tr>
|
|
54
58
|
|
|
@@ -63,18 +67,19 @@
|
|
|
63
67
|
|
|
64
68
|
<script setup>
|
|
65
69
|
|
|
66
|
-
import { ref, computed, onMounted } from 'vue';
|
|
67
|
-
import { useRoute } from 'vue-router';
|
|
68
|
-
import { callAdminForthApi } from '@/utils';
|
|
69
70
|
import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
70
|
-
import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
|
|
71
|
-
import { useCoreStore } from '@/stores/core';
|
|
72
71
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
72
|
+
import { useCoreStore } from '@/stores/core';
|
|
73
|
+
import { getCustomComponent } from '@/utils';
|
|
74
|
+
import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
|
|
75
|
+
import { onMounted, ref } from 'vue';
|
|
76
|
+
import { useRoute } from 'vue-router';
|
|
73
77
|
|
|
74
78
|
|
|
75
79
|
const item = ref(null);
|
|
76
80
|
const route = useRoute();
|
|
77
81
|
const loading = ref(false);
|
|
82
|
+
let showComponentsPerColumn = {};
|
|
78
83
|
|
|
79
84
|
const coreStore = useCoreStore();
|
|
80
85
|
|
|
@@ -87,6 +92,12 @@ onMounted(async () => {
|
|
|
87
92
|
resourceId: route.params.resourceId,
|
|
88
93
|
primaryKey: route.params.primaryKey,
|
|
89
94
|
});
|
|
95
|
+
showComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
|
|
96
|
+
if (column.component?.show) {
|
|
97
|
+
acc[column.name] = getCustomComponent(column.component.show);
|
|
98
|
+
}
|
|
99
|
+
return acc;
|
|
100
|
+
}, {});
|
|
90
101
|
loading.value = false;
|
|
91
102
|
});
|
|
92
103
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/** @type {import('tailwindcss').Config} */
|
|
2
2
|
export default {
|
|
3
|
-
content: ["./src/**/*.{vue, js}", "./node_modules/flowbite/**/*.js"],
|
|
3
|
+
content: ["./src/**/*.{vue, js}","./src/*.{vue, js}", "./index.html", "./node_modules/flowbite/**/*.js"],
|
|
4
4
|
theme: {
|
|
5
|
-
extend: {
|
|
5
|
+
extend: {
|
|
6
|
+
/* IMPORTANT:ADMINFORTH TAILWIND STYLES */
|
|
7
|
+
}
|
|
6
8
|
},
|
|
9
|
+
|
|
7
10
|
darkMode: 'class',
|
|
8
11
|
plugins: [
|
|
9
12
|
require('flowbite/plugin'),
|
package/index.ts
CHANGED
|
@@ -20,7 +20,6 @@ import { AdminForthConfig, AdminForthClass, AdminForthComponentDeclaration, Admi
|
|
|
20
20
|
} from './types/AdminForthConfig.js';
|
|
21
21
|
import path from 'path';
|
|
22
22
|
import AdminForthPlugin from './plugins/base.js';
|
|
23
|
-
import { tr } from '@faker-js/faker';
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
//get array from enum AdminForthResourcePages
|
package/package.json
CHANGED
|
@@ -12,3 +12,15 @@ export function showErrorTost(message: string, timeout?: number) {
|
|
|
12
12
|
window.adminforth.alert({ message, variant: 'danger', timeout: timeout || 'unlimited'});
|
|
13
13
|
return message;
|
|
14
14
|
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
const useFrontendApi = () => {
|
|
18
|
+
return {
|
|
19
|
+
showSuccesTost,
|
|
20
|
+
showWarningTost,
|
|
21
|
+
showErrorTost
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export default useFrontendApi;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
Edit
|
|
17
17
|
</RouterLink>
|
|
18
18
|
|
|
19
|
-
<button v-if="coreStore?.resourceOptions?.allowedActions?.delete" @click="
|
|
19
|
+
<button v-if="coreStore?.resourceOptions?.allowedActions?.delete" @click="deleteRecord"
|
|
20
20
|
class="flex items-center py-1 px-3 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-red-500 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
|
21
21
|
>
|
|
22
22
|
<IconTrashBinSolid class="w-4 h-4" />
|
|
@@ -120,6 +120,7 @@ import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbi
|
|
|
120
120
|
import { onMounted, ref } from 'vue';
|
|
121
121
|
import { useRoute,useRouter } from 'vue-router';
|
|
122
122
|
import {callAdminForthApi} from '@/utils';
|
|
123
|
+
import {showSuccesTost} from '@/composables/useFrontendApi';
|
|
123
124
|
|
|
124
125
|
|
|
125
126
|
const item = ref(null);
|
|
@@ -154,40 +155,34 @@ onMounted(async () => {
|
|
|
154
155
|
loading.value = false;
|
|
155
156
|
});
|
|
156
157
|
|
|
157
|
-
function showDeleteModal(row) {
|
|
158
|
-
if (!coreStore.config?.deleteConfirmation) {
|
|
159
|
-
return deleteRecord(row);
|
|
160
|
-
}
|
|
161
|
-
modalStore.setModalContent({
|
|
162
|
-
content: 'Are you sure you want to delete this item?',
|
|
163
|
-
acceptText: 'Delete',
|
|
164
|
-
cancelText: 'Cancel',
|
|
165
|
-
});
|
|
166
|
-
modalStore.setOnAcceptFunction(() => {
|
|
167
|
-
return deleteRecord(row)
|
|
168
|
-
})
|
|
169
|
-
modalStore.togleModal();
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
158
|
async function deleteRecord(row) {
|
|
175
|
-
|
|
176
|
-
|
|
159
|
+
const data = await window.adminforth.confirm({
|
|
160
|
+
message: 'Are you sure you want to delete this item?',
|
|
161
|
+
yes: 'Delete',
|
|
162
|
+
no: 'Cancel',
|
|
163
|
+
});
|
|
164
|
+
if (data) {
|
|
165
|
+
try {
|
|
166
|
+
const res = await callAdminForthApi({
|
|
177
167
|
path: '/delete_record',
|
|
178
168
|
method: 'POST',
|
|
179
169
|
body: {
|
|
180
170
|
resourceId: route.params.resourceId,
|
|
181
171
|
primaryKey: route.params.primaryKey,
|
|
182
|
-
|
|
172
|
+
}});
|
|
173
|
+
if (!res.error){
|
|
174
|
+
router.push({ name: 'resource-list', params: { resourceId: route.params.resourceId } });
|
|
175
|
+
showSuccesTost('Record deleted successfully')
|
|
176
|
+
} else {
|
|
177
|
+
console.error(res.error)
|
|
183
178
|
}
|
|
184
|
-
});
|
|
185
|
-
router.push({ name: 'resource-list', params: { resourceId: route.params.resourceId } });
|
|
186
|
-
} catch (error) {
|
|
187
|
-
console.log(error);
|
|
188
|
-
}
|
|
189
179
|
|
|
190
|
-
|
|
180
|
+
} catch (e) {
|
|
181
|
+
console.error(e);
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
191
186
|
}
|
|
192
187
|
|
|
193
188
|
</script>
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
|
-
import { AllowedActionsEnum } from "../../types/AdminForthConfig.js";
|
|
11
|
-
import AdminForthPlugin from "../base.js";
|
|
12
|
-
class AccessControlPlugin extends AdminForthPlugin {
|
|
13
|
-
constructor(options) {
|
|
14
|
-
super(options, import.meta.url);
|
|
15
|
-
this.options = options;
|
|
16
|
-
}
|
|
17
|
-
modifyResourceConfig(adminforth, resourceConfig) {
|
|
18
|
-
super.modifyResourceConfig(adminforth, resourceConfig);
|
|
19
|
-
this.adminforth = adminforth;
|
|
20
|
-
if (!resourceConfig.hooks) {
|
|
21
|
-
resourceConfig.hooks = {};
|
|
22
|
-
}
|
|
23
|
-
const checkAccess = (adminUser, action, meta) => __awaiter(this, void 0, void 0, function* () {
|
|
24
|
-
const hasAccessOrError = yield this.options.hasAccess(adminUser, action, meta);
|
|
25
|
-
if (hasAccessOrError === true) {
|
|
26
|
-
return { ok: true };
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
return { ok: false, error: hasAccessOrError || AccessControlPlugin.defaultError };
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
const bindHookCheck = (action, hookName) => {
|
|
33
|
-
if (!resourceConfig.hooks[action]) {
|
|
34
|
-
resourceConfig.hooks[action] = {};
|
|
35
|
-
}
|
|
36
|
-
if (!resourceConfig.hooks[action][hookName]) {
|
|
37
|
-
resourceConfig.hooks[action][hookName] = [];
|
|
38
|
-
}
|
|
39
|
-
if (hookName === 'beforeDatasourceRequest') {
|
|
40
|
-
resourceConfig.hooks[action][hookName].unshift((_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, query }) {
|
|
41
|
-
return checkAccess(adminUser, action, { query });
|
|
42
|
-
}));
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
resourceConfig.hooks[action][hookName].unshift((_b) => __awaiter(this, [_b], void 0, function* ({ adminUser, record }) {
|
|
46
|
-
return checkAccess(adminUser, action, { record });
|
|
47
|
-
}));
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
// List check
|
|
51
|
-
bindHookCheck(AllowedActionsEnum.list, 'beforeDatasourceRequest');
|
|
52
|
-
// Show check
|
|
53
|
-
bindHookCheck(AllowedActionsEnum.show, 'beforeDatasourceRequest');
|
|
54
|
-
// Edit check
|
|
55
|
-
bindHookCheck(AllowedActionsEnum.edit, 'beforeDatasourceRequest');
|
|
56
|
-
// create check
|
|
57
|
-
bindHookCheck(AllowedActionsEnum.create, 'beforeSave');
|
|
58
|
-
// edit check
|
|
59
|
-
bindHookCheck(AllowedActionsEnum.edit, 'beforeSave');
|
|
60
|
-
// delete check
|
|
61
|
-
bindHookCheck(AllowedActionsEnum.delete, 'beforeSave');
|
|
62
|
-
console.log('resourceConfig', resourceConfig);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
AccessControlPlugin.defaultError = 'Sorry, you do not have access to this resource.';
|
|
66
|
-
export default AccessControlPlugin;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<Teleport to="body">
|
|
3
|
-
<!-- todo exclude foreign column-->
|
|
4
|
-
<Filters
|
|
5
|
-
v-if="listResource"
|
|
6
|
-
:columns="listResource.columns.filter((c) => c.name !== listResourceRefColumn.name)"
|
|
7
|
-
v-model:filters="filters"
|
|
8
|
-
:columnsMinMax="columnsMinMax"
|
|
9
|
-
:show="filtersShow"
|
|
10
|
-
@hide="filtersShow = false"
|
|
11
|
-
/>
|
|
12
|
-
</Teleport>
|
|
13
|
-
|
|
14
|
-
<td colspan="2">
|
|
15
|
-
<div class="flex items-center gap-1">
|
|
16
|
-
<h4 v-if="listResource"
|
|
17
|
-
class="px-6 py-4"
|
|
18
|
-
>{{ listResource.label }} inline records</h4>
|
|
19
|
-
|
|
20
|
-
<button
|
|
21
|
-
@click="()=>{checkboxes = []}"
|
|
22
|
-
v-if="checkboxes.length"
|
|
23
|
-
data-tooltip-target="tooltip-remove-all"
|
|
24
|
-
data-tooltip-placement="bottom"
|
|
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"
|
|
26
|
-
>
|
|
27
|
-
<IconBanOutline class="w-5 h-5 "/>
|
|
28
|
-
<div id="tooltip-remove-all" role="tooltip"
|
|
29
|
-
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">
|
|
30
|
-
Remove selection
|
|
31
|
-
<div class="tooltip-arrow" data-popper-arrow></div>
|
|
32
|
-
</div>
|
|
33
|
-
</button>
|
|
34
|
-
|
|
35
|
-
<button
|
|
36
|
-
v-if="checkboxes.length"
|
|
37
|
-
v-for="(action,i) in listResource?.options?.bulkActions"
|
|
38
|
-
:key="action.id"
|
|
39
|
-
@click="startBulkAction(action.id)"
|
|
40
|
-
class="flex gap-1 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"
|
|
41
|
-
: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',
|
|
42
|
-
'bg-blue-100 text-blue-800 border-blue-400 dark:bg-blue-700 dark:text-blue-400 dark:border-blue-400':action.state==='active',
|
|
43
|
-
}"
|
|
44
|
-
>
|
|
45
|
-
<component
|
|
46
|
-
v-if="action.icon"
|
|
47
|
-
:is="getIcon(action.icon)"
|
|
48
|
-
class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white"></component>
|
|
49
|
-
|
|
50
|
-
{{ `${action.label} (${checkboxes.length})` }}
|
|
51
|
-
</button>
|
|
52
|
-
|
|
53
|
-
<RouterLink v-if="listResource?.options?.allowedActions?.create"
|
|
54
|
-
:to="{
|
|
55
|
-
name: 'resource-create',
|
|
56
|
-
params: { resourceId: listResource.resourceId },
|
|
57
|
-
query: {
|
|
58
|
-
values: encodeURIComponent(JSON.stringify({[listResourceRefColumn.name]: props.record[selfPrimaryKeyColumn.name]})),
|
|
59
|
-
returnTo: $route.fullPath,
|
|
60
|
-
},
|
|
61
|
-
}"
|
|
62
|
-
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"
|
|
63
|
-
>
|
|
64
|
-
<IconPlusOutline class="w-4 h-4 me-2"/>
|
|
65
|
-
Create
|
|
66
|
-
</RouterLink>
|
|
67
|
-
|
|
68
|
-
<button
|
|
69
|
-
class="flex gap-1 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"
|
|
70
|
-
@click="()=>{filtersShow = !filtersShow}"
|
|
71
|
-
>
|
|
72
|
-
<IconFilterOutline class="w-4 h-4 me-2"/>
|
|
73
|
-
Filter
|
|
74
|
-
<span
|
|
75
|
-
class="bg-red-100 text-red-800 text-xs font-medium px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400"
|
|
76
|
-
v-if="filters.length">
|
|
77
|
-
{{ filters.length }}
|
|
78
|
-
</span>
|
|
79
|
-
</button>
|
|
80
|
-
|
|
81
|
-
</div>
|
|
82
|
-
|
|
83
|
-
<ResourceListTable
|
|
84
|
-
:resource="listResource"
|
|
85
|
-
:rows="rows"
|
|
86
|
-
@update:page="page = $event"
|
|
87
|
-
@update:sort="sort = $event"
|
|
88
|
-
@update:checkboxes="checkboxes = $event"
|
|
89
|
-
:pageSize="pageSize"
|
|
90
|
-
:totalRows="totalRows"
|
|
91
|
-
:checkboxes="checkboxes"
|
|
92
|
-
/>
|
|
93
|
-
|
|
94
|
-
</td>
|
|
95
|
-
</template>
|
|
96
|
-
|
|
97
|
-
<script setup>
|
|
98
|
-
import { callAdminForthApi } from '@/utils';
|
|
99
|
-
import { ref, onMounted, watch, computed } from 'vue';
|
|
100
|
-
import ResourceListTable from '@/components/ResourceListTable.vue';
|
|
101
|
-
import Filters from '@/components/Filters.vue';
|
|
102
|
-
import {
|
|
103
|
-
IconBanOutline,
|
|
104
|
-
IconFilterOutline,
|
|
105
|
-
IconPlusOutline,
|
|
106
|
-
} from '@iconify-prerendered/vue-flowbite';
|
|
107
|
-
|
|
108
|
-
import { getIcon } from '@/utils';
|
|
109
|
-
|
|
110
|
-
const props = defineProps(['column', 'record', 'meta', 'resource', 'adminUser']);
|
|
111
|
-
|
|
112
|
-
const listResource = ref(null);
|
|
113
|
-
const loading = ref(true);
|
|
114
|
-
|
|
115
|
-
const page = ref(1);
|
|
116
|
-
const sort = ref([]);
|
|
117
|
-
const checkboxes = ref([]);
|
|
118
|
-
const pageSize = computed(() => listResource.value?.options?.listPageSize || 10);
|
|
119
|
-
|
|
120
|
-
const rows = ref(null);
|
|
121
|
-
const totalRows = ref(0);
|
|
122
|
-
|
|
123
|
-
const filters = ref([]);
|
|
124
|
-
const filtersShow = ref(false);
|
|
125
|
-
const columnsMinMax = ref(null);
|
|
126
|
-
|
|
127
|
-
const listResourceRefColumn = computed(() => {
|
|
128
|
-
if (!listResource.value) {
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
return listResource.value.columns.find(c => c.foreignResource?.resourceId === props.resource.resourceId);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
const selfPrimaryKeyColumn = computed(() => {
|
|
135
|
-
return props.resource.columns.find(c => c.primaryKey);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
const endFilters = computed(() => {
|
|
139
|
-
if (!listResource.value) {
|
|
140
|
-
return [];
|
|
141
|
-
}
|
|
142
|
-
// get name of the column that is foreign key
|
|
143
|
-
const refColumn = listResourceRefColumn.value;
|
|
144
|
-
|
|
145
|
-
const primaryKeyColumn = selfPrimaryKeyColumn.value;
|
|
146
|
-
|
|
147
|
-
if (!refColumn) {
|
|
148
|
-
window.adminforth.alert({
|
|
149
|
-
message: `Column with foreignResource.resourceId which is equal to '${props.resource.resourceId}' not found in resource which is specified as foreighResourceId '${listResource.value.resourceId}'`,
|
|
150
|
-
variant: 'danger',
|
|
151
|
-
});
|
|
152
|
-
return [];
|
|
153
|
-
}
|
|
154
|
-
return [
|
|
155
|
-
...filters.value,
|
|
156
|
-
{
|
|
157
|
-
field: refColumn.name,
|
|
158
|
-
operator: 'eq',
|
|
159
|
-
value: props.record[primaryKeyColumn.name],
|
|
160
|
-
},
|
|
161
|
-
];
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
watch([page], async () => {
|
|
165
|
-
await getList();
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
watch([sort], async () => {
|
|
169
|
-
await getList();
|
|
170
|
-
}, {deep: true});
|
|
171
|
-
|
|
172
|
-
watch([filters], async () => {
|
|
173
|
-
page.value = 1;
|
|
174
|
-
checkboxes.value = [];
|
|
175
|
-
await getList();
|
|
176
|
-
}, {deep: true});
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
async function startBulkAction(actionId) {
|
|
180
|
-
const data = await callAdminForthApi({
|
|
181
|
-
path: '/start_bulk_action',
|
|
182
|
-
method: 'POST',
|
|
183
|
-
body: {
|
|
184
|
-
resourceId: listResource.value.resourceId,
|
|
185
|
-
actionId: actionId,
|
|
186
|
-
recordIds: checkboxes.value
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
if (data?.status === 'success') {
|
|
190
|
-
checkboxes.value = [];
|
|
191
|
-
}
|
|
192
|
-
await getList();
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async function getList() {
|
|
196
|
-
rows.value = null;
|
|
197
|
-
console.log('getList', listResource.value)
|
|
198
|
-
const data = await callAdminForthApi({
|
|
199
|
-
path: '/get_resource_data',
|
|
200
|
-
method: 'POST',
|
|
201
|
-
body: {
|
|
202
|
-
source: 'list',
|
|
203
|
-
resourceId: listResource.value.resourceId,
|
|
204
|
-
limit: pageSize.value,
|
|
205
|
-
offset: (page.value - 1) * pageSize.value,
|
|
206
|
-
filters: endFilters.value,
|
|
207
|
-
sort: sort.value,
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
if (data.error) {
|
|
212
|
-
window.adminforth.alert({
|
|
213
|
-
message: data.error,
|
|
214
|
-
variant: 'danger',
|
|
215
|
-
timeout: 'unlimited',
|
|
216
|
-
});
|
|
217
|
-
rows.value = [];
|
|
218
|
-
totalRows.value = 0;
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
rows.value = data.data?.map(row => {
|
|
223
|
-
row._primaryKeyValue = row[listResource.value.columns.find(c => c.primaryKey).name];
|
|
224
|
-
return row;
|
|
225
|
-
});
|
|
226
|
-
totalRows.value = data.total;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
onMounted( async () => {
|
|
230
|
-
loading.value = true;
|
|
231
|
-
const foreighResourceId = props.meta.foreignResourceId;
|
|
232
|
-
listResource.value = (await callAdminForthApi({
|
|
233
|
-
path: `/plugin/${props.meta.pluginInstanceId}/get_resource`,
|
|
234
|
-
method: 'POST',
|
|
235
|
-
body: {},
|
|
236
|
-
})).resource;
|
|
237
|
-
columnsMinMax.value = await callAdminForthApi({
|
|
238
|
-
path: '/get_min_max_for_columns',
|
|
239
|
-
method: 'POST',
|
|
240
|
-
body: {
|
|
241
|
-
resourceId: foreighResourceId,
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
loading.value = false;
|
|
245
|
-
await getList();
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
</script>
|