SyncExecution.tsx 5.3 KB

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