adminforth 1.0.81 → 1.0.83
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +6 -0
- package/dist/modules/codeInjector.js +7 -0
- package/dist/spa/spa/src/App.vue +1 -1
- package/dist/spa/spa/src/components/ResourceForm.vue +5 -4
- package/dist/spa/spa/src/stores/core.ts +1 -0
- package/dist/spa/spa/src/utils.ts +12 -0
- package/dist/spa/spa/src/views/CreateView.vue +5 -1
- package/dist/spa/spa/src/views/EditView.vue +3 -1
- package/dist/spa/spa/src/views/ListView.vue +2 -1
- package/dist/spa/spa/src/views/ShowView.vue +47 -3
- package/documentation/docusaurus.config.ts +37 -25
- package/documentation/package.json +1 -1
- package/documentation/src/components/HomepageFeatures/index.tsx +13 -15
- package/documentation/src/pages/index.module.css +1 -0
- package/documentation/src/pages/index.tsx +2 -2
- package/documentation/static/img/css.png +0 -0
- package/documentation/static/img/db.png +0 -0
- package/documentation/static/img/db2.png +0 -0
- package/documentation/static/img/gen.sh +24 -0
- package/documentation/static/img/tail.png +0 -0
- package/documentation/static/img/tail.png:Zone.Identifier +0 -0
- package/documentation/static/img/vue.png +0 -0
- package/index.ts +6 -0
- package/modules/codeInjector.ts +7 -0
- package/package.json +2 -2
- package/spa/src/App.vue +1 -1
- package/spa/src/components/ResourceForm.vue +5 -4
- package/spa/src/stores/core.ts +1 -0
- package/spa/src/utils.ts +12 -0
- package/spa/src/views/CreateView.vue +5 -1
- package/spa/src/views/EditView.vue +3 -1
- package/spa/src/views/ListView.vue +2 -1
- package/spa/src/views/ShowView.vue +47 -3
- package/styles.js +52 -0
- package/types/AdminForthConfig.ts +240 -9
- package/documentation/static/img/undraw_docusaurus_mountain.svg +0 -171
- package/documentation/static/img/undraw_docusaurus_react.svg +0 -170
- package/documentation/static/img/undraw_docusaurus_tree.svg +0 -40
package/dist/index.js
CHANGED
|
@@ -859,6 +859,12 @@ class AdminForth {
|
|
|
859
859
|
if (!resource) {
|
|
860
860
|
return { error: `Resource '${body['resourceId']}' not found` };
|
|
861
861
|
}
|
|
862
|
+
if (!record) {
|
|
863
|
+
return { error: `Record with ${body['primaryKey']} not found` };
|
|
864
|
+
}
|
|
865
|
+
if (resource.options.allowedActions.delete === false) {
|
|
866
|
+
return { error: `Resource '${resource.resourceId}' does not allow delete action` };
|
|
867
|
+
}
|
|
862
868
|
// execute hook if needed
|
|
863
869
|
for (const hook of getFunctionList((_10 = (_9 = resource.hooks) === null || _9 === void 0 ? void 0 : _9.delete) === null || _10 === void 0 ? void 0 : _10.beforeSave)) {
|
|
864
870
|
const resp = yield hook({ resource, record, adminUser });
|
|
@@ -17,6 +17,7 @@ import path from 'path';
|
|
|
17
17
|
import { promisify } from 'util';
|
|
18
18
|
import { ADMIN_FORTH_ABSOLUTE_PATH } from './utils.js';
|
|
19
19
|
import { getComponentNameFromPath } from './utils.js';
|
|
20
|
+
import { styles } from '../styles.js';
|
|
20
21
|
let TMP_DIR;
|
|
21
22
|
try {
|
|
22
23
|
TMP_DIR = os.tmpdir();
|
|
@@ -290,6 +291,12 @@ class CodeInjector {
|
|
|
290
291
|
tailwindConfigContent = tailwindConfigContent.replace('/* IMPORTANT:ADMINFORTH TAILWIND STYLES */', stylesText);
|
|
291
292
|
yield fs.promises.writeFile(tailwindConfigPath, tailwindConfigContent);
|
|
292
293
|
}
|
|
294
|
+
else {
|
|
295
|
+
let tailwindConfigPath = path.join(CodeInjector.SPA_TMP_PATH, 'tailwind.config.js');
|
|
296
|
+
let tailwindConfigContent = yield fs.promises.readFile(tailwindConfigPath, 'utf-8');
|
|
297
|
+
tailwindConfigContent = tailwindConfigContent.replace('/* IMPORTANT:ADMINFORTH TAILWIND STYLES */', styles());
|
|
298
|
+
yield fs.promises.writeFile(tailwindConfigPath, tailwindConfigContent);
|
|
299
|
+
}
|
|
293
300
|
const routerVuePath = path.join(CodeInjector.SPA_TMP_PATH, 'src', 'router', 'index.ts');
|
|
294
301
|
let routerVueContent = yield fs.promises.readFile(routerVuePath, 'utf-8');
|
|
295
302
|
routerVueContent = routerVueContent.replace('/* IMPORTANT:ADMINFORTH ROUTES IMPORTS */', routerComponents);
|
package/dist/spa/spa/src/App.vue
CHANGED
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
:class="{ '-translate-x-full': !sideBarOpen, 'transform-none': sideBarOpen }"
|
|
64
64
|
aria-label="Sidebar"
|
|
65
65
|
>
|
|
66
|
-
<div class="h-full px-3 pb-4 overflow-y-auto bg-nav-menu-bg dark:bg-gray-800">
|
|
66
|
+
<div class="h-full px-3 pb-4 overflow-y-auto bg-nav-menu-bg dark:bg-gray-800 border-r">
|
|
67
67
|
|
|
68
68
|
<div class="flex ms-2 md:me-24 m-4 ">
|
|
69
69
|
<img :src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')" :alt="`${ coreStore.config?.brandName } Logo`" class="h-8 me-3" />
|
|
@@ -121,9 +121,6 @@
|
|
|
121
121
|
<IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
|
|
122
122
|
<IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
|
|
123
123
|
</button>
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
124
|
<div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
|
|
128
125
|
|
|
129
126
|
<div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-gray-400 dark:text-gray-500">{{ column.editingNote[mode] }}</div>
|
|
@@ -197,7 +194,11 @@ const columnError = (column) => {
|
|
|
197
194
|
for (let i = 0; i < validationArray.length; i++) {
|
|
198
195
|
if (validationArray[i].regExp) {
|
|
199
196
|
const regExp = new RegExp(validationArray[i].regExp);
|
|
200
|
-
|
|
197
|
+
let value = currentValues.value[column.name];
|
|
198
|
+
if (value === undefined || value === null) {
|
|
199
|
+
value = '';
|
|
200
|
+
}
|
|
201
|
+
if (!regExp.test(value)) {
|
|
201
202
|
return validationArray[i].message;
|
|
202
203
|
}
|
|
203
204
|
}
|
|
@@ -2,6 +2,7 @@ import { onMounted, ref, resolveComponent } from 'vue';
|
|
|
2
2
|
import type { CoreConfig } from './spa_types/core';
|
|
3
3
|
|
|
4
4
|
import router from "./router";
|
|
5
|
+
import { useRouter } from 'vue-router';
|
|
5
6
|
import { useCoreStore } from './stores/core';
|
|
6
7
|
|
|
7
8
|
export async function callApi({path, method, body=undefined} ) {
|
|
@@ -80,3 +81,14 @@ export function checkEmptyValues(value: any, viewType:'show' | 'list' ) {
|
|
|
80
81
|
}
|
|
81
82
|
return value;
|
|
82
83
|
}
|
|
84
|
+
|
|
85
|
+
export function checkAcessByAllowedActions(allowedActions:any, action:any ) {
|
|
86
|
+
if (!allowedActions) {
|
|
87
|
+
console.warn('allowedActions not set');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if(allowedActions[action] === false) {
|
|
91
|
+
console.warn(`Action ${action} is not allowed`);
|
|
92
|
+
router.back();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
</BreadcrumbsWithButtons>
|
|
24
24
|
|
|
25
25
|
<SingleSkeletLoader v-if="loading"></SingleSkeletLoader>
|
|
26
|
+
|
|
26
27
|
|
|
27
28
|
<ResourceForm
|
|
28
29
|
v-else
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
>
|
|
36
37
|
</ResourceForm>
|
|
37
38
|
|
|
39
|
+
|
|
38
40
|
</div>
|
|
39
41
|
</template>
|
|
40
42
|
|
|
@@ -45,11 +47,12 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
|
45
47
|
import ResourceForm from '@/components/ResourceForm.vue';
|
|
46
48
|
import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
|
|
47
49
|
import { useCoreStore } from '@/stores/core';
|
|
48
|
-
import { callAdminForthApi, getCustomComponent } from '@/utils';
|
|
50
|
+
import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions } from '@/utils';
|
|
49
51
|
import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
|
|
50
52
|
import { onMounted, ref } from 'vue';
|
|
51
53
|
import { useRoute, useRouter } from 'vue-router';
|
|
52
54
|
|
|
55
|
+
|
|
53
56
|
const isValid = ref(false);
|
|
54
57
|
const validating = ref(false);
|
|
55
58
|
|
|
@@ -81,6 +84,7 @@ onMounted(async () => {
|
|
|
81
84
|
return acc;
|
|
82
85
|
}, {});
|
|
83
86
|
loading.value = false;
|
|
87
|
+
checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'create');
|
|
84
88
|
});
|
|
85
89
|
|
|
86
90
|
async function saveRecord() {
|
|
@@ -41,7 +41,7 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
|
41
41
|
import ResourceForm from '@/components/ResourceForm.vue';
|
|
42
42
|
import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
|
|
43
43
|
import { useCoreStore } from '@/stores/core';
|
|
44
|
-
import { callAdminForthApi, getCustomComponent } from '@/utils';
|
|
44
|
+
import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions } from '@/utils';
|
|
45
45
|
import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
|
|
46
46
|
import { computed, onMounted, ref } from 'vue';
|
|
47
47
|
import { useRoute, useRouter } from 'vue-router';
|
|
@@ -79,6 +79,7 @@ const editableRecord = computed(() => {
|
|
|
79
79
|
})
|
|
80
80
|
|
|
81
81
|
onMounted(async () => {
|
|
82
|
+
|
|
82
83
|
loading.value = true;
|
|
83
84
|
|
|
84
85
|
await coreStore.fetchResourceFull({
|
|
@@ -88,6 +89,7 @@ onMounted(async () => {
|
|
|
88
89
|
resourceId: route.params.resourceId,
|
|
89
90
|
primaryKey: route.params.primaryKey,
|
|
90
91
|
});
|
|
92
|
+
checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'edit');
|
|
91
93
|
|
|
92
94
|
editComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
|
|
93
95
|
if (column.component?.edit) {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
</BreadcrumbsWithButtons>
|
|
66
66
|
|
|
67
67
|
<!-- table -->
|
|
68
|
-
<div class="relative overflow-x-auto shadow-list-table-shadow dark:shadow-black
|
|
68
|
+
<div class="relative overflow-x-auto shadow-list-table-shadow dark:shadow-black overflow-y-hidden rounded-default">
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
<!-- skelet loader -->
|
|
@@ -208,6 +208,7 @@
|
|
|
208
208
|
<td class=" items-center px-6 py-4">
|
|
209
209
|
<div class="flex">
|
|
210
210
|
<RouterLink
|
|
211
|
+
v-if="allowedActions.show"
|
|
211
212
|
:to="{ name: 'resource-show', params: { resourceId: $route.params.resourceId, primaryKey: row._primaryKeyValue } }"
|
|
212
213
|
class="font-medium text-blue-600 dark:text-blue-500 hover:underline"
|
|
213
214
|
:data-tooltip-target="`tooltip-show-${rowI}`"
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
Edit
|
|
9
9
|
</RouterLink>
|
|
10
10
|
|
|
11
|
-
<button v-if="coreStore?.resourceOptions?.allowedActions?.delete" @click="
|
|
11
|
+
<button v-if="coreStore?.resourceOptions?.allowedActions?.delete" @click="showDeleteModal"
|
|
12
12
|
class="flex items-center py-1 px-3 mb-2 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-red-500 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
|
13
13
|
>
|
|
14
14
|
<IconTrashBinSolid class="w-4 h-4" />
|
|
@@ -83,20 +83,26 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
|
83
83
|
|
|
84
84
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
85
85
|
import { useCoreStore } from '@/stores/core';
|
|
86
|
-
import {
|
|
86
|
+
import { useModalStore } from '@/stores/modal';
|
|
87
|
+
import { getCustomComponent,checkAcessByAllowedActions } from '@/utils';
|
|
87
88
|
import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
|
|
88
89
|
import { onMounted, ref } from 'vue';
|
|
89
|
-
import { useRoute } from 'vue-router';
|
|
90
|
+
import { useRoute,useRouter } from 'vue-router';
|
|
91
|
+
import {callAdminForthApi} from '@/utils';
|
|
90
92
|
|
|
91
93
|
|
|
92
94
|
const item = ref(null);
|
|
93
95
|
const route = useRoute();
|
|
96
|
+
const router = useRouter();
|
|
94
97
|
const loading = ref(false);
|
|
95
98
|
let showComponentsPerColumn = {};
|
|
96
99
|
let showRowComponentsPerColumn = {};
|
|
97
100
|
|
|
101
|
+
console.log(route.params,'showWiev');
|
|
102
|
+
|
|
98
103
|
|
|
99
104
|
const coreStore = useCoreStore();
|
|
105
|
+
const modalStore = useModalStore();
|
|
100
106
|
|
|
101
107
|
onMounted(async () => {
|
|
102
108
|
loading.value = true;
|
|
@@ -107,6 +113,8 @@ onMounted(async () => {
|
|
|
107
113
|
resourceId: route.params.resourceId,
|
|
108
114
|
primaryKey: route.params.primaryKey,
|
|
109
115
|
});
|
|
116
|
+
checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'show');
|
|
117
|
+
|
|
110
118
|
showComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
|
|
111
119
|
if (column.component?.show) {
|
|
112
120
|
acc[column.name] = getCustomComponent(column.component.show);
|
|
@@ -123,4 +131,40 @@ onMounted(async () => {
|
|
|
123
131
|
loading.value = false;
|
|
124
132
|
});
|
|
125
133
|
|
|
134
|
+
function showDeleteModal(row) {
|
|
135
|
+
if (!coreStore.config?.deleteConfirmation) {
|
|
136
|
+
return deleteRecord(row);
|
|
137
|
+
}
|
|
138
|
+
modalStore.setModalContent({
|
|
139
|
+
content: 'Are you sure you want to delete this item?',
|
|
140
|
+
acceptText: 'Delete',
|
|
141
|
+
cancelText: 'Cancel',
|
|
142
|
+
});
|
|
143
|
+
modalStore.setOnAcceptFunction(() => {
|
|
144
|
+
return deleteRecord(row)
|
|
145
|
+
})
|
|
146
|
+
modalStore.togleModal();
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function deleteRecord(row) {
|
|
152
|
+
try{
|
|
153
|
+
await callAdminForthApi({
|
|
154
|
+
path: '/delete_record',
|
|
155
|
+
method: 'POST',
|
|
156
|
+
body: {
|
|
157
|
+
resourceId: route.params.resourceId,
|
|
158
|
+
primaryKey: route.params.primaryKey,
|
|
159
|
+
recordId: route.params.primaryKey
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
router.push({ name: 'resource-list', params: { resourceId: route.params.resourceId } });
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.log(error);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
modalStore.resetmodalState()
|
|
168
|
+
}
|
|
169
|
+
|
|
126
170
|
</script>
|
|
@@ -4,7 +4,7 @@ import type * as Preset from '@docusaurus/preset-classic';
|
|
|
4
4
|
|
|
5
5
|
const config: Config = {
|
|
6
6
|
title: 'AdminForth',
|
|
7
|
-
tagline: '
|
|
7
|
+
tagline: 'OpenSource Dashboard on Tailwind UI extendable with Vue3 ',
|
|
8
8
|
favicon: 'img/favicon.png',
|
|
9
9
|
|
|
10
10
|
// Set the production url of your site here
|
|
@@ -16,7 +16,7 @@ const config: Config = {
|
|
|
16
16
|
// GitHub pages deployment config.
|
|
17
17
|
// If you aren't using GitHub pages, you don't need these.
|
|
18
18
|
organizationName: 'devforth', // Usually your GitHub org/user name.
|
|
19
|
-
projectName: '
|
|
19
|
+
projectName: 'devforth.github.io', // Usually your repo name.
|
|
20
20
|
|
|
21
21
|
onBrokenLinks: 'throw',
|
|
22
22
|
onBrokenMarkdownLinks: 'warn',
|
|
@@ -58,7 +58,7 @@ const config: Config = {
|
|
|
58
58
|
[
|
|
59
59
|
"docusaurus-plugin-typedoc",
|
|
60
60
|
{
|
|
61
|
-
entryPoints: "../
|
|
61
|
+
entryPoints: ["../types/AdminForthConfig.ts"],
|
|
62
62
|
plugin: ["./typedoc-plugin.mjs"],
|
|
63
63
|
readme: "none",
|
|
64
64
|
indexFormat: "table",
|
|
@@ -96,7 +96,7 @@ const config: Config = {
|
|
|
96
96
|
position: 'left',
|
|
97
97
|
label: 'API',
|
|
98
98
|
},
|
|
99
|
-
{to: '/blog', label: 'Blog', position: 'left'},
|
|
99
|
+
// {to: '/blog', label: 'Blog', position: 'left'},
|
|
100
100
|
{
|
|
101
101
|
href: 'https://github.com/devforth/adminforth',
|
|
102
102
|
label: 'GitHub',
|
|
@@ -112,45 +112,57 @@ const config: Config = {
|
|
|
112
112
|
items: [
|
|
113
113
|
{
|
|
114
114
|
label: 'Tutorial',
|
|
115
|
-
to: '/docs/
|
|
116
|
-
},
|
|
117
|
-
],
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
title: 'Community',
|
|
121
|
-
items: [
|
|
122
|
-
{
|
|
123
|
-
label: 'Stack Overflow',
|
|
124
|
-
href: 'https://stackoverflow.com/questions/tagged/docusaurus',
|
|
115
|
+
to: '/docs/Getting%20Started',
|
|
125
116
|
},
|
|
126
117
|
{
|
|
127
|
-
label: '
|
|
128
|
-
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
label: 'Twitter',
|
|
132
|
-
href: 'https://twitter.com/docusaurus',
|
|
118
|
+
label: 'API',
|
|
119
|
+
to: '/docs/api',
|
|
133
120
|
},
|
|
134
121
|
],
|
|
135
122
|
},
|
|
123
|
+
// {
|
|
124
|
+
// title: 'Community',
|
|
125
|
+
// items: [
|
|
126
|
+
// {
|
|
127
|
+
// label: 'Stack Overflow',
|
|
128
|
+
// href: 'https://stackoverflow.com/questions/tagged/docusaurus',
|
|
129
|
+
// },
|
|
130
|
+
// {
|
|
131
|
+
// label: 'Discord',
|
|
132
|
+
// href: 'https://discordapp.com/invite/docusaurus',
|
|
133
|
+
// },
|
|
134
|
+
// {
|
|
135
|
+
// label: 'Twitter',
|
|
136
|
+
// href: 'https://twitter.com/docusaurus',
|
|
137
|
+
// },
|
|
138
|
+
// ],
|
|
139
|
+
// },
|
|
136
140
|
{
|
|
137
141
|
title: 'More',
|
|
138
142
|
items: [
|
|
143
|
+
// {
|
|
144
|
+
// label: 'Blog',
|
|
145
|
+
// to: '/blog',
|
|
146
|
+
// },
|
|
147
|
+
{
|
|
148
|
+
label: 'DevForth.io',
|
|
149
|
+
href: 'https://devforth.io',
|
|
150
|
+
},
|
|
139
151
|
{
|
|
140
|
-
label: '
|
|
141
|
-
|
|
152
|
+
label: 'We can develop dashboard for you',
|
|
153
|
+
href: 'https://devforth.io/contact',
|
|
142
154
|
},
|
|
143
155
|
{
|
|
144
156
|
label: 'GitHub',
|
|
145
|
-
href: 'https://github.com/
|
|
157
|
+
href: 'https://github.com/devforth/adminforth',
|
|
146
158
|
},
|
|
147
159
|
],
|
|
148
160
|
},
|
|
149
161
|
],
|
|
150
|
-
copyright: `Copyright © ${new Date().getFullYear()}
|
|
162
|
+
copyright: `Copyright © ${new Date().getFullYear()} Devforth sp. z o.o.`,
|
|
151
163
|
},
|
|
152
164
|
prism: {
|
|
153
|
-
theme: prismThemes.
|
|
165
|
+
theme: prismThemes.vsLight,
|
|
154
166
|
darkTheme: prismThemes.dracula,
|
|
155
167
|
},
|
|
156
168
|
} satisfies Preset.ThemeConfig,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"start": "docusaurus start",
|
|
9
9
|
"build": "docusaurus build",
|
|
10
10
|
"swizzle": "docusaurus swizzle",
|
|
11
|
-
"deploy": "docusaurus deploy",
|
|
11
|
+
"deploy": "DEPLOYMENT_BRANCH=main docusaurus deploy",
|
|
12
12
|
"clear": "docusaurus clear",
|
|
13
13
|
"serve": "docusaurus serve",
|
|
14
14
|
"write-translations": "docusaurus write-translations",
|
|
@@ -4,48 +4,46 @@ import styles from './styles.module.css';
|
|
|
4
4
|
|
|
5
5
|
type FeatureItem = {
|
|
6
6
|
title: string;
|
|
7
|
-
|
|
7
|
+
img: string;
|
|
8
8
|
description: JSX.Element;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
const FeatureList: FeatureItem[] = [
|
|
12
12
|
{
|
|
13
|
-
title: '
|
|
14
|
-
|
|
13
|
+
title: 'CRUD Out of the Box',
|
|
14
|
+
img: require('@site/static/img/db2.png').default,
|
|
15
15
|
description: (
|
|
16
16
|
<>
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
Initialize AdminForth with your database URL and get a full-fledged admin panel to
|
|
18
|
+
List, Create, Update, Filter and Delete data with ease.
|
|
19
19
|
</>
|
|
20
20
|
),
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
|
-
title: '
|
|
24
|
-
|
|
23
|
+
title: 'Vue3 Driven',
|
|
24
|
+
img: require('@site/static/img/vue.png').default,
|
|
25
25
|
description: (
|
|
26
26
|
<>
|
|
27
|
-
|
|
28
|
-
ahead and move your docs into the <code>docs</code> directory.
|
|
27
|
+
Extend easily by creating own Vue3 components and pages
|
|
29
28
|
</>
|
|
30
29
|
),
|
|
31
30
|
},
|
|
32
31
|
{
|
|
33
|
-
title: '
|
|
34
|
-
|
|
32
|
+
title: 'Tailwind Look',
|
|
33
|
+
img: require('@site/static/img/css.png').default,
|
|
35
34
|
description: (
|
|
36
35
|
<>
|
|
37
|
-
|
|
38
|
-
be extended while reusing the same header and footer.
|
|
36
|
+
Look is based on TailwindCSS, with a themes customization and dark mode available
|
|
39
37
|
</>
|
|
40
38
|
),
|
|
41
39
|
},
|
|
42
40
|
];
|
|
43
41
|
|
|
44
|
-
function Feature({title,
|
|
42
|
+
function Feature({title, img, description}: FeatureItem) {
|
|
45
43
|
return (
|
|
46
44
|
<div className={clsx('col col--4')}>
|
|
47
45
|
<div className="text--center">
|
|
48
|
-
<
|
|
46
|
+
<img src={img} alt={title} style={{borderRadius: '50px', width: 250}} />
|
|
49
47
|
</div>
|
|
50
48
|
<div className="text--center padding-horiz--md">
|
|
51
49
|
<Heading as="h3">{title}</Heading>
|
|
@@ -19,8 +19,8 @@ function HomepageHeader() {
|
|
|
19
19
|
<div className={styles.buttons}>
|
|
20
20
|
<Link
|
|
21
21
|
className="button button--secondary button--lg"
|
|
22
|
-
to="/docs/
|
|
23
|
-
|
|
22
|
+
to="/docs/Getting%20Started">
|
|
23
|
+
AdminForth Tutorial - 5min ⏱️
|
|
24
24
|
</Link>
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
DO_N_TIME=10
|
|
2
|
+
|
|
3
|
+
for i in $(seq 1 $DO_N_TIME)
|
|
4
|
+
do
|
|
5
|
+
# curl https://api.openai.com/v1/images/generations \
|
|
6
|
+
# -H "Content-Type: application/json" \
|
|
7
|
+
# -H "Authorization: Bearer $OPENAI_TOKEN" \
|
|
8
|
+
# -d '{
|
|
9
|
+
# "model": "dall-e-3",
|
|
10
|
+
# "prompt": "A photorealistic image of a cute kitty lying on top of a database symbol. Kitty should have eyes open. The database should be represented as a cylindrical shape, and the kitty should look relaxed and content, possibly curled up or stretched out.",
|
|
11
|
+
# "n": 1,
|
|
12
|
+
# "size": "1024x1024"
|
|
13
|
+
# }'
|
|
14
|
+
|
|
15
|
+
curl https://api.openai.com/v1/images/generations \
|
|
16
|
+
-H "Content-Type: application/json" \
|
|
17
|
+
-H "Authorization: Bearer $OPENAI_TOKEN" \
|
|
18
|
+
-d '{
|
|
19
|
+
"model": "dall-e-3",
|
|
20
|
+
"prompt": "A photorealistic image of a cute kitty playing with nice rectangle box. Box is rounded and has CSS on it. The kitty should look playful and curious",
|
|
21
|
+
"n": 1,
|
|
22
|
+
"size": "1024x1024"
|
|
23
|
+
}'
|
|
24
|
+
done
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
package/index.ts
CHANGED
|
@@ -966,6 +966,12 @@ class AdminForth implements AdminForthClass {
|
|
|
966
966
|
if (!resource) {
|
|
967
967
|
return { error: `Resource '${body['resourceId']}' not found` };
|
|
968
968
|
}
|
|
969
|
+
if (!record){
|
|
970
|
+
return { error: `Record with ${body['primaryKey']} not found` };
|
|
971
|
+
}
|
|
972
|
+
if (resource.options.allowedActions.delete === false) {
|
|
973
|
+
return { error: `Resource '${resource.resourceId}' does not allow delete action` };
|
|
974
|
+
}
|
|
969
975
|
|
|
970
976
|
// execute hook if needed
|
|
971
977
|
for (const hook of getFunctionList(resource.hooks?.delete?.beforeSave)) {
|
package/modules/codeInjector.ts
CHANGED
|
@@ -9,6 +9,8 @@ import { promisify } from 'util';
|
|
|
9
9
|
import AdminForth from '../index.js';
|
|
10
10
|
import { ADMIN_FORTH_ABSOLUTE_PATH } from './utils.js';
|
|
11
11
|
import { getComponentNameFromPath } from './utils.js';
|
|
12
|
+
import { styles } from '../styles.js'
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
|
|
@@ -327,6 +329,11 @@ class CodeInjector {
|
|
|
327
329
|
let tailwindConfigContent = await fs.promises.readFile(tailwindConfigPath, 'utf-8');
|
|
328
330
|
tailwindConfigContent = tailwindConfigContent.replace('/* IMPORTANT:ADMINFORTH TAILWIND STYLES */', stylesText);
|
|
329
331
|
await fs.promises.writeFile(tailwindConfigPath, tailwindConfigContent);
|
|
332
|
+
} else {
|
|
333
|
+
let tailwindConfigPath = path.join(CodeInjector.SPA_TMP_PATH, 'tailwind.config.js');
|
|
334
|
+
let tailwindConfigContent = await fs.promises.readFile(tailwindConfigPath, 'utf-8');
|
|
335
|
+
tailwindConfigContent = tailwindConfigContent.replace('/* IMPORTANT:ADMINFORTH TAILWIND STYLES */',styles());
|
|
336
|
+
await fs.promises.writeFile(tailwindConfigPath, tailwindConfigContent);
|
|
330
337
|
}
|
|
331
338
|
|
|
332
339
|
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adminforth",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.83",
|
|
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
|
-
"rollout": "tsc && cp -r spa dist/spa && npm version patch && npm publish",
|
|
10
|
+
"rollout": "tsc && cp -r spa dist/spa && npm version patch && npm publish && cd documentation && npm run build && npm run deploy",
|
|
11
11
|
"docs": "typedoc"
|
|
12
12
|
},
|
|
13
13
|
"author": "devforth.io",
|
package/spa/src/App.vue
CHANGED
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
:class="{ '-translate-x-full': !sideBarOpen, 'transform-none': sideBarOpen }"
|
|
64
64
|
aria-label="Sidebar"
|
|
65
65
|
>
|
|
66
|
-
<div class="h-full px-3 pb-4 overflow-y-auto bg-nav-menu-bg dark:bg-gray-800">
|
|
66
|
+
<div class="h-full px-3 pb-4 overflow-y-auto bg-nav-menu-bg dark:bg-gray-800 border-r">
|
|
67
67
|
|
|
68
68
|
<div class="flex ms-2 md:me-24 m-4 ">
|
|
69
69
|
<img :src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')" :alt="`${ coreStore.config?.brandName } Logo`" class="h-8 me-3" />
|
|
@@ -121,9 +121,6 @@
|
|
|
121
121
|
<IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
|
|
122
122
|
<IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
|
|
123
123
|
</button>
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
124
|
<div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
|
|
128
125
|
|
|
129
126
|
<div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-gray-400 dark:text-gray-500">{{ column.editingNote[mode] }}</div>
|
|
@@ -197,7 +194,11 @@ const columnError = (column) => {
|
|
|
197
194
|
for (let i = 0; i < validationArray.length; i++) {
|
|
198
195
|
if (validationArray[i].regExp) {
|
|
199
196
|
const regExp = new RegExp(validationArray[i].regExp);
|
|
200
|
-
|
|
197
|
+
let value = currentValues.value[column.name];
|
|
198
|
+
if (value === undefined || value === null) {
|
|
199
|
+
value = '';
|
|
200
|
+
}
|
|
201
|
+
if (!regExp.test(value)) {
|
|
201
202
|
return validationArray[i].message;
|
|
202
203
|
}
|
|
203
204
|
}
|
package/spa/src/stores/core.ts
CHANGED
package/spa/src/utils.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { onMounted, ref, resolveComponent } from 'vue';
|
|
|
2
2
|
import type { CoreConfig } from './spa_types/core';
|
|
3
3
|
|
|
4
4
|
import router from "./router";
|
|
5
|
+
import { useRouter } from 'vue-router';
|
|
5
6
|
import { useCoreStore } from './stores/core';
|
|
6
7
|
|
|
7
8
|
export async function callApi({path, method, body=undefined} ) {
|
|
@@ -80,3 +81,14 @@ export function checkEmptyValues(value: any, viewType:'show' | 'list' ) {
|
|
|
80
81
|
}
|
|
81
82
|
return value;
|
|
82
83
|
}
|
|
84
|
+
|
|
85
|
+
export function checkAcessByAllowedActions(allowedActions:any, action:any ) {
|
|
86
|
+
if (!allowedActions) {
|
|
87
|
+
console.warn('allowedActions not set');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if(allowedActions[action] === false) {
|
|
91
|
+
console.warn(`Action ${action} is not allowed`);
|
|
92
|
+
router.back();
|
|
93
|
+
}
|
|
94
|
+
}
|