ldap-rest 0.0.1
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/LICENSE +661 -0
- package/README.md +48 -0
- package/bin/cleanup-external-users.mjs +219 -0
- package/bin/index.mjs +54 -0
- package/bin/sync-james.mjs +190 -0
- package/dist/abstract/ldapFlat.js +1 -0
- package/dist/abstract/plugin.js +1 -0
- package/dist/abstract/twakePlugin.js +1 -0
- package/dist/bin/index.js +1 -0
- package/dist/config/args.js +1 -0
- package/dist/lib/auth/base.js +1 -0
- package/dist/lib/authz/base.js +1 -0
- package/dist/lib/expressFormatedResponses.js +1 -0
- package/dist/lib/ldapActions.js +1 -0
- package/dist/lib/parseConfig.js +1 -0
- package/dist/lib/utils.js +1 -0
- package/dist/logger/winston.js +1 -0
- package/dist/plugins/auth/authzLinid1.js +1 -0
- package/dist/plugins/auth/authzPerBranch.js +1 -0
- package/dist/plugins/auth/crowdsec.js +1 -0
- package/dist/plugins/auth/llng.js +1 -0
- package/dist/plugins/auth/openidconnect.js +1 -0
- package/dist/plugins/auth/rateLimit.js +1 -0
- package/dist/plugins/auth/token.js +1 -0
- package/dist/plugins/configApi.js +1 -0
- package/dist/plugins/demo/helloworld.js +1 -0
- package/dist/plugins/ldap/bulkImport.js +1 -0
- package/dist/plugins/ldap/departmentSync.js +1 -0
- package/dist/plugins/ldap/externalUsersInGroups.js +1 -0
- package/dist/plugins/ldap/flatGeneric.js +1 -0
- package/dist/plugins/ldap/groups.js +1 -0
- package/dist/plugins/ldap/onChange.js +1 -0
- package/dist/plugins/ldap/organization.js +1 -0
- package/dist/plugins/ldap/trash.js +1 -0
- package/dist/plugins/priority.json.js +1 -0
- package/dist/plugins/static.js +1 -0
- package/dist/plugins/twake/calendarResources.js +1 -0
- package/dist/plugins/twake/james.js +1 -0
- package/dist/plugins/weblogs.js +1 -0
- package/dist/src/abstract/ldapFlat.d.ts +94 -0
- package/dist/src/abstract/plugin.d.ts +43 -0
- package/dist/src/abstract/twakePlugin.d.ts +90 -0
- package/dist/src/bin/index.d.ts +43 -0
- package/dist/src/browser/ldap-group-editor/LdapGroupEditor.d.ts +31 -0
- package/dist/src/browser/ldap-group-editor/api/GroupApiClient.d.ts +23 -0
- package/dist/src/browser/ldap-group-editor/components/CreateGroupModal.d.ts +19 -0
- package/dist/src/browser/ldap-group-editor/components/GroupPropertyEditor.d.ts +30 -0
- package/dist/src/browser/ldap-group-editor/components/GroupTree.d.ts +17 -0
- package/dist/src/browser/ldap-group-editor/components/MoveGroupModal.d.ts +20 -0
- package/dist/src/browser/ldap-group-editor/index.d.ts +6 -0
- package/dist/src/browser/ldap-group-editor/types.d.ts +58 -0
- package/dist/src/browser/ldap-resource-editor/api/ResourceApiClient.d.ts +53 -0
- package/dist/src/browser/ldap-resource-editor/cache/CacheManager.d.ts +65 -0
- package/dist/src/browser/ldap-resource-editor/index.d.ts +6 -0
- package/dist/src/browser/ldap-resource-editor/types.d.ts +78 -0
- package/dist/src/browser/ldap-tree-viewer/LdapTreeViewer.d.ts +26 -0
- package/dist/src/browser/ldap-tree-viewer/api/LdapApiClient.d.ts +30 -0
- package/dist/src/browser/ldap-tree-viewer/components/TreeNode.d.ts +17 -0
- package/dist/src/browser/ldap-tree-viewer/components/TreeRoot.d.ts +14 -0
- package/dist/src/browser/ldap-tree-viewer/index.d.ts +7 -0
- package/dist/src/browser/ldap-tree-viewer/store/Store.d.ts +18 -0
- package/dist/src/browser/ldap-tree-viewer/store/actions.d.ts +28 -0
- package/dist/src/browser/ldap-tree-viewer/store/reducers.d.ts +7 -0
- package/dist/src/browser/ldap-tree-viewer/types.d.ts +34 -0
- package/dist/src/browser/ldap-unit-editor/LdapUnitEditor.d.ts +27 -0
- package/dist/src/browser/ldap-unit-editor/api/UnitApiClient.d.ts +24 -0
- package/dist/src/browser/ldap-unit-editor/components/MoveUnitModal.d.ts +28 -0
- package/dist/src/browser/ldap-unit-editor/components/UnitPropertyEditor.d.ts +28 -0
- package/dist/src/browser/ldap-unit-editor/components/UnitTree.d.ts +18 -0
- package/dist/src/browser/ldap-unit-editor/index.d.ts +6 -0
- package/dist/src/browser/ldap-unit-editor/types.d.ts +55 -0
- package/dist/src/browser/ldap-user-editor/LdapUserEditor.d.ts +32 -0
- package/dist/src/browser/ldap-user-editor/api/UserApiClient.d.ts +55 -0
- package/dist/src/browser/ldap-user-editor/cache/CacheManager.d.ts +65 -0
- package/dist/src/browser/ldap-user-editor/components/MoveUserModal.d.ts +28 -0
- package/dist/src/browser/ldap-user-editor/components/PointerField.d.ts +22 -0
- package/dist/src/browser/ldap-user-editor/components/UserEditor.d.ts +46 -0
- package/dist/src/browser/ldap-user-editor/components/UserList.d.ts +24 -0
- package/dist/src/browser/ldap-user-editor/components/UserTree.d.ts +24 -0
- package/dist/src/browser/ldap-user-editor/index.d.ts +7 -0
- package/dist/src/browser/ldap-user-editor/types.d.ts +67 -0
- package/dist/src/browser/shared/components/Modal.d.ts +38 -0
- package/dist/src/browser/shared/components/StatusMessage.d.ts +48 -0
- package/dist/src/browser/shared/index.d.ts +10 -0
- package/dist/src/browser/shared/types/index.d.ts +65 -0
- package/dist/src/browser/shared/utils/dom.d.ts +33 -0
- package/dist/src/browser/shared/utils/form.d.ts +28 -0
- package/dist/src/browser/shared/utils/schema.d.ts +29 -0
- package/dist/src/config/args.d.ts +128 -0
- package/dist/src/config/schema.d.ts +18 -0
- package/dist/src/hooks.d.ts +76 -0
- package/dist/src/lib/auth/base.d.ts +9 -0
- package/dist/src/lib/authz/base.d.ts +63 -0
- package/dist/src/lib/expressFormatedResponses.d.ts +26 -0
- package/dist/src/lib/ldapActions.d.ts +91 -0
- package/dist/src/lib/parseConfig.d.ts +23 -0
- package/dist/src/lib/utils.d.ts +73 -0
- package/dist/src/logger/winston.d.ts +3 -0
- package/dist/src/plugins/auth/authzLinid1.d.ts +46 -0
- package/dist/src/plugins/auth/authzPerBranch.d.ts +59 -0
- package/dist/src/plugins/auth/crowdsec.d.ts +29 -0
- package/dist/src/plugins/auth/llng.d.ts +8 -0
- package/dist/src/plugins/auth/openidconnect.d.ts +11 -0
- package/dist/src/plugins/auth/rateLimit.d.ts +24 -0
- package/dist/src/plugins/auth/token.d.ts +17 -0
- package/dist/src/plugins/configApi.d.ts +24 -0
- package/dist/src/plugins/demo/helloworld.d.ts +12 -0
- package/dist/src/plugins/ldap/bulkImport.d.ts +48 -0
- package/dist/src/plugins/ldap/departmentSync.d.ts +35 -0
- package/dist/src/plugins/ldap/externalUsersInGroups.d.ts +21 -0
- package/dist/src/plugins/ldap/flatGeneric.d.ts +31 -0
- package/dist/src/plugins/ldap/groups.d.ts +95 -0
- package/dist/src/plugins/ldap/onChange.d.ts +32 -0
- package/dist/src/plugins/ldap/organization.d.ts +65 -0
- package/dist/src/plugins/ldap/trash.d.ts +52 -0
- package/dist/src/plugins/static.d.ts +7 -0
- package/dist/src/plugins/twake/calendarResources.d.ts +31 -0
- package/dist/src/plugins/twake/james.d.ts +83 -0
- package/dist/src/plugins/weblogs.d.ts +8 -0
- package/docs/DEVELOPER_GUIDE.md +298 -0
- package/docs/OPENAPI.md +440 -0
- package/docs/PLUGIN_DEPENDENCIES.md +612 -0
- package/docs/README.md +466 -0
- package/docs/api/REFERENCE.md +983 -0
- package/docs/api/REST_API.md +1237 -0
- package/docs/authentication.md +469 -0
- package/docs/authzLinid1.md +521 -0
- package/docs/authzPerBranch.md +596 -0
- package/docs/browser/LIBRARIES.md +1196 -0
- package/docs/examples/EXAMPLES.md +1659 -0
- package/docs/examples/twake-mailbox-type-nomenclature.ldif +37 -0
- package/docs/examples/twake-mailbox-type-schema-amendment.ldif +30 -0
- package/docs/jamesTeamMailboxes.md +343 -0
- package/docs/ldapBulkImport.md +722 -0
- package/docs/ldapExternalUsersInGroups.md +325 -0
- package/docs/ldapFlatGeneric.md +444 -0
- package/docs/ldapGroups.md +589 -0
- package/docs/ldapOrganizations.md +659 -0
- package/docs/ldapTrash.md +260 -0
- package/docs/linagora.png +0 -0
- package/docs/onChange.md +318 -0
- package/docs/plugins/DEVELOPMENT.md +1219 -0
- package/docs/presenterm-fr.md +721 -0
- package/docs/presenterm.md +721 -0
- package/docs/schemas/SCHEMAS.md +1212 -0
- package/docs/static.md +410 -0
- package/docs/twakeCalendarResources.md +148 -0
- package/docs/twakeJames.md +661 -0
- package/docs/weblogs.md +399 -0
- package/examples/web/ldap-group-editor.html +113 -0
- package/examples/web/ldap-tree-viewer.html +368 -0
- package/examples/web/ldap-unit-editor.html +108 -0
- package/examples/web/ldap-user-editor.html +412 -0
- package/package.json +458 -0
- package/static/browser/common-demo.css +450 -0
- package/static/browser/ldap-group-editor/LdapGroupEditor.js +207 -0
- package/static/browser/ldap-group-editor/LdapGroupEditor.js.map +1 -0
- package/static/browser/ldap-group-editor/api/GroupApiClient.js +112 -0
- package/static/browser/ldap-group-editor/api/GroupApiClient.js.map +1 -0
- package/static/browser/ldap-group-editor/components/CreateGroupModal.js +212 -0
- package/static/browser/ldap-group-editor/components/CreateGroupModal.js.map +1 -0
- package/static/browser/ldap-group-editor/components/GroupPropertyEditor.js +497 -0
- package/static/browser/ldap-group-editor/components/GroupPropertyEditor.js.map +1 -0
- package/static/browser/ldap-group-editor/components/GroupTree.js +153 -0
- package/static/browser/ldap-group-editor/components/GroupTree.js.map +1 -0
- package/static/browser/ldap-group-editor/components/MoveGroupModal.js +235 -0
- package/static/browser/ldap-group-editor/components/MoveGroupModal.js.map +1 -0
- package/static/browser/ldap-group-editor/index.js +2 -0
- package/static/browser/ldap-group-editor/index.js.map +1 -0
- package/static/browser/ldap-group-editor/types.js +2 -0
- package/static/browser/ldap-group-editor/types.js.map +1 -0
- package/static/browser/ldap-group-editor.esm.js +1399 -0
- package/static/browser/ldap-group-editor.esm.js.map +1 -0
- package/static/browser/ldap-group-editor.js +1407 -0
- package/static/browser/ldap-group-editor.js.map +1 -0
- package/static/browser/ldap-group-editor.min.js +2 -0
- package/static/browser/ldap-group-editor.min.js.map +1 -0
- package/static/browser/ldap-resource-editor/api/ResourceApiClient.js +226 -0
- package/static/browser/ldap-resource-editor/api/ResourceApiClient.js.map +1 -0
- package/static/browser/ldap-resource-editor/cache/CacheManager.js +123 -0
- package/static/browser/ldap-resource-editor/cache/CacheManager.js.map +1 -0
- package/static/browser/ldap-resource-editor/index.js +2 -0
- package/static/browser/ldap-resource-editor/index.js.map +1 -0
- package/static/browser/ldap-resource-editor/types.js +2 -0
- package/static/browser/ldap-resource-editor/types.js.map +1 -0
- package/static/browser/ldap-tree-viewer/LdapTreeViewer.js +187 -0
- package/static/browser/ldap-tree-viewer/LdapTreeViewer.js.map +1 -0
- package/static/browser/ldap-tree-viewer/api/LdapApiClient.js +50 -0
- package/static/browser/ldap-tree-viewer/api/LdapApiClient.js.map +1 -0
- package/static/browser/ldap-tree-viewer/components/TreeNode.js +140 -0
- package/static/browser/ldap-tree-viewer/components/TreeNode.js.map +1 -0
- package/static/browser/ldap-tree-viewer/components/TreeRoot.js +98 -0
- package/static/browser/ldap-tree-viewer/components/TreeRoot.js.map +1 -0
- package/static/browser/ldap-tree-viewer/index.js +2 -0
- package/static/browser/ldap-tree-viewer/index.js.map +1 -0
- package/static/browser/ldap-tree-viewer/store/Store.js +29 -0
- package/static/browser/ldap-tree-viewer/store/Store.js.map +1 -0
- package/static/browser/ldap-tree-viewer/store/actions.js +53 -0
- package/static/browser/ldap-tree-viewer/store/actions.js.map +1 -0
- package/static/browser/ldap-tree-viewer/store/reducers.js +79 -0
- package/static/browser/ldap-tree-viewer/store/reducers.js.map +1 -0
- package/static/browser/ldap-tree-viewer/types.js +2 -0
- package/static/browser/ldap-tree-viewer/types.js.map +1 -0
- package/static/browser/ldap-tree-viewer.css +2 -0
- package/static/browser/ldap-tree-viewer.css.map +1 -0
- package/static/browser/ldap-tree-viewer.esm.js +606 -0
- package/static/browser/ldap-tree-viewer.esm.js.map +1 -0
- package/static/browser/ldap-tree-viewer.js +614 -0
- package/static/browser/ldap-tree-viewer.js.map +1 -0
- package/static/browser/ldap-tree-viewer.min.js +2 -0
- package/static/browser/ldap-tree-viewer.min.js.map +1 -0
- package/static/browser/ldap-unit-editor/LdapUnitEditor.js +119 -0
- package/static/browser/ldap-unit-editor/LdapUnitEditor.js.map +1 -0
- package/static/browser/ldap-unit-editor/api/UnitApiClient.js +95 -0
- package/static/browser/ldap-unit-editor/api/UnitApiClient.js.map +1 -0
- package/static/browser/ldap-unit-editor/components/MoveUnitModal.js +295 -0
- package/static/browser/ldap-unit-editor/components/MoveUnitModal.js.map +1 -0
- package/static/browser/ldap-unit-editor/components/UnitPropertyEditor.js +321 -0
- package/static/browser/ldap-unit-editor/components/UnitPropertyEditor.js.map +1 -0
- package/static/browser/ldap-unit-editor/components/UnitTree.js +197 -0
- package/static/browser/ldap-unit-editor/components/UnitTree.js.map +1 -0
- package/static/browser/ldap-unit-editor/index.js +2 -0
- package/static/browser/ldap-unit-editor/index.js.map +1 -0
- package/static/browser/ldap-unit-editor/types.js +2 -0
- package/static/browser/ldap-unit-editor/types.js.map +1 -0
- package/static/browser/ldap-unit-editor.esm.js +1013 -0
- package/static/browser/ldap-unit-editor.esm.js.map +1 -0
- package/static/browser/ldap-unit-editor.js +1021 -0
- package/static/browser/ldap-unit-editor.js.map +1 -0
- package/static/browser/ldap-unit-editor.min.js +2 -0
- package/static/browser/ldap-unit-editor.min.js.map +1 -0
- package/static/browser/ldap-user-editor/LdapUserEditor.js +184 -0
- package/static/browser/ldap-user-editor/LdapUserEditor.js.map +1 -0
- package/static/browser/ldap-user-editor/api/UserApiClient.js +209 -0
- package/static/browser/ldap-user-editor/api/UserApiClient.js.map +1 -0
- package/static/browser/ldap-user-editor/cache/CacheManager.js +123 -0
- package/static/browser/ldap-user-editor/cache/CacheManager.js.map +1 -0
- package/static/browser/ldap-user-editor/components/MoveUserModal.js +302 -0
- package/static/browser/ldap-user-editor/components/MoveUserModal.js.map +1 -0
- package/static/browser/ldap-user-editor/components/PointerField.js +152 -0
- package/static/browser/ldap-user-editor/components/PointerField.js.map +1 -0
- package/static/browser/ldap-user-editor/components/UserEditor.js +608 -0
- package/static/browser/ldap-user-editor/components/UserEditor.js.map +1 -0
- package/static/browser/ldap-user-editor/components/UserList.js +181 -0
- package/static/browser/ldap-user-editor/components/UserList.js.map +1 -0
- package/static/browser/ldap-user-editor/components/UserTree.js +164 -0
- package/static/browser/ldap-user-editor/components/UserTree.js.map +1 -0
- package/static/browser/ldap-user-editor/index.js +2 -0
- package/static/browser/ldap-user-editor/index.js.map +1 -0
- package/static/browser/ldap-user-editor/types.js +2 -0
- package/static/browser/ldap-user-editor/types.js.map +1 -0
- package/static/browser/ldap-user-editor.css +2 -0
- package/static/browser/ldap-user-editor.css.map +1 -0
- package/static/browser/ldap-user-editor.esm.js +1897 -0
- package/static/browser/ldap-user-editor.esm.js.map +1 -0
- package/static/browser/ldap-user-editor.js +1905 -0
- package/static/browser/ldap-user-editor.js.map +1 -0
- package/static/browser/ldap-user-editor.min.js +2 -0
- package/static/browser/ldap-user-editor.min.js.map +1 -0
- package/static/browser/shared/components/Modal.js +121 -0
- package/static/browser/shared/components/Modal.js.map +1 -0
- package/static/browser/shared/components/StatusMessage.js +119 -0
- package/static/browser/shared/components/StatusMessage.js.map +1 -0
- package/static/browser/shared/index.js +7 -0
- package/static/browser/shared/index.js.map +1 -0
- package/static/browser/shared/types/index.js +37 -0
- package/static/browser/shared/types/index.js.map +1 -0
- package/static/browser/shared/utils/dom.js +78 -0
- package/static/browser/shared/utils/dom.js.map +1 -0
- package/static/browser/shared/utils/form.js +234 -0
- package/static/browser/shared/utils/form.js.map +1 -0
- package/static/browser/shared/utils/schema.js +121 -0
- package/static/browser/shared/utils/schema.js.map +1 -0
- package/static/browser/shared.esm.js +694 -0
- package/static/browser/shared.esm.js.map +1 -0
- package/static/browser/shared.js +723 -0
- package/static/browser/shared.js.map +1 -0
- package/static/browser/shared.min.js +2 -0
- package/static/browser/shared.min.js.map +1 -0
- package/static/schemas/ad/computers.json +92 -0
- package/static/schemas/ad/groups.json +55 -0
- package/static/schemas/ad/organizations.json +66 -0
- package/static/schemas/ad/users.json +116 -0
- package/static/schemas/standard/devices.json +72 -0
- package/static/schemas/standard/groups.json +54 -0
- package/static/schemas/standard/organizations.json +49 -0
- package/static/schemas/standard/users.json +86 -0
- package/static/schemas/twake/groups.json +75 -0
- package/static/schemas/twake/nomenclature/twakeAccountStatus.json +31 -0
- package/static/schemas/twake/nomenclature/twakeDeliveryMode.json +31 -0
- package/static/schemas/twake/nomenclature/twakeListType.json +31 -0
- package/static/schemas/twake/nomenclature/twakeMailboxType.json +31 -0
- package/static/schemas/twake/nomenclature/twakeTitle.json +31 -0
- package/static/schemas/twake/organizations.json +52 -0
- package/static/schemas/twake/positions.json +32 -0
- package/static/schemas/twake/users.json +112 -0
|
@@ -0,0 +1,1013 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit Tree Component - Shows organizations tree (same as UserTree)
|
|
3
|
+
*/
|
|
4
|
+
class UnitTree {
|
|
5
|
+
constructor(container, apiBaseUrl, onOrgSelect) {
|
|
6
|
+
this.expandedNodes = new Set();
|
|
7
|
+
this.selectedDn = null;
|
|
8
|
+
this.rootDn = null;
|
|
9
|
+
this.container = container;
|
|
10
|
+
this.apiBaseUrl = apiBaseUrl;
|
|
11
|
+
this.onOrgSelect = onOrgSelect;
|
|
12
|
+
}
|
|
13
|
+
async init() {
|
|
14
|
+
this.container.innerHTML = `
|
|
15
|
+
<div class="unit-tree">
|
|
16
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
|
17
|
+
<h3 style="margin: 0; display: flex; align-items: center; gap: 8px;">
|
|
18
|
+
<span class="material-icons">account_tree</span>
|
|
19
|
+
Organizations
|
|
20
|
+
</h3>
|
|
21
|
+
<button id="create-unit-btn" style="background: var(--primary-color, #6200ee); color: white; border: none; border-radius: 4px; padding: 8px 12px; cursor: pointer; display: flex; align-items: center; gap: 4px; font-size: 14px;">
|
|
22
|
+
<span class="material-icons" style="font-size: 18px;">add</span>
|
|
23
|
+
Create
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
<div id="tree-content">
|
|
27
|
+
<div class="loading"><div class="spinner"></div></div>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
`;
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch(`${this.apiBaseUrl}/api/v1/ldap/organizations/top`);
|
|
33
|
+
if (!response.ok)
|
|
34
|
+
throw new Error('Failed to load organizations');
|
|
35
|
+
const topOrg = await response.json();
|
|
36
|
+
this.rootDn = topOrg.dn;
|
|
37
|
+
await this.renderTree();
|
|
38
|
+
// Attach create button listener
|
|
39
|
+
const createBtn = this.container.querySelector('#create-unit-btn');
|
|
40
|
+
if (createBtn) {
|
|
41
|
+
createBtn.addEventListener('click', () => this.handleCreateUnit());
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.error('Failed to init tree:', error);
|
|
46
|
+
const treeEl = this.container.querySelector('#tree-content');
|
|
47
|
+
if (treeEl) {
|
|
48
|
+
treeEl.innerHTML =
|
|
49
|
+
'<p style="color: #f44336;">Failed to load organizations</p>';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async handleCreateUnit() {
|
|
54
|
+
const parentDn = this.selectedDn || this.rootDn;
|
|
55
|
+
if (!parentDn) {
|
|
56
|
+
alert('Please select a parent organization first');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// eslint-disable-next-line no-undef
|
|
60
|
+
const ouName = prompt('Enter the name for the new organizational unit (ou):');
|
|
61
|
+
if (!ouName || !ouName.trim())
|
|
62
|
+
return;
|
|
63
|
+
try {
|
|
64
|
+
const response = await fetch(`${this.apiBaseUrl}/api/v1/ldap/organizations`, {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: { 'Content-Type': 'application/json' },
|
|
67
|
+
body: JSON.stringify({
|
|
68
|
+
ou: ouName.trim(),
|
|
69
|
+
parentDn,
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
const error = await response.text();
|
|
74
|
+
throw new Error(error || 'Failed to create organizational unit');
|
|
75
|
+
}
|
|
76
|
+
alert(`Organizational unit "${ouName}" created successfully!`);
|
|
77
|
+
// Expand the parent node and refresh tree
|
|
78
|
+
this.expandedNodes.add(parentDn);
|
|
79
|
+
await this.renderTree();
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error('Failed to create unit:', error);
|
|
83
|
+
alert(`Failed to create unit: ${error.message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async renderTree() {
|
|
87
|
+
const treeEl = this.container.querySelector('#tree-content');
|
|
88
|
+
if (!treeEl || !this.rootDn)
|
|
89
|
+
return;
|
|
90
|
+
try {
|
|
91
|
+
const html = await this.renderNode(this.rootDn, 0);
|
|
92
|
+
treeEl.innerHTML = html;
|
|
93
|
+
this.attachEventListeners();
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
console.error('Failed to render tree:', error);
|
|
97
|
+
treeEl.innerHTML = '<p style="color: #f44336;">Error rendering tree</p>';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async renderNode(dn, level) {
|
|
101
|
+
const isExpanded = this.expandedNodes.has(dn);
|
|
102
|
+
const isSelected = this.selectedDn === dn;
|
|
103
|
+
const indent = level * 20;
|
|
104
|
+
// Get node info
|
|
105
|
+
const response = await fetch(`${this.apiBaseUrl}/api/v1/ldap/organizations/${encodeURIComponent(dn)}`);
|
|
106
|
+
if (!response.ok)
|
|
107
|
+
throw new Error('Failed to load node');
|
|
108
|
+
const node = await response.json();
|
|
109
|
+
// Extract display name
|
|
110
|
+
let displayName = dn;
|
|
111
|
+
if (node.ou) {
|
|
112
|
+
displayName = Array.isArray(node.ou) ? node.ou[0] : node.ou;
|
|
113
|
+
}
|
|
114
|
+
else if (node.cn) {
|
|
115
|
+
displayName = Array.isArray(node.cn) ? node.cn[0] : node.cn;
|
|
116
|
+
}
|
|
117
|
+
let html = `
|
|
118
|
+
<div class="tree-node ${isSelected ? 'active' : ''}" style="padding-left: ${indent}px; padding: 8px 8px 8px ${indent}px; cursor: pointer; display: flex; align-items: center; gap: 4px;">
|
|
119
|
+
<span class="material-icons tree-node-toggle" data-dn="${this.escapeHtml(dn)}" style="font-size: 18px; cursor: pointer;">
|
|
120
|
+
${isExpanded ? 'expand_more' : 'chevron_right'}
|
|
121
|
+
</span>
|
|
122
|
+
<span class="material-icons" style="font-size: 18px;">business</span>
|
|
123
|
+
<span class="tree-node-label" data-dn="${this.escapeHtml(dn)}">${this.escapeHtml(displayName)}</span>
|
|
124
|
+
</div>
|
|
125
|
+
`;
|
|
126
|
+
if (isExpanded) {
|
|
127
|
+
try {
|
|
128
|
+
const subnodesResponse = await fetch(`${this.apiBaseUrl}/api/v1/ldap/organizations/${encodeURIComponent(dn)}/subnodes`);
|
|
129
|
+
if (!subnodesResponse.ok)
|
|
130
|
+
throw new Error('Failed to load subnodes');
|
|
131
|
+
const subnodes = await subnodesResponse.json();
|
|
132
|
+
const orgs = subnodes.filter((n) => {
|
|
133
|
+
const classes = Array.isArray(n.objectClass)
|
|
134
|
+
? n.objectClass
|
|
135
|
+
: n.objectClass
|
|
136
|
+
? [n.objectClass]
|
|
137
|
+
: [];
|
|
138
|
+
return (classes.includes('organizationalUnit') ||
|
|
139
|
+
classes.includes('organization'));
|
|
140
|
+
});
|
|
141
|
+
for (const subnode of orgs) {
|
|
142
|
+
html += await this.renderNode(subnode.dn, level + 1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
console.error('Failed to load subnodes for', dn, error);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return html;
|
|
150
|
+
}
|
|
151
|
+
attachEventListeners() {
|
|
152
|
+
// Toggle expand/collapse
|
|
153
|
+
this.container.querySelectorAll('.tree-node-toggle').forEach(el => {
|
|
154
|
+
el.addEventListener('click', async (e) => {
|
|
155
|
+
e.stopPropagation();
|
|
156
|
+
const dn = el.getAttribute('data-dn');
|
|
157
|
+
if (!dn)
|
|
158
|
+
return;
|
|
159
|
+
if (this.expandedNodes.has(dn)) {
|
|
160
|
+
this.expandedNodes.delete(dn);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
this.expandedNodes.add(dn);
|
|
164
|
+
}
|
|
165
|
+
await this.renderTree();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
// Select organization
|
|
169
|
+
this.container.querySelectorAll('.tree-node-label').forEach(el => {
|
|
170
|
+
el.addEventListener('click', e => {
|
|
171
|
+
e.stopPropagation();
|
|
172
|
+
const dn = el.getAttribute('data-dn');
|
|
173
|
+
if (!dn)
|
|
174
|
+
return;
|
|
175
|
+
this.selectedDn = dn;
|
|
176
|
+
this.onOrgSelect(dn);
|
|
177
|
+
this.renderTree();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
escapeHtml(text) {
|
|
182
|
+
if (text == null)
|
|
183
|
+
return '';
|
|
184
|
+
const map = {
|
|
185
|
+
'&': '&',
|
|
186
|
+
'<': '<',
|
|
187
|
+
'>': '>',
|
|
188
|
+
'"': '"',
|
|
189
|
+
"'": ''',
|
|
190
|
+
'/': '/',
|
|
191
|
+
};
|
|
192
|
+
return String(text).replace(/[&<>"'/]/g, m => map[m]);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* LDAP Unit Editor - Move Unit Modal Component
|
|
198
|
+
* Modal dialog for selecting target organization when moving a unit
|
|
199
|
+
*/
|
|
200
|
+
class MoveUnitModal {
|
|
201
|
+
constructor(api, currentOrgDn, onMove) {
|
|
202
|
+
this.modal = null;
|
|
203
|
+
this.selectedOrgDn = null;
|
|
204
|
+
this.expandedNodes = new Set();
|
|
205
|
+
this.rootDn = null;
|
|
206
|
+
this.api = api;
|
|
207
|
+
this.currentOrgDn = currentOrgDn;
|
|
208
|
+
this.onMove = onMove;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Escape HTML special characters to prevent XSS attacks
|
|
212
|
+
*/
|
|
213
|
+
static escapeHtml(text) {
|
|
214
|
+
if (text == null)
|
|
215
|
+
return '';
|
|
216
|
+
const map = {
|
|
217
|
+
'&': '&',
|
|
218
|
+
'<': '<',
|
|
219
|
+
'>': '>',
|
|
220
|
+
'"': '"',
|
|
221
|
+
"'": ''',
|
|
222
|
+
'/': '/',
|
|
223
|
+
};
|
|
224
|
+
return String(text).replace(/[&<>"'/]/g, m => map[m]);
|
|
225
|
+
}
|
|
226
|
+
async show() {
|
|
227
|
+
// Create modal container
|
|
228
|
+
this.modal = document.createElement('div');
|
|
229
|
+
this.modal.className = 'modal-overlay active';
|
|
230
|
+
this.modal.innerHTML = `
|
|
231
|
+
<div class="modal-container" style="max-width: 600px;">
|
|
232
|
+
<div class="modal-header">
|
|
233
|
+
<h3 style="margin: 0; display: flex; align-items: center; gap: 0.5rem;">
|
|
234
|
+
<span class="material-icons">folder_open</span>
|
|
235
|
+
Move Unit to Organization
|
|
236
|
+
</h3>
|
|
237
|
+
<button class="modal-close" id="modal-close-btn">
|
|
238
|
+
<span class="material-icons">close</span>
|
|
239
|
+
</button>
|
|
240
|
+
</div>
|
|
241
|
+
<div class="modal-body">
|
|
242
|
+
<div id="move-modal-alert"></div>
|
|
243
|
+
<div style="margin-bottom: 1rem; color: #666; font-size: 0.9rem;">
|
|
244
|
+
Select the target organization where you want to move this unit.
|
|
245
|
+
</div>
|
|
246
|
+
<div id="organization-tree-container" style="max-height: 400px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; padding: 0.5rem;">
|
|
247
|
+
<div class="loading">
|
|
248
|
+
<div class="spinner"></div>
|
|
249
|
+
<p style="margin-top: 1rem">Loading organizations...</p>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
<div class="modal-footer">
|
|
254
|
+
<button type="button" class="btn btn-secondary" id="modal-cancel-btn">
|
|
255
|
+
Cancel
|
|
256
|
+
</button>
|
|
257
|
+
<button type="button" class="btn btn-primary" id="modal-move-btn" disabled>
|
|
258
|
+
<span class="material-icons">drive_file_move</span>
|
|
259
|
+
Move Unit
|
|
260
|
+
</button>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
`;
|
|
264
|
+
document.body.appendChild(this.modal);
|
|
265
|
+
this.attachHandlers();
|
|
266
|
+
await this.loadOrganizationTree();
|
|
267
|
+
}
|
|
268
|
+
attachHandlers() {
|
|
269
|
+
if (!this.modal)
|
|
270
|
+
return;
|
|
271
|
+
// Close button
|
|
272
|
+
const closeBtn = this.modal.querySelector('#modal-close-btn');
|
|
273
|
+
closeBtn?.addEventListener('click', () => this.close());
|
|
274
|
+
// Cancel button
|
|
275
|
+
const cancelBtn = this.modal.querySelector('#modal-cancel-btn');
|
|
276
|
+
cancelBtn?.addEventListener('click', () => this.close());
|
|
277
|
+
// Move button
|
|
278
|
+
const moveBtn = this.modal.querySelector('#modal-move-btn');
|
|
279
|
+
moveBtn?.addEventListener('click', () => this.handleMove());
|
|
280
|
+
// Close on overlay click
|
|
281
|
+
this.modal.addEventListener('click', e => {
|
|
282
|
+
if (e.target === this.modal) {
|
|
283
|
+
this.close();
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
// Close on Escape key
|
|
287
|
+
const handleEscape = (e) => {
|
|
288
|
+
if (e.key === 'Escape') {
|
|
289
|
+
this.close();
|
|
290
|
+
document.removeEventListener('keydown', handleEscape);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
document.addEventListener('keydown', handleEscape);
|
|
294
|
+
}
|
|
295
|
+
async loadOrganizationTree() {
|
|
296
|
+
const container = this.modal?.querySelector('#organization-tree-container');
|
|
297
|
+
if (!container)
|
|
298
|
+
return;
|
|
299
|
+
try {
|
|
300
|
+
// Get top organization
|
|
301
|
+
const topOrg = await this.api.getOrganizations();
|
|
302
|
+
this.rootDn = topOrg.dn;
|
|
303
|
+
// Expand root by default
|
|
304
|
+
this.expandedNodes.add(this.rootDn);
|
|
305
|
+
await this.renderTree();
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
container.innerHTML = `
|
|
309
|
+
<div class="alert alert-error">
|
|
310
|
+
<span class="material-icons">error</span>
|
|
311
|
+
<div>Failed to load organizations: ${MoveUnitModal.escapeHtml(error.message)}</div>
|
|
312
|
+
</div>
|
|
313
|
+
`;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
async renderTree() {
|
|
317
|
+
const container = this.modal?.querySelector('#organization-tree-container');
|
|
318
|
+
if (!container || !this.rootDn)
|
|
319
|
+
return;
|
|
320
|
+
try {
|
|
321
|
+
const html = await this.renderNode(this.rootDn, 0);
|
|
322
|
+
container.innerHTML = html;
|
|
323
|
+
this.attachTreeHandlers();
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
container.innerHTML = `
|
|
327
|
+
<div class="alert alert-error">
|
|
328
|
+
<span class="material-icons">error</span>
|
|
329
|
+
<div>Error rendering tree: ${MoveUnitModal.escapeHtml(error.message)}</div>
|
|
330
|
+
</div>
|
|
331
|
+
`;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async renderNode(dn, level) {
|
|
335
|
+
const isExpanded = this.expandedNodes.has(dn);
|
|
336
|
+
const isCurrentOrg = dn === this.currentOrgDn;
|
|
337
|
+
const isSelected = dn === this.selectedOrgDn;
|
|
338
|
+
const indent = level * 20;
|
|
339
|
+
// Get node info
|
|
340
|
+
const node = await this.api.getUnit(dn);
|
|
341
|
+
// Extract display name
|
|
342
|
+
const ou = Array.isArray(node.ou) ? node.ou[0] : node.ou;
|
|
343
|
+
const path = node.twakeDepartmentPath
|
|
344
|
+
? Array.isArray(node.twakeDepartmentPath)
|
|
345
|
+
? node.twakeDepartmentPath[0]
|
|
346
|
+
: node.twakeDepartmentPath
|
|
347
|
+
: undefined;
|
|
348
|
+
const displayName = path || ou || dn;
|
|
349
|
+
let html = `
|
|
350
|
+
<div
|
|
351
|
+
class="org-node ${isCurrentOrg ? 'org-current' : ''} ${isSelected ? 'org-selected' : ''}"
|
|
352
|
+
data-org-dn="${MoveUnitModal.escapeHtml(dn)}"
|
|
353
|
+
style="
|
|
354
|
+
padding: 0.5rem;
|
|
355
|
+
padding-left: ${indent + 8}px;
|
|
356
|
+
cursor: ${isCurrentOrg ? 'not-allowed' : 'pointer'};
|
|
357
|
+
background: ${isCurrentOrg ? '#f5f5f5' : isSelected ? '#e3f2fd' : 'white'};
|
|
358
|
+
opacity: ${isCurrentOrg ? '0.6' : '1'};
|
|
359
|
+
display: flex;
|
|
360
|
+
align-items: center;
|
|
361
|
+
gap: 0.5rem;
|
|
362
|
+
border-left: 2px solid ${isSelected ? '#2196f3' : 'transparent'};
|
|
363
|
+
"
|
|
364
|
+
>
|
|
365
|
+
<span class="material-icons tree-toggle" data-dn="${MoveUnitModal.escapeHtml(dn)}" style="cursor: pointer; font-size: 1.2rem;">
|
|
366
|
+
${isExpanded ? 'expand_more' : 'chevron_right'}
|
|
367
|
+
</span>
|
|
368
|
+
<span class="material-icons" style="font-size: 1.2rem; color: ${isCurrentOrg ? '#999' : '#2196f3'};">
|
|
369
|
+
${isCurrentOrg ? 'business' : 'folder'}
|
|
370
|
+
</span>
|
|
371
|
+
<span style="flex: 1; font-weight: ${isSelected ? '600' : '500'};">
|
|
372
|
+
${MoveUnitModal.escapeHtml(displayName)}
|
|
373
|
+
${isCurrentOrg ? '<span style="font-size: 0.85rem; color: #666; margin-left: 0.5rem;">(current)</span>' : ''}
|
|
374
|
+
</span>
|
|
375
|
+
${isSelected ? '<span class="material-icons" style="color: #2196f3;">check_circle</span>' : ''}
|
|
376
|
+
</div>
|
|
377
|
+
`;
|
|
378
|
+
// If expanded, load and render subnodes
|
|
379
|
+
if (isExpanded) {
|
|
380
|
+
try {
|
|
381
|
+
// Use the organizations subnodes API to get only organizational units
|
|
382
|
+
const response = await fetch(`${this.api['baseUrl']}/api/v1/ldap/organizations/${encodeURIComponent(dn)}/subnodes`);
|
|
383
|
+
if (!response.ok) {
|
|
384
|
+
throw new Error('Failed to load subnodes');
|
|
385
|
+
}
|
|
386
|
+
const subnodes = await response.json();
|
|
387
|
+
// Filter for organizational units only
|
|
388
|
+
const orgs = subnodes.filter((n) => {
|
|
389
|
+
const classes = Array.isArray(n.objectClass)
|
|
390
|
+
? n.objectClass
|
|
391
|
+
: n.objectClass
|
|
392
|
+
? [n.objectClass]
|
|
393
|
+
: [];
|
|
394
|
+
return (classes.includes('organizationalUnit') ||
|
|
395
|
+
classes.includes('organization'));
|
|
396
|
+
});
|
|
397
|
+
for (const subnode of orgs) {
|
|
398
|
+
html += await this.renderNode(subnode.dn, level + 1);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
console.error('Failed to load subnodes for', dn, error);
|
|
403
|
+
// No subnodes or error loading them
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return html;
|
|
407
|
+
}
|
|
408
|
+
attachTreeHandlers() {
|
|
409
|
+
// Handle tree toggle (expand/collapse)
|
|
410
|
+
const toggles = this.modal?.querySelectorAll('.tree-toggle');
|
|
411
|
+
toggles?.forEach(toggle => {
|
|
412
|
+
toggle.addEventListener('click', async (e) => {
|
|
413
|
+
e.stopPropagation();
|
|
414
|
+
const dn = toggle.getAttribute('data-dn');
|
|
415
|
+
if (!dn)
|
|
416
|
+
return;
|
|
417
|
+
if (this.expandedNodes.has(dn)) {
|
|
418
|
+
this.expandedNodes.delete(dn);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
this.expandedNodes.add(dn);
|
|
422
|
+
}
|
|
423
|
+
await this.renderTree();
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
// Handle node selection
|
|
427
|
+
const nodes = this.modal?.querySelectorAll('.org-node');
|
|
428
|
+
nodes?.forEach(node => {
|
|
429
|
+
const orgDn = node.getAttribute('data-org-dn');
|
|
430
|
+
if (!orgDn || orgDn === this.currentOrgDn)
|
|
431
|
+
return;
|
|
432
|
+
node.addEventListener('click', () => {
|
|
433
|
+
this.selectOrganization(orgDn);
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
async selectOrganization(orgDn) {
|
|
438
|
+
this.selectedOrgDn = orgDn;
|
|
439
|
+
// Re-render tree to update selection state
|
|
440
|
+
await this.renderTree();
|
|
441
|
+
// Enable move button
|
|
442
|
+
const moveBtn = this.modal?.querySelector('#modal-move-btn');
|
|
443
|
+
if (moveBtn) {
|
|
444
|
+
moveBtn.disabled = false;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
async handleMove() {
|
|
448
|
+
if (!this.selectedOrgDn)
|
|
449
|
+
return;
|
|
450
|
+
const moveBtn = this.modal?.querySelector('#modal-move-btn');
|
|
451
|
+
const alertContainer = this.modal?.querySelector('#move-modal-alert');
|
|
452
|
+
if (moveBtn) {
|
|
453
|
+
moveBtn.disabled = true;
|
|
454
|
+
moveBtn.innerHTML = `
|
|
455
|
+
<span class="spinner" style="width: 1rem; height: 1rem; border-width: 2px;"></span>
|
|
456
|
+
Moving...
|
|
457
|
+
`;
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
this.onMove(this.selectedOrgDn);
|
|
461
|
+
this.close();
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
if (alertContainer) {
|
|
465
|
+
alertContainer.innerHTML = `
|
|
466
|
+
<div class="alert alert-error" style="margin-bottom: 1rem;">
|
|
467
|
+
<span class="material-icons">error</span>
|
|
468
|
+
<div>Move failed: ${MoveUnitModal.escapeHtml(error.message)}</div>
|
|
469
|
+
</div>
|
|
470
|
+
`;
|
|
471
|
+
}
|
|
472
|
+
if (moveBtn) {
|
|
473
|
+
moveBtn.disabled = false;
|
|
474
|
+
moveBtn.innerHTML = `
|
|
475
|
+
<span class="material-icons">drive_file_move</span>
|
|
476
|
+
Move Unit
|
|
477
|
+
`;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
close() {
|
|
482
|
+
if (this.modal) {
|
|
483
|
+
this.modal.remove();
|
|
484
|
+
this.modal = null;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Unit Property Editor Component - Edits properties of a selected organizational unit
|
|
491
|
+
*/
|
|
492
|
+
class UnitPropertyEditor {
|
|
493
|
+
constructor(container, api, unitDn) {
|
|
494
|
+
this.unit = null;
|
|
495
|
+
this.schema = null;
|
|
496
|
+
this.saveCallback = null;
|
|
497
|
+
this.hasSubnodes = false;
|
|
498
|
+
this.container = container;
|
|
499
|
+
this.api = api;
|
|
500
|
+
this.unitDn = unitDn;
|
|
501
|
+
}
|
|
502
|
+
async init() {
|
|
503
|
+
await this.loadUnit();
|
|
504
|
+
await this.loadSchema();
|
|
505
|
+
await this.checkSubnodes();
|
|
506
|
+
this.render();
|
|
507
|
+
this.attachEventListeners();
|
|
508
|
+
}
|
|
509
|
+
async loadUnit() {
|
|
510
|
+
this.unit = await this.api.getUnit(this.unitDn);
|
|
511
|
+
}
|
|
512
|
+
async checkSubnodes() {
|
|
513
|
+
try {
|
|
514
|
+
const response = await fetch(`${this.api['baseUrl']}/api/v1/ldap/organizations/${encodeURIComponent(this.unitDn)}/subnodes`);
|
|
515
|
+
if (response.ok) {
|
|
516
|
+
const subnodes = await response.json();
|
|
517
|
+
// Check if there are any organizational subnodes
|
|
518
|
+
this.hasSubnodes = subnodes.some((node) => {
|
|
519
|
+
const classes = Array.isArray(node.objectClass)
|
|
520
|
+
? node.objectClass
|
|
521
|
+
: node.objectClass
|
|
522
|
+
? [node.objectClass]
|
|
523
|
+
: [];
|
|
524
|
+
return (classes.includes('organizationalUnit') ||
|
|
525
|
+
classes.includes('organization'));
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
catch (error) {
|
|
530
|
+
console.error('Failed to check subnodes:', error);
|
|
531
|
+
// On error, assume it might have subnodes to be safe
|
|
532
|
+
this.hasSubnodes = false;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
async loadSchema() {
|
|
536
|
+
console.warn('[UnitPropertyEditor] Loading schema...');
|
|
537
|
+
const config = await this.api.getConfig();
|
|
538
|
+
// First check in features.ldapOrganizations (from ldapOrganizations plugin)
|
|
539
|
+
const orgsConfig = config.features?.ldapOrganizations;
|
|
540
|
+
console.warn('[UnitPropertyEditor] Organizations config:', orgsConfig);
|
|
541
|
+
if (orgsConfig?.schemaUrl) {
|
|
542
|
+
console.warn('[UnitPropertyEditor] Loading schema from URL:', orgsConfig.schemaUrl);
|
|
543
|
+
const response = await fetch(orgsConfig.schemaUrl);
|
|
544
|
+
this.schema = await response.json();
|
|
545
|
+
console.warn('[UnitPropertyEditor] Schema loaded from URL:', this.schema);
|
|
546
|
+
}
|
|
547
|
+
else if (orgsConfig?.schema) {
|
|
548
|
+
console.warn('[UnitPropertyEditor] Using schema from config');
|
|
549
|
+
this.schema = orgsConfig.schema;
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
// Fallback: check in flatResources
|
|
553
|
+
console.warn('[UnitPropertyEditor] Falling back to flatResources');
|
|
554
|
+
const unitsResource = config.features?.ldapFlatGeneric?.flatResources?.find(r => r.pluralName === 'organizations' || r.name === 'organizations');
|
|
555
|
+
if (unitsResource?.schemaUrl) {
|
|
556
|
+
const response = await fetch(unitsResource.schemaUrl);
|
|
557
|
+
this.schema = await response.json();
|
|
558
|
+
}
|
|
559
|
+
else if (unitsResource?.schema) {
|
|
560
|
+
this.schema = unitsResource.schema;
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
console.warn('[UnitPropertyEditor] No schema found, using default');
|
|
564
|
+
// Create a minimal default schema
|
|
565
|
+
this.schema = {
|
|
566
|
+
entity: {
|
|
567
|
+
objectClass: ['organizationalUnit'],
|
|
568
|
+
},
|
|
569
|
+
attributes: {
|
|
570
|
+
ou: { type: 'string', required: true },
|
|
571
|
+
description: { type: 'string' },
|
|
572
|
+
},
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
onSave(callback) {
|
|
578
|
+
this.saveCallback = callback;
|
|
579
|
+
}
|
|
580
|
+
render() {
|
|
581
|
+
if (!this.unit)
|
|
582
|
+
return;
|
|
583
|
+
this.container.innerHTML = `
|
|
584
|
+
<div class="unit-property-editor">
|
|
585
|
+
<h3 style="margin: 0 0 16px 0; display: flex; align-items: center; gap: 8px;">
|
|
586
|
+
<span class="material-icons">business</span>
|
|
587
|
+
Edit Organizational Unit
|
|
588
|
+
</h3>
|
|
589
|
+
<form id="unit-edit-form">
|
|
590
|
+
${this.renderFields()}
|
|
591
|
+
<div class="form-actions" style="margin-top: 24px; padding-top: 24px; border-top: 1px solid #e0e0e0; display: flex; gap: 12px; justify-content: space-between;">
|
|
592
|
+
<button type="button" id="move-unit-btn" class="btn btn-secondary" style="background: #757575; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; display: flex; align-items: center; gap: 8px;">
|
|
593
|
+
<span class="material-icons" style="font-size: 18px;">drive_file_move</span>
|
|
594
|
+
Move
|
|
595
|
+
</button>
|
|
596
|
+
<div style="display: flex; gap: 12px;">
|
|
597
|
+
<button type="button" id="delete-unit-btn" class="btn btn-danger" ${this.hasSubnodes ? 'disabled title="Cannot delete: organization has sub-organizations"' : ''} style="background: ${this.hasSubnodes ? '#ccc' : '#d32f2f'}; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: ${this.hasSubnodes ? 'not-allowed' : 'pointer'}; display: flex; align-items: center; gap: 8px; opacity: ${this.hasSubnodes ? '0.5' : '1'};">
|
|
598
|
+
<span class="material-icons" style="font-size: 18px;">delete</span>
|
|
599
|
+
Delete Unit
|
|
600
|
+
</button>
|
|
601
|
+
<button type="submit" class="btn btn-primary" style="background: var(--primary-color, #6200ee); color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; display: flex; align-items: center; gap: 8px;">
|
|
602
|
+
<span class="material-icons" style="font-size: 18px;">save</span>
|
|
603
|
+
Save Changes
|
|
604
|
+
</button>
|
|
605
|
+
</div>
|
|
606
|
+
</div>
|
|
607
|
+
</form>
|
|
608
|
+
</div>
|
|
609
|
+
`;
|
|
610
|
+
}
|
|
611
|
+
renderFields() {
|
|
612
|
+
if (!this.unit || !this.schema) {
|
|
613
|
+
return '<div>Loading...</div>';
|
|
614
|
+
}
|
|
615
|
+
let html = '';
|
|
616
|
+
// Group fields by category
|
|
617
|
+
const basicFields = [];
|
|
618
|
+
const otherFields = [];
|
|
619
|
+
for (const [fieldName, attribute] of Object.entries(this.schema.attributes)) {
|
|
620
|
+
if (attribute.fixed || fieldName === 'objectClass')
|
|
621
|
+
continue;
|
|
622
|
+
if (fieldName === 'ou' || fieldName === 'description') {
|
|
623
|
+
basicFields.push([fieldName, attribute]);
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
otherFields.push([fieldName, attribute]);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
// Basic Information
|
|
630
|
+
if (basicFields.length > 0) {
|
|
631
|
+
html +=
|
|
632
|
+
'<div class="field-group" style="margin-bottom: 20px; padding: 16px; background: #f9f9f9; border-radius: 4px;">';
|
|
633
|
+
html +=
|
|
634
|
+
'<div class="field-group-title" style="font-weight: 500; color: var(--primary-color, #6200ee); margin-bottom: 12px; font-size: 14px; text-transform: uppercase;">Basic Information</div>';
|
|
635
|
+
for (const [fieldName, attribute] of basicFields) {
|
|
636
|
+
html += this.renderField(fieldName, attribute);
|
|
637
|
+
}
|
|
638
|
+
html += '</div>';
|
|
639
|
+
}
|
|
640
|
+
// Additional Properties
|
|
641
|
+
if (otherFields.length > 0) {
|
|
642
|
+
html +=
|
|
643
|
+
'<div class="field-group" style="margin-bottom: 20px; padding: 16px; background: #f9f9f9; border-radius: 4px;">';
|
|
644
|
+
html +=
|
|
645
|
+
'<div class="field-group-title" style="font-weight: 500; color: var(--primary-color, #6200ee); margin-bottom: 12px; font-size: 14px; text-transform: uppercase;">Additional Properties</div>';
|
|
646
|
+
for (const [fieldName, attribute] of otherFields) {
|
|
647
|
+
html += this.renderField(fieldName, attribute);
|
|
648
|
+
}
|
|
649
|
+
html += '</div>';
|
|
650
|
+
}
|
|
651
|
+
return html;
|
|
652
|
+
}
|
|
653
|
+
renderField(fieldName, attribute) {
|
|
654
|
+
const value = this.unit?.[fieldName];
|
|
655
|
+
const label = this.getFieldLabel(fieldName);
|
|
656
|
+
const required = attribute.required ? 'required' : '';
|
|
657
|
+
if (attribute.type === 'array') {
|
|
658
|
+
const arrayValue = Array.isArray(value) ? value.join('\n') : '';
|
|
659
|
+
return `
|
|
660
|
+
<div class="form-group" style="margin-bottom: 16px;">
|
|
661
|
+
<label style="display: block; margin-bottom: 8px; font-weight: 500;">${this.escapeHtml(label)}</label>
|
|
662
|
+
<textarea name="${this.escapeHtml(fieldName)}" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; min-height: 80px; font-family: 'Roboto', sans-serif;" ${required}>${this.escapeHtml(arrayValue)}</textarea>
|
|
663
|
+
<div style="font-size: 12px; color: #666; margin-top: 4px;">Enter one value per line</div>
|
|
664
|
+
</div>
|
|
665
|
+
`;
|
|
666
|
+
}
|
|
667
|
+
if (attribute.type === 'number' || attribute.type === 'integer') {
|
|
668
|
+
return `
|
|
669
|
+
<div class="form-group" style="margin-bottom: 16px;">
|
|
670
|
+
<label style="display: block; margin-bottom: 8px; font-weight: 500;">${this.escapeHtml(label)}</label>
|
|
671
|
+
<input type="number" name="${this.escapeHtml(fieldName)}" value="${value || ''}" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px;" ${required} />
|
|
672
|
+
</div>
|
|
673
|
+
`;
|
|
674
|
+
}
|
|
675
|
+
// String field
|
|
676
|
+
return `
|
|
677
|
+
<div class="form-group" style="margin-bottom: 16px;">
|
|
678
|
+
<label style="display: block; margin-bottom: 8px; font-weight: 500;">${this.escapeHtml(label)}</label>
|
|
679
|
+
<input type="text" name="${this.escapeHtml(fieldName)}" value="${this.escapeHtml(String(value || ''))}" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px;" ${required} />
|
|
680
|
+
</div>
|
|
681
|
+
`;
|
|
682
|
+
}
|
|
683
|
+
getFieldLabel(fieldName) {
|
|
684
|
+
const labelMap = {
|
|
685
|
+
ou: 'Organization Unit',
|
|
686
|
+
description: 'Description',
|
|
687
|
+
};
|
|
688
|
+
return (labelMap[fieldName] ||
|
|
689
|
+
fieldName
|
|
690
|
+
.replace(/([A-Z])/g, ' $1')
|
|
691
|
+
.replace(/_/g, ' ')
|
|
692
|
+
.replace(/^./, str => str.toUpperCase()));
|
|
693
|
+
}
|
|
694
|
+
attachEventListeners() {
|
|
695
|
+
const form = document.getElementById('unit-edit-form');
|
|
696
|
+
if (form) {
|
|
697
|
+
form.addEventListener('submit', e => this.handleSubmit(e));
|
|
698
|
+
}
|
|
699
|
+
const deleteBtn = document.getElementById('delete-unit-btn');
|
|
700
|
+
if (deleteBtn) {
|
|
701
|
+
deleteBtn.addEventListener('click', () => this.handleDelete());
|
|
702
|
+
}
|
|
703
|
+
const moveBtn = document.getElementById('move-unit-btn');
|
|
704
|
+
if (moveBtn) {
|
|
705
|
+
moveBtn.addEventListener('click', () => this.handleMove());
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
async handleSubmit(e) {
|
|
709
|
+
e.preventDefault();
|
|
710
|
+
const form = e.target;
|
|
711
|
+
const formData = new FormData(form);
|
|
712
|
+
const updates = {};
|
|
713
|
+
for (const [key, value] of formData.entries()) {
|
|
714
|
+
if (this.schema?.attributes[key]?.type === 'array') {
|
|
715
|
+
updates[key] = value
|
|
716
|
+
.split('\n')
|
|
717
|
+
.map(v => v.trim())
|
|
718
|
+
.filter(v => v);
|
|
719
|
+
}
|
|
720
|
+
else if (this.schema?.attributes[key]?.type === 'number' ||
|
|
721
|
+
this.schema?.attributes[key]?.type === 'integer') {
|
|
722
|
+
updates[key] = Number(value);
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
updates[key] = value;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
try {
|
|
729
|
+
await this.api.updateUnit(this.unitDn, updates);
|
|
730
|
+
alert('Organizational unit updated successfully!');
|
|
731
|
+
if (this.saveCallback) {
|
|
732
|
+
this.saveCallback();
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
catch (error) {
|
|
736
|
+
console.error('Failed to update unit:', error);
|
|
737
|
+
alert(`Failed to update unit: ${error.message}`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
async handleMove() {
|
|
741
|
+
if (!this.unit)
|
|
742
|
+
return;
|
|
743
|
+
// Extract parent DN from current unit DN
|
|
744
|
+
const parentDn = this.unitDn.split(',').slice(1).join(',');
|
|
745
|
+
const modal = new MoveUnitModal(this.api, parentDn, async (targetOrgDn) => {
|
|
746
|
+
try {
|
|
747
|
+
const result = await this.api.moveUnit(this.unitDn, targetOrgDn);
|
|
748
|
+
alert('Unit moved successfully!');
|
|
749
|
+
// Reload the unit with its new DN
|
|
750
|
+
if (result.newDn) {
|
|
751
|
+
this.unitDn = result.newDn;
|
|
752
|
+
await this.loadUnit();
|
|
753
|
+
this.render();
|
|
754
|
+
this.attachEventListeners();
|
|
755
|
+
}
|
|
756
|
+
if (this.saveCallback) {
|
|
757
|
+
this.saveCallback();
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
catch (error) {
|
|
761
|
+
console.error('Failed to move unit:', error);
|
|
762
|
+
alert(`Failed to move unit: ${error.message}`);
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
await modal.show();
|
|
766
|
+
}
|
|
767
|
+
async handleDelete() {
|
|
768
|
+
// Don't allow delete if there are subnodes
|
|
769
|
+
if (this.hasSubnodes) {
|
|
770
|
+
alert('Cannot delete this organizational unit because it contains sub-organizations. Please delete all sub-organizations first.');
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
if (!confirm(`Are you sure you want to delete this organizational unit?\n\n${this.unitDn}\n\nThis action cannot be undone.`)) {
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
try {
|
|
777
|
+
await this.api.deleteEntry(this.unitDn);
|
|
778
|
+
alert('Organizational unit deleted successfully!');
|
|
779
|
+
this.container.innerHTML = `
|
|
780
|
+
<div class="empty-state">
|
|
781
|
+
<span class="material-icons">business</span>
|
|
782
|
+
<p>Unit deleted. Select another unit to edit.</p>
|
|
783
|
+
</div>
|
|
784
|
+
`;
|
|
785
|
+
}
|
|
786
|
+
catch (error) {
|
|
787
|
+
console.error('Failed to delete unit:', error);
|
|
788
|
+
alert(`Failed to delete unit: ${error.message}`);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
escapeHtml(text) {
|
|
792
|
+
if (text == null)
|
|
793
|
+
return '';
|
|
794
|
+
const map = {
|
|
795
|
+
'&': '&',
|
|
796
|
+
'<': '<',
|
|
797
|
+
'>': '>',
|
|
798
|
+
'"': '"',
|
|
799
|
+
"'": ''',
|
|
800
|
+
'/': '/',
|
|
801
|
+
};
|
|
802
|
+
return String(text).replace(/[&<>"'/]/g, m => map[m]);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* API Client for LDAP Unit operations
|
|
808
|
+
*/
|
|
809
|
+
class UnitApiClient {
|
|
810
|
+
constructor(baseUrl) {
|
|
811
|
+
this.baseUrl = baseUrl;
|
|
812
|
+
}
|
|
813
|
+
async getConfig() {
|
|
814
|
+
const response = await fetch(`${this.baseUrl}/api/v1/config`);
|
|
815
|
+
if (!response.ok) {
|
|
816
|
+
throw new Error(`Failed to load config: ${response.statusText}`);
|
|
817
|
+
}
|
|
818
|
+
return response.json();
|
|
819
|
+
}
|
|
820
|
+
async getOrganizations() {
|
|
821
|
+
const response = await fetch(`${this.baseUrl}/api/v1/ldap/organizations/top`);
|
|
822
|
+
if (!response.ok) {
|
|
823
|
+
throw new Error(`Failed to load organizations: ${response.statusText}`);
|
|
824
|
+
}
|
|
825
|
+
return response.json();
|
|
826
|
+
}
|
|
827
|
+
async getUnit(dn) {
|
|
828
|
+
const response = await fetch(`${this.baseUrl}/api/v1/ldap/organizations/${encodeURIComponent(dn)}`);
|
|
829
|
+
if (!response.ok) {
|
|
830
|
+
throw new Error(`Failed to load unit: ${response.statusText}`);
|
|
831
|
+
}
|
|
832
|
+
return response.json();
|
|
833
|
+
}
|
|
834
|
+
async updateUnit(dn, data) {
|
|
835
|
+
const response = await fetch(`${this.baseUrl}/api/v1/ldap/organizations/${encodeURIComponent(dn)}`, {
|
|
836
|
+
method: 'PUT',
|
|
837
|
+
headers: { 'Content-Type': 'application/json' },
|
|
838
|
+
body: JSON.stringify(data),
|
|
839
|
+
});
|
|
840
|
+
if (!response.ok) {
|
|
841
|
+
const error = await response.text();
|
|
842
|
+
throw new Error(`Failed to update unit: ${error}`);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
async createEntry(dn, data) {
|
|
846
|
+
// Use the organizations API for creating units
|
|
847
|
+
// Extract ou and parentDn from DN
|
|
848
|
+
// DN format: ou=value,parent,dn,parts
|
|
849
|
+
const match = dn.match(/^ou=([^,]+),(.+)$/);
|
|
850
|
+
if (!match) {
|
|
851
|
+
throw new Error(`Invalid DN format: ${dn}`);
|
|
852
|
+
}
|
|
853
|
+
const [, ou, parentDn] = match;
|
|
854
|
+
const response = await fetch(`${this.baseUrl}/api/v1/ldap/organizations`, {
|
|
855
|
+
method: 'POST',
|
|
856
|
+
headers: { 'Content-Type': 'application/json' },
|
|
857
|
+
body: JSON.stringify({ ou, parentDn, ...data }),
|
|
858
|
+
});
|
|
859
|
+
if (!response.ok) {
|
|
860
|
+
const error = await response.text();
|
|
861
|
+
throw new Error(`Failed to create entry: ${error}`);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
async deleteEntry(dn) {
|
|
865
|
+
// Use the organizations API for deleting units
|
|
866
|
+
const response = await fetch(`${this.baseUrl}/api/v1/ldap/organizations/${encodeURIComponent(dn)}`, {
|
|
867
|
+
method: 'DELETE',
|
|
868
|
+
});
|
|
869
|
+
if (!response.ok) {
|
|
870
|
+
const error = await response.text();
|
|
871
|
+
throw new Error(`Failed to delete entry: ${error}`);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
async getPointerOptions(branch) {
|
|
875
|
+
const response = await fetch(`${this.baseUrl}/api/v1/ldap/search?base=${encodeURIComponent(branch)}&scope=one`);
|
|
876
|
+
if (!response.ok) {
|
|
877
|
+
throw new Error(`Failed to load pointer options: ${response.statusText}`);
|
|
878
|
+
}
|
|
879
|
+
const entries = await response.json();
|
|
880
|
+
return entries.map((entry) => ({
|
|
881
|
+
dn: entry.dn,
|
|
882
|
+
label: entry.cn?.[0] || entry.ou?.[0] || entry.dn,
|
|
883
|
+
}));
|
|
884
|
+
}
|
|
885
|
+
async moveUnit(dn, targetOrgDn) {
|
|
886
|
+
const response = await fetch(`${this.baseUrl}/api/v1/ldap/organizations/${encodeURIComponent(dn)}/move`, {
|
|
887
|
+
method: 'POST',
|
|
888
|
+
headers: { 'Content-Type': 'application/json' },
|
|
889
|
+
body: JSON.stringify({ targetOrgDn }),
|
|
890
|
+
});
|
|
891
|
+
if (!response.ok) {
|
|
892
|
+
const error = await response.text();
|
|
893
|
+
throw new Error(error || 'Failed to move unit');
|
|
894
|
+
}
|
|
895
|
+
return response.json();
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* LDAP Unit Editor - Main component for editing organizational units
|
|
901
|
+
*/
|
|
902
|
+
class LdapUnitEditor {
|
|
903
|
+
constructor(options) {
|
|
904
|
+
this.container = null;
|
|
905
|
+
this.unitTree = null;
|
|
906
|
+
this.unitEditor = null;
|
|
907
|
+
this.currentUnitDn = null;
|
|
908
|
+
this.config = null;
|
|
909
|
+
this.options = options;
|
|
910
|
+
this.api = new UnitApiClient(options.apiBaseUrl || '');
|
|
911
|
+
}
|
|
912
|
+
async init() {
|
|
913
|
+
const container = document.getElementById(this.options.containerId);
|
|
914
|
+
if (!container) {
|
|
915
|
+
throw new Error(`Container element with id '${this.options.containerId}' not found`);
|
|
916
|
+
}
|
|
917
|
+
this.container = container;
|
|
918
|
+
// Load config first
|
|
919
|
+
this.config = await this.api.getConfig();
|
|
920
|
+
this.render();
|
|
921
|
+
await this.initializeComponents();
|
|
922
|
+
}
|
|
923
|
+
render() {
|
|
924
|
+
if (!this.container)
|
|
925
|
+
return;
|
|
926
|
+
this.container.innerHTML = `
|
|
927
|
+
<div class="ldap-unit-editor" style="display: flex; gap: 24px; height: 100%;">
|
|
928
|
+
<div id="unit-tree-container" style="flex: 0 0 300px; overflow-y: auto; border-right: 1px solid #e0e0e0; padding-right: 24px;"></div>
|
|
929
|
+
<div id="unit-editor-container" style="flex: 1; overflow-y: auto;">
|
|
930
|
+
<div class="empty-state" style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #999;">
|
|
931
|
+
<span class="material-icons" style="font-size: 64px; margin-bottom: 16px;">business</span>
|
|
932
|
+
<p style="font-size: 16px; margin: 0;">Select an organizational unit to edit</p>
|
|
933
|
+
</div>
|
|
934
|
+
</div>
|
|
935
|
+
</div>
|
|
936
|
+
`;
|
|
937
|
+
}
|
|
938
|
+
async initializeComponents() {
|
|
939
|
+
const treeContainer = document.getElementById('unit-tree-container');
|
|
940
|
+
if (!treeContainer)
|
|
941
|
+
return;
|
|
942
|
+
this.unitTree = new UnitTree(treeContainer, this.options.apiBaseUrl || '', unitDn => this.onUnitSelected(unitDn));
|
|
943
|
+
await this.unitTree.init();
|
|
944
|
+
}
|
|
945
|
+
async onUnitSelected(unitDn) {
|
|
946
|
+
this.currentUnitDn = unitDn;
|
|
947
|
+
const editorContainer = document.getElementById('unit-editor-container');
|
|
948
|
+
if (!editorContainer)
|
|
949
|
+
return;
|
|
950
|
+
try {
|
|
951
|
+
this.unitEditor = new UnitPropertyEditor(editorContainer, this.api, unitDn);
|
|
952
|
+
await this.unitEditor.init();
|
|
953
|
+
this.unitEditor.onSave(() => {
|
|
954
|
+
if (this.options.onUnitSaved) {
|
|
955
|
+
this.options.onUnitSaved(unitDn);
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
catch (error) {
|
|
960
|
+
console.error('Failed to load unit editor:', error);
|
|
961
|
+
editorContainer.innerHTML = `
|
|
962
|
+
<div class="error-state" style="padding: 24px; color: #f44336;">
|
|
963
|
+
<h3>Error loading unit</h3>
|
|
964
|
+
<p>${error.message}</p>
|
|
965
|
+
</div>
|
|
966
|
+
`;
|
|
967
|
+
if (this.options.onError) {
|
|
968
|
+
this.options.onError(error);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
getCurrentUnitDn() {
|
|
973
|
+
return this.currentUnitDn;
|
|
974
|
+
}
|
|
975
|
+
async refresh() {
|
|
976
|
+
if (this.unitTree) {
|
|
977
|
+
await this.unitTree.init();
|
|
978
|
+
}
|
|
979
|
+
if (this.currentUnitDn && this.unitEditor) {
|
|
980
|
+
const editorContainer = document.getElementById('unit-editor-container');
|
|
981
|
+
if (editorContainer) {
|
|
982
|
+
this.unitEditor = new UnitPropertyEditor(editorContainer, this.api, this.currentUnitDn);
|
|
983
|
+
await this.unitEditor.init();
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
// Public API for compatibility with tests
|
|
988
|
+
getApi() {
|
|
989
|
+
return this.api;
|
|
990
|
+
}
|
|
991
|
+
getConfig() {
|
|
992
|
+
return this.config;
|
|
993
|
+
}
|
|
994
|
+
getCurrentOrgDn() {
|
|
995
|
+
// For units, the current org is the current unit
|
|
996
|
+
return this.currentUnitDn;
|
|
997
|
+
}
|
|
998
|
+
getCurrentUserDn() {
|
|
999
|
+
// For compatibility - in unit context, "user" means "unit"
|
|
1000
|
+
return this.currentUnitDn;
|
|
1001
|
+
}
|
|
1002
|
+
async createUser(data) {
|
|
1003
|
+
// For compatibility - actually creates an organizational unit
|
|
1004
|
+
return this.api.createEntry(data.dn, data);
|
|
1005
|
+
}
|
|
1006
|
+
async deleteUser(dn) {
|
|
1007
|
+
// For compatibility - actually deletes an organizational unit
|
|
1008
|
+
return this.api.deleteEntry(dn);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
export { LdapUnitEditor };
|
|
1013
|
+
//# sourceMappingURL=ldap-unit-editor.esm.js.map
|