G2GDataTransfer.tsx 11 KB

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