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.
- package/helpers/JsUtils.d.ts +1 -0
- package/helpers/JsUtils.js +3 -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/interfaces/adminpanelConfig.d.ts +8 -1
- package/lib/Adminizer.js +2 -0
- package/lib/datatable/NodeTable.js +6 -3
- package/lib/v4/DataAccessor.js +10 -5
- package/lib/v4/model/ModelHandler.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isObject(obj: unknown): obj is object;
|
|
@@ -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 "./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,
|
|
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
|
}
|
|
@@ -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][
|
|
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 "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 =
|
|
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 {
|