adminforth 1.3.20 → 1.3.22
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/auth.ts +10 -7
- package/dist/auth.js +10 -8
- package/dist/modules/codeInjector.js +2 -2
- package/dist/modules/configValidator.js +8 -1
- package/dist/modules/restApi.js +3 -2
- package/index.ts +1 -1
- package/modules/codeInjector.ts +7 -1
- package/modules/configValidator.ts +11 -2
- package/modules/restApi.ts +4 -2
- package/package.json +1 -1
- package/spa/src/App.vue +5 -1
- package/spa/src/components/ResourceForm.vue +8 -11
- package/spa/src/components/ValueRenderer.vue +11 -1
- package/spa/src/composables/useFrontendApi.ts +1 -1
- package/spa/src/composables/useStores.ts +5 -1
- package/spa/src/stores/core.ts +1 -1
- package/spa/src/stores/toast.ts +22 -6
- package/spa/src/views/CreateView.vue +4 -0
- package/spa/src/views/ListView.vue +18 -1
- package/spa/src/views/LoginView.vue +15 -0
- package/types/AdminForthConfig.ts +21 -2
package/auth.ts
CHANGED
|
@@ -63,7 +63,7 @@ class AdminForthAuth {
|
|
|
63
63
|
return jwt.sign({...payload, t: type}, secret, { expiresIn });
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
async verify(jwtToken: string, mustHaveType: string): Promise<Object> {
|
|
66
|
+
async verify(jwtToken: string, mustHaveType: string, decodeUser: boolean | undefined = true): Promise<Object> {
|
|
67
67
|
// read ADMINFORH_SECRET from environment if not drop error
|
|
68
68
|
const secret = process.env.ADMINFORTH_SECRET;
|
|
69
69
|
if (!secret) {
|
|
@@ -88,13 +88,16 @@ class AdminForthAuth {
|
|
|
88
88
|
console.error(`Invalid token type during verification: ${t}, must be ${mustHaveType}`);
|
|
89
89
|
return null;
|
|
90
90
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
if (decodeUser !== false) {
|
|
92
|
+
const dbUser = await this.adminforth.getUserByPk(pk);
|
|
93
|
+
if (!dbUser) {
|
|
94
|
+
console.error(`User with pk ${pk} not found in database`);
|
|
95
|
+
// will logout user which was deleted
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
decoded.dbUser = dbUser;
|
|
96
99
|
}
|
|
97
|
-
|
|
100
|
+
|
|
98
101
|
return decoded;
|
|
99
102
|
}
|
|
100
103
|
|
package/dist/auth.js
CHANGED
|
@@ -51,8 +51,8 @@ class AdminForthAuth {
|
|
|
51
51
|
const expiresIn = process.env.ADMINFORTH_AUTH_EXPIRESIN || '24h';
|
|
52
52
|
return jwt.sign(Object.assign(Object.assign({}, payload), { t: type }), secret, { expiresIn });
|
|
53
53
|
}
|
|
54
|
-
verify(
|
|
55
|
-
return __awaiter(this,
|
|
54
|
+
verify(jwtToken_1, mustHaveType_1) {
|
|
55
|
+
return __awaiter(this, arguments, void 0, function* (jwtToken, mustHaveType, decodeUser = true) {
|
|
56
56
|
// read ADMINFORH_SECRET from environment if not drop error
|
|
57
57
|
const secret = process.env.ADMINFORTH_SECRET;
|
|
58
58
|
if (!secret) {
|
|
@@ -80,13 +80,15 @@ class AdminForthAuth {
|
|
|
80
80
|
console.error(`Invalid token type during verification: ${t}, must be ${mustHaveType}`);
|
|
81
81
|
return null;
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
if (decodeUser !== false) {
|
|
84
|
+
const dbUser = yield this.adminforth.getUserByPk(pk);
|
|
85
|
+
if (!dbUser) {
|
|
86
|
+
console.error(`User with pk ${pk} not found in database`);
|
|
87
|
+
// will logout user which was deleted
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
decoded.dbUser = dbUser;
|
|
88
91
|
}
|
|
89
|
-
decoded.dbUser = dbUser;
|
|
90
92
|
return decoded;
|
|
91
93
|
});
|
|
92
94
|
}
|
|
@@ -195,12 +195,12 @@ class CodeInjector {
|
|
|
195
195
|
const registerCustomPages = (config) => {
|
|
196
196
|
if (config.customization.customPages) {
|
|
197
197
|
config.customization.customPages.forEach((page) => {
|
|
198
|
-
var _a, _b, _c
|
|
198
|
+
var _a, _b, _c;
|
|
199
199
|
routes += `{
|
|
200
200
|
path: '${page.path}',
|
|
201
201
|
name: '${page.path}',
|
|
202
202
|
component: () => import('${((_a = page === null || page === void 0 ? void 0 : page.component) === null || _a === void 0 ? void 0 : _a.file) || page.component}'),
|
|
203
|
-
meta:
|
|
203
|
+
meta: ${JSON.stringify(Object.assign(Object.assign({}, (((_b = page === null || page === void 0 ? void 0 : page.component) === null || _b === void 0 ? void 0 : _b.meta) || {})), { title: ((_c = page.meta) === null || _c === void 0 ? void 0 : _c.title) || page.path.replace('/', '') }))}
|
|
204
204
|
},`;
|
|
205
205
|
});
|
|
206
206
|
}
|
|
@@ -119,6 +119,12 @@ export default class ConfigValidator {
|
|
|
119
119
|
if (((_a = this.config) === null || _a === void 0 ? void 0 : _a.customization.brandName) === undefined) {
|
|
120
120
|
this.config.customization.brandName = 'AdminForth';
|
|
121
121
|
}
|
|
122
|
+
if (this.config.customization.loginPageInjections === undefined) {
|
|
123
|
+
this.config.customization.loginPageInjections = {};
|
|
124
|
+
}
|
|
125
|
+
if (this.config.customization.loginPageInjections.underInputs === undefined) {
|
|
126
|
+
this.config.customization.loginPageInjections.underInputs = [];
|
|
127
|
+
}
|
|
122
128
|
if (this.config.customization.brandLogo) {
|
|
123
129
|
errors.push(...this.checkCustomFileExists(this.config.customization.brandLogo));
|
|
124
130
|
}
|
|
@@ -245,6 +251,7 @@ export default class ConfigValidator {
|
|
|
245
251
|
label: `Delete checked`,
|
|
246
252
|
state: 'danger',
|
|
247
253
|
icon: 'flowbite:trash-bin-outline',
|
|
254
|
+
confirm: 'Are you sure you want to delete selected items?',
|
|
248
255
|
allowed: (_b) => __awaiter(this, [_b], void 0, function* ({ resource, adminUser, allowedActions }) { return allowedActions.delete; }),
|
|
249
256
|
action: (_c) => __awaiter(this, [_c], void 0, function* ({ selectedIds, adminUser }) {
|
|
250
257
|
const connector = this.adminforth.connectors[res.dataSource];
|
|
@@ -280,7 +287,7 @@ export default class ConfigValidator {
|
|
|
280
287
|
if (error) {
|
|
281
288
|
return { error, ok: false };
|
|
282
289
|
}
|
|
283
|
-
return { ok: true };
|
|
290
|
+
return { ok: true, successMessage: `${selectedIds.length} item${selectedIds.length > 1 ? 's' : ''} deleted` };
|
|
284
291
|
})
|
|
285
292
|
});
|
|
286
293
|
}
|
package/dist/modules/restApi.js
CHANGED
|
@@ -43,7 +43,7 @@ export default class AdminForthRestAPI {
|
|
|
43
43
|
path: '/login',
|
|
44
44
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response }) {
|
|
45
45
|
var _b, _c, _d, _e;
|
|
46
|
-
const INVALID_MESSAGE = 'Invalid
|
|
46
|
+
const INVALID_MESSAGE = 'Invalid Username or Password';
|
|
47
47
|
const { username, password } = body;
|
|
48
48
|
let adminUser;
|
|
49
49
|
let toReturn = { ok: true, allowedLogin: true };
|
|
@@ -72,7 +72,7 @@ export default class AdminForthRestAPI {
|
|
|
72
72
|
sort: [],
|
|
73
73
|
})).data) === null || _b === void 0 ? void 0 : _b[0];
|
|
74
74
|
if (!userRecord) {
|
|
75
|
-
return { error:
|
|
75
|
+
return { error: INVALID_MESSAGE };
|
|
76
76
|
}
|
|
77
77
|
const passwordHash = userRecord[this.adminforth.config.auth.passwordHashField];
|
|
78
78
|
const valid = yield AdminForthAuth.verifyPassword(password, passwordHash);
|
|
@@ -139,6 +139,7 @@ export default class AdminForthRestAPI {
|
|
|
139
139
|
title: (_j = this.adminforth.config.customization) === null || _j === void 0 ? void 0 : _j.title,
|
|
140
140
|
demoCredentials: this.adminforth.config.auth.demoCredentials,
|
|
141
141
|
loginPromptHTML: this.adminforth.config.auth.loginPromptHTML,
|
|
142
|
+
loginPageInjections: this.adminforth.config.customization.loginPageInjections,
|
|
142
143
|
};
|
|
143
144
|
}),
|
|
144
145
|
});
|
package/index.ts
CHANGED
|
@@ -264,7 +264,7 @@ class AdminForth implements IAdminForth {
|
|
|
264
264
|
return { ok, error, createdRecord };
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
resource(resourceId: string) {
|
|
267
|
+
resource(resourceId: string): IOperationalResource {
|
|
268
268
|
if (this.statuses.dbDiscover !== 'done') {
|
|
269
269
|
if (this.statuses.dbDiscover === 'running') {
|
|
270
270
|
throw new Error('Database discovery is running. You can\'t use data API while database discovery is not finished.\n'+
|
package/modules/codeInjector.ts
CHANGED
|
@@ -10,6 +10,7 @@ import AdminForth from '../index.js';
|
|
|
10
10
|
import { ADMIN_FORTH_ABSOLUTE_PATH, getComponentNameFromPath, transformObject, deepMerge } from './utils.js';
|
|
11
11
|
import { ICodeInjector } from '../types/AdminForthConfig.js';
|
|
12
12
|
import { StylesGenerator } from './styleGenerator.js';
|
|
13
|
+
import { title } from 'process';
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
let TMP_DIR;
|
|
@@ -211,7 +212,12 @@ class CodeInjector implements ICodeInjector {
|
|
|
211
212
|
path: '${page.path}',
|
|
212
213
|
name: '${page.path}',
|
|
213
214
|
component: () => import('${page?.component?.file || page.component}'),
|
|
214
|
-
meta:
|
|
215
|
+
meta: ${
|
|
216
|
+
JSON.stringify({
|
|
217
|
+
...(page?.component?.meta || {}),
|
|
218
|
+
title: page.meta?.title || page.path.replace('/', '')
|
|
219
|
+
})
|
|
220
|
+
}
|
|
215
221
|
},`})
|
|
216
222
|
|
|
217
223
|
}}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
AdminForthResourcePages, AllowedActionsEnum,
|
|
7
7
|
type AdminForthComponentDeclarationFull,
|
|
8
8
|
type AfterSaveFunction,
|
|
9
|
+
AdminForthBulkAction,
|
|
9
10
|
} from "../types/AdminForthConfig.js";
|
|
10
11
|
|
|
11
12
|
import fs from 'fs';
|
|
@@ -127,6 +128,13 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
127
128
|
if (this.config?.customization.brandName === undefined) {
|
|
128
129
|
this.config.customization.brandName = 'AdminForth';
|
|
129
130
|
}
|
|
131
|
+
if (this.config.customization.loginPageInjections === undefined) {
|
|
132
|
+
this.config.customization.loginPageInjections = {};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (this.config.customization.loginPageInjections.underInputs === undefined) {
|
|
136
|
+
this.config.customization.loginPageInjections.underInputs = [];
|
|
137
|
+
}
|
|
130
138
|
if (this.config.customization.brandLogo) {
|
|
131
139
|
errors.push(...this.checkCustomFileExists(this.config.customization.brandLogo));
|
|
132
140
|
}
|
|
@@ -253,7 +261,7 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
253
261
|
|
|
254
262
|
|
|
255
263
|
//check if resource has bulkActions
|
|
256
|
-
let bulkActions = res?.options?.bulkActions || [];
|
|
264
|
+
let bulkActions: AdminForthBulkAction[] = res?.options?.bulkActions || [];
|
|
257
265
|
|
|
258
266
|
if (!Array.isArray(bulkActions)) {
|
|
259
267
|
errors.push(`Resource "${res.resourceId}" bulkActions must be an array`);
|
|
@@ -265,6 +273,7 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
265
273
|
label: `Delete checked`,
|
|
266
274
|
state: 'danger',
|
|
267
275
|
icon: 'flowbite:trash-bin-outline',
|
|
276
|
+
confirm: 'Are you sure you want to delete selected items?',
|
|
268
277
|
allowed: async ({ resource, adminUser, allowedActions }) => { return allowedActions.delete },
|
|
269
278
|
action: async ({ selectedIds, adminUser }) => {
|
|
270
279
|
const connector = this.adminforth.connectors[res.dataSource];
|
|
@@ -317,7 +326,7 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
317
326
|
if (error) {
|
|
318
327
|
return { error, ok: false };
|
|
319
328
|
}
|
|
320
|
-
return { ok: true };
|
|
329
|
+
return { ok: true, successMessage: `${selectedIds.length} item${selectedIds.length > 1 ? 's' : ''} deleted` };
|
|
321
330
|
}
|
|
322
331
|
});
|
|
323
332
|
}
|
package/modules/restApi.ts
CHANGED
|
@@ -62,7 +62,7 @@ export default class AdminForthRestAPI {
|
|
|
62
62
|
path: '/login',
|
|
63
63
|
handler: async ({ body, response }) => {
|
|
64
64
|
|
|
65
|
-
const INVALID_MESSAGE = 'Invalid
|
|
65
|
+
const INVALID_MESSAGE = 'Invalid Username or Password';
|
|
66
66
|
const { username, password } = body;
|
|
67
67
|
let adminUser: AdminUser;
|
|
68
68
|
let toReturn: { ok: boolean, redirectTo?: string, allowedLogin:boolean } = { ok: true, allowedLogin:true};
|
|
@@ -95,8 +95,9 @@ export default class AdminForthRestAPI {
|
|
|
95
95
|
})
|
|
96
96
|
).data?.[0];
|
|
97
97
|
|
|
98
|
+
|
|
98
99
|
if (!userRecord) {
|
|
99
|
-
return { error:
|
|
100
|
+
return { error: INVALID_MESSAGE };
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
const passwordHash = userRecord[this.adminforth.config.auth.passwordHashField];
|
|
@@ -170,6 +171,7 @@ export default class AdminForthRestAPI {
|
|
|
170
171
|
title: this.adminforth.config.customization?.title,
|
|
171
172
|
demoCredentials: this.adminforth.config.auth.demoCredentials,
|
|
172
173
|
loginPromptHTML: this.adminforth.config.auth.loginPromptHTML,
|
|
174
|
+
loginPageInjections: this.adminforth.config.customization.loginPageInjections,
|
|
173
175
|
};
|
|
174
176
|
},
|
|
175
177
|
});
|
package/package.json
CHANGED
package/spa/src/App.vue
CHANGED
|
@@ -297,7 +297,11 @@ async function initRouter() {
|
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
async function loadMenu() {
|
|
300
|
-
await
|
|
300
|
+
await initRouter();
|
|
301
|
+
if (!route.meta.customLayout) {
|
|
302
|
+
// for custom layouts we don't need to fetch menu
|
|
303
|
+
await coreStore.fetchMenuAndResource();
|
|
304
|
+
}
|
|
301
305
|
loginRedirectCheckIsReady.value = true;
|
|
302
306
|
}
|
|
303
307
|
|
|
@@ -6,16 +6,14 @@
|
|
|
6
6
|
<form autocomplete="off" @submit.prevent>
|
|
7
7
|
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 ">
|
|
8
8
|
<thead class="text-xs text-gray-700 uppercase bg-lightFormHeading dark:bg-gray-700 dark:text-gray-400 block md:table-row-group">
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
</tr>
|
|
9
|
+
<tr>
|
|
10
|
+
<th scope="col" class="px-6 py-3 hidden md:table-cell">
|
|
11
|
+
Field
|
|
12
|
+
</th>
|
|
13
|
+
<th scope="col" class="px-6 py-3 w-5/6 hidden md:table-cell">
|
|
14
|
+
Value
|
|
15
|
+
</th>
|
|
16
|
+
</tr>
|
|
19
17
|
</thead>
|
|
20
18
|
<tbody>
|
|
21
19
|
<tr v-for="column, i in editableColumns" :key="column.name"
|
|
@@ -147,7 +145,6 @@
|
|
|
147
145
|
|
|
148
146
|
import CustomDatePicker from "@/components/CustomDatePicker.vue";
|
|
149
147
|
import Dropdown from '@/components/Dropdown.vue';
|
|
150
|
-
import { useCoreStore } from '@/stores/core';
|
|
151
148
|
import { callAdminForthApi, getCustomComponent } from '@/utils';
|
|
152
149
|
import { IconExclamationCircleSolid, IconEyeSlashSolid, IconEyeSolid } from '@iconify-prerendered/vue-flowbite';
|
|
153
150
|
import { computedAsync } from '@vueuse/core';
|
|
@@ -56,8 +56,18 @@ const props = defineProps({
|
|
|
56
56
|
|
|
57
57
|
function protectAgainstXSS(value) {
|
|
58
58
|
return sanitizeHtml(value, {
|
|
59
|
+
allowedTags: [
|
|
60
|
+
"address", "article", "aside", "footer", "header", "h1", "h2", "h3", "h4",
|
|
61
|
+
"h5", "h6", "hgroup", "main", "nav", "section", "blockquote", "dd", "div",
|
|
62
|
+
"dl", "dt", "figcaption", "figure", "hr", "li", "main", "ol", "p", "pre",
|
|
63
|
+
"ul", "a", "abbr", "b", "bdi", "bdo", "br", "cite", "code", "data", "dfn",
|
|
64
|
+
"em", "i", "kbd", "mark", "q", "rb", "rp", "rt", "rtc", "ruby", "s", "samp",
|
|
65
|
+
"small", "span", "strong", "sub", "sup", "time", "u", "var", "wbr", "caption",
|
|
66
|
+
"col", "colgroup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", 'img'
|
|
67
|
+
],
|
|
59
68
|
allowedAttributes: {
|
|
60
|
-
|
|
69
|
+
'li': [ 'data-list' ],
|
|
70
|
+
'img': [ 'src', 'srcset', 'alt', 'title', 'width', 'height', 'loading' ]
|
|
61
71
|
}
|
|
62
72
|
});
|
|
63
73
|
}
|
|
@@ -9,7 +9,7 @@ export function showWarningTost(message: string) {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function showErrorTost(message: string, timeout?: number) {
|
|
12
|
-
window.adminforth.alert({ message, variant: 'danger', timeout: timeout ||
|
|
12
|
+
window.adminforth.alert({ message, variant: 'danger', timeout: timeout || 30});
|
|
13
13
|
return message;
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -57,7 +57,11 @@ export class FrontendAPI implements FrontendAPIInterface {
|
|
|
57
57
|
|
|
58
58
|
confirm(params: ConfirmParams): Promise<void> {
|
|
59
59
|
return new Promise((resolve, reject) => {
|
|
60
|
-
this.modalStore.setModalContent({
|
|
60
|
+
this.modalStore.setModalContent({
|
|
61
|
+
content: params.message,
|
|
62
|
+
acceptText: params.yes || 'Yes',
|
|
63
|
+
cancelText: params.no || 'Cancel'
|
|
64
|
+
})
|
|
61
65
|
this.modalStore.onAcceptFunction = resolve
|
|
62
66
|
this.modalStore.onCancelFunction = reject
|
|
63
67
|
this.modalStore.togleModal()
|
package/spa/src/stores/core.ts
CHANGED
package/spa/src/stores/toast.ts
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
|
-
import { ref,
|
|
1
|
+
import { ref, watch, type Ref } from 'vue'
|
|
2
2
|
import { defineStore } from 'pinia'
|
|
3
|
-
import { callAdminForthApi } from '@/utils';
|
|
4
3
|
import { v1 as uuid } from 'uuid';
|
|
4
|
+
import { useRoute } from 'vue-router';
|
|
5
|
+
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
export const useToastStore = defineStore('toast', () => {
|
|
7
|
-
const toasts = ref([]);
|
|
8
|
-
const
|
|
9
|
-
|
|
9
|
+
const toasts: Ref<any[]> = ref([]);
|
|
10
|
+
const route = useRoute();
|
|
11
|
+
|
|
12
|
+
watch(route, () => {
|
|
13
|
+
console.log('route changed 121');
|
|
14
|
+
// on route change clear all toasts older then 5 seconds
|
|
15
|
+
const now = +new Date();
|
|
16
|
+
toasts.value = toasts.value.filter((t) => now - t.createdAt < 5000);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const addToast = (toast: { message: string; variant: string }) => {
|
|
20
|
+
const toastId = uuid();
|
|
21
|
+
toasts.value.push({
|
|
22
|
+
...toast,
|
|
23
|
+
id: toastId,
|
|
24
|
+
createdAt: +new Date(),
|
|
25
|
+
});
|
|
10
26
|
};
|
|
11
|
-
const removeToast = (toast) => {
|
|
27
|
+
const removeToast = (toast: { id: string }) => {
|
|
12
28
|
toasts.value = toasts.value.filter((t) => t.id !== toast.id);
|
|
13
29
|
};
|
|
14
30
|
return { toasts, addToast, removeToast };
|
|
@@ -110,7 +110,6 @@
|
|
|
110
110
|
import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
111
111
|
import ResourceListTable from '@/components/ResourceListTable.vue';
|
|
112
112
|
import { useCoreStore } from '@/stores/core';
|
|
113
|
-
import { useModalStore } from '@/stores/modal';
|
|
114
113
|
import { useFiltersStore } from '@/stores/filters';
|
|
115
114
|
import { callAdminForthApi, getIcon } from '@/utils';
|
|
116
115
|
import { initFlowbite } from 'flowbite';
|
|
@@ -180,6 +179,16 @@ async function getList() {
|
|
|
180
179
|
}
|
|
181
180
|
|
|
182
181
|
async function startBulkAction(actionId) {
|
|
182
|
+
const action = coreStore.resource.options.bulkActions.find(a => a.id === actionId);
|
|
183
|
+
if (action.confirm) {
|
|
184
|
+
const confirmed = await window.adminforth.confirm({
|
|
185
|
+
message: action.confirm,
|
|
186
|
+
});
|
|
187
|
+
if (!confirmed) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
183
192
|
const data = await callAdminForthApi({
|
|
184
193
|
path: '/start_bulk_action',
|
|
185
194
|
method: 'POST',
|
|
@@ -193,6 +202,14 @@ async function startBulkAction(actionId) {
|
|
|
193
202
|
if (data?.ok) {
|
|
194
203
|
checkboxes.value = [];
|
|
195
204
|
await getList();
|
|
205
|
+
|
|
206
|
+
if (data.successMessage) {
|
|
207
|
+
window.adminforth.alert({
|
|
208
|
+
message: data.successMessage,
|
|
209
|
+
variant: 'success'
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
196
213
|
}
|
|
197
214
|
if (data?.error) {
|
|
198
215
|
showErrorTost(data.error);
|
|
@@ -56,6 +56,19 @@
|
|
|
56
56
|
</button>
|
|
57
57
|
</div>
|
|
58
58
|
|
|
59
|
+
<div class="flex items-start mb-5 hidden">
|
|
60
|
+
<div class="flex items-center h-5">
|
|
61
|
+
<input id="remember" type="checkbox" value="" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800" required />
|
|
62
|
+
</div>
|
|
63
|
+
<label for="remember" class="ms-2 text-sm font-medium text-gray-900 dark:text-gray-300">Remember me</label>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<component
|
|
67
|
+
v-for="c in coreStore?.config?.loginPageInjections?.underInputs || []"
|
|
68
|
+
:is="getCustomComponent(c)"
|
|
69
|
+
:meta="c.meta"
|
|
70
|
+
/>
|
|
71
|
+
|
|
59
72
|
<div v-if="error" class="flex items-center p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
|
|
60
73
|
<svg class="flex-shrink-0 inline w-4 h-4 me-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
|
61
74
|
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
|
|
@@ -66,6 +79,7 @@
|
|
|
66
79
|
</div>
|
|
67
80
|
</div>
|
|
68
81
|
|
|
82
|
+
|
|
69
83
|
|
|
70
84
|
<div v-if="coreStore.config?.loginPromptHTML"
|
|
71
85
|
class="flex items-center p-4 mb-4 text-sm text-gray-800 rounded-lg bg-gray-50 dark:bg-gray-800 dark:text-gray-400" role="alert"
|
|
@@ -101,6 +115,7 @@
|
|
|
101
115
|
|
|
102
116
|
<script setup>
|
|
103
117
|
|
|
118
|
+
import { getCustomComponent } from '@/utils';
|
|
104
119
|
import { onMounted, ref, computed } from 'vue';
|
|
105
120
|
import { useCoreStore } from '@/stores/core';
|
|
106
121
|
import { useUserStore } from '@/stores/user';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Express } from 'express';
|
|
2
|
+
import OperationalResource from '../modules/operationalResource.js';
|
|
2
3
|
|
|
3
4
|
export interface ICodeInjector {
|
|
4
5
|
srcFoldersToSync: Object;
|
|
@@ -237,7 +238,7 @@ export interface IAdminForthDataSourceConnectorConstructor {
|
|
|
237
238
|
}
|
|
238
239
|
|
|
239
240
|
export interface IAdminForthAuth {
|
|
240
|
-
verify(jwt : string, mustHaveType: string): Promise<any>;
|
|
241
|
+
verify(jwt : string, mustHaveType: string, decodeUser?: boolean): Promise<any>;
|
|
241
242
|
issueJWT(payload: Object, type: string): string;
|
|
242
243
|
|
|
243
244
|
removeCustomCookie({response, name}: {response: any, name: string}): void;
|
|
@@ -291,6 +292,11 @@ export interface IAdminForth {
|
|
|
291
292
|
*/
|
|
292
293
|
bundleNow({ hotReload, verbose }: { hotReload: boolean, verbose: boolean }): Promise<void>;
|
|
293
294
|
|
|
295
|
+
/**
|
|
296
|
+
* Resource to get access to operational resources for data api fetching and manipulation.
|
|
297
|
+
*/
|
|
298
|
+
resource(resourceId: string): IOperationalResource;
|
|
299
|
+
|
|
294
300
|
/**
|
|
295
301
|
* This method will be automatically called from AdminForth HTTP adapter to serve AdminForth SPA.
|
|
296
302
|
*/
|
|
@@ -738,13 +744,17 @@ export type AdminForthBulkAction = {
|
|
|
738
744
|
* Callback which will be called on backend when user clicks on action button.
|
|
739
745
|
* It should return Promise which will be resolved when action is done.
|
|
740
746
|
*/
|
|
741
|
-
action: ({ resource, selectedIds, adminUser }: { resource: AdminForthResource, selectedIds: Array<any>, adminUser: AdminUser }) => Promise<{ ok: boolean, error?: string }>,
|
|
747
|
+
action: ({ resource, selectedIds, adminUser }: { resource: AdminForthResource, selectedIds: Array<any>, adminUser: AdminUser }) => Promise<{ ok: boolean, error?: string, successMessage?: string }>,
|
|
742
748
|
|
|
743
749
|
/**
|
|
744
750
|
* Confirmation message which will be displayed to user before action is executed.
|
|
745
751
|
*/
|
|
746
752
|
confirm?: string,
|
|
747
753
|
|
|
754
|
+
/**
|
|
755
|
+
* Success message which will be displayed to user after action is executed.
|
|
756
|
+
*/
|
|
757
|
+
successMessage?: string,
|
|
748
758
|
/**
|
|
749
759
|
* Allowed callback called to check whether action is allowed for user.
|
|
750
760
|
* 1. It called first time when user goes to list view. If callback returns false, action button will be hidden on list view.
|
|
@@ -1326,6 +1336,15 @@ export type AdminForthConfig = {
|
|
|
1326
1336
|
* Execution is done on admin app load.
|
|
1327
1337
|
*/
|
|
1328
1338
|
announcementBadge?: (user: AdminUser) => { text?: string, html?: string, closable?: boolean, title?: string } | null,
|
|
1339
|
+
|
|
1340
|
+
/**
|
|
1341
|
+
* Custom panel components or array of components which will be displayed in the login form
|
|
1342
|
+
* right after the inputs. Use it to add custom authorization methods like social login or other custom fields e.g. 'reset'
|
|
1343
|
+
* password link.
|
|
1344
|
+
*/
|
|
1345
|
+
loginPageInjections?: {
|
|
1346
|
+
underInputs?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
1347
|
+
}
|
|
1329
1348
|
}
|
|
1330
1349
|
|
|
1331
1350
|
/**
|