SyncExecution.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import {
  2. FC, useCallback, useEffect, useState,
  3. } from 'react';
  4. import { useTranslation } from 'react-i18next';
  5. import { Modal, ModalHeader, ModalBody } from 'reactstrap';
  6. import { toastError, toastSuccess } from '~/client/util/toastr';
  7. import LabeledProgressBar from '~/components/Admin/Common/LabeledProgressBar';
  8. import { ExternalGroupProviderType } from '~/features/external-user-group/interfaces/external-user-group';
  9. import { SocketEventName } from '~/interfaces/websocket';
  10. import { useAdminSocket } from '~/stores/socket-io';
  11. import { useSWRxExternalUserGroupList } from '../../stores/external-user-group';
  12. type SyncExecutionProps = {
  13. provider: ExternalGroupProviderType
  14. requestSyncAPI: (e?: React.FormEvent<HTMLFormElement>) => Promise<void>
  15. AdditionalForm?: FC
  16. }
  17. enum SyncStatus {
  18. beforeSync,
  19. syncExecuting,
  20. syncCompleted,
  21. syncFailed,
  22. }
  23. export const SyncExecution = ({
  24. provider,
  25. requestSyncAPI,
  26. AdditionalForm = () => <></>,
  27. }: SyncExecutionProps): JSX.Element => {
  28. const { t } = useTranslation('admin');
  29. const { data: socket } = useAdminSocket();
  30. const { mutate: mutateExternalUserGroups } = useSWRxExternalUserGroupList();
  31. const [syncStatus, setSyncStatus] = useState<SyncStatus>(SyncStatus.beforeSync);
  32. const [progress, setProgress] = useState({
  33. total: 0,
  34. current: 0,
  35. });
  36. const [isAlertModalOpen, setIsAlertModalOpen] = useState(false);
  37. // value to propagate the submit event of form to submit confirm modal
  38. const [currentSubmitEvent, setCurrentSubmitEvent] = useState<React.FormEvent<HTMLFormElement>>();
  39. useEffect(() => {
  40. if (socket == null) return;
  41. const eventName = SocketEventName.externalUserGroup[provider];
  42. socket.on(eventName.GroupSyncProgress, (data) => {
  43. setSyncStatus(SyncStatus.syncExecuting);
  44. setProgress({
  45. total: data.totalCount,
  46. current: data.count,
  47. });
  48. });
  49. socket.on(eventName.GroupSyncCompleted, () => {
  50. setSyncStatus(SyncStatus.syncCompleted);
  51. mutateExternalUserGroups();
  52. toastSuccess(t('external_user_group.sync_succeeded'));
  53. });
  54. socket.on(eventName.GroupSyncFailed, () => {
  55. setSyncStatus(SyncStatus.syncFailed);
  56. mutateExternalUserGroups();
  57. toastError(t('external_user_group.sync_failed'));
  58. });
  59. return () => {
  60. socket.off(eventName.GroupSyncProgress);
  61. socket.off(eventName.GroupSyncCompleted);
  62. socket.off(eventName.GroupSyncFailed);
  63. };
  64. }, [socket, mutateExternalUserGroups, t, provider]);
  65. const onSyncBtnClick = (e: React.FormEvent<HTMLFormElement>) => {
  66. e.preventDefault();
  67. setCurrentSubmitEvent(e);
  68. setIsAlertModalOpen(true);
  69. };
  70. const onSyncExecConfirmBtnClick = useCallback(async() => {
  71. setIsAlertModalOpen(false);
  72. try {
  73. // set sync status before requesting to API, so that setting to syncFailed does not get overwritten
  74. setSyncStatus(SyncStatus.syncExecuting);
  75. setProgress({ total: 0, current: 0 });
  76. await requestSyncAPI(currentSubmitEvent);
  77. }
  78. catch (errs) {
  79. setSyncStatus(SyncStatus.syncFailed);
  80. toastError(t(errs[0]?.code));
  81. }
  82. }, [t, requestSyncAPI, currentSubmitEvent]);
  83. const renderProgressBar = () => {
  84. if (syncStatus === SyncStatus.beforeSync) return null;
  85. let header;
  86. if (syncStatus === SyncStatus.syncExecuting) {
  87. header = 'Processing..';
  88. }
  89. else if (syncStatus === SyncStatus.syncCompleted) {
  90. header = 'Completed';
  91. }
  92. else {
  93. header = 'Failed';
  94. }
  95. return (
  96. <LabeledProgressBar
  97. header={header}
  98. currentCount={progress.current}
  99. totalCount={progress.total}
  100. />
  101. );
  102. };
  103. return (
  104. <>
  105. <h3 className="border-bottom mb-3">{t('external_user_group.execute_sync')}</h3>
  106. <div className="row">
  107. <div className="col-md-3"></div>
  108. <div className="col-md-9">
  109. {renderProgressBar()}
  110. </div>
  111. </div>
  112. <form onSubmit={onSyncBtnClick}>
  113. <AdditionalForm />
  114. <div className="row">
  115. <div className="col-md-3"></div>
  116. <div className="col-md-6"><button className="btn btn-primary" type="submit">{t('external_user_group.sync')}</button></div>
  117. </div>
  118. </form>
  119. <Modal
  120. className="select-grant-group"
  121. isOpen={isAlertModalOpen}
  122. toggle={() => setIsAlertModalOpen(false)}
  123. >
  124. <ModalHeader tag="h4" toggle={() => setIsAlertModalOpen(false)} className="bg-purple text-light">
  125. <i className="icon-fw icon-exclamation align-middle"></i>
  126. <span className="align-middle">{t('external_user_group.confirmation_before_sync')}</span>
  127. </ModalHeader>
  128. <ModalBody>
  129. <ul>
  130. <li>{t('external_user_group.execution_time_warning')}</li>
  131. <li>{t('external_user_group.parallel_sync_forbidden')}</li>
  132. </ul>
  133. <div className="text-center">
  134. <button className="btn btn-primary" type="button" onClick={onSyncExecConfirmBtnClick}>{t('Execute')}</button>
  135. </div>
  136. </ModalBody>
  137. </Modal>
  138. </>
  139. );
  140. };