feat(webui): support backup file export and import

This commit is contained in:
CJACK.
2026-03-22 00:29:01 +08:00
parent 01f33c409f
commit 4884773639
5 changed files with 85 additions and 1 deletions

View File

@@ -6,7 +6,9 @@ export default function BackupSection({
setImportMode,
importing,
onLoadExportData,
onDownloadExportFile,
onImport,
onImportFileChange,
importText,
setImportText,
exportData,
@@ -23,6 +25,27 @@ export default function BackupSection({
<Download className="w-4 h-4" />
{t('settings.loadExport')}
</button>
<button
type="button"
onClick={onDownloadExportFile}
className="px-3 py-2 rounded-lg bg-secondary border border-border hover:bg-secondary/80 text-sm flex items-center gap-2"
>
<Download className="w-4 h-4" />
{t('settings.downloadExport')}
</button>
<label className="px-3 py-2 rounded-lg bg-secondary border border-border hover:bg-secondary/80 text-sm flex items-center gap-2 cursor-pointer">
<Upload className="w-4 h-4" />
{t('settings.chooseImportFile')}
<input
type="file"
accept=".json,application/json"
className="hidden"
onChange={(e) => {
onImportFileChange(e.target.files?.[0] || null)
e.target.value = ''
}}
/>
</label>
<select
value={importMode}
onChange={(e) => setImportMode(e.target.value)}

View File

@@ -36,6 +36,8 @@ export default function SettingsContainer({ onRefresh, onMessage, authFetch, onF
saveSettings,
updatePassword,
loadExportData,
downloadExportFile,
loadImportFile,
doImport,
} = useSettingsForm({
apiFetch,
@@ -102,7 +104,9 @@ export default function SettingsContainer({ onRefresh, onMessage, authFetch, onF
setImportMode={setImportMode}
importing={importing}
onLoadExportData={loadExportData}
onDownloadExportFile={downloadExportFile}
onImport={doImport}
onImportFileChange={loadImportFile}
importText={importText}
setImportText={setImportText}
exportData={exportData}

View File

@@ -222,15 +222,60 @@ export function useSettingsForm({ apiFetch, t, onMessage, onRefresh, onForceLogo
const { res, data } = await getExportData(apiFetch)
if (!res.ok) {
onMessage('error', data.detail || t('settings.exportFailed'))
return
return null
}
setExportData(data)
onMessage('success', t('settings.exportLoaded'))
return data
} catch (_e) {
onMessage('error', t('settings.exportFailed'))
return null
}
}, [apiFetch, onMessage, t])
const downloadExportFile = useCallback(async () => {
let latest = exportData
if (!latest?.json) {
const loaded = await loadExportData()
if (!loaded) {
return
}
latest = loaded
}
const jsonText = String(latest?.json || '').trim()
if (!jsonText) {
onMessage('error', t('settings.exportFailed'))
return
}
const blob = new Blob([jsonText], { type: 'application/json;charset=utf-8' })
const url = URL.createObjectURL(blob)
const now = new Date()
const pad = (n) => String(n).padStart(2, '0')
const filename = `ds2api-config-backup-${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}.json`
const link = document.createElement('a')
link.href = url
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
onMessage('success', t('settings.exportDownloaded'))
}, [exportData, loadExportData, onMessage, t])
const loadImportFile = useCallback((file) => {
if (!file) return
const reader = new FileReader()
reader.onload = () => {
const text = String(reader.result || '')
setImportText(text)
onMessage('success', t('settings.importFileLoaded'))
}
reader.onerror = () => {
onMessage('error', t('settings.importFileReadFailed'))
}
reader.readAsText(file, 'utf-8')
}, [onMessage, t])
const doImport = useCallback(async () => {
if (!String(importText || '').trim()) {
onMessage('error', t('settings.importEmpty'))
@@ -290,6 +335,8 @@ export function useSettingsForm({ apiFetch, t, onMessage, onRefresh, onForceLogo
saveSettings,
updatePassword,
loadExportData,
downloadExportFile,
loadImportFile,
doImport,
}
}