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.
- package/helpers/accessRightsHelper.d.ts +3 -0
- package/helpers/accessRightsHelper.js +12 -0
- package/helpers/fieldsHelper.d.ts +3 -2
- package/helpers/fieldsHelper.js +5 -1
- package/helpers/inertiaAddHelper.d.ts +2 -3
- package/helpers/inertiaAddHelper.js +25 -7
- package/helpers/inertiaMenuHelper.js +1 -1
- package/helpers/menuHelper.d.ts +2 -1
- package/helpers/menuHelper.js +8 -2
- package/interfaces/adminpanelConfig.d.ts +19 -3
- package/lib/datatable/NodeTable.js +6 -3
- package/lib/v4/DataAccessor.js +11 -5
- package/package.json +1 -1
|
@@ -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?:
|
|
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;
|
package/helpers/fieldsHelper.js
CHANGED
|
@@ -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,
|
|
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:
|
|
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,
|
|
204
|
-
const displayField =
|
|
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
|
|
219
|
-
|
|
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 (
|
|
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 => {
|
package/helpers/menuHelper.d.ts
CHANGED
|
@@ -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
|
}
|
package/helpers/menuHelper.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
*
|
|
187
|
+
* If the field is not definitely, then it will appear in Navbar
|
|
188
188
|
* */
|
|
189
|
-
|
|
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][
|
|
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[
|
|
183
|
-
displayValues.push(item[
|
|
185
|
+
if (item[displayField]) {
|
|
186
|
+
displayValues.push(item[displayField]);
|
|
184
187
|
}
|
|
185
188
|
else {
|
|
186
189
|
displayValues.push(item[fieldConfigConfig.identifierField]);
|
package/lib/v4/DataAccessor.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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;
|