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,1659 @@
|
|
|
1
|
+
# Integration Examples - LDAP-Rest
|
|
2
|
+
|
|
3
|
+
This guide provides complete, production-ready examples for integrating LDAP-Rest into your applications using React, Vue.js, and Vanilla JavaScript.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [React Application Example](#react-application-example)
|
|
8
|
+
- [Vue.js Application Example](#vuejs-application-example)
|
|
9
|
+
- [Vanilla JavaScript with TreeViewer](#vanilla-javascript-with-treeviewer)
|
|
10
|
+
- [Group Management Application](#group-management-application)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## React Application Example
|
|
15
|
+
|
|
16
|
+
This example demonstrates a complete React functional component that fetches users from LDAP-Rest and displays them in a list.
|
|
17
|
+
|
|
18
|
+
### Complete React Component
|
|
19
|
+
|
|
20
|
+
```jsx
|
|
21
|
+
import React, { useState, useEffect } from 'react';
|
|
22
|
+
|
|
23
|
+
const UserManager = () => {
|
|
24
|
+
const [users, setUsers] = useState([]);
|
|
25
|
+
const [config, setConfig] = useState(null);
|
|
26
|
+
const [usersEndpoint, setUsersEndpoint] = useState(null);
|
|
27
|
+
const [loading, setLoading] = useState(true);
|
|
28
|
+
const [error, setError] = useState(null);
|
|
29
|
+
|
|
30
|
+
// Load configuration on component mount
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const loadConfig = async () => {
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch('http://localhost:8081/api/v1/config');
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
37
|
+
}
|
|
38
|
+
const configData = await response.json();
|
|
39
|
+
setConfig(configData);
|
|
40
|
+
|
|
41
|
+
// Discover users endpoint from config
|
|
42
|
+
const userResource = configData.features?.flatResources?.find(
|
|
43
|
+
r => r.pluralName === 'users'
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (!userResource) {
|
|
47
|
+
throw new Error('Users endpoint not found in configuration');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setUsersEndpoint(userResource.endpoints.list);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error('Failed to load config:', err);
|
|
53
|
+
setError(err.message);
|
|
54
|
+
setLoading(false);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
loadConfig();
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
// Load users when endpoint is discovered
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!usersEndpoint) return;
|
|
64
|
+
|
|
65
|
+
const loadUsers = async () => {
|
|
66
|
+
try {
|
|
67
|
+
setLoading(true);
|
|
68
|
+
const response = await fetch(usersEndpoint);
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
71
|
+
}
|
|
72
|
+
const usersData = await response.json();
|
|
73
|
+
setUsers(usersData);
|
|
74
|
+
setError(null);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error('Failed to load users:', err);
|
|
77
|
+
setError(err.message);
|
|
78
|
+
} finally {
|
|
79
|
+
setLoading(false);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
loadUsers();
|
|
84
|
+
}, [usersEndpoint]);
|
|
85
|
+
|
|
86
|
+
// Error state
|
|
87
|
+
if (error) {
|
|
88
|
+
return (
|
|
89
|
+
<div className="error-container">
|
|
90
|
+
<h2>Error</h2>
|
|
91
|
+
<p>{error}</p>
|
|
92
|
+
<button onClick={() => window.location.reload()}>Retry</button>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Loading state
|
|
98
|
+
if (loading) {
|
|
99
|
+
return (
|
|
100
|
+
<div className="loading-container">
|
|
101
|
+
<div className="spinner"></div>
|
|
102
|
+
<p>Loading users...</p>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Success state - display users
|
|
108
|
+
return (
|
|
109
|
+
<div className="user-manager">
|
|
110
|
+
<header className="header">
|
|
111
|
+
<h1>User Management</h1>
|
|
112
|
+
<p className="subtitle">
|
|
113
|
+
Managing {users.length} users in {config?.ldapBase}
|
|
114
|
+
</p>
|
|
115
|
+
</header>
|
|
116
|
+
|
|
117
|
+
<div className="user-list">
|
|
118
|
+
{users.length === 0 ? (
|
|
119
|
+
<div className="empty-state">
|
|
120
|
+
<p>No users found</p>
|
|
121
|
+
</div>
|
|
122
|
+
) : (
|
|
123
|
+
<table className="user-table">
|
|
124
|
+
<thead>
|
|
125
|
+
<tr>
|
|
126
|
+
<th>Username</th>
|
|
127
|
+
<th>Full Name</th>
|
|
128
|
+
<th>Email</th>
|
|
129
|
+
<th>DN</th>
|
|
130
|
+
</tr>
|
|
131
|
+
</thead>
|
|
132
|
+
<tbody>
|
|
133
|
+
{users.map(user => (
|
|
134
|
+
<tr key={user.dn}>
|
|
135
|
+
<td className="username">{user.uid}</td>
|
|
136
|
+
<td>{user.cn}</td>
|
|
137
|
+
<td>
|
|
138
|
+
{user.mail ? (
|
|
139
|
+
<a href={`mailto:${user.mail}`}>{user.mail}</a>
|
|
140
|
+
) : (
|
|
141
|
+
<span className="no-email">No email</span>
|
|
142
|
+
)}
|
|
143
|
+
</td>
|
|
144
|
+
<td className="dn">{user.dn}</td>
|
|
145
|
+
</tr>
|
|
146
|
+
))}
|
|
147
|
+
</tbody>
|
|
148
|
+
</table>
|
|
149
|
+
)}
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export default UserManager;
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Styles (Optional)
|
|
159
|
+
|
|
160
|
+
```css
|
|
161
|
+
.user-manager {
|
|
162
|
+
max-width: 1200px;
|
|
163
|
+
margin: 0 auto;
|
|
164
|
+
padding: 2rem;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.header {
|
|
168
|
+
margin-bottom: 2rem;
|
|
169
|
+
border-bottom: 2px solid #e2e8f0;
|
|
170
|
+
padding-bottom: 1rem;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.header h1 {
|
|
174
|
+
margin: 0 0 0.5rem 0;
|
|
175
|
+
color: #1e293b;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.subtitle {
|
|
179
|
+
color: #64748b;
|
|
180
|
+
margin: 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.loading-container,
|
|
184
|
+
.error-container {
|
|
185
|
+
display: flex;
|
|
186
|
+
flex-direction: column;
|
|
187
|
+
align-items: center;
|
|
188
|
+
justify-content: center;
|
|
189
|
+
min-height: 400px;
|
|
190
|
+
gap: 1rem;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.spinner {
|
|
194
|
+
width: 50px;
|
|
195
|
+
height: 50px;
|
|
196
|
+
border: 4px solid #e2e8f0;
|
|
197
|
+
border-top-color: #6200ee;
|
|
198
|
+
border-radius: 50%;
|
|
199
|
+
animation: spin 1s linear infinite;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@keyframes spin {
|
|
203
|
+
to {
|
|
204
|
+
transform: rotate(360deg);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.error-container {
|
|
209
|
+
color: #c62828;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.error-container button {
|
|
213
|
+
padding: 0.5rem 1rem;
|
|
214
|
+
background: #6200ee;
|
|
215
|
+
color: white;
|
|
216
|
+
border: none;
|
|
217
|
+
border-radius: 4px;
|
|
218
|
+
cursor: pointer;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.user-table {
|
|
222
|
+
width: 100%;
|
|
223
|
+
border-collapse: collapse;
|
|
224
|
+
background: white;
|
|
225
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.user-table th,
|
|
229
|
+
.user-table td {
|
|
230
|
+
padding: 1rem;
|
|
231
|
+
text-align: left;
|
|
232
|
+
border-bottom: 1px solid #e2e8f0;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.user-table th {
|
|
236
|
+
background: #f8fafc;
|
|
237
|
+
font-weight: 600;
|
|
238
|
+
color: #1e293b;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.user-table tr:hover {
|
|
242
|
+
background: #f8fafc;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.username {
|
|
246
|
+
font-family: monospace;
|
|
247
|
+
font-weight: 600;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.dn {
|
|
251
|
+
font-family: monospace;
|
|
252
|
+
font-size: 0.875rem;
|
|
253
|
+
color: #64748b;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.no-email {
|
|
257
|
+
color: #94a3b8;
|
|
258
|
+
font-style: italic;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.empty-state {
|
|
262
|
+
text-align: center;
|
|
263
|
+
padding: 3rem;
|
|
264
|
+
color: #64748b;
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Usage
|
|
269
|
+
|
|
270
|
+
```jsx
|
|
271
|
+
import React from 'react';
|
|
272
|
+
import ReactDOM from 'react-dom/client';
|
|
273
|
+
import UserManager from './UserManager';
|
|
274
|
+
|
|
275
|
+
const root = ReactDOM.createRoot(document.getElementById('root'));
|
|
276
|
+
root.render(
|
|
277
|
+
<React.StrictMode>
|
|
278
|
+
<UserManager />
|
|
279
|
+
</React.StrictMode>
|
|
280
|
+
);
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Vue.js Application Example
|
|
286
|
+
|
|
287
|
+
This example shows a complete Vue.js component using the LdapUserEditor library with proper lifecycle management and toast notifications.
|
|
288
|
+
|
|
289
|
+
### Complete Vue Component
|
|
290
|
+
|
|
291
|
+
```vue
|
|
292
|
+
<template>
|
|
293
|
+
<div class="ldap-editor-wrapper">
|
|
294
|
+
<header class="app-header">
|
|
295
|
+
<h1>LDAP User Editor</h1>
|
|
296
|
+
<p class="subtitle">Manage your organization's users</p>
|
|
297
|
+
</header>
|
|
298
|
+
|
|
299
|
+
<div v-if="loading" class="loading-state">
|
|
300
|
+
<div class="spinner"></div>
|
|
301
|
+
<p>Initializing editor...</p>
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
<div v-else-if="error" class="error-state">
|
|
305
|
+
<span class="material-icons">error_outline</span>
|
|
306
|
+
<h2>Failed to Initialize</h2>
|
|
307
|
+
<p>{{ error }}</p>
|
|
308
|
+
<button @click="retryInit" class="retry-btn">Retry</button>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<div v-else id="editor-container" class="editor-container"></div>
|
|
312
|
+
|
|
313
|
+
<!-- Toast Notifications -->
|
|
314
|
+
<Transition name="toast">
|
|
315
|
+
<div v-if="toast.visible" :class="['toast', `toast-${toast.type}`]">
|
|
316
|
+
<span class="material-icons">{{ getToastIcon(toast.type) }}</span>
|
|
317
|
+
<span class="toast-message">{{ toast.message }}</span>
|
|
318
|
+
</div>
|
|
319
|
+
</Transition>
|
|
320
|
+
</div>
|
|
321
|
+
</template>
|
|
322
|
+
|
|
323
|
+
<script>
|
|
324
|
+
export default {
|
|
325
|
+
name: 'LdapEditorApp',
|
|
326
|
+
|
|
327
|
+
data() {
|
|
328
|
+
return {
|
|
329
|
+
editor: null,
|
|
330
|
+
loading: true,
|
|
331
|
+
error: null,
|
|
332
|
+
toast: {
|
|
333
|
+
visible: false,
|
|
334
|
+
message: '',
|
|
335
|
+
type: 'info', // 'success', 'error', 'info', 'warning'
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
mounted() {
|
|
341
|
+
this.initEditor();
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
beforeUnmount() {
|
|
345
|
+
if (this.editor) {
|
|
346
|
+
this.editor.destroy();
|
|
347
|
+
this.editor = null;
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
methods: {
|
|
352
|
+
async initEditor() {
|
|
353
|
+
try {
|
|
354
|
+
this.loading = true;
|
|
355
|
+
this.error = null;
|
|
356
|
+
|
|
357
|
+
// Load LdapUserEditor library
|
|
358
|
+
const { LdapUserEditor } = window.LdapUserEditor;
|
|
359
|
+
|
|
360
|
+
if (!LdapUserEditor) {
|
|
361
|
+
throw new Error('LdapUserEditor library not loaded');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Create editor instance
|
|
365
|
+
this.editor = new LdapUserEditor({
|
|
366
|
+
containerId: 'editor-container',
|
|
367
|
+
apiBaseUrl: window.location.origin,
|
|
368
|
+
onUserSaved: this.handleUserSaved,
|
|
369
|
+
onError: this.handleError,
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// Initialize the editor
|
|
373
|
+
await this.editor.init();
|
|
374
|
+
|
|
375
|
+
this.loading = false;
|
|
376
|
+
this.showToast('Editor initialized successfully', 'success');
|
|
377
|
+
} catch (err) {
|
|
378
|
+
console.error('Failed to initialize editor:', err);
|
|
379
|
+
this.error = err.message || 'Unknown error occurred';
|
|
380
|
+
this.loading = false;
|
|
381
|
+
this.showToast('Failed to initialize editor', 'error');
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
handleUserSaved(userDn) {
|
|
386
|
+
console.log('User saved:', userDn);
|
|
387
|
+
this.showToast('User saved successfully!', 'success');
|
|
388
|
+
|
|
389
|
+
// Optional: Track analytics
|
|
390
|
+
if (window.gtag) {
|
|
391
|
+
window.gtag('event', 'user_updated', {
|
|
392
|
+
event_category: 'ldap',
|
|
393
|
+
event_label: userDn,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
|
|
398
|
+
handleError(error) {
|
|
399
|
+
console.error('Editor error:', error);
|
|
400
|
+
this.showToast(error.message || 'An error occurred', 'error');
|
|
401
|
+
},
|
|
402
|
+
|
|
403
|
+
retryInit() {
|
|
404
|
+
this.initEditor();
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
showToast(message, type = 'info') {
|
|
408
|
+
this.toast = {
|
|
409
|
+
visible: true,
|
|
410
|
+
message,
|
|
411
|
+
type,
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// Auto-hide after 3 seconds
|
|
415
|
+
setTimeout(() => {
|
|
416
|
+
this.toast.visible = false;
|
|
417
|
+
}, 3000);
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
getToastIcon(type) {
|
|
421
|
+
const icons = {
|
|
422
|
+
success: 'check_circle',
|
|
423
|
+
error: 'error',
|
|
424
|
+
warning: 'warning',
|
|
425
|
+
info: 'info',
|
|
426
|
+
};
|
|
427
|
+
return icons[type] || 'info';
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
</script>
|
|
432
|
+
|
|
433
|
+
<style scoped>
|
|
434
|
+
.ldap-editor-wrapper {
|
|
435
|
+
min-height: 100vh;
|
|
436
|
+
background: #f5f5f5;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.app-header {
|
|
440
|
+
background: white;
|
|
441
|
+
padding: 2rem;
|
|
442
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
443
|
+
margin-bottom: 2rem;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.app-header h1 {
|
|
447
|
+
margin: 0 0 0.5rem 0;
|
|
448
|
+
color: #1e293b;
|
|
449
|
+
font-size: 2rem;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.subtitle {
|
|
453
|
+
margin: 0;
|
|
454
|
+
color: #64748b;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.loading-state,
|
|
458
|
+
.error-state {
|
|
459
|
+
display: flex;
|
|
460
|
+
flex-direction: column;
|
|
461
|
+
align-items: center;
|
|
462
|
+
justify-content: center;
|
|
463
|
+
min-height: 400px;
|
|
464
|
+
gap: 1rem;
|
|
465
|
+
text-align: center;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.spinner {
|
|
469
|
+
width: 50px;
|
|
470
|
+
height: 50px;
|
|
471
|
+
border: 4px solid #e2e8f0;
|
|
472
|
+
border-top-color: #6200ee;
|
|
473
|
+
border-radius: 50%;
|
|
474
|
+
animation: spin 1s linear infinite;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
@keyframes spin {
|
|
478
|
+
to {
|
|
479
|
+
transform: rotate(360deg);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.error-state {
|
|
484
|
+
color: #c62828;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.error-state .material-icons {
|
|
488
|
+
font-size: 64px;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.retry-btn {
|
|
492
|
+
padding: 0.75rem 1.5rem;
|
|
493
|
+
background: #6200ee;
|
|
494
|
+
color: white;
|
|
495
|
+
border: none;
|
|
496
|
+
border-radius: 4px;
|
|
497
|
+
font-size: 1rem;
|
|
498
|
+
cursor: pointer;
|
|
499
|
+
transition: background 0.2s;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.retry-btn:hover {
|
|
503
|
+
background: #3700b3;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.editor-container {
|
|
507
|
+
padding: 2rem;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/* Toast Notifications */
|
|
511
|
+
.toast {
|
|
512
|
+
position: fixed;
|
|
513
|
+
bottom: 2rem;
|
|
514
|
+
right: 2rem;
|
|
515
|
+
display: flex;
|
|
516
|
+
align-items: center;
|
|
517
|
+
gap: 0.75rem;
|
|
518
|
+
padding: 1rem 1.5rem;
|
|
519
|
+
background: white;
|
|
520
|
+
border-radius: 8px;
|
|
521
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
522
|
+
z-index: 1000;
|
|
523
|
+
min-width: 300px;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.toast-success {
|
|
527
|
+
border-left: 4px solid #2e7d32;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.toast-success .material-icons {
|
|
531
|
+
color: #2e7d32;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.toast-error {
|
|
535
|
+
border-left: 4px solid #c62828;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.toast-error .material-icons {
|
|
539
|
+
color: #c62828;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.toast-warning {
|
|
543
|
+
border-left: 4px solid #f57c00;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.toast-warning .material-icons {
|
|
547
|
+
color: #f57c00;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.toast-info {
|
|
551
|
+
border-left: 4px solid #1976d2;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.toast-info .material-icons {
|
|
555
|
+
color: #1976d2;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.toast-message {
|
|
559
|
+
flex: 1;
|
|
560
|
+
color: #1e293b;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/* Toast Transitions */
|
|
564
|
+
.toast-enter-active,
|
|
565
|
+
.toast-leave-active {
|
|
566
|
+
transition: all 0.3s ease;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.toast-enter-from,
|
|
570
|
+
.toast-leave-to {
|
|
571
|
+
transform: translateX(400px);
|
|
572
|
+
opacity: 0;
|
|
573
|
+
}
|
|
574
|
+
</style>
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### HTML Template
|
|
578
|
+
|
|
579
|
+
```html
|
|
580
|
+
<!DOCTYPE html>
|
|
581
|
+
<html lang="en">
|
|
582
|
+
<head>
|
|
583
|
+
<meta charset="UTF-8" />
|
|
584
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
585
|
+
<title>LDAP User Editor - Vue.js</title>
|
|
586
|
+
|
|
587
|
+
<!-- Google Fonts -->
|
|
588
|
+
<link
|
|
589
|
+
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
|
|
590
|
+
rel="stylesheet"
|
|
591
|
+
/>
|
|
592
|
+
<link
|
|
593
|
+
href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
|
594
|
+
rel="stylesheet"
|
|
595
|
+
/>
|
|
596
|
+
|
|
597
|
+
<!-- LdapUserEditor CSS -->
|
|
598
|
+
<link
|
|
599
|
+
rel="stylesheet"
|
|
600
|
+
href="/static/browser/ldap-user-editor/LdapUserEditor.css"
|
|
601
|
+
/>
|
|
602
|
+
|
|
603
|
+
<!-- Vue.js (Development version) -->
|
|
604
|
+
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
|
|
605
|
+
|
|
606
|
+
<style>
|
|
607
|
+
body {
|
|
608
|
+
margin: 0;
|
|
609
|
+
font-family: 'Roboto', sans-serif;
|
|
610
|
+
}
|
|
611
|
+
</style>
|
|
612
|
+
</head>
|
|
613
|
+
<body>
|
|
614
|
+
<div id="app"></div>
|
|
615
|
+
|
|
616
|
+
<!-- LdapUserEditor JavaScript -->
|
|
617
|
+
<script src="/static/browser/ldap-user-editor/LdapUserEditor.js"></script>
|
|
618
|
+
|
|
619
|
+
<!-- Your Vue App -->
|
|
620
|
+
<script src="./app.js"></script>
|
|
621
|
+
</body>
|
|
622
|
+
</html>
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
### App Entry Point (app.js)
|
|
626
|
+
|
|
627
|
+
```javascript
|
|
628
|
+
const { createApp } = Vue;
|
|
629
|
+
import LdapEditorApp from './LdapEditorApp.vue';
|
|
630
|
+
|
|
631
|
+
createApp(LdapEditorApp).mount('#app');
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
## Vanilla JavaScript with TreeViewer
|
|
637
|
+
|
|
638
|
+
This example demonstrates a complete HTML page with sidebar tree navigation and content area for displaying node details.
|
|
639
|
+
|
|
640
|
+
### Complete HTML Page
|
|
641
|
+
|
|
642
|
+
```html
|
|
643
|
+
<!DOCTYPE html>
|
|
644
|
+
<html lang="en">
|
|
645
|
+
<head>
|
|
646
|
+
<meta charset="UTF-8" />
|
|
647
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
648
|
+
<title>Organization Browser - LDAP-Rest</title>
|
|
649
|
+
|
|
650
|
+
<!-- Material Icons -->
|
|
651
|
+
<link
|
|
652
|
+
href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
|
653
|
+
rel="stylesheet"
|
|
654
|
+
/>
|
|
655
|
+
|
|
656
|
+
<!-- LdapTreeViewer CSS -->
|
|
657
|
+
<link
|
|
658
|
+
rel="stylesheet"
|
|
659
|
+
href="/static/browser/ldap-tree-viewer/LdapTreeViewer.css"
|
|
660
|
+
/>
|
|
661
|
+
|
|
662
|
+
<style>
|
|
663
|
+
* {
|
|
664
|
+
margin: 0;
|
|
665
|
+
padding: 0;
|
|
666
|
+
box-sizing: border-box;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
body {
|
|
670
|
+
font-family:
|
|
671
|
+
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
|
672
|
+
Cantarell, sans-serif;
|
|
673
|
+
background: #f5f5f5;
|
|
674
|
+
color: #333;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
.app-container {
|
|
678
|
+
display: flex;
|
|
679
|
+
height: 100vh;
|
|
680
|
+
overflow: hidden;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/* Sidebar with Tree */
|
|
684
|
+
.sidebar {
|
|
685
|
+
width: 350px;
|
|
686
|
+
background: white;
|
|
687
|
+
border-right: 1px solid #e0e0e0;
|
|
688
|
+
display: flex;
|
|
689
|
+
flex-direction: column;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
.sidebar-header {
|
|
693
|
+
padding: 1.5rem;
|
|
694
|
+
background: #6200ee;
|
|
695
|
+
color: white;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.sidebar-header h1 {
|
|
699
|
+
font-size: 1.25rem;
|
|
700
|
+
margin-bottom: 0.25rem;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.sidebar-header p {
|
|
704
|
+
font-size: 0.875rem;
|
|
705
|
+
opacity: 0.9;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
.tree-container {
|
|
709
|
+
flex: 1;
|
|
710
|
+
overflow-y: auto;
|
|
711
|
+
padding: 1rem;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/* Main Content Area */
|
|
715
|
+
.content-area {
|
|
716
|
+
flex: 1;
|
|
717
|
+
display: flex;
|
|
718
|
+
flex-direction: column;
|
|
719
|
+
overflow: hidden;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.content-header {
|
|
723
|
+
padding: 1.5rem 2rem;
|
|
724
|
+
background: white;
|
|
725
|
+
border-bottom: 1px solid #e0e0e0;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
.content-header h2 {
|
|
729
|
+
font-size: 1.5rem;
|
|
730
|
+
color: #1e293b;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
.content-body {
|
|
734
|
+
flex: 1;
|
|
735
|
+
overflow-y: auto;
|
|
736
|
+
padding: 2rem;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
.node-details {
|
|
740
|
+
background: white;
|
|
741
|
+
border-radius: 8px;
|
|
742
|
+
padding: 2rem;
|
|
743
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.empty-state {
|
|
747
|
+
display: flex;
|
|
748
|
+
flex-direction: column;
|
|
749
|
+
align-items: center;
|
|
750
|
+
justify-content: center;
|
|
751
|
+
height: 100%;
|
|
752
|
+
color: #64748b;
|
|
753
|
+
text-align: center;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.empty-state .material-icons {
|
|
757
|
+
font-size: 80px;
|
|
758
|
+
margin-bottom: 1rem;
|
|
759
|
+
opacity: 0.5;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
.node-type-badge {
|
|
763
|
+
display: inline-block;
|
|
764
|
+
padding: 0.25rem 0.75rem;
|
|
765
|
+
border-radius: 12px;
|
|
766
|
+
font-size: 0.75rem;
|
|
767
|
+
font-weight: 600;
|
|
768
|
+
text-transform: uppercase;
|
|
769
|
+
margin-bottom: 1rem;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
.node-type-organization {
|
|
773
|
+
background: #e3f2fd;
|
|
774
|
+
color: #1976d2;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
.node-type-user {
|
|
778
|
+
background: #f3e5f5;
|
|
779
|
+
color: #7b1fa2;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
.node-type-group {
|
|
783
|
+
background: #e8f5e9;
|
|
784
|
+
color: #388e3c;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
.detail-section {
|
|
788
|
+
margin-bottom: 2rem;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
.detail-section h3 {
|
|
792
|
+
font-size: 1rem;
|
|
793
|
+
color: #64748b;
|
|
794
|
+
margin-bottom: 1rem;
|
|
795
|
+
text-transform: uppercase;
|
|
796
|
+
letter-spacing: 0.05em;
|
|
797
|
+
font-weight: 600;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
.detail-row {
|
|
801
|
+
display: flex;
|
|
802
|
+
padding: 0.75rem 0;
|
|
803
|
+
border-bottom: 1px solid #f1f5f9;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
.detail-row:last-child {
|
|
807
|
+
border-bottom: none;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
.detail-label {
|
|
811
|
+
width: 200px;
|
|
812
|
+
font-weight: 600;
|
|
813
|
+
color: #475569;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.detail-value {
|
|
817
|
+
flex: 1;
|
|
818
|
+
color: #1e293b;
|
|
819
|
+
word-break: break-all;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
.detail-value code {
|
|
823
|
+
background: #f1f5f9;
|
|
824
|
+
padding: 0.125rem 0.375rem;
|
|
825
|
+
border-radius: 3px;
|
|
826
|
+
font-family: 'Courier New', monospace;
|
|
827
|
+
font-size: 0.875rem;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
.children-list {
|
|
831
|
+
list-style: none;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
.children-list li {
|
|
835
|
+
padding: 0.5rem 0;
|
|
836
|
+
color: #1e293b;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
.children-list li::before {
|
|
840
|
+
content: '→';
|
|
841
|
+
margin-right: 0.5rem;
|
|
842
|
+
color: #6200ee;
|
|
843
|
+
font-weight: bold;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
.loading-overlay {
|
|
847
|
+
position: absolute;
|
|
848
|
+
top: 0;
|
|
849
|
+
left: 0;
|
|
850
|
+
right: 0;
|
|
851
|
+
bottom: 0;
|
|
852
|
+
background: rgba(255, 255, 255, 0.9);
|
|
853
|
+
display: flex;
|
|
854
|
+
align-items: center;
|
|
855
|
+
justify-content: center;
|
|
856
|
+
z-index: 10;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
.spinner {
|
|
860
|
+
width: 50px;
|
|
861
|
+
height: 50px;
|
|
862
|
+
border: 4px solid #e0e0e0;
|
|
863
|
+
border-top-color: #6200ee;
|
|
864
|
+
border-radius: 50%;
|
|
865
|
+
animation: spin 1s linear infinite;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
@keyframes spin {
|
|
869
|
+
to {
|
|
870
|
+
transform: rotate(360deg);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
</style>
|
|
874
|
+
</head>
|
|
875
|
+
<body>
|
|
876
|
+
<div class="app-container">
|
|
877
|
+
<!-- Sidebar with Tree -->
|
|
878
|
+
<aside class="sidebar">
|
|
879
|
+
<div class="sidebar-header">
|
|
880
|
+
<h1>Organizations</h1>
|
|
881
|
+
<p>Browse your LDAP directory</p>
|
|
882
|
+
</div>
|
|
883
|
+
<div id="tree" class="tree-container"></div>
|
|
884
|
+
</aside>
|
|
885
|
+
|
|
886
|
+
<!-- Main Content Area -->
|
|
887
|
+
<main class="content-area">
|
|
888
|
+
<div class="content-header">
|
|
889
|
+
<h2>Node Details</h2>
|
|
890
|
+
</div>
|
|
891
|
+
<div class="content-body">
|
|
892
|
+
<div id="details" class="empty-state">
|
|
893
|
+
<span class="material-icons">account_tree</span>
|
|
894
|
+
<p>Select a node from the tree to view details</p>
|
|
895
|
+
</div>
|
|
896
|
+
</div>
|
|
897
|
+
</main>
|
|
898
|
+
</div>
|
|
899
|
+
|
|
900
|
+
<!-- LdapTreeViewer JavaScript -->
|
|
901
|
+
<script src="/static/browser/ldap-tree-viewer/LdapTreeViewer.js"></script>
|
|
902
|
+
|
|
903
|
+
<script>
|
|
904
|
+
const { LdapTreeViewer } = window.LdapTreeViewer;
|
|
905
|
+
|
|
906
|
+
// Initialize the tree viewer
|
|
907
|
+
const viewer = new LdapTreeViewer({
|
|
908
|
+
containerId: 'tree',
|
|
909
|
+
apiBaseUrl: window.location.origin,
|
|
910
|
+
onNodeClick: handleNodeClick,
|
|
911
|
+
onNodeExpand: handleNodeExpand,
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
// Handle node clicks
|
|
915
|
+
async function handleNodeClick(node) {
|
|
916
|
+
const detailsContainer = document.getElementById('details');
|
|
917
|
+
|
|
918
|
+
// Show loading state
|
|
919
|
+
detailsContainer.innerHTML = `
|
|
920
|
+
<div class="loading-overlay">
|
|
921
|
+
<div class="spinner"></div>
|
|
922
|
+
</div>
|
|
923
|
+
`;
|
|
924
|
+
|
|
925
|
+
try {
|
|
926
|
+
// Fetch full node details
|
|
927
|
+
const apiUrl = `${viewer.options.apiBaseUrl}/api/v1/ldap/organizations/${encodeURIComponent(node.dn)}`;
|
|
928
|
+
const response = await fetch(apiUrl);
|
|
929
|
+
|
|
930
|
+
if (!response.ok) {
|
|
931
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const nodeData = await response.json();
|
|
935
|
+
|
|
936
|
+
// Display node details
|
|
937
|
+
displayNodeDetails(node, nodeData);
|
|
938
|
+
} catch (error) {
|
|
939
|
+
console.error('Failed to load node details:', error);
|
|
940
|
+
detailsContainer.innerHTML = `
|
|
941
|
+
<div class="empty-state">
|
|
942
|
+
<span class="material-icons">error_outline</span>
|
|
943
|
+
<p>Failed to load node details</p>
|
|
944
|
+
<p style="font-size: 0.875rem; margin-top: 0.5rem;">${error.message}</p>
|
|
945
|
+
</div>
|
|
946
|
+
`;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Handle node expansion
|
|
951
|
+
function handleNodeExpand(node) {
|
|
952
|
+
console.log(
|
|
953
|
+
'Node expanded:',
|
|
954
|
+
node.displayName,
|
|
955
|
+
'(',
|
|
956
|
+
node.childrenDns.length,
|
|
957
|
+
'children)'
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Display node details in the content area
|
|
962
|
+
function displayNodeDetails(node, fullData) {
|
|
963
|
+
const detailsContainer = document.getElementById('details');
|
|
964
|
+
|
|
965
|
+
// Build attributes list
|
|
966
|
+
const attributesHtml = Object.entries(fullData)
|
|
967
|
+
.filter(([key]) => key !== 'dn')
|
|
968
|
+
.map(([key, value]) => {
|
|
969
|
+
const displayValue = Array.isArray(value)
|
|
970
|
+
? value.join(', ')
|
|
971
|
+
: String(value);
|
|
972
|
+
|
|
973
|
+
return `
|
|
974
|
+
<div class="detail-row">
|
|
975
|
+
<div class="detail-label">${key}</div>
|
|
976
|
+
<div class="detail-value"><code>${escapeHtml(displayValue)}</code></div>
|
|
977
|
+
</div>
|
|
978
|
+
`;
|
|
979
|
+
})
|
|
980
|
+
.join('');
|
|
981
|
+
|
|
982
|
+
// Build children list
|
|
983
|
+
const childrenHtml =
|
|
984
|
+
node.childrenDns.length > 0
|
|
985
|
+
? `
|
|
986
|
+
<div class="detail-section">
|
|
987
|
+
<h3>Children (${node.childrenDns.length})</h3>
|
|
988
|
+
<ul class="children-list">
|
|
989
|
+
${node.childrenDns
|
|
990
|
+
.slice(0, 10)
|
|
991
|
+
.map(dn => `<li>${escapeHtml(dn)}</li>`)
|
|
992
|
+
.join('')}
|
|
993
|
+
${node.childrenDns.length > 10 ? `<li>... and ${node.childrenDns.length - 10} more</li>` : ''}
|
|
994
|
+
</ul>
|
|
995
|
+
</div>
|
|
996
|
+
`
|
|
997
|
+
: '';
|
|
998
|
+
|
|
999
|
+
detailsContainer.innerHTML = `
|
|
1000
|
+
<div class="node-details">
|
|
1001
|
+
<span class="node-type-badge node-type-${node.type}">${node.type}</span>
|
|
1002
|
+
<h2 style="margin-bottom: 0.5rem;">${escapeHtml(node.displayName)}</h2>
|
|
1003
|
+
<p style="color: #64748b; font-family: monospace; font-size: 0.875rem; margin-bottom: 2rem;">
|
|
1004
|
+
${escapeHtml(node.dn)}
|
|
1005
|
+
</p>
|
|
1006
|
+
|
|
1007
|
+
<div class="detail-section">
|
|
1008
|
+
<h3>Attributes</h3>
|
|
1009
|
+
${attributesHtml}
|
|
1010
|
+
</div>
|
|
1011
|
+
|
|
1012
|
+
${childrenHtml}
|
|
1013
|
+
</div>
|
|
1014
|
+
`;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// Utility function to escape HTML
|
|
1018
|
+
function escapeHtml(text) {
|
|
1019
|
+
const div = document.createElement('div');
|
|
1020
|
+
div.textContent = text;
|
|
1021
|
+
return div.innerHTML;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// Initialize the viewer
|
|
1025
|
+
viewer.init().catch(error => {
|
|
1026
|
+
console.error('Failed to initialize tree viewer:', error);
|
|
1027
|
+
document.getElementById('tree').innerHTML = `
|
|
1028
|
+
<div style="padding: 1rem; color: #c62828; text-align: center;">
|
|
1029
|
+
<p><strong>Failed to initialize</strong></p>
|
|
1030
|
+
<p style="font-size: 0.875rem; margin-top: 0.5rem;">${error.message}</p>
|
|
1031
|
+
</div>
|
|
1032
|
+
`;
|
|
1033
|
+
});
|
|
1034
|
+
</script>
|
|
1035
|
+
</body>
|
|
1036
|
+
</html>
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
---
|
|
1040
|
+
|
|
1041
|
+
## Group Management Application
|
|
1042
|
+
|
|
1043
|
+
This example demonstrates a complete class-based group management application with initialization, group operations, and member management.
|
|
1044
|
+
|
|
1045
|
+
### Complete GroupManager Class
|
|
1046
|
+
|
|
1047
|
+
```javascript
|
|
1048
|
+
/**
|
|
1049
|
+
* GroupManager - Complete group management application
|
|
1050
|
+
* Provides methods for listing, creating, and managing LDAP groups
|
|
1051
|
+
*/
|
|
1052
|
+
class GroupManager {
|
|
1053
|
+
constructor(apiBaseUrl = window.location.origin) {
|
|
1054
|
+
this.apiBaseUrl = apiBaseUrl;
|
|
1055
|
+
this.config = null;
|
|
1056
|
+
this.groupsEndpoint = null;
|
|
1057
|
+
this.groupsBase = null;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Initialize the manager by loading configuration
|
|
1062
|
+
*/
|
|
1063
|
+
async init() {
|
|
1064
|
+
try {
|
|
1065
|
+
// Load configuration from API
|
|
1066
|
+
const response = await fetch(`${this.apiBaseUrl}/api/v1/config`);
|
|
1067
|
+
if (!response.ok) {
|
|
1068
|
+
throw new Error(`Failed to load config: ${response.status}`);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
this.config = await response.json();
|
|
1072
|
+
|
|
1073
|
+
// Discover groups endpoints from config
|
|
1074
|
+
const groupsConfig = this.config.features?.groups;
|
|
1075
|
+
if (!groupsConfig || !groupsConfig.enabled) {
|
|
1076
|
+
throw new Error(
|
|
1077
|
+
'Groups feature is not enabled in LDAP-Rest configuration'
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
this.groupsEndpoint = groupsConfig.endpoints;
|
|
1082
|
+
this.groupsBase = groupsConfig.base;
|
|
1083
|
+
|
|
1084
|
+
console.log('GroupManager initialized successfully');
|
|
1085
|
+
console.log('Groups base:', this.groupsBase);
|
|
1086
|
+
|
|
1087
|
+
return true;
|
|
1088
|
+
} catch (error) {
|
|
1089
|
+
console.error('Failed to initialize GroupManager:', error);
|
|
1090
|
+
throw error;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* List all groups or filter by name pattern
|
|
1096
|
+
* @param {string} matchPattern - Optional pattern to filter groups (e.g., "admin*")
|
|
1097
|
+
* @returns {Promise<Object>} Object with group CNs as keys and group data as values
|
|
1098
|
+
*/
|
|
1099
|
+
async listGroups(matchPattern = null) {
|
|
1100
|
+
if (!this.groupsEndpoint) {
|
|
1101
|
+
throw new Error('GroupManager not initialized. Call init() first.');
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
try {
|
|
1105
|
+
let url = this.groupsEndpoint.list;
|
|
1106
|
+
|
|
1107
|
+
// Add filter if provided
|
|
1108
|
+
if (matchPattern) {
|
|
1109
|
+
url += `?match=${encodeURIComponent(matchPattern)}`;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
const response = await fetch(url);
|
|
1113
|
+
if (!response.ok) {
|
|
1114
|
+
throw new Error(`Failed to list groups: ${response.status}`);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
const groups = await response.json();
|
|
1118
|
+
console.log(`Found ${Object.keys(groups).length} groups`);
|
|
1119
|
+
|
|
1120
|
+
return groups;
|
|
1121
|
+
} catch (error) {
|
|
1122
|
+
console.error('Error listing groups:', error);
|
|
1123
|
+
throw error;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* Get a specific group by CN
|
|
1129
|
+
* @param {string} cn - Group common name
|
|
1130
|
+
* @returns {Promise<Object>} Group data
|
|
1131
|
+
*/
|
|
1132
|
+
async getGroup(cn) {
|
|
1133
|
+
if (!this.groupsEndpoint) {
|
|
1134
|
+
throw new Error('GroupManager not initialized. Call init() first.');
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
try {
|
|
1138
|
+
const url = this.groupsEndpoint.get.replace(
|
|
1139
|
+
':cn',
|
|
1140
|
+
encodeURIComponent(cn)
|
|
1141
|
+
);
|
|
1142
|
+
const response = await fetch(url);
|
|
1143
|
+
|
|
1144
|
+
if (!response.ok) {
|
|
1145
|
+
if (response.status === 404) {
|
|
1146
|
+
throw new Error(`Group "${cn}" not found`);
|
|
1147
|
+
}
|
|
1148
|
+
throw new Error(`Failed to get group: ${response.status}`);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
return await response.json();
|
|
1152
|
+
} catch (error) {
|
|
1153
|
+
console.error(`Error getting group "${cn}":`, error);
|
|
1154
|
+
throw error;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* Create a new group
|
|
1160
|
+
* @param {string} cn - Group common name
|
|
1161
|
+
* @param {Object} options - Additional group options
|
|
1162
|
+
* @param {string[]} options.members - Array of member DNs
|
|
1163
|
+
* @param {string} options.description - Group description
|
|
1164
|
+
* @returns {Promise<Object>} Response with success status and DN
|
|
1165
|
+
*/
|
|
1166
|
+
async createGroup(cn, options = {}) {
|
|
1167
|
+
if (!this.groupsEndpoint) {
|
|
1168
|
+
throw new Error('GroupManager not initialized. Call init() first.');
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
try {
|
|
1172
|
+
const groupData = {
|
|
1173
|
+
cn: cn,
|
|
1174
|
+
...options,
|
|
1175
|
+
};
|
|
1176
|
+
|
|
1177
|
+
const response = await fetch(this.groupsEndpoint.create, {
|
|
1178
|
+
method: 'POST',
|
|
1179
|
+
headers: {
|
|
1180
|
+
'Content-Type': 'application/json',
|
|
1181
|
+
Accept: 'application/json',
|
|
1182
|
+
},
|
|
1183
|
+
body: JSON.stringify(groupData),
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
if (!response.ok) {
|
|
1187
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1188
|
+
throw new Error(
|
|
1189
|
+
errorData.error || `Failed to create group: ${response.status}`
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
const result = await response.json();
|
|
1194
|
+
console.log(`Group "${cn}" created successfully:`, result.dn);
|
|
1195
|
+
|
|
1196
|
+
return result;
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
console.error(`Error creating group "${cn}":`, error);
|
|
1199
|
+
throw error;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* Update a group's attributes
|
|
1205
|
+
* @param {string} cn - Group common name
|
|
1206
|
+
* @param {Object} changes - LDAP modify operations (replace, add, delete)
|
|
1207
|
+
* @returns {Promise<Object>} Response with success status
|
|
1208
|
+
*/
|
|
1209
|
+
async updateGroup(cn, changes) {
|
|
1210
|
+
if (!this.groupsEndpoint) {
|
|
1211
|
+
throw new Error('GroupManager not initialized. Call init() first.');
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
try {
|
|
1215
|
+
const url = this.groupsEndpoint.update.replace(
|
|
1216
|
+
':cn',
|
|
1217
|
+
encodeURIComponent(cn)
|
|
1218
|
+
);
|
|
1219
|
+
const response = await fetch(url, {
|
|
1220
|
+
method: 'PUT',
|
|
1221
|
+
headers: {
|
|
1222
|
+
'Content-Type': 'application/json',
|
|
1223
|
+
Accept: 'application/json',
|
|
1224
|
+
},
|
|
1225
|
+
body: JSON.stringify(changes),
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
if (!response.ok) {
|
|
1229
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1230
|
+
throw new Error(
|
|
1231
|
+
errorData.error || `Failed to update group: ${response.status}`
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
const result = await response.json();
|
|
1236
|
+
console.log(`Group "${cn}" updated successfully`);
|
|
1237
|
+
|
|
1238
|
+
return result;
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
console.error(`Error updating group "${cn}":`, error);
|
|
1241
|
+
throw error;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* Delete a group
|
|
1247
|
+
* @param {string} cn - Group common name
|
|
1248
|
+
* @returns {Promise<Object>} Response with success status
|
|
1249
|
+
*/
|
|
1250
|
+
async deleteGroup(cn) {
|
|
1251
|
+
if (!this.groupsEndpoint) {
|
|
1252
|
+
throw new Error('GroupManager not initialized. Call init() first.');
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
try {
|
|
1256
|
+
const url = this.groupsEndpoint.delete.replace(
|
|
1257
|
+
':cn',
|
|
1258
|
+
encodeURIComponent(cn)
|
|
1259
|
+
);
|
|
1260
|
+
const response = await fetch(url, {
|
|
1261
|
+
method: 'DELETE',
|
|
1262
|
+
headers: {
|
|
1263
|
+
Accept: 'application/json',
|
|
1264
|
+
},
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
if (!response.ok) {
|
|
1268
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1269
|
+
throw new Error(
|
|
1270
|
+
errorData.error || `Failed to delete group: ${response.status}`
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
const result = await response.json();
|
|
1275
|
+
console.log(`Group "${cn}" deleted successfully`);
|
|
1276
|
+
|
|
1277
|
+
return result;
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
console.error(`Error deleting group "${cn}":`, error);
|
|
1280
|
+
throw error;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* Add a member to a group
|
|
1286
|
+
* @param {string} groupCn - Group common name
|
|
1287
|
+
* @param {string|string[]} memberDn - Member DN(s) to add
|
|
1288
|
+
* @returns {Promise<Object>} Response with success status
|
|
1289
|
+
*/
|
|
1290
|
+
async addMember(groupCn, memberDn) {
|
|
1291
|
+
if (!this.groupsEndpoint) {
|
|
1292
|
+
throw new Error('GroupManager not initialized. Call init() first.');
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
try {
|
|
1296
|
+
const url = this.groupsEndpoint.addMember.replace(
|
|
1297
|
+
':id',
|
|
1298
|
+
encodeURIComponent(groupCn)
|
|
1299
|
+
);
|
|
1300
|
+
const response = await fetch(url, {
|
|
1301
|
+
method: 'POST',
|
|
1302
|
+
headers: {
|
|
1303
|
+
'Content-Type': 'application/json',
|
|
1304
|
+
Accept: 'application/json',
|
|
1305
|
+
},
|
|
1306
|
+
body: JSON.stringify({ member: memberDn }),
|
|
1307
|
+
});
|
|
1308
|
+
|
|
1309
|
+
if (!response.ok) {
|
|
1310
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1311
|
+
throw new Error(
|
|
1312
|
+
errorData.error || `Failed to add member: ${response.status}`
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
const result = await response.json();
|
|
1317
|
+
const memberCount = Array.isArray(memberDn) ? memberDn.length : 1;
|
|
1318
|
+
console.log(`Added ${memberCount} member(s) to group "${groupCn}"`);
|
|
1319
|
+
|
|
1320
|
+
return result;
|
|
1321
|
+
} catch (error) {
|
|
1322
|
+
console.error(`Error adding member to group "${groupCn}":`, error);
|
|
1323
|
+
throw error;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* Remove a member from a group
|
|
1329
|
+
* @param {string} groupCn - Group common name
|
|
1330
|
+
* @param {string} memberDn - Member DN to remove
|
|
1331
|
+
* @returns {Promise<Object>} Response with success status
|
|
1332
|
+
*/
|
|
1333
|
+
async removeMember(groupCn, memberDn) {
|
|
1334
|
+
if (!this.groupsEndpoint) {
|
|
1335
|
+
throw new Error('GroupManager not initialized. Call init() first.');
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
try {
|
|
1339
|
+
const url = this.groupsEndpoint.removeMember
|
|
1340
|
+
.replace(':cn', encodeURIComponent(groupCn))
|
|
1341
|
+
.replace(':memberId', encodeURIComponent(memberDn));
|
|
1342
|
+
|
|
1343
|
+
const response = await fetch(url, {
|
|
1344
|
+
method: 'DELETE',
|
|
1345
|
+
headers: {
|
|
1346
|
+
Accept: 'application/json',
|
|
1347
|
+
},
|
|
1348
|
+
});
|
|
1349
|
+
|
|
1350
|
+
if (!response.ok) {
|
|
1351
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1352
|
+
throw new Error(
|
|
1353
|
+
errorData.error || `Failed to remove member: ${response.status}`
|
|
1354
|
+
);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
const result = await response.json();
|
|
1358
|
+
console.log(`Removed member from group "${groupCn}"`);
|
|
1359
|
+
|
|
1360
|
+
return result;
|
|
1361
|
+
} catch (error) {
|
|
1362
|
+
console.error(`Error removing member from group "${groupCn}":`, error);
|
|
1363
|
+
throw error;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
/**
|
|
1368
|
+
* Get all members of a group
|
|
1369
|
+
* @param {string} cn - Group common name
|
|
1370
|
+
* @returns {Promise<string[]>} Array of member DNs
|
|
1371
|
+
*/
|
|
1372
|
+
async getMembers(cn) {
|
|
1373
|
+
try {
|
|
1374
|
+
const group = await this.getGroup(cn);
|
|
1375
|
+
const members = group.member || [];
|
|
1376
|
+
|
|
1377
|
+
// LDAP attributes are always arrays, ensure we return an array
|
|
1378
|
+
return Array.isArray(members) ? members : [members];
|
|
1379
|
+
} catch (error) {
|
|
1380
|
+
console.error(`Error getting members of group "${cn}":`, error);
|
|
1381
|
+
throw error;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
/**
|
|
1386
|
+
* Check if a user is a member of a group
|
|
1387
|
+
* @param {string} groupCn - Group common name
|
|
1388
|
+
* @param {string} userDn - User DN to check
|
|
1389
|
+
* @returns {Promise<boolean>} True if user is a member
|
|
1390
|
+
*/
|
|
1391
|
+
async isMember(groupCn, userDn) {
|
|
1392
|
+
try {
|
|
1393
|
+
const members = await this.getMembers(groupCn);
|
|
1394
|
+
return members.includes(userDn);
|
|
1395
|
+
} catch (error) {
|
|
1396
|
+
console.error(`Error checking membership in group "${groupCn}":`, error);
|
|
1397
|
+
throw error;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// Export for use in modules
|
|
1403
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
1404
|
+
module.exports = GroupManager;
|
|
1405
|
+
}
|
|
1406
|
+
```
|
|
1407
|
+
|
|
1408
|
+
### Usage Example
|
|
1409
|
+
|
|
1410
|
+
```javascript
|
|
1411
|
+
// Initialize the GroupManager
|
|
1412
|
+
const manager = new GroupManager('http://localhost:8081');
|
|
1413
|
+
|
|
1414
|
+
async function demonstrateGroupManagement() {
|
|
1415
|
+
try {
|
|
1416
|
+
// Initialize
|
|
1417
|
+
console.log('Initializing GroupManager...');
|
|
1418
|
+
await manager.init();
|
|
1419
|
+
|
|
1420
|
+
// List all groups
|
|
1421
|
+
console.log('\n--- Listing all groups ---');
|
|
1422
|
+
const allGroups = await manager.listGroups();
|
|
1423
|
+
console.log('All groups:', Object.keys(allGroups));
|
|
1424
|
+
|
|
1425
|
+
// Create a new group
|
|
1426
|
+
console.log('\n--- Creating a new group ---');
|
|
1427
|
+
const newGroup = await manager.createGroup('developers', {
|
|
1428
|
+
description: 'Development Team',
|
|
1429
|
+
member: ['uid=alice,ou=users,dc=example,dc=com'],
|
|
1430
|
+
});
|
|
1431
|
+
console.log('Created group:', newGroup.dn);
|
|
1432
|
+
|
|
1433
|
+
// Get group details
|
|
1434
|
+
console.log('\n--- Getting group details ---');
|
|
1435
|
+
const groupDetails = await manager.getGroup('developers');
|
|
1436
|
+
console.log('Group details:', groupDetails);
|
|
1437
|
+
|
|
1438
|
+
// Add members to group
|
|
1439
|
+
console.log('\n--- Adding members ---');
|
|
1440
|
+
await manager.addMember('developers', 'uid=bob,ou=users,dc=example,dc=com');
|
|
1441
|
+
await manager.addMember('developers', [
|
|
1442
|
+
'uid=carol,ou=users,dc=example,dc=com',
|
|
1443
|
+
'uid=dave,ou=users,dc=example,dc=com',
|
|
1444
|
+
]);
|
|
1445
|
+
|
|
1446
|
+
// Get all members
|
|
1447
|
+
console.log('\n--- Getting all members ---');
|
|
1448
|
+
const members = await manager.getMembers('developers');
|
|
1449
|
+
console.log('Group members:', members);
|
|
1450
|
+
|
|
1451
|
+
// Check membership
|
|
1452
|
+
console.log('\n--- Checking membership ---');
|
|
1453
|
+
const isBobMember = await manager.isMember(
|
|
1454
|
+
'developers',
|
|
1455
|
+
'uid=bob,ou=users,dc=example,dc=com'
|
|
1456
|
+
);
|
|
1457
|
+
console.log('Is Bob a member?', isBobMember);
|
|
1458
|
+
|
|
1459
|
+
// Update group description
|
|
1460
|
+
console.log('\n--- Updating group ---');
|
|
1461
|
+
await manager.updateGroup('developers', {
|
|
1462
|
+
replace: {
|
|
1463
|
+
description: 'Software Development Team',
|
|
1464
|
+
},
|
|
1465
|
+
});
|
|
1466
|
+
|
|
1467
|
+
// Remove a member
|
|
1468
|
+
console.log('\n--- Removing member ---');
|
|
1469
|
+
await manager.removeMember(
|
|
1470
|
+
'developers',
|
|
1471
|
+
'uid=dave,ou=users,dc=example,dc=com'
|
|
1472
|
+
);
|
|
1473
|
+
|
|
1474
|
+
// List updated members
|
|
1475
|
+
const updatedMembers = await manager.getMembers('developers');
|
|
1476
|
+
console.log('Updated members:', updatedMembers);
|
|
1477
|
+
|
|
1478
|
+
// Filter groups
|
|
1479
|
+
console.log('\n--- Filtering groups ---');
|
|
1480
|
+
const filteredGroups = await manager.listGroups('dev*');
|
|
1481
|
+
console.log('Groups matching "dev*":', Object.keys(filteredGroups));
|
|
1482
|
+
} catch (error) {
|
|
1483
|
+
console.error('Error in demonstration:', error);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
// Run the demonstration
|
|
1488
|
+
demonstrateGroupManagement();
|
|
1489
|
+
```
|
|
1490
|
+
|
|
1491
|
+
### HTML Usage Example
|
|
1492
|
+
|
|
1493
|
+
```html
|
|
1494
|
+
<!DOCTYPE html>
|
|
1495
|
+
<html lang="en">
|
|
1496
|
+
<head>
|
|
1497
|
+
<meta charset="UTF-8" />
|
|
1498
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1499
|
+
<title>Group Management - LDAP-Rest</title>
|
|
1500
|
+
<style>
|
|
1501
|
+
body {
|
|
1502
|
+
font-family: Arial, sans-serif;
|
|
1503
|
+
max-width: 1200px;
|
|
1504
|
+
margin: 0 auto;
|
|
1505
|
+
padding: 2rem;
|
|
1506
|
+
background: #f5f5f5;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
.container {
|
|
1510
|
+
background: white;
|
|
1511
|
+
padding: 2rem;
|
|
1512
|
+
border-radius: 8px;
|
|
1513
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
h1 {
|
|
1517
|
+
margin-bottom: 2rem;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
.button {
|
|
1521
|
+
padding: 0.5rem 1rem;
|
|
1522
|
+
margin: 0.25rem;
|
|
1523
|
+
background: #6200ee;
|
|
1524
|
+
color: white;
|
|
1525
|
+
border: none;
|
|
1526
|
+
border-radius: 4px;
|
|
1527
|
+
cursor: pointer;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
.button:hover {
|
|
1531
|
+
background: #3700b3;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
#output {
|
|
1535
|
+
margin-top: 2rem;
|
|
1536
|
+
padding: 1rem;
|
|
1537
|
+
background: #f8f9fa;
|
|
1538
|
+
border-radius: 4px;
|
|
1539
|
+
font-family: monospace;
|
|
1540
|
+
white-space: pre-wrap;
|
|
1541
|
+
max-height: 600px;
|
|
1542
|
+
overflow-y: auto;
|
|
1543
|
+
}
|
|
1544
|
+
</style>
|
|
1545
|
+
</head>
|
|
1546
|
+
<body>
|
|
1547
|
+
<div class="container">
|
|
1548
|
+
<h1>Group Management</h1>
|
|
1549
|
+
|
|
1550
|
+
<div>
|
|
1551
|
+
<button class="button" onclick="listAllGroups()">
|
|
1552
|
+
List All Groups
|
|
1553
|
+
</button>
|
|
1554
|
+
<button class="button" onclick="createTestGroup()">
|
|
1555
|
+
Create Test Group
|
|
1556
|
+
</button>
|
|
1557
|
+
<button class="button" onclick="addTestMember()">Add Member</button>
|
|
1558
|
+
<button class="button" onclick="removeTestMember()">
|
|
1559
|
+
Remove Member
|
|
1560
|
+
</button>
|
|
1561
|
+
</div>
|
|
1562
|
+
|
|
1563
|
+
<div id="output">Ready. Click a button to perform an operation.</div>
|
|
1564
|
+
</div>
|
|
1565
|
+
|
|
1566
|
+
<script src="./GroupManager.js"></script>
|
|
1567
|
+
<script>
|
|
1568
|
+
const manager = new GroupManager();
|
|
1569
|
+
const output = document.getElementById('output');
|
|
1570
|
+
|
|
1571
|
+
// Initialize on page load
|
|
1572
|
+
manager
|
|
1573
|
+
.init()
|
|
1574
|
+
.then(() => {
|
|
1575
|
+
output.textContent =
|
|
1576
|
+
'GroupManager initialized successfully!\n\nClick a button to perform an operation.';
|
|
1577
|
+
})
|
|
1578
|
+
.catch(error => {
|
|
1579
|
+
output.textContent = 'Failed to initialize:\n' + error.message;
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1582
|
+
async function listAllGroups() {
|
|
1583
|
+
try {
|
|
1584
|
+
output.textContent = 'Loading groups...';
|
|
1585
|
+
const groups = await manager.listGroups();
|
|
1586
|
+
output.textContent = 'Groups:\n\n' + JSON.stringify(groups, null, 2);
|
|
1587
|
+
} catch (error) {
|
|
1588
|
+
output.textContent = 'Error:\n' + error.message;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
async function createTestGroup() {
|
|
1593
|
+
try {
|
|
1594
|
+
output.textContent = 'Creating test group...';
|
|
1595
|
+
const result = await manager.createGroup('test-group', {
|
|
1596
|
+
description: 'Test Group',
|
|
1597
|
+
member: ['uid=testuser,ou=users,dc=example,dc=com'],
|
|
1598
|
+
});
|
|
1599
|
+
output.textContent =
|
|
1600
|
+
'Group created:\n\n' + JSON.stringify(result, null, 2);
|
|
1601
|
+
} catch (error) {
|
|
1602
|
+
output.textContent = 'Error:\n' + error.message;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
async function addTestMember() {
|
|
1607
|
+
try {
|
|
1608
|
+
output.textContent = 'Adding member...';
|
|
1609
|
+
const result = await manager.addMember(
|
|
1610
|
+
'test-group',
|
|
1611
|
+
'uid=newuser,ou=users,dc=example,dc=com'
|
|
1612
|
+
);
|
|
1613
|
+
output.textContent =
|
|
1614
|
+
'Member added:\n\n' + JSON.stringify(result, null, 2);
|
|
1615
|
+
} catch (error) {
|
|
1616
|
+
output.textContent = 'Error:\n' + error.message;
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
async function removeTestMember() {
|
|
1621
|
+
try {
|
|
1622
|
+
output.textContent = 'Removing member...';
|
|
1623
|
+
const result = await manager.removeMember(
|
|
1624
|
+
'test-group',
|
|
1625
|
+
'uid=newuser,ou=users,dc=example,dc=com'
|
|
1626
|
+
);
|
|
1627
|
+
output.textContent =
|
|
1628
|
+
'Member removed:\n\n' + JSON.stringify(result, null, 2);
|
|
1629
|
+
} catch (error) {
|
|
1630
|
+
output.textContent = 'Error:\n' + error.message;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
</script>
|
|
1634
|
+
</body>
|
|
1635
|
+
</html>
|
|
1636
|
+
```
|
|
1637
|
+
|
|
1638
|
+
---
|
|
1639
|
+
|
|
1640
|
+
## Next Steps
|
|
1641
|
+
|
|
1642
|
+
- **[REST API Documentation](../api/REST_API.md)** - Learn about all available API endpoints
|
|
1643
|
+
- **[Browser Libraries Guide](../browser/LIBRARIES.md)** - Detailed documentation for LdapTreeViewer and LdapUserEditor
|
|
1644
|
+
- **[JSON Schemas Guide](../schemas/SCHEMAS.md)** - Understanding schema-driven architecture
|
|
1645
|
+
- **[Developer Guide](../DEVELOPER_GUIDE.md)** - Complete developer documentation
|
|
1646
|
+
|
|
1647
|
+
---
|
|
1648
|
+
|
|
1649
|
+
## Resources
|
|
1650
|
+
|
|
1651
|
+
- [GitHub Repository](https://github.com/linagora/ldap-rest)
|
|
1652
|
+
- [API Reference](../api/REFERENCE.md)
|
|
1653
|
+
- [Plugin Development](../plugins/DEVELOPMENT.md)
|
|
1654
|
+
|
|
1655
|
+
---
|
|
1656
|
+
|
|
1657
|
+
## License
|
|
1658
|
+
|
|
1659
|
+
AGPL-3.0 - Copyright 2025-present LINAGORA
|