feat(sandbox): preserve user config when switching to system default

Update frontend to use new backend API:
- save_config now accepts optional 'activate' parameter
- activate endpoint now requires 'type' parameter ('system' | 'user')

When switching to managed mode, call activate with type='system' instead
of deleting user config, so custom configurations are preserved for
future use.
This commit is contained in:
yyh
2026-01-19 22:27:06 +08:00
parent ff210a98db
commit 8f4a4214a1
4 changed files with 21 additions and 9 deletions

View File

@ -12,6 +12,7 @@ import Modal from '@/app/components/base/modal'
import RadioUI from '@/app/components/base/radio/ui'
import { useToastContext } from '@/app/components/base/toast'
import {
useActivateSandboxProvider,
useDeleteSandboxProviderConfig,
useSaveSandboxProviderConfig,
} from '@/service/use-sandbox-provider'
@ -69,6 +70,7 @@ function ConfigModal({ provider, onClose }: ConfigModalProps) {
const { mutateAsync: saveConfig, isPending: isSaving } = useSaveSandboxProviderConfig()
const { mutateAsync: deleteConfig, isPending: isDeleting } = useDeleteSandboxProviderConfig()
const { mutateAsync: activateProvider, isPending: isActivating } = useActivateSandboxProvider()
// Determine if mode selection should be shown (for providers that support it)
const shouldShowModeSelection = PROVIDERS_WITH_MODE_SELECTION.includes(provider.provider_type)
@ -102,10 +104,10 @@ function ConfigModal({ provider, onClose }: ConfigModalProps) {
}, [provider.config_schema, provider.config, t])
const handleSave = useCallback(async () => {
// For managed mode, delete user config to use system defaults
// For managed mode, activate system config (preserves user config for future use)
if (shouldShowModeSelection && configMode === 'managed') {
try {
await deleteConfig(provider.provider_type)
await activateProvider({ providerType: provider.provider_type, type: 'system' })
notify({ type: 'success', message: t('api.saved', { ns: 'common' }) })
onClose()
}
@ -127,6 +129,7 @@ function ConfigModal({ provider, onClose }: ConfigModalProps) {
await saveConfig({
providerType: provider.provider_type,
config: formValues.values,
activate: true,
})
notify({ type: 'success', message: t('api.saved', { ns: 'common' }) })
onClose()
@ -134,7 +137,7 @@ function ConfigModal({ provider, onClose }: ConfigModalProps) {
catch {
// Error toast is handled by fetch layer
}
}, [shouldShowModeSelection, configMode, saveConfig, deleteConfig, provider.provider_type, notify, t, onClose])
}, [shouldShowModeSelection, configMode, saveConfig, activateProvider, provider.provider_type, notify, t, onClose])
const handleRevoke = useCallback(async () => {
try {
@ -154,7 +157,7 @@ function ConfigModal({ provider, onClose }: ConfigModalProps) {
// Only show revoke button when in BYOK mode, tenant has custom config, and provider is not active
// (active provider cannot be revoked to prevent "no sandbox provider" error)
const showRevokeButton = provider.is_tenant_configured && !provider.is_active && (!shouldShowModeSelection || configMode === 'byok')
const isActionDisabled = isSaving || isDeleting
const isActionDisabled = isSaving || isDeleting || isActivating
const showByokForm = !shouldShowModeSelection || configMode === 'byok'
return (

View File

@ -23,16 +23,20 @@ const SwitchModal = ({
const { mutateAsync: activateProvider, isPending } = useActivateSandboxProvider()
// Determine the type based on provider configuration
// If tenant has custom config, activate as 'user', otherwise as 'system'
const activationType: 'system' | 'user' = provider.is_tenant_configured ? 'user' : 'system'
const handleConfirm = useCallback(async () => {
try {
await activateProvider(provider.provider_type)
await activateProvider({ providerType: provider.provider_type, type: activationType })
notify({ type: 'success', message: t('api.success', { ns: 'common' }) })
onClose()
}
catch {
// Error toast is handled by fetch layer
}
}, [activateProvider, provider.provider_type, notify, t, onClose])
}, [activateProvider, provider.provider_type, activationType, notify, t, onClose])
return (
<Modal

View File

@ -21,6 +21,7 @@ export const saveSandboxProviderConfigContract = base
}
body: {
config: Record<string, string>
activate?: boolean
}
}>())
.output(type<{ result: string }>())
@ -46,5 +47,8 @@ export const activateSandboxProviderContract = base
params: {
providerType: string
}
body: {
type: 'system' | 'user'
}
}>())
.output(type<{ result: string }>())

View File

@ -16,10 +16,10 @@ export const useSaveSandboxProviderConfig = () => {
const queryClient = useQueryClient()
return useMutation({
mutationKey: consoleQuery.sandboxProvider.saveSandboxProviderConfig.mutationKey(),
mutationFn: ({ providerType, config }: { providerType: string, config: Record<string, string> }) => {
mutationFn: ({ providerType, config, activate }: { providerType: string, config: Record<string, string>, activate?: boolean }) => {
return consoleClient.sandboxProvider.saveSandboxProviderConfig({
params: { providerType },
body: { config },
body: { config, activate },
})
},
onSuccess: () => {
@ -47,9 +47,10 @@ export const useActivateSandboxProvider = () => {
const queryClient = useQueryClient()
return useMutation({
mutationKey: consoleQuery.sandboxProvider.activateSandboxProvider.mutationKey(),
mutationFn: (providerType: string) => {
mutationFn: ({ providerType, type }: { providerType: string, type: 'system' | 'user' }) => {
return consoleClient.sandboxProvider.activateSandboxProvider({
params: { providerType },
body: { type },
})
},
onSuccess: () => {