G2GDataTransfer.tsx 11 KB

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