feat(dbprovider): added prompt database update process when backup is running (#5905)

* temp

* update

* upt

* upt

* upt
This commit is contained in:
asterRyan
2025-09-01 17:15:42 +08:00
committed by GitHub
parent b2fb6291db
commit f0251c5449
4 changed files with 231 additions and 4 deletions
@@ -279,6 +279,16 @@
"manual_backup_tip": "Tip: Backup during off-peak hours. Avoid DDL operations to prevent locking. Be patient if data size is large. Backup starts 1 min after confirmation.",
"max_replicas": "Max Replicas: ",
"memory": "Memory",
"backups":{
"db_backup_running": "Database backup in progress",
"backup_running": "Running",
"stop_backup": "Stop Backup?",
"stop_backup_tip": "Database backup in progress, stopping backup cannot be recovered, confirm to stop backup?",
"stop_backup_confirm": "Stop Backup",
"stop_update_cancel": "Cancel Update",
"stop_backup_success": "Backup stopped",
"stop_backup_error": "Error stopping backup"
},
"migrate": {
"Incremental migration prompt information": "Enable \\\"Continuous Migration\\\" for incremental DB sync (disabled by default).",
"mongo": {
@@ -278,6 +278,16 @@
"manual_backup_tip": "建议在业务低峰期备份实例。备份期间,请勿执行DDL操作,避免锁表导致备份失败。若数据量较大,花费的时问可能较长,请耐心等待。点击 [开始备份] 后,将在1分钟后开始备份。",
"max_replicas": "实例数最大为: ",
"memory": "内存",
"backups":{
"db_backup_running": "数据库备份中",
"backup_running": "备份中",
"stop_backup": "是否停止备份?",
"stop_backup_tip": "数据库备份中,停止备份后不可恢复,确认停止备份?",
"stop_backup_confirm": "停止备份",
"stop_update_cancel": "取消变更",
"stop_backup_success": "备份已停止",
"stop_backup_error": "处理备份时出错"
},
"migrate": {
"Incremental migration prompt information": "如果需要数据库增量同步,可以开启\"持续迁移\",默认是关闭。",
"mongo": {
@@ -0,0 +1,133 @@
import { useTranslation } from 'next-i18next';
import { useRef } from 'react';
import {
Box,
Flex,
AlertDialog,
AlertDialogBody,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogOverlay,
Alert,
AlertIcon,
Badge,
Button,
CloseButton,
HStack,
Image,
Text,
VStack
} from '@chakra-ui/react';
const StopBackupModal = ({
isOpen,
onClose,
onConfirm,
onCancel,
dbName
}: {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
onCancel: () => void;
dbName: string;
}) => {
const { t } = useTranslation();
const cancelRef = useRef<HTMLButtonElement>(null);
return (
<AlertDialog
isOpen={isOpen}
leastDestructiveRef={cancelRef}
onClose={onClose}
isCentered
size="md"
>
<AlertDialogOverlay>
<AlertDialogContent borderRadius="8px" maxW="480px" mx={4}>
<AlertDialogHeader fontSize="18px" fontWeight="600" pb={4} position="relative">
<Flex justify="space-between" align="center">
<Text>{t('backups.stop_backup')}</Text>
<CloseButton onClick={onClose} size="sm" />
</Flex>
</AlertDialogHeader>
<AlertDialogBody pb={6}>
<VStack spacing={4} align="stretch">
<Alert status="warning" borderRadius="6px" bg={'#FEFCE8'}>
<AlertIcon />
<Text fontSize="14px" color={'#A16207'}>
{t('backups.stop_backup_tip')}
</Text>
</Alert>
<Flex align="center" justify="space-between" p={3} borderRadius="6px" bg="gray.50">
<HStack spacing={3}>
<Box
borderRadius="6px"
overflow="hidden"
bg="white"
display="flex"
alignItems="center"
justifyContent="center"
border="1px solid"
borderColor="gray.200"
w="24px"
h="24px"
>
<Image
src="/logo.svg"
alt="Database"
w="16px"
h="16px"
fallback={<Box w="16px" h="16px" bg="green.500" borderRadius="2px" />}
/>
</Box>
<VStack align="start" spacing={0}>
<Text fontSize="14px" fontWeight="500" color="gray.900">
Database
</Text>
<Text fontSize="12px" color="gray.600">
{t('backups.db_backup_running')}
</Text>
</VStack>
</HStack>
<Badge
colorScheme="green"
variant="subtle"
fontSize="12px"
px={2}
py={1}
borderRadius="4px"
>
{t('backups.backup_running')}
</Badge>
</Flex>
</VStack>
</AlertDialogBody>
<AlertDialogFooter pt={0}>
<HStack spacing={3}>
<Button ref={cancelRef} onClick={onCancel} variant="outline" size="md" px={6}>
{t('backups.stop_update_cancel')}
</Button>
<Button
onClick={onConfirm}
bg={'red.600'}
color="white"
_hover={{ bg: 'red.700' }}
size="md"
px={6}
>
{t('backups.stop_backup_confirm')}
</Button>
</HStack>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
);
};
export default StopBackupModal;
@@ -12,7 +12,7 @@ import type { DBEditType } from '@/types/db';
import { adaptDBForm } from '@/utils/adapt';
import { serviceSideProps } from '@/utils/i18n';
import { json2Account, json2CreateCluster, limitRangeYaml } from '@/utils/json2Yaml';
import { Box, Flex } from '@chakra-ui/react';
import { Box, Flex, useDisclosure } from '@chakra-ui/react';
import { useMessage } from '@sealos/ui';
import { track } from '@sealos/gtm';
import { useQuery } from '@tanstack/react-query';
@@ -30,8 +30,9 @@ import { ResponseCode } from '@/types/response';
import { useGuideStore } from '@/store/guide';
import { getDBSecret } from '@/api/db';
import type { ConnectionInfo } from '../detail/components/AppBaseInfo';
import type { DBType } from '@/types/db';
import type { DBType, BackupItemType } from '@/types/db';
import { getBackups, deleteBackup } from '@/api/backup';
import StopBackupModal from '../detail/components/StopBackupModal';
const ErrorModal = dynamic(() => import('@/components/ErrorModal'));
const defaultEdit = {
@@ -53,6 +54,14 @@ const EditApp = ({ dbName, tabType }: { dbName?: string; tabType?: 'form' | 'yam
const oldDBEditData = useRef<DBEditType>();
const { checkQuotaAllow } = useUserStore();
// Stop backup modal state
const {
isOpen: isStopBackupOpen,
onOpen: onStopBackupOpen,
onClose: onStopBackupClose
} = useDisclosure();
const [pendingFormData, setPendingFormData] = useState<DBEditType | null>(null);
const { title, applyBtnText, applyMessage, applySuccess, applyError } = editModeMap(!!dbName);
const { openConfirm, ConfirmChild } = useConfirm({
content: t(applyMessage)
@@ -229,6 +238,32 @@ const EditApp = ({ dbName, tabType }: { dbName?: string; tabType?: 'form' | 'yam
setIsLoading(false);
};
const handleBackupCheck = async (formData: DBEditType) => {
try {
if (isEdit) {
const result = await getBackups();
const allBackups = result || [];
const backups = allBackups.filter(
(backup: BackupItemType) => backup.dbName === formData.dbName
);
const inProgressBackups = backups.filter(
(backup: BackupItemType) => backup.status.value === 'Running'
);
if (inProgressBackups.length > 0) {
setPendingFormData(formData);
onStopBackupOpen();
return;
}
}
await submitSuccess(formData);
} catch (error) {
console.error(error);
toast({ title: t('backups.stop_backup_error'), status: 'error' });
}
};
const submitError = (err: FieldErrors<DBEditType>) => {
// deep search message
const deepSearch = (obj: any): string => {
@@ -301,7 +336,7 @@ const EditApp = ({ dbName, tabType }: { dbName?: string; tabType?: 'form' | 'yam
applyBtnText={applyBtnText}
applyCb={() =>
formHook.handleSubmit(
(data) => openConfirm(() => submitSuccess(data))(),
(data) => handleBackupCheck(data),
(err) => submitError(err)
)()
}
@@ -330,6 +365,45 @@ const EditApp = ({ dbName, tabType }: { dbName?: string; tabType?: 'form' | 'yam
onClose={() => setErrorMessage('')}
/>
)}
<StopBackupModal
isOpen={isStopBackupOpen}
onClose={onStopBackupClose}
onConfirm={async () => {
if (pendingFormData) {
onStopBackupClose();
setIsLoading(true);
try {
const result = await getBackups();
const allBackups = result || [];
const backups = allBackups.filter(
(backup: BackupItemType) => backup.dbName === pendingFormData.dbName
);
const inProgressBackups = backups.filter(
(backup: BackupItemType) => backup.status.value === 'Running'
);
if (inProgressBackups.length > 0) {
await Promise.all(
inProgressBackups.map(async (backup: BackupItemType) => {
await deleteBackup(backup.name);
})
);
toast({ title: t('backups.stop_backup_success'), status: 'success' });
}
// Proceed with submission after stopping backups
await submitSuccess(pendingFormData);
} catch (error) {
console.error(error);
toast({ title: t('backups.stop_backup_error'), status: 'error' });
} finally {
setIsLoading(false);
}
}
}}
onCancel={onStopBackupClose}
dbName={pendingFormData?.dbName || ''}
/>
</>
);
};