mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-10 06:36:06 +00:00
36e75143fa
- Index dashboard regains the 8 cards that were lost in the SPA port (3X-UI panel info, Operation Hours, System Load, Usage, Overall Speed, Total Data, IP Addresses, Connection Stats), plus a Config button that shows the live xray config.json. Version display falls back through panelUpdateInfo → window.__X_UI_CUR_VER__ → '?' so dev mode isn't blank. - Xray config no longer hangs on load: useXraySetting surfaces failures instead of leaving a perpetual spinner, and the Vite dev proxy stops hijacking POST requests to migrated routes (only GETs get bypassed). - Inbound page no longer throws __asyncLoader/emitsOptions errors — inbound.js was missing imports (NumberFormatter, SizeFormatter, Wireguard) and InboundList kept emitting after unmount. - Login round-trip works after logout: a public /csrf-token endpoint bootstraps the SPA before authentication, axios caches the token module-level, and the dev 401 handler navigates to /login.html instead of reloading the dashboard into a redirect loop. - legacy.css mirrors the legacy panel's surface/text variables so dark and ultra-dark themes match main; every SPA entry imports it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
102 lines
2.8 KiB
Vue
102 lines
2.8 KiB
Vue
<script setup>
|
|
import { useI18n } from 'vue-i18n';
|
|
import { DownloadOutlined, UploadOutlined } from '@ant-design/icons-vue';
|
|
import { HttpUtil, PromiseUtil } from '@/utils';
|
|
|
|
const { t } = useI18n();
|
|
|
|
defineProps({
|
|
open: { type: Boolean, default: false },
|
|
basePath: { type: String, default: '' },
|
|
});
|
|
|
|
const emit = defineEmits(['update:open', 'busy']);
|
|
|
|
function close() {
|
|
emit('update:open', false);
|
|
}
|
|
|
|
function exportDb() {
|
|
// The Go endpoint streams x-ui.db as a download. Setting
|
|
// window.location triggers a browser download without leaving
|
|
// the page (the Go side responds with Content-Disposition: attachment).
|
|
window.location = '/panel/api/server/getDb';
|
|
}
|
|
|
|
function importDb() {
|
|
const fileInput = document.createElement('input');
|
|
fileInput.type = 'file';
|
|
fileInput.accept = '.db';
|
|
fileInput.addEventListener('change', async (e) => {
|
|
const dbFile = e.target.files?.[0];
|
|
if (!dbFile) return;
|
|
|
|
const formData = new FormData();
|
|
formData.append('db', dbFile);
|
|
|
|
close();
|
|
emit('busy', { 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) {
|
|
emit('busy', { busy: false });
|
|
return;
|
|
}
|
|
|
|
emit('busy', { 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 {
|
|
emit('busy', { busy: false });
|
|
}
|
|
});
|
|
fileInput.click();
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<a-modal :open="open" :title="t('pages.index.backupTitle')" :closable="true" :footer="null" @cancel="close">
|
|
<a-list bordered class="backup-list">
|
|
<a-list-item class="backup-item">
|
|
<a-list-item-meta>
|
|
<template #title>{{ t('pages.index.exportDatabase') }}</template>
|
|
<template #description>{{ t('pages.index.exportDatabaseDesc') }}</template>
|
|
</a-list-item-meta>
|
|
<a-button type="primary" @click="exportDb">
|
|
<template #icon>
|
|
<DownloadOutlined />
|
|
</template>
|
|
</a-button>
|
|
</a-list-item>
|
|
|
|
<a-list-item class="backup-item">
|
|
<a-list-item-meta>
|
|
<template #title>{{ t('pages.index.importDatabase') }}</template>
|
|
<template #description>{{ t('pages.index.importDatabaseDesc') }}</template>
|
|
</a-list-item-meta>
|
|
<a-button type="primary" @click="importDb">
|
|
<template #icon>
|
|
<UploadOutlined />
|
|
</template>
|
|
</a-button>
|
|
</a-list-item>
|
|
</a-list>
|
|
</a-modal>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.backup-list {
|
|
width: 100%;
|
|
}
|
|
|
|
.backup-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
}
|
|
</style>
|