adminizer 4.1.0-build.20 → 4.1.0-build.22

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.
@@ -13,3 +13,6 @@ export declare class AccessRightsHelper {
13
13
  enoughPermissions(tokens: string[], user: UserAP): boolean;
14
14
  hasPermission(tokenId: string, user: UserAP): boolean;
15
15
  }
16
+ export declare class GroupsAccessRightsHelper {
17
+ static hasAccess(user: UserAP, groupsAccessRights?: string[]): boolean;
18
+ }
@@ -62,3 +62,15 @@ export class AccessRightsHelper {
62
62
  return user.groups.some((group) => group.tokens?.includes(tokenId));
63
63
  }
64
64
  }
65
+ export class GroupsAccessRightsHelper {
66
+ static hasAccess(user, groupsAccessRights) {
67
+ const userGroups = user.groups?.map(group => group.name.toLowerCase());
68
+ if (groupsAccessRights) {
69
+ const allowedGroups = groupsAccessRights.map(item => item.toLowerCase());
70
+ return userGroups?.some(group => allowedGroups.includes(group)) ?? false;
71
+ }
72
+ else {
73
+ return true;
74
+ }
75
+ }
76
+ }
@@ -1,9 +1,9 @@
1
- import { ActionType, BaseFieldConfig, FieldsTypes } from "../interfaces/adminpanelConfig";
1
+ import { ActionType, BaseFieldConfig, FieldsTypes, ModelConfig } from "../interfaces/adminpanelConfig";
2
2
  import { Attribute } from "../lib/v4/model/AbstractModel";
3
3
  export type Field = {
4
4
  config: BaseFieldConfig & {
5
5
  /** @deprecated record should not be in config anymore */
6
- records?: object[];
6
+ records?: Record<string, any>[];
7
7
  file?: string;
8
8
  key?: string;
9
9
  required?: boolean;
@@ -15,6 +15,7 @@ export type Field = {
15
15
  [key: string]: Field;
16
16
  } | undefined;
17
17
  model: Attribute;
18
+ modelConfig: ModelConfig;
18
19
  };
19
20
  export type Fields = {
20
21
  [key: string]: Field;
@@ -1,5 +1,6 @@
1
1
  import { DataAccessor } from "../lib/v4/DataAccessor";
2
2
  import { Adminizer } from "../lib/Adminizer";
3
+ import { isObject } from "lodash-es";
3
4
  export class FieldsHelper {
4
5
  /**
5
6
  * Load list of records for all associations into `fields`
@@ -20,6 +21,9 @@ export class FieldsHelper {
20
21
  */
21
22
  let loadAssoc = async function (key, action) {
22
23
  let fieldConfigConfig = fields[key].config;
24
+ if (!isObject(fieldConfigConfig)) {
25
+ throw 'type error: fieldConfigConfig should be normalized';
26
+ }
23
27
  if (fieldConfigConfig.type !== 'association' && fieldConfigConfig.type !== 'association-many') {
24
28
  return;
25
29
  }
@@ -42,7 +46,7 @@ export class FieldsHelper {
42
46
  name: modelName, config: req.adminizer.config.models[modelName],
43
47
  model: Model, uri: `${req.adminizer.config.routePrefix}/model/${modelName}`, type: "model"
44
48
  };
45
- let dataAccessor = new DataAccessor(req.adminizer, req.user, entity, action);
49
+ let dataAccessor = new DataAccessor(req.adminizer, req.user, entity, "view");
46
50
  list = await Model.find({}, dataAccessor);
47
51
  }
48
52
  catch (e) {
@@ -1,7 +1,6 @@
1
1
  import { Entity, PropsField } from "../interfaces/types";
2
2
  import { Actions } from "./inertiaActionsHelper";
3
- import { Fields } from "./fieldsHelper";
4
- import { BaseFieldConfig } from "../interfaces/adminpanelConfig";
3
+ import { Fields, Field } from "./fieldsHelper";
5
4
  import { AbstractControls, ControlType } from "../lib/controls/AbstractControls";
6
5
  export type PropsFieldType = 'text' | 'number' | 'range' | 'week' | 'month' | 'email' | 'color' | 'time' | 'date' | 'datetime-local' | 'password' | 'select' | 'select-many' | 'association-many' | 'association' | 'textarea' | 'checkbox' | ControlType;
7
6
  interface FieldProps extends Record<string | number | symbol, unknown> {
@@ -21,7 +20,7 @@ interface FieldProps extends Record<string | number | symbol, unknown> {
21
20
  postLink: string;
22
21
  }
23
22
  export default function inertiaAddHelper(req: ReqType, entity: Entity, fields: Fields, record?: Record<string, string | boolean | number | string[]>, view?: boolean): FieldProps;
24
- export declare function getControlsOptions(fieldConfig: BaseFieldConfig, req: ReqType, type: ControlType, defaultControlName: string): {
23
+ export declare function getControlsOptions(fieldConfig: Field["config"], req: ReqType, type: ControlType, defaultControlName: string): {
25
24
  name: string;
26
25
  config: {
27
26
  el?: HTMLElement;
@@ -1,5 +1,6 @@
1
1
  import inertiaActionsHelper from "./inertiaActionsHelper";
2
2
  import chalk from "chalk";
3
+ import { isObject } from "lodash-es";
3
4
  export default function inertiaAddHelper(req, entity, fields, record, view = false) {
4
5
  const actionType = 'add';
5
6
  let props = {
@@ -25,6 +26,8 @@ export default function inertiaAddHelper(req, entity, fields, record, view = fal
25
26
  continue;
26
27
  let field = fields[key];
27
28
  let fieldConfig = field.config;
29
+ if (!isObject(fieldConfig))
30
+ throw `Type error: fieldConfig is object`;
28
31
  if (!!fieldConfig.visible === false)
29
32
  continue;
30
33
  const type = (fieldConfig.type || fieldConfig.type).toLowerCase();
@@ -115,6 +118,8 @@ export default function inertiaAddHelper(req, entity, fields, record, view = fal
115
118
  return props;
116
119
  }
117
120
  export function getControlsOptions(fieldConfig, req, type, defaultControlName) {
121
+ if (!isObject(fieldConfig))
122
+ throw `Type error: fieldConfig is object`;
118
123
  const fieldOptions = fieldConfig?.options;
119
124
  let control = getControl(req, type, fieldOptions?.name, defaultControlName);
120
125
  let editorName = control.getName();
@@ -192,6 +197,8 @@ function setAssociationValues(field, value) {
192
197
  let options = [];
193
198
  let initValue = [];
194
199
  const config = field.config;
200
+ if (!isObject(config))
201
+ throw `Type error: config is object`;
195
202
  const isOptionSelected = (option, value) => {
196
203
  if (Array.isArray(value)) {
197
204
  return value.includes(option);
@@ -200,8 +207,8 @@ function setAssociationValues(field, value) {
200
207
  return (option == value);
201
208
  }
202
209
  };
203
- const getAssociationValue = (value, config) => {
204
- const displayField = config.displayField || 'id';
210
+ const getAssociationValue = (value, field) => {
211
+ const displayField = field.modelConfig?.titleField || 'id';
205
212
  if (value === null)
206
213
  return [];
207
214
  if (Array.isArray(value)) {
@@ -214,13 +221,24 @@ function setAssociationValues(field, value) {
214
221
  return String(value);
215
222
  };
216
223
  for (let opt of config.records) {
224
+ const optAny = opt;
225
+ const displayField = typeof field.modelConfig.titleField !== 'undefined'
226
+ ? field.modelConfig.titleField
227
+ : typeof optAny.title !== 'undefined'
228
+ ? 'title'
229
+ : typeof optAny.name !== 'undefined'
230
+ ? 'name'
231
+ : config.identifierField;
232
+ const label = typeof config.displayModifier === 'function'
233
+ ? config.displayModifier(opt)
234
+ : optAny[displayField];
217
235
  options.push({
218
- label: config.displayModifier && typeof config.displayModifier === 'function'
219
- ? config.displayModifier(opt)
220
- : (config.displayField ? opt[config.displayField] : opt[config.identifierField]),
221
- value: opt[config.identifierField],
236
+ label,
237
+ value: optAny[config.identifierField],
222
238
  });
223
- if (isOptionSelected(opt[config.identifierField], getAssociationValue(value, config))) {
239
+ if (!isObject(config))
240
+ throw `Type error: config is object`;
241
+ if (config.identifierField && isOptionSelected(opt[config.identifierField], getAssociationValue(value, field))) {
224
242
  initValue.push(opt[config.identifierField]);
225
243
  }
226
244
  }
@@ -5,7 +5,7 @@ export class InertiaMenuHelper {
5
5
  }
6
6
  getMenuItems(req) {
7
7
  let menu = [];
8
- for (const menuItem of this.adminizer.menuHelper.getMenuItems()) {
8
+ for (const menuItem of this.adminizer.menuHelper.getMenuItems(req.user)) {
9
9
  const menuItemTokens = menuItem.actions ? menuItem.actions.map(item => {
10
10
  return item.accessRightsToken;
11
11
  }).filter(item => {
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * @constructor
5
5
  */
6
+ import { UserAP } from "models/UserAP";
6
7
  import { ActionType, AdminpanelConfig, HrefConfig, ModelConfig } from "../interfaces/adminpanelConfig";
7
8
  export type MenuItem = {
8
9
  link: string;
@@ -60,5 +61,5 @@ export declare class MenuHelper {
60
61
  *
61
62
  * @returns {Array}
62
63
  */
63
- getMenuItems(): MenuItem[];
64
+ getMenuItems(user: UserAP): MenuItem[];
64
65
  }
@@ -1,3 +1,4 @@
1
+ import { GroupsAccessRightsHelper } from "./accessRightsHelper";
1
2
  export class MenuHelper {
2
3
  static config;
3
4
  constructor(config) {
@@ -98,7 +99,7 @@ export class MenuHelper {
98
99
  *
99
100
  * @returns {Array}
100
101
  */
101
- getMenuItems() {
102
+ getMenuItems(user) {
102
103
  let menus = [];
103
104
  if (MenuHelper.config.navbar.additionalLinks && MenuHelper.config.navbar.additionalLinks.length > 0) {
104
105
  MenuHelper.config.navbar.additionalLinks.forEach(function (additionalLink) {
@@ -118,7 +119,12 @@ export class MenuHelper {
118
119
  }
119
120
  if (MenuHelper.config.models) {
120
121
  Object.entries(MenuHelper.config.models).forEach(function ([key, val]) {
121
- if (!val.hide) {
122
+ const hide = typeof val.navbar?.visible === 'boolean'
123
+ ? !val.navbar.visible
124
+ : val.navbar?.groupsAccessRights
125
+ ? !GroupsAccessRightsHelper.hasAccess(user, val.navbar.groupsAccessRights)
126
+ : false;
127
+ if (!hide) {
122
128
  if (val.tools && val.tools.length > 0 && val.tools[0].id !== "overview") {
123
129
  val.tools.unshift({
124
130
  id: "overview",
@@ -184,9 +184,19 @@ export interface ModelConfig {
184
184
  * */
185
185
  model: string;
186
186
  /**
187
- * Hide entity in left navbar
187
+ * If the field is not definitely, then it will appear in Navbar
188
188
  * */
189
- hide?: boolean;
189
+ navbar?: {
190
+ /**
191
+ * @default true
192
+ */
193
+ visible?: boolean;
194
+ /**
195
+ * User groups who will see the menu item
196
+ * For which it will be shown if not established will be shown to all groups who have the rights to read
197
+ */
198
+ groupsAccessRights?: string[];
199
+ };
190
200
  /**
191
201
  * Entity fields configuration
192
202
  * */
@@ -213,7 +223,7 @@ export interface ModelConfig {
213
223
  filter?: {
214
224
  [key: string]: {
215
225
  name: string;
216
- criteria: any;
226
+ criteria: Record<string, any>;
217
227
  };
218
228
  };
219
229
  } | boolean;
@@ -241,6 +251,11 @@ export interface ModelConfig {
241
251
  * Entity icon
242
252
  * */
243
253
  icon?: MaterialIcon;
254
+ /**
255
+ * The field that will be shown for communication
256
+ * @default `title` or `name`
257
+ */
258
+ titleField?: string;
244
259
  /**
245
260
  * Force set primary key
246
261
  * */
@@ -291,6 +306,7 @@ export interface BaseFieldConfig {
291
306
  identifierField?: string;
292
307
  /**
293
308
  * Label for associations
309
+ * @deprecated use model labelField prop
294
310
  * */
295
311
  displayField?: string;
296
312
  /**
@@ -160,6 +160,9 @@ export class NodeTable {
160
160
  Object.keys(this.fields).forEach((key) => {
161
161
  let fieldConfigConfig = this.fields[key].config;
162
162
  let fieldName = key;
163
+ let displayField = this.fields[key].modelConfig?.titleField ?? typeof row["title"] !== "undefined" ? 'title'
164
+ : typeof row["name"] !== "undefined" ? 'name'
165
+ : typeof row[this.fields[key].modelConfig?.identifierField] !== "undefined" ? this.fields[key].modelConfig?.identifierField : this.fields[key].modelConfig?.identifierField;
163
166
  if (fieldConfigConfig.displayModifier) {
164
167
  row[fieldName] = fieldConfigConfig.displayModifier(elem[key]);
165
168
  }
@@ -169,7 +172,7 @@ export class NodeTable {
169
172
  row[fieldName] = null;
170
173
  }
171
174
  else {
172
- row[fieldName] = elem[key][fieldConfigConfig.displayField];
175
+ row[fieldName] = elem[key][displayField];
173
176
  }
174
177
  }
175
178
  else if (this.fields[key].model.type === "association-many" || this.fields[key].model.type === "association") {
@@ -179,8 +182,8 @@ export class NodeTable {
179
182
  else {
180
183
  let displayValues = [];
181
184
  elem[key].forEach((item) => {
182
- if (item[fieldConfigConfig.displayField]) {
183
- displayValues.push(item[fieldConfigConfig.displayField]);
185
+ if (item[displayField]) {
186
+ displayValues.push(item[displayField]);
184
187
  }
185
188
  else {
186
189
  displayValues.push(item[fieldConfigConfig.identifierField]);
@@ -1,5 +1,6 @@
1
1
  import { ControllerHelper } from "../../helpers/controllerHelper";
2
2
  import { Adminizer } from "../Adminizer";
3
+ import { isObject } from "lodash-es";
3
4
  export class DataAccessor {
4
5
  adminizer;
5
6
  user;
@@ -32,7 +33,7 @@ export class DataAccessor {
32
33
  const modelAttributes = this.entity.model.attributes;
33
34
  const tokenId = `${this.actionVerb}-${this.entity.model.modelname}-${this.entity.type}`;
34
35
  if (!this.adminizer.accessRightsHelper.hasPermission(tokenId, this.user)) {
35
- Adminizer.log.debug(`No access rights to ${this.entity.type}: ${this.entity.model.modelname}`);
36
+ Adminizer.log.debug(`getFieldsConfig > No access rights to ${this.actionVerb} ${this.entity.type}: ${this.entity.model.modelname}`);
36
37
  return undefined;
37
38
  }
38
39
  const result = {};
@@ -56,6 +57,7 @@ export class DataAccessor {
56
57
  }
57
58
  // Getting base field config
58
59
  let fldConfig = { key: key, title: key };
60
+ let associatedModelConfig = undefined;
59
61
  /** Combine the field configuration from global and action-specific configs
60
62
  * (now combine it before check, earlier was opposite).
61
63
  * Action-specific config should overwrite the global one */
@@ -77,7 +79,7 @@ export class DataAccessor {
77
79
  let populatedModelFieldsConfig = {};
78
80
  if (modelField.type === "association" || modelField.type === "association-many") {
79
81
  const modelName = modelField.model || modelField.collection;
80
- const tokenId = `${this.actionVerb}-${modelName}-${this.entity.type}`;
82
+ const tokenId = `read-${modelName}-${this.entity.type}`;
81
83
  if (!this.adminizer.accessRightsHelper.hasPermission(tokenId, this.user)) {
82
84
  Adminizer.log.silly(`No access rights to ${this.entity.type}: ${this.entity.model.modelname}`);
83
85
  return undefined;
@@ -86,6 +88,9 @@ export class DataAccessor {
86
88
  const model = this.adminizer.modelHandler.model.get(modelName);
87
89
  if (model) {
88
90
  populatedModelFieldsConfig = this.getAssociatedFieldsConfig(modelName);
91
+ if (!isObject(this.adminizer.config.models[modelName]))
92
+ throw `type error: model config of ${modelName} is ${typeof (this.adminizer.config.models[modelName])} expected object`;
93
+ associatedModelConfig = this.adminizer.config.models[modelName];
89
94
  }
90
95
  else {
91
96
  Adminizer.log.error(`DataAccessor > getFieldsConfig > Model not found: ${modelName} when ${key}`);
@@ -99,7 +104,7 @@ export class DataAccessor {
99
104
  // Normalize final configuration
100
105
  fldConfig = this.adminizer.configHelper.normalizeFieldConfig(this.adminizer, fldConfig, key, modelField);
101
106
  // Add new field to result set
102
- result[key] = { config: fldConfig, model: modelField, populated: populatedModelFieldsConfig };
107
+ result[key] = { config: fldConfig, model: modelField, populated: populatedModelFieldsConfig, modelConfig: associatedModelConfig };
103
108
  });
104
109
  this.fields = result;
105
110
  return result;
@@ -110,9 +115,9 @@ export class DataAccessor {
110
115
  return undefined;
111
116
  }
112
117
  // Check if user has access to the associated model
113
- const tokenId = `${this.actionVerb}-${modelName}-${this.entity.type}`;
118
+ const tokenId = `read-${modelName}-${this.entity.type}`;
114
119
  if (!this.adminizer.accessRightsHelper.hasPermission(tokenId, this.user)) {
115
- Adminizer.log.debug(`No access rights to ${this.entity.type}: ${modelName}`);
120
+ Adminizer.log.debug(`getAssociatedFieldsConfig > No access rights to ${this.actionVerb} ${this.entity.type}: ${modelName}`);
116
121
  return undefined;
117
122
  }
118
123
  const associatedFields = {};
@@ -161,6 +166,7 @@ export class DataAccessor {
161
166
  config: fldConfig,
162
167
  model: modelField,
163
168
  populated: undefined, // set undefined for already populated fields
169
+ modelConfig: undefined
164
170
  };
165
171
  });
166
172
  return associatedFields;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "adminizer",
3
3
  "type": "module",
4
- "version": "4.1.0-build.20",
4
+ "version": "4.1.0-build.22",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "vitest --reporter verbose",