adminforth 1.0.80 → 1.0.82
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 +13 -0
- package/dist/modules/codeInjector.js +8 -6
- 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 +34 -26
- 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 +13 -0
- package/modules/codeInjector.ts +8 -6
- 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
|
@@ -76,6 +76,13 @@ class AdminForth {
|
|
|
76
76
|
if (!this.config.customization.customComponentsDir) {
|
|
77
77
|
this.config.customization.customComponentsDir = './custom';
|
|
78
78
|
}
|
|
79
|
+
try {
|
|
80
|
+
// check customComponentsDir exists
|
|
81
|
+
fs.accessSync(this.config.customization.customComponentsDir, fs.constants.R_OK);
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
this.config.customization.customComponentsDir = undefined;
|
|
85
|
+
}
|
|
79
86
|
if (this.config.auth) {
|
|
80
87
|
if (!this.config.auth.resourceId) {
|
|
81
88
|
throw new Error('No config.auth.resourceId defined');
|
|
@@ -852,6 +859,12 @@ class AdminForth {
|
|
|
852
859
|
if (!resource) {
|
|
853
860
|
return { error: `Resource '${body['resourceId']}' not found` };
|
|
854
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
|
+
}
|
|
855
868
|
// execute hook if needed
|
|
856
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)) {
|
|
857
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);
|
|
@@ -394,12 +401,7 @@ class CodeInjector {
|
|
|
394
401
|
watchCustomComponentsForCopy(_a) {
|
|
395
402
|
return __awaiter(this, arguments, void 0, function* ({ verbose }) {
|
|
396
403
|
const customComponentsDir = this.adminforth.config.customization.customComponentsDir;
|
|
397
|
-
|
|
398
|
-
try {
|
|
399
|
-
yield fs.promises.access(customComponentsDir, fs.constants.F_OK);
|
|
400
|
-
}
|
|
401
|
-
catch (e) {
|
|
402
|
-
console.log(`Custom components dir "${customComponentsDir}" does not exist, feel free to create it place custom assets and Vue files there`);
|
|
404
|
+
if (!customComponentsDir) {
|
|
403
405
|
return;
|
|
404
406
|
}
|
|
405
407
|
// check if folder exists
|
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,53 @@ const config: Config = {
|
|
|
112
112
|
items: [
|
|
113
113
|
{
|
|
114
114
|
label: 'Tutorial',
|
|
115
|
-
to: '/docs/
|
|
115
|
+
to: '/docs/Getting%20Started',
|
|
116
116
|
},
|
|
117
117
|
],
|
|
118
118
|
},
|
|
119
|
+
// {
|
|
120
|
+
// title: 'Community',
|
|
121
|
+
// items: [
|
|
122
|
+
// {
|
|
123
|
+
// label: 'Stack Overflow',
|
|
124
|
+
// href: 'https://stackoverflow.com/questions/tagged/docusaurus',
|
|
125
|
+
// },
|
|
126
|
+
// {
|
|
127
|
+
// label: 'Discord',
|
|
128
|
+
// href: 'https://discordapp.com/invite/docusaurus',
|
|
129
|
+
// },
|
|
130
|
+
// {
|
|
131
|
+
// label: 'Twitter',
|
|
132
|
+
// href: 'https://twitter.com/docusaurus',
|
|
133
|
+
// },
|
|
134
|
+
// ],
|
|
135
|
+
// },
|
|
119
136
|
{
|
|
120
|
-
title: '
|
|
137
|
+
title: 'More',
|
|
121
138
|
items: [
|
|
139
|
+
// {
|
|
140
|
+
// label: 'Blog',
|
|
141
|
+
// to: '/blog',
|
|
142
|
+
// },
|
|
122
143
|
{
|
|
123
|
-
label: '
|
|
124
|
-
href: 'https://
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
label: 'Discord',
|
|
128
|
-
href: 'https://discordapp.com/invite/docusaurus',
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
label: 'Twitter',
|
|
132
|
-
href: 'https://twitter.com/docusaurus',
|
|
144
|
+
label: 'DevForth.io',
|
|
145
|
+
href: 'https://devforth.io',
|
|
133
146
|
},
|
|
134
|
-
],
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
title: 'More',
|
|
138
|
-
items: [
|
|
139
147
|
{
|
|
140
|
-
label: '
|
|
141
|
-
|
|
148
|
+
label: 'We can develop dashboard for you',
|
|
149
|
+
href: 'https://devforth.io/contact',
|
|
142
150
|
},
|
|
143
151
|
{
|
|
144
152
|
label: 'GitHub',
|
|
145
|
-
href: 'https://github.com/
|
|
153
|
+
href: 'https://github.com/devforth/adminforth',
|
|
146
154
|
},
|
|
147
155
|
],
|
|
148
156
|
},
|
|
149
157
|
],
|
|
150
|
-
copyright: `Copyright © ${new Date().getFullYear()}
|
|
158
|
+
copyright: `Copyright © ${new Date().getFullYear()} Devforth sp. z o.o.`,
|
|
151
159
|
},
|
|
152
160
|
prism: {
|
|
153
|
-
theme: prismThemes.
|
|
161
|
+
theme: prismThemes.vsLight,
|
|
154
162
|
darkTheme: prismThemes.dracula,
|
|
155
163
|
},
|
|
156
164
|
} 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
|
@@ -98,6 +98,13 @@ class AdminForth implements AdminForthClass {
|
|
|
98
98
|
this.config.customization.customComponentsDir = './custom';
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
try {
|
|
102
|
+
// check customComponentsDir exists
|
|
103
|
+
fs.accessSync(this.config.customization.customComponentsDir, fs.constants.R_OK);
|
|
104
|
+
} catch (e) {
|
|
105
|
+
this.config.customization.customComponentsDir = undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
101
108
|
if (this.config.auth) {
|
|
102
109
|
if (!this.config.auth.resourceId) {
|
|
103
110
|
throw new Error('No config.auth.resourceId defined');
|
|
@@ -959,6 +966,12 @@ class AdminForth implements AdminForthClass {
|
|
|
959
966
|
if (!resource) {
|
|
960
967
|
return { error: `Resource '${body['resourceId']}' not found` };
|
|
961
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
|
+
}
|
|
962
975
|
|
|
963
976
|
// execute hook if needed
|
|
964
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
|
|
|
@@ -456,14 +463,9 @@ async watchForReprepare({ verbose }) {
|
|
|
456
463
|
|
|
457
464
|
async watchCustomComponentsForCopy({ verbose }) {
|
|
458
465
|
const customComponentsDir = this.adminforth.config.customization.customComponentsDir;
|
|
459
|
-
|
|
460
|
-
try {
|
|
461
|
-
await fs.promises.access(customComponentsDir, fs.constants.F_OK);
|
|
462
|
-
} catch (e) {
|
|
463
|
-
console.log(`Custom components dir "${customComponentsDir}" does not exist, feel free to create it place custom assets and Vue files there`);
|
|
466
|
+
if (!customComponentsDir) {
|
|
464
467
|
return;
|
|
465
468
|
}
|
|
466
|
-
|
|
467
469
|
// check if folder exists
|
|
468
470
|
try {
|
|
469
471
|
await fs.promises.access(customComponentsDir, fs.constants.F_OK);
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adminforth",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.82",
|
|
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 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" />
|