ExternalUserGroupManagement.tsx 7.3 KB

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