G2GDataTransfer.tsx 11 KB

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