adminforth 1.2.12 → 1.2.13
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/basePlugin.ts +5 -5
- package/dist/basePlugin.js +4 -4
- package/dist/index.js +1 -0
- package/dist/modules/codeInjector.js +63 -32
- package/dist/modules/configValidator.js +4 -3
- package/dist/modules/restApi.js +11 -1
- package/index.ts +1 -0
- package/modules/codeInjector.ts +76 -37
- package/modules/configValidator.ts +2 -1
- package/modules/restApi.ts +19 -3
- package/package.json +1 -1
- package/types/AdminForthConfig.ts +37 -0
package/basePlugin.ts
CHANGED
|
@@ -11,11 +11,13 @@ export default class AdminForthPlugin implements IAdminForthPlugin {
|
|
|
11
11
|
pluginDir: string;
|
|
12
12
|
customFolderName: string = 'custom';
|
|
13
13
|
pluginInstanceId: string;
|
|
14
|
+
customFolderPath: string;
|
|
14
15
|
|
|
15
16
|
constructor(pluginOptions: any, metaUrl: string) {
|
|
16
17
|
// set up plugin here
|
|
17
18
|
this.pluginDir = currentFileDir(metaUrl);
|
|
18
19
|
this.pluginInstanceId = uuidv4();
|
|
20
|
+
this.customFolderPath = path.join(this.pluginDir, this.customFolderName);
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
setupEndpoints(server: any) {
|
|
@@ -30,14 +32,12 @@ export default class AdminForthPlugin implements IAdminForthPlugin {
|
|
|
30
32
|
const key = `@@/plugins/${this.constructor.name}/${componentFile}`;
|
|
31
33
|
const componentName = getComponentNameFromPath(key);
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (!this.adminforth.codeInjector.srcFoldersToSync[absSrcFolder]) {
|
|
36
|
-
this.adminforth.codeInjector.srcFoldersToSync[absSrcFolder] = `./plugins/${this.constructor.name}/`;
|
|
35
|
+
if (!this.adminforth.codeInjector.srcFoldersToSync[this.customFolderPath]) {
|
|
36
|
+
this.adminforth.codeInjector.srcFoldersToSync[this.customFolderPath] = `./plugins/${this.constructor.name}/`;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
if (!this.adminforth.codeInjector.allComponentNames[key]) {
|
|
40
|
-
const absSrcPath = path.join(
|
|
40
|
+
const absSrcPath = path.join(this.customFolderPath, componentFile);
|
|
41
41
|
if (!fs.existsSync(absSrcPath)) {
|
|
42
42
|
throw new Error(`Plugin "${this.constructor.name}" tried to use file as component which does not exist at "${absSrcPath}"`);
|
|
43
43
|
}
|
package/dist/basePlugin.js
CHANGED
|
@@ -9,6 +9,7 @@ export default class AdminForthPlugin {
|
|
|
9
9
|
// set up plugin here
|
|
10
10
|
this.pluginDir = currentFileDir(metaUrl);
|
|
11
11
|
this.pluginInstanceId = uuidv4();
|
|
12
|
+
this.customFolderPath = path.join(this.pluginDir, this.customFolderName);
|
|
12
13
|
}
|
|
13
14
|
setupEndpoints(server) {
|
|
14
15
|
}
|
|
@@ -18,12 +19,11 @@ export default class AdminForthPlugin {
|
|
|
18
19
|
componentPath(componentFile) {
|
|
19
20
|
const key = `@@/plugins/${this.constructor.name}/${componentFile}`;
|
|
20
21
|
const componentName = getComponentNameFromPath(key);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.adminforth.codeInjector.srcFoldersToSync[absSrcFolder] = `./plugins/${this.constructor.name}/`;
|
|
22
|
+
if (!this.adminforth.codeInjector.srcFoldersToSync[this.customFolderPath]) {
|
|
23
|
+
this.adminforth.codeInjector.srcFoldersToSync[this.customFolderPath] = `./plugins/${this.constructor.name}/`;
|
|
24
24
|
}
|
|
25
25
|
if (!this.adminforth.codeInjector.allComponentNames[key]) {
|
|
26
|
-
const absSrcPath = path.join(
|
|
26
|
+
const absSrcPath = path.join(this.customFolderPath, componentFile);
|
|
27
27
|
if (!fs.existsSync(absSrcPath)) {
|
|
28
28
|
throw new Error(`Plugin "${this.constructor.name}" tried to use file as component which does not exist at "${absSrcPath}"`);
|
|
29
29
|
}
|
package/dist/index.js
CHANGED
|
@@ -49,6 +49,7 @@ class AdminForth {
|
|
|
49
49
|
console.log(`🚀 AdminForth v${ADMINFORTH_VERSION} starting up`);
|
|
50
50
|
}
|
|
51
51
|
activatePlugins() {
|
|
52
|
+
process.env.HEAVY_DEBUG && console.log('🔌🔌🔌 Activating plugins');
|
|
52
53
|
for (let resource of this.config.resources) {
|
|
53
54
|
for (let pluginInstance of resource.plugins || []) {
|
|
54
55
|
pluginInstance.modifyResourceConfig(this, resource);
|
|
@@ -87,6 +87,45 @@ class CodeInjector {
|
|
|
87
87
|
}
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
|
+
packagesFromNpm(dir) {
|
|
91
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
92
|
+
const usersPackagePath = path.join(dir, 'package.json');
|
|
93
|
+
let packageContent = null;
|
|
94
|
+
let lockHash = '';
|
|
95
|
+
let packages = [];
|
|
96
|
+
try {
|
|
97
|
+
packageContent = JSON.parse(yield fs.promises.readFile(usersPackagePath, 'utf-8'));
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
// user package.json does not exist, user does not have custom components
|
|
101
|
+
}
|
|
102
|
+
if (packageContent) {
|
|
103
|
+
const lockPath = path.join(dir, 'package-lock.json');
|
|
104
|
+
let lock = null;
|
|
105
|
+
try {
|
|
106
|
+
lock = JSON.parse(yield fs.promises.readFile(lockPath, 'utf-8'));
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
throw new Error(`Custom package-lock.json does not exist in ${dir}, but package.json does.
|
|
110
|
+
We can't determine version of packages without package-lock.json. Please run npm install in ${dir}`);
|
|
111
|
+
}
|
|
112
|
+
lockHash = hashify(lock);
|
|
113
|
+
packages = [
|
|
114
|
+
...Object.keys(packageContent.dependencies || []),
|
|
115
|
+
...Object.keys(packageContent.devDependencies || [])
|
|
116
|
+
].reduce((acc, packageName) => {
|
|
117
|
+
const pack = lock.packages[`node_modules/${packageName}`];
|
|
118
|
+
if (!pack) {
|
|
119
|
+
throw new Error(`Package ${packageName} is not in package-lock.json but is in package.json. Please run 'npm install' in ${dir}`);
|
|
120
|
+
}
|
|
121
|
+
const version = pack.version;
|
|
122
|
+
acc.push(`${packageName}@${version}`);
|
|
123
|
+
return acc;
|
|
124
|
+
}, []);
|
|
125
|
+
}
|
|
126
|
+
return [lockHash, packages];
|
|
127
|
+
});
|
|
128
|
+
}
|
|
90
129
|
prepareSources(_a) {
|
|
91
130
|
return __awaiter(this, arguments, void 0, function* ({ filesUpdated }) {
|
|
92
131
|
var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
@@ -358,30 +397,26 @@ class CodeInjector {
|
|
|
358
397
|
const spaLockHash = hashify(spaPackageLock);
|
|
359
398
|
/* customPackageLock */
|
|
360
399
|
let usersLockHash = '';
|
|
361
|
-
let
|
|
362
|
-
let usersPackage = null;
|
|
400
|
+
let usersPackages = [];
|
|
363
401
|
if ((_m = this.adminforth.config.customization) === null || _m === void 0 ? void 0 : _m.customComponentsDir) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
catch (e) {
|
|
377
|
-
throw new Error(`Custom package-lock.json does not exist in ${this.adminforth.config.customization.customComponentsDir}, but package.json does.
|
|
378
|
-
We can't determine version of packages without package-lock.json. Please run npm install in ${this.adminforth.config.customization.customComponentsDir}`);
|
|
379
|
-
}
|
|
380
|
-
usersLockHash = hashify(usersLock);
|
|
402
|
+
[usersLockHash, usersPackages] = yield this.packagesFromNpm(this.adminforth.config.customization.customComponentsDir);
|
|
403
|
+
}
|
|
404
|
+
const pluginPackages = [];
|
|
405
|
+
// for every installed plugin generate packages
|
|
406
|
+
for (const plugin of this.adminforth.activatedPlugins) {
|
|
407
|
+
const [lockHash, packages] = yield this.packagesFromNpm(plugin.customFolderPath);
|
|
408
|
+
if (packages.length) {
|
|
409
|
+
pluginPackages.push({
|
|
410
|
+
pluginName: plugin.constructor.name,
|
|
411
|
+
lockHash,
|
|
412
|
+
packages,
|
|
413
|
+
});
|
|
381
414
|
}
|
|
382
415
|
}
|
|
416
|
+
// form string "pluginName:lockHash::pLugin2Name:lockHash"
|
|
417
|
+
const pluginsLockHash = pluginPackages.map(({ pluginName, lockHash }) => `${pluginName}>${lockHash}`).join('::');
|
|
383
418
|
const iconPackagesNamesHash = hashify(iconPackageNames);
|
|
384
|
-
const fullHash =
|
|
419
|
+
const fullHash = `spa>${spaLockHash}::icons>${iconPackagesNamesHash}::user/custom>${usersLockHash}::${pluginsLockHash}`;
|
|
385
420
|
const hashPath = path.join(CodeInjector.SPA_TMP_PATH, 'node_modules', '.adminforth_hash');
|
|
386
421
|
try {
|
|
387
422
|
const existingHash = yield fs.promises.readFile(hashPath, 'utf-8');
|
|
@@ -398,19 +433,12 @@ class CodeInjector {
|
|
|
398
433
|
process.env.HEAVY_DEBUG && console.log('🪲Hash file does not exist, proceeding with npm ci/install');
|
|
399
434
|
}
|
|
400
435
|
yield this.runNpmShell({ command: 'ci', cwd: CodeInjector.SPA_TMP_PATH });
|
|
401
|
-
let customPackgeNames = [];
|
|
402
|
-
if (usersPackage && usersLock) {
|
|
403
|
-
customPackgeNames = [
|
|
404
|
-
...Object.keys(usersPackage.dependencies || []),
|
|
405
|
-
...Object.keys(usersPackage.devDependencies || [])
|
|
406
|
-
].reduce((acc, packageName) => {
|
|
407
|
-
const version = usersLock.packages[`node_modules/${packageName}`].version;
|
|
408
|
-
acc.push(`${packageName}@${version}`);
|
|
409
|
-
return acc;
|
|
410
|
-
}, []);
|
|
411
|
-
}
|
|
412
436
|
if (iconPackageNames.length) {
|
|
413
|
-
const npmInstallCommand = `install ${[
|
|
437
|
+
const npmInstallCommand = `install ${[
|
|
438
|
+
...iconPackageNames,
|
|
439
|
+
...usersPackages,
|
|
440
|
+
...pluginPackages.map(({ packages }) => packages)
|
|
441
|
+
].join(' ')}`;
|
|
414
442
|
yield this.runNpmShell({ command: npmInstallCommand, cwd: CodeInjector.SPA_TMP_PATH });
|
|
415
443
|
}
|
|
416
444
|
yield fs.promises.writeFile(hashPath, fullHash);
|
|
@@ -466,6 +494,9 @@ class CodeInjector {
|
|
|
466
494
|
const directories = [];
|
|
467
495
|
const files = [];
|
|
468
496
|
const collectDirectories = (dir) => __awaiter(this, void 0, void 0, function* () {
|
|
497
|
+
if (['node_modules', 'dist'].includes(path.basename(dir))) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
469
500
|
directories.push(dir);
|
|
470
501
|
const filesAndDirs = yield fs.promises.readdir(dir, { withFileTypes: true });
|
|
471
502
|
yield Promise.all(filesAndDirs.map((file) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -134,7 +134,7 @@ export default class ConfigValidator {
|
|
|
134
134
|
}
|
|
135
135
|
if (this.config.resources) {
|
|
136
136
|
this.config.resources.forEach((res) => {
|
|
137
|
-
var _a
|
|
137
|
+
var _a;
|
|
138
138
|
if (!res.table) {
|
|
139
139
|
errors.push(`Resource "${res.dataSource}" is missing table`);
|
|
140
140
|
}
|
|
@@ -240,12 +240,13 @@ export default class ConfigValidator {
|
|
|
240
240
|
errors.push(`Resource "${res.resourceId}" bulkActions must be an array`);
|
|
241
241
|
bulkActions = [];
|
|
242
242
|
}
|
|
243
|
-
if (
|
|
243
|
+
if (!bulkActions.find((action) => action.label === 'Delete checked')) {
|
|
244
244
|
bulkActions.push({
|
|
245
245
|
label: `Delete checked`,
|
|
246
246
|
state: 'danger',
|
|
247
247
|
icon: 'flowbite:trash-bin-outline',
|
|
248
|
-
|
|
248
|
+
allowed: (_b) => __awaiter(this, [_b], void 0, function* ({ resource, adminUser, allowedActions }) { return allowedActions.delete; }),
|
|
249
|
+
action: (_c) => __awaiter(this, [_c], void 0, function* ({ selectedIds }) {
|
|
249
250
|
const connector = this.adminforth.connectors[res.dataSource];
|
|
250
251
|
yield Promise.all(selectedIds.map((recordId) => __awaiter(this, void 0, void 0, function* () {
|
|
251
252
|
yield connector.deleteRecord({ resource: res, recordId });
|
package/dist/modules/restApi.js
CHANGED
|
@@ -242,9 +242,19 @@ export default class AdminForthRestAPI {
|
|
|
242
242
|
return { error: `Resource ${resourceId} not found` };
|
|
243
243
|
}
|
|
244
244
|
const { allowedActions } = yield interpretResource(adminUser, resource, {}, ActionCheckSource.DisplayButtons);
|
|
245
|
+
const allowedBulkActions = [];
|
|
246
|
+
yield Promise.all(resource.options.bulkActions.map((action) => __awaiter(this, void 0, void 0, function* () {
|
|
247
|
+
if (action.allowed) {
|
|
248
|
+
const res = yield action.allowed({ adminUser, resource, allowedActions });
|
|
249
|
+
console.log('🪲🪲🪲🪲checking for allowedActions', allowedActions, 'res', res);
|
|
250
|
+
if (res) {
|
|
251
|
+
allowedBulkActions.push(action);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
})));
|
|
245
255
|
// exclude "plugins" key
|
|
246
256
|
return {
|
|
247
|
-
resource: Object.assign(Object.assign({}, resource), { plugins: undefined, options: Object.assign(Object.assign({}, resource.options), { allowedActions }) })
|
|
257
|
+
resource: Object.assign(Object.assign({}, resource), { plugins: undefined, options: Object.assign(Object.assign({}, resource.options), { bulkActions: allowedBulkActions, allowedActions }) })
|
|
248
258
|
};
|
|
249
259
|
}),
|
|
250
260
|
});
|
package/index.ts
CHANGED
|
@@ -77,6 +77,7 @@ class AdminForth implements IAdminForth {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
activatePlugins() {
|
|
80
|
+
process.env.HEAVY_DEBUG && console.log('🔌🔌🔌 Activating plugins');
|
|
80
81
|
for (let resource of this.config.resources) {
|
|
81
82
|
for (let pluginInstance of resource.plugins || []) {
|
|
82
83
|
pluginInstance.modifyResourceConfig(this, resource);
|
package/modules/codeInjector.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { ADMIN_FORTH_ABSOLUTE_PATH, getComponentNameFromPath, transformObject, d
|
|
|
11
11
|
import { styles } from './styles.js'
|
|
12
12
|
import { AdminForthComponentDeclaration, ICodeInjector } from '../types/AdminForthConfig.js';
|
|
13
13
|
import { StylesGenerator } from './styleGenerator.js';
|
|
14
|
+
import { string } from 'zod';
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
|
|
@@ -103,6 +104,47 @@ class CodeInjector implements ICodeInjector {
|
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
|
|
107
|
+
async packagesFromNpm(dir: string): Promise<[string, string[]]> {
|
|
108
|
+
const usersPackagePath = path.join(dir, 'package.json');
|
|
109
|
+
let packageContent: { dependencies: any, devDependencies: any } = null;
|
|
110
|
+
let lockHash: string = '';
|
|
111
|
+
let packages: string[] = [];
|
|
112
|
+
try {
|
|
113
|
+
packageContent = JSON.parse(await fs.promises.readFile(usersPackagePath, 'utf-8'));
|
|
114
|
+
} catch (e) {
|
|
115
|
+
// user package.json does not exist, user does not have custom components
|
|
116
|
+
}
|
|
117
|
+
if (packageContent) {
|
|
118
|
+
const lockPath = path.join(dir, 'package-lock.json');
|
|
119
|
+
let lock = null;
|
|
120
|
+
try {
|
|
121
|
+
lock = JSON.parse(await fs.promises.readFile(lockPath, 'utf-8'));
|
|
122
|
+
} catch (e) {
|
|
123
|
+
throw new Error(`Custom package-lock.json does not exist in ${dir}, but package.json does.
|
|
124
|
+
We can't determine version of packages without package-lock.json. Please run npm install in ${dir}`);
|
|
125
|
+
}
|
|
126
|
+
lockHash = hashify(lock);
|
|
127
|
+
|
|
128
|
+
packages = [
|
|
129
|
+
...Object.keys(packageContent.dependencies || []),
|
|
130
|
+
...Object.keys(packageContent.devDependencies || [])
|
|
131
|
+
].reduce(
|
|
132
|
+
(acc, packageName) => {
|
|
133
|
+
const pack = lock.packages[`node_modules/${packageName}`];
|
|
134
|
+
if (!pack) {
|
|
135
|
+
throw new Error(`Package ${packageName} is not in package-lock.json but is in package.json. Please run 'npm install' in ${dir}`);
|
|
136
|
+
}
|
|
137
|
+
const version = pack.version;
|
|
138
|
+
|
|
139
|
+
acc.push(`${packageName}@${version}`);
|
|
140
|
+
return acc;
|
|
141
|
+
}, []
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
}
|
|
145
|
+
return [lockHash, packages];
|
|
146
|
+
}
|
|
147
|
+
|
|
106
148
|
async prepareSources({ filesUpdated }: { filesUpdated?: string[] }) {
|
|
107
149
|
// check SPA_TMP_PATH exists and create if not
|
|
108
150
|
try {
|
|
@@ -251,8 +293,6 @@ class CodeInjector implements ICodeInjector {
|
|
|
251
293
|
}
|
|
252
294
|
}
|
|
253
295
|
|
|
254
|
-
|
|
255
|
-
|
|
256
296
|
//collect all 'icon' fields from resources bulkActions
|
|
257
297
|
this.adminforth.config.resources.forEach((resource) => {
|
|
258
298
|
if (resource.options?.bulkActions) {
|
|
@@ -409,32 +449,37 @@ class CodeInjector implements ICodeInjector {
|
|
|
409
449
|
const spaLockHash = hashify(spaPackageLock);
|
|
410
450
|
|
|
411
451
|
/* customPackageLock */
|
|
412
|
-
let usersLockHash = '';
|
|
413
|
-
let
|
|
414
|
-
|
|
452
|
+
let usersLockHash: string = '';
|
|
453
|
+
let usersPackages: string[] = [];
|
|
454
|
+
|
|
415
455
|
|
|
416
456
|
if (this.adminforth.config.customization?.customComponentsDir) {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
457
|
+
[usersLockHash, usersPackages] = await this.packagesFromNpm(this.adminforth.config.customization.customComponentsDir);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const pluginPackages: {
|
|
461
|
+
pluginName: string,
|
|
462
|
+
lockHash: string,
|
|
463
|
+
packages: string[],
|
|
464
|
+
}[] = [];
|
|
465
|
+
|
|
466
|
+
// for every installed plugin generate packages
|
|
467
|
+
for (const plugin of this.adminforth.activatedPlugins) {
|
|
468
|
+
const [lockHash, packages] = await this.packagesFromNpm(plugin.customFolderPath);
|
|
469
|
+
if (packages.length) {
|
|
470
|
+
pluginPackages.push({
|
|
471
|
+
pluginName: plugin.constructor.name,
|
|
472
|
+
lockHash,
|
|
473
|
+
packages,
|
|
474
|
+
});
|
|
432
475
|
}
|
|
433
476
|
}
|
|
477
|
+
// form string "pluginName:lockHash::pLugin2Name:lockHash"
|
|
478
|
+
const pluginsLockHash = pluginPackages.map(({ pluginName, lockHash }) => `${pluginName}>${lockHash}`).join('::');
|
|
434
479
|
|
|
435
480
|
const iconPackagesNamesHash = hashify(iconPackageNames);
|
|
436
481
|
|
|
437
|
-
const fullHash =
|
|
482
|
+
const fullHash = `spa>${spaLockHash}::icons>${iconPackagesNamesHash}::user/custom>${usersLockHash}::${pluginsLockHash}`;
|
|
438
483
|
const hashPath = path.join(CodeInjector.SPA_TMP_PATH, 'node_modules', '.adminforth_hash');
|
|
439
484
|
|
|
440
485
|
try {
|
|
@@ -452,29 +497,20 @@ class CodeInjector implements ICodeInjector {
|
|
|
452
497
|
|
|
453
498
|
await this.runNpmShell({command: 'ci', cwd: CodeInjector.SPA_TMP_PATH});
|
|
454
499
|
|
|
455
|
-
|
|
456
|
-
if (usersPackage && usersLock) {
|
|
457
|
-
customPackgeNames = [
|
|
458
|
-
...Object.keys(usersPackage.dependencies || []),
|
|
459
|
-
...Object.keys(usersPackage.devDependencies || [])
|
|
460
|
-
].reduce(
|
|
461
|
-
(acc, packageName) => {
|
|
462
|
-
const version = usersLock.packages[`node_modules/${packageName}`].version;
|
|
463
|
-
acc.push(`${packageName}@${version}`);
|
|
464
|
-
return acc;
|
|
465
|
-
}, []
|
|
466
|
-
);
|
|
467
|
-
}
|
|
468
|
-
|
|
500
|
+
|
|
469
501
|
if (iconPackageNames.length) {
|
|
470
|
-
const npmInstallCommand = `install ${[
|
|
502
|
+
const npmInstallCommand = `install ${[
|
|
503
|
+
...iconPackageNames,
|
|
504
|
+
...usersPackages,
|
|
505
|
+
...pluginPackages.map(({ packages }) => packages)
|
|
506
|
+
].join(' ')}`;
|
|
471
507
|
await this.runNpmShell({command: npmInstallCommand, cwd: CodeInjector.SPA_TMP_PATH});
|
|
472
508
|
}
|
|
473
509
|
|
|
474
510
|
await fs.promises.writeFile(hashPath, fullHash);
|
|
475
511
|
}
|
|
476
512
|
|
|
477
|
-
async watchForReprepare({}) {
|
|
513
|
+
async watchForReprepare({}) {
|
|
478
514
|
const spaPath = path.join(ADMIN_FORTH_ABSOLUTE_PATH, 'spa');
|
|
479
515
|
// get list of all subdirectories in spa recursively
|
|
480
516
|
const directories = [];
|
|
@@ -528,6 +564,9 @@ async watchForReprepare({}) {
|
|
|
528
564
|
const directories = [];
|
|
529
565
|
const files = []
|
|
530
566
|
const collectDirectories = async (dir) => {
|
|
567
|
+
if (['node_modules', 'dist'].includes(path.basename(dir))) {
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
531
570
|
directories.push(dir);
|
|
532
571
|
|
|
533
572
|
const filesAndDirs = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
@@ -260,11 +260,12 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
260
260
|
bulkActions = [];
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
-
if (
|
|
263
|
+
if (!bulkActions.find((action) => action.label === 'Delete checked')) {
|
|
264
264
|
bulkActions.push({
|
|
265
265
|
label: `Delete checked`,
|
|
266
266
|
state: 'danger',
|
|
267
267
|
icon: 'flowbite:trash-bin-outline',
|
|
268
|
+
allowed: async ({ resource, adminUser, allowedActions }) => { return allowedActions.delete },
|
|
268
269
|
action: async ({ selectedIds }) => {
|
|
269
270
|
const connector = this.adminforth.connectors[res.dataSource];
|
|
270
271
|
await Promise.all(selectedIds.map(async (recordId) => {
|
package/modules/restApi.ts
CHANGED
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
BeforeSaveFunction,
|
|
13
13
|
AfterDataSourceResponseFunction,
|
|
14
14
|
BeforeDataSourceRequestFunction,
|
|
15
|
-
AfterSaveFunction
|
|
15
|
+
AfterSaveFunction,
|
|
16
|
+
AllowedActionsResolved
|
|
16
17
|
|
|
17
18
|
} from "../types/AdminForthConfig.js";
|
|
18
19
|
|
|
@@ -223,7 +224,7 @@ export default class AdminForthRestAPI {
|
|
|
223
224
|
},
|
|
224
225
|
});
|
|
225
226
|
|
|
226
|
-
async function interpretResource(adminUser: AdminUser, resource: AdminForthResource, meta: any, source: ActionCheckSource): Promise<{allowedActions:
|
|
227
|
+
async function interpretResource(adminUser: AdminUser, resource: AdminForthResource, meta: any, source: ActionCheckSource): Promise<{allowedActions: AllowedActionsResolved}> {
|
|
227
228
|
if (process.env.HEAVY_DEBUG) {
|
|
228
229
|
console.log('🪲Interpreting resource', resource.resourceId, source);
|
|
229
230
|
}
|
|
@@ -274,13 +275,28 @@ export default class AdminForthRestAPI {
|
|
|
274
275
|
|
|
275
276
|
const { allowedActions } = await interpretResource(adminUser, resource, {}, ActionCheckSource.DisplayButtons);
|
|
276
277
|
|
|
278
|
+
|
|
279
|
+
const allowedBulkActions = [];
|
|
280
|
+
await Promise.all(
|
|
281
|
+
resource.options.bulkActions.map(async (action) => {
|
|
282
|
+
if (action.allowed) {
|
|
283
|
+
const res = await action.allowed({ adminUser, resource, allowedActions });
|
|
284
|
+
console.log('🪲🪲🪲🪲checking for allowedActions', allowedActions, 'res', res);
|
|
285
|
+
if (res) {
|
|
286
|
+
allowedBulkActions.push(action);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
})
|
|
290
|
+
);
|
|
291
|
+
|
|
277
292
|
// exclude "plugins" key
|
|
278
293
|
return {
|
|
279
294
|
resource: {
|
|
280
|
-
...resource,
|
|
295
|
+
...resource,
|
|
281
296
|
plugins: undefined,
|
|
282
297
|
options: {
|
|
283
298
|
...resource.options,
|
|
299
|
+
bulkActions: allowedBulkActions,
|
|
284
300
|
allowedActions,
|
|
285
301
|
}
|
|
286
302
|
}
|
package/package.json
CHANGED
|
@@ -271,6 +271,7 @@ export interface IAdminForthPlugin {
|
|
|
271
271
|
pluginDir: string;
|
|
272
272
|
customFolderName: string;
|
|
273
273
|
pluginInstanceId: string;
|
|
274
|
+
customFolderPath: string;
|
|
274
275
|
|
|
275
276
|
/**
|
|
276
277
|
* AdminForth plugins concept is based on modification of full AdminForth configuration
|
|
@@ -728,9 +729,36 @@ export type AdminForthResource = {
|
|
|
728
729
|
afterSave?: BeforeSaveFunction | Array<BeforeSaveFunction>,
|
|
729
730
|
},
|
|
730
731
|
},
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* General options for resource.
|
|
735
|
+
*/
|
|
731
736
|
options?: {
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Default sort for list view.
|
|
740
|
+
* Example:
|
|
741
|
+
*
|
|
742
|
+
* ```ts
|
|
743
|
+
* defaultSort: {
|
|
744
|
+
* columnName: 'created_at',
|
|
745
|
+
* direction: AdminForthSortDirections.ASC,
|
|
746
|
+
* // or
|
|
747
|
+
* // direction: 'asc' // if you are using non-typescript
|
|
748
|
+
* }
|
|
749
|
+
* ```
|
|
750
|
+
*
|
|
751
|
+
*/
|
|
732
752
|
defaultSort?: {
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Column name which will be used to sort records.
|
|
756
|
+
*/
|
|
733
757
|
columnName: string,
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Direction of sorting. Can be 'asc' or 'desc'.
|
|
761
|
+
*/
|
|
734
762
|
direction: AdminForthSortDirections | string,
|
|
735
763
|
}
|
|
736
764
|
bulkActions?: Array<{
|
|
@@ -740,6 +768,11 @@ export type AdminForthResource = {
|
|
|
740
768
|
icon?: string,
|
|
741
769
|
action: Function,
|
|
742
770
|
confirm?: string,
|
|
771
|
+
allowed?: ({ resource, adminUser }: {
|
|
772
|
+
resource: AdminForthResource,
|
|
773
|
+
adminUser: AdminUser,
|
|
774
|
+
allowedActions: AllowedActionsResolved,
|
|
775
|
+
}) => Promise<boolean>,
|
|
743
776
|
}>,
|
|
744
777
|
allowedActions?: AllowedActions,
|
|
745
778
|
|
|
@@ -1111,6 +1144,10 @@ export type AllowedActions = {
|
|
|
1111
1144
|
all?: AllowedActionValue;
|
|
1112
1145
|
}
|
|
1113
1146
|
|
|
1147
|
+
export type AllowedActionsResolved = {
|
|
1148
|
+
[key in AllowedActionsEnum]?: boolean
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1114
1151
|
export type ValidationObject = {
|
|
1115
1152
|
/**
|
|
1116
1153
|
* Should be pure string (not RegExp string)
|