adminforth 1.1.4 → 1.1.6
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/index.js +6 -0
- package/dist/modules/styles.js +7 -6
- package/dist/plugins/ForeignInlineListPlugin/index.js +32 -1
- package/dist/plugins/base.js +4 -0
- package/dist/spa/spa/src/App.vue +2 -2
- package/dist/spa/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
- package/dist/spa/spa/src/components/Filters.vue +19 -15
- package/dist/spa/spa/src/components/ResourceForm.vue +15 -15
- package/dist/spa/spa/src/components/ResourceListTable.vue +36 -16
- package/dist/spa/spa/src/composables/useStores.ts +64 -3
- package/dist/spa/spa/src/stores/core.ts +17 -11
- package/dist/spa/spa/src/stores/filters.ts +22 -0
- package/dist/spa/spa/src/stores/modal.ts +0 -1
- package/dist/spa/spa/src/views/CreateView.vue +24 -10
- package/dist/spa/spa/src/views/EditView.vue +5 -5
- package/dist/spa/spa/src/views/ListView.vue +25 -23
- package/dist/spa/spa/src/views/ShowView.vue +5 -5
- package/index.ts +10 -0
- package/modules/styles.ts +7 -6
- package/package.json +1 -1
- package/plugins/ForeignInlineListPlugin/custom/InlineList.vue +138 -14
- package/plugins/ForeignInlineListPlugin/index.ts +32 -5
- package/plugins/base.ts +7 -0
- package/spa/src/App.vue +2 -2
- package/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
- package/spa/src/components/Filters.vue +19 -15
- package/spa/src/components/ResourceForm.vue +15 -15
- package/spa/src/components/ResourceListTable.vue +36 -16
- package/spa/src/composables/useStores.ts +64 -3
- package/spa/src/stores/core.ts +17 -11
- package/spa/src/stores/filters.ts +22 -0
- package/spa/src/stores/modal.ts +0 -1
- package/spa/src/views/CreateView.vue +24 -10
- package/spa/src/views/EditView.vue +5 -5
- package/spa/src/views/ListView.vue +25 -23
- package/spa/src/views/ShowView.vue +5 -5
- package/types/AdminForthConfig.ts +32 -1
- package/types/FrontendAPI.ts +47 -0
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
<BreadcrumbsWithButtons>
|
|
14
14
|
<!-- save and cancle -->
|
|
15
15
|
<button @click="$router.back()"
|
|
16
|
-
class="flex items-center py-1 px-3 me-2
|
|
16
|
+
class="flex items-center py-1 px-3 me-2 text-sm font-medium rounded-default 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"
|
|
17
17
|
>
|
|
18
18
|
Cancel
|
|
19
19
|
</button>
|
|
20
20
|
|
|
21
21
|
<button @click="saveRecord"
|
|
22
|
-
class="flex items-center py-1 px-3
|
|
22
|
+
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 disabled:opacity-50"
|
|
23
23
|
:disabled="saving || (validating && !isValid)"
|
|
24
24
|
>
|
|
25
25
|
<svg v-if="saving" x
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
|
|
46
46
|
<ResourceForm
|
|
47
47
|
v-else
|
|
48
|
-
:record="
|
|
49
|
-
:
|
|
48
|
+
:record="initalValues"
|
|
49
|
+
:resource="coreStore.resource"
|
|
50
50
|
@update:record="onUpdateRecord"
|
|
51
51
|
@update:isValid="isValid = $event"
|
|
52
52
|
:validating="validating"
|
|
@@ -77,6 +77,7 @@ import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions } from
|
|
|
77
77
|
import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
|
|
78
78
|
import { onMounted, ref } from 'vue';
|
|
79
79
|
import { useRoute, useRouter } from 'vue-router';
|
|
80
|
+
import { computed } from 'vue';
|
|
80
81
|
|
|
81
82
|
|
|
82
83
|
const isValid = ref(false);
|
|
@@ -93,6 +94,13 @@ let createComponentsPerColumn = {};
|
|
|
93
94
|
|
|
94
95
|
const coreStore = useCoreStore();
|
|
95
96
|
|
|
97
|
+
const initalValues = computed(() => {
|
|
98
|
+
if (!route.query.values) {
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
101
|
+
return JSON.parse(decodeURIComponent(route.query.values));
|
|
102
|
+
});
|
|
103
|
+
|
|
96
104
|
async function onUpdateRecord(newRecord) {
|
|
97
105
|
console.log('newRecord', newRecord);
|
|
98
106
|
record.value = newRecord;
|
|
@@ -103,7 +111,7 @@ onMounted(async () => {
|
|
|
103
111
|
await coreStore.fetchResourceFull({
|
|
104
112
|
resourceId: route.params.resourceId
|
|
105
113
|
});
|
|
106
|
-
createComponentsPerColumn = coreStore.
|
|
114
|
+
createComponentsPerColumn = coreStore.resource.columns.reduce((acc, column) => {
|
|
107
115
|
if (column.components?.create) {
|
|
108
116
|
acc[column.name] = getCustomComponent(column.components.create);
|
|
109
117
|
}
|
|
@@ -130,11 +138,17 @@ async function saveRecord() {
|
|
|
130
138
|
},
|
|
131
139
|
});
|
|
132
140
|
saving.value = false;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
if (route.query.returnTo) {
|
|
142
|
+
router.push(route.query.returnTo);
|
|
143
|
+
} else {
|
|
144
|
+
router.push({
|
|
145
|
+
name: 'resource-show',
|
|
146
|
+
params: {
|
|
147
|
+
resourceId: route.params.resourceId,
|
|
148
|
+
primaryKey: response.newRecordId
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
138
152
|
}
|
|
139
153
|
|
|
140
154
|
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
<BreadcrumbsWithButtons>
|
|
13
13
|
<!-- save and cancle -->
|
|
14
14
|
<button @click="$router.back()"
|
|
15
|
-
class="flex items-center py-1 px-3 me-2
|
|
15
|
+
class="flex items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 rounded-default 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"
|
|
16
16
|
>
|
|
17
17
|
Cancel
|
|
18
18
|
</button>
|
|
19
19
|
|
|
20
20
|
<button @click="saveRecord"
|
|
21
|
-
class="flex items-center py-1 px-3
|
|
21
|
+
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"
|
|
22
22
|
:disabled="saving || (validating && !isValid)"
|
|
23
23
|
>
|
|
24
24
|
<IconFloppyDiskSolid class="w-4 h-4" />
|
|
@@ -95,10 +95,10 @@ async function onUpdateRecord(newRecord) {
|
|
|
95
95
|
|
|
96
96
|
const editableRecord = computed(() => {
|
|
97
97
|
const newRecord = { ...coreStore.record };
|
|
98
|
-
if (!coreStore.
|
|
98
|
+
if (!coreStore.resource) {
|
|
99
99
|
return {};
|
|
100
100
|
}
|
|
101
|
-
coreStore.
|
|
101
|
+
coreStore.resource.columns.forEach(column => {
|
|
102
102
|
if (column.foreignResource) {
|
|
103
103
|
newRecord[column.name] = newRecord[column.name]?.pk
|
|
104
104
|
}
|
|
@@ -119,7 +119,7 @@ onMounted(async () => {
|
|
|
119
119
|
});
|
|
120
120
|
checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'edit');
|
|
121
121
|
|
|
122
|
-
editComponentsPerColumn = coreStore.
|
|
122
|
+
editComponentsPerColumn = coreStore.resource.columns.reduce((acc, column) => {
|
|
123
123
|
if (column.components?.edit) {
|
|
124
124
|
acc[column.name] = getCustomComponent(column.components.edit);
|
|
125
125
|
}
|
|
@@ -2,8 +2,7 @@
|
|
|
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
|
/>
|
|
@@ -23,7 +22,7 @@
|
|
|
23
22
|
v-if="checkboxes.length"
|
|
24
23
|
data-tooltip-target="tooltip-remove-all"
|
|
25
24
|
data-tooltip-placement="bottom"
|
|
26
|
-
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"
|
|
27
26
|
>
|
|
28
27
|
<IconBanOutline class="w-5 h-5 "/>
|
|
29
28
|
|
|
@@ -39,7 +38,7 @@
|
|
|
39
38
|
v-for="(action,i) in coreStore.resource?.options?.bulkActions"
|
|
40
39
|
:key="action.id"
|
|
41
40
|
@click="startBulkAction(action.id)"
|
|
42
|
-
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 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"
|
|
43
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',
|
|
44
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',
|
|
45
44
|
}"
|
|
@@ -52,24 +51,24 @@
|
|
|
52
51
|
{{ `${action.label} (${checkboxes.length})` }}
|
|
53
52
|
</button>
|
|
54
53
|
|
|
55
|
-
<RouterLink v-if="coreStore.resource?.options?.create"
|
|
54
|
+
<RouterLink v-if="coreStore.resource?.options?.allowedActions?.create"
|
|
56
55
|
:to="{ name: 'resource-create', params: { resourceId: $route.params.resourceId } }"
|
|
57
|
-
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"
|
|
58
57
|
>
|
|
59
58
|
<IconPlusOutline class="w-4 h-4 me-2"/>
|
|
60
59
|
Create
|
|
61
60
|
</RouterLink>
|
|
62
61
|
|
|
63
62
|
<button
|
|
64
|
-
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"
|
|
65
64
|
@click="()=>{filtersShow = !filtersShow}"
|
|
66
65
|
>
|
|
67
66
|
<IconFilterOutline class="w-4 h-4 me-2"/>
|
|
68
67
|
Filter
|
|
69
68
|
<span
|
|
70
69
|
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"
|
|
71
|
-
v-if="filters.length">
|
|
72
|
-
{{ filters.length }}
|
|
70
|
+
v-if="filtersStore.filters.length">
|
|
71
|
+
{{ filtersStore.filters.length }}
|
|
73
72
|
</span>
|
|
74
73
|
</button>
|
|
75
74
|
</BreadcrumbsWithButtons>
|
|
@@ -90,6 +89,7 @@
|
|
|
90
89
|
@update:checkboxes="checkboxes = $event"
|
|
91
90
|
:pageSize="pageSize"
|
|
92
91
|
:totalRows="totalRows"
|
|
92
|
+
:checkboxes="checkboxes"
|
|
93
93
|
/>
|
|
94
94
|
|
|
95
95
|
<component
|
|
@@ -108,6 +108,7 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
|
108
108
|
import ResourceListTable from '@/components/ResourceListTable.vue';
|
|
109
109
|
import { useCoreStore } from '@/stores/core';
|
|
110
110
|
import { useModalStore } from '@/stores/modal';
|
|
111
|
+
import { useFiltersStore } from '@/stores/filters';
|
|
111
112
|
import { callAdminForthApi, getIcon } from '@/utils';
|
|
112
113
|
import { initFlowbite } from 'flowbite';
|
|
113
114
|
import { computed, onMounted, ref, watch } from 'vue';
|
|
@@ -116,17 +117,15 @@ import { useRoute } from 'vue-router';
|
|
|
116
117
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
117
118
|
import { getCustomComponent } from '@/utils';
|
|
118
119
|
|
|
119
|
-
import {
|
|
120
|
-
IconInboxOutline,
|
|
121
|
-
IconPlusOutline
|
|
122
|
-
} from '@iconify-prerendered/vue-flowbite';
|
|
123
120
|
|
|
124
121
|
import {
|
|
125
|
-
IconBanOutline,
|
|
126
|
-
IconEyeSolid,
|
|
127
|
-
IconFilterOutline,
|
|
128
|
-
IconPenSolid,
|
|
129
|
-
IconTrashBinSolid
|
|
122
|
+
IconBanOutline,
|
|
123
|
+
IconEyeSolid,
|
|
124
|
+
IconFilterOutline,
|
|
125
|
+
IconPenSolid,
|
|
126
|
+
IconTrashBinSolid,
|
|
127
|
+
IconInboxOutline,
|
|
128
|
+
IconPlusOutline
|
|
130
129
|
} from '@iconify-prerendered/vue-flowbite';
|
|
131
130
|
|
|
132
131
|
import Filters from '@/components/Filters.vue';
|
|
@@ -135,11 +134,12 @@ const filtersShow = ref(false);
|
|
|
135
134
|
|
|
136
135
|
const coreStore = useCoreStore();
|
|
137
136
|
const modalStore = useModalStore();
|
|
137
|
+
const filtersStore = useFiltersStore();
|
|
138
138
|
|
|
139
139
|
const route = useRoute();
|
|
140
140
|
|
|
141
141
|
const page = ref(1);
|
|
142
|
-
const filters =
|
|
142
|
+
const filters = filtersStore.filters;
|
|
143
143
|
const columnsMinMax = ref({});
|
|
144
144
|
const sort = ref([]);
|
|
145
145
|
|
|
@@ -157,8 +157,10 @@ watch([page], async () => {
|
|
|
157
157
|
await getList();
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
-
watch(
|
|
160
|
+
watch(()=>filtersStore.filters, async () => {
|
|
161
161
|
page.value = 1;
|
|
162
|
+
checkboxes.value = [];
|
|
163
|
+
|
|
162
164
|
await getList();
|
|
163
165
|
}, {deep: true});
|
|
164
166
|
|
|
@@ -177,13 +179,13 @@ async function getList() {
|
|
|
177
179
|
resourceId: route.params.resourceId,
|
|
178
180
|
limit: pageSize.value,
|
|
179
181
|
offset: (page.value - 1) * pageSize.value,
|
|
180
|
-
filters: filters
|
|
182
|
+
filters: filtersStore.filters,
|
|
181
183
|
sort: sort.value,
|
|
182
184
|
}
|
|
183
185
|
});
|
|
184
186
|
|
|
185
187
|
rows.value = data.data?.map(row => {
|
|
186
|
-
row._primaryKeyValue = row[coreStore.
|
|
188
|
+
row._primaryKeyValue = row[coreStore.resource.columns.find(c => c.primaryKey).name];
|
|
187
189
|
return row;
|
|
188
190
|
});
|
|
189
191
|
totalRows.value = data.total;
|
|
@@ -265,7 +267,7 @@ onMounted(async () => {
|
|
|
265
267
|
|
|
266
268
|
// on route param change
|
|
267
269
|
watch(() => route.params.resourceId, async () => {
|
|
268
|
-
|
|
270
|
+
filtersStore.setFilters([]);
|
|
269
271
|
checkboxes.value = [];
|
|
270
272
|
sort.value = [];
|
|
271
273
|
await init();
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
/>
|
|
11
11
|
<BreadcrumbsWithButtons>
|
|
12
12
|
<RouterLink v-if="coreStore?.resourceOptions?.allowedActions?.edit" :to="{ name: 'resource-edit', params: { resourceId: $route.params.resourceId, primaryKey: $route.params.primaryKey } }"
|
|
13
|
-
class="flex items-center py-1 px-3
|
|
13
|
+
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"
|
|
14
14
|
>
|
|
15
15
|
<IconPenSolid class="w-4 h-4" />
|
|
16
16
|
Edit
|
|
17
17
|
</RouterLink>
|
|
18
18
|
|
|
19
19
|
<button v-if="coreStore?.resourceOptions?.allowedActions?.delete" @click="showDeleteModal"
|
|
20
|
-
class="flex items-center py-1 px-3
|
|
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" />
|
|
23
23
|
Delete
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
</tr>
|
|
59
59
|
</thead>
|
|
60
60
|
<tbody>
|
|
61
|
-
<tr v-for="column in coreStore.
|
|
61
|
+
<tr v-for="column in coreStore.resource?.columns.filter(c => c.showIn.includes('show'))" :key="column.name"
|
|
62
62
|
class="bg-form-view-bg odd:dark:bg-gray-900 even:dark:bg-gray-800 border-b dark:border-gray-700"
|
|
63
63
|
>
|
|
64
64
|
<component
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
:record="coreStore.record"
|
|
71
71
|
/>
|
|
72
72
|
<template v-else>
|
|
73
|
-
<td class="px-6 py-4 whitespace-nowrap">
|
|
73
|
+
<td class="px-6 py-4 whitespace-nowrap "> <!--align-top-->
|
|
74
74
|
{{ column.label }}
|
|
75
75
|
</td>
|
|
76
76
|
<td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap">
|
|
@@ -145,7 +145,7 @@ onMounted(async () => {
|
|
|
145
145
|
});
|
|
146
146
|
checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'show');
|
|
147
147
|
|
|
148
|
-
showComponentsPerColumn = coreStore.
|
|
148
|
+
showComponentsPerColumn = coreStore.resource.columns.reduce((acc, column) => {
|
|
149
149
|
if (column.components?.show) {
|
|
150
150
|
acc[column.name] = getCustomComponent(column.components.show);
|
|
151
151
|
}
|
package/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { AdminForthConfig, AdminForthClass, AdminForthComponentDeclaration,
|
|
|
13
13
|
AdminForthFilterOperators, AdminForthDataTypes, AdminForthResourcePages } from './types/AdminForthConfig.js';
|
|
14
14
|
import { getFunctionList } from './modules/utils.js';
|
|
15
15
|
import path from 'path';
|
|
16
|
+
import AdminForthPlugin from './plugins/base.js';
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
//get array from enum AdminForthResourcePages
|
|
@@ -40,6 +41,7 @@ class AdminForth implements AdminForthClass {
|
|
|
40
41
|
connectors: any;
|
|
41
42
|
connectorClasses: any;
|
|
42
43
|
runningHotReload: boolean;
|
|
44
|
+
activatedPlugins: Array<AdminForthPlugin>;
|
|
43
45
|
|
|
44
46
|
statuses: {
|
|
45
47
|
dbDiscover?: 'running' | 'done',
|
|
@@ -48,6 +50,7 @@ class AdminForth implements AdminForthClass {
|
|
|
48
50
|
constructor(config: AdminForthConfig) {
|
|
49
51
|
this.config = {...this.#defaultConfig,...config};
|
|
50
52
|
this.codeInjector = new CodeInjector(this);
|
|
53
|
+
this.activatedPlugins = [];
|
|
51
54
|
|
|
52
55
|
this.validateConfig();
|
|
53
56
|
this.activatePlugins();
|
|
@@ -57,6 +60,7 @@ class AdminForth implements AdminForthClass {
|
|
|
57
60
|
this.auth = new Auth();
|
|
58
61
|
this.connectors = {};
|
|
59
62
|
this.statuses = {};
|
|
63
|
+
|
|
60
64
|
console.log(`🚀 AdminForth v${ADMINFORTH_VERSION} starting up`)
|
|
61
65
|
}
|
|
62
66
|
|
|
@@ -64,6 +68,7 @@ class AdminForth implements AdminForthClass {
|
|
|
64
68
|
for (let resource of this.config.resources) {
|
|
65
69
|
for (let pluginInstance of resource.plugins || []) {
|
|
66
70
|
pluginInstance.modifyResourceConfig(this, resource);
|
|
71
|
+
this.activatedPlugins.push(pluginInstance);
|
|
67
72
|
}
|
|
68
73
|
};
|
|
69
74
|
}
|
|
@@ -1062,6 +1067,11 @@ class AdminForth implements AdminForthClass {
|
|
|
1062
1067
|
}
|
|
1063
1068
|
}
|
|
1064
1069
|
})
|
|
1070
|
+
|
|
1071
|
+
// setup endpoints for all plugins
|
|
1072
|
+
this.activatedPlugins.forEach((plugin) => {
|
|
1073
|
+
plugin.setupEndpoints(server);
|
|
1074
|
+
});
|
|
1065
1075
|
}
|
|
1066
1076
|
}
|
|
1067
1077
|
|
package/modules/styles.ts
CHANGED
|
@@ -2,19 +2,20 @@ export const styles = () => `
|
|
|
2
2
|
|
|
3
3
|
"colors": {
|
|
4
4
|
"html-bg": "#FFFFFF", // Changed to white
|
|
5
|
-
"header-bg": "#
|
|
5
|
+
"header-bg": "#FFFFFF", // Light grey
|
|
6
6
|
"header-bg-hover": "#D3D3D3", // Light grey hover
|
|
7
7
|
"header-text": "#333333", // Dark grey text
|
|
8
8
|
"header-text-hover": "#333333", // Dark grey text hover
|
|
9
9
|
"header-icons": "#333333", // Dark grey icons
|
|
10
10
|
"header-icons-hover": "#333333", // Dark grey icons hover
|
|
11
11
|
"header-logo-color": "#333333", // Dark grey logo
|
|
12
|
-
"header-border-color": "
|
|
12
|
+
"header-border-color": "rgb(229 231 235)", // Light grey border
|
|
13
13
|
|
|
14
|
-
"nav-menu-bg": "#
|
|
15
|
-
"nav-menu-
|
|
16
|
-
"nav-menu-bg-
|
|
17
|
-
"nav-menu-
|
|
14
|
+
"nav-menu-bg": "#f9fafb", // Very light grey
|
|
15
|
+
"nav-menu-border": "rgb(229 231 235)",
|
|
16
|
+
"nav-menu-bg-hover": "rgb(243 244 246)", // Light grey hover
|
|
17
|
+
"nav-menu-bg-active": "rgb(233, 234, 236)", // Light grey active
|
|
18
|
+
"nav-menu-hover": "#rgb(209 213 219)", // Light grey hover
|
|
18
19
|
"nav-menu-active": "#CCCCCC", // Light grey active
|
|
19
20
|
"nav-menu-active-text": "#333333", // Dark grey active text
|
|
20
21
|
"nav-menu-text": "#333333", // Dark grey text
|
package/package.json
CHANGED
|
@@ -1,9 +1,84 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
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>
|
|
5
13
|
|
|
6
|
-
|
|
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>
|
|
7
82
|
|
|
8
83
|
<ResourceListTable
|
|
9
84
|
:resource="listResource"
|
|
@@ -13,6 +88,7 @@
|
|
|
13
88
|
@update:checkboxes="checkboxes = $event"
|
|
14
89
|
:pageSize="pageSize"
|
|
15
90
|
:totalRows="totalRows"
|
|
91
|
+
:checkboxes="checkboxes"
|
|
16
92
|
/>
|
|
17
93
|
</td>
|
|
18
94
|
</template>
|
|
@@ -21,6 +97,14 @@
|
|
|
21
97
|
import { callAdminForthApi } from '@/utils';
|
|
22
98
|
import { ref, onMounted, watch, computed } from 'vue';
|
|
23
99
|
import ResourceListTable from '@/components/ResourceListTable.vue';
|
|
100
|
+
import Filters from '@/components/Filters.vue';
|
|
101
|
+
import {
|
|
102
|
+
IconBanOutline,
|
|
103
|
+
IconFilterOutline,
|
|
104
|
+
IconPlusOutline,
|
|
105
|
+
} from '@iconify-prerendered/vue-flowbite';
|
|
106
|
+
|
|
107
|
+
import { getIcon } from '@/utils';
|
|
24
108
|
|
|
25
109
|
const props = defineProps(['column', 'record', 'meta', 'resource', 'adminUser']);
|
|
26
110
|
|
|
@@ -30,21 +114,34 @@ const loading = ref(true);
|
|
|
30
114
|
const page = ref(1);
|
|
31
115
|
const sort = ref([]);
|
|
32
116
|
const checkboxes = ref([]);
|
|
33
|
-
const pageSize = computed(() => props.meta.
|
|
117
|
+
const pageSize = computed(() => props.meta.listPageSize || 10);
|
|
34
118
|
|
|
35
119
|
const rows = ref(null);
|
|
36
120
|
const totalRows = ref(0);
|
|
37
121
|
|
|
122
|
+
const filters = ref([]);
|
|
123
|
+
const filtersShow = ref(false);
|
|
124
|
+
const columnsMinMax = ref(null);
|
|
125
|
+
|
|
126
|
+
const listResourceRefColumn = computed(() => {
|
|
127
|
+
if (!listResource.value) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
return listResource.value.columns.find(c => c.foreignResource?.resourceId === props.resource.resourceId);
|
|
131
|
+
});
|
|
38
132
|
|
|
133
|
+
const selfPrimaryKeyColumn = computed(() => {
|
|
134
|
+
return props.resource.columns.find(c => c.primaryKey);
|
|
135
|
+
});
|
|
39
136
|
|
|
40
|
-
const
|
|
137
|
+
const endFilters = computed(() => {
|
|
41
138
|
if (!listResource.value) {
|
|
42
139
|
return [];
|
|
43
140
|
}
|
|
44
141
|
// get name of the column that is foreign key
|
|
45
|
-
const refColumn =
|
|
142
|
+
const refColumn = listResourceRefColumn.value;
|
|
46
143
|
|
|
47
|
-
const primaryKeyColumn =
|
|
144
|
+
const primaryKeyColumn = selfPrimaryKeyColumn.value;
|
|
48
145
|
|
|
49
146
|
if (!refColumn) {
|
|
50
147
|
window.adminforth.alert({
|
|
@@ -54,6 +151,7 @@ const filters = computed(() => {
|
|
|
54
151
|
return [];
|
|
55
152
|
}
|
|
56
153
|
return [
|
|
154
|
+
...filters.value,
|
|
57
155
|
{
|
|
58
156
|
field: refColumn.name,
|
|
59
157
|
operator: 'eq',
|
|
@@ -70,6 +168,28 @@ watch([sort], async () => {
|
|
|
70
168
|
await getList();
|
|
71
169
|
}, {deep: true});
|
|
72
170
|
|
|
171
|
+
watch([filters], async () => {
|
|
172
|
+
page.value = 1;
|
|
173
|
+
checkboxes.value = [];
|
|
174
|
+
await getList();
|
|
175
|
+
}, {deep: true});
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
async function startBulkAction(actionId) {
|
|
179
|
+
const data = await callAdminForthApi({
|
|
180
|
+
path: '/start_bulk_action',
|
|
181
|
+
method: 'POST',
|
|
182
|
+
body: {
|
|
183
|
+
resourceId: listResource.value.resourceId,
|
|
184
|
+
actionId: actionId,
|
|
185
|
+
recordIds: checkboxes.value
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
if (data?.status === 'success') {
|
|
189
|
+
checkboxes.value = [];
|
|
190
|
+
}
|
|
191
|
+
await getList();
|
|
192
|
+
}
|
|
73
193
|
|
|
74
194
|
async function getList() {
|
|
75
195
|
rows.value = null;
|
|
@@ -82,7 +202,7 @@ async function getList() {
|
|
|
82
202
|
resourceId: listResource.value.resourceId,
|
|
83
203
|
limit: pageSize.value,
|
|
84
204
|
offset: (page.value - 1) * pageSize.value,
|
|
85
|
-
filters:
|
|
205
|
+
filters: endFilters.value,
|
|
86
206
|
sort: sort.value,
|
|
87
207
|
}
|
|
88
208
|
});
|
|
@@ -94,17 +214,21 @@ async function getList() {
|
|
|
94
214
|
totalRows.value = data.total;
|
|
95
215
|
}
|
|
96
216
|
|
|
97
|
-
|
|
98
217
|
onMounted( async () => {
|
|
99
218
|
loading.value = true;
|
|
100
219
|
const foreighResourceId = props.meta.foreignResourceId;
|
|
101
220
|
listResource.value = (await callAdminForthApi({
|
|
102
|
-
path:
|
|
221
|
+
path: `/plugin/${props.meta.pluginInstanceId}/get_resource`,
|
|
103
222
|
method: 'POST',
|
|
104
|
-
body: {
|
|
105
|
-
resourceId: foreighResourceId,
|
|
106
|
-
},
|
|
223
|
+
body: {},
|
|
107
224
|
})).resource;
|
|
225
|
+
columnsMinMax.value = await callAdminForthApi({
|
|
226
|
+
path: '/get_min_max_for_columns',
|
|
227
|
+
method: 'POST',
|
|
228
|
+
body: {
|
|
229
|
+
resourceId: foreighResourceId,
|
|
230
|
+
}
|
|
231
|
+
});
|
|
108
232
|
loading.value = false;
|
|
109
233
|
await getList();
|
|
110
234
|
});
|