BackupModal.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import { useState } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import { Button, Checkbox, Modal } from 'antd';
  4. import { DownloadOutlined, UploadOutlined } from '@ant-design/icons';
  5. import { HttpUtil, PromiseUtil } from '@/utils';
  6. import './BackupModal.css';
  7. interface BusyEvent {
  8. busy: boolean;
  9. tip?: string;
  10. }
  11. interface BackupModalProps {
  12. open: boolean;
  13. basePath: string;
  14. onClose: () => void;
  15. onBusy: (e: BusyEvent) => void;
  16. }
  17. export default function BackupModal({
  18. open,
  19. basePath: _basePath,
  20. onClose,
  21. onBusy,
  22. }: BackupModalProps) {
  23. const { t } = useTranslation();
  24. const isPostgres = window.X_UI_DB_TYPE === 'postgres';
  25. const [keepHostSettings, setKeepHostSettings] = useState(true);
  26. function exportDb() {
  27. window.location.href = (window.X_UI_BASE_PATH || '') + 'panel/api/server/getDb';
  28. }
  29. function exportMigration() {
  30. window.location.href = (window.X_UI_BASE_PATH || '') + 'panel/api/server/getMigration';
  31. }
  32. function importDb() {
  33. const fileInput = document.createElement('input');
  34. fileInput.type = 'file';
  35. fileInput.accept = '.dump,.db';
  36. fileInput.addEventListener('change', async (e) => {
  37. const dbFile = (e.target as HTMLInputElement).files?.[0];
  38. if (!dbFile) return;
  39. const formData = new FormData();
  40. formData.append('db', dbFile);
  41. formData.append('keepHostSettings', String(keepHostSettings));
  42. onClose();
  43. onBusy({ busy: true, tip: `${t('pages.index.importDatabase')}…` });
  44. const upload = await HttpUtil.post('/panel/api/server/importDB', formData, {
  45. headers: { 'Content-Type': 'multipart/form-data' },
  46. });
  47. if (!upload?.success) {
  48. onBusy({ busy: false });
  49. return;
  50. }
  51. // importDB schedules the panel restart server-side; wait it out, then reload.
  52. onBusy({ busy: true, tip: `${t('pages.settings.restartPanel')}…` });
  53. await PromiseUtil.sleep(5000);
  54. window.location.reload();
  55. });
  56. fileInput.click();
  57. }
  58. return (
  59. <Modal open={open} title={t('pages.index.backupTitle')} footer={null} onCancel={onClose}>
  60. {isPostgres && (
  61. <div className="backup-description" style={{ marginBottom: 16 }}>
  62. {t('pages.index.backupPostgresNote')}
  63. </div>
  64. )}
  65. <div className="backup-list">
  66. <div className="backup-item">
  67. <div className="backup-meta">
  68. <div className="backup-title">{t('pages.index.exportDatabase')}</div>
  69. <div className="backup-description">
  70. {isPostgres
  71. ? t('pages.index.exportDatabasePgDesc')
  72. : t('pages.index.exportDatabaseDesc')}
  73. </div>
  74. </div>
  75. <Button
  76. type="primary"
  77. aria-label={t('pages.index.exportDatabase')}
  78. onClick={exportDb}
  79. icon={<DownloadOutlined />}
  80. />
  81. </div>
  82. {isPostgres && (
  83. <div className="backup-item">
  84. <div className="backup-meta">
  85. <div className="backup-title">{t('pages.index.migrationDownload')}</div>
  86. <div className="backup-description">{t('pages.index.migrationDownloadPgDesc')}</div>
  87. </div>
  88. <Button
  89. type="primary"
  90. aria-label={t('pages.index.migrationDownload')}
  91. onClick={exportMigration}
  92. icon={<DownloadOutlined />}
  93. />
  94. </div>
  95. )}
  96. <div className="backup-item">
  97. <div className="backup-meta">
  98. <div className="backup-title">{t('pages.index.importDatabase')}</div>
  99. <div className="backup-description">
  100. {isPostgres
  101. ? t('pages.index.importDatabasePgDesc')
  102. : t('pages.index.importDatabaseDesc')}
  103. </div>
  104. </div>
  105. <Button
  106. type="primary"
  107. aria-label={t('pages.index.importDatabase')}
  108. onClick={importDb}
  109. icon={<UploadOutlined />}
  110. />
  111. </div>
  112. <div className="backup-item">
  113. <div className="backup-meta">
  114. <Checkbox
  115. checked={keepHostSettings}
  116. onChange={(e) => setKeepHostSettings(e.target.checked)}
  117. >
  118. {t('pages.index.importKeepHostSettings')}
  119. </Checkbox>
  120. <div className="backup-description">{t('pages.index.importKeepHostSettingsDesc')}</div>
  121. </div>
  122. </div>
  123. </div>
  124. </Modal>
  125. );
  126. }