ExternalUserGroupManagement.tsx 7.7 KB

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