adminizer 4.1.0-build.21 → 4.1.0-build.23

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.
@@ -0,0 +1 @@
1
+ export declare function isObject(obj: unknown): obj is object;
@@ -0,0 +1,3 @@
1
+ export function isObject(obj) {
2
+ return typeof obj === "object" && obj !== null;
3
+ }
@@ -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 "./JsUtils";
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
  }
@@ -192,6 +192,7 @@ export interface ModelConfig {
192
192
  */
193
193
  visible?: boolean;
194
194
  /**
195
+ * User groups who will see the menu item
195
196
  * For which it will be shown if not established will be shown to all groups who have the rights to read
196
197
  */
197
198
  groupsAccessRights?: string[];
@@ -222,7 +223,7 @@ export interface ModelConfig {
222
223
  filter?: {
223
224
  [key: string]: {
224
225
  name: string;
225
- criteria: any;
226
+ criteria: Record<string, any>;
226
227
  };
227
228
  };
228
229
  } | boolean;
@@ -250,6 +251,11 @@ export interface ModelConfig {
250
251
  * Entity icon
251
252
  * */
252
253
  icon?: MaterialIcon;
254
+ /**
255
+ * The field that will be shown for communication
256
+ * @default `title` or `name`
257
+ */
258
+ titleField?: string;
253
259
  /**
254
260
  * Force set primary key
255
261
  * */
@@ -300,6 +306,7 @@ export interface BaseFieldConfig {
300
306
  identifierField?: string;
301
307
  /**
302
308
  * Label for associations
309
+ * @deprecated use model labelField prop
303
310
  * */
304
311
  displayField?: string;
305
312
  /**
package/lib/Adminizer.js CHANGED
@@ -126,11 +126,13 @@ export class Adminizer {
126
126
  }
127
127
  };
128
128
  this.modelHandler = new ModelHandler();
129
+ // TODO: 'hot reload' unbind models & unbind forms
129
130
  await bindModels(this);
130
131
  await bindForms(this);
131
132
  this.config.rootPath = path.resolve(import.meta.dirname + "/..");
132
133
  this.policyManager = new PolicyManager(this);
133
134
  await this.policyManager.loadPolicies();
135
+ // TODO: 'hot reload' problem with deleting access right tokens
134
136
  this.accessRightsHelper = new AccessRightsHelper(this);
135
137
  // Helpers go to construtor
136
138
  this.configHelper = new ConfigHelper(this);
@@ -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 "helpers/JsUtils";
3
4
  export class DataAccessor {
4
5
  adminizer;
5
6
  user;
@@ -32,12 +33,11 @@ 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 = {};
39
40
  Object.entries(modelAttributes).forEach(([key, modelField]) => {
40
- console.log(key);
41
41
  // The fields that are recorded separately from the connection in some ORMs, because they are processed at the level above them.
42
42
  if (modelAttributes[key].primaryKeyForAssociation === true) {
43
43
  return undefined;
@@ -57,6 +57,7 @@ export class DataAccessor {
57
57
  }
58
58
  // Getting base field config
59
59
  let fldConfig = { key: key, title: key };
60
+ let associatedModelConfig = undefined;
60
61
  /** Combine the field configuration from global and action-specific configs
61
62
  * (now combine it before check, earlier was opposite).
62
63
  * Action-specific config should overwrite the global one */
@@ -87,6 +88,9 @@ export class DataAccessor {
87
88
  const model = this.adminizer.modelHandler.model.get(modelName);
88
89
  if (model) {
89
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];
90
94
  }
91
95
  else {
92
96
  Adminizer.log.error(`DataAccessor > getFieldsConfig > Model not found: ${modelName} when ${key}`);
@@ -100,7 +104,7 @@ export class DataAccessor {
100
104
  // Normalize final configuration
101
105
  fldConfig = this.adminizer.configHelper.normalizeFieldConfig(this.adminizer, fldConfig, key, modelField);
102
106
  // Add new field to result set
103
- result[key] = { config: fldConfig, model: modelField, populated: populatedModelFieldsConfig };
107
+ result[key] = { config: fldConfig, model: modelField, populated: populatedModelFieldsConfig, modelConfig: associatedModelConfig };
104
108
  });
105
109
  this.fields = result;
106
110
  return result;
@@ -111,9 +115,9 @@ export class DataAccessor {
111
115
  return undefined;
112
116
  }
113
117
  // Check if user has access to the associated model
114
- const tokenId = `${this.actionVerb}-${modelName}-${this.entity.type}`;
118
+ const tokenId = `read-${modelName}-${this.entity.type}`;
115
119
  if (!this.adminizer.accessRightsHelper.hasPermission(tokenId, this.user)) {
116
- 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}`);
117
121
  return undefined;
118
122
  }
119
123
  const associatedFields = {};
@@ -162,6 +166,7 @@ export class DataAccessor {
162
166
  config: fldConfig,
163
167
  model: modelField,
164
168
  populated: undefined, // set undefined for already populated fields
169
+ modelConfig: undefined
165
170
  };
166
171
  });
167
172
  return associatedFields;
@@ -9,6 +9,7 @@ export class ModelHandler {
9
9
  this.models.set(modelname, modelInstance);
10
10
  Adminizer.log.debug(`Model with name [${modelname}] was registered`);
11
11
  }
12
+ // TODO: 'hot reload' need add method for delete model
12
13
  /** Improved model getter, so you can write both model.get("UserAP") and model.get("userap") */
13
14
  get model() {
14
15
  return {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "adminizer",
3
3
  "type": "module",
4
- "version": "4.1.0-build.21",
4
+ "version": "4.1.0-build.23",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "vitest --reporter verbose",