adminforth 1.2.11 → 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 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
- const absSrcFolder = path.join(this.pluginDir, this.customFolderName);
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(absSrcFolder, componentFile);
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
  }
@@ -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
- const absSrcFolder = path.join(this.pluginDir, this.customFolderName);
22
- if (!this.adminforth.codeInjector.srcFoldersToSync[absSrcFolder]) {
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(absSrcFolder, componentFile);
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,62 +397,51 @@ class CodeInjector {
358
397
  const spaLockHash = hashify(spaPackageLock);
359
398
  /* customPackageLock */
360
399
  let usersLockHash = '';
361
- let usersLock = null;
400
+ let usersPackages = [];
362
401
  if ((_m = this.adminforth.config.customization) === null || _m === void 0 ? void 0 : _m.customComponentsDir) {
363
- const usersPackagePath = path.join(this.adminforth.config.customization.customComponentsDir, 'package.json');
364
- let usersPackage = null;
365
- try {
366
- usersPackage = JSON.parse(yield fs.promises.readFile(usersPackagePath, 'utf-8'));
367
- }
368
- catch (e) {
369
- // user package.json does not exist, user does not have custom components
370
- }
371
- if (usersPackage) {
372
- const usersLockPath = path.join(this.adminforth.config.customization.customComponentsDir, 'package-lock.json');
373
- try {
374
- usersLock = JSON.parse(yield fs.promises.readFile(usersLockPath, 'utf-8'));
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);
381
- }
382
- const iconPackagesNamesHash = hashify(iconPackageNames);
383
- const fullHash = `${spaLockHash}::${iconPackagesNamesHash}::${usersLockHash}`;
384
- const hashPath = path.join(CodeInjector.SPA_TMP_PATH, 'node_modules', '.adminforth_hash');
385
- try {
386
- const existingHash = yield fs.promises.readFile(hashPath, 'utf-8');
387
- if (existingHash === fullHash) {
388
- process.env.HEAVY_DEBUG && console.log(`🪲Hashes match, skipping npm ci/install, from file: ${existingHash}, actual: ${fullHash}`);
389
- return;
390
- }
391
- else {
392
- process.env.HEAVY_DEBUG && console.log(`🪲 Hashes do not match: from file: ${existingHash} actual: ${fullHash}, proceeding with npm ci/install`);
393
- }
394
- }
395
- catch (e) {
396
- // ignore
397
- process.env.HEAVY_DEBUG && console.log('🪲Hash file does not exist, proceeding with npm ci/install');
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
+ });
398
414
  }
399
- yield this.runNpmShell({ command: 'ci', cwd: CodeInjector.SPA_TMP_PATH });
400
- let customPackgeNames = [];
401
- if (usersPackage && usersLock) {
402
- customPackgeNames = [
403
- ...Object.keys(usersPackage.dependencies || []),
404
- ...Object.keys(usersPackage.devDependencies || [])
405
- ].reduce((acc, packageName) => {
406
- const version = usersLock.packages[`node_modules/${packageName}`].version;
407
- acc.push(`${packageName}@${version}`);
408
- return acc;
409
- }, []);
415
+ }
416
+ // form string "pluginName:lockHash::pLugin2Name:lockHash"
417
+ const pluginsLockHash = pluginPackages.map(({ pluginName, lockHash }) => `${pluginName}>${lockHash}`).join('::');
418
+ const iconPackagesNamesHash = hashify(iconPackageNames);
419
+ const fullHash = `spa>${spaLockHash}::icons>${iconPackagesNamesHash}::user/custom>${usersLockHash}::${pluginsLockHash}`;
420
+ const hashPath = path.join(CodeInjector.SPA_TMP_PATH, 'node_modules', '.adminforth_hash');
421
+ try {
422
+ const existingHash = yield fs.promises.readFile(hashPath, 'utf-8');
423
+ if (existingHash === fullHash) {
424
+ process.env.HEAVY_DEBUG && console.log(`🪲Hashes match, skipping npm ci/install, from file: ${existingHash}, actual: ${fullHash}`);
425
+ return;
410
426
  }
411
- if (iconPackageNames.length) {
412
- const npmInstallCommand = `install ${[...iconPackageNames, ...customPackgeNames].join(' ')}`;
413
- yield this.runNpmShell({ command: npmInstallCommand, cwd: CodeInjector.SPA_TMP_PATH });
427
+ else {
428
+ process.env.HEAVY_DEBUG && console.log(`🪲 Hashes do not match: from file: ${existingHash} actual: ${fullHash}, proceeding with npm ci/install`);
414
429
  }
415
- yield fs.promises.writeFile(hashPath, fullHash);
416
430
  }
431
+ catch (e) {
432
+ // ignore
433
+ process.env.HEAVY_DEBUG && console.log('🪲Hash file does not exist, proceeding with npm ci/install');
434
+ }
435
+ yield this.runNpmShell({ command: 'ci', cwd: CodeInjector.SPA_TMP_PATH });
436
+ if (iconPackageNames.length) {
437
+ const npmInstallCommand = `install ${[
438
+ ...iconPackageNames,
439
+ ...usersPackages,
440
+ ...pluginPackages.map(({ packages }) => packages)
441
+ ].join(' ')}`;
442
+ yield this.runNpmShell({ command: npmInstallCommand, cwd: CodeInjector.SPA_TMP_PATH });
443
+ }
444
+ yield fs.promises.writeFile(hashPath, fullHash);
417
445
  });
418
446
  }
419
447
  watchForReprepare(_a) {
@@ -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, _b, _c;
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 (((_c = (_b = res.options) === null || _b === void 0 ? void 0 : _b.allowedActions) === null || _c === void 0 ? void 0 : _c.delete) && !bulkActions.find((action) => action.label === 'Delete checked')) {
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
- action: (_d) => __awaiter(this, [_d], void 0, function* ({ selectedIds }) {
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 });
@@ -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);
@@ -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,30 +449,37 @@ class CodeInjector implements ICodeInjector {
409
449
  const spaLockHash = hashify(spaPackageLock);
410
450
 
411
451
  /* customPackageLock */
412
- let usersLockHash = '';
413
- let usersLock = null;
452
+ let usersLockHash: string = '';
453
+ let usersPackages: string[] = [];
454
+
455
+
414
456
  if (this.adminforth.config.customization?.customComponentsDir) {
415
- const usersPackagePath = path.join(this.adminforth.config.customization.customComponentsDir, 'package.json');
416
- let usersPackage = null;
417
- try {
418
- usersPackage = JSON.parse(await fs.promises.readFile(usersPackagePath, 'utf-8'));
419
- } catch (e) {
420
- // user package.json does not exist, user does not have custom components
421
- }
422
- if (usersPackage) {
423
- const usersLockPath = path.join(this.adminforth.config.customization.customComponentsDir, 'package-lock.json');
424
- try {
425
- usersLock = JSON.parse(await fs.promises.readFile(usersLockPath, 'utf-8'));
426
- } catch (e) {
427
- throw new Error(`Custom package-lock.json does not exist in ${this.adminforth.config.customization.customComponentsDir}, but package.json does.
428
- We can't determine version of packages without package-lock.json. Please run npm install in ${this.adminforth.config.customization.customComponentsDir}`);
429
- }
430
- usersLockHash = hashify(usersLock);
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
+ });
431
475
  }
476
+ }
477
+ // form string "pluginName:lockHash::pLugin2Name:lockHash"
478
+ const pluginsLockHash = pluginPackages.map(({ pluginName, lockHash }) => `${pluginName}>${lockHash}`).join('::');
432
479
 
433
480
  const iconPackagesNamesHash = hashify(iconPackageNames);
434
481
 
435
- const fullHash = `${spaLockHash}::${iconPackagesNamesHash}::${usersLockHash}`;
482
+ const fullHash = `spa>${spaLockHash}::icons>${iconPackagesNamesHash}::user/custom>${usersLockHash}::${pluginsLockHash}`;
436
483
  const hashPath = path.join(CodeInjector.SPA_TMP_PATH, 'node_modules', '.adminforth_hash');
437
484
 
438
485
  try {
@@ -450,31 +497,20 @@ class CodeInjector implements ICodeInjector {
450
497
 
451
498
  await this.runNpmShell({command: 'ci', cwd: CodeInjector.SPA_TMP_PATH});
452
499
 
453
- let customPackgeNames = [];
454
- if (usersPackage && usersLock) {
455
- customPackgeNames = [
456
- ...Object.keys(usersPackage.dependencies || []),
457
- ...Object.keys(usersPackage.devDependencies || [])
458
- ].reduce(
459
- (acc, packageName) => {
460
- const version = usersLock.packages[`node_modules/${packageName}`].version;
461
- acc.push(`${packageName}@${version}`);
462
- return acc;
463
- }, []
464
- );
465
- }
466
-
500
+
467
501
  if (iconPackageNames.length) {
468
- const npmInstallCommand = `install ${[...iconPackageNames, ...customPackgeNames].join(' ')}`;
502
+ const npmInstallCommand = `install ${[
503
+ ...iconPackageNames,
504
+ ...usersPackages,
505
+ ...pluginPackages.map(({ packages }) => packages)
506
+ ].join(' ')}`;
469
507
  await this.runNpmShell({command: npmInstallCommand, cwd: CodeInjector.SPA_TMP_PATH});
470
508
  }
471
509
 
472
510
  await fs.promises.writeFile(hashPath, fullHash);
473
-
474
511
  }
475
- }
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 (res.options?.allowedActions?.delete && !bulkActions.find((action) => action.label === 'Delete checked')) {
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) => {
@@ -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: 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.2.11",
3
+ "version": "1.2.13",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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)