adminizer 5.0.0-build.9 → 5.1.0-build.25
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/abnfDiagram-VCTEODGH-BTgLIXrb.js +1 -0
- package/assets/add-C8V8qjvm.js +1 -0
- package/assets/add-group-Ezq_Ocot.js +1 -0
- package/assets/add-user-CluNf2QG.js +1 -0
- package/assets/ai-assistant/agent.es.js +28464 -0
- package/assets/app-C96pcs-J.js +138 -0
- package/assets/app-DPmI-MvN.css +1 -0
- package/assets/arc-G6n0TtRf.js +1 -0
- package/assets/architectureDiagram-5GKGNRK7-TQ4_iSAG.js +36 -0
- package/assets/asmcrypto-DZeQH0LC.js +1 -0
- package/assets/blockDiagram-I7D4REHJ-1q050zQd.js +129 -0
- package/assets/c4Diagram-7LVT6UL2-BXn0hW62.js +38 -0
- package/assets/{catalog-dk6a0I1u.js → catalog-Cpx4OH_r.js} +3 -3
- package/assets/channel-JZzXP_E5.js +1 -0
- package/assets/chunk-2Q5K7J3B-BSnocdBz.js +1 -0
- package/assets/chunk-5VM5RSS4-BvfoMSpq.js +15 -0
- package/assets/chunk-F27PBJKO-CMyFcBlX.js +1 -0
- package/assets/chunk-IMKFNOWR--8Y02hwt.js +231 -0
- package/assets/chunk-JWPE2WC7-C18m6v8d.js +1 -0
- package/assets/chunk-POPQ4Y6H-DzoTZe0a.js +1 -0
- package/assets/chunk-SVP7TREG-Ce98IXh7.js +88 -0
- package/assets/chunk-TICWLB2K-BeLoUlaL.js +206 -0
- package/assets/chunk-XXDRQBXY-DCZSZ1aW.js +1 -0
- package/assets/classDiagram-ZZMXUADV-jA4zeKxs.js +1 -0
- package/assets/classDiagram-v2-VYDZK3BY-jA4zeKxs.js +1 -0
- package/assets/controls/ckeditor.es.js +9382 -9320
- package/assets/controls/handsontable.es.js +11971 -11938
- package/assets/controls/jsoneditor.es.js +10442 -10233
- package/assets/controls/toast-ui.es.js +1308 -1279
- package/assets/cose-bilkent-JH36ORCC-Cy9np608.js +1 -0
- package/assets/cynefin-OW5HDTMX-Ba6uP79R.js +166 -0
- package/assets/cynefinDiagram-5FMLGOSQ-DhMBBq7T.js +62 -0
- package/assets/cytoscape.esm-DzSz-X2X.js +321 -0
- package/assets/dagre-GXQ25YYZ-BdAnQUCV.js +4 -0
- package/assets/dashboard-BFVFBIio.js +3 -0
- package/assets/debounce-B3mXI_sx.js +1 -0
- package/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/assets/diagram-S7CK7UJ4-BDYUGbNl.js +30 -0
- package/assets/diagram-UQ7AKVKN-PURK0-X-.js +41 -0
- package/assets/diagram-VSXAHHWV-DxHdFxl9.js +3 -0
- package/assets/diagram-VX7I27RA-Vz8xmeYc.js +24 -0
- package/assets/diagram-Z3DM3KII-CFXUpBMy.js +24 -0
- package/assets/docs-DU3h-HtS.js +1 -0
- package/assets/ebnfDiagram-PWID7BFC-BXNvxMGP.js +1 -0
- package/assets/erDiagram-RLTQ6QDP-DN0GgZEI.js +99 -0
- package/assets/flowDiagram-HODETNUW-DXdnwgBv.js +168 -0
- package/assets/ganttDiagram-EL5Y4UJY-Cv8_dxX3.js +292 -0
- package/assets/gitGraphDiagram-WWUBYQGX-BO8T858E.js +106 -0
- package/assets/history-BmwWpckq.js +1 -0
- package/assets/infoDiagram-27XIBGKW-DMSJ1RNc.js +2 -0
- package/assets/init-Gi6I4Gst.js +1 -0
- package/assets/init-user-BIdB8U_4.js +1 -0
- package/assets/isObject-DL-BuaBl.js +1 -0
- package/assets/ishikawaDiagram-5VMMS53U-_tLJESbl.js +70 -0
- package/assets/journeyDiagram-3NMN7TZE-DMMhdo-_.js +139 -0
- package/assets/kanban-definition-UXKFOSKX-CxQrB4t6.js +89 -0
- package/assets/katex-HP8lGamR.js +257 -0
- package/assets/layout-_oS_xxCJ.js +1 -0
- package/assets/linear-VLYuNK3x.js +1 -0
- package/assets/{list-CcMtS4hD.js → list-YTyJQtiR.js} +2 -2
- package/assets/login-B9GlhjZh.js +1 -0
- package/assets/manifest.json +834 -58
- package/assets/media-manager-Bb1VWlzd.js +6 -0
- package/assets/mermaid.core-BknV2vft.js +310 -0
- package/assets/mindmap-definition-YA3MSWOX-DBjjjDq4.js +96 -0
- package/assets/module-DQ1NK8Xg.js +1 -0
- package/assets/notification-CgNOsXvn.js +20 -0
- package/assets/ordinal-Cboi1Yqb.js +1 -0
- package/assets/pegDiagram-XKGWAZYB-uRznTaO4.js +1 -0
- package/assets/pieDiagram-E7YTZNPT-D1TkCH8I.js +39 -0
- package/assets/quadrantDiagram-AXDQQJYC-BofJ36Jz.js +7 -0
- package/assets/railroadDiagram-O6MQD6OU-e1ZM2IT1.js +1 -0
- package/assets/register-DjTtMayF.js +1 -0
- package/assets/requirementDiagram-BXWQKSXE-DQWArRbb.js +84 -0
- package/assets/ru-CqAtka69.js +1 -0
- package/assets/sankeyDiagram-P5KCCOFB-fVOu2jzi.js +40 -0
- package/assets/sequenceDiagram-WJ2MYXX4-Dj24cqpu.js +162 -0
- package/assets/sizeCapture-INFHLROL-CsApVrVi.js +1 -0
- package/assets/stateDiagram-D77RDMKH-IU2Szlid.js +1 -0
- package/assets/stateDiagram-v2-MP3YSRHH-B-EW3cIC.js +1 -0
- package/assets/swimlanes-42K2YHIH-97rBqTR-.js +1 -0
- package/assets/swimlanesDiagram-VR7AAH4N-CqG2iydK.js +8 -0
- package/assets/timeline-definition-24CTP7MA-DSgRzx1V.js +120 -0
- package/assets/toNumber-DZb-r9sy.js +1 -0
- package/assets/use-appearance-CXz2xWgT.js +1 -0
- package/assets/user-filters-list-BAR4CBN_.js +1 -0
- package/assets/vennDiagram-4TSXK5OY-BqMksFxE.js +34 -0
- package/assets/wardleyDiagram-VM6X3IG4-BMmxRODC.js +78 -0
- package/assets/welcome-CPXT7_4b.js +1 -0
- package/assets/with-app-layout-C6dqetfx.js +31 -0
- package/assets/xychartDiagram-S5SC5T6Z-3xRuF0l4.js +7 -0
- package/controllers/access-rights/permissionOptions.d.ts +2 -0
- package/controllers/access-rights/permissionOptions.js +23 -0
- package/controllers/add.js +2 -2
- package/controllers/addGroup.js +15 -6
- package/controllers/ai/AiAgentController.d.ts +66 -0
- package/controllers/ai/AiAgentController.js +435 -0
- package/controllers/ai/AiAssistantController.d.ts +0 -4
- package/controllers/ai/AiAssistantController.js +4 -78
- package/controllers/catalog/FrontendCatalogAdapter.js +7 -8
- package/controllers/docs/index.d.ts +22 -0
- package/controllers/docs/index.js +126 -0
- package/controllers/edit.js +2 -2
- package/controllers/editGroup.js +15 -6
- package/controllers/filter-fields/savedFilters.js +1 -1
- package/controllers/history-actions/HistoryController.js +7 -7
- package/controllers/inlineUpdate.js +10 -3
- package/controllers/list.js +1 -1
- package/controllers/login.js +1 -1
- package/controllers/notifications/NotificationController.js +14 -8
- package/controllers/view.js +3 -3
- package/controllers/widgets/filterInfo.js +1 -1
- package/helpers/accessRightsHelper.d.ts +63 -2
- package/helpers/accessRightsHelper.js +202 -13
- package/helpers/assetVersionHelper.d.ts +14 -0
- package/helpers/assetVersionHelper.js +42 -0
- package/helpers/configHelper.js +19 -18
- package/helpers/controllerHelper.d.ts +1 -0
- package/helpers/controllerHelper.js +25 -15
- package/helpers/fieldsHelper.d.ts +2 -1
- package/helpers/fieldsHelper.js +11 -5
- package/helpers/inertiaActionsHelper.d.ts +1 -1
- package/helpers/inertiaActionsHelper.js +4 -5
- package/helpers/inertiaAddHelper.d.ts +1 -1
- package/helpers/inertiaAddHelper.js +7 -6
- package/helpers/inertiaGroupHelper.js +10 -2
- package/helpers/inertiaListHelper.d.ts +1 -1
- package/helpers/inertiaListHelper.js +7 -8
- package/helpers/inertiaMenuHelper.d.ts +7 -2
- package/helpers/inertiaMenuHelper.js +12 -27
- package/helpers/menuHelper.d.ts +11 -1
- package/helpers/menuHelper.js +12 -0
- package/helpers/modelResourceHelper.d.ts +9 -0
- package/helpers/modelResourceHelper.js +43 -0
- package/helpers/navigationAccessHelper.d.ts +13 -0
- package/helpers/navigationAccessHelper.js +45 -0
- package/index.d.ts +11 -0
- package/index.js +11 -0
- package/interfaces/adminpanelConfig.d.ts +160 -2
- package/interfaces/queryCriteria.d.ts +13 -0
- package/interfaces/types.d.ts +169 -0
- package/lib/Adminizer.d.ts +8 -0
- package/lib/Adminizer.js +30 -2
- package/lib/DataAccessor.d.ts +100 -3
- package/lib/DataAccessor.js +454 -245
- package/lib/access-graph/AccessGraphResolver.d.ts +136 -0
- package/lib/access-graph/AccessGraphResolver.js +605 -0
- package/lib/access-graph/RecordAccessCache.d.ts +23 -0
- package/lib/access-graph/RecordAccessCache.js +30 -0
- package/lib/access-graph/RecordAccessResolver.d.ts +83 -0
- package/lib/access-graph/RecordAccessResolver.js +295 -0
- package/lib/access-graph/shared.d.ts +60 -0
- package/lib/access-graph/shared.js +121 -0
- package/lib/admin-links/AdminLinkHandler.d.ts +72 -0
- package/lib/admin-links/AdminLinkHandler.js +183 -0
- package/lib/ai-assistant/AbstractAiConversationHistoryService.d.ts +39 -0
- package/lib/ai-assistant/AbstractAiConversationHistoryService.js +98 -0
- package/lib/ai-assistant/AbstractAiModelService.d.ts +139 -2
- package/lib/ai-assistant/AbstractAiModelService.js +154 -1
- package/lib/ai-assistant/AiAgentRunManager.d.ts +35 -0
- package/lib/ai-assistant/AiAgentRunManager.js +109 -0
- package/lib/ai-assistant/AiAssistantAgentSkillHandler.d.ts +61 -0
- package/lib/ai-assistant/AiAssistantAgentSkillHandler.js +90 -0
- package/lib/ai-assistant/AiAssistantHandler.d.ts +1 -0
- package/lib/ai-assistant/AiAssistantHandler.js +4 -0
- package/lib/ai-assistant/AiAssistantUiMethodHandler.d.ts +60 -0
- package/lib/ai-assistant/AiAssistantUiMethodHandler.js +144 -0
- package/lib/ai-assistant/builtinAgentSkills.d.ts +9 -0
- package/lib/ai-assistant/builtinAgentSkills.js +259 -0
- package/lib/ai-assistant/jsonSafe.d.ts +13 -0
- package/lib/ai-assistant/jsonSafe.js +81 -0
- package/lib/app-manager/AdminizerApp.d.ts +40 -1
- package/lib/app-manager/AppManager.js +48 -42
- package/lib/app-manager/ConfigLayerHandler.js +4 -0
- package/lib/docs/AbstractDocumentation.d.ts +100 -0
- package/lib/docs/AbstractDocumentation.js +108 -0
- package/lib/docs/DocumentationHandler.d.ts +100 -0
- package/lib/docs/DocumentationHandler.js +198 -0
- package/lib/docs/FileDocumentation.d.ts +47 -0
- package/lib/docs/FileDocumentation.js +189 -0
- package/lib/docs/docsAgentSkills.d.ts +10 -0
- package/lib/docs/docsAgentSkills.js +93 -0
- package/lib/filters/FilterService.js +4 -3
- package/lib/history-actions/AbstractHistoryAdapter.d.ts +26 -1
- package/lib/history-actions/AbstractHistoryAdapter.js +155 -25
- package/lib/media-manager/helpers/MediaManagerHelper.js +9 -7
- package/lib/model/AbstractModel.d.ts +48 -0
- package/lib/model/AbstractModel.js +14 -4
- package/lib/model/ModelHandler.d.ts +61 -2
- package/lib/model/ModelHandler.js +136 -8
- package/lib/model/adapter/sequelize.d.ts +40 -1
- package/lib/model/adapter/sequelize.js +243 -77
- package/lib/model/adapter/typeorm.d.ts +16 -0
- package/lib/model/adapter/typeorm.js +136 -9
- package/lib/notifications/GeneralNotificationService.js +6 -5
- package/lib/notifications/SystemNotificationService.js +1 -1
- package/lib/ui-i18n/uiTranslationKeys.d.ts +2 -1
- package/lib/ui-i18n/uiTranslationKeys.js +42 -1
- package/lib/widgets/widgetHandler.js +10 -6
- package/models/Group.d.ts +2 -1
- package/package.json +35 -23
- package/policies/authPolicies.js +6 -6
- package/policies/permissionResolvers.d.ts +1 -0
- package/policies/permissionResolvers.js +3 -0
- package/system/Router.js +21 -3
- package/system/bindAccessRights.js +13 -3
- package/system/bindDocs.d.ts +13 -0
- package/system/bindDocs.js +77 -0
- package/system/bindInertia.js +30 -7
- package/system/buildInternalModelAccess.d.ts +9 -0
- package/system/buildInternalModelAccess.js +37 -4
- package/system/defaults.js +7 -1
- package/system/validateSystemModels.js +29 -9
- package/assets/add-CBlLpLr0.js +0 -1
- package/assets/add-group-H0IKIsNm.js +0 -1
- package/assets/add-user-CoTLmxtF.js +0 -1
- package/assets/app-DNXiApCk.css +0 -1
- package/assets/app-layout-xxJpLAvb.js +0 -1
- package/assets/app.js +0 -138
- package/assets/dashboard-DVJS5EPP.js +0 -3
- package/assets/debounce-BMmXVQ06.js +0 -1
- package/assets/history-bWRuzbye.js +0 -1
- package/assets/init-user-8aUohVDq.js +0 -1
- package/assets/login-CO4laf6m.js +0 -1
- package/assets/media-manager-BDbpSSjH.js +0 -6
- package/assets/module-CqO_BKsH.js +0 -1
- package/assets/notification-B5l2wwmi.js +0 -20
- package/assets/register-DBYIMXGQ.js +0 -1
- package/assets/ru-rYKdQbWF.js +0 -1
- package/assets/use-appearance-_EbbjHTh.js +0 -1
- package/assets/user-filters-list-C7kMKN5f.js +0 -1
- package/assets/welcome-6z-7sXnQ.js +0 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { resolveModelResource } from '../../helpers/modelResourceHelper.js';
|
|
2
|
+
import { getUiTranslations } from '../../lib/ui-i18n/getUiTranslations.js';
|
|
3
|
+
import { DOCS_UI_TRANSLATION_KEYS } from '../../lib/ui-i18n/uiTranslationKeys.js';
|
|
4
|
+
import { DataAccessor } from '../../lib/DataAccessor.js';
|
|
5
|
+
/**
|
|
6
|
+
* Thin JSON proxy of the documentation subsystem: policies have checked the
|
|
7
|
+
* auth and the base token, `DocumentationHandler` applies per-document rights,
|
|
8
|
+
* the implementation does the actual work. No logic lives here.
|
|
9
|
+
*/
|
|
10
|
+
function requestedLocale(req) {
|
|
11
|
+
const locale = typeof req.query.locale === 'string' ? req.query.locale.trim() : '';
|
|
12
|
+
return locale || req.adminizer.documentationHandler.localeFor(req.user);
|
|
13
|
+
}
|
|
14
|
+
function describeMeta(meta) {
|
|
15
|
+
return {
|
|
16
|
+
id: meta.id,
|
|
17
|
+
title: meta.title,
|
|
18
|
+
section: meta.section,
|
|
19
|
+
keywords: meta.keywords,
|
|
20
|
+
models: meta.models,
|
|
21
|
+
urls: meta.urls,
|
|
22
|
+
locales: meta.locales,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export async function docsList(req, res) {
|
|
26
|
+
const documents = await req.adminizer.documentationHandler.list(req.user, requestedLocale(req));
|
|
27
|
+
return res.json({ documents: documents.map(describeMeta) });
|
|
28
|
+
}
|
|
29
|
+
export async function docsKeywords(req, res) {
|
|
30
|
+
const keywords = await req.adminizer.documentationHandler.keywords(req.user, requestedLocale(req));
|
|
31
|
+
return res.json({ keywords: Object.fromEntries(keywords) });
|
|
32
|
+
}
|
|
33
|
+
export async function docsSearch(req, res) {
|
|
34
|
+
const keywords = typeof req.query.keywords === 'string'
|
|
35
|
+
? req.query.keywords.split(',').map((keyword) => keyword.trim()).filter(Boolean)
|
|
36
|
+
: undefined;
|
|
37
|
+
const results = await req.adminizer.documentationHandler.search(req.user, {
|
|
38
|
+
query: typeof req.query.q === 'string' ? req.query.q : undefined,
|
|
39
|
+
keywords,
|
|
40
|
+
url: typeof req.query.url === 'string' ? req.query.url : undefined,
|
|
41
|
+
model: typeof req.query.model === 'string' ? req.query.model : undefined,
|
|
42
|
+
}, requestedLocale(req));
|
|
43
|
+
return res.json({
|
|
44
|
+
results: results.map((result) => ({ ...describeMeta(result.meta), snippets: result.snippets })),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
export async function docsContext(req, res) {
|
|
48
|
+
const documents = await req.adminizer.documentationHandler.forContext(req.user, {
|
|
49
|
+
url: typeof req.query.url === 'string' ? req.query.url : undefined,
|
|
50
|
+
model: typeof req.query.model === 'string' ? req.query.model : undefined,
|
|
51
|
+
}, requestedLocale(req));
|
|
52
|
+
return res.json({ documents: documents.map(describeMeta) });
|
|
53
|
+
}
|
|
54
|
+
export async function docsDoc(req, res) {
|
|
55
|
+
const content = await req.adminizer.documentationHandler.get(req.user, String(req.params.id ?? ''), requestedLocale(req));
|
|
56
|
+
// A document the user may not read does not exist for them.
|
|
57
|
+
if (!content) {
|
|
58
|
+
return res.status(404).json({ error: req.i18n?.__('Not found') || 'Not found' });
|
|
59
|
+
}
|
|
60
|
+
return res.json({ meta: describeMeta(content.meta), markdown: content.markdown, locale: content.locale });
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Resolves references written inside a document to document ids. How a
|
|
64
|
+
* reference is spelled is the implementation's business; the viewer only
|
|
65
|
+
* learns which of them point at a document this user may open — the rest stay
|
|
66
|
+
* unresolved and are rendered as plain text.
|
|
67
|
+
*/
|
|
68
|
+
export async function docsResolve(req, res) {
|
|
69
|
+
const from = typeof req.query.from === 'string' ? req.query.from : '';
|
|
70
|
+
// `refs` is the comma-separated form the viewer uses; repeated `ref`
|
|
71
|
+
// parameters are accepted as well for hand-written calls.
|
|
72
|
+
const raw = req.query.ref;
|
|
73
|
+
const refs = [
|
|
74
|
+
...(typeof req.query.refs === 'string' ? req.query.refs.split(',') : []),
|
|
75
|
+
...(Array.isArray(raw) ? raw : [raw]),
|
|
76
|
+
]
|
|
77
|
+
.filter((ref) => typeof ref === 'string' && ref.trim() !== '')
|
|
78
|
+
.map((ref) => ref.trim());
|
|
79
|
+
const resolved = {};
|
|
80
|
+
for (const ref of refs) {
|
|
81
|
+
const id = await req.adminizer.documentationHandler.resolveLink(req.user, from, ref);
|
|
82
|
+
if (id)
|
|
83
|
+
resolved[ref] = id;
|
|
84
|
+
}
|
|
85
|
+
return res.json({ resolved });
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The viewer page. The document itself is fetched by the page through the API,
|
|
89
|
+
* so a deep link only has to name the document it opens with.
|
|
90
|
+
*/
|
|
91
|
+
export async function docsPage(req) {
|
|
92
|
+
return req.Inertia.render({
|
|
93
|
+
component: 'docs',
|
|
94
|
+
props: {
|
|
95
|
+
title: req.i18n.__('Documentation'),
|
|
96
|
+
docId: typeof req.params.id === 'string' ? req.params.id : null,
|
|
97
|
+
i18nPage: getUiTranslations(req, DOCS_UI_TRANSLATION_KEYS),
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Structure of a model resource as this user is allowed to see it — the same
|
|
103
|
+
* fields DataAccessor would hand to the list page, nothing more.
|
|
104
|
+
*/
|
|
105
|
+
export async function docsSchema(req, res) {
|
|
106
|
+
const name = typeof req.query.model === 'string' ? req.query.model.trim() : '';
|
|
107
|
+
const resource = name ? resolveModelResource(req.adminizer, name) : undefined;
|
|
108
|
+
if (!resource) {
|
|
109
|
+
return res.status(404).json({ error: req.i18n.__('Model not found') });
|
|
110
|
+
}
|
|
111
|
+
if (!await req.adminizer.accessRightsHelper.checkPermission(`read-${resource.name}-model`, req.user)) {
|
|
112
|
+
return res.status(403).json({ error: req.i18n.__('Access denied') });
|
|
113
|
+
}
|
|
114
|
+
const fields = new DataAccessor(req.adminizer, req.user, resource, 'list').getFieldsConfig() ?? {};
|
|
115
|
+
return res.json({
|
|
116
|
+
model: resource.name,
|
|
117
|
+
title: req.i18n.__(resource.config?.title ?? resource.name),
|
|
118
|
+
url: resource.uri,
|
|
119
|
+
fields: Object.entries(fields).map(([key, field]) => ({
|
|
120
|
+
name: key,
|
|
121
|
+
title: field.config?.title ? req.i18n.__(field.config.title) : key,
|
|
122
|
+
type: String(field.config?.type ?? field.model?.type ?? 'string'),
|
|
123
|
+
required: Boolean(field.config?.required ?? field.model?.required),
|
|
124
|
+
})),
|
|
125
|
+
});
|
|
126
|
+
}
|
package/controllers/edit.js
CHANGED
|
@@ -33,7 +33,7 @@ export default async function edit(req, res) {
|
|
|
33
33
|
}
|
|
34
34
|
let fields = dataAccessor.getFieldsConfig();
|
|
35
35
|
// add deprecated 'records' to config
|
|
36
|
-
fields = await FieldsHelper.loadAssociations(req, fields, "edit");
|
|
36
|
+
fields = await FieldsHelper.loadAssociations(req, fields, "edit", dataAccessor.recordAccessCache);
|
|
37
37
|
// Save
|
|
38
38
|
if (req.method.toUpperCase() === 'POST') {
|
|
39
39
|
const identifierField = modelResource.config.identifierField || req.adminizer.config.identifierField;
|
|
@@ -134,7 +134,7 @@ export default async function edit(req, res) {
|
|
|
134
134
|
});
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
|
-
const props = inertiaAddHelper(req, modelResource, fields, record);
|
|
137
|
+
const props = await inertiaAddHelper(req, modelResource, fields, record);
|
|
138
138
|
if (req.query?.without_layout) {
|
|
139
139
|
return res.json({
|
|
140
140
|
props: props
|
package/controllers/editGroup.js
CHANGED
|
@@ -44,13 +44,22 @@ export default async function editGroup(req, res) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
}
|
|
48
|
+
for (const token of allTokens) {
|
|
49
|
+
if (req.body[`token-checkbox-${token.id}`] !== true)
|
|
50
|
+
continue;
|
|
51
|
+
if (!token.getOptions) {
|
|
52
|
+
tokensOfThisGroup.push(token.id);
|
|
53
|
+
continue;
|
|
53
54
|
}
|
|
55
|
+
const submittedRights = req.body[`token-record-rights-${token.id}`];
|
|
56
|
+
const rights = Array.isArray(submittedRights)
|
|
57
|
+
? Array.from(new Set(submittedRights.map(String)))
|
|
58
|
+
: [];
|
|
59
|
+
tokensOfThisGroup.push({
|
|
60
|
+
tokenId: token.id,
|
|
61
|
+
rights,
|
|
62
|
+
});
|
|
54
63
|
}
|
|
55
64
|
let updatedGroup;
|
|
56
65
|
try {
|
|
@@ -125,7 +125,7 @@ export async function saveFilter(req, res) {
|
|
|
125
125
|
}
|
|
126
126
|
// Determine visibility and groupIds
|
|
127
127
|
const isAdmin = req.user?.isAdministrator === true;
|
|
128
|
-
const canManageGroupVisibility = isAdmin || req.adminizer.accessRightsHelper.
|
|
128
|
+
const canManageGroupVisibility = isAdmin || await req.adminizer.accessRightsHelper.checkPermission(GROUP_FILTER_VISIBILITY_TOKEN, req.user);
|
|
129
129
|
let effectiveVisibility = 'private';
|
|
130
130
|
let effectiveGroupIds;
|
|
131
131
|
if (visibility === 'public' && isAdmin) {
|
|
@@ -3,13 +3,13 @@ import { getUiTranslations } from "../../lib/ui-i18n/getUiTranslations.js";
|
|
|
3
3
|
import { HISTORY_UI_TRANSLATION_KEYS } from "../../lib/ui-i18n/uiTranslationKeys.js";
|
|
4
4
|
export class HistoryController {
|
|
5
5
|
static async index(req, res) {
|
|
6
|
-
if (!HistoryController.checkHistoryPermission(req, res))
|
|
6
|
+
if (!await HistoryController.checkHistoryPermission(req, res))
|
|
7
7
|
return;
|
|
8
8
|
const adapter = HistoryController.getAdapter(req);
|
|
9
9
|
if (req.method.toUpperCase() === 'GET') {
|
|
10
|
-
const rawModels = adapter.getModels(req.user);
|
|
10
|
+
const rawModels = await adapter.getModels(req.user);
|
|
11
11
|
let users = [];
|
|
12
|
-
const accessToUsersHistory = req.adminizer.accessRightsHelper.
|
|
12
|
+
const accessToUsersHistory = await req.adminizer.accessRightsHelper.checkAnyPermission([
|
|
13
13
|
`users-history-${adapter.id}`
|
|
14
14
|
], req.user);
|
|
15
15
|
if (accessToUsersHistory)
|
|
@@ -59,7 +59,7 @@ export class HistoryController {
|
|
|
59
59
|
return res.status(405).end();
|
|
60
60
|
}
|
|
61
61
|
static async getAllModelHistory(req, res) {
|
|
62
|
-
if (!HistoryController.checkHistoryPermission(req, res))
|
|
62
|
+
if (!await HistoryController.checkHistoryPermission(req, res))
|
|
63
63
|
return;
|
|
64
64
|
const { modelId, modelName } = req.body;
|
|
65
65
|
if (!modelId || !modelName) {
|
|
@@ -80,7 +80,7 @@ export class HistoryController {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
static async getModelFieldsHistory(req, res) {
|
|
83
|
-
if (!HistoryController.checkHistoryPermission(req, res))
|
|
83
|
+
if (!await HistoryController.checkHistoryPermission(req, res))
|
|
84
84
|
return;
|
|
85
85
|
const { historyId } = req.body;
|
|
86
86
|
if (!historyId) {
|
|
@@ -103,12 +103,12 @@ export class HistoryController {
|
|
|
103
103
|
const adapter = req.adminizer.config.history?.adapter ?? 'default';
|
|
104
104
|
return req.adminizer.historyHandler.get(adapter);
|
|
105
105
|
}
|
|
106
|
-
static checkHistoryPermission(req, res) {
|
|
106
|
+
static async checkHistoryPermission(req, res) {
|
|
107
107
|
if (!req.adminizer?.historyHandler) {
|
|
108
108
|
res.status(401).json({ error: 'History system not initialized' });
|
|
109
109
|
return false;
|
|
110
110
|
}
|
|
111
|
-
const hasPermission = req.adminizer.accessRightsHelper.
|
|
111
|
+
const hasPermission = await req.adminizer.accessRightsHelper.checkPermission(`history-${req.adminizer.config.history?.adapter ?? 'default'}`, req.user);
|
|
112
112
|
if (!hasPermission) {
|
|
113
113
|
res.sendStatus(403);
|
|
114
114
|
return false;
|
|
@@ -62,10 +62,17 @@ export default async function inlineUpdate(req, res) {
|
|
|
62
62
|
const convertedValue = convertInlineValue(newValue, fieldType);
|
|
63
63
|
// Update record
|
|
64
64
|
const id = req.params.id;
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
// Reuse the resolved identifier: without the primary-key fallback the key is
|
|
66
|
+
// `undefined`, the adapter drops the unknown condition, and the update hits every
|
|
67
|
+
// record the user may reach instead of the one addressed by :id.
|
|
68
|
+
const params = { [identifierField]: id };
|
|
67
69
|
try {
|
|
68
|
-
await modelResource.model.update(params, { [fieldName]: convertedValue }, dataAccessor);
|
|
70
|
+
const updated = await modelResource.model.update(params, { [fieldName]: convertedValue }, dataAccessor);
|
|
71
|
+
// Record access is enforced by the criteria, so a record out of the user's reach
|
|
72
|
+
// simply matches nothing. Reporting success would leave the edited value on screen.
|
|
73
|
+
if (!updated.length) {
|
|
74
|
+
return res.status(404).json({ error: 'Record not found' });
|
|
75
|
+
}
|
|
69
76
|
return res.json({
|
|
70
77
|
success: true,
|
|
71
78
|
data: {
|
package/controllers/list.js
CHANGED
|
@@ -14,7 +14,7 @@ export default async function list(req, res) {
|
|
|
14
14
|
}
|
|
15
15
|
let dataAccessor = new DataAccessor(req.adminizer, req.user, modelResource, "list");
|
|
16
16
|
let fields = dataAccessor.getFieldsConfig();
|
|
17
|
-
const header = inertiaListHelper(modelResource, req, fields);
|
|
17
|
+
const header = await inertiaListHelper(modelResource, req, fields);
|
|
18
18
|
const i18nPage = getUiTranslations(req, FILTER_UI_TRANSLATION_KEYS);
|
|
19
19
|
// Parse pagination
|
|
20
20
|
const defaultPageSize = req.adminizer.config.list?.defaultPageSize ?? 50;
|
package/controllers/login.js
CHANGED
|
@@ -61,7 +61,7 @@ export default async function login(req, res) {
|
|
|
61
61
|
//Here we use the captchaSolution key to output messages unrelated to the form fields.
|
|
62
62
|
return inertiaAdminMessage(req, "Profile expired, contact the administrator", 'captchaSolution');
|
|
63
63
|
}
|
|
64
|
-
if (!req.adminizer.accessRightsHelper.
|
|
64
|
+
if (!await req.adminizer.accessRightsHelper.checkPermission('access-to-adminpanel', user)) {
|
|
65
65
|
return inertiaAdminMessage(req, "The user is not allowed to enter, please contact the administrator", 'captchaSolution');
|
|
66
66
|
}
|
|
67
67
|
const token = signUser({
|
|
@@ -7,7 +7,7 @@ export class NotificationController {
|
|
|
7
7
|
return;
|
|
8
8
|
if (req.method.toUpperCase() === 'POST') {
|
|
9
9
|
const { s, notificationClass } = req.body;
|
|
10
|
-
const hasPermission = req.adminizer.accessRightsHelper.
|
|
10
|
+
const hasPermission = await req.adminizer.accessRightsHelper.checkPermission(`notification-${notificationClass}`, req.user);
|
|
11
11
|
if (!hasPermission) {
|
|
12
12
|
res.status(403).json({ error: 'Forbidden' });
|
|
13
13
|
return;
|
|
@@ -42,7 +42,7 @@ export class NotificationController {
|
|
|
42
42
|
const services = req.adminizer.notificationHandler.getAllServices();
|
|
43
43
|
let activeServices = [];
|
|
44
44
|
for (const service of services) {
|
|
45
|
-
const hasPermission = req.adminizer.accessRightsHelper.
|
|
45
|
+
const hasPermission = await req.adminizer.accessRightsHelper.checkPermission(`notification-${service.notificationClass}`, req.user);
|
|
46
46
|
if (hasPermission) {
|
|
47
47
|
activeServices.push({
|
|
48
48
|
displayName: req.i18n.__(service.displayName),
|
|
@@ -68,12 +68,12 @@ export class NotificationController {
|
|
|
68
68
|
res.write(': stream-open\n\n');
|
|
69
69
|
const clientId = `user-${req.user?.id}-${Date.now()}`;
|
|
70
70
|
// Function for sending events to the client
|
|
71
|
-
const sendEvent = (event) => {
|
|
71
|
+
const sendEvent = async (event) => {
|
|
72
72
|
// Filtering notifications by user rights
|
|
73
73
|
if (event.type === 'notification') {
|
|
74
74
|
const notificationClass = event.notificationClass;
|
|
75
75
|
// UNIFIED rights check via AccessRightsHelper
|
|
76
|
-
const hasPermission = req.adminizer.accessRightsHelper.
|
|
76
|
+
const hasPermission = await req.adminizer.accessRightsHelper.checkPermission(`notification-${notificationClass}`, req.user);
|
|
77
77
|
if (!hasPermission) {
|
|
78
78
|
return; // The user does not have rights to this notification class
|
|
79
79
|
}
|
|
@@ -88,7 +88,10 @@ export class NotificationController {
|
|
|
88
88
|
};
|
|
89
89
|
// We connect the client to all services
|
|
90
90
|
const services = req.adminizer.notificationHandler.getAllServices();
|
|
91
|
-
const allowedServices = services.
|
|
91
|
+
const allowedServices = (await Promise.all(services.map(async (service) => ({
|
|
92
|
+
service,
|
|
93
|
+
allowed: await req.adminizer.accessRightsHelper.checkPermission(`notification-${service.notificationClass}`, req.user),
|
|
94
|
+
})))).filter(({ allowed }) => allowed).map(({ service }) => service);
|
|
92
95
|
allowedServices.forEach(service => {
|
|
93
96
|
service.addClient(clientId, sendEvent, req.user);
|
|
94
97
|
// For a system service, add a client to CRUD channels
|
|
@@ -100,7 +103,7 @@ export class NotificationController {
|
|
|
100
103
|
// });
|
|
101
104
|
// }
|
|
102
105
|
});
|
|
103
|
-
sendEvent({
|
|
106
|
+
await sendEvent({
|
|
104
107
|
type: 'connected',
|
|
105
108
|
data: {
|
|
106
109
|
message: 'Connected to unified notification stream',
|
|
@@ -144,7 +147,7 @@ export class NotificationController {
|
|
|
144
147
|
const { notificationClass } = req.params;
|
|
145
148
|
const { limit = 20, skip = 0, unreadOnly = false } = req.query;
|
|
146
149
|
// Checking access rights
|
|
147
|
-
const hasPermission = req.adminizer.accessRightsHelper.
|
|
150
|
+
const hasPermission = await req.adminizer.accessRightsHelper.checkPermission(`notification-${notificationClass}`, req.user);
|
|
148
151
|
if (!hasPermission) {
|
|
149
152
|
res.status(403).json({ error: 'Forbidden' });
|
|
150
153
|
return;
|
|
@@ -170,7 +173,10 @@ export class NotificationController {
|
|
|
170
173
|
try {
|
|
171
174
|
// Filtering services by access rights
|
|
172
175
|
const services = req.adminizer.notificationHandler.getAllServices();
|
|
173
|
-
const allowedServices = services.
|
|
176
|
+
const allowedServices = (await Promise.all(services.map(async (service) => ({
|
|
177
|
+
service,
|
|
178
|
+
allowed: await req.adminizer.accessRightsHelper.checkPermission(`notification-${service.notificationClass}`, req.user),
|
|
179
|
+
})))).filter(({ allowed }) => allowed).map(({ service }) => service);
|
|
174
180
|
const allNotifications = [];
|
|
175
181
|
for (const service of allowedServices) {
|
|
176
182
|
const notifications = await service.getNotifications(req.user.id, Number(limit), Number(skip), unreadOnly === 'true');
|
package/controllers/view.js
CHANGED
|
@@ -29,7 +29,7 @@ export default async function view(req, res) {
|
|
|
29
29
|
Adminizer.log.error(e);
|
|
30
30
|
return res.status(500).send({ error: 'Internal Server Error' });
|
|
31
31
|
}
|
|
32
|
-
switch (modelResource.
|
|
32
|
+
switch (modelResource.name) {
|
|
33
33
|
case 'User':
|
|
34
34
|
let groups;
|
|
35
35
|
try {
|
|
@@ -71,7 +71,7 @@ export default async function view(req, res) {
|
|
|
71
71
|
props: groupProps
|
|
72
72
|
});
|
|
73
73
|
default:
|
|
74
|
-
fields = await FieldsHelper.loadAssociations(req, fields, "edit");
|
|
74
|
+
fields = await FieldsHelper.loadAssociations(req, fields, "edit", dataAccessor.recordAccessCache);
|
|
75
75
|
for (const field of Object.keys(fields)) {
|
|
76
76
|
let fieldConfigConfig = fields[field].config;
|
|
77
77
|
if (fieldConfigConfig.type === 'mediamanager') {
|
|
@@ -83,7 +83,7 @@ export default async function view(req, res) {
|
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
-
const props = inertiaAddHelper(req, modelResource, fields, record, true);
|
|
86
|
+
const props = await inertiaAddHelper(req, modelResource, fields, record, true);
|
|
87
87
|
return req.Inertia.render({
|
|
88
88
|
component: 'add',
|
|
89
89
|
props: props
|
|
@@ -28,7 +28,7 @@ export async function widgetFilterInfoController(req, res) {
|
|
|
28
28
|
if (!filter || !filterService.canViewFilter(filter, user)) {
|
|
29
29
|
return res.status(404).send({ error: req.i18n.__("Not found") });
|
|
30
30
|
}
|
|
31
|
-
if (!req.adminizer.accessRightsHelper.
|
|
31
|
+
if (!await req.adminizer.accessRightsHelper.checkPermission(`read-${filter.modelName}-model`, user)) {
|
|
32
32
|
return res.sendStatus(403);
|
|
33
33
|
}
|
|
34
34
|
if (req.method.toUpperCase() === "GET") {
|
|
@@ -1,20 +1,81 @@
|
|
|
1
1
|
import { User } from "../models/User.js";
|
|
2
|
-
import { AccessRightsToken } from "../interfaces/types.js";
|
|
2
|
+
import { AccessRightsToken, GroupPermissionGrant, PermissionContext } from "../interfaces/types.js";
|
|
3
3
|
import { Adminizer } from "../lib/Adminizer.js";
|
|
4
|
+
export declare function parseGroupPermissionGrant(value: unknown): GroupPermissionGrant | null;
|
|
4
5
|
export declare class AccessRightsHelper {
|
|
5
6
|
private _tokens;
|
|
7
|
+
private warnedDeprecations;
|
|
6
8
|
adminizer: Adminizer;
|
|
7
9
|
constructor(adminizer: Adminizer);
|
|
8
10
|
registerToken(accessRightsToken: AccessRightsToken): void;
|
|
9
11
|
unregisterToken(tokenId: string): boolean;
|
|
10
12
|
hasToken(tokenId: string): boolean;
|
|
13
|
+
getToken(tokenId: string): AccessRightsToken | undefined;
|
|
11
14
|
registerTokens(accessRightsTokens: AccessRightsToken[]): void;
|
|
12
15
|
registerModelTokens(modelName: string, department?: string): void;
|
|
13
16
|
getTokens(): AccessRightsToken[];
|
|
14
17
|
getTokensByDepartment(department: string): AccessRightsToken[];
|
|
15
18
|
getAllDepartments(): string[];
|
|
19
|
+
/**
|
|
20
|
+
* The full access decision: the token exists, a group of this user carries
|
|
21
|
+
* it, and — for a contextual token — its own `check` accepts the context.
|
|
22
|
+
*
|
|
23
|
+
* This is the only entry point that can honour a contextual `check`, and the
|
|
24
|
+
* one every Adminizer call site uses. It replaces {@link hasPermission},
|
|
25
|
+
* which stays synchronous forever so that modules written against it cannot
|
|
26
|
+
* be broken open by a missing `await`.
|
|
27
|
+
*/
|
|
28
|
+
checkPermission(tokenId: string | undefined, user: User, context?: PermissionContext): Promise<boolean>;
|
|
29
|
+
/** Grants when {@link checkPermission} grants any of `tokens`. */
|
|
30
|
+
checkAnyPermission(tokens: string[], user: User): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Synchronous access decision for call sites that cannot await, such as the
|
|
33
|
+
* field configuration built by `DataAccessor`.
|
|
34
|
+
*
|
|
35
|
+
* A contextual token is denied here: its `check` is asynchronous and there
|
|
36
|
+
* is no context to hand it, so the only safe answer is "no". Model CRUD
|
|
37
|
+
* tokens never declare `check`, so the ordinary panel paths are unaffected.
|
|
38
|
+
*/
|
|
39
|
+
hasStaticPermission(tokenId: string | undefined, user: User): boolean;
|
|
40
|
+
/** Fail-closed synchronous variant of {@link checkAnyPermission}. */
|
|
41
|
+
enoughStaticPermissions(tokens: string[], user: User): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* @deprecated Use {@link checkPermission}, which can honour a contextual
|
|
44
|
+
* token's `check`. This method stays synchronous and fails closed.
|
|
45
|
+
*
|
|
46
|
+
* Kept synchronous deliberately, and never to be made `async`. Modules and
|
|
47
|
+
* apps outside this repository call it as `if (hasPermission(...))`, and we
|
|
48
|
+
* can neither find nor warn them at compile time. An `async` version returns
|
|
49
|
+
* a pending promise there, which is truthy — every such check would grant
|
|
50
|
+
* access to everyone. So this signature is frozen at `boolean`: the worst a
|
|
51
|
+
* stale caller gets is a denial it can see, never a silent grant.
|
|
52
|
+
*/
|
|
53
|
+
hasPermission(tokenId: string | undefined, user: User): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* @deprecated Use {@link checkAnyPermission}. Synchronous and fail-closed
|
|
56
|
+
* for the same reason as {@link hasPermission}.
|
|
57
|
+
*/
|
|
16
58
|
enoughPermissions(tokens: string[], user: User): boolean;
|
|
17
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Warns once per call site, with the caller's frame so the module still
|
|
61
|
+
* using it can actually be found.
|
|
62
|
+
*
|
|
63
|
+
* Keyed on the frame rather than the method name: an app reaching the
|
|
64
|
+
* deprecated method through the runtime bridge used to spend the single
|
|
65
|
+
* warning of the whole process on a frame naming `AppManager` instead of
|
|
66
|
+
* itself, after which no other stale caller could ever surface.
|
|
67
|
+
*/
|
|
68
|
+
private warnDeprecated;
|
|
69
|
+
/**
|
|
70
|
+
* The first stack frame outside the forwarding layer — this helper and the
|
|
71
|
+
* app-manager bridge — which is the module that actually made the call.
|
|
72
|
+
*/
|
|
73
|
+
private resolveCallerFrame;
|
|
74
|
+
/** Returns trusted option IDs granted by all groups for a contextual token. */
|
|
75
|
+
getPermissionRights(tokenId: string | undefined, user: User): string[] | null;
|
|
76
|
+
private getRegisteredToken;
|
|
77
|
+
private hasAssignedPermission;
|
|
78
|
+
private runTokenCheck;
|
|
18
79
|
}
|
|
19
80
|
export declare class GroupsAccessRightsHelper {
|
|
20
81
|
static hasAccess(user: User, groupsAccessRights?: string[]): boolean;
|