G2GDataTransfer.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. import React, {
  2. useCallback, useEffect, useState,
  3. } from 'react';
  4. import { useTranslation } from 'next-i18next';
  5. import { useGenerateTransferKey } from '~/client/services/g2g-transfer';
  6. import { apiv3Get, apiv3Post } from '~/client/util/apiv3-client';
  7. import { toastError, toastSuccess } from '~/client/util/toastr';
  8. import { G2G_PROGRESS_STATUS, type G2GProgress } from '~/interfaces/g2g-transfer';
  9. import { useAdminSocket } from '~/stores/socket-io';
  10. import CustomCopyToClipBoard from '../Common/CustomCopyToClipBoard';
  11. // import { FileUploadSettingMolecule } from './App/FileUploadSetting';
  12. import G2GDataTransferExportForm from './G2GDataTransferExportForm';
  13. import G2GDataTransferStatusIcon from './G2GDataTransferStatusIcon';
  14. const IGNORED_COLLECTION_NAMES = [
  15. 'sessions', 'rlflx', 'activities', 'attachmentFiles.files', 'attachmentFiles.chunks',
  16. ];
  17. const G2GDataTransfer = (): JSX.Element => {
  18. const { data: socket } = useAdminSocket();
  19. const { t } = useTranslation(['admin', 'commons']);
  20. const [startTransferKey, setStartTransferKey] = useState('');
  21. const [collections, setCollections] = useState<string[]>([]);
  22. const [selectedCollections, setSelectedCollections] = useState<Set<string>>(new Set());
  23. const [optionsMap, setOptionsMap] = useState<any>({});
  24. const [isShowExportForm, setShowExportForm] = useState(false);
  25. const [isTransferring, setTransferring] = useState(false);
  26. const [g2gProgress, setG2GProgress] = useState<G2GProgress>({
  27. mongo: G2G_PROGRESS_STATUS.PENDING,
  28. attachments: G2G_PROGRESS_STATUS.PENDING,
  29. });
  30. // File upload settings
  31. // const [fileUploadType, setFileUploadType] = useState('aws');
  32. // const [s3ReferenceFileWithRelayMode, setS3ReferenceFileWithRelayMode] = useState(false);
  33. // const [s3Region, setS3Region] = useState('');
  34. // const [s3CustomEndpoint, setS3CustomEndpoint] = useState('');
  35. // const [s3Bucket, setS3Bucket] = useState('');
  36. // const [s3AccessKeyId, setS3AccessKeyId] = useState('');
  37. // const [s3SecretAccessKey, setS3SecretAccessKey] = useState('');
  38. // const [gcsReferenceFileWithRelayMode, setGcsReferenceFileWithRelayMode] = useState(false);
  39. // const [gcsApiKeyJsonPath, setGcsApiKeyJsonPath] = useState('');
  40. // const [gcsBucket, setGcsBucket] = useState('');
  41. // const [gcsUploadNamespace, setGcsUploadNamespace] = useState('');
  42. const updateSelectedCollections = (newSelectedCollections: Set<string>) => {
  43. setSelectedCollections(newSelectedCollections);
  44. };
  45. const updateOptionsMap = (newOptionsMap: any) => {
  46. setOptionsMap(newOptionsMap);
  47. };
  48. const onChangeTransferKeyHandler = useCallback((e) => {
  49. setStartTransferKey(e.target.value);
  50. }, []);
  51. const setCollectionsAndSelectedCollections = useCallback(async() => {
  52. const { data: collectionsData } = await apiv3Get<{collections: any[]}>('/mongo/collections', {});
  53. // filter only not ignored collection names
  54. const filteredCollections = collectionsData.collections.filter((collectionName) => {
  55. return !IGNORED_COLLECTION_NAMES.includes(collectionName);
  56. });
  57. setCollections(filteredCollections);
  58. setSelectedCollections(new Set(filteredCollections));
  59. }, []);
  60. const setupWebsocketEventHandler = useCallback(() => {
  61. if (socket != null) {
  62. socket.on('admin:g2gProgress', (g2gProgress: G2GProgress) => {
  63. setG2GProgress(g2gProgress);
  64. if (g2gProgress.mongo === G2G_PROGRESS_STATUS.COMPLETED && g2gProgress.attachments === G2G_PROGRESS_STATUS.COMPLETED) {
  65. toastSuccess(t('admin:g2g:transfer_success'));
  66. }
  67. });
  68. socket.on('admin:g2gError', ({ key }) => {
  69. setTransferring(false);
  70. toastError(t(key));
  71. });
  72. }
  73. }, [socket, t, setTransferring, setG2GProgress]);
  74. const cleanUpWebsocketEventHandler = useCallback(() => {
  75. if (socket != null) {
  76. socket.off('admin:g2gProgress');
  77. socket.off('admin:g2gError');
  78. }
  79. }, [socket]);
  80. const { transferKey, generateTransferKey } = useGenerateTransferKey();
  81. const onClickHandler = useCallback(async() => {
  82. try {
  83. await generateTransferKey();
  84. }
  85. catch (errs) {
  86. toastError(errs);
  87. }
  88. }, [generateTransferKey]);
  89. const startTransfer = useCallback(async(e) => {
  90. e.preventDefault();
  91. setTransferring(true);
  92. try {
  93. await apiv3Post('/g2g-transfer/transfer', {
  94. transferKey: startTransferKey,
  95. collections: Array.from(selectedCollections),
  96. optionsMap,
  97. });
  98. }
  99. catch (errs) {
  100. toastError(errs);
  101. }
  102. }, [setTransferring, startTransferKey, selectedCollections, optionsMap]);
  103. // File upload
  104. // const onChangeFileUploadTypeHandler = useCallback((e: ChangeEvent, type: string) => {
  105. // setFileUploadType(type);
  106. // }, []);
  107. // S3
  108. // const onChangeS3ReferenceFileWithRelayModeHandler = useCallback((val: boolean) => {
  109. // setS3ReferenceFileWithRelayMode(val);
  110. // }, []);
  111. // const onChangeS3RegionHandler = useCallback((val: string) => {
  112. // setS3Region(val);
  113. // }, []);
  114. // const onChangeS3CustomEndpointHandler = useCallback((val: string) => {
  115. // setS3CustomEndpoint(val);
  116. // }, []);
  117. // const onChangeS3BucketHandler = useCallback((val: string) => {
  118. // setS3Bucket(val);
  119. // }, []);
  120. // const onChangeS3AccessKeyIdHandler = useCallback((val: string) => {
  121. // setS3AccessKeyId(val);
  122. // }, []);
  123. // const onChangeS3SecretAccessKeyHandler = useCallback((val: string) => {
  124. // setS3SecretAccessKey(val);
  125. // }, []);
  126. // // GCS
  127. // const onChangeGcsReferenceFileWithRelayModeHandler = useCallback((val: boolean) => {
  128. // setGcsReferenceFileWithRelayMode(val);
  129. // }, []);
  130. // const onChangeGcsApiKeyJsonPathHandler = useCallback((val: string) => {
  131. // setGcsApiKeyJsonPath(val);
  132. // }, []);
  133. // const onChangeGcsBucketHandler = useCallback((val: string) => {
  134. // setGcsBucket(val);
  135. // }, []);
  136. // const onChangeGcsUploadNamespaceHandler = useCallback((val: string) => {
  137. // setGcsUploadNamespace(val);
  138. // }, []);
  139. useEffect(() => {
  140. setCollectionsAndSelectedCollections();
  141. setupWebsocketEventHandler();
  142. return () => {
  143. cleanUpWebsocketEventHandler();
  144. };
  145. }, [setCollectionsAndSelectedCollections, setupWebsocketEventHandler, cleanUpWebsocketEventHandler]);
  146. return (
  147. <div data-testid="admin-export-archive-data">
  148. <h2 className="border-bottom">{t('admin:g2g_data_transfer.transfer_data_to_another_growi')}</h2>
  149. <button type="button" className="btn btn-outline-secondary mt-4" disabled={isTransferring} onClick={() => setShowExportForm(!isShowExportForm)}>
  150. {t('admin:g2g_data_transfer.advanced_options')}
  151. </button>
  152. {collections.length !== 0 && (
  153. <div className={`${isShowExportForm ? '' : 'd-none'} px-3 pt-3`}>
  154. {/* <h3 className='mb-1'>{t('admin:app_setting.file_upload')}</h3>
  155. <FileUploadSettingMolecule
  156. fileUploadType={fileUploadType}
  157. isFixedFileUploadByEnvVar={false}
  158. onChangeFileUploadType={onChangeFileUploadTypeHandler}
  159. s3ReferenceFileWithRelayMode={s3ReferenceFileWithRelayMode}
  160. s3Region={s3Region}
  161. s3CustomEndpoint={s3CustomEndpoint}
  162. s3Bucket={s3Bucket}
  163. s3AccessKeyId={s3AccessKeyId}
  164. s3SecretAccessKey={s3SecretAccessKey}
  165. onChangeS3ReferenceFileWithRelayMode={onChangeS3ReferenceFileWithRelayModeHandler}
  166. onChangeS3Region={onChangeS3RegionHandler}
  167. onChangeS3CustomEndpoint={onChangeS3CustomEndpointHandler}
  168. onChangeS3Bucket={onChangeS3BucketHandler}
  169. onChangeS3AccessKeyId={onChangeS3AccessKeyIdHandler}
  170. onChangeS3SecretAccessKey={onChangeS3SecretAccessKeyHandler}
  171. gcsReferenceFileWithRelayMode={gcsReferenceFileWithRelayMode}
  172. gcsUseOnlyEnvVars={false}
  173. gcsApiKeyJsonPath={gcsApiKeyJsonPath}
  174. gcsBucket={gcsBucket}
  175. gcsUploadNamespace={gcsUploadNamespace}
  176. onChangeGcsReferenceFileWithRelayMode={onChangeGcsReferenceFileWithRelayModeHandler}
  177. onChangeGcsApiKeyJsonPath={onChangeGcsApiKeyJsonPathHandler}
  178. onChangeGcsBucket={onChangeGcsBucketHandler}
  179. onChangeGcsUploadNamespace={onChangeGcsUploadNamespaceHandler}
  180. /> */}
  181. <h3 className="mb-1">{t('export_management.export_archive_data')}</h3>
  182. <G2GDataTransferExportForm
  183. allCollectionNames={collections}
  184. selectedCollections={selectedCollections}
  185. updateSelectedCollections={updateSelectedCollections}
  186. optionsMap={optionsMap}
  187. updateOptionsMap={updateOptionsMap}
  188. />
  189. </div>
  190. )}
  191. <form onSubmit={startTransfer}>
  192. <div className="form-group row mt-3">
  193. <div className="col-9">
  194. <input
  195. className="form-control"
  196. type="text"
  197. placeholder={t('admin:g2g_data_transfer.paste_transfer_key')}
  198. onChange={onChangeTransferKeyHandler}
  199. required
  200. />
  201. </div>
  202. <div className="col-3">
  203. <button type="submit" className="btn btn-primary w-100">{t('admin:g2g_data_transfer.start_transfer')}</button>
  204. </div>
  205. </div>
  206. </form>
  207. {isTransferring && (
  208. <div className="border rounded p-4">
  209. <div>
  210. <G2GDataTransferStatusIcon className="me-2 mb-2" status={g2gProgress.mongo} /> MongoDB
  211. </div>
  212. <div>
  213. <G2GDataTransferStatusIcon className="me-2" status={g2gProgress.attachments} /> Attachments
  214. </div>
  215. </div>
  216. )}
  217. <h2 className="border-bottom mt-5">{t('commons:g2g_data_transfer.transfer_data_to_this_growi')}</h2>
  218. <div className="form-group row mt-4">
  219. <div className="col-md-3">
  220. <button type="button" className="btn btn-primary w-100" onClick={onClickHandler}>
  221. {t('commons:g2g_data_transfer.publish_transfer_key')}
  222. </button>
  223. </div>
  224. <div className="col-md-9">
  225. <div className="input-group-prepend mx-1">
  226. <input className="form-control" type="text" value={transferKey} readOnly />
  227. <CustomCopyToClipBoard textToBeCopied={transferKey} message="admin:slack_integration.copied_to_clipboard" />
  228. </div>
  229. </div>
  230. </div>
  231. <div className="alert alert-warning mt-4">
  232. <p className="mb-1">{t('commons:g2g_data_transfer.transfer_key_limit')}</p>
  233. <p className="mb-1">{t('commons:g2g_data_transfer.once_transfer_key_used')}</p>
  234. <p className="mb-0">{t('commons:g2g_data_transfer.transfer_to_growi_cloud')}</p>
  235. </div>
  236. </div>
  237. );
  238. };
  239. export default G2GDataTransfer;