adminizer 5.0.0-build.12 → 5.0.0-build.13
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/assets/{add-BhTWhSNx.js → add-B_hn__Ba.js} +1 -1
- package/assets/{add-group-BEOEkMGK.js → add-group-0SdYgiXZ.js} +1 -1
- package/assets/{add-user-MW-fSILU.js → add-user-Bh1iqSem.js} +1 -1
- package/assets/ai-assistant/agent.es.js +3948 -3889
- package/assets/app.js +32 -32
- package/assets/{catalog-D-lFVV9U.js → catalog-DjK8r0Mn.js} +1 -1
- package/assets/controls/handsontable.es.js +11637 -11632
- package/assets/controls/jsoneditor.es.js +6308 -6297
- package/assets/controls/toast-ui.es.js +18 -17
- package/assets/{dashboard-Cmj4Df_C.js → dashboard-CbJIK7ka.js} +1 -1
- package/assets/{history-BXfeZHTD.js → history-CZAJgVXP.js} +1 -1
- package/assets/{list-Dp8F_hor.js → list-C6w_xDK2.js} +1 -1
- package/assets/manifest.json +24 -24
- package/assets/{module-Q-1AzZH9.js → module-DvPjKyF_.js} +1 -1
- package/assets/{notification-CSeMEsPn.js → notification-N_b-N0Zu.js} +1 -1
- package/assets/{user-filters-list-Do6Dis5D.js → user-filters-list-xbJoY_5_.js} +1 -1
- package/assets/{welcome-D3dg746Y.js → welcome-BUkdkKa5.js} +1 -1
- package/assets/{with-app-layout-Bjo8B9zO.js → with-app-layout-WhRyO5WK.js} +1 -1
- package/controllers/view.js +1 -1
- package/helpers/configHelper.js +12 -17
- package/helpers/controllerHelper.d.ts +1 -0
- package/helpers/controllerHelper.js +25 -15
- package/helpers/fieldsHelper.js +3 -3
- package/helpers/inertiaAddHelper.js +1 -1
- package/helpers/modelResourceHelper.d.ts +1 -1
- package/helpers/modelResourceHelper.js +8 -4
- package/interfaces/adminpanelConfig.d.ts +6 -1
- package/lib/DataAccessor.d.ts +1 -0
- package/lib/DataAccessor.js +45 -47
- package/lib/filters/FilterService.js +4 -3
- package/lib/history-actions/AbstractHistoryAdapter.js +11 -9
- package/lib/model/AbstractModel.d.ts +2 -0
- package/lib/model/ModelHandler.d.ts +47 -2
- package/lib/model/ModelHandler.js +107 -8
- package/package.json +1 -1
- package/system/bindAccessRights.js +2 -3
- package/system/buildInternalModelAccess.js +5 -2
- package/system/validateSystemModels.js +29 -9
|
@@ -40,18 +40,27 @@ export class AppModelAccess {
|
|
|
40
40
|
export class ModelHandler {
|
|
41
41
|
models = new Map();
|
|
42
42
|
modelAliases = new Map();
|
|
43
|
+
hostModelResources = new Map();
|
|
43
44
|
internalAccess;
|
|
44
45
|
appAccessRecords = new Map();
|
|
45
|
-
add(
|
|
46
|
-
const
|
|
47
|
-
this.models.
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
add(resourceName, modelInstance, options = {}) {
|
|
47
|
+
const resourceId = normalizeName(resourceName);
|
|
48
|
+
if (this.models.has(resourceId)) {
|
|
49
|
+
throw new Error(`Model resource "${resourceName}" is already registered`);
|
|
50
|
+
}
|
|
51
|
+
const normalizedOptions = Array.isArray(options) ? { aliases: options } : options;
|
|
52
|
+
const hostModelName = normalizedOptions.hostModelName ?? resourceName;
|
|
53
|
+
this.registerHostModel(resourceName, hostModelName, true);
|
|
54
|
+
this.models.set(resourceId, {
|
|
55
|
+
id: resourceId,
|
|
56
|
+
name: resourceName,
|
|
57
|
+
hostModelName,
|
|
58
|
+
primaryHostModel: normalizedOptions.primary === true,
|
|
50
59
|
model: modelInstance,
|
|
51
60
|
enabled: true,
|
|
52
61
|
});
|
|
53
|
-
this.registerAliases(
|
|
54
|
-
Adminizer.log.debug(`Model
|
|
62
|
+
this.registerAliases(resourceName, normalizedOptions.aliases ?? []);
|
|
63
|
+
Adminizer.log.debug(`Model resource [${resourceName}] was registered for host model [${hostModelName}]`);
|
|
55
64
|
}
|
|
56
65
|
register(appName, modelName, modelInstance) {
|
|
57
66
|
const normalizedModelName = normalizeName(modelName);
|
|
@@ -61,10 +70,13 @@ export class ModelHandler {
|
|
|
61
70
|
this.models.set(normalizedModelName, {
|
|
62
71
|
id: normalizedModelName,
|
|
63
72
|
name: modelName,
|
|
73
|
+
hostModelName: modelName,
|
|
74
|
+
primaryHostModel: true,
|
|
64
75
|
model: modelInstance,
|
|
65
76
|
ownerApp: appName,
|
|
66
77
|
enabled: true,
|
|
67
78
|
});
|
|
79
|
+
this.registerHostModel(modelName, modelName, false);
|
|
68
80
|
Adminizer.log.debug(`Model with name [${normalizedModelName}] was registered by app [${appName}]`);
|
|
69
81
|
return normalizedModelName;
|
|
70
82
|
}
|
|
@@ -74,6 +86,7 @@ export class ModelHandler {
|
|
|
74
86
|
return;
|
|
75
87
|
}
|
|
76
88
|
this.models.delete(normalizeName(id));
|
|
89
|
+
this.unregisterHostModel(record.hostModelName, record.name);
|
|
77
90
|
}
|
|
78
91
|
disable(id) {
|
|
79
92
|
const record = this.models.get(normalizeName(id));
|
|
@@ -118,7 +131,10 @@ export class ModelHandler {
|
|
|
118
131
|
return new AppModelAccess((modelName) => this.model.get(modelName), appName, () => this.getAllowedAppModels(appName));
|
|
119
132
|
}
|
|
120
133
|
// TODO: 'hot reload' need add method for delete model & unbind
|
|
121
|
-
/**
|
|
134
|
+
/**
|
|
135
|
+
* Improved model getter with case-insensitive lookup and configured alias support.
|
|
136
|
+
* @deprecated New code should use getResource() and getByHostModel() explicitly.
|
|
137
|
+
*/
|
|
122
138
|
get model() {
|
|
123
139
|
return {
|
|
124
140
|
get: (modelName) => this.models.get(this.resolveName(modelName))?.enabled
|
|
@@ -130,6 +146,65 @@ export class ModelHandler {
|
|
|
130
146
|
values: () => this.enabledModelValues(),
|
|
131
147
|
};
|
|
132
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Returns a model by its Adminizer resource name or an explicit deprecated compatibility alias.
|
|
151
|
+
* New code must use canonical resource names.
|
|
152
|
+
*/
|
|
153
|
+
getResource(resourceName) {
|
|
154
|
+
return this.model.get(resourceName);
|
|
155
|
+
}
|
|
156
|
+
getResourceRecord(resourceName) {
|
|
157
|
+
const record = this.models.get(this.resolveName(resourceName));
|
|
158
|
+
return record?.enabled ? record : undefined;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Resolves a physical ORM model name to its Adminizer resource.
|
|
162
|
+
* This lookup is deliberately separate from getResource(): a host model may have the
|
|
163
|
+
* same name as a system resource while being exposed under a project resource alias.
|
|
164
|
+
*/
|
|
165
|
+
resolveResourceByHostModel(hostModelName) {
|
|
166
|
+
const resourceIds = this.hostModelResources.get(normalizeName(hostModelName));
|
|
167
|
+
const records = Array.from(resourceIds ?? [])
|
|
168
|
+
.map((resourceId) => this.models.get(resourceId))
|
|
169
|
+
.filter((record) => Boolean(record?.enabled));
|
|
170
|
+
if (records.length === 1) {
|
|
171
|
+
return records[0].name;
|
|
172
|
+
}
|
|
173
|
+
const primaryRecords = records.filter((record) => record.primaryHostModel);
|
|
174
|
+
if (primaryRecords.length === 1) {
|
|
175
|
+
return primaryRecords[0].name;
|
|
176
|
+
}
|
|
177
|
+
if (records.length > 1) {
|
|
178
|
+
throw new Error(this.getSharedHostModelError(hostModelName, records));
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
getByHostModel(hostModelName) {
|
|
183
|
+
const resourceName = this.resolveResourceByHostModel(hostModelName);
|
|
184
|
+
return resourceName ? this.getResource(resourceName) : undefined;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Resolves an ORM association target. Adapter-provided host model names take precedence;
|
|
188
|
+
* system-model relations may pass their explicit canonical resource name instead.
|
|
189
|
+
*/
|
|
190
|
+
resolveAssociationResource(targetName, resourceName) {
|
|
191
|
+
if (resourceName) {
|
|
192
|
+
return this.getResourceRecord(resourceName)?.name;
|
|
193
|
+
}
|
|
194
|
+
return this.resolveResourceByHostModel(targetName)
|
|
195
|
+
?? this.getResourceRecord(targetName)?.name;
|
|
196
|
+
}
|
|
197
|
+
/** Validates that every host ORM model used by multiple resources has one primary resource. */
|
|
198
|
+
validateHostModelMappings() {
|
|
199
|
+
for (const [normalizedHostModelName, resourceIds] of this.hostModelResources) {
|
|
200
|
+
const records = Array.from(resourceIds)
|
|
201
|
+
.map((resourceId) => this.models.get(resourceId))
|
|
202
|
+
.filter((record) => Boolean(record?.enabled));
|
|
203
|
+
if (records.length > 1 && records.filter((record) => record.primaryHostModel).length !== 1) {
|
|
204
|
+
throw new Error(this.getSharedHostModelError(records[0]?.hostModelName ?? normalizedHostModelName, records));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
133
208
|
resolveModelName(modelName) {
|
|
134
209
|
const resolvedName = this.resolveName(modelName);
|
|
135
210
|
return this.models.get(resolvedName)?.name ?? modelName;
|
|
@@ -184,6 +259,30 @@ export class ModelHandler {
|
|
|
184
259
|
this.modelAliases.set(normalizedAlias, normalizedModelName);
|
|
185
260
|
}
|
|
186
261
|
}
|
|
262
|
+
registerHostModel(resourceName, hostModelName, allowMultipleResources) {
|
|
263
|
+
const normalizedHostModelName = normalizeName(hostModelName);
|
|
264
|
+
const resourceId = normalizeName(resourceName);
|
|
265
|
+
const resourceIds = this.hostModelResources.get(normalizedHostModelName) ?? new Set();
|
|
266
|
+
if (!allowMultipleResources && resourceIds.size && !resourceIds.has(resourceId)) {
|
|
267
|
+
const existingResourceId = resourceIds.values().next().value;
|
|
268
|
+
const existingResource = this.models.get(existingResourceId)?.name ?? existingResourceId;
|
|
269
|
+
throw new Error(`Host model "${hostModelName}" is already registered for Adminizer resource "${existingResource}"`);
|
|
270
|
+
}
|
|
271
|
+
resourceIds.add(resourceId);
|
|
272
|
+
this.hostModelResources.set(normalizedHostModelName, resourceIds);
|
|
273
|
+
}
|
|
274
|
+
unregisterHostModel(hostModelName, resourceName) {
|
|
275
|
+
const normalizedHostModelName = normalizeName(hostModelName);
|
|
276
|
+
const resourceIds = this.hostModelResources.get(normalizedHostModelName);
|
|
277
|
+
resourceIds?.delete(normalizeName(resourceName));
|
|
278
|
+
if (!resourceIds?.size) {
|
|
279
|
+
this.hostModelResources.delete(normalizedHostModelName);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
getSharedHostModelError(hostModelName, records) {
|
|
283
|
+
return (`Host model "${hostModelName}" is mapped to multiple Adminizer resources: ` +
|
|
284
|
+
`${records.map((record) => record.name).join(", ")}. Configure exactly one with primary: true.`);
|
|
285
|
+
}
|
|
187
286
|
resolveName(modelName) {
|
|
188
287
|
const normalizedModelName = normalizeName(modelName);
|
|
189
288
|
return this.modelAliases.get(normalizedModelName) ?? normalizedModelName;
|
package/package.json
CHANGED
|
@@ -2,10 +2,9 @@ import { GROUP_FILTER_VISIBILITY_TOKEN } from "../policies/permissionResolvers.j
|
|
|
2
2
|
export default async function bindAccessRights(adminizer) {
|
|
3
3
|
if (adminizer.config.models) {
|
|
4
4
|
let models = adminizer.config.models;
|
|
5
|
-
for (
|
|
6
|
-
const model = models[key];
|
|
5
|
+
for (const [resourceName, model] of Object.entries(models)) {
|
|
7
6
|
if (typeof model !== "boolean") {
|
|
8
|
-
adminizer.accessRightsHelper.registerModelTokens(
|
|
7
|
+
adminizer.accessRightsHelper.registerModelTokens(resourceName);
|
|
9
8
|
}
|
|
10
9
|
}
|
|
11
10
|
}
|
|
@@ -30,10 +30,13 @@ function getDataAccessorInternalModels(config, modelHandler) {
|
|
|
30
30
|
if (!userAccessRelation || typeof userAccessRelation !== "object" || Array.isArray(userAccessRelation)) {
|
|
31
31
|
continue;
|
|
32
32
|
}
|
|
33
|
-
const model = modelHandler.
|
|
33
|
+
const model = modelHandler.getResource(modelName);
|
|
34
34
|
const intermediateModelName = model?.attributes?.[userAccessRelation.field]?.model;
|
|
35
35
|
if (intermediateModelName) {
|
|
36
|
-
|
|
36
|
+
const resourceName = modelHandler.resolveAssociationResource(intermediateModelName, model?.attributes?.[userAccessRelation.field]?.resourceName);
|
|
37
|
+
if (resourceName) {
|
|
38
|
+
models.push(resourceName);
|
|
39
|
+
}
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
return Array.from(new Set(models));
|
|
@@ -10,13 +10,11 @@ export function validateSystemModels(adminizer) {
|
|
|
10
10
|
}
|
|
11
11
|
const systemAdapter = getAdapter(adminizer, defaultOrmAdapter);
|
|
12
12
|
const bindings = buildSystemModelBindings(systemAdapter);
|
|
13
|
-
const
|
|
13
|
+
const systemResourceNames = new Set(SYSTEM_MODEL_CONTRACTS.map((contract) => contract.name));
|
|
14
14
|
for (const contract of SYSTEM_MODEL_CONTRACTS) {
|
|
15
15
|
const binding = bindings.get(contract.name.toLowerCase()) ?? {
|
|
16
16
|
model: contract.name,
|
|
17
17
|
};
|
|
18
|
-
systemModels.add(contract.name.toLowerCase());
|
|
19
|
-
systemModels.add(binding.model.toLowerCase());
|
|
20
18
|
const registeredModel = systemAdapter.getModel(binding.model);
|
|
21
19
|
if (!registeredModel) {
|
|
22
20
|
throw new Error(`System model "${contract.name}" was not provided by adapter "${systemAdapter.ormType}" as host model "${binding.model}". ` +
|
|
@@ -27,7 +25,11 @@ export function validateSystemModels(adminizer) {
|
|
|
27
25
|
resolveRelationTarget: (target) => resolveSystemRelationTarget(target, bindings),
|
|
28
26
|
});
|
|
29
27
|
normalizeSystemModelRelations(model, bindings);
|
|
30
|
-
adminizer.modelHandler.add(contract.name, model,
|
|
28
|
+
adminizer.modelHandler.add(contract.name, model, {
|
|
29
|
+
hostModelName: binding.model,
|
|
30
|
+
// @deprecated Compatibility lookup for modules that still request UserAP/GroupAP.
|
|
31
|
+
aliases: [binding.model],
|
|
32
|
+
});
|
|
31
33
|
}
|
|
32
34
|
const modelsFromConfig = Object.entries(adminizer.config.models ?? {});
|
|
33
35
|
Adminizer.log.debug(`Bind models > Models from config: ${modelsFromConfig.map(([name]) => name)}`);
|
|
@@ -35,7 +37,10 @@ export function validateSystemModels(adminizer) {
|
|
|
35
37
|
const modelName = modelConfig && typeof modelConfig !== "boolean"
|
|
36
38
|
? modelConfig.model
|
|
37
39
|
: configName;
|
|
38
|
-
|
|
40
|
+
// System resources are already registered above. Do not compare host model names
|
|
41
|
+
// here: a project resource may intentionally map its own Adminizer name to a
|
|
42
|
+
// host model named "User" or "Group".
|
|
43
|
+
if (systemResourceNames.has(configName)) {
|
|
39
44
|
continue;
|
|
40
45
|
}
|
|
41
46
|
const adapterName = modelConfig && typeof modelConfig !== "boolean"
|
|
@@ -46,8 +51,12 @@ export function validateSystemModels(adminizer) {
|
|
|
46
51
|
if (!registeredModel) {
|
|
47
52
|
throw new Error(`Bind models > Model not found: ${modelName}`);
|
|
48
53
|
}
|
|
49
|
-
adminizer.modelHandler.add(
|
|
54
|
+
adminizer.modelHandler.add(configName, new ormAdapter.Model(modelName, registeredModel), {
|
|
55
|
+
hostModelName: modelName,
|
|
56
|
+
primary: modelConfig && typeof modelConfig !== "boolean" && modelConfig.primary === true,
|
|
57
|
+
});
|
|
50
58
|
}
|
|
59
|
+
adminizer.modelHandler.validateHostModelMappings();
|
|
51
60
|
Adminizer.log.info("Models loaded");
|
|
52
61
|
}
|
|
53
62
|
function getAdapter(adminizer, adapterName) {
|
|
@@ -79,6 +88,9 @@ function normalizeSystemModelBinding(binding) {
|
|
|
79
88
|
return binding;
|
|
80
89
|
}
|
|
81
90
|
function resolveSystemRelationTarget(target, bindings) {
|
|
91
|
+
return getSystemRelationResource(target, bindings) ?? target;
|
|
92
|
+
}
|
|
93
|
+
function getSystemRelationResource(target, bindings) {
|
|
82
94
|
const normalizedTarget = target.toLowerCase();
|
|
83
95
|
for (const contract of SYSTEM_MODEL_CONTRACTS) {
|
|
84
96
|
const binding = bindings.get(contract.name.toLowerCase());
|
|
@@ -87,15 +99,23 @@ function resolveSystemRelationTarget(target, bindings) {
|
|
|
87
99
|
return contract.name;
|
|
88
100
|
}
|
|
89
101
|
}
|
|
90
|
-
return
|
|
102
|
+
return undefined;
|
|
91
103
|
}
|
|
92
104
|
function normalizeSystemModelRelations(model, bindings) {
|
|
93
105
|
for (const attribute of Object.values(model.attributes)) {
|
|
94
106
|
if (attribute.model) {
|
|
95
|
-
|
|
107
|
+
const resourceName = getSystemRelationResource(attribute.model, bindings);
|
|
108
|
+
if (resourceName) {
|
|
109
|
+
attribute.resourceName = resourceName;
|
|
110
|
+
attribute.model = resourceName;
|
|
111
|
+
}
|
|
96
112
|
}
|
|
97
113
|
if (attribute.collection) {
|
|
98
|
-
|
|
114
|
+
const resourceName = getSystemRelationResource(attribute.collection, bindings);
|
|
115
|
+
if (resourceName) {
|
|
116
|
+
attribute.resourceName = resourceName;
|
|
117
|
+
attribute.collection = resourceName;
|
|
118
|
+
}
|
|
99
119
|
}
|
|
100
120
|
}
|
|
101
121
|
}
|