adminforth 1.1.13 → 1.1.15
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/auth.ts +17 -3
- package/dist/auth.js +32 -18
- package/dist/index.js +114 -58
- package/dist/modules/codeInjector.js +0 -1
- package/dist/modules/utils.js +2 -12
- package/dist/plugins/AccessControl/index.js +66 -0
- package/dist/plugins/AccessControl/types.js +1 -0
- package/dist/plugins/ForeignInlineListPlugin/index.js +1 -1
- package/dist/plugins/plugins/ForeignInlineListPlugin/custom/InlineList.vue +248 -0
- package/dist/servers/express.js +2 -2
- package/dist/spa/spa/src/App.vue +7 -3
- package/dist/spa/spa/src/components/Toast.vue +5 -4
- package/dist/spa/spa/src/components/ValueRenderer.vue +1 -1
- package/dist/spa/spa/src/composables/useStores.ts +2 -1
- package/dist/spa/spa/src/router/index.ts +13 -1
- package/dist/spa/spa/src/stores/core.ts +13 -11
- package/dist/spa/spa/src/stores/user.ts +54 -0
- package/dist/spa/spa/src/utils.ts +9 -4
- package/dist/spa/spa/src/views/CreateView.vue +7 -0
- package/dist/spa/spa/src/views/EditView.vue +8 -1
- package/dist/spa/spa/src/views/ListView.vue +10 -1
- package/dist/spa/spa/src/views/LoginView.vue +4 -0
- package/dist/types/AdminForthConfig.js +9 -0
- package/dist/types/FrontendAPI.js +4 -4
- package/index.ts +106 -42
- package/modules/codeInjector.ts +0 -1
- package/modules/utils.ts +2 -10
- package/package.json +3 -2
- package/plugins/AccessControl/index.ts +83 -0
- package/plugins/AccessControl/types.ts +14 -0
- package/plugins/ForeignInlineListPlugin/custom/InlineList.vue +11 -0
- package/plugins/ForeignInlineListPlugin/index.ts +2 -1
- package/servers/express.ts +2 -2
- package/spa/src/App.vue +7 -3
- package/spa/src/components/Toast.vue +5 -4
- package/spa/src/components/ValueRenderer.vue +1 -1
- package/spa/src/composables/useStores.ts +2 -1
- package/spa/src/router/index.ts +13 -1
- package/spa/src/stores/core.ts +13 -11
- package/spa/src/stores/user.ts +54 -0
- package/spa/src/utils.ts +9 -4
- package/spa/src/views/CreateView.vue +7 -0
- package/spa/src/views/EditView.vue +8 -1
- package/spa/src/views/ListView.vue +10 -1
- package/spa/src/views/LoginView.vue +4 -0
- package/types/AdminForthConfig.ts +73 -21
- package/types/FrontendAPI.ts +11 -5
|
@@ -0,0 +1,248 @@
|
|
|
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>
|
package/dist/servers/express.js
CHANGED
|
@@ -149,7 +149,7 @@ class ExpressServer {
|
|
|
149
149
|
res.status(401).send('Unauthorized by AdminForth');
|
|
150
150
|
return;
|
|
151
151
|
}
|
|
152
|
-
const adminforthUser = this.adminforth.auth.verify(jwt);
|
|
152
|
+
const adminforthUser = yield this.adminforth.auth.verify(jwt);
|
|
153
153
|
if (!adminforthUser) {
|
|
154
154
|
res.status(401).send('Unauthorized by AdminForth');
|
|
155
155
|
}
|
|
@@ -191,7 +191,7 @@ class ExpressServer {
|
|
|
191
191
|
this.message = message;
|
|
192
192
|
}
|
|
193
193
|
};
|
|
194
|
-
const input = { body, query, headers, cookies, response, _raw_express_req: req, _raw_express_res: res };
|
|
194
|
+
const input = { body, query, headers, cookies, adminUser, response, _raw_express_req: req, _raw_express_res: res };
|
|
195
195
|
let output;
|
|
196
196
|
try {
|
|
197
197
|
output = yield handler(input);
|
package/dist/spa/spa/src/App.vue
CHANGED
|
@@ -64,7 +64,6 @@
|
|
|
64
64
|
aria-label="Sidebar"
|
|
65
65
|
>
|
|
66
66
|
<div class="h-full px-3 pb-4 overflow-y-auto bg-nav-menu-bg dark:bg-gray-800 border-r">
|
|
67
|
-
|
|
68
67
|
<div class="flex ms-2 md:me-24 m-4 ">
|
|
69
68
|
<img :src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')" :alt="`${ coreStore.config?.brandName } Logo`" class="h-8 me-3" />
|
|
70
69
|
<span class="self-center text-header-text-size font-semibold sm:text-header-text-size whitespace-nowrap dark:text-header-text text-header-logo-color">
|
|
@@ -168,6 +167,7 @@ import { RouterLink, RouterView } from 'vue-router';
|
|
|
168
167
|
import { initFlowbite } from 'flowbite'
|
|
169
168
|
import './index.scss'
|
|
170
169
|
import { useCoreStore } from '@/stores/core';
|
|
170
|
+
import { useUserStore } from '@/stores/user';
|
|
171
171
|
import {useModalStore} from '@/stores/modal';
|
|
172
172
|
import { IconMoonSolid, IconSunSolid } from '@iconify-prerendered/vue-flowbite';
|
|
173
173
|
import AcceptModal from './components/AcceptModal.vue';
|
|
@@ -184,6 +184,7 @@ import { FrontendAPI } from '@/composables/useStores'
|
|
|
184
184
|
const coreStore = useCoreStore();
|
|
185
185
|
const modalStore = useModalStore();
|
|
186
186
|
const toastStore = useToastStore();
|
|
187
|
+
const userStore = useUserStore();
|
|
187
188
|
const frontendApi = new FrontendAPI();
|
|
188
189
|
frontendApi.init();
|
|
189
190
|
const splitAtLast = (str: string, separator: string) => {
|
|
@@ -221,8 +222,9 @@ if (opened.value.includes(label)) {
|
|
|
221
222
|
}
|
|
222
223
|
|
|
223
224
|
async function logout() {
|
|
224
|
-
|
|
225
|
-
|
|
225
|
+
userStore.unauthorize();
|
|
226
|
+
await userStore.logout();
|
|
227
|
+
router.push({ name: 'login' })
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
|
|
@@ -244,6 +246,8 @@ async function loadMenu() {
|
|
|
244
246
|
});
|
|
245
247
|
}
|
|
246
248
|
|
|
249
|
+
|
|
250
|
+
|
|
247
251
|
watch(route, () => {
|
|
248
252
|
title.value = `${coreStore.config?.title || coreStore.config?.brandName || 'Adminforth'} | ${ Object.values(route.params)[0] || route.meta.title || ' '}`;
|
|
249
253
|
useHead({
|
|
@@ -46,16 +46,17 @@ const props = defineProps<{
|
|
|
46
46
|
toast:{message: string;
|
|
47
47
|
variant: string;
|
|
48
48
|
id: string;
|
|
49
|
-
|
|
49
|
+
timeout?: number|'unlimited';}
|
|
50
50
|
}>();
|
|
51
51
|
function closeToast() {
|
|
52
52
|
emit('close');
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
onMounted(() => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
if (props.toast.timeout === 'unlimited') return;
|
|
57
|
+
else {
|
|
58
|
+
setTimeout(() => {emit('close');}, props.toast.timeout || 10000 );
|
|
59
|
+
}
|
|
59
60
|
});
|
|
60
61
|
|
|
61
62
|
</script>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRouter, createWebHistory } from 'vue-router'
|
|
2
2
|
import ResourceParent from '@/views/ResourceParent.vue'
|
|
3
|
+
import { useUserStore } from '@/stores/user'
|
|
3
4
|
/* IMPORTANT:ADMINFORTH ROUTES IMPORTS */
|
|
4
5
|
|
|
5
6
|
const router = createRouter({
|
|
@@ -9,7 +10,15 @@ const router = createRouter({
|
|
|
9
10
|
path: '/login',
|
|
10
11
|
name: 'login',
|
|
11
12
|
component: () => import('@/views/LoginView.vue'),
|
|
12
|
-
meta: { title: 'login' }
|
|
13
|
+
meta: { title: 'login' },
|
|
14
|
+
beforeEnter: async (to, from, next) => {
|
|
15
|
+
const userStore = useUserStore()
|
|
16
|
+
if(localStorage.getItem('isAuthorized') === 'true'){
|
|
17
|
+
next({name: 'home'})
|
|
18
|
+
} else {
|
|
19
|
+
next()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
13
22
|
},
|
|
14
23
|
{
|
|
15
24
|
path: '/resource/:resourceId',
|
|
@@ -49,4 +58,7 @@ const router = createRouter({
|
|
|
49
58
|
})
|
|
50
59
|
|
|
51
60
|
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
52
64
|
export default router
|
|
@@ -8,7 +8,7 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
8
8
|
const resourceById: Ref<Object> = ref({});
|
|
9
9
|
const menu = ref([]);
|
|
10
10
|
const config = ref({});
|
|
11
|
-
const record = ref({});
|
|
11
|
+
const record: Ref<any | null> = ref({});
|
|
12
12
|
const resource: Ref<AdminForthResource | null> = ref(null);
|
|
13
13
|
|
|
14
14
|
const resourceColumnsWithFilters = computed(() => {
|
|
@@ -67,9 +67,17 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
67
67
|
}
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
if (respData.error) {
|
|
71
|
+
window.adminforth.alert({
|
|
72
|
+
message: respData.error,
|
|
73
|
+
variant: 'danger',
|
|
74
|
+
timeout: 'unlimited'
|
|
75
|
+
});
|
|
76
|
+
record.value = {};
|
|
77
|
+
} else {
|
|
78
|
+
record.value = respData.data[0];
|
|
79
|
+
}
|
|
80
|
+
|
|
73
81
|
}
|
|
74
82
|
|
|
75
83
|
async function fetchResourceFull({ resourceId }: { resourceId: string }) {
|
|
@@ -103,12 +111,7 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
103
111
|
config.value = {...config.value, ...res};
|
|
104
112
|
}
|
|
105
113
|
|
|
106
|
-
|
|
107
|
-
await callAdminForthApi({
|
|
108
|
-
path: '/logout',
|
|
109
|
-
method: 'POST',
|
|
110
|
-
});
|
|
111
|
-
}
|
|
114
|
+
|
|
112
115
|
|
|
113
116
|
const username = computed(() => {
|
|
114
117
|
const usernameField = config.value.usernameField;
|
|
@@ -134,7 +137,6 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
134
137
|
fetchResourceFull,
|
|
135
138
|
resourceColumnsError,
|
|
136
139
|
resourceOptions,
|
|
137
|
-
logout,
|
|
138
140
|
resource,
|
|
139
141
|
adminUser,
|
|
140
142
|
resourceColumnsWithFilters
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {ref} from 'vue';
|
|
2
|
+
import {defineStore} from 'pinia';
|
|
3
|
+
import { callAdminForthApi } from '@/utils';
|
|
4
|
+
|
|
5
|
+
export const useUserStore = defineStore('user', () => {
|
|
6
|
+
const isAuthorized = ref(false);
|
|
7
|
+
|
|
8
|
+
function authorize() {
|
|
9
|
+
isAuthorized.value = true;
|
|
10
|
+
localStorage.setItem('isAuthorized', 'true');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function unauthorize() {
|
|
14
|
+
isAuthorized.value = false;
|
|
15
|
+
localStorage.setItem('isAuthorized', 'false');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function logout() {
|
|
19
|
+
await callAdminForthApi({
|
|
20
|
+
path: '/logout',
|
|
21
|
+
method: 'POST',
|
|
22
|
+
});
|
|
23
|
+
unauthorize();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// async function checkAuth( skipApiCall = false){
|
|
27
|
+
// console.log('checkAuth', isAuthorized.value, skipApiCall)
|
|
28
|
+
// if(isAuthorized.value) {
|
|
29
|
+
// return true}
|
|
30
|
+
// else {
|
|
31
|
+
// if(skipApiCall) return false;
|
|
32
|
+
// const resp = await callAdminForthApi({
|
|
33
|
+
// path: '/check_auth',
|
|
34
|
+
// method: 'POST',
|
|
35
|
+
// });
|
|
36
|
+
// if (resp.status !== 401) {
|
|
37
|
+
// authorize();
|
|
38
|
+
// return true;
|
|
39
|
+
// }
|
|
40
|
+
// else {
|
|
41
|
+
// unauthorize();
|
|
42
|
+
// return false;}
|
|
43
|
+
// }
|
|
44
|
+
|
|
45
|
+
// }
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
isAuthorized,
|
|
49
|
+
authorize,
|
|
50
|
+
unauthorize,
|
|
51
|
+
logout
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
});
|
|
@@ -4,6 +4,7 @@ import type { CoreConfig } from './spa_types/core';
|
|
|
4
4
|
import router from "./router";
|
|
5
5
|
import { useRouter } from 'vue-router';
|
|
6
6
|
import { useCoreStore } from './stores/core';
|
|
7
|
+
import { useUserStore } from './stores/user';
|
|
7
8
|
|
|
8
9
|
export async function callApi({path, method, body=undefined} ) {
|
|
9
10
|
const options = {
|
|
@@ -16,14 +17,18 @@ export async function callApi({path, method, body=undefined} ) {
|
|
|
16
17
|
const fullPath = `${import.meta.env.VITE_ADMINFORTH_PUBLIC_PATH || ''}${path}`;
|
|
17
18
|
const r = await fetch(fullPath, options);
|
|
18
19
|
if (r.status == 401) {
|
|
19
|
-
|
|
20
|
-
router.push({name: 'login'});
|
|
20
|
+
useUserStore().unauthorize();
|
|
21
|
+
router.push({ name: 'login' });
|
|
21
22
|
return null;
|
|
22
|
-
}
|
|
23
|
+
}
|
|
23
24
|
return await r.json();
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
export async function callAdminForthApi({ path, method, body=undefined }
|
|
27
|
+
export async function callAdminForthApi({ path, method, body=undefined }: {
|
|
28
|
+
path: string,
|
|
29
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
|
|
30
|
+
body?: any
|
|
31
|
+
}) {
|
|
27
32
|
try {
|
|
28
33
|
return callApi({path: `/adminapi/v1${path}`, method, body} );
|
|
29
34
|
} catch (e) {
|
|
@@ -137,6 +137,13 @@ async function saveRecord() {
|
|
|
137
137
|
record: record.value,
|
|
138
138
|
},
|
|
139
139
|
});
|
|
140
|
+
if (response.error) {
|
|
141
|
+
window.adminforth.alert({
|
|
142
|
+
message: resp.error,
|
|
143
|
+
variant: 'danger',
|
|
144
|
+
timeout: 'unlimited',
|
|
145
|
+
})
|
|
146
|
+
}
|
|
140
147
|
saving.value = false;
|
|
141
148
|
if (route.query.returnTo) {
|
|
142
149
|
router.push(route.query.returnTo);
|
|
@@ -145,7 +145,7 @@ async function saveRecord() {
|
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
await callAdminForthApi({
|
|
148
|
+
const resp = await callAdminForthApi({
|
|
149
149
|
method: 'POST',
|
|
150
150
|
path: `/update_record`,
|
|
151
151
|
body: {
|
|
@@ -154,6 +154,13 @@ async function saveRecord() {
|
|
|
154
154
|
record: updates,
|
|
155
155
|
},
|
|
156
156
|
});
|
|
157
|
+
if (resp.error) {
|
|
158
|
+
window.adminforth.alert({
|
|
159
|
+
message: resp.error,
|
|
160
|
+
variant: 'danger',
|
|
161
|
+
timeout: 'unlimited',
|
|
162
|
+
})
|
|
163
|
+
}
|
|
157
164
|
saving.value = false;
|
|
158
165
|
router.push({ name: 'resource-show', params: { resourceId: route.params.resourceId, primaryKey: coreStore.record[coreStore.primaryKey] } });
|
|
159
166
|
}
|
|
@@ -183,7 +183,16 @@ async function getList() {
|
|
|
183
183
|
sort: sort.value,
|
|
184
184
|
}
|
|
185
185
|
});
|
|
186
|
-
|
|
186
|
+
if (data.error) {
|
|
187
|
+
window.adminforth.alert({
|
|
188
|
+
message: data.error,
|
|
189
|
+
variant: 'danger',
|
|
190
|
+
timeout: 'unlimited',
|
|
191
|
+
});
|
|
192
|
+
rows.value = [];
|
|
193
|
+
totalRows.value = 0;
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
187
196
|
rows.value = data.data?.map(row => {
|
|
188
197
|
row._primaryKeyValue = row[coreStore.resource.columns.find(c => c.primaryKey).name];
|
|
189
198
|
return row;
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
|
|
82
82
|
import { onMounted, ref, watchEffect } from 'vue';
|
|
83
83
|
import { useCoreStore } from '@/stores/core';
|
|
84
|
+
import { useUserStore } from '@/stores/user';
|
|
84
85
|
import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
|
|
85
86
|
import { callAdminForthApi, loadFile } from '@/utils';
|
|
86
87
|
import { useRouter } from 'vue-router';
|
|
@@ -91,6 +92,8 @@ const router = useRouter();
|
|
|
91
92
|
const inProgress = ref(false);
|
|
92
93
|
|
|
93
94
|
const coreStore = useCoreStore();
|
|
95
|
+
const user = useUserStore();
|
|
96
|
+
|
|
94
97
|
|
|
95
98
|
const showPw = ref(false);
|
|
96
99
|
|
|
@@ -115,6 +118,7 @@ async function login() {
|
|
|
115
118
|
error.value = resp.error;
|
|
116
119
|
} else {
|
|
117
120
|
error.value = null;
|
|
121
|
+
user.authorize()
|
|
118
122
|
router.push('/');
|
|
119
123
|
await router.isReady();
|
|
120
124
|
await coreStore.fetchMenuAndResource();
|
|
@@ -52,6 +52,15 @@ export var AdminForthResourcePages;
|
|
|
52
52
|
AdminForthResourcePages["create"] = "create";
|
|
53
53
|
AdminForthResourcePages["filter"] = "filter";
|
|
54
54
|
})(AdminForthResourcePages || (AdminForthResourcePages = {}));
|
|
55
|
+
export var AllowedActionsEnum;
|
|
56
|
+
(function (AllowedActionsEnum) {
|
|
57
|
+
AllowedActionsEnum["show"] = "show";
|
|
58
|
+
AllowedActionsEnum["list"] = "list";
|
|
59
|
+
AllowedActionsEnum["edit"] = "edit";
|
|
60
|
+
AllowedActionsEnum["create"] = "create";
|
|
61
|
+
AllowedActionsEnum["delete"] = "delete";
|
|
62
|
+
AllowedActionsEnum["filter"] = "filter";
|
|
63
|
+
})(AllowedActionsEnum || (AllowedActionsEnum = {}));
|
|
55
64
|
export var AdminForthDataTypes;
|
|
56
65
|
(function (AdminForthDataTypes) {
|
|
57
66
|
AdminForthDataTypes["STRING"] = "string";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export var AlertVariant;
|
|
2
2
|
(function (AlertVariant) {
|
|
3
|
-
AlertVariant["
|
|
4
|
-
AlertVariant["
|
|
5
|
-
AlertVariant["
|
|
6
|
-
AlertVariant["
|
|
3
|
+
AlertVariant["danger"] = "danger";
|
|
4
|
+
AlertVariant["success"] = "success";
|
|
5
|
+
AlertVariant["warning"] = "warning";
|
|
6
|
+
AlertVariant["info"] = "info";
|
|
7
7
|
})(AlertVariant || (AlertVariant = {}));
|