adminforth 1.0.51 → 1.0.53
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/postgres.ts +12 -1
- package/dist/dataConnectors/postgres.js +12 -1
- package/dist/index.js +2 -3
- package/dist/modules/codeInjector.js +18 -3
- package/dist/spa/spa/index.html +2 -2
- package/dist/spa/spa/package-lock.json +18 -0
- package/dist/spa/spa/package.json +1 -0
- package/dist/spa/spa/src/App.vue +53 -51
- package/dist/spa/spa/src/components/Dropdown.vue +2 -1
- package/dist/spa/spa/src/components/Filters.vue +1 -1
- package/dist/spa/spa/src/components/MenuLink.vue +2 -2
- package/dist/spa/spa/src/components/ResourceForm.vue +3 -3
- package/dist/spa/spa/src/views/ListView.vue +5 -5
- package/dist/spa/spa/src/views/ShowView.vue +3 -3
- package/dist/spa/spa/tailwind.config.js +5 -2
- package/index.ts +11 -5
- package/modules/codeInjector.ts +27 -10
- package/package.json +1 -1
- package/spa/index.html +2 -2
- package/spa/package-lock.json +18 -0
- package/spa/package.json +1 -0
- package/spa/src/App.vue +53 -51
- package/spa/src/components/Dropdown.vue +2 -1
- package/spa/src/components/Filters.vue +1 -1
- package/spa/src/components/MenuLink.vue +2 -2
- package/spa/src/components/ResourceForm.vue +3 -3
- package/spa/src/views/ListView.vue +5 -5
- package/spa/src/views/ShowView.vue +3 -3
- package/spa/tailwind.config.js +5 -2
- package/types/AdminForthConfig.ts +2 -0
|
@@ -83,6 +83,10 @@ class PostgresConnector {
|
|
|
83
83
|
field.type = AdminForthTypes.BOOLEAN;
|
|
84
84
|
field._underlineType = 'bool';
|
|
85
85
|
|
|
86
|
+
} else if (baseType == 'uuid') {
|
|
87
|
+
field.type = AdminForthTypes.STRING;
|
|
88
|
+
field._underlineType = 'uuid';
|
|
89
|
+
|
|
86
90
|
} else if (baseType.includes('character varying')) {
|
|
87
91
|
field.type = AdminForthTypes.STRING;
|
|
88
92
|
field._underlineType = 'varchar';
|
|
@@ -183,6 +187,7 @@ class PostgresConnector {
|
|
|
183
187
|
let totalCounter = 1;
|
|
184
188
|
const where = filters.length ? `WHERE ${filters.map((f, i) => {
|
|
185
189
|
let placeholder = '$'+(totalCounter);
|
|
190
|
+
const fieldData = resource.dataSourceColumns.find((col) => col.name == f.field);
|
|
186
191
|
let field = f.field;
|
|
187
192
|
let operator = this.OperatorsMap[f.operator];
|
|
188
193
|
if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
@@ -191,7 +196,13 @@ class PostgresConnector {
|
|
|
191
196
|
} else {
|
|
192
197
|
totalCounter += 1;
|
|
193
198
|
}
|
|
194
|
-
|
|
199
|
+
|
|
200
|
+
if (fieldData._underlineType == 'uuid') {
|
|
201
|
+
field = `cast("${field}" as text)`
|
|
202
|
+
} else {
|
|
203
|
+
field = `"${field}"`
|
|
204
|
+
}
|
|
205
|
+
return `${field} ${operator} ${placeholder}`;
|
|
195
206
|
}).join(' AND ')}` : '';
|
|
196
207
|
|
|
197
208
|
const filterValues = [];
|
|
@@ -84,6 +84,10 @@ class PostgresConnector {
|
|
|
84
84
|
field.type = AdminForthTypes.BOOLEAN;
|
|
85
85
|
field._underlineType = 'bool';
|
|
86
86
|
}
|
|
87
|
+
else if (baseType == 'uuid') {
|
|
88
|
+
field.type = AdminForthTypes.STRING;
|
|
89
|
+
field._underlineType = 'uuid';
|
|
90
|
+
}
|
|
87
91
|
else if (baseType.includes('character varying')) {
|
|
88
92
|
field.type = AdminForthTypes.STRING;
|
|
89
93
|
field._underlineType = 'varchar';
|
|
@@ -185,6 +189,7 @@ class PostgresConnector {
|
|
|
185
189
|
let totalCounter = 1;
|
|
186
190
|
const where = filters.length ? `WHERE ${filters.map((f, i) => {
|
|
187
191
|
let placeholder = '$' + (totalCounter);
|
|
192
|
+
const fieldData = resource.dataSourceColumns.find((col) => col.name == f.field);
|
|
188
193
|
let field = f.field;
|
|
189
194
|
let operator = this.OperatorsMap[f.operator];
|
|
190
195
|
if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
@@ -194,7 +199,13 @@ class PostgresConnector {
|
|
|
194
199
|
else {
|
|
195
200
|
totalCounter += 1;
|
|
196
201
|
}
|
|
197
|
-
|
|
202
|
+
if (fieldData._underlineType == 'uuid') {
|
|
203
|
+
field = `cast("${field}" as text)`;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
field = `"${field}"`;
|
|
207
|
+
}
|
|
208
|
+
return `${field} ${operator} ${placeholder}`;
|
|
198
209
|
}).join(' AND ')}` : '';
|
|
199
210
|
const filterValues = [];
|
|
200
211
|
filters.length ? filters.forEach((f) => {
|
package/dist/index.js
CHANGED
|
@@ -548,8 +548,8 @@ class AdminForth {
|
|
|
548
548
|
data.data.forEach((item) => {
|
|
549
549
|
item._label = resource.itemLabel(item);
|
|
550
550
|
});
|
|
551
|
-
if ((_p = (_o = resource.hooks) === null || _o === void 0 ? void 0 : _o[source]) === null || _p === void 0 ? void 0 : _p.
|
|
552
|
-
const resp = yield ((_r = (_q = resource.hooks) === null || _q === void 0 ? void 0 : _q[source]) === null || _r === void 0 ? void 0 : _r.
|
|
551
|
+
if ((_p = (_o = resource.hooks) === null || _o === void 0 ? void 0 : _o[source]) === null || _p === void 0 ? void 0 : _p.afterDatasourceResponse) {
|
|
552
|
+
const resp = yield ((_r = (_q = resource.hooks) === null || _q === void 0 ? void 0 : _q[source]) === null || _r === void 0 ? void 0 : _r.afterDatasourceResponse({ resource, response: data.data, adminUser }));
|
|
553
553
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
554
554
|
throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
|
|
555
555
|
}
|
|
@@ -565,7 +565,6 @@ class AdminForth {
|
|
|
565
565
|
}
|
|
566
566
|
});
|
|
567
567
|
});
|
|
568
|
-
console.log('data', data);
|
|
569
568
|
return Object.assign(Object.assign({}, data), { options: resource === null || resource === void 0 ? void 0 : resource.options });
|
|
570
569
|
}),
|
|
571
570
|
});
|
|
@@ -72,7 +72,7 @@ class CodeInjector {
|
|
|
72
72
|
}
|
|
73
73
|
prepareSources(_a) {
|
|
74
74
|
return __awaiter(this, arguments, void 0, function* ({ filesUpdated, verbose = false }) {
|
|
75
|
-
var _b, _c, _d, _e, _f, _g, _h;
|
|
75
|
+
var _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
76
76
|
// check SPA_TMP_PATH exists and create if not
|
|
77
77
|
try {
|
|
78
78
|
yield fs.promises.access(CodeInjector.SPA_TMP_PATH, fs.constants.F_OK);
|
|
@@ -162,6 +162,9 @@ class CodeInjector {
|
|
|
162
162
|
// if file - return, if dir - go recursively
|
|
163
163
|
const customComponents = yield fs.promises.readdir(customComponentsDir);
|
|
164
164
|
for (const filePath of customComponents) {
|
|
165
|
+
if (!filePath.endsWith('.vue')) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
165
168
|
fs.stat(`${customComponentsDir}/${filePath}`, (err, data) => {
|
|
166
169
|
if (err) {
|
|
167
170
|
console.log(err);
|
|
@@ -187,6 +190,9 @@ class CodeInjector {
|
|
|
187
190
|
if (customComponentsDir) {
|
|
188
191
|
const customComponents = yield fs.promises.readdir(customComponentsDir);
|
|
189
192
|
for (const filePath of customComponents) {
|
|
193
|
+
if (!filePath.endsWith('.vue')) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
190
196
|
fs.stat(`${customComponentsDir}/${filePath}`, (err, data) => {
|
|
191
197
|
if (err) {
|
|
192
198
|
console.log('Custom components importing error: ', err);
|
|
@@ -213,13 +219,22 @@ class CodeInjector {
|
|
|
213
219
|
appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH CUSTOM USES */', 'addCustomUses(app);');
|
|
214
220
|
}
|
|
215
221
|
yield fs.promises.writeFile(appVuePath, appVueContent);
|
|
222
|
+
// generate tailwind extend styles
|
|
223
|
+
if ((_f = this.adminforth.config) === null || _f === void 0 ? void 0 : _f.styles) {
|
|
224
|
+
const tailwindStyles = (_g = this.adminforth.config) === null || _g === void 0 ? void 0 : _g.styles;
|
|
225
|
+
const stylesText = JSON.stringify(tailwindStyles, null, 2).slice(1, -1);
|
|
226
|
+
let tailwindConfigPath = path.join(CodeInjector.SPA_TMP_PATH, 'tailwind.config.js');
|
|
227
|
+
let tailwindConfigContent = yield fs.promises.readFile(tailwindConfigPath, 'utf-8');
|
|
228
|
+
tailwindConfigContent = tailwindConfigContent.replace('/* IMPORTANT:ADMINFORTH TAILWIND STYLES */', stylesText);
|
|
229
|
+
yield fs.promises.writeFile(tailwindConfigPath, tailwindConfigContent);
|
|
230
|
+
}
|
|
216
231
|
/* generate custom rotes */
|
|
217
232
|
const homepageMenuItem = this.adminforth.config.menu.find((mi) => mi.homepage);
|
|
218
233
|
let childrenHomePageMenuItem = this.adminforth.config.menu.find((mi) => { var _a; return mi.children && ((_a = mi.children) === null || _a === void 0 ? void 0 : _a.find((mi) => mi.homepage)); });
|
|
219
|
-
let childrenHomepage = (
|
|
234
|
+
let childrenHomepage = (_h = childrenHomePageMenuItem === null || childrenHomePageMenuItem === void 0 ? void 0 : childrenHomePageMenuItem.children) === null || _h === void 0 ? void 0 : _h.find((mi) => mi.homepage);
|
|
220
235
|
let homePagePath = (homepageMenuItem === null || homepageMenuItem === void 0 ? void 0 : homepageMenuItem.path) || `/resource/${childrenHomepage === null || childrenHomepage === void 0 ? void 0 : childrenHomepage.resourceId}`;
|
|
221
236
|
if (!homePagePath) {
|
|
222
|
-
homePagePath = ((
|
|
237
|
+
homePagePath = ((_j = this.adminforth.config.menu.filter((mi) => mi.path)[0]) === null || _j === void 0 ? void 0 : _j.path) || `/resource/${(_k = this.adminforth.config.menu.filter((mi) => mi.children)[0]) === null || _k === void 0 ? void 0 : _k.resourceId}`;
|
|
223
238
|
}
|
|
224
239
|
routes += `{
|
|
225
240
|
path: '/',
|
package/dist/spa/spa/index.html
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
</script> -->
|
|
17
17
|
|
|
18
18
|
</head>
|
|
19
|
-
<body>
|
|
20
|
-
<div id="app" class="min-h-screen dark:bg-gray-800"></div>
|
|
19
|
+
<body class=" ">
|
|
20
|
+
<div id="app" class="min-h-screen bg-html-bg dark:bg-gray-800"></div>
|
|
21
21
|
<script type="module" src="/src/main.ts"></script>
|
|
22
22
|
</body>
|
|
23
23
|
</html>
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"version": "0.0.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@iconify-prerendered/vue-flowbite": "^0.23.1714023977",
|
|
12
|
+
"@unhead/vue": "^1.9.12",
|
|
12
13
|
"@vueuse/core": "^10.10.0",
|
|
13
14
|
"dayjs": "^1.11.11",
|
|
14
15
|
"debounce": "^2.1.0",
|
|
@@ -1185,6 +1186,23 @@
|
|
|
1185
1186
|
"url": "https://github.com/sponsors/harlan-zw"
|
|
1186
1187
|
}
|
|
1187
1188
|
},
|
|
1189
|
+
"node_modules/@unhead/vue": {
|
|
1190
|
+
"version": "1.9.12",
|
|
1191
|
+
"resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-1.9.12.tgz",
|
|
1192
|
+
"integrity": "sha512-keE4EuswgzCqVU7zmZprU+ToMvNWc3s8NoLreH5AtJd2u0FgBygD8sxRVyEnZw1KwFYOJ2C7yD2TChSKZioPGQ==",
|
|
1193
|
+
"dependencies": {
|
|
1194
|
+
"@unhead/schema": "1.9.12",
|
|
1195
|
+
"@unhead/shared": "1.9.12",
|
|
1196
|
+
"hookable": "^5.5.3",
|
|
1197
|
+
"unhead": "1.9.12"
|
|
1198
|
+
},
|
|
1199
|
+
"funding": {
|
|
1200
|
+
"url": "https://github.com/sponsors/harlan-zw"
|
|
1201
|
+
},
|
|
1202
|
+
"peerDependencies": {
|
|
1203
|
+
"vue": ">=2.7 || >=3"
|
|
1204
|
+
}
|
|
1205
|
+
},
|
|
1188
1206
|
"node_modules/@vitejs/plugin-vue": {
|
|
1189
1207
|
"version": "5.0.4",
|
|
1190
1208
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz",
|
package/dist/spa/spa/src/App.vue
CHANGED
|
@@ -1,89 +1,84 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<nav
|
|
3
3
|
v-if="loggedIn"
|
|
4
|
-
|
|
5
|
-
class="fixed h-14 top-0 z-50 w-full bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700">
|
|
4
|
+
class="fixed h-14 top-0 z-40 w-full border-b shadow-sm bg-header-bg shadow-header-shadow dark:bg-header-bg-hover dark:border-nav-menu-devider">
|
|
6
5
|
<div class="px-3 py-3 lg:px-5 lg:pl-3">
|
|
7
6
|
<div class="flex items-center justify-between">
|
|
8
7
|
<div class="flex items-center justify-start rtl:justify-end">
|
|
9
|
-
<button data-drawer-target="logo-sidebar" data-drawer-toggle="logo-sidebar" aria-controls="logo-sidebar" type="button" class="inline-flex items-center p-2 text-sm
|
|
8
|
+
<button data-drawer-target="logo-sidebar" data-drawer-toggle="logo-sidebar" aria-controls="logo-sidebar" type="button" class="inline-flex items-center p-2 text-sm rounded-lg sm:hidden hover:bg-nav-menu-bg-hover focus:outline-none focus:ring-2 focus:ring-nav-menu-devider dark:text-nav-menu-icons dark:hover:bg-nav-menu-bg-hover dark:focus:ring-nav-menu-devider">
|
|
10
9
|
<span class="sr-only">Open sidebar</span>
|
|
11
10
|
<svg class="w-6 h-6" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
|
12
11
|
<path clip-rule="evenodd" fill-rule="evenodd" d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"></path>
|
|
13
12
|
</svg>
|
|
14
13
|
</button>
|
|
15
|
-
|
|
16
|
-
<img src="https://flowbite.com/docs/images/logo.svg" class="h-8 me-3" alt="FlowBite Logo" />
|
|
17
|
-
<span class="self-center text-xl font-semibold sm:text-2xl whitespace-nowrap dark:text-white">
|
|
18
|
-
{{ coreStore.config?.brandName }}
|
|
19
|
-
</span>
|
|
20
|
-
</div>
|
|
14
|
+
|
|
21
15
|
</div>
|
|
22
16
|
<div class="flex items-center">
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
17
|
+
<div class="flex items-center ms-3 ">
|
|
18
|
+
<div>
|
|
19
|
+
<button type="button" class="flex text-sm bg-header-bg-hover rounded-full focus:ring-4 focus:ring-nav-menu-devider dark:focus:ring-nav-menu-devider dark:bg-header-bg-hover" aria-expanded="false" data-dropdown-toggle="dropdown-user">
|
|
20
|
+
<span class="sr-only">Open user menu</span>
|
|
21
|
+
<svg class="w-8 h-8 text-nav-menu-icons dark:text-header-icons" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
|
22
|
+
<path fill-rule="evenodd" d="M12 20a7.966 7.966 0 0 1-5.002-1.756l.002.001v-.683c0-1.794 1.492-3.25 3.333-3.25h3.334c1.84 0 3.333 1.456 3.333 3.25v.683A7.966 7.966 0 0 1 12 20ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10c0 5.5-4.44 9.963-9.932 10h-.138C6.438 21.962 2 17.5 2 12Zm10-5c-1.84 0-3.333 1.455-3.333 3.25S10.159 13.5 12 13.5c1.84 0 3.333-1.455 3.333-3.25S13.841 7 12 7Z" clip-rule="evenodd"/>
|
|
23
|
+
</svg>
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded shadow dark:shadow-black dark:bg-nav-menu-bg dark:divide-nav-menu-devider dark:shadow-black" id="dropdown-user">
|
|
27
|
+
<div class="px-4 py-3" role="none">
|
|
28
|
+
<p class="text-sm text-gray-900 dark:text-header-text" role="none" v-if="coreStore.userFullname">
|
|
29
|
+
{{ coreStore.userFullname }}
|
|
30
|
+
</p>
|
|
31
|
+
<p class="text-sm font-medium text-gray-900 truncate dark:text-nav-menu-text" role="none">
|
|
32
|
+
{{ coreStore.username }}
|
|
33
|
+
</p>
|
|
34
34
|
</div>
|
|
35
|
-
<
|
|
36
|
-
<
|
|
37
|
-
<
|
|
38
|
-
{{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
</
|
|
43
|
-
</div>
|
|
44
|
-
<ul class="py-1" role="none">
|
|
45
|
-
<li >
|
|
46
|
-
<span @click="toggleTheme" class=" cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white" role="menuitem">
|
|
47
|
-
{{ theme === 'dark' ? 'Light' : 'Dark' }}
|
|
48
|
-
<IconMoonSolid class="w-5 h-5 text-blue-300
|
|
49
|
-
" v-if="theme !== 'dark'"/>
|
|
50
|
-
<IconSunSolid class="w-5 h-5 text-yellow-300
|
|
51
|
-
" v-else/>
|
|
52
|
-
mode
|
|
53
|
-
</span>
|
|
54
|
-
</li>
|
|
55
|
-
|
|
56
|
-
<li>
|
|
57
|
-
<button @click="logout" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white" role="menuitem">Sign out</button>
|
|
35
|
+
<ul class="py-1" role="none">
|
|
36
|
+
<li>
|
|
37
|
+
<span @click="toggleTheme" class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black hover:bg-html-bg dark:text-nav-menu-text-hover dark:hover:bg-nav-menu-hover dark:hover:text-nav-menu-text-active" role="menuitem">
|
|
38
|
+
{{ theme === 'dark' ? 'Light' : 'Dark' }}
|
|
39
|
+
<IconMoonSolid class="w-5 h-5 text-blue-300" v-if="theme !== 'dark'" />
|
|
40
|
+
<IconSunSolid class="w-5 h-5 text-yellow-300" v-else />
|
|
41
|
+
mode
|
|
42
|
+
</span>
|
|
58
43
|
</li>
|
|
59
|
-
|
|
60
|
-
|
|
44
|
+
<li>
|
|
45
|
+
<button @click="logout" class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black hover:bg-html-bg dark:text-nav-menu-text-hover dark:hover:bg-nav-menu-hover dark:hover:text-nav-menu-text-active w-full" role="menuitem">Sign out</button>
|
|
46
|
+
</li>
|
|
47
|
+
</ul>
|
|
61
48
|
</div>
|
|
62
49
|
</div>
|
|
50
|
+
</div>
|
|
63
51
|
</div>
|
|
64
52
|
</div>
|
|
65
|
-
|
|
53
|
+
</nav>
|
|
54
|
+
|
|
66
55
|
|
|
67
56
|
|
|
68
57
|
<aside
|
|
69
58
|
v-if="loggedIn"
|
|
70
59
|
|
|
71
|
-
id="logo-sidebar" class="fixed top-0 left-0 z-
|
|
72
|
-
<div class="h-full px-3 pb-4 overflow-y-auto bg-
|
|
60
|
+
id="logo-sidebar" class="fixed border-none top-0 left-0 z-50 w-64 h-screen transition-transform -translate-x-full bg-nav-menu-bg border-r border- sm:translate-x-0 dark:bg-gray-800 dark:border-gray-700" aria-label="Sidebar">
|
|
61
|
+
<div class="h-full px-3 pb-4 overflow-y-auto bg-nav-menu-bg dark:bg-gray-800">
|
|
62
|
+
<div class="flex ms-2 md:me-24 m-4 ">
|
|
63
|
+
<img src="https://flowbite.com/docs/images/logo.svg" class="h-8 me-3" alt="FlowBite Logo" />
|
|
64
|
+
<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">
|
|
65
|
+
{{ coreStore.config?.brandName }}
|
|
66
|
+
</span>
|
|
67
|
+
</div>
|
|
73
68
|
<ul class="space-y-2 font-medium">
|
|
74
69
|
<template v-for="(item, i) in coreStore.menu" :key="`menu-${i}`">
|
|
75
|
-
<div v-if="item.type === 'divider'" class="border-t border-
|
|
70
|
+
<div v-if="item.type === 'divider'" class="border-t border-nav-menu-devider dark:border-gray-700"></div>
|
|
76
71
|
<div v-else-if="item.type === 'gap'" class="flex items-center justify-center h-8"></div>
|
|
77
72
|
<div v-else-if="item.type === 'heading'" class="flex items-center justify-left pl-2 h-8 text-gray-400 dark:text-gray-400
|
|
78
73
|
">{{ item.label }}</div>
|
|
79
74
|
|
|
80
75
|
|
|
81
76
|
<li v-else-if="item.children">
|
|
82
|
-
<button type="button" class="flex items-center w-full p-2 text-base text-
|
|
77
|
+
<button type="button" class="flex items-center w-full p-2 text-base text-nav-menu-text transition duration-75 group hover:bg-nav-menu-bg-hover dark:text-white dark:hover:bg-gray-700"
|
|
83
78
|
:aria-controls="`dropdown-example${i}`"
|
|
84
79
|
:data-collapse-toggle="`dropdown-example${i}`"
|
|
85
80
|
>
|
|
86
|
-
<component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400
|
|
81
|
+
<component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 dark:group-hover:text-white" ></component>
|
|
87
82
|
|
|
88
83
|
<span class="flex-1 ms-3 text-left rtl:text-right whitespace-nowrap">{{ item.label }}</span>
|
|
89
84
|
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
|
@@ -135,6 +130,7 @@ import { useRoute, useRouter } from 'vue-router';
|
|
|
135
130
|
import { getIcon } from '@/utils';
|
|
136
131
|
import { useHead } from 'unhead'
|
|
137
132
|
import { createHead } from 'unhead'
|
|
133
|
+
// import { link } from 'fs';
|
|
138
134
|
const coreStore = useCoreStore();
|
|
139
135
|
|
|
140
136
|
|
|
@@ -182,6 +178,12 @@ onMounted(async () => {
|
|
|
182
178
|
title.value = coreStore.config.title || 'Admin Forth';
|
|
183
179
|
useHead({
|
|
184
180
|
title: title.value,
|
|
181
|
+
link: [
|
|
182
|
+
{
|
|
183
|
+
rel: 'icon',
|
|
184
|
+
href: coreStore.config.favicon || '/favicon.ico',
|
|
185
|
+
},
|
|
186
|
+
],
|
|
185
187
|
|
|
186
188
|
})
|
|
187
189
|
})
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
:placeholder="selectedItems.length ? '' : 'Select...'"
|
|
10
10
|
/>
|
|
11
11
|
<div class="absolute inset-y-0 left-2 flex items-center pr-2 flex-wrap">
|
|
12
|
-
|
|
12
|
+
{{ }}
|
|
13
|
+
<div v-for="item in selectedItems" :key="item?.name" class="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-blue-900 dark:text-blue-300">
|
|
13
14
|
<span>{{ item.label }}</span>
|
|
14
15
|
<button
|
|
15
16
|
type="button"
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
</div>
|
|
93
93
|
</div>
|
|
94
94
|
|
|
95
|
-
<div v-if="show" drawer-backdrop="" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-
|
|
95
|
+
<div v-if="show" drawer-backdrop="" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-50"
|
|
96
96
|
@click="$emit('hide')">
|
|
97
97
|
</div>
|
|
98
98
|
</template>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<RouterLink
|
|
3
3
|
:to="{name: item.resourceId ? 'resource-list' : item.path, params: item.resourceId ? { resourceId: item.resourceId }: {}}"
|
|
4
|
-
class="flex items-center py-2 text-
|
|
4
|
+
class="flex items-center py-2 text-nav-menu-text hover:bg-nav-menu-bg-hover active:bg-nav-menu-bg-active" role="menuitem"
|
|
5
5
|
:class="{
|
|
6
6
|
'px-4': isChild,
|
|
7
7
|
'px-2': !isChild,
|
|
8
|
-
'bg-
|
|
8
|
+
'bg-nav-menu-bg-active dark:bg-gray-700': item.resourceId ?
|
|
9
9
|
($route.params.resourceId === item.resourceId && $route.name === 'resource-list') :
|
|
10
10
|
($route.name === item.path)
|
|
11
11
|
}"
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
<div>
|
|
3
3
|
|
|
4
4
|
<div
|
|
5
|
-
class="relative shadow-
|
|
5
|
+
class="relative shadow-resourse-form-shadow sm:rounded-lg dark:shadow-2xl dark:shadow-black"
|
|
6
6
|
>
|
|
7
7
|
<form autocomplete="off" @submit.prevent>
|
|
8
8
|
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
|
9
|
-
<thead class="text-xs text-gray-700 uppercase bg-
|
|
9
|
+
<thead class="text-xs text-gray-700 uppercase bg-form-view-heading dark:bg-gray-700 dark:text-gray-400">
|
|
10
10
|
<tr>
|
|
11
11
|
<th scope="col" class="px-6 py-3">
|
|
12
12
|
Field
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
</thead>
|
|
19
19
|
<tbody>
|
|
20
20
|
<tr v-for="column, i in editableColumns" :key="column.name"
|
|
21
|
-
class="
|
|
21
|
+
class="bg-form-view-bg dark:bg-gray-800 border-b dark:border-gray-700"
|
|
22
22
|
>
|
|
23
23
|
<!-- if column is in customComponentsPerColumn, use this component. If not, use this code -->
|
|
24
24
|
<template v-if="customComponentsPerColumn[column.name]">
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
</BreadcrumbsWithButtons>
|
|
66
66
|
|
|
67
67
|
<!-- table -->
|
|
68
|
-
<div class="relative overflow-x-auto shadow-
|
|
68
|
+
<div class="relative overflow-x-auto shadow-list-table-shadow dark:shadow-black dark:s">
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
<!-- skelet loader -->
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
</div>
|
|
91
91
|
|
|
92
92
|
<table v-else class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
|
93
|
-
<thead class="text-xs text-
|
|
93
|
+
<thead class="text-xs text-view-table-heading-text bg-list-view-table-heading dark:bg-gray-700 dark:text-gray-400">
|
|
94
94
|
<tr>
|
|
95
95
|
<th scope="col" class="p-4">
|
|
96
96
|
<div v-if="rows && rows.length" class="flex items-center">
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
</tr>
|
|
128
128
|
</thead>
|
|
129
129
|
<tbody>
|
|
130
|
-
<tr v-if="!rows" class="bg-
|
|
130
|
+
<tr v-if="!rows" class="bg-list-view-table-bg border-b dark:bg-gray-800 dark:border-gray-700">
|
|
131
131
|
<td :colspan="coreStore.resourceColumns.length + 2">
|
|
132
132
|
|
|
133
133
|
<div role="status"
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
</div>
|
|
172
172
|
</td>
|
|
173
173
|
</tr>
|
|
174
|
-
<tr v-else-if="rows.length === 0" class="bg-
|
|
174
|
+
<tr v-else-if="rows.length === 0" class="bg-list-view-table-bg border-b dark:bg-gray-800 dark:border-gray-700">
|
|
175
175
|
<td :colspan="coreStore.resourceColumns.length + 2">
|
|
176
176
|
|
|
177
177
|
<div id="toast-simple"
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
</tr>
|
|
186
186
|
|
|
187
187
|
<tr v-else v-for="(row, rowI) in rows" :key="row.id"
|
|
188
|
-
class="bg-
|
|
188
|
+
class="bg-list-view-table-bg border-b dark:bg-gray-800 border-list-view-border-color dark:border-gray-700 hover:bg-list-view-table-row-hover dark:hover:bg-gray-600">
|
|
189
189
|
<td class="w-4 p-4">
|
|
190
190
|
<div class="flex items center">
|
|
191
191
|
<input
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
</div>
|
|
28
28
|
<div
|
|
29
29
|
v-else
|
|
30
|
-
class="relative overflow-x-auto shadow-
|
|
30
|
+
class="relative overflow-x-auto shadow-resourse-form-shadow "
|
|
31
31
|
>
|
|
32
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-
|
|
33
|
+
<thead class="text-xs text-gray-700 uppercase bg-form-view-heading dark:bg-gray-700 dark:text-gray-400">
|
|
34
34
|
<tr>
|
|
35
35
|
<th scope="col" class="px-6 py-3">
|
|
36
36
|
Field
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
</thead>
|
|
43
43
|
<tbody>
|
|
44
44
|
<tr v-for="column in coreStore.resourceColumns?.filter(c => c.showIn.includes('show'))" :key="column.name"
|
|
45
|
-
class="
|
|
45
|
+
class="bg-form-view-bg odd:dark:bg-gray-900 even:dark:bg-gray-800 border-b dark:border-gray-700"
|
|
46
46
|
>
|
|
47
47
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
48
48
|
{{ column.label }}
|
|
@@ -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}","./src/*.{vue, js}", "./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/index.ts
CHANGED
|
@@ -16,6 +16,11 @@ const AVAILABLE_SHOW_IN = ['list', 'edit', 'create', 'filter', 'show'];
|
|
|
16
16
|
const DEFAULT_ALLOWED_ACTIONS = {create: true, edit: true, show: true, delete: true};
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
type ValidationObject = {
|
|
20
|
+
regex: string,
|
|
21
|
+
message: string,
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
class AdminForth {
|
|
20
25
|
static Types = AdminForthTypes;
|
|
21
26
|
|
|
@@ -624,9 +629,8 @@ class AdminForth {
|
|
|
624
629
|
item._label = resource.itemLabel(item);
|
|
625
630
|
})
|
|
626
631
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
const resp = await resource.hooks?.[source]?.afterDatasourceRequest({ resource, response: data.data, adminUser });
|
|
632
|
+
if (resource.hooks?.[source]?.afterDatasourceResponse) {
|
|
633
|
+
const resp = await resource.hooks?.[source]?.afterDatasourceResponse({ resource, response: data.data, adminUser });
|
|
630
634
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
631
635
|
throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
|
|
632
636
|
}
|
|
@@ -644,9 +648,11 @@ class AdminForth {
|
|
|
644
648
|
}
|
|
645
649
|
})
|
|
646
650
|
});
|
|
647
|
-
console.log('data', data);
|
|
648
651
|
|
|
649
|
-
return {
|
|
652
|
+
return {
|
|
653
|
+
...data,
|
|
654
|
+
options: resource?.options,
|
|
655
|
+
};
|
|
650
656
|
},
|
|
651
657
|
});
|
|
652
658
|
server.endpoint({
|
package/modules/codeInjector.ts
CHANGED
|
@@ -180,6 +180,9 @@ class CodeInjector {
|
|
|
180
180
|
// if file - return, if dir - go recursively
|
|
181
181
|
const customComponents = await fs.promises.readdir(customComponentsDir);
|
|
182
182
|
for (const filePath of customComponents) {
|
|
183
|
+
if (!filePath.endsWith('.vue')) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
183
186
|
fs.stat(`${customComponentsDir}/${filePath}`, (err, data) => {
|
|
184
187
|
if (err) {
|
|
185
188
|
console.log(err);
|
|
@@ -207,16 +210,19 @@ class CodeInjector {
|
|
|
207
210
|
if (customComponentsDir) {
|
|
208
211
|
const customComponents = await fs.promises.readdir(customComponentsDir);
|
|
209
212
|
for (const filePath of customComponents) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
213
|
+
if (!filePath.endsWith('.vue')) {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
fs.stat(`${customComponentsDir}/${filePath}`, (err, data) => {
|
|
217
|
+
if (err) {
|
|
218
|
+
console.log('Custom components importing error: ', err);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (data.isFile()) {
|
|
222
|
+
const componentName = getComponentNameFromPath(filePath);
|
|
223
|
+
customComponentsComponents += `app.component('${componentName}', ${componentName});\n`;
|
|
224
|
+
}
|
|
225
|
+
})
|
|
220
226
|
}
|
|
221
227
|
}
|
|
222
228
|
|
|
@@ -239,6 +245,17 @@ class CodeInjector {
|
|
|
239
245
|
}
|
|
240
246
|
await fs.promises.writeFile(appVuePath, appVueContent);
|
|
241
247
|
|
|
248
|
+
// generate tailwind extend styles
|
|
249
|
+
if(this.adminforth.config?.styles){
|
|
250
|
+
const tailwindStyles = this.adminforth.config?.styles
|
|
251
|
+
const stylesText = JSON.stringify(tailwindStyles, null, 2).slice(1, -1);
|
|
252
|
+
let tailwindConfigPath = path.join(CodeInjector.SPA_TMP_PATH, 'tailwind.config.js');
|
|
253
|
+
let tailwindConfigContent = await fs.promises.readFile(tailwindConfigPath, 'utf-8');
|
|
254
|
+
tailwindConfigContent = tailwindConfigContent.replace('/* IMPORTANT:ADMINFORTH TAILWIND STYLES */', stylesText);
|
|
255
|
+
await fs.promises.writeFile(tailwindConfigPath, tailwindConfigContent);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
242
259
|
|
|
243
260
|
/* generate custom rotes */
|
|
244
261
|
const homepageMenuItem = this.adminforth.config.menu.find((mi)=>mi.homepage);
|
package/package.json
CHANGED
package/spa/index.html
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
</script> -->
|
|
17
17
|
|
|
18
18
|
</head>
|
|
19
|
-
<body>
|
|
20
|
-
<div id="app" class="min-h-screen dark:bg-gray-800"></div>
|
|
19
|
+
<body class=" ">
|
|
20
|
+
<div id="app" class="min-h-screen bg-html-bg dark:bg-gray-800"></div>
|
|
21
21
|
<script type="module" src="/src/main.ts"></script>
|
|
22
22
|
</body>
|
|
23
23
|
</html>
|
package/spa/package-lock.json
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"version": "0.0.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@iconify-prerendered/vue-flowbite": "^0.23.1714023977",
|
|
12
|
+
"@unhead/vue": "^1.9.12",
|
|
12
13
|
"@vueuse/core": "^10.10.0",
|
|
13
14
|
"dayjs": "^1.11.11",
|
|
14
15
|
"debounce": "^2.1.0",
|
|
@@ -1185,6 +1186,23 @@
|
|
|
1185
1186
|
"url": "https://github.com/sponsors/harlan-zw"
|
|
1186
1187
|
}
|
|
1187
1188
|
},
|
|
1189
|
+
"node_modules/@unhead/vue": {
|
|
1190
|
+
"version": "1.9.12",
|
|
1191
|
+
"resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-1.9.12.tgz",
|
|
1192
|
+
"integrity": "sha512-keE4EuswgzCqVU7zmZprU+ToMvNWc3s8NoLreH5AtJd2u0FgBygD8sxRVyEnZw1KwFYOJ2C7yD2TChSKZioPGQ==",
|
|
1193
|
+
"dependencies": {
|
|
1194
|
+
"@unhead/schema": "1.9.12",
|
|
1195
|
+
"@unhead/shared": "1.9.12",
|
|
1196
|
+
"hookable": "^5.5.3",
|
|
1197
|
+
"unhead": "1.9.12"
|
|
1198
|
+
},
|
|
1199
|
+
"funding": {
|
|
1200
|
+
"url": "https://github.com/sponsors/harlan-zw"
|
|
1201
|
+
},
|
|
1202
|
+
"peerDependencies": {
|
|
1203
|
+
"vue": ">=2.7 || >=3"
|
|
1204
|
+
}
|
|
1205
|
+
},
|
|
1188
1206
|
"node_modules/@vitejs/plugin-vue": {
|
|
1189
1207
|
"version": "5.0.4",
|
|
1190
1208
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz",
|
package/spa/package.json
CHANGED
package/spa/src/App.vue
CHANGED
|
@@ -1,89 +1,84 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<nav
|
|
3
3
|
v-if="loggedIn"
|
|
4
|
-
|
|
5
|
-
class="fixed h-14 top-0 z-50 w-full bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700">
|
|
4
|
+
class="fixed h-14 top-0 z-40 w-full border-b shadow-sm bg-header-bg shadow-header-shadow dark:bg-header-bg-hover dark:border-nav-menu-devider">
|
|
6
5
|
<div class="px-3 py-3 lg:px-5 lg:pl-3">
|
|
7
6
|
<div class="flex items-center justify-between">
|
|
8
7
|
<div class="flex items-center justify-start rtl:justify-end">
|
|
9
|
-
<button data-drawer-target="logo-sidebar" data-drawer-toggle="logo-sidebar" aria-controls="logo-sidebar" type="button" class="inline-flex items-center p-2 text-sm
|
|
8
|
+
<button data-drawer-target="logo-sidebar" data-drawer-toggle="logo-sidebar" aria-controls="logo-sidebar" type="button" class="inline-flex items-center p-2 text-sm rounded-lg sm:hidden hover:bg-nav-menu-bg-hover focus:outline-none focus:ring-2 focus:ring-nav-menu-devider dark:text-nav-menu-icons dark:hover:bg-nav-menu-bg-hover dark:focus:ring-nav-menu-devider">
|
|
10
9
|
<span class="sr-only">Open sidebar</span>
|
|
11
10
|
<svg class="w-6 h-6" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
|
12
11
|
<path clip-rule="evenodd" fill-rule="evenodd" d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"></path>
|
|
13
12
|
</svg>
|
|
14
13
|
</button>
|
|
15
|
-
|
|
16
|
-
<img src="https://flowbite.com/docs/images/logo.svg" class="h-8 me-3" alt="FlowBite Logo" />
|
|
17
|
-
<span class="self-center text-xl font-semibold sm:text-2xl whitespace-nowrap dark:text-white">
|
|
18
|
-
{{ coreStore.config?.brandName }}
|
|
19
|
-
</span>
|
|
20
|
-
</div>
|
|
14
|
+
|
|
21
15
|
</div>
|
|
22
16
|
<div class="flex items-center">
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
17
|
+
<div class="flex items-center ms-3 ">
|
|
18
|
+
<div>
|
|
19
|
+
<button type="button" class="flex text-sm bg-header-bg-hover rounded-full focus:ring-4 focus:ring-nav-menu-devider dark:focus:ring-nav-menu-devider dark:bg-header-bg-hover" aria-expanded="false" data-dropdown-toggle="dropdown-user">
|
|
20
|
+
<span class="sr-only">Open user menu</span>
|
|
21
|
+
<svg class="w-8 h-8 text-nav-menu-icons dark:text-header-icons" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
|
22
|
+
<path fill-rule="evenodd" d="M12 20a7.966 7.966 0 0 1-5.002-1.756l.002.001v-.683c0-1.794 1.492-3.25 3.333-3.25h3.334c1.84 0 3.333 1.456 3.333 3.25v.683A7.966 7.966 0 0 1 12 20ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10c0 5.5-4.44 9.963-9.932 10h-.138C6.438 21.962 2 17.5 2 12Zm10-5c-1.84 0-3.333 1.455-3.333 3.25S10.159 13.5 12 13.5c1.84 0 3.333-1.455 3.333-3.25S13.841 7 12 7Z" clip-rule="evenodd"/>
|
|
23
|
+
</svg>
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded shadow dark:shadow-black dark:bg-nav-menu-bg dark:divide-nav-menu-devider dark:shadow-black" id="dropdown-user">
|
|
27
|
+
<div class="px-4 py-3" role="none">
|
|
28
|
+
<p class="text-sm text-gray-900 dark:text-header-text" role="none" v-if="coreStore.userFullname">
|
|
29
|
+
{{ coreStore.userFullname }}
|
|
30
|
+
</p>
|
|
31
|
+
<p class="text-sm font-medium text-gray-900 truncate dark:text-nav-menu-text" role="none">
|
|
32
|
+
{{ coreStore.username }}
|
|
33
|
+
</p>
|
|
34
34
|
</div>
|
|
35
|
-
<
|
|
36
|
-
<
|
|
37
|
-
<
|
|
38
|
-
{{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
</
|
|
43
|
-
</div>
|
|
44
|
-
<ul class="py-1" role="none">
|
|
45
|
-
<li >
|
|
46
|
-
<span @click="toggleTheme" class=" cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white" role="menuitem">
|
|
47
|
-
{{ theme === 'dark' ? 'Light' : 'Dark' }}
|
|
48
|
-
<IconMoonSolid class="w-5 h-5 text-blue-300
|
|
49
|
-
" v-if="theme !== 'dark'"/>
|
|
50
|
-
<IconSunSolid class="w-5 h-5 text-yellow-300
|
|
51
|
-
" v-else/>
|
|
52
|
-
mode
|
|
53
|
-
</span>
|
|
54
|
-
</li>
|
|
55
|
-
|
|
56
|
-
<li>
|
|
57
|
-
<button @click="logout" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white" role="menuitem">Sign out</button>
|
|
35
|
+
<ul class="py-1" role="none">
|
|
36
|
+
<li>
|
|
37
|
+
<span @click="toggleTheme" class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black hover:bg-html-bg dark:text-nav-menu-text-hover dark:hover:bg-nav-menu-hover dark:hover:text-nav-menu-text-active" role="menuitem">
|
|
38
|
+
{{ theme === 'dark' ? 'Light' : 'Dark' }}
|
|
39
|
+
<IconMoonSolid class="w-5 h-5 text-blue-300" v-if="theme !== 'dark'" />
|
|
40
|
+
<IconSunSolid class="w-5 h-5 text-yellow-300" v-else />
|
|
41
|
+
mode
|
|
42
|
+
</span>
|
|
58
43
|
</li>
|
|
59
|
-
|
|
60
|
-
|
|
44
|
+
<li>
|
|
45
|
+
<button @click="logout" class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black hover:bg-html-bg dark:text-nav-menu-text-hover dark:hover:bg-nav-menu-hover dark:hover:text-nav-menu-text-active w-full" role="menuitem">Sign out</button>
|
|
46
|
+
</li>
|
|
47
|
+
</ul>
|
|
61
48
|
</div>
|
|
62
49
|
</div>
|
|
50
|
+
</div>
|
|
63
51
|
</div>
|
|
64
52
|
</div>
|
|
65
|
-
|
|
53
|
+
</nav>
|
|
54
|
+
|
|
66
55
|
|
|
67
56
|
|
|
68
57
|
<aside
|
|
69
58
|
v-if="loggedIn"
|
|
70
59
|
|
|
71
|
-
id="logo-sidebar" class="fixed top-0 left-0 z-
|
|
72
|
-
<div class="h-full px-3 pb-4 overflow-y-auto bg-
|
|
60
|
+
id="logo-sidebar" class="fixed border-none top-0 left-0 z-50 w-64 h-screen transition-transform -translate-x-full bg-nav-menu-bg border-r border- sm:translate-x-0 dark:bg-gray-800 dark:border-gray-700" aria-label="Sidebar">
|
|
61
|
+
<div class="h-full px-3 pb-4 overflow-y-auto bg-nav-menu-bg dark:bg-gray-800">
|
|
62
|
+
<div class="flex ms-2 md:me-24 m-4 ">
|
|
63
|
+
<img src="https://flowbite.com/docs/images/logo.svg" class="h-8 me-3" alt="FlowBite Logo" />
|
|
64
|
+
<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">
|
|
65
|
+
{{ coreStore.config?.brandName }}
|
|
66
|
+
</span>
|
|
67
|
+
</div>
|
|
73
68
|
<ul class="space-y-2 font-medium">
|
|
74
69
|
<template v-for="(item, i) in coreStore.menu" :key="`menu-${i}`">
|
|
75
|
-
<div v-if="item.type === 'divider'" class="border-t border-
|
|
70
|
+
<div v-if="item.type === 'divider'" class="border-t border-nav-menu-devider dark:border-gray-700"></div>
|
|
76
71
|
<div v-else-if="item.type === 'gap'" class="flex items-center justify-center h-8"></div>
|
|
77
72
|
<div v-else-if="item.type === 'heading'" class="flex items-center justify-left pl-2 h-8 text-gray-400 dark:text-gray-400
|
|
78
73
|
">{{ item.label }}</div>
|
|
79
74
|
|
|
80
75
|
|
|
81
76
|
<li v-else-if="item.children">
|
|
82
|
-
<button type="button" class="flex items-center w-full p-2 text-base text-
|
|
77
|
+
<button type="button" class="flex items-center w-full p-2 text-base text-nav-menu-text transition duration-75 group hover:bg-nav-menu-bg-hover dark:text-white dark:hover:bg-gray-700"
|
|
83
78
|
:aria-controls="`dropdown-example${i}`"
|
|
84
79
|
:data-collapse-toggle="`dropdown-example${i}`"
|
|
85
80
|
>
|
|
86
|
-
<component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400
|
|
81
|
+
<component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 dark:group-hover:text-white" ></component>
|
|
87
82
|
|
|
88
83
|
<span class="flex-1 ms-3 text-left rtl:text-right whitespace-nowrap">{{ item.label }}</span>
|
|
89
84
|
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
|
@@ -135,6 +130,7 @@ import { useRoute, useRouter } from 'vue-router';
|
|
|
135
130
|
import { getIcon } from '@/utils';
|
|
136
131
|
import { useHead } from 'unhead'
|
|
137
132
|
import { createHead } from 'unhead'
|
|
133
|
+
// import { link } from 'fs';
|
|
138
134
|
const coreStore = useCoreStore();
|
|
139
135
|
|
|
140
136
|
|
|
@@ -182,6 +178,12 @@ onMounted(async () => {
|
|
|
182
178
|
title.value = coreStore.config.title || 'Admin Forth';
|
|
183
179
|
useHead({
|
|
184
180
|
title: title.value,
|
|
181
|
+
link: [
|
|
182
|
+
{
|
|
183
|
+
rel: 'icon',
|
|
184
|
+
href: coreStore.config.favicon || '/favicon.ico',
|
|
185
|
+
},
|
|
186
|
+
],
|
|
185
187
|
|
|
186
188
|
})
|
|
187
189
|
})
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
:placeholder="selectedItems.length ? '' : 'Select...'"
|
|
10
10
|
/>
|
|
11
11
|
<div class="absolute inset-y-0 left-2 flex items-center pr-2 flex-wrap">
|
|
12
|
-
|
|
12
|
+
{{ }}
|
|
13
|
+
<div v-for="item in selectedItems" :key="item?.name" class="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-blue-900 dark:text-blue-300">
|
|
13
14
|
<span>{{ item.label }}</span>
|
|
14
15
|
<button
|
|
15
16
|
type="button"
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
</div>
|
|
93
93
|
</div>
|
|
94
94
|
|
|
95
|
-
<div v-if="show" drawer-backdrop="" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-
|
|
95
|
+
<div v-if="show" drawer-backdrop="" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-50"
|
|
96
96
|
@click="$emit('hide')">
|
|
97
97
|
</div>
|
|
98
98
|
</template>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<RouterLink
|
|
3
3
|
:to="{name: item.resourceId ? 'resource-list' : item.path, params: item.resourceId ? { resourceId: item.resourceId }: {}}"
|
|
4
|
-
class="flex items-center py-2 text-
|
|
4
|
+
class="flex items-center py-2 text-nav-menu-text hover:bg-nav-menu-bg-hover active:bg-nav-menu-bg-active" role="menuitem"
|
|
5
5
|
:class="{
|
|
6
6
|
'px-4': isChild,
|
|
7
7
|
'px-2': !isChild,
|
|
8
|
-
'bg-
|
|
8
|
+
'bg-nav-menu-bg-active dark:bg-gray-700': item.resourceId ?
|
|
9
9
|
($route.params.resourceId === item.resourceId && $route.name === 'resource-list') :
|
|
10
10
|
($route.name === item.path)
|
|
11
11
|
}"
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
<div>
|
|
3
3
|
|
|
4
4
|
<div
|
|
5
|
-
class="relative shadow-
|
|
5
|
+
class="relative shadow-resourse-form-shadow sm:rounded-lg dark:shadow-2xl dark:shadow-black"
|
|
6
6
|
>
|
|
7
7
|
<form autocomplete="off" @submit.prevent>
|
|
8
8
|
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
|
9
|
-
<thead class="text-xs text-gray-700 uppercase bg-
|
|
9
|
+
<thead class="text-xs text-gray-700 uppercase bg-form-view-heading dark:bg-gray-700 dark:text-gray-400">
|
|
10
10
|
<tr>
|
|
11
11
|
<th scope="col" class="px-6 py-3">
|
|
12
12
|
Field
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
</thead>
|
|
19
19
|
<tbody>
|
|
20
20
|
<tr v-for="column, i in editableColumns" :key="column.name"
|
|
21
|
-
class="
|
|
21
|
+
class="bg-form-view-bg dark:bg-gray-800 border-b dark:border-gray-700"
|
|
22
22
|
>
|
|
23
23
|
<!-- if column is in customComponentsPerColumn, use this component. If not, use this code -->
|
|
24
24
|
<template v-if="customComponentsPerColumn[column.name]">
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
</BreadcrumbsWithButtons>
|
|
66
66
|
|
|
67
67
|
<!-- table -->
|
|
68
|
-
<div class="relative overflow-x-auto shadow-
|
|
68
|
+
<div class="relative overflow-x-auto shadow-list-table-shadow dark:shadow-black dark:s">
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
<!-- skelet loader -->
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
</div>
|
|
91
91
|
|
|
92
92
|
<table v-else class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
|
93
|
-
<thead class="text-xs text-
|
|
93
|
+
<thead class="text-xs text-view-table-heading-text bg-list-view-table-heading dark:bg-gray-700 dark:text-gray-400">
|
|
94
94
|
<tr>
|
|
95
95
|
<th scope="col" class="p-4">
|
|
96
96
|
<div v-if="rows && rows.length" class="flex items-center">
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
</tr>
|
|
128
128
|
</thead>
|
|
129
129
|
<tbody>
|
|
130
|
-
<tr v-if="!rows" class="bg-
|
|
130
|
+
<tr v-if="!rows" class="bg-list-view-table-bg border-b dark:bg-gray-800 dark:border-gray-700">
|
|
131
131
|
<td :colspan="coreStore.resourceColumns.length + 2">
|
|
132
132
|
|
|
133
133
|
<div role="status"
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
</div>
|
|
172
172
|
</td>
|
|
173
173
|
</tr>
|
|
174
|
-
<tr v-else-if="rows.length === 0" class="bg-
|
|
174
|
+
<tr v-else-if="rows.length === 0" class="bg-list-view-table-bg border-b dark:bg-gray-800 dark:border-gray-700">
|
|
175
175
|
<td :colspan="coreStore.resourceColumns.length + 2">
|
|
176
176
|
|
|
177
177
|
<div id="toast-simple"
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
</tr>
|
|
186
186
|
|
|
187
187
|
<tr v-else v-for="(row, rowI) in rows" :key="row.id"
|
|
188
|
-
class="bg-
|
|
188
|
+
class="bg-list-view-table-bg border-b dark:bg-gray-800 border-list-view-border-color dark:border-gray-700 hover:bg-list-view-table-row-hover dark:hover:bg-gray-600">
|
|
189
189
|
<td class="w-4 p-4">
|
|
190
190
|
<div class="flex items center">
|
|
191
191
|
<input
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
</div>
|
|
28
28
|
<div
|
|
29
29
|
v-else
|
|
30
|
-
class="relative overflow-x-auto shadow-
|
|
30
|
+
class="relative overflow-x-auto shadow-resourse-form-shadow "
|
|
31
31
|
>
|
|
32
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-
|
|
33
|
+
<thead class="text-xs text-gray-700 uppercase bg-form-view-heading dark:bg-gray-700 dark:text-gray-400">
|
|
34
34
|
<tr>
|
|
35
35
|
<th scope="col" class="px-6 py-3">
|
|
36
36
|
Field
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
</thead>
|
|
43
43
|
<tbody>
|
|
44
44
|
<tr v-for="column in coreStore.resourceColumns?.filter(c => c.showIn.includes('show'))" :key="column.name"
|
|
45
|
-
class="
|
|
45
|
+
class="bg-form-view-bg odd:dark:bg-gray-900 even:dark:bg-gray-800 border-b dark:border-gray-700"
|
|
46
46
|
>
|
|
47
47
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
48
48
|
{{ column.label }}
|
package/spa/tailwind.config.js
CHANGED
|
@@ -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}","./src/*.{vue, js}", "./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'),
|
|
@@ -104,12 +104,14 @@ export type AdminForthConfig = {
|
|
|
104
104
|
customization?: {
|
|
105
105
|
customComponentsDir?: string,
|
|
106
106
|
vueUsesFile?: string,
|
|
107
|
+
customPublicDir?: string,
|
|
107
108
|
},
|
|
108
109
|
baseUrl?: string,
|
|
109
110
|
brandName?: string,
|
|
110
111
|
datesFormat?: string,
|
|
111
112
|
deleteConfirmation?: boolean,
|
|
112
113
|
title?: string,
|
|
114
|
+
styles?: Object,
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
export type AllowedActions = {
|