ExternalUserGroupManagement.tsx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import type { IGrantedGroup } from '@growi/core';
  2. import { GroupType, getIdForRef } from '@growi/core';
  3. import type { FC } from 'react';
  4. import { useCallback, useMemo, useState } from 'react';
  5. import { useTranslation } from 'react-i18next';
  6. import { TabContent, TabPane } from 'reactstrap';
  7. import { UserGroupDeleteModal } from '~/client/components/Admin/UserGroup/UserGroupDeleteModal';
  8. import { UserGroupModal } from '~/client/components/Admin/UserGroup/UserGroupModal';
  9. import { UserGroupTable } from '~/client/components/Admin/UserGroup/UserGroupTable';
  10. import CustomNav from '~/client/components/CustomNavigation/CustomNav';
  11. import { apiv3Delete, apiv3Put } from '~/client/util/apiv3-client';
  12. import { toastError, toastSuccess } from '~/client/util/toastr';
  13. import type { IExternalUserGroupHasId } from '~/features/external-user-group/interfaces/external-user-group';
  14. import type { PageActionOnGroupDelete } from '~/interfaces/user-group';
  15. import { useIsAclEnabled } from '~/states/server-configurations';
  16. import { useSWRxUserGroupList } from '~/stores/user-group';
  17. import {
  18. useSWRxChildExternalUserGroupList,
  19. useSWRxExternalUserGroupList,
  20. useSWRxExternalUserGroupRelationList,
  21. } from '../../stores/external-user-group';
  22. import { KeycloakGroupManagement } from './KeycloakGroupManagement';
  23. import { LdapGroupManagement } from './LdapGroupManagement';
  24. export const ExternalGroupManagement: FC = () => {
  25. const { data: externalUserGroupList, mutate: mutateExternalUserGroups } =
  26. useSWRxExternalUserGroupList();
  27. const { data: userGroupList } = useSWRxUserGroupList();
  28. const externalUserGroups =
  29. externalUserGroupList != null ? externalUserGroupList : [];
  30. const externalUserGroupsForDeleteModal: IGrantedGroup[] =
  31. externalUserGroups.map((group) => {
  32. return { item: group, type: GroupType.externalUserGroup };
  33. });
  34. const userGroupsForDeleteModal: IGrantedGroup[] =
  35. userGroupList != null
  36. ? userGroupList.map((group) => {
  37. return { item: group, type: GroupType.userGroup };
  38. })
  39. : [];
  40. const externalUserGroupIds = externalUserGroups.map((group) => group._id);
  41. const { data: externalUserGroupRelationList } =
  42. useSWRxExternalUserGroupRelationList(externalUserGroupIds);
  43. const externalUserGroupRelations =
  44. externalUserGroupRelationList != null ? externalUserGroupRelationList : [];
  45. const { data: childExternalUserGroupsList } =
  46. useSWRxChildExternalUserGroupList(externalUserGroupIds);
  47. const childExternalUserGroups =
  48. childExternalUserGroupsList?.childUserGroups != null
  49. ? childExternalUserGroupsList.childUserGroups
  50. : [];
  51. const [isAclEnabled] = useIsAclEnabled();
  52. const [activeTab, setActiveTab] = useState('ldap');
  53. const [activeComponents, setActiveComponents] = useState(new Set(['ldap']));
  54. const [selectedExternalUserGroup, setSelectedExternalUserGroup] = useState<
  55. IExternalUserGroupHasId | undefined
  56. >(undefined); // not null but undefined (to use defaultProps in UserGroupDeleteModal)
  57. const [isUpdateModalShown, setUpdateModalShown] = useState<boolean>(false);
  58. const [isDeleteModalShown, setDeleteModalShown] = useState<boolean>(false);
  59. const { t } = useTranslation('admin');
  60. const showUpdateModal = useCallback((group: IExternalUserGroupHasId) => {
  61. setUpdateModalShown(true);
  62. setSelectedExternalUserGroup(group);
  63. }, []);
  64. const hideUpdateModal = useCallback(() => {
  65. setUpdateModalShown(false);
  66. setSelectedExternalUserGroup(undefined);
  67. }, []);
  68. const syncUserGroupAndRelations = useCallback(async () => {
  69. try {
  70. await mutateExternalUserGroups();
  71. } catch (err) {
  72. toastError(err);
  73. }
  74. }, [mutateExternalUserGroups]);
  75. const showDeleteModal = useCallback(
  76. async (group: IExternalUserGroupHasId) => {
  77. try {
  78. await syncUserGroupAndRelations();
  79. setSelectedExternalUserGroup(group);
  80. setDeleteModalShown(true);
  81. } catch (err) {
  82. toastError(err);
  83. }
  84. },
  85. [syncUserGroupAndRelations],
  86. );
  87. const hideDeleteModal = useCallback(() => {
  88. setSelectedExternalUserGroup(undefined);
  89. setDeleteModalShown(false);
  90. }, []);
  91. const updateExternalUserGroup = useCallback(
  92. async (userGroupData: IExternalUserGroupHasId) => {
  93. try {
  94. await apiv3Put(`/external-user-groups/${userGroupData._id}`, {
  95. description: userGroupData.description,
  96. });
  97. toastSuccess(
  98. t('toaster.update_successed', {
  99. target: t('ExternalUserGroup'),
  100. ns: 'commons',
  101. }),
  102. );
  103. await mutateExternalUserGroups();
  104. hideUpdateModal();
  105. } catch (err) {
  106. toastError(err);
  107. }
  108. },
  109. [t, mutateExternalUserGroups, hideUpdateModal],
  110. );
  111. const deleteExternalUserGroupById = useCallback(
  112. async (
  113. deleteGroupId: string,
  114. actionName: PageActionOnGroupDelete,
  115. transferToUserGroup: IGrantedGroup | null,
  116. ) => {
  117. const transferToUserGroupId =
  118. transferToUserGroup != null
  119. ? getIdForRef(transferToUserGroup.item)
  120. : null;
  121. const transferToUserGroupType =
  122. transferToUserGroup != null ? transferToUserGroup.type : null;
  123. try {
  124. await apiv3Delete(`/external-user-groups/${deleteGroupId}`, {
  125. actionName,
  126. transferToUserGroupId,
  127. transferToUserGroupType,
  128. });
  129. // sync
  130. await mutateExternalUserGroups();
  131. hideDeleteModal();
  132. toastSuccess(`Deleted ${selectedExternalUserGroup?.name} group.`);
  133. } catch (err) {
  134. toastError(new Error('Unable to delete the groups'));
  135. }
  136. },
  137. [mutateExternalUserGroups, selectedExternalUserGroup, hideDeleteModal],
  138. );
  139. const switchActiveTab = (selectedTab) => {
  140. setActiveTab(selectedTab);
  141. setActiveComponents(activeComponents.add(selectedTab));
  142. };
  143. const navTabMapping = useMemo(() => {
  144. return {
  145. ldap: {
  146. Icon: () => (
  147. <span className="material-symbols-outlined">network_node</span>
  148. ),
  149. i18n: 'LDAP',
  150. },
  151. keycloak: {
  152. Icon: () => <span className="material-symbols-outlined">key</span>,
  153. i18n: 'Keycloak',
  154. },
  155. };
  156. }, []);
  157. return (
  158. <>
  159. <h2 className="border-bottom mb-4">
  160. {t('external_user_group.management')}
  161. </h2>
  162. <UserGroupTable
  163. headerLabel={t('admin:user_group_management.group_list')}
  164. userGroups={externalUserGroups}
  165. childUserGroups={childExternalUserGroups}
  166. isAclEnabled={isAclEnabled ?? false}
  167. onEdit={showUpdateModal}
  168. onDelete={showDeleteModal}
  169. userGroupRelations={externalUserGroupRelations}
  170. isExternalGroup
  171. />
  172. <UserGroupModal
  173. userGroup={selectedExternalUserGroup}
  174. buttonLabel={t('Update')}
  175. onClickSubmit={updateExternalUserGroup}
  176. isShow={isUpdateModalShown}
  177. onHide={hideUpdateModal}
  178. isExternalGroup
  179. />
  180. <UserGroupDeleteModal
  181. userGroups={userGroupsForDeleteModal.concat(
  182. externalUserGroupsForDeleteModal,
  183. )}
  184. deleteUserGroup={selectedExternalUserGroup}
  185. onDelete={deleteExternalUserGroupById}
  186. isShow={isDeleteModalShown}
  187. onHide={hideDeleteModal}
  188. />
  189. <CustomNav
  190. activeTab={activeTab}
  191. navTabMapping={navTabMapping}
  192. onNavSelected={switchActiveTab}
  193. hideBorderBottom
  194. breakpointToSwitchDropdownDown="md"
  195. />
  196. <TabContent activeTab={activeTab} className="p-5">
  197. <TabPane tabId="ldap">
  198. {activeComponents.has('ldap') && <LdapGroupManagement />}
  199. </TabPane>
  200. <TabPane tabId="keycloak">
  201. {activeComponents.has('keycloak') && <KeycloakGroupManagement />}
  202. </TabPane>
  203. </TabContent>
  204. </>
  205. );
  206. };