mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-04 08:25:26 +08:00
Merge pull request #136 from CJackHwang/codex/add-file-import-and-export-for-project-config
feat(webui): add config backup download and file-based import in Settings
This commit is contained in:
@@ -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)}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,8 +240,10 @@
|
||||
"autoDeleteWarning": "Warning: Enabling this will delete all session history after each request. Use with caution.",
|
||||
"backupTitle": "Backup & Restore",
|
||||
"loadExport": "Load current export",
|
||||
"downloadExport": "Download backup file",
|
||||
"importModeMerge": "Merge import (default)",
|
||||
"importModeReplace": "Replace all import",
|
||||
"chooseImportFile": "Choose import file",
|
||||
"importNow": "Import now",
|
||||
"importing": "Importing...",
|
||||
"importPlaceholder": "Paste config JSON to import",
|
||||
@@ -249,8 +251,11 @@
|
||||
"importInvalidJson": "Import JSON is invalid.",
|
||||
"importFailed": "Import failed.",
|
||||
"importSuccess": "Config imported (mode: {mode}).",
|
||||
"importFileLoaded": "Import file content loaded.",
|
||||
"importFileReadFailed": "Failed to read import file.",
|
||||
"exportFailed": "Export failed.",
|
||||
"exportLoaded": "Current export loaded.",
|
||||
"exportDownloaded": "Backup file download started.",
|
||||
"exportJson": "Export JSON",
|
||||
"invalidJsonField": "{field} is not a valid JSON object.",
|
||||
"defaultPasswordWarning": "You are using the default admin password \"admin\". Please change it.",
|
||||
|
||||
@@ -240,8 +240,10 @@
|
||||
"autoDeleteWarning": "开启此功能后,每次请求完成都会删除该账号的所有历史会话,请谨慎使用。",
|
||||
"backupTitle": "备份与恢复",
|
||||
"loadExport": "加载当前导出",
|
||||
"downloadExport": "下载备份文件",
|
||||
"importModeMerge": "合并导入(默认)",
|
||||
"importModeReplace": "全量覆盖导入",
|
||||
"chooseImportFile": "选择导入文件",
|
||||
"importNow": "立即导入",
|
||||
"importing": "导入中...",
|
||||
"importPlaceholder": "粘贴要导入的 JSON 配置",
|
||||
@@ -249,8 +251,11 @@
|
||||
"importInvalidJson": "导入 JSON 格式无效",
|
||||
"importFailed": "导入失败",
|
||||
"importSuccess": "配置导入成功(模式:{mode})",
|
||||
"importFileLoaded": "已读取导入文件内容",
|
||||
"importFileReadFailed": "读取导入文件失败",
|
||||
"exportFailed": "导出失败",
|
||||
"exportLoaded": "已加载当前配置导出",
|
||||
"exportDownloaded": "备份文件下载已开始",
|
||||
"exportJson": "导出 JSON",
|
||||
"invalidJsonField": "{field} 不是有效 JSON 对象",
|
||||
"defaultPasswordWarning": "当前使用默认密码 admin,请尽快在此修改。",
|
||||
|
||||
Reference in New Issue
Block a user