mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-11 08:25:04 +03:00
a07c7b7f4e
Binary: extend the migrate-db subcommand with --dump and --restore so a SQLite database can be exported to a portable SQL text dump and rebuilt from one, alongside the existing --dsn PostgreSQL copy. Implemented in Go via the bundled sqlite driver (new database/dump_sqlite.go); no external sqlite3 client is required. Add ExportPostgresToSQLite (reverse of MigrateData) to build a SQLite .db from live PostgreSQL data, reusing the shared copyAllModels helper. Overview: add a "Download Migration" item to Backup & Restore plus a getMigration endpoint/service that returns a .dump on SQLite or a .db on PostgreSQL, so the data can seed a panel on the other backend. Document the endpoint in api-docs and translate the three new strings across all locales. Tests: cover the destination-side copy (AutoMigrate + copyTable into SQLite) and the dump/restore round-trip including quoted values. Ignore *.dump. The x-ui.sh helper that drives this from the CLI is in PR #4910.
112 lines
3.6 KiB
TypeScript
112 lines
3.6 KiB
TypeScript
import { useTranslation } from 'react-i18next';
|
|
import { Button, Modal } from 'antd';
|
|
import { DownloadOutlined, UploadOutlined } from '@ant-design/icons';
|
|
|
|
import { HttpUtil, PromiseUtil } from '@/utils';
|
|
import './BackupModal.css';
|
|
|
|
interface BusyEvent {
|
|
busy: boolean;
|
|
tip?: string;
|
|
}
|
|
|
|
interface BackupModalProps {
|
|
open: boolean;
|
|
basePath: string;
|
|
onClose: () => void;
|
|
onBusy: (e: BusyEvent) => void;
|
|
}
|
|
|
|
export default function BackupModal({ open, basePath: _basePath, onClose, onBusy }: BackupModalProps) {
|
|
const { t } = useTranslation();
|
|
const isPostgres = window.X_UI_DB_TYPE === 'postgres';
|
|
|
|
function exportDb() {
|
|
window.location.href = (window.X_UI_BASE_PATH || '') + 'panel/api/server/getDb';
|
|
}
|
|
|
|
function exportMigration() {
|
|
window.location.href = (window.X_UI_BASE_PATH || '') + 'panel/api/server/getMigration';
|
|
}
|
|
|
|
function importDb() {
|
|
const fileInput = document.createElement('input');
|
|
fileInput.type = 'file';
|
|
fileInput.accept = isPostgres ? '.dump' : '.db';
|
|
fileInput.addEventListener('change', async (e) => {
|
|
const dbFile = (e.target as HTMLInputElement).files?.[0];
|
|
if (!dbFile) return;
|
|
|
|
const formData = new FormData();
|
|
formData.append('db', dbFile);
|
|
|
|
onClose();
|
|
onBusy({ busy: true, tip: `${t('pages.index.importDatabase')}…` });
|
|
|
|
const upload = await HttpUtil.post('/panel/api/server/importDB', formData, {
|
|
headers: { 'Content-Type': 'multipart/form-data' },
|
|
});
|
|
if (!upload?.success) {
|
|
onBusy({ busy: false });
|
|
return;
|
|
}
|
|
|
|
onBusy({ busy: true, tip: `${t('pages.settings.restartPanel')}…` });
|
|
const restart = await HttpUtil.post('/panel/setting/restartPanel');
|
|
if (restart?.success) {
|
|
await PromiseUtil.sleep(5000);
|
|
window.location.reload();
|
|
} else {
|
|
onBusy({ busy: false });
|
|
}
|
|
});
|
|
fileInput.click();
|
|
}
|
|
|
|
return (
|
|
<Modal
|
|
open={open}
|
|
title={t('pages.index.backupTitle')}
|
|
footer={null}
|
|
onCancel={onClose}
|
|
>
|
|
{isPostgres && (
|
|
<div className="backup-description" style={{ marginBottom: 16 }}>
|
|
{t('pages.index.backupPostgresNote')}
|
|
</div>
|
|
)}
|
|
<div className="backup-list">
|
|
<div className="backup-item">
|
|
<div className="backup-meta">
|
|
<div className="backup-title">{t('pages.index.exportDatabase')}</div>
|
|
<div className="backup-description">
|
|
{isPostgres ? t('pages.index.exportDatabasePgDesc') : t('pages.index.exportDatabaseDesc')}
|
|
</div>
|
|
</div>
|
|
<Button type="primary" onClick={exportDb} icon={<DownloadOutlined />} />
|
|
</div>
|
|
|
|
<div className="backup-item">
|
|
<div className="backup-meta">
|
|
<div className="backup-title">{t('pages.index.migrationDownload')}</div>
|
|
<div className="backup-description">
|
|
{isPostgres ? t('pages.index.migrationDownloadPgDesc') : t('pages.index.migrationDownloadDesc')}
|
|
</div>
|
|
</div>
|
|
<Button type="primary" onClick={exportMigration} icon={<DownloadOutlined />} />
|
|
</div>
|
|
|
|
<div className="backup-item">
|
|
<div className="backup-meta">
|
|
<div className="backup-title">{t('pages.index.importDatabase')}</div>
|
|
<div className="backup-description">
|
|
{isPostgres ? t('pages.index.importDatabasePgDesc') : t('pages.index.importDatabaseDesc')}
|
|
</div>
|
|
</div>
|
|
<Button type="primary" onClick={importDb} icon={<UploadOutlined />} />
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
);
|
|
}
|