adminforth 1.2.56 → 1.2.57
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/spa/index.html +1 -1
- package/dist/spa/package-lock.json +215 -1
- package/dist/spa/package.json +2 -0
- package/dist/spa/spa/src/types/AdminForthConfig.ts +1477 -0
- package/dist/spa/spa/src/types/FrontendAPI.ts +121 -0
- package/dist/spa/src/App.vue +176 -133
- package/dist/spa/src/assets/base.css +2 -0
- package/dist/spa/src/components/Breadcrumbs.vue +3 -2
- package/dist/spa/src/components/CustomDatePicker.vue +6 -4
- package/dist/spa/src/components/CustomRangePicker.vue +11 -7
- package/dist/spa/src/components/Filters.vue +16 -5
- package/dist/spa/src/components/MenuLink.vue +3 -3
- package/dist/spa/src/components/ResourceForm.vue +140 -111
- package/dist/spa/src/components/ResourceListTable.vue +76 -101
- package/dist/spa/src/components/SkeleteLoader.vue +23 -0
- package/dist/spa/src/components/Toast.vue +18 -7
- package/dist/spa/src/components/ValueRenderer.vue +10 -4
- package/dist/spa/src/composables/useStores.ts +25 -11
- package/dist/spa/src/index.scss +2 -1
- package/dist/spa/src/main.ts +1 -1
- package/dist/spa/src/router/index.ts +0 -1
- package/dist/spa/src/stores/user.ts +21 -3
- package/dist/spa/src/types/AdminForthConfig.ts +1477 -0
- package/dist/spa/src/types/FrontendAPI.ts +121 -0
- package/dist/spa/src/utils.ts +6 -4
- package/dist/spa/src/views/CreateView.vue +2 -9
- package/dist/spa/src/views/EditView.vue +1 -9
- package/dist/spa/src/views/ListView.vue +17 -16
- package/dist/spa/src/views/LoginView.vue +57 -31
- package/dist/spa/src/views/ResourceParent.vue +1 -2
- package/dist/spa/src/views/ShowView.vue +17 -21
- package/dist/spa/tailwind.config.js +3 -1
- package/dist/spa/vite.config.ts +7 -0
- package/package.json +2 -2
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
|
|
2
|
+
export interface FrontendAPIInterface {
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Show a confirmation dialog
|
|
6
|
+
*
|
|
7
|
+
* The dialog will be displayed to the user
|
|
8
|
+
*
|
|
9
|
+
* Example:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const isConfirmed = await window.adminforth.confirm({message: 'Are you sure?', yes: 'Yes', no: 'No'})
|
|
13
|
+
* if (isConfirmed) {
|
|
14
|
+
* your code...
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @param params - The parameters of the dialog
|
|
19
|
+
* @returns A promise that resolves when the user confirms the dialog
|
|
20
|
+
*/
|
|
21
|
+
confirm(params:ConfirmParams ): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Show an alert
|
|
24
|
+
*
|
|
25
|
+
* The alert will be displayed to the user
|
|
26
|
+
*
|
|
27
|
+
* Example:
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* window.adminforth.alert({message: 'Hello', variant: 'success'})
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @param params - The parameters of the alert
|
|
34
|
+
*/
|
|
35
|
+
alert(params:AlertParams): void;
|
|
36
|
+
/**
|
|
37
|
+
* Add a filter to the list of filters.
|
|
38
|
+
* Works only when user located on the list page.
|
|
39
|
+
* Can be used to set filter from charts or other components in pageInjections.
|
|
40
|
+
*
|
|
41
|
+
* Example:
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* window.adminforth.updateListFilter({field: 'name', operator: 'ilike', value: 'john'})
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @param filter - The filter to add
|
|
48
|
+
*/
|
|
49
|
+
setListFilter(filter: any): void;
|
|
50
|
+
/**
|
|
51
|
+
* Update a filter in the list of filters
|
|
52
|
+
*
|
|
53
|
+
* Example:
|
|
54
|
+
*
|
|
55
|
+
* ```ts
|
|
56
|
+
* window.adminforth.updateListFilter({field: 'name', operator: 'ilike', value: 'john'})
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @param filter - The filter to update
|
|
60
|
+
*/
|
|
61
|
+
updateListFilter(filter: any): void;
|
|
62
|
+
/**
|
|
63
|
+
* Clear all filters from the list
|
|
64
|
+
*/
|
|
65
|
+
clearListFilters(): void;
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type ConfirmParams = {
|
|
70
|
+
/**
|
|
71
|
+
* The message to display in the dialog
|
|
72
|
+
*/
|
|
73
|
+
message?: string;
|
|
74
|
+
/**
|
|
75
|
+
* The text to display in the "accept" button
|
|
76
|
+
*/
|
|
77
|
+
yes?: string;
|
|
78
|
+
/**
|
|
79
|
+
* The text to display in the "cancel" button
|
|
80
|
+
*/
|
|
81
|
+
no?: string;
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type AlertParams = {
|
|
86
|
+
/**
|
|
87
|
+
* The message to display in the alert
|
|
88
|
+
*/
|
|
89
|
+
message?: string;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The message to display in the alert as HTML (can be used instead of message)
|
|
93
|
+
*/
|
|
94
|
+
messageHtml?: string;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The variant of the alert
|
|
98
|
+
*/
|
|
99
|
+
variant?: AlertVariant | keyof typeof AlertVariant;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The timeout of the alert in seconds or 'unlimited' to keep the alert open until the user closes it.
|
|
103
|
+
* Default is 10 seconds;
|
|
104
|
+
*/
|
|
105
|
+
timeout?: number | 'unlimited';
|
|
106
|
+
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
export enum AlertVariant {
|
|
112
|
+
danger = 'danger',
|
|
113
|
+
success = 'success',
|
|
114
|
+
warning = 'warning',
|
|
115
|
+
info = 'info'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
package/dist/spa/src/utils.ts
CHANGED
|
@@ -6,7 +6,10 @@ import { useRouter } from 'vue-router';
|
|
|
6
6
|
import { useCoreStore } from './stores/core';
|
|
7
7
|
import { useUserStore } from './stores/user';
|
|
8
8
|
|
|
9
|
-
export async function callApi({path, method, body=undefined}
|
|
9
|
+
export async function callApi({path, method, body=undefined}: {
|
|
10
|
+
path: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
|
|
11
|
+
body?: any
|
|
12
|
+
}): Promise<any> {
|
|
10
13
|
const options = {
|
|
11
14
|
method,
|
|
12
15
|
headers: {
|
|
@@ -16,7 +19,7 @@ export async function callApi({path, method, body=undefined} ) {
|
|
|
16
19
|
};
|
|
17
20
|
const fullPath = `${import.meta.env.VITE_ADMINFORTH_PUBLIC_PATH || ''}${path}`;
|
|
18
21
|
const r = await fetch(fullPath, options);
|
|
19
|
-
if (r.status == 401) {
|
|
22
|
+
if (r.status == 401 ) {
|
|
20
23
|
useUserStore().unauthorize();
|
|
21
24
|
router.push({ name: 'login' });
|
|
22
25
|
return null;
|
|
@@ -39,7 +42,6 @@ export async function callAdminForthApi({ path, method, body=undefined }: {
|
|
|
39
42
|
|
|
40
43
|
export function getCustomComponent({ file, meta }: { file: string, meta: any }) {
|
|
41
44
|
const name = file.replace(/@/g, '').replace(/\./g, '').replace(/\//g, '');
|
|
42
|
-
console.log('resolving name', name);
|
|
43
45
|
return resolveComponent(name);
|
|
44
46
|
}
|
|
45
47
|
|
|
@@ -71,7 +73,7 @@ export const loadFile = (file: string) => {
|
|
|
71
73
|
return baseUrl;
|
|
72
74
|
}
|
|
73
75
|
|
|
74
|
-
export function checkEmptyValues(value: any, viewType:'show' | 'list' ) {
|
|
76
|
+
export function checkEmptyValues(value: any, viewType: 'show' | 'list' ) {
|
|
75
77
|
const config: CoreConfig | {} = useCoreStore().config;
|
|
76
78
|
let emptyFieldPlaceholder = '';
|
|
77
79
|
if (config.emptyFieldPlaceholder) {
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
@update:record="onUpdateRecord"
|
|
51
51
|
@update:isValid="isValid = $event"
|
|
52
52
|
:validating="validating"
|
|
53
|
-
:
|
|
53
|
+
:source="'create'"
|
|
54
54
|
>
|
|
55
55
|
</ResourceForm>
|
|
56
56
|
|
|
@@ -91,7 +91,6 @@ const route = useRoute();
|
|
|
91
91
|
const router = useRouter();
|
|
92
92
|
|
|
93
93
|
const record = ref({});
|
|
94
|
-
let createComponentsPerColumn = {};
|
|
95
94
|
|
|
96
95
|
const coreStore = useCoreStore();
|
|
97
96
|
|
|
@@ -115,17 +114,12 @@ onMounted(async () => {
|
|
|
115
114
|
await coreStore.fetchResourceFull({
|
|
116
115
|
resourceId: route.params.resourceId
|
|
117
116
|
});
|
|
118
|
-
createComponentsPerColumn = coreStore.resource.columns.reduce((acc, column) => {
|
|
119
|
-
if (column.components?.create) {
|
|
120
|
-
acc[column.name] = getCustomComponent(column.components.create);
|
|
121
|
-
}
|
|
122
|
-
return acc;
|
|
123
|
-
}, {});
|
|
124
117
|
loading.value = false;
|
|
125
118
|
checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'create');
|
|
126
119
|
});
|
|
127
120
|
|
|
128
121
|
async function saveRecord() {
|
|
122
|
+
console.log('saveRecord isValid', isValid.value);
|
|
129
123
|
if (!isValid.value) {
|
|
130
124
|
validating.value = true;
|
|
131
125
|
return;
|
|
@@ -143,7 +137,6 @@ async function saveRecord() {
|
|
|
143
137
|
});
|
|
144
138
|
if (response.error) {
|
|
145
139
|
showErrorTost(response.error);
|
|
146
|
-
|
|
147
140
|
}
|
|
148
141
|
saving.value = false;
|
|
149
142
|
if (route.query.returnTo) {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
@update:record="onUpdateRecord"
|
|
47
47
|
@update:isValid="isValid = $event"
|
|
48
48
|
:validating="validating"
|
|
49
|
-
:
|
|
49
|
+
:source="'edit'"
|
|
50
50
|
>
|
|
51
51
|
</ResourceForm>
|
|
52
52
|
|
|
@@ -88,7 +88,6 @@ const loading = ref(false);
|
|
|
88
88
|
const saving = ref(false);
|
|
89
89
|
|
|
90
90
|
const record = ref({});
|
|
91
|
-
let editComponentsPerColumn = {};
|
|
92
91
|
|
|
93
92
|
async function onUpdateRecord(newRecord) {
|
|
94
93
|
record.value = newRecord;
|
|
@@ -119,13 +118,6 @@ onMounted(async () => {
|
|
|
119
118
|
primaryKey: route.params.primaryKey,
|
|
120
119
|
});
|
|
121
120
|
checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'edit');
|
|
122
|
-
|
|
123
|
-
editComponentsPerColumn = coreStore.resource.columns.reduce((acc, column) => {
|
|
124
|
-
if (column.components?.edit) {
|
|
125
|
-
acc[column.name] = getCustomComponent(column.components.edit);
|
|
126
|
-
}
|
|
127
|
-
return acc;
|
|
128
|
-
}, {});
|
|
129
121
|
loading.value = false;
|
|
130
122
|
});
|
|
131
123
|
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
v-if="checkboxes.length"
|
|
23
23
|
data-tooltip-target="tooltip-remove-all"
|
|
24
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-
|
|
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-darkListTable dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
|
|
26
26
|
>
|
|
27
27
|
<IconBanOutline class="w-5 h-5 "/>
|
|
28
28
|
|
|
@@ -70,7 +70,7 @@
|
|
|
70
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"
|
|
71
71
|
v-if="filtersStore.filters.length">
|
|
72
72
|
{{ filtersStore.filters.length }}
|
|
73
|
-
|
|
73
|
+
</span>
|
|
74
74
|
</button>
|
|
75
75
|
</BreadcrumbsWithButtons>
|
|
76
76
|
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
@update:sort="sort = $event"
|
|
90
90
|
@update:checkboxes="checkboxes = $event"
|
|
91
91
|
@update:records="getList"
|
|
92
|
+
:sort="sort"
|
|
92
93
|
:pageSize="pageSize"
|
|
93
94
|
:totalRows="totalRows"
|
|
94
95
|
:checkboxes="checkboxes"
|
|
@@ -123,11 +124,7 @@ import { getCustomComponent } from '@/utils';
|
|
|
123
124
|
|
|
124
125
|
import {
|
|
125
126
|
IconBanOutline,
|
|
126
|
-
IconEyeSolid,
|
|
127
127
|
IconFilterOutline,
|
|
128
|
-
IconPenSolid,
|
|
129
|
-
IconTrashBinSolid,
|
|
130
|
-
IconInboxOutline,
|
|
131
128
|
IconPlusOutline
|
|
132
129
|
} from '@iconify-prerendered/vue-flowbite';
|
|
133
130
|
|
|
@@ -136,13 +133,11 @@ import Filters from '@/components/Filters.vue';
|
|
|
136
133
|
const filtersShow = ref(false);
|
|
137
134
|
|
|
138
135
|
const coreStore = useCoreStore();
|
|
139
|
-
const modalStore = useModalStore();
|
|
140
136
|
const filtersStore = useFiltersStore();
|
|
141
137
|
|
|
142
138
|
const route = useRoute();
|
|
143
139
|
|
|
144
140
|
const page = ref(1);
|
|
145
|
-
const filters = filtersStore.filters;
|
|
146
141
|
const columnsMinMax = ref({});
|
|
147
142
|
const sort = ref([]);
|
|
148
143
|
|
|
@@ -199,11 +194,6 @@ async function getList() {
|
|
|
199
194
|
totalRows.value = data.total;
|
|
200
195
|
}
|
|
201
196
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
197
|
async function startBulkAction(actionId) {
|
|
208
198
|
const data = await callAdminForthApi({
|
|
209
199
|
path: '/start_bulk_action',
|
|
@@ -215,10 +205,13 @@ async function startBulkAction(actionId) {
|
|
|
215
205
|
|
|
216
206
|
}
|
|
217
207
|
});
|
|
218
|
-
if (data?.
|
|
208
|
+
if (data?.ok) {
|
|
219
209
|
checkboxes.value = [];
|
|
210
|
+
await getList();
|
|
211
|
+
}
|
|
212
|
+
if (data?.error) {
|
|
213
|
+
showErrorTost(data.error);
|
|
220
214
|
}
|
|
221
|
-
await getList();
|
|
222
215
|
}
|
|
223
216
|
|
|
224
217
|
|
|
@@ -229,6 +222,15 @@ async function init() {
|
|
|
229
222
|
resourceId: route.params.resourceId
|
|
230
223
|
});
|
|
231
224
|
|
|
225
|
+
if (coreStore.resource.options?.defaultSort) {
|
|
226
|
+
sort.value = [{
|
|
227
|
+
field: coreStore.resource.options.defaultSort.columnName,
|
|
228
|
+
direction: coreStore.resource.options.defaultSort.direction
|
|
229
|
+
}];
|
|
230
|
+
} else {
|
|
231
|
+
sort.value = [];
|
|
232
|
+
}
|
|
233
|
+
|
|
232
234
|
await getList();
|
|
233
235
|
columnsMinMax.value = await callAdminForthApi({
|
|
234
236
|
path: '/get_min_max_for_columns',
|
|
@@ -250,7 +252,6 @@ onMounted(async () => {
|
|
|
250
252
|
watch(() => route.params.resourceId, async () => {
|
|
251
253
|
filtersStore.setFilters([]);
|
|
252
254
|
checkboxes.value = [];
|
|
253
|
-
sort.value = [];
|
|
254
255
|
await init();
|
|
255
256
|
});
|
|
256
257
|
|
|
@@ -22,17 +22,23 @@
|
|
|
22
22
|
</div>
|
|
23
23
|
<!-- Modal body -->
|
|
24
24
|
<div class="p-4 md:p-5">
|
|
25
|
-
<form class="space-y-4"
|
|
25
|
+
<form class="space-y-4" @submit.prevent>
|
|
26
26
|
<div>
|
|
27
27
|
<label for="username" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your {{ coreStore.config?.usernameFieldName?.toLowerCase() }}</label>
|
|
28
|
-
<input type="username" name="username" id="username"
|
|
28
|
+
<input type="username" name="username" id="username"
|
|
29
|
+
ref="usernameInput"
|
|
30
|
+
@keydown.enter="passwordInput.focus()"
|
|
31
|
+
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" placeholder="name@company.com" required />
|
|
29
32
|
</div>
|
|
30
33
|
<div class="relative">
|
|
31
34
|
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your password</label>
|
|
32
|
-
<input
|
|
35
|
+
<input
|
|
36
|
+
ref="passwordInput"
|
|
37
|
+
@keydown.enter="login"
|
|
38
|
+
:type="!showPw ? 'password': 'text'" name="password" id="password" placeholder="••••••••" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" required />
|
|
33
39
|
<button type="button" @click="showPw = !showPw" class="absolute top-12 right-3 -translate-y-1/2 text-gray-400 dark:text-gray-300">
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
<IconEyeSolid class="w-5 h-5" v-if="!showPw" />
|
|
41
|
+
<IconEyeSlashSolid class="w-5 h-5" v-else />
|
|
36
42
|
</button>
|
|
37
43
|
</div>
|
|
38
44
|
<!-- <div class="flex justify-between">
|
|
@@ -55,6 +61,17 @@
|
|
|
55
61
|
</div>
|
|
56
62
|
</div>
|
|
57
63
|
|
|
64
|
+
|
|
65
|
+
<div v-if="coreStore.config?.loginPromptHTML"
|
|
66
|
+
class="flex items-center p-4 mb-4 text-sm text-gray-800 rounded-lg bg-gray-50 dark:bg-gray-800 dark:text-gray-400" role="alert"
|
|
67
|
+
>
|
|
68
|
+
<svg class="flex-shrink-0 inline w-4 h-4 me-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
|
69
|
+
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
|
|
70
|
+
</svg>
|
|
71
|
+
<span class="sr-only">Info</span>
|
|
72
|
+
<div v-html="coreStore.config?.loginPromptHTML"></div>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
58
75
|
<button
|
|
59
76
|
@click="login"
|
|
60
77
|
type="submit" class="flex items-center justify-center gap-1 w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
|
|
@@ -85,8 +102,9 @@ import { useUserStore } from '@/stores/user';
|
|
|
85
102
|
import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
|
|
86
103
|
import { callAdminForthApi, loadFile } from '@/utils';
|
|
87
104
|
import { useRouter } from 'vue-router';
|
|
88
|
-
import { initFlowbite } from 'flowbite'
|
|
89
105
|
|
|
106
|
+
const passwordInput = ref(null);
|
|
107
|
+
const usernameInput = ref(null);
|
|
90
108
|
|
|
91
109
|
const router = useRouter();
|
|
92
110
|
const inProgress = ref(false);
|
|
@@ -99,35 +117,43 @@ const showPw = ref(false);
|
|
|
99
117
|
|
|
100
118
|
const error = ref(null);
|
|
101
119
|
|
|
102
|
-
onMounted(() => {
|
|
103
|
-
coreStore.getPublicConfig()
|
|
120
|
+
onMounted(async () => {
|
|
121
|
+
await coreStore.getPublicConfig();
|
|
122
|
+
if (coreStore.config?.demoCredentials) {
|
|
123
|
+
console.log('Setting demo credentials');
|
|
124
|
+
const [username, password] = coreStore.config.demoCredentials.split(':');
|
|
125
|
+
usernameInput.value.value = username;
|
|
126
|
+
passwordInput.value.value = password;
|
|
127
|
+
}
|
|
128
|
+
usernameInput.value.focus();
|
|
104
129
|
});
|
|
105
130
|
|
|
106
|
-
async function login() {
|
|
107
|
-
inProgress.value = true;
|
|
108
|
-
const resp = await callAdminForthApi({
|
|
109
|
-
path: '/login',
|
|
110
|
-
method: 'POST',
|
|
111
|
-
body: {
|
|
112
|
-
username: document.getElementById('username').value,
|
|
113
|
-
password: document.getElementById('password').value,
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
inProgress.value = false;
|
|
117
|
-
if (resp.error) {
|
|
118
|
-
error.value = resp.error;
|
|
119
|
-
} else {
|
|
120
|
-
error.value = null;
|
|
121
|
-
user.authorize()
|
|
122
|
-
router.push('/');
|
|
123
|
-
await router.isReady();
|
|
124
|
-
await coreStore.fetchMenuAndResource();
|
|
125
|
-
setTimeout(() => {
|
|
126
|
-
initFlowbite();
|
|
127
|
-
});
|
|
128
131
|
|
|
132
|
+
async function login() {
|
|
133
|
+
const username = usernameInput.value.value;
|
|
134
|
+
const password = passwordInput.value.value;
|
|
135
|
+
|
|
136
|
+
if (!username || !password) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
inProgress.value = true;
|
|
140
|
+
const resp = await callAdminForthApi({
|
|
141
|
+
path: '/login',
|
|
142
|
+
method: 'POST',
|
|
143
|
+
body: {
|
|
144
|
+
username,
|
|
145
|
+
password,
|
|
129
146
|
}
|
|
130
|
-
|
|
147
|
+
});
|
|
148
|
+
inProgress.value = false;
|
|
149
|
+
if (resp.error) {
|
|
150
|
+
error.value = resp.error;
|
|
151
|
+
} else if (resp.redirectTo) {
|
|
152
|
+
router.push(resp.redirectTo);
|
|
153
|
+
} else {
|
|
154
|
+
error.value = null;
|
|
155
|
+
await user.finishLogin();
|
|
156
|
+
}
|
|
131
157
|
}
|
|
132
158
|
|
|
133
159
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :key="`${$route?.params.resourceId}---${$route?.params.primaryKey}`">
|
|
2
|
+
<div :key="`${$route?.params.resourceId}---${$route?.params.primaryKey}`" class="p-4">
|
|
3
3
|
<RouterView/>
|
|
4
4
|
</div>
|
|
5
5
|
</template>
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
import { useCoreStore } from '@/stores/core';
|
|
14
|
-
import { onMounted } from 'vue';
|
|
15
14
|
|
|
16
15
|
const coreStore = useCoreStore()
|
|
17
16
|
|
|
@@ -43,23 +43,23 @@
|
|
|
43
43
|
<span class="sr-only">Loading...</span>
|
|
44
44
|
</div>
|
|
45
45
|
<div
|
|
46
|
-
v-else
|
|
47
|
-
class="relative overflow-x-auto shadow-
|
|
46
|
+
v-else-if="coreStore.record"
|
|
47
|
+
class="relative overflow-x-auto rounded-default shadow-resourseFormShadow "
|
|
48
48
|
>
|
|
49
49
|
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 rounded-default">
|
|
50
|
-
<thead class="text-xs text-gray-700 uppercase bg-
|
|
50
|
+
<thead class="text-xs text-gray-700 uppercase bg-lightFormHeading dark:bg-gray-700 dark:text-gray-400">
|
|
51
51
|
<tr>
|
|
52
52
|
<th scope="col" class="px-6 py-3">
|
|
53
53
|
Field
|
|
54
54
|
</th>
|
|
55
|
-
<th scope="col" class="px-6 py-3 w-
|
|
55
|
+
<th scope="col" class="px-6 py-3 w-5/6">
|
|
56
56
|
Value
|
|
57
57
|
</th>
|
|
58
58
|
</tr>
|
|
59
59
|
</thead>
|
|
60
60
|
<tbody>
|
|
61
61
|
<tr v-for="column in coreStore.resource?.columns.filter(c => c.showIn.includes('show'))" :key="column.name"
|
|
62
|
-
class="bg-
|
|
62
|
+
class="bg-lightForm bg-darkForm odd:dark:bg-gray-900 even:dark:bg-gray-800 border-b dark:border-gray-700"
|
|
63
63
|
>
|
|
64
64
|
<component
|
|
65
65
|
v-if="column.components?.showRow"
|
|
@@ -75,8 +75,8 @@
|
|
|
75
75
|
</td>
|
|
76
76
|
<td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap">
|
|
77
77
|
<component
|
|
78
|
-
v-if="
|
|
79
|
-
:is="
|
|
78
|
+
v-if="column?.components?.show"
|
|
79
|
+
:is="getCustomComponent(column?.components?.show)"
|
|
80
80
|
:resource="coreStore.resource"
|
|
81
81
|
:meta="column.components.show.meta"
|
|
82
82
|
:column="column"
|
|
@@ -90,7 +90,12 @@
|
|
|
90
90
|
</template>
|
|
91
91
|
</tr>
|
|
92
92
|
</tbody>
|
|
93
|
-
|
|
93
|
+
</table>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div v-else class="text-center text-gray-500 dark:text-gray-400 mt-10">
|
|
97
|
+
Ooops. Record not found
|
|
98
|
+
</div>
|
|
94
99
|
|
|
95
100
|
<component
|
|
96
101
|
v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.bottom || []"
|
|
@@ -101,7 +106,6 @@
|
|
|
101
106
|
:resource="coreStore.resource"
|
|
102
107
|
:adminUser="coreStore.adminUser"
|
|
103
108
|
/>
|
|
104
|
-
</div>
|
|
105
109
|
|
|
106
110
|
|
|
107
111
|
</div>
|
|
@@ -120,14 +124,13 @@ import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbi
|
|
|
120
124
|
import { onMounted, ref } from 'vue';
|
|
121
125
|
import { useRoute,useRouter } from 'vue-router';
|
|
122
126
|
import {callAdminForthApi} from '@/utils';
|
|
123
|
-
import {showSuccesTost} from '@/composables/useFrontendApi';
|
|
127
|
+
import { showSuccesTost, showErrorTost } from '@/composables/useFrontendApi';
|
|
124
128
|
|
|
125
129
|
|
|
126
130
|
const item = ref(null);
|
|
127
131
|
const route = useRoute();
|
|
128
132
|
const router = useRouter();
|
|
129
|
-
const loading = ref(
|
|
130
|
-
let showComponentsPerColumn = {};
|
|
133
|
+
const loading = ref(true);
|
|
131
134
|
|
|
132
135
|
console.log(route.params,'showWiev');
|
|
133
136
|
|
|
@@ -145,13 +148,6 @@ onMounted(async () => {
|
|
|
145
148
|
primaryKey: route.params.primaryKey,
|
|
146
149
|
});
|
|
147
150
|
checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'show');
|
|
148
|
-
|
|
149
|
-
showComponentsPerColumn = coreStore.resource.columns.reduce((acc, column) => {
|
|
150
|
-
if (column.components?.show) {
|
|
151
|
-
acc[column.name] = getCustomComponent(column.components.show);
|
|
152
|
-
}
|
|
153
|
-
return acc;
|
|
154
|
-
}, {});
|
|
155
151
|
loading.value = false;
|
|
156
152
|
});
|
|
157
153
|
|
|
@@ -174,7 +170,7 @@ async function deleteRecord(row) {
|
|
|
174
170
|
router.push({ name: 'resource-list', params: { resourceId: route.params.resourceId } });
|
|
175
171
|
showSuccesTost('Record deleted successfully')
|
|
176
172
|
} else {
|
|
177
|
-
|
|
173
|
+
showErrorTost(res.error)
|
|
178
174
|
}
|
|
179
175
|
|
|
180
176
|
} catch (e) {
|
|
@@ -185,4 +181,4 @@ async function deleteRecord(row) {
|
|
|
185
181
|
|
|
186
182
|
}
|
|
187
183
|
|
|
188
|
-
</script>
|
|
184
|
+
</script>
|
package/dist/spa/vite.config.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { fileURLToPath, URL } from 'node:url'
|
|
|
2
2
|
|
|
3
3
|
import { defineConfig } from 'vite'
|
|
4
4
|
import vue from '@vitejs/plugin-vue'
|
|
5
|
+
import { clear, error } from 'node:console';
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
const customLogger = {
|
|
@@ -26,7 +27,13 @@ const customLogger = {
|
|
|
26
27
|
clear() {
|
|
27
28
|
console.clear();
|
|
28
29
|
},
|
|
30
|
+
clearScreen() {
|
|
31
|
+
console.clear();
|
|
32
|
+
},
|
|
29
33
|
hasWarned: false,
|
|
34
|
+
hasErrorLogged: (error: any): boolean => {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
30
37
|
};
|
|
31
38
|
|
|
32
39
|
// https://vitejs.dev/config/
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adminforth",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.57",
|
|
4
4
|
"description": "OpenSource Vue3 powered forth-generation admin panel",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
9
9
|
"build": "tsc",
|
|
10
|
-
"prepareDist": "cp -
|
|
10
|
+
"prepareDist": "cp -rL spa dist/spa",
|
|
11
11
|
"rollout": "tsc && npm run prepareDist && npm version patch && npm publish && npm run rollout-doc",
|
|
12
12
|
"rollout-doc": "cd documentation && npm run build && npm run deploy",
|
|
13
13
|
"docs": "typedoc",
|