adminforth 1.3.52-next.0 → 1.3.52-next.2
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/modules/codeInjector.js +23 -15
- package/dist/modules/configValidator.js +25 -7
- package/dist/modules/restApi.js +22 -18
- package/index.ts +1 -3
- package/modules/codeInjector.ts +21 -8
- package/modules/configValidator.ts +34 -8
- package/modules/restApi.ts +7 -2
- package/package.json +2 -1
- package/spa/index.html +2 -2
- package/spa/src/App.vue +28 -4
- package/types/AdminForthConfig.ts +11 -0
|
@@ -158,7 +158,7 @@ class CodeInjector {
|
|
|
158
158
|
}
|
|
159
159
|
prepareSources(_a) {
|
|
160
160
|
return __awaiter(this, arguments, void 0, function* ({ filesUpdated }) {
|
|
161
|
-
var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
161
|
+
var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
162
162
|
// check SPA_TMP_PATH exists and create if not
|
|
163
163
|
try {
|
|
164
164
|
yield fs.promises.access(CodeInjector.SPA_TMP_PATH, fs.constants.F_OK);
|
|
@@ -325,6 +325,16 @@ class CodeInjector {
|
|
|
325
325
|
}).join('\n');
|
|
326
326
|
// for each custom component generate import statement
|
|
327
327
|
const customResourceComponents = [];
|
|
328
|
+
function checkInjections(filePathes) {
|
|
329
|
+
filePathes.forEach(({ file }) => {
|
|
330
|
+
if (!customResourceComponents.includes(file)) {
|
|
331
|
+
if (file === undefined) {
|
|
332
|
+
throw new Error('file is undefined');
|
|
333
|
+
}
|
|
334
|
+
customResourceComponents.push(file);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
328
338
|
this.adminforth.config.resources.forEach((resource) => {
|
|
329
339
|
var _a;
|
|
330
340
|
resource.columns.forEach((column) => {
|
|
@@ -341,17 +351,15 @@ class CodeInjector {
|
|
|
341
351
|
});
|
|
342
352
|
(Object.values(((_a = resource.options) === null || _a === void 0 ? void 0 : _a.pageInjections) || {})).forEach((injection) => {
|
|
343
353
|
Object.values(injection).forEach((filePathes) => {
|
|
344
|
-
filePathes
|
|
345
|
-
if (!customResourceComponents.includes(file)) {
|
|
346
|
-
if (file === undefined) {
|
|
347
|
-
throw new Error('file is undefined');
|
|
348
|
-
}
|
|
349
|
-
customResourceComponents.push(file);
|
|
350
|
-
}
|
|
351
|
-
});
|
|
354
|
+
checkInjections(filePathes);
|
|
352
355
|
});
|
|
353
356
|
});
|
|
354
357
|
});
|
|
358
|
+
if ((_e = this.adminforth.config.customization) === null || _e === void 0 ? void 0 : _e.globalInjections) {
|
|
359
|
+
Object.values(this.adminforth.config.customization.globalInjections).forEach((injection) => {
|
|
360
|
+
checkInjections(injection);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
355
363
|
customResourceComponents.forEach((filePath) => {
|
|
356
364
|
const componentName = getComponentNameFromPath(filePath);
|
|
357
365
|
this.allComponentNames[filePath] = componentName;
|
|
@@ -376,7 +384,7 @@ class CodeInjector {
|
|
|
376
384
|
}
|
|
377
385
|
let imports = iconImports + '\n';
|
|
378
386
|
imports += customComponentsImports + '\n';
|
|
379
|
-
if ((
|
|
387
|
+
if ((_f = this.adminforth.config.customization) === null || _f === void 0 ? void 0 : _f.vueUsesFile) {
|
|
380
388
|
imports += `import addCustomUses from '${this.adminforth.config.customization.vueUsesFile}';\n`;
|
|
381
389
|
}
|
|
382
390
|
// inject that code into spa_tmp/src/App.vue
|
|
@@ -384,12 +392,12 @@ class CodeInjector {
|
|
|
384
392
|
let appVueContent = yield fs.promises.readFile(appVuePath, 'utf-8');
|
|
385
393
|
appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH IMPORTS */', imports);
|
|
386
394
|
appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH COMPONENT REGISTRATIONS */', iconComponents + '\n' + customComponentsComponents + '\n');
|
|
387
|
-
if ((
|
|
395
|
+
if ((_g = this.adminforth.config.customization) === null || _g === void 0 ? void 0 : _g.vueUsesFile) {
|
|
388
396
|
appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH CUSTOM USES */', 'addCustomUses(app);');
|
|
389
397
|
}
|
|
390
398
|
yield fs.promises.writeFile(appVuePath, appVueContent);
|
|
391
399
|
// generate tailwind extend styles
|
|
392
|
-
const stylesGenerator = new StylesGenerator((
|
|
400
|
+
const stylesGenerator = new StylesGenerator((_h = this.adminforth.config.customization) === null || _h === void 0 ? void 0 : _h.styles);
|
|
393
401
|
const stylesText = JSON.stringify(stylesGenerator.mergeStyles(), null, 2).slice(1, -1);
|
|
394
402
|
let tailwindConfigPath = path.join(CodeInjector.SPA_TMP_PATH, 'tailwind.config.js');
|
|
395
403
|
let tailwindConfigContent = yield fs.promises.readFile(tailwindConfigPath, 'utf-8');
|
|
@@ -402,7 +410,7 @@ class CodeInjector {
|
|
|
402
410
|
const indexHtmlPath = path.join(CodeInjector.SPA_TMP_PATH, 'index.html');
|
|
403
411
|
let indexHtmlContent = yield fs.promises.readFile(indexHtmlPath, 'utf-8');
|
|
404
412
|
indexHtmlContent = indexHtmlContent.replace('/* IMPORTANT:ADMINFORTH TITLE */', `${this.adminforth.config.customization.title || 'AdminForth'}`);
|
|
405
|
-
indexHtmlContent = indexHtmlContent.replace('/* IMPORTANT:ADMINFORTH FAVICON */', ((
|
|
413
|
+
indexHtmlContent = indexHtmlContent.replace('/* IMPORTANT:ADMINFORTH FAVICON */', ((_j = this.adminforth.config.customization.favicon) === null || _j === void 0 ? void 0 : _j.replace('@@/', `${this.adminforth.baseUrlSlashed}assets/`))
|
|
406
414
|
||
|
|
407
415
|
`${this.adminforth.baseUrlSlashed}assets/favicon.png`);
|
|
408
416
|
yield fs.promises.writeFile(indexHtmlPath, indexHtmlContent);
|
|
@@ -417,7 +425,7 @@ class CodeInjector {
|
|
|
417
425
|
}
|
|
418
426
|
let homePagePath = homepageMenuItem.path || `/resource/${homepageMenuItem.resourceId}`;
|
|
419
427
|
if (!homePagePath) {
|
|
420
|
-
homePagePath = ((
|
|
428
|
+
homePagePath = ((_k = this.adminforth.config.menu.filter((mi) => mi.path)[0]) === null || _k === void 0 ? void 0 : _k.path) || `/resource/${(_l = this.adminforth.config.menu.filter((mi) => mi.children)[0]) === null || _l === void 0 ? void 0 : _l.resourceId}`;
|
|
421
429
|
}
|
|
422
430
|
routes += `{
|
|
423
431
|
path: '/',
|
|
@@ -434,7 +442,7 @@ class CodeInjector {
|
|
|
434
442
|
/* customPackageLock */
|
|
435
443
|
let usersLockHash = '';
|
|
436
444
|
let usersPackages = [];
|
|
437
|
-
if ((
|
|
445
|
+
if ((_m = this.adminforth.config.customization) === null || _m === void 0 ? void 0 : _m.customComponentsDir) {
|
|
438
446
|
[usersLockHash, usersPackages] = yield this.packagesFromNpm(this.adminforth.config.customization.customComponentsDir);
|
|
439
447
|
}
|
|
440
448
|
const pluginPackages = [];
|
|
@@ -19,6 +19,15 @@ export default class ConfigValidator {
|
|
|
19
19
|
this.adminforth = adminforth;
|
|
20
20
|
this.config = config;
|
|
21
21
|
}
|
|
22
|
+
validateAndListifyInjection(obj, key, errors) {
|
|
23
|
+
if (!Array.isArray(obj[key])) {
|
|
24
|
+
// not array
|
|
25
|
+
obj[key] = [obj[key]];
|
|
26
|
+
}
|
|
27
|
+
obj[key].forEach((target, i) => {
|
|
28
|
+
obj[key][i] = this.validateComponent(target, errors);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
22
31
|
checkCustomFileExists(filePath) {
|
|
23
32
|
if (filePath.startsWith('@@/')) {
|
|
24
33
|
const checkPath = path.join(this.config.customization.customComponentsDir, filePath.replace('@@/', ''));
|
|
@@ -99,6 +108,9 @@ export default class ConfigValidator {
|
|
|
99
108
|
if (this.config.customization.loginPageInjections === undefined) {
|
|
100
109
|
this.config.customization.loginPageInjections = {};
|
|
101
110
|
}
|
|
111
|
+
if (this.config.customization.globalInjections === undefined) {
|
|
112
|
+
this.config.customization.globalInjections = {};
|
|
113
|
+
}
|
|
102
114
|
if (this.config.customization.loginPageInjections.underInputs === undefined) {
|
|
103
115
|
this.config.customization.loginPageInjections.underInputs = [];
|
|
104
116
|
}
|
|
@@ -297,13 +309,7 @@ export default class ConfigValidator {
|
|
|
297
309
|
}
|
|
298
310
|
Object.entries(value).map(([injection, target]) => {
|
|
299
311
|
if (possibleInjections.includes(injection)) {
|
|
300
|
-
|
|
301
|
-
// not array
|
|
302
|
-
res.options.pageInjections[key][injection] = [target];
|
|
303
|
-
}
|
|
304
|
-
res.options.pageInjections[key][injection].forEach((target, i) => {
|
|
305
|
-
res.options.pageInjections[key][injection][i] = this.validateComponent(target, errors);
|
|
306
|
-
});
|
|
312
|
+
this.validateAndListifyInjection(res.options.pageInjections[key], injection, errors);
|
|
307
313
|
}
|
|
308
314
|
else {
|
|
309
315
|
const similar = suggestIfTypo(possibleInjections, injection);
|
|
@@ -351,6 +357,18 @@ export default class ConfigValidator {
|
|
|
351
357
|
}
|
|
352
358
|
}
|
|
353
359
|
});
|
|
360
|
+
if (this.config.customization.globalInjections) {
|
|
361
|
+
const ALLOWED_GLOBAL_INJECTIONS = ['userMenu', 'header', 'sidebar',];
|
|
362
|
+
Object.keys(this.config.customization.globalInjections).forEach((injection) => {
|
|
363
|
+
if (ALLOWED_GLOBAL_INJECTIONS.includes(injection)) {
|
|
364
|
+
this.validateAndListifyInjection(this.config.customization.globalInjections, injection, errors);
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
const similar = suggestIfTypo(ALLOWED_GLOBAL_INJECTIONS, injection);
|
|
368
|
+
errors.push(`Global injection key "${injection}" is not allowed. Allowed keys are ${ALLOWED_GLOBAL_INJECTIONS.join(', ')}. ${similar ? `Did you mean "${similar}"?` : ''}`);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
}
|
|
354
372
|
if (!this.config.menu) {
|
|
355
373
|
errors.push('No config.menu defined');
|
|
356
374
|
}
|
package/dist/modules/restApi.js
CHANGED
|
@@ -154,15 +154,18 @@ export default class AdminForthRestAPI {
|
|
|
154
154
|
method: 'GET',
|
|
155
155
|
path: '/get_base_config',
|
|
156
156
|
handler: (_k) => __awaiter(this, [_k], void 0, function* ({ input, adminUser, cookies }) {
|
|
157
|
-
var _l, _m, _o, _p;
|
|
157
|
+
var _l, _m, _o, _p, _q;
|
|
158
158
|
let username = '';
|
|
159
159
|
let userFullName = '';
|
|
160
160
|
const dbUser = adminUser.dbUser;
|
|
161
161
|
username = dbUser[this.adminforth.config.auth.usernameField];
|
|
162
162
|
userFullName = dbUser[this.adminforth.config.auth.userFullNameField];
|
|
163
|
+
const userResource = this.adminforth.config.resources.find((res) => res.resourceId === this.adminforth.config.auth.usersResourceId);
|
|
164
|
+
const userPk = dbUser[userResource.columns.find((col) => col.primaryKey).name];
|
|
163
165
|
const userData = {
|
|
164
166
|
[this.adminforth.config.auth.usernameField]: username,
|
|
165
|
-
[this.adminforth.config.auth.userFullNameField]: userFullName
|
|
167
|
+
[this.adminforth.config.auth.userFullNameField]: userFullName,
|
|
168
|
+
pk: userPk,
|
|
166
169
|
};
|
|
167
170
|
const checkIsMenuItemVisible = (menuItem) => {
|
|
168
171
|
if (typeof menuItem.visible === 'function') {
|
|
@@ -227,6 +230,7 @@ export default class AdminForthRestAPI {
|
|
|
227
230
|
title: (_o = this.adminforth.config.customization) === null || _o === void 0 ? void 0 : _o.title,
|
|
228
231
|
emptyFieldPlaceholder: (_p = this.adminforth.config.customization) === null || _p === void 0 ? void 0 : _p.emptyFieldPlaceholder,
|
|
229
232
|
announcementBadge,
|
|
233
|
+
globalInjections: (_q = this.adminforth.config.customization) === null || _q === void 0 ? void 0 : _q.globalInjections,
|
|
230
234
|
},
|
|
231
235
|
adminUser,
|
|
232
236
|
version: ADMINFORTH_VERSION,
|
|
@@ -243,7 +247,7 @@ export default class AdminForthRestAPI {
|
|
|
243
247
|
server.endpoint({
|
|
244
248
|
method: 'POST',
|
|
245
249
|
path: '/get_resource',
|
|
246
|
-
handler: (
|
|
250
|
+
handler: (_r) => __awaiter(this, [_r], void 0, function* ({ body, adminUser }) {
|
|
247
251
|
const { resourceId } = body;
|
|
248
252
|
if (!this.adminforth.statuses.dbDiscover) {
|
|
249
253
|
return { error: 'Database discovery not started' };
|
|
@@ -277,8 +281,8 @@ export default class AdminForthRestAPI {
|
|
|
277
281
|
server.endpoint({
|
|
278
282
|
method: 'POST',
|
|
279
283
|
path: '/get_resource_data',
|
|
280
|
-
handler: (
|
|
281
|
-
var
|
|
284
|
+
handler: (_s) => __awaiter(this, [_s], void 0, function* ({ body, adminUser }) {
|
|
285
|
+
var _t, _u, _v, _w;
|
|
282
286
|
const { resourceId, source } = body;
|
|
283
287
|
if (['show', 'list'].includes(source) === false) {
|
|
284
288
|
return { error: 'Invalid source, should be list or show' };
|
|
@@ -298,7 +302,7 @@ export default class AdminForthRestAPI {
|
|
|
298
302
|
if (!allowed) {
|
|
299
303
|
return { error };
|
|
300
304
|
}
|
|
301
|
-
for (const hook of listify((
|
|
305
|
+
for (const hook of listify((_u = (_t = resource.hooks) === null || _t === void 0 ? void 0 : _t[source]) === null || _u === void 0 ? void 0 : _u.beforeDatasourceRequest)) {
|
|
302
306
|
const resp = yield hook({ resource, query: body, adminUser });
|
|
303
307
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
304
308
|
throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -381,7 +385,7 @@ export default class AdminForthRestAPI {
|
|
|
381
385
|
})));
|
|
382
386
|
}
|
|
383
387
|
// only after adminforth made all post processing, give user ability to edit it
|
|
384
|
-
for (const hook of listify((
|
|
388
|
+
for (const hook of listify((_w = (_v = resource.hooks) === null || _v === void 0 ? void 0 : _v[source]) === null || _w === void 0 ? void 0 : _w.afterDatasourceResponse)) {
|
|
385
389
|
const resp = yield hook({ resource, response: data.data, adminUser });
|
|
386
390
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
387
391
|
throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -396,8 +400,8 @@ export default class AdminForthRestAPI {
|
|
|
396
400
|
server.endpoint({
|
|
397
401
|
method: 'POST',
|
|
398
402
|
path: '/get_resource_foreign_data',
|
|
399
|
-
handler: (
|
|
400
|
-
var
|
|
403
|
+
handler: (_x) => __awaiter(this, [_x], void 0, function* ({ body, adminUser }) {
|
|
404
|
+
var _y, _z, _0, _1;
|
|
401
405
|
const { resourceId, column } = body;
|
|
402
406
|
if (!this.adminforth.statuses.dbDiscover) {
|
|
403
407
|
return { error: 'Database discovery not started' };
|
|
@@ -418,7 +422,7 @@ export default class AdminForthRestAPI {
|
|
|
418
422
|
}
|
|
419
423
|
const targetResourceId = columnConfig.foreignResource.resourceId;
|
|
420
424
|
const targetResource = this.adminforth.config.resources.find((res) => res.resourceId == targetResourceId);
|
|
421
|
-
for (const hook of listify((
|
|
425
|
+
for (const hook of listify((_z = (_y = columnConfig.foreignResource.hooks) === null || _y === void 0 ? void 0 : _y.dropdownList) === null || _z === void 0 ? void 0 : _z.beforeDatasourceRequest)) {
|
|
422
426
|
const resp = yield hook({ query: body, adminUser, resource: targetResource });
|
|
423
427
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
424
428
|
throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -447,7 +451,7 @@ export default class AdminForthRestAPI {
|
|
|
447
451
|
const response = {
|
|
448
452
|
items
|
|
449
453
|
};
|
|
450
|
-
for (const hook of listify((
|
|
454
|
+
for (const hook of listify((_1 = (_0 = columnConfig.foreignResource.hooks) === null || _0 === void 0 ? void 0 : _0.dropdownList) === null || _1 === void 0 ? void 0 : _1.afterDatasourceResponse)) {
|
|
451
455
|
const resp = yield hook({ response, adminUser, resource: targetResource });
|
|
452
456
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
453
457
|
throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -462,7 +466,7 @@ export default class AdminForthRestAPI {
|
|
|
462
466
|
server.endpoint({
|
|
463
467
|
method: 'POST',
|
|
464
468
|
path: '/get_min_max_for_columns',
|
|
465
|
-
handler: (
|
|
469
|
+
handler: (_2) => __awaiter(this, [_2], void 0, function* ({ body }) {
|
|
466
470
|
const { resourceId } = body;
|
|
467
471
|
if (!this.adminforth.statuses.dbDiscover) {
|
|
468
472
|
return { error: 'Database discovery not started' };
|
|
@@ -491,8 +495,8 @@ export default class AdminForthRestAPI {
|
|
|
491
495
|
server.endpoint({
|
|
492
496
|
method: 'POST',
|
|
493
497
|
path: '/create_record',
|
|
494
|
-
handler: (
|
|
495
|
-
var
|
|
498
|
+
handler: (_3) => __awaiter(this, [_3], void 0, function* ({ body, adminUser }) {
|
|
499
|
+
var _4;
|
|
496
500
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
497
501
|
if (!resource) {
|
|
498
502
|
return { error: `Resource '${body['resourceId']}' not found` };
|
|
@@ -504,7 +508,7 @@ export default class AdminForthRestAPI {
|
|
|
504
508
|
}
|
|
505
509
|
const { record } = body;
|
|
506
510
|
for (const column of resource.columns) {
|
|
507
|
-
if (((
|
|
511
|
+
if (((_4 = column.required) === null || _4 === void 0 ? void 0 : _4.create) &&
|
|
508
512
|
record[column.name] === undefined &&
|
|
509
513
|
column.showIn.includes(AdminForthResourcePages.create)) {
|
|
510
514
|
return { error: `Column '${column.name}' is required`, ok: false };
|
|
@@ -524,7 +528,7 @@ export default class AdminForthRestAPI {
|
|
|
524
528
|
server.endpoint({
|
|
525
529
|
method: 'POST',
|
|
526
530
|
path: '/update_record',
|
|
527
|
-
handler: (
|
|
531
|
+
handler: (_5) => __awaiter(this, [_5], void 0, function* ({ body, adminUser }) {
|
|
528
532
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
529
533
|
if (!resource) {
|
|
530
534
|
return { error: `Resource '${body['resourceId']}' not found` };
|
|
@@ -554,7 +558,7 @@ export default class AdminForthRestAPI {
|
|
|
554
558
|
server.endpoint({
|
|
555
559
|
method: 'POST',
|
|
556
560
|
path: '/delete_record',
|
|
557
|
-
handler: (
|
|
561
|
+
handler: (_6) => __awaiter(this, [_6], void 0, function* ({ body, adminUser }) {
|
|
558
562
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
559
563
|
const record = yield this.adminforth.connectors[resource.dataSource].getRecordByPrimaryKey(resource, body['primaryKey']);
|
|
560
564
|
if (!resource) {
|
|
@@ -584,7 +588,7 @@ export default class AdminForthRestAPI {
|
|
|
584
588
|
server.endpoint({
|
|
585
589
|
method: 'POST',
|
|
586
590
|
path: '/start_bulk_action',
|
|
587
|
-
handler: (
|
|
591
|
+
handler: (_7) => __awaiter(this, [_7], void 0, function* ({ body, adminUser }) {
|
|
588
592
|
const { resourceId, actionId, recordIds } = body;
|
|
589
593
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
|
|
590
594
|
if (!resource) {
|
package/index.ts
CHANGED
|
@@ -12,8 +12,7 @@ import {
|
|
|
12
12
|
type IConfigValidator,
|
|
13
13
|
IOperationalResource,
|
|
14
14
|
AdminForthFilterOperators,
|
|
15
|
-
AdminForthDataTypes,
|
|
16
|
-
IHttpServer,
|
|
15
|
+
AdminForthDataTypes, IHttpServer,
|
|
17
16
|
BeforeSaveFunction,
|
|
18
17
|
AfterSaveFunction,
|
|
19
18
|
AdminUser,
|
|
@@ -25,7 +24,6 @@ import ConfigValidator from './modules/configValidator.js';
|
|
|
25
24
|
import AdminForthRestAPI, { interpretResource } from './modules/restApi.js';
|
|
26
25
|
import ClickhouseConnector from './dataConnectors/clickhouse.js';
|
|
27
26
|
import OperationalResource from './modules/operationalResource.js';
|
|
28
|
-
import { error } from 'console';
|
|
29
27
|
|
|
30
28
|
// exports
|
|
31
29
|
export * from './types/AdminForthConfig.js';
|
package/modules/codeInjector.ts
CHANGED
|
@@ -356,6 +356,18 @@ class CodeInjector implements ICodeInjector {
|
|
|
356
356
|
|
|
357
357
|
// for each custom component generate import statement
|
|
358
358
|
const customResourceComponents = [];
|
|
359
|
+
|
|
360
|
+
function checkInjections(filePathes) {
|
|
361
|
+
filePathes.forEach(({ file }) => {
|
|
362
|
+
if (!customResourceComponents.includes(file)) {
|
|
363
|
+
if (file === undefined) {
|
|
364
|
+
throw new Error('file is undefined');
|
|
365
|
+
}
|
|
366
|
+
customResourceComponents.push(file);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
359
371
|
this.adminforth.config.resources.forEach((resource) => {
|
|
360
372
|
resource.columns.forEach((column) => {
|
|
361
373
|
if (column.components) {
|
|
@@ -369,20 +381,21 @@ class CodeInjector implements ICodeInjector {
|
|
|
369
381
|
});
|
|
370
382
|
}
|
|
371
383
|
});
|
|
384
|
+
|
|
372
385
|
(Object.values(resource.options?.pageInjections || {})).forEach((injection) => {
|
|
373
386
|
Object.values(injection).forEach((filePathes: {file: string}[]) => {
|
|
374
|
-
filePathes
|
|
375
|
-
if (!customResourceComponents.includes(file)) {
|
|
376
|
-
if (file === undefined) {
|
|
377
|
-
throw new Error('file is undefined');
|
|
378
|
-
}
|
|
379
|
-
customResourceComponents.push(file);
|
|
380
|
-
}
|
|
381
|
-
});
|
|
387
|
+
checkInjections(filePathes);
|
|
382
388
|
});
|
|
383
389
|
});
|
|
384
390
|
});
|
|
385
391
|
|
|
392
|
+
if (this.adminforth.config.customization?.globalInjections) {
|
|
393
|
+
Object.values(this.adminforth.config.customization.globalInjections).forEach((injection) => {
|
|
394
|
+
checkInjections(injection);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
|
|
386
399
|
customResourceComponents.forEach((filePath) => {
|
|
387
400
|
const componentName = getComponentNameFromPath(filePath);
|
|
388
401
|
this.allComponentNames[filePath] = componentName;
|
|
@@ -15,6 +15,9 @@ import { guessLabelFromName, suggestIfTypo } from './utils.js';
|
|
|
15
15
|
|
|
16
16
|
import crypto from 'crypto';
|
|
17
17
|
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
18
21
|
export default class ConfigValidator implements IConfigValidator {
|
|
19
22
|
|
|
20
23
|
constructor(private adminforth: IAdminForth, private config: AdminForthConfig) {
|
|
@@ -22,6 +25,16 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
22
25
|
this.config = config;
|
|
23
26
|
}
|
|
24
27
|
|
|
28
|
+
validateAndListifyInjection(obj, key, errors) {
|
|
29
|
+
if (!Array.isArray(obj[key])) {
|
|
30
|
+
// not array
|
|
31
|
+
obj[key] = [obj[key]];
|
|
32
|
+
}
|
|
33
|
+
obj[key].forEach((target, i) => {
|
|
34
|
+
obj[key][i] = this.validateComponent(target, errors);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
checkCustomFileExists(filePath: string): Array<string> {
|
|
26
39
|
if (filePath.startsWith('@@/')) {
|
|
27
40
|
const checkPath = path.join(this.config.customization.customComponentsDir, filePath.replace('@@/', ''));
|
|
@@ -112,7 +125,9 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
112
125
|
if (this.config.customization.loginPageInjections === undefined) {
|
|
113
126
|
this.config.customization.loginPageInjections = {};
|
|
114
127
|
}
|
|
115
|
-
|
|
128
|
+
if (this.config.customization.globalInjections === undefined) {
|
|
129
|
+
this.config.customization.globalInjections = {};
|
|
130
|
+
}
|
|
116
131
|
if (this.config.customization.loginPageInjections.underInputs === undefined) {
|
|
117
132
|
this.config.customization.loginPageInjections.underInputs = [];
|
|
118
133
|
}
|
|
@@ -339,7 +354,11 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
339
354
|
const possibleInjections = ['beforeBreadcrumbs', 'afterBreadcrumbs', 'bottom', 'threeDotsDropdownItems', 'customActionIcons'];
|
|
340
355
|
const possiblePages = ['list', 'show', 'create', 'edit'];
|
|
341
356
|
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
|
|
342
360
|
if (res.options.pageInjections) {
|
|
361
|
+
|
|
343
362
|
Object.entries(res.options.pageInjections).map(([key, value]) => {
|
|
344
363
|
if (!possiblePages.includes(key)) {
|
|
345
364
|
const similar = suggestIfTypo(possiblePages, key);
|
|
@@ -348,13 +367,7 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
348
367
|
|
|
349
368
|
Object.entries(value).map(([injection, target]) => {
|
|
350
369
|
if (possibleInjections.includes(injection)) {
|
|
351
|
-
|
|
352
|
-
// not array
|
|
353
|
-
res.options.pageInjections[key][injection] = [target];
|
|
354
|
-
}
|
|
355
|
-
res.options.pageInjections[key][injection].forEach((target, i) => {
|
|
356
|
-
res.options.pageInjections[key][injection][i] = this.validateComponent(target, errors);
|
|
357
|
-
});
|
|
370
|
+
this.validateAndListifyInjection(res.options.pageInjections[key], injection, errors);
|
|
358
371
|
} else {
|
|
359
372
|
const similar = suggestIfTypo(possibleInjections, injection);
|
|
360
373
|
errors.push(`Resource "${res.resourceId}" has invalid pageInjection key "${injection}", Supported keys are ${possibleInjections.join(', ')} ${similar ? `Did you mean "${similar}"?` : ''}`);
|
|
@@ -362,6 +375,7 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
362
375
|
});
|
|
363
376
|
|
|
364
377
|
})
|
|
378
|
+
|
|
365
379
|
}
|
|
366
380
|
|
|
367
381
|
// transform all hooks Functions to array of functions
|
|
@@ -407,6 +421,18 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
407
421
|
}
|
|
408
422
|
});
|
|
409
423
|
|
|
424
|
+
if (this.config.customization.globalInjections) {
|
|
425
|
+
const ALLOWED_GLOBAL_INJECTIONS = ['userMenu', 'header', 'sidebar',]
|
|
426
|
+
Object.keys(this.config.customization.globalInjections).forEach((injection) => {
|
|
427
|
+
if (ALLOWED_GLOBAL_INJECTIONS.includes(injection)) {
|
|
428
|
+
this.validateAndListifyInjection(this.config.customization.globalInjections, injection, errors);
|
|
429
|
+
} else {
|
|
430
|
+
const similar = suggestIfTypo(ALLOWED_GLOBAL_INJECTIONS, injection);
|
|
431
|
+
errors.push(`Global injection key "${injection}" is not allowed. Allowed keys are ${ALLOWED_GLOBAL_INJECTIONS.join(', ')}. ${similar ? `Did you mean "${similar}"?` : ''}`);
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
410
436
|
if (!this.config.menu) {
|
|
411
437
|
errors.push('No config.menu defined');
|
|
412
438
|
}
|
package/modules/restApi.ts
CHANGED
|
@@ -193,11 +193,15 @@ export default class AdminForthRestAPI {
|
|
|
193
193
|
|
|
194
194
|
const dbUser = adminUser.dbUser;
|
|
195
195
|
username = dbUser[this.adminforth.config.auth.usernameField];
|
|
196
|
-
userFullName =dbUser[this.adminforth.config.auth.userFullNameField];
|
|
196
|
+
userFullName = dbUser[this.adminforth.config.auth.userFullNameField];
|
|
197
|
+
const userResource = this.adminforth.config.resources.find((res) => res.resourceId === this.adminforth.config.auth.usersResourceId);
|
|
198
|
+
|
|
199
|
+
const userPk = dbUser[userResource.columns.find((col) => col.primaryKey).name];
|
|
197
200
|
|
|
198
201
|
const userData = {
|
|
199
202
|
[this.adminforth.config.auth.usernameField]: username,
|
|
200
|
-
[this.adminforth.config.auth.userFullNameField]: userFullName
|
|
203
|
+
[this.adminforth.config.auth.userFullNameField]: userFullName,
|
|
204
|
+
pk: userPk,
|
|
201
205
|
};
|
|
202
206
|
const checkIsMenuItemVisible = (menuItem) => {
|
|
203
207
|
if (typeof menuItem.visible === 'function') {
|
|
@@ -265,6 +269,7 @@ export default class AdminForthRestAPI {
|
|
|
265
269
|
title: this.adminforth.config.customization?.title,
|
|
266
270
|
emptyFieldPlaceholder: this.adminforth.config.customization?.emptyFieldPlaceholder,
|
|
267
271
|
announcementBadge,
|
|
272
|
+
globalInjections: this.adminforth.config.customization?.globalInjections,
|
|
268
273
|
},
|
|
269
274
|
adminUser,
|
|
270
275
|
version: ADMINFORTH_VERSION,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adminforth",
|
|
3
|
-
"version": "1.3.52-next.
|
|
3
|
+
"version": "1.3.52-next.2",
|
|
4
4
|
"description": "OpenSource Vue3 powered forth-generation admin panel",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"type": "module",
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@clickhouse/client": "^1.4.0",
|
|
22
|
+
"adminforth": "^1.3.52-next.0",
|
|
22
23
|
"better-sqlite3": "^10.0.0",
|
|
23
24
|
"dayjs": "^1.11.11",
|
|
24
25
|
"express": "^4.21.0",
|
package/spa/index.html
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
</script> -->
|
|
17
17
|
|
|
18
18
|
</head>
|
|
19
|
-
<body class=" ">
|
|
20
|
-
<div id="app" class="
|
|
19
|
+
<body class="min-h-screen flex flex-column">
|
|
20
|
+
<div id="app" class="grow bg-lightHtml dark:bg-darkHtml"></div>
|
|
21
21
|
<script type="module" src="/src/main.ts"></script>
|
|
22
22
|
</body>
|
|
23
23
|
</html>
|
package/spa/src/App.vue
CHANGED
|
@@ -17,7 +17,18 @@
|
|
|
17
17
|
|
|
18
18
|
</div>
|
|
19
19
|
<div class="flex items-center">
|
|
20
|
+
|
|
21
|
+
<component
|
|
22
|
+
v-for="c in coreStore?.config?.globalInjections?.header || []"
|
|
23
|
+
:is="getCustomComponent(c)"
|
|
24
|
+
:meta="c.meta"
|
|
25
|
+
:adminUser="coreStore.adminUser"
|
|
26
|
+
/>
|
|
27
|
+
|
|
20
28
|
<div class="flex items-center ms-3 ">
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
21
32
|
<span @click="toggleTheme" class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black hover:bg-lightHtml dark:text-darkSidebarTextHover dark:hover:bg-darkHtml dark:hover:text-darkSidebarTextActive" role="menuitem">
|
|
22
33
|
<IconMoonSolid class="w-5 h-5 text-blue-300" v-if="theme !== 'dark'" />
|
|
23
34
|
<IconSunSolid class="w-5 h-5 text-yellow-300" v-else />
|
|
@@ -41,9 +52,13 @@
|
|
|
41
52
|
</p>
|
|
42
53
|
</div>
|
|
43
54
|
<ul class="py-1" role="none">
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
<li v-for="c in coreStore?.config?.globalInjections?.userMenu || []">
|
|
56
|
+
<component
|
|
57
|
+
:is="getCustomComponent(c)"
|
|
58
|
+
:meta="c.meta"
|
|
59
|
+
:adminUser="coreStore.adminUser"
|
|
60
|
+
/>
|
|
61
|
+
</li>
|
|
47
62
|
<li>
|
|
48
63
|
<button @click="logout" class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black hover:bg-html dark:text-darkSidebarTextHover dark:hover:bg-darkSidebarItemHover dark:hover:text-darkSidebarTextActive w-full" role="menuitem">Sign out</button>
|
|
49
64
|
</li>
|
|
@@ -145,6 +160,13 @@
|
|
|
145
160
|
</p>
|
|
146
161
|
<!-- <a class="text-sm text-lightPrimary underline font-medium hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300" href="#">Turn new navigation off</a> -->
|
|
147
162
|
</div>
|
|
163
|
+
|
|
164
|
+
<component
|
|
165
|
+
v-for="c in coreStore?.config?.globalInjections?.sidebar || []"
|
|
166
|
+
:is="getCustomComponent(c)"
|
|
167
|
+
:meta="c.meta"
|
|
168
|
+
:adminUser="coreStore.adminUser"
|
|
169
|
+
/>
|
|
148
170
|
</div>
|
|
149
171
|
</aside>
|
|
150
172
|
|
|
@@ -237,7 +259,9 @@ import { createHead } from 'unhead'
|
|
|
237
259
|
import { loadFile } from '@/utils';
|
|
238
260
|
import Toast from './components/Toast.vue';
|
|
239
261
|
import {useToastStore} from '@/stores/toast';
|
|
240
|
-
import { FrontendAPI } from '@/composables/useStores'
|
|
262
|
+
import { FrontendAPI } from '@/composables/useStores';
|
|
263
|
+
import { getCustomComponent } from '@/utils';
|
|
264
|
+
|
|
241
265
|
// import { link } from 'fs';
|
|
242
266
|
const coreStore = useCoreStore();
|
|
243
267
|
const modalStore = useModalStore();
|
|
@@ -1075,6 +1075,8 @@ export type AdminForthResource = {
|
|
|
1075
1075
|
*
|
|
1076
1076
|
*/
|
|
1077
1077
|
pageInjections?: {
|
|
1078
|
+
|
|
1079
|
+
|
|
1078
1080
|
/**
|
|
1079
1081
|
* Custom components which can be injected into resource list page.
|
|
1080
1082
|
*
|
|
@@ -1405,6 +1407,15 @@ export type AdminForthConfig = {
|
|
|
1405
1407
|
loginPageInjections?: {
|
|
1406
1408
|
underInputs?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
1407
1409
|
}
|
|
1410
|
+
|
|
1411
|
+
/**
|
|
1412
|
+
* Custom panel components or array of components which will be displayed in different parts of the admin panel.
|
|
1413
|
+
*/
|
|
1414
|
+
globalInjections?: {
|
|
1415
|
+
userMenu?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
1416
|
+
header?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
1417
|
+
sidebar?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
1418
|
+
}
|
|
1408
1419
|
}
|
|
1409
1420
|
|
|
1410
1421
|
/**
|