adminforth 1.1.31 → 1.1.33
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 +9 -0
- package/dist/modules/codeInjector.js +19 -7
- package/dist/modules/styles.js +2 -2
- package/dist/spa/index.html +4 -4
- package/dist/spa/package-lock.json +206 -5
- package/dist/spa/package.json +8 -2
- package/dist/spa/src/App.vue +171 -72
- package/dist/spa/src/assets/logo.svg +19 -1
- package/dist/spa/src/components/AcceptModal.vue +3 -10
- package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
- package/dist/spa/src/components/CustomDatePicker.vue +19 -6
- 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 +91 -37
- package/dist/spa/src/components/MenuLink.vue +2 -2
- package/dist/spa/src/components/ResourceForm.vue +163 -103
- package/dist/spa/src/components/ValueRenderer.vue +22 -6
- package/dist/spa/src/main.ts +1 -1
- package/dist/spa/src/router/index.ts +30 -19
- package/dist/spa/src/stores/core.ts +65 -59
- package/dist/spa/src/stores/modal.ts +13 -3
- package/dist/spa/src/utils.ts +65 -8
- package/dist/spa/src/views/CreateView.vue +76 -16
- package/dist/spa/src/views/EditView.vue +76 -14
- package/dist/spa/src/views/ListView.vue +88 -360
- package/dist/spa/src/views/LoginView.vue +17 -5
- package/dist/spa/src/views/ResourceParent.vue +1 -1
- package/dist/spa/src/views/ShowView.vue +114 -19
- package/dist/spa/tailwind.config.js +5 -2
- package/dist/spa/vite.config.ts +6 -0
- package/index.ts +11 -0
- package/modules/codeInjector.ts +19 -1
- package/modules/styles.ts +2 -2
- package/package.json +1 -1
- package/spa/index.html +1 -1
- package/spa/src/App.vue +3 -2
- package/spa/src/components/ResourceForm.vue +1 -1
- package/types/AdminForthConfig.ts +15 -0
- package/dist/plugins/AccessControl/index.js +0 -13
- package/dist/plugins/AccessControl/types.js +0 -1
- package/dist/plugins/plugins/ForeignInlineListPlugin/custom/InlineList.vue +0 -248
- 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 -4359
- package/dist/spa/spa/package.json +0 -47
- 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 -270
- 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 -211
- 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 -265
- package/dist/spa/spa/src/components/ShowTableItem.vue +0 -14
- package/dist/spa/spa/src/components/SingleSkeletLoader.vue +0 -13
- package/dist/spa/spa/src/components/ValueRenderer.vue +0 -60
- 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 -26
- package/dist/spa/spa/src/main.ts +0 -18
- package/dist/spa/spa/src/router/index.ts +0 -64
- 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 -101
- package/dist/spa/spa/src/views/CreateView.vue +0 -163
- package/dist/spa/spa/src/views/EditView.vue +0 -165
- package/dist/spa/spa/src/views/HomeView.vue +0 -8
- package/dist/spa/spa/src/views/ListView.vue +0 -257
- package/dist/spa/spa/src/views/LoginView.vue +0 -134
- package/dist/spa/spa/src/views/ResourceParent.vue +0 -18
- package/dist/spa/spa/src/views/ShowView.vue +0 -188
- package/dist/spa/spa/tailwind.config.js +0 -15
- 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 -49
- 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/ResourceListTable.vue +0 -0
- /package/dist/spa/{spa/src → src}/components/Toast.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
- /package/spa/public/{favicon.png → assets/favicon.png} +0 -0
|
@@ -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>
|
|
@@ -27,10 +44,10 @@
|
|
|
27
44
|
</div>
|
|
28
45
|
<div
|
|
29
46
|
v-else
|
|
30
|
-
class="relative overflow-x-auto shadow-
|
|
47
|
+
class="relative overflow-x-auto shadow-resourse-form-shadow "
|
|
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-form-view-heading dark:bg-gray-700 dark:text-gray-400">
|
|
34
51
|
<tr>
|
|
35
52
|
<th scope="col" class="px-6 py-3">
|
|
36
53
|
Field
|
|
@@ -41,19 +58,49 @@
|
|
|
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-form-view-bg 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="showComponentsPerColumn[column.name]"
|
|
79
|
+
:is="showComponentsPerColumn[column.name]"
|
|
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
93
|
</table>
|
|
94
|
+
|
|
95
|
+
<component
|
|
96
|
+
v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.bottom || []"
|
|
97
|
+
:is="getCustomComponent(c)"
|
|
98
|
+
:meta="c.meta"
|
|
99
|
+
:column="column"
|
|
100
|
+
:record="coreStore.record"
|
|
101
|
+
:resource="coreStore.resource"
|
|
102
|
+
:adminUser="coreStore.adminUser"
|
|
103
|
+
/>
|
|
57
104
|
</div>
|
|
58
105
|
|
|
59
106
|
|
|
@@ -63,31 +110,79 @@
|
|
|
63
110
|
|
|
64
111
|
<script setup>
|
|
65
112
|
|
|
66
|
-
import { ref, computed, onMounted } from 'vue';
|
|
67
|
-
import { useRoute } from 'vue-router';
|
|
68
|
-
import { callAdminForthApi } from '@/utils';
|
|
69
113
|
import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
70
|
-
|
|
71
|
-
import { useCoreStore } from '@/stores/core';
|
|
114
|
+
|
|
72
115
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
116
|
+
import { useCoreStore } from '@/stores/core';
|
|
117
|
+
import { useModalStore } from '@/stores/modal';
|
|
118
|
+
import { getCustomComponent, checkAcessByAllowedActions } from '@/utils';
|
|
119
|
+
import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
|
|
120
|
+
import { onMounted, ref } from 'vue';
|
|
121
|
+
import { useRoute,useRouter } from 'vue-router';
|
|
122
|
+
import {callAdminForthApi} from '@/utils';
|
|
123
|
+
import {showSuccesTost} from '@/composables/useFrontendApi';
|
|
73
124
|
|
|
74
125
|
|
|
75
126
|
const item = ref(null);
|
|
76
127
|
const route = useRoute();
|
|
128
|
+
const router = useRouter();
|
|
77
129
|
const loading = ref(false);
|
|
130
|
+
let showComponentsPerColumn = {};
|
|
131
|
+
|
|
132
|
+
console.log(route.params,'showWiev');
|
|
133
|
+
|
|
78
134
|
|
|
79
135
|
const coreStore = useCoreStore();
|
|
136
|
+
const modalStore = useModalStore();
|
|
80
137
|
|
|
81
138
|
onMounted(async () => {
|
|
82
139
|
loading.value = true;
|
|
83
|
-
await coreStore.
|
|
140
|
+
await coreStore.fetchResourceFull({
|
|
84
141
|
resourceId: route.params.resourceId
|
|
85
142
|
});
|
|
86
143
|
await coreStore.fetchRecord({
|
|
87
144
|
resourceId: route.params.resourceId,
|
|
88
145
|
primaryKey: route.params.primaryKey,
|
|
89
146
|
});
|
|
147
|
+
checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'show');
|
|
148
|
+
|
|
149
|
+
showComponentsPerColumn = coreStore.resource.columns.reduce((acc, column) => {
|
|
150
|
+
if (column.components?.show) {
|
|
151
|
+
acc[column.name] = getCustomComponent(column.components.show);
|
|
152
|
+
}
|
|
153
|
+
return acc;
|
|
154
|
+
}, {});
|
|
90
155
|
loading.value = false;
|
|
91
156
|
});
|
|
92
157
|
|
|
158
|
+
async function deleteRecord(row) {
|
|
159
|
+
const data = await window.adminforth.confirm({
|
|
160
|
+
message: 'Are you sure you want to delete this item?',
|
|
161
|
+
yes: 'Delete',
|
|
162
|
+
no: 'Cancel',
|
|
163
|
+
});
|
|
164
|
+
if (data) {
|
|
165
|
+
try {
|
|
166
|
+
const res = await callAdminForthApi({
|
|
167
|
+
path: '/delete_record',
|
|
168
|
+
method: 'POST',
|
|
169
|
+
body: {
|
|
170
|
+
resourceId: route.params.resourceId,
|
|
171
|
+
primaryKey: route.params.primaryKey,
|
|
172
|
+
}});
|
|
173
|
+
if (!res.error){
|
|
174
|
+
router.push({ name: 'resource-list', params: { resourceId: route.params.resourceId } });
|
|
175
|
+
showSuccesTost('Record deleted successfully')
|
|
176
|
+
} else {
|
|
177
|
+
console.error(res.error)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
} catch (e) {
|
|
181
|
+
console.error(e);
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
|
|
93
188
|
</script>
|
|
@@ -1,9 +1,12 @@
|
|
|
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
12
|
require('flowbite/plugin'),
|
package/dist/spa/vite.config.ts
CHANGED
package/index.ts
CHANGED
|
@@ -49,6 +49,8 @@ class AdminForth implements AdminForthClass {
|
|
|
49
49
|
runningHotReload: boolean;
|
|
50
50
|
activatedPlugins: Array<AdminForthPlugin>;
|
|
51
51
|
|
|
52
|
+
baseUrlSlashed: string;
|
|
53
|
+
|
|
52
54
|
statuses: {
|
|
53
55
|
dbDiscover?: 'running' | 'done',
|
|
54
56
|
}
|
|
@@ -159,6 +161,11 @@ class AdminForth implements AdminForthClass {
|
|
|
159
161
|
if (!this.config.baseUrl) {
|
|
160
162
|
this.config.baseUrl = '';
|
|
161
163
|
}
|
|
164
|
+
if (!this.config.baseUrl.endsWith('/')) {
|
|
165
|
+
this.baseUrlSlashed = this.config.baseUrl + '/';
|
|
166
|
+
} else {
|
|
167
|
+
this.baseUrlSlashed = this.config.baseUrl;
|
|
168
|
+
}
|
|
162
169
|
if (!this.config?.customization.brandName) {
|
|
163
170
|
this.config.customization.brandName = 'AdminForth';
|
|
164
171
|
}
|
|
@@ -166,6 +173,10 @@ class AdminForth implements AdminForthClass {
|
|
|
166
173
|
errors.push(...this.checkCustomFileExists(this.config.customization.brandLogo));
|
|
167
174
|
}
|
|
168
175
|
|
|
176
|
+
if (!this.config.customization.favicon) {
|
|
177
|
+
errors.push(...this.checkCustomFileExists(this.config.customization.favicon));
|
|
178
|
+
}
|
|
179
|
+
|
|
169
180
|
if (!this.config.customization.datesFormat) {
|
|
170
181
|
this.config.customization.datesFormat = 'MMM D, YYYY HH:mm:ss';
|
|
171
182
|
}
|
package/modules/codeInjector.ts
CHANGED
|
@@ -209,6 +209,18 @@ class CodeInjector implements CodeInjectorType {
|
|
|
209
209
|
this.srcFoldersToSync[this.adminforth.config.customization.customComponentsDir] = './'
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
// if this.adminforth.config.customization.favicon is set, copy it to assets
|
|
213
|
+
const customFav = this.adminforth.config.customization?.favicon;
|
|
214
|
+
if (customFav) {
|
|
215
|
+
|
|
216
|
+
const faviconPath = path.join(this.adminforth.config.customization?.customComponentsDir, customFav.replace('@@/', ''));
|
|
217
|
+
const dest = path.join(CodeInjector.SPA_TMP_PATH, 'public', 'assets', customFav.replace('@@/', ''));
|
|
218
|
+
// make sure all folders in dest exist
|
|
219
|
+
await fsExtra.ensureDir(path.dirname(dest));
|
|
220
|
+
|
|
221
|
+
await fsExtra.copy(faviconPath, dest);
|
|
222
|
+
}
|
|
223
|
+
|
|
212
224
|
for (const [src, dest] of Object.entries(this.srcFoldersToSync)) {
|
|
213
225
|
const to = path.join(CodeInjector.SPA_TMP_PATH, 'src', 'custom', dest);
|
|
214
226
|
if (process.env.HEAVY_DEBUG) {
|
|
@@ -343,7 +355,6 @@ class CodeInjector implements CodeInjectorType {
|
|
|
343
355
|
await fs.promises.writeFile(tailwindConfigPath, tailwindConfigContent);
|
|
344
356
|
}
|
|
345
357
|
|
|
346
|
-
|
|
347
358
|
const routerVuePath = path.join(CodeInjector.SPA_TMP_PATH, 'src', 'router', 'index.ts');
|
|
348
359
|
|
|
349
360
|
let routerVueContent = await fs.promises.readFile(routerVuePath, 'utf-8');
|
|
@@ -353,6 +364,13 @@ class CodeInjector implements CodeInjectorType {
|
|
|
353
364
|
const indexHtmlPath = path.join(CodeInjector.SPA_TMP_PATH, 'index.html');
|
|
354
365
|
let indexHtmlContent = await fs.promises.readFile(indexHtmlPath, 'utf-8');
|
|
355
366
|
indexHtmlContent = indexHtmlContent.replace('/* IMPORTANT:ADMINFORTH TITLE */', `${this.adminforth.config.customization.title || 'AdminForth'}`);
|
|
367
|
+
|
|
368
|
+
indexHtmlContent = indexHtmlContent.replace(
|
|
369
|
+
'/* IMPORTANT:ADMINFORTH FAVICON */',
|
|
370
|
+
this.adminforth.config.customization.favicon?.replace('@@/', `${this.adminforth.baseUrlSlashed}assets/`)
|
|
371
|
+
||
|
|
372
|
+
`${this.adminforth.baseUrlSlashed}assets/favicon.png`
|
|
373
|
+
);
|
|
356
374
|
await fs.promises.writeFile(indexHtmlPath, indexHtmlContent);
|
|
357
375
|
|
|
358
376
|
|
package/modules/styles.ts
CHANGED
|
@@ -27,10 +27,10 @@ export const styles = () => `
|
|
|
27
27
|
|
|
28
28
|
"list-view-bg": "#FFFFFF", // White background
|
|
29
29
|
"list-view-table-bg": "#FFFFFF", // White table background
|
|
30
|
-
"list-view-table-heading": "
|
|
30
|
+
"list-view-table-heading": "rgb(249 250 251)", // Very light grey heading
|
|
31
31
|
"list-view-table-heading-text": "#333333", // Dark grey heading text
|
|
32
32
|
"list-view-table-text": "#333333", // Dark grey text
|
|
33
|
-
"list-view-table-row-hover": "
|
|
33
|
+
"list-view-table-row-hover": "rgb(249 250 251)", // Very light grey row hover
|
|
34
34
|
"list-view-bread-crumbs-text": "#666666", // Medium grey bread crumbs
|
|
35
35
|
"list-view-border-color": "#DDDDDD", // Light grey border
|
|
36
36
|
|
package/package.json
CHANGED
package/spa/index.html
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
|
-
<link rel="icon" href="
|
|
5
|
+
<link rel="icon" href="/* IMPORTANT:ADMINFORTH FAVICON */">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
7
|
<title>/* IMPORTANT:ADMINFORTH TITLE */</title>
|
|
8
8
|
<!--
|
package/spa/src/App.vue
CHANGED
|
@@ -59,17 +59,18 @@
|
|
|
59
59
|
|
|
60
60
|
v-if="loggedIn && routerIsReady && loginRedirectCheckIsReady"
|
|
61
61
|
|
|
62
|
-
id="logo-sidebar" class="fixed border-none top-0 left-0 z-20 w-64 h-screen transition-transform
|
|
62
|
+
id="logo-sidebar" class="fixed border-none top-0 left-0 z-20 w-64 h-screen transition-transform border-r border-nav-menu-border sm:translate-x-0 dark:bg-gray-800 dark:border-gray-700"
|
|
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-
|
|
66
|
+
<div class="h-full px-3 pb-4 overflow-y-auto bg-white dark:bg-gray-800 border-r">
|
|
67
67
|
<div class="flex ms-2 md:me-24 m-4 ">
|
|
68
68
|
<img :src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')" :alt="`${ coreStore.config?.brandName } Logo`" class="h-8 me-3" />
|
|
69
69
|
<span class="self-center text-header-text-size font-semibold sm:text-header-text-size whitespace-nowrap dark:text-header-text text-header-logo-color">
|
|
70
70
|
{{ coreStore.config?.brandName }}
|
|
71
71
|
</span>
|
|
72
72
|
</div>
|
|
73
|
+
|
|
73
74
|
<ul class="space-y-2 font-medium">
|
|
74
75
|
<template v-for="(item, i) in coreStore.menu" :key="`menu-${i}`">
|
|
75
76
|
<div v-if="item.type === 'divider'" class="border-t border-nav-menu-devider dark:border-gray-700"></div>
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
single
|
|
49
49
|
v-if="column.foreignResource"
|
|
50
50
|
:options="columnOptions[column.name] || []"
|
|
51
|
-
:placeholder = "columnOptions[column.name]
|
|
51
|
+
:placeholder = "columnOptions[column.name]?.length || 'There are no options available'"
|
|
52
52
|
:modelValue="currentValues[column.name]"
|
|
53
53
|
@update:modelValue="setCurrentValue(column.name, $event)"
|
|
54
54
|
></Dropdown>
|
|
@@ -742,6 +742,21 @@ export type AdminForthConfig = {
|
|
|
742
742
|
*/
|
|
743
743
|
brandLogo?: string,
|
|
744
744
|
|
|
745
|
+
/**
|
|
746
|
+
* Path to your app favicon
|
|
747
|
+
*
|
|
748
|
+
* Example:
|
|
749
|
+
* Place file `favicon.png` to `./custom` folder and set this option:
|
|
750
|
+
*
|
|
751
|
+
* ```ts
|
|
752
|
+
* favicon: '@@/favicon.png',
|
|
753
|
+
* ```
|
|
754
|
+
*/
|
|
755
|
+
favicon?: string,
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
favicon?: string,
|
|
759
|
+
|
|
745
760
|
/**
|
|
746
761
|
* DayJS format string for all dates in the app
|
|
747
762
|
*/
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import AdminForthPlugin from "../base.js";
|
|
2
|
-
class AuditLogPlugin extends AdminForthPlugin {
|
|
3
|
-
constructor(options) {
|
|
4
|
-
super(options, import.meta.url);
|
|
5
|
-
this.options = options;
|
|
6
|
-
}
|
|
7
|
-
modifyResourceConfig(adminforth, resourceConfig) {
|
|
8
|
-
super.modifyResourceConfig(adminforth, resourceConfig);
|
|
9
|
-
this.adminforth = adminforth;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
AuditLogPlugin.defaultError = 'Sorry, you do not have access to this resource.';
|
|
13
|
-
export default AuditLogPlugin;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<Teleport to="body">
|
|
3
|
-
<!-- todo exclude foreign column-->
|
|
4
|
-
<Filters
|
|
5
|
-
v-if="listResource"
|
|
6
|
-
:columns="listResource.columns.filter((c) => c.name !== listResourceRefColumn.name)"
|
|
7
|
-
v-model:filters="filters"
|
|
8
|
-
:columnsMinMax="columnsMinMax"
|
|
9
|
-
:show="filtersShow"
|
|
10
|
-
@hide="filtersShow = false"
|
|
11
|
-
/>
|
|
12
|
-
</Teleport>
|
|
13
|
-
|
|
14
|
-
<td colspan="2">
|
|
15
|
-
<div class="flex items-center gap-1">
|
|
16
|
-
<h4 v-if="listResource"
|
|
17
|
-
class="px-6 py-4"
|
|
18
|
-
>{{ listResource.label }} inline records</h4>
|
|
19
|
-
|
|
20
|
-
<button
|
|
21
|
-
@click="()=>{checkboxes = []}"
|
|
22
|
-
v-if="checkboxes.length"
|
|
23
|
-
data-tooltip-target="tooltip-remove-all"
|
|
24
|
-
data-tooltip-placement="bottom"
|
|
25
|
-
class="flex gap-1 items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
|
26
|
-
>
|
|
27
|
-
<IconBanOutline class="w-5 h-5 "/>
|
|
28
|
-
<div id="tooltip-remove-all" role="tooltip"
|
|
29
|
-
class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
|
|
30
|
-
Remove selection
|
|
31
|
-
<div class="tooltip-arrow" data-popper-arrow></div>
|
|
32
|
-
</div>
|
|
33
|
-
</button>
|
|
34
|
-
|
|
35
|
-
<button
|
|
36
|
-
v-if="checkboxes.length"
|
|
37
|
-
v-for="(action,i) in listResource?.options?.bulkActions"
|
|
38
|
-
:key="action.id"
|
|
39
|
-
@click="startBulkAction(action.id)"
|
|
40
|
-
class="flex gap-1 items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
|
41
|
-
:class="{'bg-red-100 text-red-800 border-red-400 dark:bg-red-700 dark:text-red-400 dark:border-red-400':action.state==='danger', 'bg-green-100 text-green-800 border-green-400 dark:bg-green-700 dark:text-green-400 dark:border-green-400':action.state==='success',
|
|
42
|
-
'bg-blue-100 text-blue-800 border-blue-400 dark:bg-blue-700 dark:text-blue-400 dark:border-blue-400':action.state==='active',
|
|
43
|
-
}"
|
|
44
|
-
>
|
|
45
|
-
<component
|
|
46
|
-
v-if="action.icon"
|
|
47
|
-
:is="getIcon(action.icon)"
|
|
48
|
-
class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white"></component>
|
|
49
|
-
|
|
50
|
-
{{ `${action.label} (${checkboxes.length})` }}
|
|
51
|
-
</button>
|
|
52
|
-
|
|
53
|
-
<RouterLink v-if="listResource?.options?.allowedActions?.create"
|
|
54
|
-
:to="{
|
|
55
|
-
name: 'resource-create',
|
|
56
|
-
params: { resourceId: listResource.resourceId },
|
|
57
|
-
query: {
|
|
58
|
-
values: encodeURIComponent(JSON.stringify({[listResourceRefColumn.name]: props.record[selfPrimaryKeyColumn.name]})),
|
|
59
|
-
returnTo: $route.fullPath,
|
|
60
|
-
},
|
|
61
|
-
}"
|
|
62
|
-
class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
|
|
63
|
-
>
|
|
64
|
-
<IconPlusOutline class="w-4 h-4 me-2"/>
|
|
65
|
-
Create
|
|
66
|
-
</RouterLink>
|
|
67
|
-
|
|
68
|
-
<button
|
|
69
|
-
class="flex gap-1 items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
|
|
70
|
-
@click="()=>{filtersShow = !filtersShow}"
|
|
71
|
-
>
|
|
72
|
-
<IconFilterOutline class="w-4 h-4 me-2"/>
|
|
73
|
-
Filter
|
|
74
|
-
<span
|
|
75
|
-
class="bg-red-100 text-red-800 text-xs font-medium px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400"
|
|
76
|
-
v-if="filters.length">
|
|
77
|
-
{{ filters.length }}
|
|
78
|
-
</span>
|
|
79
|
-
</button>
|
|
80
|
-
|
|
81
|
-
</div>
|
|
82
|
-
|
|
83
|
-
<ResourceListTable
|
|
84
|
-
:resource="listResource"
|
|
85
|
-
:rows="rows"
|
|
86
|
-
@update:page="page = $event"
|
|
87
|
-
@update:sort="sort = $event"
|
|
88
|
-
@update:checkboxes="checkboxes = $event"
|
|
89
|
-
:pageSize="pageSize"
|
|
90
|
-
:totalRows="totalRows"
|
|
91
|
-
:checkboxes="checkboxes"
|
|
92
|
-
/>
|
|
93
|
-
|
|
94
|
-
</td>
|
|
95
|
-
</template>
|
|
96
|
-
|
|
97
|
-
<script setup>
|
|
98
|
-
import { callAdminForthApi } from '@/utils';
|
|
99
|
-
import { ref, onMounted, watch, computed } from 'vue';
|
|
100
|
-
import ResourceListTable from '@/components/ResourceListTable.vue';
|
|
101
|
-
import Filters from '@/components/Filters.vue';
|
|
102
|
-
import {
|
|
103
|
-
IconBanOutline,
|
|
104
|
-
IconFilterOutline,
|
|
105
|
-
IconPlusOutline,
|
|
106
|
-
} from '@iconify-prerendered/vue-flowbite';
|
|
107
|
-
|
|
108
|
-
import { getIcon } from '@/utils';
|
|
109
|
-
|
|
110
|
-
const props = defineProps(['column', 'record', 'meta', 'resource', 'adminUser']);
|
|
111
|
-
|
|
112
|
-
const listResource = ref(null);
|
|
113
|
-
const loading = ref(true);
|
|
114
|
-
|
|
115
|
-
const page = ref(1);
|
|
116
|
-
const sort = ref([]);
|
|
117
|
-
const checkboxes = ref([]);
|
|
118
|
-
const pageSize = computed(() => listResource.value?.options?.listPageSize || 10);
|
|
119
|
-
|
|
120
|
-
const rows = ref(null);
|
|
121
|
-
const totalRows = ref(0);
|
|
122
|
-
|
|
123
|
-
const filters = ref([]);
|
|
124
|
-
const filtersShow = ref(false);
|
|
125
|
-
const columnsMinMax = ref(null);
|
|
126
|
-
|
|
127
|
-
const listResourceRefColumn = computed(() => {
|
|
128
|
-
if (!listResource.value) {
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
return listResource.value.columns.find(c => c.foreignResource?.resourceId === props.resource.resourceId);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
const selfPrimaryKeyColumn = computed(() => {
|
|
135
|
-
return props.resource.columns.find(c => c.primaryKey);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
const endFilters = computed(() => {
|
|
139
|
-
if (!listResource.value) {
|
|
140
|
-
return [];
|
|
141
|
-
}
|
|
142
|
-
// get name of the column that is foreign key
|
|
143
|
-
const refColumn = listResourceRefColumn.value;
|
|
144
|
-
|
|
145
|
-
const primaryKeyColumn = selfPrimaryKeyColumn.value;
|
|
146
|
-
|
|
147
|
-
if (!refColumn) {
|
|
148
|
-
window.adminforth.alert({
|
|
149
|
-
message: `Column with foreignResource.resourceId which is equal to '${props.resource.resourceId}' not found in resource which is specified as foreighResourceId '${listResource.value.resourceId}'`,
|
|
150
|
-
variant: 'danger',
|
|
151
|
-
});
|
|
152
|
-
return [];
|
|
153
|
-
}
|
|
154
|
-
return [
|
|
155
|
-
...filters.value,
|
|
156
|
-
{
|
|
157
|
-
field: refColumn.name,
|
|
158
|
-
operator: 'eq',
|
|
159
|
-
value: props.record[primaryKeyColumn.name],
|
|
160
|
-
},
|
|
161
|
-
];
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
watch([page], async () => {
|
|
165
|
-
await getList();
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
watch([sort], async () => {
|
|
169
|
-
await getList();
|
|
170
|
-
}, {deep: true});
|
|
171
|
-
|
|
172
|
-
watch([filters], async () => {
|
|
173
|
-
page.value = 1;
|
|
174
|
-
checkboxes.value = [];
|
|
175
|
-
await getList();
|
|
176
|
-
}, {deep: true});
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
async function startBulkAction(actionId) {
|
|
180
|
-
const data = await callAdminForthApi({
|
|
181
|
-
path: '/start_bulk_action',
|
|
182
|
-
method: 'POST',
|
|
183
|
-
body: {
|
|
184
|
-
resourceId: listResource.value.resourceId,
|
|
185
|
-
actionId: actionId,
|
|
186
|
-
recordIds: checkboxes.value
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
if (data?.status === 'success') {
|
|
190
|
-
checkboxes.value = [];
|
|
191
|
-
}
|
|
192
|
-
await getList();
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async function getList() {
|
|
196
|
-
rows.value = null;
|
|
197
|
-
console.log('getList', listResource.value)
|
|
198
|
-
const data = await callAdminForthApi({
|
|
199
|
-
path: '/get_resource_data',
|
|
200
|
-
method: 'POST',
|
|
201
|
-
body: {
|
|
202
|
-
source: 'list',
|
|
203
|
-
resourceId: listResource.value.resourceId,
|
|
204
|
-
limit: pageSize.value,
|
|
205
|
-
offset: (page.value - 1) * pageSize.value,
|
|
206
|
-
filters: endFilters.value,
|
|
207
|
-
sort: sort.value,
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
if (data.error) {
|
|
212
|
-
window.adminforth.alert({
|
|
213
|
-
message: data.error,
|
|
214
|
-
variant: 'danger',
|
|
215
|
-
timeout: 'unlimited',
|
|
216
|
-
});
|
|
217
|
-
rows.value = [];
|
|
218
|
-
totalRows.value = 0;
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
rows.value = data.data?.map(row => {
|
|
223
|
-
row._primaryKeyValue = row[listResource.value.columns.find(c => c.primaryKey).name];
|
|
224
|
-
return row;
|
|
225
|
-
});
|
|
226
|
-
totalRows.value = data.total;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
onMounted( async () => {
|
|
230
|
-
loading.value = true;
|
|
231
|
-
const foreighResourceId = props.meta.foreignResourceId;
|
|
232
|
-
listResource.value = (await callAdminForthApi({
|
|
233
|
-
path: `/plugin/${props.meta.pluginInstanceId}/get_resource`,
|
|
234
|
-
method: 'POST',
|
|
235
|
-
body: {},
|
|
236
|
-
})).resource;
|
|
237
|
-
columnsMinMax.value = await callAdminForthApi({
|
|
238
|
-
path: '/get_min_max_for_columns',
|
|
239
|
-
method: 'POST',
|
|
240
|
-
body: {
|
|
241
|
-
resourceId: foreighResourceId,
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
loading.value = false;
|
|
245
|
-
await getList();
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
</script>
|
|
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
|
-
}
|