mirror of
https://github.com/labring/sealos.git
synced 2026-10-29 17:25:58 +08:00
feat(dbprovider): added prompt database update process when backup is running (#5905)
* temp * update * upt * upt * upt
This commit is contained in:
@@ -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 || ''}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user