adminforth 1.2.96 → 1.2.98
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 +2 -1
- package/dist/modules/restApi.js +22 -23
- package/dist/spa/index.html +4 -4
- package/dist/spa/package-lock.json +421 -6
- package/dist/spa/package.json +10 -2
- package/dist/spa/src/App.vue +236 -94
- package/dist/spa/src/assets/base.css +2 -0
- package/dist/spa/src/assets/logo.svg +19 -1
- package/dist/spa/src/components/AcceptModal.vue +3 -10
- package/dist/spa/src/components/Breadcrumbs.vue +3 -2
- package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
- package/dist/spa/src/components/CustomDatePicker.vue +25 -10
- package/dist/spa/src/components/CustomDateRangePicker.vue +14 -5
- package/dist/spa/src/components/Dropdown.vue +19 -5
- package/dist/spa/src/components/Filters.vue +103 -38
- package/dist/spa/src/components/MenuLink.vue +3 -3
- package/dist/spa/src/components/ResourceForm.vue +205 -116
- package/dist/spa/{spa/src → src}/components/ResourceListTable.vue +6 -3
- package/dist/spa/{spa/src → src}/components/Toast.vue +9 -3
- package/dist/spa/src/components/ValueRenderer.vue +30 -8
- package/dist/spa/src/index.scss +2 -1
- package/dist/spa/src/main.ts +2 -2
- package/dist/spa/src/router/index.ts +29 -19
- package/dist/spa/src/stores/core.ts +65 -59
- package/dist/spa/src/stores/modal.ts +13 -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 +69 -10
- package/dist/spa/src/views/CreateView.vue +69 -16
- package/dist/spa/src/views/EditView.vue +68 -14
- package/dist/spa/src/views/ListView.vue +100 -371
- package/dist/spa/src/views/LoginView.vue +67 -29
- package/dist/spa/src/views/ResourceParent.vue +1 -2
- package/dist/spa/src/views/ShowView.vue +116 -25
- package/dist/spa/tailwind.config.js +8 -3
- package/dist/spa/vite.config.ts +13 -0
- package/index.ts +2 -1
- package/modules/restApi.ts +27 -26
- package/package.json +1 -1
- package/dist/spa/.vscode/extensions.json +0 -6
- package/dist/spa/public/favicon.ico +0 -0
- package/dist/spa/spa/.eslintrc.cjs +0 -14
- package/dist/spa/spa/.vscode/extensions.json +0 -6
- package/dist/spa/spa/README.md +0 -39
- package/dist/spa/spa/env.d.ts +0 -1
- package/dist/spa/spa/index.html +0 -23
- package/dist/spa/spa/package-lock.json +0 -4573
- package/dist/spa/spa/package.json +0 -49
- package/dist/spa/spa/postcss.config.js +0 -6
- package/dist/spa/spa/public/favicon.ico +0 -0
- package/dist/spa/spa/src/App.vue +0 -314
- package/dist/spa/spa/src/assets/base.css +0 -0
- package/dist/spa/spa/src/assets/logo.svg +0 -19
- package/dist/spa/spa/src/components/AcceptModal.vue +0 -45
- package/dist/spa/spa/src/components/Breadcrumbs.vue +0 -40
- package/dist/spa/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
- package/dist/spa/spa/src/components/CustomDatePicker.vue +0 -174
- package/dist/spa/spa/src/components/CustomDateRangePicker.vue +0 -218
- package/dist/spa/spa/src/components/Dropdown.vue +0 -168
- package/dist/spa/spa/src/components/Filters.vue +0 -222
- package/dist/spa/spa/src/components/HelloWorld.vue +0 -17
- package/dist/spa/spa/src/components/MenuLink.vue +0 -24
- package/dist/spa/spa/src/components/ResourceForm.vue +0 -289
- package/dist/spa/spa/src/components/SingleSkeletLoader.vue +0 -13
- package/dist/spa/spa/src/components/ValueRenderer.vue +0 -66
- package/dist/spa/spa/src/components/icons/IconCalendar.vue +0 -5
- package/dist/spa/spa/src/components/icons/IconCommunity.vue +0 -7
- package/dist/spa/spa/src/components/icons/IconDocumentation.vue +0 -7
- package/dist/spa/spa/src/components/icons/IconEcosystem.vue +0 -7
- package/dist/spa/spa/src/components/icons/IconSupport.vue +0 -7
- package/dist/spa/spa/src/components/icons/IconTime.vue +0 -5
- package/dist/spa/spa/src/components/icons/IconTooling.vue +0 -19
- package/dist/spa/spa/src/index.scss +0 -27
- package/dist/spa/spa/src/main.ts +0 -18
- package/dist/spa/spa/src/router/index.ts +0 -63
- package/dist/spa/spa/src/stores/core.ts +0 -144
- package/dist/spa/spa/src/stores/modal.ts +0 -48
- package/dist/spa/spa/src/utils.ts +0 -103
- package/dist/spa/spa/src/views/CreateView.vue +0 -156
- package/dist/spa/spa/src/views/EditView.vue +0 -157
- package/dist/spa/spa/src/views/HomeView.vue +0 -8
- package/dist/spa/spa/src/views/ListView.vue +0 -258
- package/dist/spa/spa/src/views/LoginView.vue +0 -143
- package/dist/spa/spa/src/views/ResourceParent.vue +0 -17
- package/dist/spa/spa/src/views/ShowView.vue +0 -184
- package/dist/spa/spa/tailwind.config.js +0 -17
- package/dist/spa/spa/tsconfig.app.json +0 -14
- package/dist/spa/spa/tsconfig.json +0 -11
- package/dist/spa/spa/tsconfig.node.json +0 -19
- package/dist/spa/spa/vite.config.ts +0 -56
- package/dist/spa/src/views/HomeView.vue +0 -8
- package/dist/types.js +0 -30
- /package/dist/spa/{spa/public → public}/assets/favicon.png +0 -0
- /package/dist/spa/{spa/src → src}/components/CustomRangePicker.vue +0 -0
- /package/dist/spa/{spa/src → src}/components/SkeleteLoader.vue +0 -0
- /package/dist/spa/{spa/src → src}/composables/useFrontendApi.ts +0 -0
- /package/dist/spa/{spa/src → src}/composables/useStores.ts +0 -0
- /package/dist/spa/{spa/src → src}/spa_types/core.ts +0 -0
- /package/dist/spa/{spa/src → src}/stores/filters.ts +0 -0
- /package/dist/spa/{spa/src → src}/stores/toast.ts +0 -0
- /package/dist/spa/{spa/src → src}/stores/user.ts +0 -0
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="relative flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-800"
|
|
3
3
|
:style="coreStore.config?.loginBackgroundImage ? {
|
|
4
|
-
'background-image': 'url(' + coreStore.config?.loginBackgroundImage + ')',
|
|
4
|
+
'background-image': 'url(' + loadFile(coreStore.config?.loginBackgroundImage) + ')',
|
|
5
5
|
'background-size': 'cover',
|
|
6
6
|
'background-position': 'center',
|
|
7
7
|
'background-blend-mode': 'darken'
|
|
8
8
|
}: {}"
|
|
9
9
|
>
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
<!-- Main modal -->
|
|
11
12
|
<div id="authentication-modal" tabindex="-1" class=" overflow-y-auto overflow-x-hidden z-50 min-w-[400px] justify-center items-center md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
|
12
13
|
<div class="relative p-4 w-full max-w-md max-h-full">
|
|
13
14
|
<!-- Modal content -->
|
|
14
|
-
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
|
15
|
+
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black" >
|
|
15
16
|
<!-- Modal header -->
|
|
16
17
|
<div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
|
|
17
18
|
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">
|
|
@@ -21,17 +22,23 @@
|
|
|
21
22
|
</div>
|
|
22
23
|
<!-- Modal body -->
|
|
23
24
|
<div class="p-4 md:p-5">
|
|
24
|
-
<form class="space-y-4"
|
|
25
|
+
<form class="space-y-4" @submit.prevent>
|
|
25
26
|
<div>
|
|
26
27
|
<label for="username" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your {{ coreStore.config?.usernameFieldName?.toLowerCase() }}</label>
|
|
27
|
-
<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 />
|
|
28
32
|
</div>
|
|
29
33
|
<div class="relative">
|
|
30
34
|
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your password</label>
|
|
31
|
-
<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 />
|
|
32
39
|
<button type="button" @click="showPw = !showPw" class="absolute top-12 right-3 -translate-y-1/2 text-gray-400 dark:text-gray-300">
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
<IconEyeSolid class="w-5 h-5" v-if="!showPw" />
|
|
41
|
+
<IconEyeSlashSolid class="w-5 h-5" v-else />
|
|
35
42
|
</button>
|
|
36
43
|
</div>
|
|
37
44
|
<!-- <div class="flex justify-between">
|
|
@@ -54,6 +61,17 @@
|
|
|
54
61
|
</div>
|
|
55
62
|
</div>
|
|
56
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
|
+
|
|
57
75
|
<button
|
|
58
76
|
@click="login"
|
|
59
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">
|
|
@@ -78,44 +96,64 @@
|
|
|
78
96
|
|
|
79
97
|
<script setup>
|
|
80
98
|
|
|
81
|
-
import { onMounted, ref } from 'vue';
|
|
99
|
+
import { onMounted, ref, watchEffect } from 'vue';
|
|
82
100
|
import { useCoreStore } from '@/stores/core';
|
|
101
|
+
import { useUserStore } from '@/stores/user';
|
|
83
102
|
import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
|
|
84
|
-
import { callAdminForthApi } from '@/utils';
|
|
103
|
+
import { callAdminForthApi, loadFile } from '@/utils';
|
|
85
104
|
import { useRouter } from 'vue-router';
|
|
86
105
|
|
|
106
|
+
const passwordInput = ref(null);
|
|
107
|
+
const usernameInput = ref(null);
|
|
108
|
+
|
|
87
109
|
const router = useRouter();
|
|
88
110
|
const inProgress = ref(false);
|
|
89
111
|
|
|
90
112
|
const coreStore = useCoreStore();
|
|
113
|
+
const user = useUserStore();
|
|
114
|
+
|
|
91
115
|
|
|
92
116
|
const showPw = ref(false);
|
|
93
117
|
|
|
94
118
|
const error = ref(null);
|
|
95
119
|
|
|
96
|
-
onMounted(() => {
|
|
97
|
-
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();
|
|
98
129
|
});
|
|
99
130
|
|
|
131
|
+
|
|
100
132
|
async function login() {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
error.value = null;
|
|
115
|
-
router.push('/');
|
|
116
|
-
await coreStore.fetchMenuAndResource();
|
|
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,
|
|
117
146
|
}
|
|
118
|
-
|
|
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
|
+
}
|
|
119
157
|
}
|
|
120
158
|
|
|
121
159
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div>
|
|
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
|
|
|
@@ -1,21 +1,38 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="relative">
|
|
3
|
+
<component
|
|
4
|
+
v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.beforeBreadcrumbs || []"
|
|
5
|
+
:is="getCustomComponent(c)"
|
|
6
|
+
:meta="c.meta"
|
|
7
|
+
:record="coreStore.record"
|
|
8
|
+
:resource="coreStore.resource"
|
|
9
|
+
:adminUser="coreStore.adminUser"
|
|
10
|
+
/>
|
|
3
11
|
<BreadcrumbsWithButtons>
|
|
4
|
-
<RouterLink :to="{ name: 'resource-edit', params: { resourceId: $route.params.resourceId, primaryKey: $route.params.primaryKey } }"
|
|
5
|
-
class="flex items-center py-1 px-3
|
|
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 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-default border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
|
6
14
|
>
|
|
7
15
|
<IconPenSolid class="w-4 h-4" />
|
|
8
16
|
Edit
|
|
9
17
|
</RouterLink>
|
|
10
18
|
|
|
11
|
-
<button @click="deleteRecord"
|
|
12
|
-
class="flex items-center py-1 px-3
|
|
19
|
+
<button v-if="coreStore?.resourceOptions?.allowedActions?.delete" @click="deleteRecord"
|
|
20
|
+
class="flex items-center py-1 px-3 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-white 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"
|
|
13
21
|
>
|
|
14
22
|
<IconTrashBinSolid class="w-4 h-4" />
|
|
15
23
|
Delete
|
|
16
24
|
</button>
|
|
17
25
|
</BreadcrumbsWithButtons>
|
|
18
26
|
|
|
27
|
+
<component
|
|
28
|
+
v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.afterBreadcrumbs || []"
|
|
29
|
+
:is="getCustomComponent(c)"
|
|
30
|
+
:meta="c.meta"
|
|
31
|
+
:record="coreStore.record"
|
|
32
|
+
:resource="coreStore.resource"
|
|
33
|
+
:adminUser="coreStore.adminUser"
|
|
34
|
+
/>
|
|
35
|
+
|
|
19
36
|
<div v-if="loading" role="status" class="max-w-sm animate-pulse">
|
|
20
37
|
<div class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4"></div>
|
|
21
38
|
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px] mb-2.5"></div>
|
|
@@ -26,35 +43,69 @@
|
|
|
26
43
|
<span class="sr-only">Loading...</span>
|
|
27
44
|
</div>
|
|
28
45
|
<div
|
|
29
|
-
v-else
|
|
30
|
-
class="relative overflow-x-auto shadow-
|
|
46
|
+
v-else-if="coreStore.record"
|
|
47
|
+
class="relative overflow-x-auto rounded-default shadow-resourseFormShadow "
|
|
31
48
|
>
|
|
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-
|
|
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-lightFormHeading dark:bg-gray-700 dark:text-gray-400">
|
|
34
51
|
<tr>
|
|
35
52
|
<th scope="col" class="px-6 py-3">
|
|
36
53
|
Field
|
|
37
54
|
</th>
|
|
38
|
-
<th scope="col" class="px-6 py-3 w-
|
|
55
|
+
<th scope="col" class="px-6 py-3 w-5/6">
|
|
39
56
|
Value
|
|
40
57
|
</th>
|
|
41
58
|
</tr>
|
|
42
59
|
</thead>
|
|
43
60
|
<tbody>
|
|
44
|
-
<tr v-for="column in coreStore.
|
|
45
|
-
class="
|
|
61
|
+
<tr v-for="column in coreStore.resource?.columns.filter(c => c.showIn.includes('show'))" :key="column.name"
|
|
62
|
+
class="bg-lightForm bg-darkForm odd:dark:bg-gray-900 even:dark:bg-gray-800 border-b dark:border-gray-700"
|
|
46
63
|
>
|
|
47
|
-
|
|
48
|
-
|
|
64
|
+
<component
|
|
65
|
+
v-if="column.components?.showRow"
|
|
66
|
+
:is="getCustomComponent(column.components.showRow)"
|
|
67
|
+
:meta="column.components.showRow.meta"
|
|
68
|
+
:column="column"
|
|
69
|
+
:resource="coreStore.resource"
|
|
70
|
+
:record="coreStore.record"
|
|
71
|
+
/>
|
|
72
|
+
<template v-else>
|
|
73
|
+
<td class="px-6 py-4 whitespace-nowrap "> <!--align-top-->
|
|
74
|
+
{{ column.label }}
|
|
49
75
|
</td>
|
|
50
76
|
<td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap">
|
|
51
|
-
|
|
77
|
+
<component
|
|
78
|
+
v-if="column?.components?.show"
|
|
79
|
+
:is="getCustomComponent(column?.components?.show)"
|
|
80
|
+
:resource="coreStore.resource"
|
|
81
|
+
:meta="column.components.show.meta"
|
|
82
|
+
:column="column"
|
|
83
|
+
:record="coreStore.record"
|
|
84
|
+
/>
|
|
85
|
+
<ValueRenderer v-else
|
|
86
|
+
:column="column"
|
|
87
|
+
:record="coreStore.record"
|
|
88
|
+
/>
|
|
52
89
|
</td>
|
|
90
|
+
</template>
|
|
53
91
|
</tr>
|
|
54
|
-
|
|
55
92
|
</tbody>
|
|
56
|
-
|
|
57
|
-
</div>
|
|
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>
|
|
99
|
+
|
|
100
|
+
<component
|
|
101
|
+
v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.bottom || []"
|
|
102
|
+
:is="getCustomComponent(c)"
|
|
103
|
+
:meta="c.meta"
|
|
104
|
+
:column="column"
|
|
105
|
+
:record="coreStore.record"
|
|
106
|
+
:resource="coreStore.resource"
|
|
107
|
+
:adminUser="coreStore.adminUser"
|
|
108
|
+
/>
|
|
58
109
|
|
|
59
110
|
|
|
60
111
|
</div>
|
|
@@ -63,31 +114,71 @@
|
|
|
63
114
|
|
|
64
115
|
<script setup>
|
|
65
116
|
|
|
66
|
-
import { ref, computed, onMounted } from 'vue';
|
|
67
|
-
import { useRoute } from 'vue-router';
|
|
68
|
-
import { callAdminForthApi } from '@/utils';
|
|
69
117
|
import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
70
|
-
|
|
71
|
-
import { useCoreStore } from '@/stores/core';
|
|
118
|
+
|
|
72
119
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
120
|
+
import { useCoreStore } from '@/stores/core';
|
|
121
|
+
import { useModalStore } from '@/stores/modal';
|
|
122
|
+
import { getCustomComponent, checkAcessByAllowedActions } from '@/utils';
|
|
123
|
+
import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
|
|
124
|
+
import { onMounted, ref } from 'vue';
|
|
125
|
+
import { useRoute,useRouter } from 'vue-router';
|
|
126
|
+
import {callAdminForthApi} from '@/utils';
|
|
127
|
+
import { showSuccesTost, showErrorTost } from '@/composables/useFrontendApi';
|
|
73
128
|
|
|
74
129
|
|
|
75
130
|
const item = ref(null);
|
|
76
131
|
const route = useRoute();
|
|
77
|
-
const
|
|
132
|
+
const router = useRouter();
|
|
133
|
+
const loading = ref(true);
|
|
134
|
+
|
|
135
|
+
console.log(route.params,'showWiev');
|
|
136
|
+
|
|
78
137
|
|
|
79
138
|
const coreStore = useCoreStore();
|
|
139
|
+
const modalStore = useModalStore();
|
|
80
140
|
|
|
81
141
|
onMounted(async () => {
|
|
82
142
|
loading.value = true;
|
|
83
|
-
await coreStore.
|
|
143
|
+
await coreStore.fetchResourceFull({
|
|
84
144
|
resourceId: route.params.resourceId
|
|
85
145
|
});
|
|
86
146
|
await coreStore.fetchRecord({
|
|
87
147
|
resourceId: route.params.resourceId,
|
|
88
148
|
primaryKey: route.params.primaryKey,
|
|
89
149
|
});
|
|
150
|
+
checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'show');
|
|
90
151
|
loading.value = false;
|
|
91
152
|
});
|
|
92
153
|
|
|
93
|
-
|
|
154
|
+
async function deleteRecord(row) {
|
|
155
|
+
const data = await window.adminforth.confirm({
|
|
156
|
+
message: 'Are you sure you want to delete this item?',
|
|
157
|
+
yes: 'Delete',
|
|
158
|
+
no: 'Cancel',
|
|
159
|
+
});
|
|
160
|
+
if (data) {
|
|
161
|
+
try {
|
|
162
|
+
const res = await callAdminForthApi({
|
|
163
|
+
path: '/delete_record',
|
|
164
|
+
method: 'POST',
|
|
165
|
+
body: {
|
|
166
|
+
resourceId: route.params.resourceId,
|
|
167
|
+
primaryKey: route.params.primaryKey,
|
|
168
|
+
}});
|
|
169
|
+
if (!res.error){
|
|
170
|
+
router.push({ name: 'resource-list', params: { resourceId: route.params.resourceId } });
|
|
171
|
+
showSuccesTost('Record deleted successfully')
|
|
172
|
+
} else {
|
|
173
|
+
showErrorTost(res.error)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
} catch (e) {
|
|
177
|
+
console.error(e);
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
</script>
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
/** @type {import('tailwindcss').Config} */
|
|
2
2
|
export default {
|
|
3
|
-
content: ["./src/**/*.{vue, js}", "./node_modules/flowbite/**/*.js"],
|
|
3
|
+
content: ["./src/**/*.{vue, js, ts, tsx}","./src/*.{vue, js, ts, tsx}", "./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
|
-
require('flowbite/plugin')
|
|
12
|
+
require('flowbite/plugin')({
|
|
13
|
+
charts: true,
|
|
14
|
+
}),
|
|
10
15
|
],
|
|
11
16
|
}
|
|
12
17
|
|
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 = {
|
|
@@ -11,6 +12,12 @@ const customLogger = {
|
|
|
11
12
|
console.log(msg);
|
|
12
13
|
}
|
|
13
14
|
},
|
|
15
|
+
warnOnce(msg: string) {
|
|
16
|
+
console.warn('warn once', msg);
|
|
17
|
+
if (!this.hasWarned) {
|
|
18
|
+
this.hasWarned = true;
|
|
19
|
+
}
|
|
20
|
+
},
|
|
14
21
|
warn(msg: string) {
|
|
15
22
|
console.warn(msg);
|
|
16
23
|
},
|
|
@@ -20,7 +27,13 @@ const customLogger = {
|
|
|
20
27
|
clear() {
|
|
21
28
|
console.clear();
|
|
22
29
|
},
|
|
30
|
+
clearScreen() {
|
|
31
|
+
console.clear();
|
|
32
|
+
},
|
|
23
33
|
hasWarned: false,
|
|
34
|
+
hasErrorLogged: (error: any): boolean => {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
24
37
|
};
|
|
25
38
|
|
|
26
39
|
// https://vitejs.dev/config/
|
package/index.ts
CHANGED
|
@@ -18,11 +18,12 @@ import {
|
|
|
18
18
|
} from './types/AdminForthConfig.js';
|
|
19
19
|
import AdminForthPlugin from './basePlugin.js';
|
|
20
20
|
import ConfigValidator from './modules/configValidator.js';
|
|
21
|
-
import AdminForthRestAPI from './modules/restApi.js';
|
|
21
|
+
import AdminForthRestAPI, { interpretResource } from './modules/restApi.js';
|
|
22
22
|
import ClickhouseConnector from './dataConnectors/clickhouse.js';
|
|
23
23
|
|
|
24
24
|
// exports
|
|
25
25
|
export * from './types/AdminForthConfig.js';
|
|
26
|
+
export { interpretResource };
|
|
26
27
|
export { AdminForthPlugin };
|
|
27
28
|
|
|
28
29
|
|
package/modules/restApi.ts
CHANGED
|
@@ -21,6 +21,32 @@ import { ADMINFORTH_VERSION, listify } from './utils.js';
|
|
|
21
21
|
|
|
22
22
|
import AdminForthAuth from "../auth.js";
|
|
23
23
|
|
|
24
|
+
|
|
25
|
+
export async function interpretResource(adminUser: AdminUser, resource: AdminForthResource, meta: any, source: ActionCheckSource): Promise<{allowedActions: AllowedActionsResolved}> {
|
|
26
|
+
// if (process.env.HEAVY_DEBUG) {
|
|
27
|
+
// console.log('🪲Interpreting resource', resource.resourceId, source, 'adminUser', adminUser);
|
|
28
|
+
// }
|
|
29
|
+
const allowedActions = {};
|
|
30
|
+
|
|
31
|
+
await Promise.all(
|
|
32
|
+
Object.entries(resource.options?.allowedActions || {}).map(
|
|
33
|
+
async ([key, value]: [string, AllowedActionValue]) => {
|
|
34
|
+
if (process.env.HEAVY_DEBUG) {
|
|
35
|
+
console.log('🪲checking for allowed call', key, 'value:', value, 'typeof', typeof value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// if callable then call
|
|
39
|
+
if (typeof value === 'function') {
|
|
40
|
+
allowedActions[key] = await value({ adminUser, resource, meta, source });
|
|
41
|
+
} else {
|
|
42
|
+
allowedActions[key] = value;
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return { allowedActions };
|
|
48
|
+
}
|
|
49
|
+
|
|
24
50
|
export default class AdminForthRestAPI {
|
|
25
51
|
|
|
26
52
|
adminforth: IAdminForth;
|
|
@@ -188,11 +214,9 @@ export default class AdminForthRestAPI {
|
|
|
188
214
|
}
|
|
189
215
|
|
|
190
216
|
async function processMenuItem(menuItem) {
|
|
191
|
-
|
|
192
217
|
if (menuItem.badge) {
|
|
193
218
|
if (typeof menuItem.badge === 'function') {
|
|
194
219
|
menuItem.badge = await menuItem.badge(adminUser);
|
|
195
|
-
return menuItem
|
|
196
220
|
}
|
|
197
221
|
}
|
|
198
222
|
}
|
|
@@ -248,30 +272,7 @@ export default class AdminForthRestAPI {
|
|
|
248
272
|
},
|
|
249
273
|
});
|
|
250
274
|
|
|
251
|
-
|
|
252
|
-
// if (process.env.HEAVY_DEBUG) {
|
|
253
|
-
// console.log('🪲Interpreting resource', resource.resourceId, source, 'adminUser', adminUser);
|
|
254
|
-
// }
|
|
255
|
-
const allowedActions = {};
|
|
256
|
-
|
|
257
|
-
await Promise.all(
|
|
258
|
-
Object.entries(resource.options?.allowedActions || {}).map(
|
|
259
|
-
async ([key, value]: [string, AllowedActionValue]) => {
|
|
260
|
-
if (process.env.HEAVY_DEBUG) {
|
|
261
|
-
console.log('🪲checking for allowed call', key, 'value:', value, 'typeof', typeof value);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// if callable then call
|
|
265
|
-
if (typeof value === 'function') {
|
|
266
|
-
allowedActions[key] = await value({ adminUser, resource, meta, source });
|
|
267
|
-
} else {
|
|
268
|
-
allowedActions[key] = value;
|
|
269
|
-
}
|
|
270
|
-
})
|
|
271
|
-
);
|
|
272
|
-
|
|
273
|
-
return { allowedActions };
|
|
274
|
-
}
|
|
275
|
+
|
|
275
276
|
|
|
276
277
|
function checkAccess(action: AllowedActionsEnum, allowedActions: AllowedActions): { allowed: boolean, error?: string } {
|
|
277
278
|
const allowed = (allowedActions[action] as boolean | string | undefined);
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/* eslint-env node */
|
|
2
|
-
require('@rushstack/eslint-patch/modern-module-resolution')
|
|
3
|
-
|
|
4
|
-
module.exports = {
|
|
5
|
-
root: true,
|
|
6
|
-
'extends': [
|
|
7
|
-
'plugin:vue/vue3-essential',
|
|
8
|
-
'eslint:recommended',
|
|
9
|
-
'@vue/eslint-config-typescript'
|
|
10
|
-
],
|
|
11
|
-
parserOptions: {
|
|
12
|
-
ecmaVersion: 'latest'
|
|
13
|
-
}
|
|
14
|
-
}
|
package/dist/spa/spa/README.md
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# spa
|
|
2
|
-
|
|
3
|
-
This template should help get you started developing with Vue 3 in Vite.
|
|
4
|
-
|
|
5
|
-
## Recommended IDE Setup
|
|
6
|
-
|
|
7
|
-
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
|
8
|
-
|
|
9
|
-
## Type Support for `.vue` Imports in TS
|
|
10
|
-
|
|
11
|
-
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
|
12
|
-
|
|
13
|
-
## Customize configuration
|
|
14
|
-
|
|
15
|
-
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
|
16
|
-
|
|
17
|
-
## Project Setup
|
|
18
|
-
|
|
19
|
-
```sh
|
|
20
|
-
npm install
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### Compile and Hot-Reload for Development
|
|
24
|
-
|
|
25
|
-
```sh
|
|
26
|
-
npm run dev
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
### Type-Check, Compile and Minify for Production
|
|
30
|
-
|
|
31
|
-
```sh
|
|
32
|
-
npm run build
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### Lint with [ESLint](https://eslint.org/)
|
|
36
|
-
|
|
37
|
-
```sh
|
|
38
|
-
npm run lint
|
|
39
|
-
```
|
package/dist/spa/spa/env.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/// <reference types="vite/client" />
|
package/dist/spa/spa/index.html
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<link rel="icon" href="/* IMPORTANT:ADMINFORTH FAVICON */">
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
-
<title>/* IMPORTANT:ADMINFORTH TITLE */</title>
|
|
8
|
-
<!--
|
|
9
|
-
<script>
|
|
10
|
-
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
|
|
11
|
-
if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
|
12
|
-
document.documentElement.classList.add('dark');
|
|
13
|
-
} else {
|
|
14
|
-
document.documentElement.classList.remove('dark')
|
|
15
|
-
}
|
|
16
|
-
</script> -->
|
|
17
|
-
|
|
18
|
-
</head>
|
|
19
|
-
<body class=" ">
|
|
20
|
-
<div id="app" class="min-h-screen bg-lightHtml dark:bg-darkHtml"></div>
|
|
21
|
-
<script type="module" src="/src/main.ts"></script>
|
|
22
|
-
</body>
|
|
23
|
-
</html>
|