Files
3x-ui/frontend/src/pages/index/BackupModal.tsx
T
MHSanaei c6f15cd53f refactor(api)!: move /panel/setting and /panel/xray under /panel/api
Settings and Xray config endpoints now live at /panel/api/setting/* and /panel/api/xray/*, registered under the existing /panel/api group so they inherit the same Bearer-or-session auth (checkAPIAuth) as the rest of the API. An API token is a full-admin credential, so this just makes the surface consistent. The SPA page routes /panel/settings and /panel/xray are unchanged.

BREAKING CHANGE: the old /panel/setting/* and /panel/xray/* paths are removed. External callers must switch to the /panel/api/ prefix. Frontend call sites, API docs, the dev proxy, and the route-documentation test are updated to match.
2026-06-06 16:22:41 +02:00

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/api/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>
);
}