You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					408 lines
				
				14 KiB
			
		
		
			
		
	
	
					408 lines
				
				14 KiB
			| 
											3 years ago
										 | "use strict"; | ||
|  | 
 | ||
|  | Object.defineProperty(exports, "__esModule", { | ||
|  |   value: true | ||
|  | }); | ||
|  | exports.registerAdapter = exports.defaultOptions = exports.default = exports.VERSION = void 0; | ||
|  | 
 | ||
|  | var _merge = _interopRequireDefault(require("lodash/merge")); | ||
|  | 
 | ||
|  | var path = _interopRequireWildcard(require("path")); | ||
|  | 
 | ||
|  | var fs = _interopRequireWildcard(require("fs")); | ||
|  | 
 | ||
|  | var _i18next = _interopRequireDefault(require("i18next")); | ||
|  | 
 | ||
|  | var _configurationError = _interopRequireDefault(require("./backend/utils/errors/configuration-error")); | ||
|  | 
 | ||
|  | var _resourcesFactory = _interopRequireDefault(require("./backend/utils/resources-factory/resources-factory")); | ||
|  | 
 | ||
|  | var _userComponentsBundler = _interopRequireDefault(require("./backend/bundler/user-components-bundler")); | ||
|  | 
 | ||
|  | var _constants = require("./constants"); | ||
|  | 
 | ||
|  | var _actions = require("./backend/actions"); | ||
|  | 
 | ||
|  | var _loginTemplate = _interopRequireDefault(require("./frontend/login-template")); | ||
|  | 
 | ||
|  | var _config = require("./locale/config"); | ||
|  | 
 | ||
|  | var _locale = require("./locale"); | ||
|  | 
 | ||
|  | var _translateFunctions = require("./utils/translate-functions.factory"); | ||
|  | 
 | ||
|  | var _fileResolver = require("./utils/file-resolver"); | ||
|  | 
 | ||
|  | var _utils = require("./backend/utils"); | ||
|  | 
 | ||
|  | var _componentLoader = require("./backend/utils/component-loader"); | ||
|  | 
 | ||
|  | function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } | ||
|  | 
 | ||
|  | function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } | ||
|  | 
 | ||
|  | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
|  | 
 | ||
|  | const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8')); | ||
|  | const VERSION = pkg.version; | ||
|  | exports.VERSION = VERSION; | ||
|  | const defaultOptions = { | ||
|  |   rootPath: _constants.DEFAULT_PATHS.rootPath, | ||
|  |   logoutPath: _constants.DEFAULT_PATHS.logoutPath, | ||
|  |   loginPath: _constants.DEFAULT_PATHS.loginPath, | ||
|  |   databases: [], | ||
|  |   resources: [], | ||
|  |   dashboard: {}, | ||
|  |   pages: {}, | ||
|  |   bundler: {} | ||
|  | }; | ||
|  | exports.defaultOptions = defaultOptions; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Main class for AdminJS extension. It takes {@link AdminJSOptions} as a | ||
|  |  * parameter and creates an admin instance. | ||
|  |  * | ||
|  |  * Its main responsibility is to fetch all the resources and/or databases given by a | ||
|  |  * user. Its instance is a currier - injected in all other classes. | ||
|  |  * | ||
|  |  * @example | ||
|  |  * const AdminJS = require('adminjs') | ||
|  |  * const admin = new AdminJS(AdminJSOptions) | ||
|  |  */ | ||
|  | class AdminJS { | ||
|  |   /** | ||
|  |    * List of all default actions. If you want to change the behavior for all actions like: | ||
|  |    * _list_, _edit_, _show_, _delete_ and _bulkDelete_ you can do this here. | ||
|  |    * | ||
|  |    * @example <caption>Modifying accessibility rules for all show actions</caption> | ||
|  |    * const { ACTIONS } = require('adminjs') | ||
|  |    * ACTIONS.show.isAccessible = () => {...} | ||
|  |    */ | ||
|  | 
 | ||
|  |   /** | ||
|  |    * AdminJS version | ||
|  |    */ | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Login override | ||
|  |    */ | ||
|  | 
 | ||
|  |   /** | ||
|  |    * @param   {AdminJSOptions} options      Options passed to AdminJS | ||
|  |    */ | ||
|  |   constructor(options = {}) { | ||
|  |     /** | ||
|  |      * @type {BaseResource[]} | ||
|  |      * @description List of all resources available for the AdminJS. | ||
|  |      * They can be fetched with the {@link AdminJS#findResource} method | ||
|  |      */ | ||
|  |     this.resources = []; | ||
|  |     /** | ||
|  |      * @type {AdminJSOptions} | ||
|  |      * @description Options given by a user | ||
|  |      */ | ||
|  | 
 | ||
|  |     this.options = (0, _merge.default)({}, defaultOptions, options); | ||
|  |     this.resolveBabelConfigPath(); | ||
|  |     this.initI18n(); | ||
|  |     const { | ||
|  |       databases, | ||
|  |       resources | ||
|  |     } = this.options; | ||
|  |     this.componentLoader = options.componentLoader ?? new _componentLoader.ComponentLoader(); | ||
|  |     const resourcesFactory = new _resourcesFactory.default(this, global.RegisteredAdapters || []); | ||
|  |     this.resources = resourcesFactory.buildResources({ | ||
|  |       databases, | ||
|  |       resources | ||
|  |     }); | ||
|  |   } | ||
|  | 
 | ||
|  |   initI18n() { | ||
|  |     var _this$options$locale, _locales$language, _this$options$locale2; | ||
|  | 
 | ||
|  |     const language = ((_this$options$locale = this.options.locale) === null || _this$options$locale === void 0 ? void 0 : _this$options$locale.language) || _locale.locales.en.language; | ||
|  |     const defaultTranslations = ((_locales$language = _locale.locales[language]) === null || _locales$language === void 0 ? void 0 : _locales$language.translations) || _locale.locales.en.translations; | ||
|  |     this.locale = { | ||
|  |       translations: (0, _config.combineTranslations)(defaultTranslations, (_this$options$locale2 = this.options.locale) === null || _this$options$locale2 === void 0 ? void 0 : _this$options$locale2.translations), | ||
|  |       language | ||
|  |     }; | ||
|  | 
 | ||
|  |     if (_i18next.default.isInitialized) { | ||
|  |       _i18next.default.addResourceBundle(this.locale.language, 'translation', this.locale.translations); | ||
|  |     } else { | ||
|  |       _i18next.default.init({ | ||
|  |         lng: this.locale.language, | ||
|  |         initImmediate: false, | ||
|  |         // loads translations immediately
 | ||
|  |         resources: { | ||
|  |           [this.locale.language]: { | ||
|  |             translation: this.locale.translations | ||
|  |           } | ||
|  |         } | ||
|  |       }); | ||
|  |     } // mixin translate functions to AdminJS instance so users will be able to
 | ||
|  |     // call AdminJS.translateMessage(...)
 | ||
|  | 
 | ||
|  | 
 | ||
|  |     this.translateFunctions = (0, _translateFunctions.createFunctions)(_i18next.default); | ||
|  |     Object.getOwnPropertyNames(this.translateFunctions).forEach(translateFunctionName => { | ||
|  |       this[translateFunctionName] = this.translateFunctions[translateFunctionName]; | ||
|  |     }); | ||
|  |   } | ||
|  |   /** | ||
|  |    * Registers various database adapters written for AdminJS. | ||
|  |    * | ||
|  |    * @example | ||
|  |    * const AdminJS = require('adminjs') | ||
|  |    * const MongooseAdapter = require('adminjs-mongoose') | ||
|  |    * AdminJS.registerAdapter(MongooseAdapter) | ||
|  |    * | ||
|  |    * @param  {Object}       options | ||
|  |    * @param  {typeof BaseDatabase} options.Database subclass of {@link BaseDatabase} | ||
|  |    * @param  {typeof BaseResource} options.Resource subclass of {@link BaseResource} | ||
|  |    */ | ||
|  | 
 | ||
|  | 
 | ||
|  |   static registerAdapter({ | ||
|  |     Database, | ||
|  |     Resource | ||
|  |   }) { | ||
|  |     if (!Database || !Resource) { | ||
|  |       throw new Error('Adapter has to have both Database and Resource'); | ||
|  |     } // TODO: check if this is actually valid because "isAdapterFor" is always defined.
 | ||
|  |     // checking if both Database and Resource have at least isAdapterFor method
 | ||
|  |     // @ts-ignore
 | ||
|  | 
 | ||
|  | 
 | ||
|  |     if (Database.isAdapterFor && Resource.isAdapterFor) { | ||
|  |       global.RegisteredAdapters = global.RegisteredAdapters || []; | ||
|  |       global.RegisteredAdapters.push({ | ||
|  |         Database, | ||
|  |         Resource | ||
|  |       }); | ||
|  |     } else { | ||
|  |       throw new Error('Adapter elements have to be a subclass of AdminJS.BaseResource and AdminJS.BaseDatabase'); | ||
|  |     } | ||
|  |   } | ||
|  |   /** | ||
|  |    * Initializes AdminJS instance in production. This function should be called by | ||
|  |    * all external plugins. | ||
|  |    */ | ||
|  | 
 | ||
|  | 
 | ||
|  |   async initialize() { | ||
|  |     if (process.env.NODE_ENV === 'production' && !(process.env.ADMIN_JS_SKIP_BUNDLE === 'true')) { | ||
|  |       // eslint-disable-next-line no-console
 | ||
|  |       console.log('AdminJS: bundling user components...'); | ||
|  |       await (0, _userComponentsBundler.default)(this, { | ||
|  |         write: true | ||
|  |       }); | ||
|  |     } | ||
|  |   } | ||
|  |   /** | ||
|  |    * Watches for local changes in files imported via {@link ComponentLoader}. | ||
|  |    * It doesn't work on production environment. | ||
|  |    * | ||
|  |    * @return  {Promise<never>} | ||
|  |    */ | ||
|  | 
 | ||
|  | 
 | ||
|  |   async watch() { | ||
|  |     if (process.env.NODE_ENV !== 'production') { | ||
|  |       return (0, _userComponentsBundler.default)(this, { | ||
|  |         write: true, | ||
|  |         watch: true | ||
|  |       }); | ||
|  |     } | ||
|  | 
 | ||
|  |     return undefined; | ||
|  |   } | ||
|  |   /** | ||
|  |    * Allows you to override the default login view by providing your React components | ||
|  |    * and custom props. | ||
|  |    * | ||
|  |    * @param  {Object} options | ||
|  |    * @param  {String} options.component       Custom React component | ||
|  |    * @param  {String} [options.props]         Props to be passed to React component | ||
|  |    * @return {Promise<void>} | ||
|  |    */ | ||
|  | 
 | ||
|  | 
 | ||
|  |   overrideLogin({ | ||
|  |     component, | ||
|  |     props | ||
|  |   }) { | ||
|  |     this.loginOverride = { | ||
|  |       component, | ||
|  |       props: props ?? {} | ||
|  |     }; | ||
|  |   } | ||
|  |   /** | ||
|  |    * Renders an entire login page with email and password fields | ||
|  |    * using {@link Renderer}. | ||
|  |    * | ||
|  |    * Used by external plugins | ||
|  |    * | ||
|  |    * @param  {Object} options | ||
|  |    * @param  {String} options.action          Login form action url - it could be | ||
|  |    *                                          '/admin/login' | ||
|  |    * @param  {String} [options.errorMessage]  Optional error message. When set, | ||
|  |    *                                          renderer will print this message in | ||
|  |    *                                          the form | ||
|  |    * @return {Promise<string>}                HTML of the rendered page | ||
|  |    */ | ||
|  | 
 | ||
|  | 
 | ||
|  |   async renderLogin({ | ||
|  |     action, | ||
|  |     errorMessage | ||
|  |   }) { | ||
|  |     if (this.loginOverride) { | ||
|  |       const { | ||
|  |         component, | ||
|  |         props = {} | ||
|  |       } = this.loginOverride; | ||
|  |       const mergedProps = { | ||
|  |         action, | ||
|  |         message: errorMessage, | ||
|  |         ...props | ||
|  |       }; | ||
|  |       return (0, _utils.getComponentHtml)(component, mergedProps, this); | ||
|  |     } | ||
|  | 
 | ||
|  |     return (0, _loginTemplate.default)(this, { | ||
|  |       action, | ||
|  |       errorMessage | ||
|  |     }); | ||
|  |   } | ||
|  |   /** | ||
|  |    * Returns resource base on its ID | ||
|  |    * | ||
|  |    * @example | ||
|  |    * const User = admin.findResource('users') | ||
|  |    * await User.findOne(userId) | ||
|  |    * | ||
|  |    * @param  {String} resourceId    ID of a resource defined under {@link BaseResource#id} | ||
|  |    * @return {BaseResource}         found resource | ||
|  |    * @throws {Error}                When resource with given id cannot be found | ||
|  |    */ | ||
|  | 
 | ||
|  | 
 | ||
|  |   findResource(resourceId) { | ||
|  |     const resource = this.resources.find(m => { | ||
|  |       var _m$_decorated; | ||
|  | 
 | ||
|  |       return ((_m$_decorated = m._decorated) === null || _m$_decorated === void 0 ? void 0 : _m$_decorated.id()) === resourceId; | ||
|  |     }); | ||
|  | 
 | ||
|  |     if (!resource) { | ||
|  |       throw new Error([`There are no resources with given id: "${resourceId}"`, 'This is the list of all registered resources you can use:', this.resources.map(r => { | ||
|  |         var _r$_decorated; | ||
|  | 
 | ||
|  |         return ((_r$_decorated = r._decorated) === null || _r$_decorated === void 0 ? void 0 : _r$_decorated.id()) || r.id(); | ||
|  |       }).join(', ')].join('\n')); | ||
|  |     } | ||
|  | 
 | ||
|  |     return resource; | ||
|  |   } | ||
|  |   /** | ||
|  |    * Resolve babel config file path, | ||
|  |    * and load configuration to this.options.bundler.babelConfig. | ||
|  |    */ | ||
|  | 
 | ||
|  | 
 | ||
|  |   resolveBabelConfigPath() { | ||
|  |     var _this$options, _this$options$bundler, _this$options2, _this$options2$bundle; | ||
|  | 
 | ||
|  |     if (typeof ((_this$options = this.options) === null || _this$options === void 0 ? void 0 : (_this$options$bundler = _this$options.bundler) === null || _this$options$bundler === void 0 ? void 0 : _this$options$bundler.babelConfig) !== 'string') { | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     let filePath = ''; | ||
|  |     let config = (_this$options2 = this.options) === null || _this$options2 === void 0 ? void 0 : (_this$options2$bundle = _this$options2.bundler) === null || _this$options2$bundle === void 0 ? void 0 : _this$options2$bundle.babelConfig; | ||
|  | 
 | ||
|  |     if (config[0] === '/') { | ||
|  |       filePath = config; | ||
|  |     } else { | ||
|  |       filePath = (0, _fileResolver.relativeFilePathResolver)(config, /new AdminJS/); | ||
|  |     } | ||
|  | 
 | ||
|  |     if (!fs.existsSync(filePath)) { | ||
|  |       throw new _configurationError.default(`Given babel config "${filePath}", doesn't exist.`, 'AdminJS.html'); | ||
|  |     } | ||
|  | 
 | ||
|  |     if (path.extname(filePath) === '.js') { | ||
|  |       // eslint-disable-next-line
 | ||
|  |       const configModule = require(filePath); | ||
|  | 
 | ||
|  |       config = configModule && configModule.__esModule ? configModule.default || undefined : configModule; | ||
|  | 
 | ||
|  |       if (!config || typeof config !== 'object' || Array.isArray(config)) { | ||
|  |         throw new Error(`${filePath}: Configuration should be an exported JavaScript object.`); | ||
|  |       } | ||
|  |     } else { | ||
|  |       try { | ||
|  |         config = JSON.parse(fs.readFileSync(filePath, 'utf8')); | ||
|  |       } catch (err) { | ||
|  |         throw new Error(`${filePath}: Error while parsing config - ${err.message}`); | ||
|  |       } | ||
|  | 
 | ||
|  |       if (!config) throw new Error(`${filePath}: No config detected`); | ||
|  | 
 | ||
|  |       if (typeof config !== 'object') { | ||
|  |         throw new Error(`${filePath}: Config returned typeof ${typeof config}`); | ||
|  |       } | ||
|  | 
 | ||
|  |       if (Array.isArray(config)) { | ||
|  |         throw new Error(`${filePath}: Expected config object but found array`); | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     this.options.bundler.babelConfig = config; | ||
|  |   } | ||
|  |   /** | ||
|  |    * Requires given `.jsx/.tsx` file, that it can be bundled to the frontend. | ||
|  |    * It will be available under AdminJS.UserComponents[componentId]. | ||
|  |    * | ||
|  |    * @param   {String}  src  Path to a file containing react component. | ||
|  |    * | ||
|  |    * @param  {OverridableComponent}  [componentName] - name of the component which you want | ||
|  |    *                                  to override | ||
|  |    * @returns {String}                componentId - uniq id of a component | ||
|  |    * | ||
|  |    * @example <caption>Passing custom components in AdminJS options</caption> | ||
|  |    * const adminJsOptions = { | ||
|  |    *   dashboard: { | ||
|  |    *     component: AdminJS.bundle('./path/to/component'), | ||
|  |    *   } | ||
|  |    * } | ||
|  |    * @example <caption>Overriding AdminJS core components</caption> | ||
|  |    * // somewhere in the code
 | ||
|  |    * AdminJS.bundle('./path/to/new-sidebar/component', 'SidebarFooter') | ||
|  |    * | ||
|  |    * @deprecated since version 6.5.0, use {@link ComponentLoader} instead | ||
|  |    */ | ||
|  | 
 | ||
|  | 
 | ||
|  |   static bundle(src, componentName) { | ||
|  |     // eslint-disable-next-line no-plusplus
 | ||
|  |     const name = componentName ?? `Component${this.__unsafe_componentIndex++}`; | ||
|  | 
 | ||
|  |     this.__unsafe_staticComponentLoader.__unsafe_addWithoutChecks(name, src, 'bundle'); | ||
|  | 
 | ||
|  |     return name; | ||
|  |   } | ||
|  | 
 | ||
|  |   static __unsafe_componentIndex = 0; | ||
|  |   static __unsafe_staticComponentLoader = new _componentLoader.ComponentLoader(); | ||
|  | } | ||
|  | 
 | ||
|  | AdminJS.VERSION = VERSION; | ||
|  | AdminJS.ACTIONS = _actions.ACTIONS; // eslint-disable-next-line @typescript-eslint/no-empty-interface
 | ||
|  | 
 | ||
|  | const { | ||
|  |   registerAdapter | ||
|  | } = AdminJS; | ||
|  | exports.registerAdapter = registerAdapter; | ||
|  | var _default = AdminJS; | ||
|  | exports.default = _default; |