UNPKG

lynx-admin-ui

Version:

this module adds annotations for entities in order to automatically generate an Administration UI

593 lines (592 loc) 29.5 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseUIController = void 0; var controller_1 = require("./controller"); var _1 = require("."); var datagrid_1 = require("lynx-datagrid/datagrid"); var sprintf_js_1 = require("sprintf-js"); /** * Basic controller that generates all the UI that contains the logic to perform the * different operations. */ var BaseUIController = /** @class */ (function (_super) { __extends(BaseUIController, _super); function BaseUIController() { return _super !== null && _super.apply(this, arguments) || this; } /** * Generate the list of entities * @param req The current Lynx request */ BaseUIController.prototype.generateEntitiesIndex = function (req) { return __awaiter(this, void 0, void 0, function () { var ctx; return __generator(this, function (_a) { ctx = { parentTemplate: _1.default.indexParentTemplatePath, entities: this.entitiesList(), }; return [2 /*return*/, this.render(_1.default.indexTemplatePath, req, ctx)]; }); }); }; /** * Perform ajax request for selection values * @param entityName the name of the entity * @param id the id of the editing entity * @param field the name of the field that is edited * @param req the current Lynx request */ BaseUIController.prototype.performAjaxRequest = function (entityName, id, field, req) { return __awaiter(this, void 0, void 0, function () { var entityData, metadata, _field, tmp, e_1, response; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.retrieveEntity(entityName, id)]; case 1: entityData = _a.sent(); if (!entityData) { throw this.error(404, 'not found'); } metadata = this.retrieveMetadata(entityName); return [4 /*yield*/, this.generateContextMetadata(metadata, req)]; case 2: metadata = _a.sent(); _field = metadata.fields[field]; if (!_field) { throw this.error(500, 'field not found'); } if (!_field.searchRequest) { throw this.error(500, "You need to implement the 'searchRequest' parameter for the field '" + field + "'"); } if (!req.query.selection) return [3 /*break*/, 7]; tmp = entityData[field]; if (!req.query.defaultId) return [3 /*break*/, 6]; _a.label = 3; case 3: _a.trys.push([3, 5, , 6]); return [4 /*yield*/, this.retrieveEntity(_field.selfType, req.query.defaultId)]; case 4: tmp = (_a.sent()); return [3 /*break*/, 6]; case 5: e_1 = _a.sent(); return [3 /*break*/, 6]; case 6: return [2 /*return*/, { data: [{ id: tmp.getId(), text: tmp.getLabel() }], pagination: false, }]; case 7: return [4 /*yield*/, _field.searchRequest(req, entityData, req.query.term, Number(req.query.page))]; case 8: response = _a.sent(); return [2 /*return*/, { data: response[0].map(function (b) { return { id: b.key, text: b.value }; }), pagination: response[1], }]; } }); }); }; /** * Retrieve entity data to dynamically display results * @param entityName the name of the entity * @param id the id of the editing entity * @param field the name of the field that is edited * @param selection the current selection for the field * @param req the current Lynx request */ BaseUIController.prototype.performAjaxDetails = function (entityName, id, field, selection, req) { return __awaiter(this, void 0, void 0, function () { var entityData, metadata, _field, _meta, data, cleaned, key; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.retrieveEntity(entityName, id)]; case 1: entityData = _a.sent(); if (!entityData) { throw this.error(404, 'not found'); } metadata = this.retrieveMetadata(entityName); return [4 /*yield*/, this.generateContextMetadata(metadata, req)]; case 2: metadata = _a.sent(); _field = metadata.fields[field]; if (!_field) { throw this.error(500, 'field not found'); } _meta = this.retrieveMetadata(_field.selfType); return [4 /*yield*/, this.generateContextMetadata(_meta, req)]; case 3: _meta = _a.sent(); return [4 /*yield*/, this.retrieveEntity(_field.selfType, selection)]; case 4: data = _a.sent(); if (!data) { throw this.error(404, 'not found'); } return [4 /*yield*/, this.cleanData(req, data, _meta, false)]; case 5: cleaned = _a.sent(); for (key in cleaned) { if (cleaned[key] instanceof datagrid_1.default) { this.logger.log('WARNING: currently not supporting Datagrid data with ExpandedAndSelection type...'); delete cleaned[key]; } } return [2 /*return*/, cleaned]; } }); }); }; /** * Delete the specified entity. * @param entityName The name of the entity class * @param id The id of the entity * @param req The current Lynx request */ BaseUIController.prototype.performEntityDelete = function (entityName, id, req) { return __awaiter(this, void 0, void 0, function () { var entityData, e_2, msg; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.retrieveEntity(entityName, id)]; case 1: entityData = _a.sent(); if (!entityData) { throw this.error(404, 'not found'); } _a.label = 2; case 2: _a.trys.push([2, 4, , 5]); return [4 /*yield*/, entityData.remove()]; case 3: _a.sent(); return [3 /*break*/, 5]; case 4: e_2 = _a.sent(); this.logger.error(e_2); msg = this.tr('admin-ui.unable-delete', req); this.addErrorMessage(sprintf_js_1.sprintf(msg, entityData.getLabel()), req); return [3 /*break*/, 5]; case 5: if (req.query.redirect) { return [2 /*return*/, this.redirect(req.query.redirect)]; } return [2 /*return*/, this.redirect('adminUI.list', { entityName: entityName })]; } }); }); }; /** * Delete the specified entities. * @param entityName The name of the entity class * @param req The current Lynx request, containing the query parameter ids, with an array of id */ BaseUIController.prototype.performEntityDeleteMultiple = function (entityName, req) { return __awaiter(this, void 0, void 0, function () { var ids, _i, ids_1, id, entityData, e_3, msg; return __generator(this, function (_a) { switch (_a.label) { case 0: ids = JSON.parse(req.query.ids); _i = 0, ids_1 = ids; _a.label = 1; case 1: if (!(_i < ids_1.length)) return [3 /*break*/, 7]; id = ids_1[_i]; return [4 /*yield*/, this.retrieveEntity(entityName, id)]; case 2: entityData = _a.sent(); if (!entityData) { throw this.error(404, 'not found'); } _a.label = 3; case 3: _a.trys.push([3, 5, , 6]); return [4 /*yield*/, entityData.remove()]; case 4: _a.sent(); return [3 /*break*/, 6]; case 5: e_3 = _a.sent(); this.logger.error(e_3); msg = this.tr('admin-ui.unable-delete', req); this.addErrorMessage(sprintf_js_1.sprintf(msg, entityData.getLabel()), req); return [3 /*break*/, 6]; case 6: _i++; return [3 /*break*/, 1]; case 7: if (req.query.redirect) { return [2 /*return*/, this.redirect(req.query.redirect)]; } return [2 /*return*/, this.redirect('adminUI.list', { entityName: entityName })]; } }); }); }; /** * Retrieve and display the list of a specified entity * @param entityName The name of the entity class * @param req The current Lynx request */ BaseUIController.prototype.retrieveEntityList = function (entityName, req) { return __awaiter(this, void 0, void 0, function () { var metadata, datagrid, fields, hasSmartSearchable, key, field, data, k, tmp, u, _i, tmp_1, t, ctx; return __generator(this, function (_a) { switch (_a.label) { case 0: metadata = this.retrieveMetadata(entityName); if (!metadata) { throw this.error(404, 'Not found'); } datagrid = new datagrid_1.default('', req); return [4 /*yield*/, this.retrieveList(req, entityName, datagrid)]; case 1: _a.sent(); return [4 /*yield*/, this.generateContextMetadata(metadata, req)]; case 2: metadata = _a.sent(); return [4 /*yield*/, this.generateContextFields(metadata, req, {})]; case 3: fields = _a.sent(); hasSmartSearchable = false; for (key in fields) { field = fields[key]; if (field.smartSearchable || field.searchable) { field.readOnly = false; field.required = false; } if (field.smartSearchable) { hasSmartSearchable = true; } } data = {}; for (k in req.query) { tmp = req.query[k]; if (tmp instanceof Array) { u = []; for (_i = 0, tmp_1 = tmp; _i < tmp_1.length; _i++) { t = tmp_1[_i]; try { u.push(parseInt(t)); } catch (e) { u.push(t); } } tmp = u; } data[k] = tmp; } ctx = { metadata: metadata, configuration: _1.default.configuration, parentTemplate: metadata.classParameters.listParentTemplate, gridData: datagrid, data: data, fields: fields, hasSmartSearchable: hasSmartSearchable, }; return [2 /*return*/, this.render(metadata.classParameters.listTemplate, req, ctx)]; } }); }); }; BaseUIController.prototype.generateCxtForEditingRender = function (data, isPopup, metadata, fields) { var _a, _b, _c, _d, _e, _f; var usedTypes = []; for (var key in fields) { if (usedTypes.indexOf(fields[key].type) == -1) { usedTypes.push(fields[key].type); } } var ctx = { data: data, configuration: _1.default.configuration, parentTemplate: isPopup ? metadata.classParameters.popupEditorParentTemplate : metadata.classParameters.editorParentTemplate, tabs: (_a = metadata.classParameters.uiSettings) === null || _a === void 0 ? void 0 : _a.tabs, defaultTab: (_b = metadata.classParameters.uiSettings) === null || _b === void 0 ? void 0 : _b.defaultTab, tabsAsSections: (_c = metadata.classParameters.uiSettings) === null || _c === void 0 ? void 0 : _c.tabsAsSections, hideTabsInExpanded: (_d = metadata.classParameters.uiSettings) === null || _d === void 0 ? void 0 : _d.hideTabsInExpanded, hideTabsInModal: (_e = metadata.classParameters.uiSettings) === null || _e === void 0 ? void 0 : _e.hideTabsInModal, hasRightColumn: (_f = metadata.classParameters.uiSettings) === null || _f === void 0 ? void 0 : _f.hasRightColumn, backButtonTemplate: metadata.classParameters.backButtonTemplate, nested: false, metadata: metadata, fields: fields, usedTypes: usedTypes, }; return ctx; }; /** * Retrieve the details of a specific entity by its id * @param entityName The name of the entity class * @param id The id of the entity to retrieve * @param req The current Lynx request */ BaseUIController.prototype.retrieveEntityDetails = function (entityName, id, req) { return __awaiter(this, void 0, void 0, function () { var entityData, metadata, _a, fields, data, usedTypes, key, isPopup, ctx; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, this.retrieveEntity(entityName, id)]; case 1: entityData = _b.sent(); if (!entityData) { throw this.error(404, 'not found'); } metadata = this.retrieveMetadata(entityName); _a = [{}]; return [4 /*yield*/, this.generateContextMetadata(metadata, req)]; case 2: metadata = __assign.apply(void 0, _a.concat([(_b.sent())])); return [4 /*yield*/, this.generateContextFields(metadata, req, entityData)]; case 3: fields = _b.sent(); metadata.fields = fields; return [4 /*yield*/, this.cleanData(req, entityData, metadata)]; case 4: data = _b.sent(); usedTypes = []; for (key in fields) { if (usedTypes.indexOf(fields[key].type) == -1) { usedTypes.push(fields[key].type); } } isPopup = req.query.popup; ctx = this.generateCxtForEditingRender(data, isPopup, metadata, fields); return [2 /*return*/, this.render(isPopup ? metadata.classParameters.popupEditorTemplate : metadata.classParameters.editorTemplate, req, ctx)]; } }); }); }; /** * Retrieve the nested view of an entity * @param entityName The name of the entity class * @param id The id of the entity to retrieve * @param nestedKey The name of the property of the entity to display * @param req The current Lynx request * @param enableDelete Determines if the delete action is enabled */ BaseUIController.prototype.retrieveNestedView = function (entityName, id, nestedKey, req, enableDelete) { if (enableDelete === void 0) { enableDelete = false; } return __awaiter(this, void 0, void 0, function () { var entityData, metadata, nestedField, entityData_1, e_4, data, ctx, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, this.retrieveEntity(entityName, id)]; case 1: entityData = _b.sent(); if (!entityData) { throw this.error(404, 'not found'); } metadata = __assign({}, this.retrieveMetadata(entityName)); return [4 /*yield*/, this.generateContextMetadata(metadata, req)]; case 2: metadata = _b.sent(); nestedField = metadata.fields[nestedKey]; metadata.fields = {}; metadata.fields[nestedKey] = nestedField; if (!(enableDelete && req.query.remove)) return [3 /*break*/, 7]; return [4 /*yield*/, this.retrieveEntity(nestedField.selfType, req.query.remove)]; case 3: entityData_1 = _b.sent(); if (!entityData_1) { throw this.error(404, 'not found'); } _b.label = 4; case 4: _b.trys.push([4, 6, , 7]); return [4 /*yield*/, entityData_1.remove()]; case 5: _b.sent(); return [3 /*break*/, 7]; case 6: e_4 = _b.sent(); this.logger.error(e_4); this.addErrorMessage('Unable to delete ' + entityData_1.getLabel() + '. Please check its dependencies.', req); return [3 /*break*/, 7]; case 7: return [4 /*yield*/, this.cleanData(req, entityData, metadata)]; case 8: data = _b.sent(); _a = { data: data, configuration: _1.default.configuration, parentTemplate: _1.default.nestedParentTemplatePath, nested: true, metadata: metadata }; return [4 /*yield*/, this.generateContextFields(metadata, req, entityData)]; case 9: ctx = (_a.fields = _b.sent(), _a); return [2 /*return*/, this.render(_1.default.nestedTemplatePath, req, ctx)]; } }); }); }; /** * Perform the edit of a specific entity * @param entityName The name of the entity class * @param id The id of the entity to edit * @param req The current Lynx request */ BaseUIController.prototype.performEntityEdit = function (entityName, id, req) { return __awaiter(this, void 0, void 0, function () { var metadata, entity, _a, _b, originalData, updated, e_5, errorField, field, _c, isPopup, fields, ctx; return __generator(this, function (_d) { switch (_d.label) { case 0: metadata = this.retrieveMetadata(entityName); if (!metadata) { throw this.error(404, 'not found'); } if (!(!id || id == '0')) return [3 /*break*/, 1]; entity = new (this.retrieveEntityClass(entityName))(); return [3 /*break*/, 3]; case 1: return [4 /*yield*/, this.retrieveEntity(entityName, id)]; case 2: entity = (_d.sent()); if (!entity) { throw this.error(404, 'not found'); } _d.label = 3; case 3: _a = [__assign({}, metadata)]; _b = {}; return [4 /*yield*/, this.generateContextFields(metadata, req, entity)]; case 4: metadata = __assign.apply(void 0, _a.concat([(_b.fields = _d.sent(), _b)])); return [4 /*yield*/, this.cleanData(req, entity, metadata)]; case 5: originalData = _d.sent(); return [4 /*yield*/, this.setData(req, entity, req.body, metadata)]; case 6: _d.sent(); _d.label = 7; case 7: _d.trys.push([7, 9, , 12]); return [4 /*yield*/, controller_1.Controller.saveEntity(entity, req)]; case 8: updated = _d.sent(); return [3 /*break*/, 12]; case 9: e_5 = _d.sent(); this.logger.log('error saving the entity', e_5); try { errorField = /'([a-zA-Z0-0_)]+)'/.exec(e_5.message)[1]; field = metadata.fields[errorField]; if (field) { this.addErrorMessage(this.app.translate('admin-ui.error-on-field', req) + this.app.translate(field.name, req), req); } else { this.addErrorMessage(e_5.message, req); } } catch (ee) { this.logger.error(e_5); this.addErrorMessage(e_5.message, req); } _c = [{}]; return [4 /*yield*/, this.generateContextMetadata(metadata, req)]; case 10: metadata = __assign.apply(void 0, _c.concat([(_d.sent())])); isPopup = req.query.popup; return [4 /*yield*/, this.generateContextFields(metadata, req, entity)]; case 11: fields = _d.sent(); metadata.fields = fields; ctx = this.generateCxtForEditingRender(__assign(__assign({}, originalData), req.body), isPopup, metadata, fields); return [2 /*return*/, this.render(isPopup ? metadata.classParameters.popupEditorTemplate : metadata.classParameters.editorTemplate, req, ctx)]; case 12: if (id == 0) { this.addSuccessMessage('admin-ui.success-create', req); } else { this.addSuccessMessage('admin-ui.success-update', req); } return [2 /*return*/, this.redirect('adminUI.details', { entityName: entityName, id: updated.id, })]; } }); }); }; return BaseUIController; }(controller_1.Controller)); exports.BaseUIController = BaseUIController;