adminforth 1.2.96 → 1.2.97
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/dataConnectors/baseConnector.ts +26 -14
- package/dataConnectors/clickhouse.ts +57 -37
- package/dataConnectors/mongo.ts +45 -14
- package/dataConnectors/postgres.ts +53 -36
- package/dataConnectors/sqlite.ts +37 -35
- package/dist/dataConnectors/baseConnector.js +16 -7
- package/dist/dataConnectors/clickhouse.js +58 -48
- package/dist/dataConnectors/mongo.js +29 -12
- package/dist/dataConnectors/postgres.js +62 -52
- package/dist/dataConnectors/sqlite.js +52 -45
- package/dist/index.js +5 -1
- package/dist/modules/operationalResource.js +88 -0
- 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/dist/types/AdminForthConfig.js +37 -0
- package/index.ts +12 -3
- package/modules/operationalResource.ts +97 -0
- package/modules/restApi.ts +27 -26
- package/package.json +1 -1
- package/types/AdminForthConfig.ts +91 -8
- 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/
|
|
@@ -53,6 +53,43 @@ export var AdminForthResourcePages;
|
|
|
53
53
|
AdminForthResourcePages["create"] = "create";
|
|
54
54
|
AdminForthResourcePages["filter"] = "filter";
|
|
55
55
|
})(AdminForthResourcePages || (AdminForthResourcePages = {}));
|
|
56
|
+
export class Filters {
|
|
57
|
+
static EQ(field, value) {
|
|
58
|
+
return { field, operator: AdminForthFilterOperators.EQ, value };
|
|
59
|
+
}
|
|
60
|
+
static NEQ(field, value) {
|
|
61
|
+
return { field, operator: AdminForthFilterOperators.NE, value };
|
|
62
|
+
}
|
|
63
|
+
static GT(field, value) {
|
|
64
|
+
return { field, operator: AdminForthFilterOperators.GT, value };
|
|
65
|
+
}
|
|
66
|
+
static GTE(field, value) {
|
|
67
|
+
return { field, operator: AdminForthFilterOperators.GTE, value };
|
|
68
|
+
}
|
|
69
|
+
static LT(field, value) {
|
|
70
|
+
return { field, operator: AdminForthFilterOperators.LT, value };
|
|
71
|
+
}
|
|
72
|
+
static LTE(field, value) {
|
|
73
|
+
return { field, operator: AdminForthFilterOperators.LTE, value };
|
|
74
|
+
}
|
|
75
|
+
static IN(field, value) {
|
|
76
|
+
return { field, operator: AdminForthFilterOperators.IN, value };
|
|
77
|
+
}
|
|
78
|
+
static NOT_IN(field, value) {
|
|
79
|
+
return { field, operator: AdminForthFilterOperators.NIN, value };
|
|
80
|
+
}
|
|
81
|
+
static LIKE(field, value) {
|
|
82
|
+
return { field, operator: AdminForthFilterOperators.LIKE, value };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export class Sorts {
|
|
86
|
+
static ASC(field) {
|
|
87
|
+
return { field, direction: AdminForthSortDirections.asc };
|
|
88
|
+
}
|
|
89
|
+
static DESC(field) {
|
|
90
|
+
return { field, direction: AdminForthSortDirections.desc };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
56
93
|
export var AllowedActionsEnum;
|
|
57
94
|
(function (AllowedActionsEnum) {
|
|
58
95
|
AllowedActionsEnum["show"] = "show";
|
package/index.ts
CHANGED
|
@@ -10,7 +10,9 @@ import {
|
|
|
10
10
|
type AdminForthConfig,
|
|
11
11
|
type IAdminForth,
|
|
12
12
|
type IConfigValidator,
|
|
13
|
-
|
|
13
|
+
IOperationalResource,
|
|
14
|
+
AdminForthFilterOperators,
|
|
15
|
+
AdminForthDataTypes, AdminForthResourcePages, IHttpServer,
|
|
14
16
|
BeforeSaveFunction,
|
|
15
17
|
AfterSaveFunction,
|
|
16
18
|
AdminUser,
|
|
@@ -18,11 +20,12 @@ import {
|
|
|
18
20
|
} from './types/AdminForthConfig.js';
|
|
19
21
|
import AdminForthPlugin from './basePlugin.js';
|
|
20
22
|
import ConfigValidator from './modules/configValidator.js';
|
|
21
|
-
import AdminForthRestAPI from './modules/restApi.js';
|
|
23
|
+
import AdminForthRestAPI, { interpretResource } from './modules/restApi.js';
|
|
22
24
|
import ClickhouseConnector from './dataConnectors/clickhouse.js';
|
|
23
25
|
|
|
24
26
|
// exports
|
|
25
27
|
export * from './types/AdminForthConfig.js';
|
|
28
|
+
export { interpretResource };
|
|
26
29
|
export { AdminForthPlugin };
|
|
27
30
|
|
|
28
31
|
|
|
@@ -49,7 +52,9 @@ class AdminForth implements IAdminForth {
|
|
|
49
52
|
activatedPlugins: Array<AdminForthPlugin>;
|
|
50
53
|
configValidator: IConfigValidator;
|
|
51
54
|
restApi: AdminForthRestAPI;
|
|
52
|
-
|
|
55
|
+
resourceInstances: {
|
|
56
|
+
[resourceId: string]: IOperationalResource,
|
|
57
|
+
}
|
|
53
58
|
baseUrlSlashed: string;
|
|
54
59
|
|
|
55
60
|
statuses: {
|
|
@@ -247,6 +252,10 @@ class AdminForth implements IAdminForth {
|
|
|
247
252
|
return { ok: true };
|
|
248
253
|
}
|
|
249
254
|
|
|
255
|
+
resource(resourceId: string) {
|
|
256
|
+
return this.config.resources.find((res) => res.resourceId === resourceId);
|
|
257
|
+
}
|
|
258
|
+
|
|
250
259
|
setupEndpoints(server: IHttpServer) {
|
|
251
260
|
this.restApi.registerEndpoints(server);
|
|
252
261
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { IAdminForthFilter, IAdminForthSort, IOperationalResource, IAdminForthDataSourceConnectorBase, AdminForthResource, IAdminForth } from '../types/AdminForthConfig.js';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// export interface IOperationalResource {
|
|
5
|
+
// get: (filters: IAdminForthFilter | IAdminForthFilter[]) => Promise<any[]>;
|
|
6
|
+
|
|
7
|
+
// list: (filters: IAdminForthFilter | IAdminForthFilter[], limit: number, offset: number, sort: IAdminForthSort | IAdminForthSort[]) => Promise<any[]>;
|
|
8
|
+
|
|
9
|
+
// count: (filters: IAdminForthFilter | IAdminForthFilter[]) => Promise<number>;
|
|
10
|
+
|
|
11
|
+
// create: (record: any) => Promise<any>;
|
|
12
|
+
|
|
13
|
+
// update: (primaryKey: any, record: any) => Promise<any>;
|
|
14
|
+
|
|
15
|
+
// delete: (primaryKey: any) => Promise<boolean>;
|
|
16
|
+
|
|
17
|
+
// deleteMany: (primaryKeys: any[]) => Promise<boolean>;
|
|
18
|
+
// }
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
// async getData({ resource, limit, offset, sort, filters }: {
|
|
22
|
+
// resource: AdminForthResource,
|
|
23
|
+
// limit: number,
|
|
24
|
+
// offset: number,
|
|
25
|
+
// sort: { field: string, direction: AdminForthSortDirections }[],
|
|
26
|
+
// filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
|
|
27
|
+
// }): Promise<{ data: any[], total: number }> {
|
|
28
|
+
|
|
29
|
+
function filtersIfFilter(filter: IAdminForthFilter | IAdminForthFilter[]): IAdminForthFilter[] {
|
|
30
|
+
return (typeof filter === 'object' ? [filter] : filter) as IAdminForthFilter[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function sortsIfSort(sort: IAdminForthSort | IAdminForthSort[]): IAdminForthSort[] {
|
|
34
|
+
return (typeof sort === 'object' ? [sort] : sort) as IAdminForthSort[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class OperationalResource implements IOperationalResource {
|
|
38
|
+
dataConnector: IAdminForthDataSourceConnectorBase;
|
|
39
|
+
resourceConfig: AdminForthResource;
|
|
40
|
+
|
|
41
|
+
constructor(dataConnector: IAdminForthDataSourceConnectorBase, resourceConfig: AdminForthResource) {
|
|
42
|
+
this.dataConnector = dataConnector;
|
|
43
|
+
this.resourceConfig = resourceConfig;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async get(filter: IAdminForthFilter | IAdminForthFilter[]): Promise<any[]> {
|
|
47
|
+
return await this.dataConnector.getData({
|
|
48
|
+
resource: this.resourceConfig,
|
|
49
|
+
filters: filtersIfFilter(filter),
|
|
50
|
+
limit: 1,
|
|
51
|
+
offset: 0,
|
|
52
|
+
sort: [],
|
|
53
|
+
})[0];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async list(
|
|
57
|
+
filter: IAdminForthFilter | IAdminForthFilter[],
|
|
58
|
+
limit: number,
|
|
59
|
+
offset: number,
|
|
60
|
+
sort: IAdminForthSort | IAdminForthSort[]
|
|
61
|
+
): Promise<any[]> {
|
|
62
|
+
const { data } = await this.dataConnector.getData({
|
|
63
|
+
resource: this.resourceConfig,
|
|
64
|
+
filters: filtersIfFilter(filter),
|
|
65
|
+
limit,
|
|
66
|
+
offset,
|
|
67
|
+
sort: sortsIfSort(sort),
|
|
68
|
+
getTotals: false,
|
|
69
|
+
});
|
|
70
|
+
return data;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async count(filter: IAdminForthFilter | IAdminForthFilter[]): Promise<number> {
|
|
74
|
+
return await this.dataConnector.getCount({
|
|
75
|
+
resource: this.resourceConfig,
|
|
76
|
+
filters: filtersIfFilter(filter),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async create(record: any): Promise<any> {
|
|
81
|
+
return await this.dataConnector.createRecord({ resource: this.resourceConfig, record });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async update(primaryKey: any, record: any): Promise<any> {
|
|
85
|
+
return await this.dataConnector.updateRecord({
|
|
86
|
+
resource: this.resourceConfig,
|
|
87
|
+
recordId: primaryKey,
|
|
88
|
+
newValues: record
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async delete(primaryKey: any): Promise<boolean> {
|
|
93
|
+
return await this.dataConnector.deleteRecord({ resource: this.resourceConfig, recordId: primaryKey });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
}
|