From 66e0a696669e713ea659189a39b0db50d6b104c9 Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 13 Jun 2026 15:25:57 +0800 Subject: [PATCH] feat(frontend): support manual storage migration triggering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Modify storage-config-tab.tsx to separate storage configuration saving from task dispatching. - Save operations now always preserve the currently active storage driver to prevent premature storage engine switching. - Add a manual '开始迁移' button to trigger storage migration explicitly. - Display a warning notice to the operator when the selected storage type differs from the active one. --- .../common/admin/storage-config-tab.tsx | 82 +++++++++++++++---- 1 file changed, 66 insertions(+), 16 deletions(-) diff --git a/frontend/components/common/admin/storage-config-tab.tsx b/frontend/components/common/admin/storage-config-tab.tsx index 417e64e9..4a63755b 100644 --- a/frontend/components/common/admin/storage-config-tab.tsx +++ b/frontend/components/common/admin/storage-config-tab.tsx @@ -134,23 +134,32 @@ export function StorageConfigTab() { React.useEffect(() => { if (query.data?.config) { - setConfig(normalizeConfig(query.data.config)) + setConfig((current) => { + const normalized = normalizeConfig(query.data.config) + if (current) { + return { + ...normalized, + driver: current.driver, + } + } + return normalized + }) } }, [query.data?.config]) const saveMutation = useMutation({ mutationFn: async (value: StorageConfig) => { - if (query.data?.config.driver === value.driver) { - await AdminService.updateSystemConfig(storageConfigKey, { - value: JSON.stringify(value), - }) - return "" + const activeDriver = query.data?.config.driver || value.driver + const valueToSave = { + ...value, + driver: activeDriver, } - const payload: StorageMigrationPayload = {target: value} - return AdminService.dispatchTask({task_type: storageMigrationTaskType, payload: JSON.stringify(payload)}) + await AdminService.updateSystemConfig(storageConfigKey, { + value: JSON.stringify(valueToSave), + }) }, - onSuccess: (taskID) => { - toast.success(taskID ? "配置已保存,存储迁移任务已下发" : "存储配置已保存") + onSuccess: () => { + toast.success("存储配置已保存") void queryClient.invalidateQueries({queryKey: ["admin", "storage-config"]}) }, onError: (error: Error) => { @@ -158,6 +167,24 @@ export function StorageConfigTab() { void queryClient.invalidateQueries({queryKey: ["admin", "storage-config"]}) }, }) + + const migrateMutation = useMutation({ + mutationFn: async (value: StorageConfig) => { + const payload: StorageMigrationPayload = {target: value} + return AdminService.dispatchTask({ + task_type: storageMigrationTaskType, + payload: JSON.stringify(payload), + }) + }, + onSuccess: () => { + toast.success("存储迁移任务已下发") + void queryClient.invalidateQueries({queryKey: ["admin", "storage-config"]}) + }, + onError: (error: Error) => { + toast.error(error.message || "下发存储迁移任务失败") + void queryClient.invalidateQueries({queryKey: ["admin", "storage-config"]}) + }, + }) const runMutation = useMutation({ mutationFn: (executionID: string) => AdminService.retryTaskExecution(executionID), onSuccess: () => { @@ -173,7 +200,10 @@ export function StorageConfigTab() { const migration = query.data?.migration const isReadOnly = migration ? migration.state !== "idle" : false - const isFormDisabled = migration ? (migration.state === "pending" || migration.state === "running") : false + const isFormDisabled = + (migration ? (migration.state === "pending" || migration.state === "running") : false) || + saveMutation.isPending || + migrateMutation.isPending const updateObject = (driver: "s3" | "r2" | "minio" | "oss", patch: Partial) => { setConfig((current) => current ? { @@ -276,11 +306,31 @@ export function StorageConfigTab() { )} - - + + {config.driver !== query.data?.config.driver && ( + + ⚠️ 您已切换存储类型。请先点击“保存配置”保存各存储端的配置凭据,然后点击“开始迁移”手动执行文件迁移。 + + )} +
+ + {config.driver !== query.data?.config.driver && ( + + )} +