From fd9542c83c19e99471b1327c2a6f9c159a0600c7 Mon Sep 17 00:00:00 2001 From: jingyang <72259332+zjy365@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:22:06 +0800 Subject: [PATCH] feat: support backup selection during database creation (#5252) * fix db backup cron * feat: support backup selection during database creation * update terminationPolicy * update * update * update SelectTimeList * update style * update * support PVC_STORAGE_MAX --- .../providers/applaunchpad/data/config.yaml | 1 + .../src/pages/api/platform/getInitData.ts | 5 +- .../pages/app/edit/components/StoreModal.tsx | 11 +- .../applaunchpad/src/store/static.ts | 2 + .../applaunchpad/src/types/index.d.ts | 1 + .../dbprovider/.vscode/settings.json | 1 - .../dbprovider/public/locales/en/common.json | 15 +- .../dbprovider/public/locales/zh/common.json | 12 +- .../providers/dbprovider/src/api/backup.ts | 14 -- .../components/Icon/icons/backupSettings.svg | 4 + .../dbprovider/src/components/Icon/index.tsx | 1 + .../providers/dbprovider/src/constants/db.ts | 38 ++- .../dbprovider/src/constants/theme.ts | 33 ++- .../dbprovider/src/pages/api/backup/policy.ts | 52 ----- .../src/pages/api/backup/updatePolicy.ts | 147 +++++++----- .../dbprovider/src/pages/api/createDB.ts | 85 ++++++- .../db/detail/components/BackupModal.tsx | 38 +-- .../dbprovider/src/pages/db/detail/index.tsx | 5 +- .../src/pages/db/edit/components/Form.tsx | 219 +++++++++++++++++- .../dbprovider/src/pages/db/edit/index.tsx | 5 +- .../dbprovider/src/pages/db/migrate/index.tsx | 3 +- .../dbprovider/src/types/backup.d.ts | 4 +- .../dbprovider/src/types/cluster.d.ts | 15 +- .../providers/dbprovider/src/types/db.d.ts | 4 + .../dbprovider/src/types/migrate.d.ts | 2 + .../providers/dbprovider/src/utils/adapt.ts | 97 ++++---- .../dbprovider/src/utils/json2Yaml.ts | 11 +- 27 files changed, 593 insertions(+), 232 deletions(-) create mode 100644 frontend/providers/dbprovider/src/components/Icon/icons/backupSettings.svg delete mode 100644 frontend/providers/dbprovider/src/pages/api/backup/policy.ts diff --git a/frontend/providers/applaunchpad/data/config.yaml b/frontend/providers/applaunchpad/data/config.yaml index f7ab8b485c6..4ee76742b03 100644 --- a/frontend/providers/applaunchpad/data/config.yaml +++ b/frontend/providers/applaunchpad/data/config.yaml @@ -9,6 +9,7 @@ common: guideEnabled: false apiEnabled: false launchpad: + pvcStorageMax: 100 eventAnalyze: enabled: false fastGPTKey: "" diff --git a/frontend/providers/applaunchpad/src/pages/api/platform/getInitData.ts b/frontend/providers/applaunchpad/src/pages/api/platform/getInitData.ts index 0b899ff4348..4c81fecda00 100644 --- a/frontend/providers/applaunchpad/src/pages/api/platform/getInitData.ts +++ b/frontend/providers/applaunchpad/src/pages/api/platform/getInitData.ts @@ -17,6 +17,7 @@ export type Response = { fileMangerConfig: FileMangerType; SEALOS_USER_DOMAINS: { name: string; secretName: string }[]; DESKTOP_DOMAIN: string; + PVC_STORAGE_MAX: number; }; export const defaultAppConfig: AppConfigType = { @@ -38,6 +39,7 @@ export const defaultAppConfig: AppConfigType = { }, launchpad: { currencySymbol: Coin.shellCoin, + pvcStorageMax: 20, eventAnalyze: { enabled: false, fastGPTKey: '' @@ -93,7 +95,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) fileMangerConfig: global.AppConfig.launchpad.fileManger, CURRENCY: global.AppConfig.launchpad.currencySymbol || Coin.shellCoin, SEALOS_USER_DOMAINS: global.AppConfig.cloud.userDomains || [], - DESKTOP_DOMAIN: global.AppConfig.cloud.desktopDomain + DESKTOP_DOMAIN: global.AppConfig.cloud.desktopDomain, + PVC_STORAGE_MAX: global.AppConfig.launchpad.pvcStorageMax || 20 } }); } catch (error) { diff --git a/frontend/providers/applaunchpad/src/pages/app/edit/components/StoreModal.tsx b/frontend/providers/applaunchpad/src/pages/app/edit/components/StoreModal.tsx index 9a26406738f..98ac957f8c0 100644 --- a/frontend/providers/applaunchpad/src/pages/app/edit/components/StoreModal.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/edit/components/StoreModal.tsx @@ -22,6 +22,7 @@ import MyFormControl from '@/components/FormControl'; import { useTranslation } from 'next-i18next'; import { pathToNameFormat } from '@/utils/tools'; import { MyTooltip } from '@sealos/ui'; +import { PVC_STORAGE_MAX } from '@/store/static'; export type StoreType = { id?: string; @@ -82,8 +83,8 @@ const StoreModal = ({ {t('capacity')} - - + + diff --git a/frontend/providers/applaunchpad/src/store/static.ts b/frontend/providers/applaunchpad/src/store/static.ts index 10c8bd2a79e..a5019dfb09c 100644 --- a/frontend/providers/applaunchpad/src/store/static.ts +++ b/frontend/providers/applaunchpad/src/store/static.ts @@ -9,6 +9,7 @@ export let SHOW_EVENT_ANALYZE = false; export let CURRENCY = Coin.shellCoin; export let UPLOAD_LIMIT = 50; export let DOWNLOAD_LIMIT = 100; +export let PVC_STORAGE_MAX = 20; export const loadInitData = async () => { try { @@ -21,6 +22,7 @@ export const loadInitData = async () => { UPLOAD_LIMIT = res.fileMangerConfig.uploadLimit; DOWNLOAD_LIMIT = res.fileMangerConfig.downloadLimit; DESKTOP_DOMAIN = res.DESKTOP_DOMAIN; + PVC_STORAGE_MAX = res.PVC_STORAGE_MAX; return { SEALOS_DOMAIN, diff --git a/frontend/providers/applaunchpad/src/types/index.d.ts b/frontend/providers/applaunchpad/src/types/index.d.ts index 2d3df3c88b5..51cbc29e43c 100644 --- a/frontend/providers/applaunchpad/src/types/index.d.ts +++ b/frontend/providers/applaunchpad/src/types/index.d.ts @@ -41,6 +41,7 @@ export type AppConfigType = { }; launchpad: { currencySymbol: Coin; + pvcStorageMax: number; eventAnalyze: { enabled: boolean; fastGPTKey?: string; diff --git a/frontend/providers/dbprovider/.vscode/settings.json b/frontend/providers/dbprovider/.vscode/settings.json index be18ba21857..bc175c84cd6 100644 --- a/frontend/providers/dbprovider/.vscode/settings.json +++ b/frontend/providers/dbprovider/.vscode/settings.json @@ -17,7 +17,6 @@ "i18n-ally.pathMatcher": "{locale}/{namespaces}.json", "i18n-ally.extract.targetPickingStrategy": "most-similar-by-key", "i18n-ally.translate.engines": [ - "deepl", "google", ] } \ No newline at end of file diff --git a/frontend/providers/dbprovider/public/locales/en/common.json b/frontend/providers/dbprovider/public/locales/en/common.json index 93c6f82c577..373942dc3d8 100644 --- a/frontend/providers/dbprovider/public/locales/en/common.json +++ b/frontend/providers/dbprovider/public/locales/en/common.json @@ -40,7 +40,7 @@ "Running": "Running", "Saturday": "Sat", "Save": "Save", - "SaveTime": "Retention Period", + "SaveTime": "Retention", "Start": "Start", "Starting": "Starting", "Success": "succeeded", @@ -80,6 +80,7 @@ "backup_name_cannot_empty": "Must provide backup name", "backup_processing": "Saving Backup", "backup_running": "Saving Backup", + "backup_settings": "Backup", "backup_success_tip": "The backup task has been successfully created", "backup_time": "Backup Timestamp", "balance": "balance", @@ -117,7 +118,7 @@ "copy_success": "Copy succeeded", "covering_risks": "Coverage Risks", "cpu": "CPU", - "create_db": "Create New Database", + "create_db": "Create Database", "creation_time": "Creation Time", "current_connections": "Current Connections", "data_migration_config": "Data Migration Settings", @@ -129,6 +130,7 @@ "database_name": "Database Name", "database_name_cannot_empty": "Database name cannot be empty", "database_name_empty": "Application name is required", + "database_name_max_length": "Database name length cannot exceed {{length}} characters", "database_name_regex": "Start with a letter, only allow lowercase letters, numbers and -", "database_name_regex_error": "Database name can only contain lowercase letters, numbers, -, and must start with a letter.", "database_password_empty": "Please enter the database password", @@ -201,6 +203,7 @@ "guide_terminal_button": "Convenient terminal connection method improves data processing efficiency", "have_error": "Failed", "hits_ratio": "Hits Ratio", + "hour": "hour", "import_through_file": "Import via File", "important_tips_for_migrating": "Tip: Create a new database in sink DB if source_database and sink_database have overlapping data, to avoid conflicts", "innodb_buffer_pool": "InnoDB Buffer Pool", @@ -289,6 +292,7 @@ "start_hour": "Hour", "start_minute": "Minute", "start_success": "Database started. Please wait...", + "start_time": "Start Time", "status": "Status", "storage": "Storage", "storage_cannot_empty": "Please specify storage size", @@ -300,6 +304,8 @@ "successfully_closed_external_network_access": "Internet access disabled", "table_locks": "Table-level locks", "terminated_logs": "Terminated logs", + "termination_policy": "Delete Policy", + "termination_policy_tip": "Whether to keep a backup when deleting the database", "total_price": "total_price", "total_price_tip": "The estimated cost does not include port fees and traffic fees, and is subject to actual usage.", "turn_on": "Enable", @@ -317,5 +323,8 @@ "within_5_minutes": "Within 5 minutes", "yaml_file": "YAML", "you_have_successfully_deployed_database": "You have successfully deployed and created a database!", - "database_name_max_length": "Database name length cannot exceed {{length}} characters" + "delete_backup_with_db": "Keep Backups", + "delete_backup_with_db_tip": "Delete the databases but leave the backups as they are", + "wipeout_backup_with_db": "Discard Backups", + "wipeout_backup_with_db_tip": "Delete the databases and the backups" } \ No newline at end of file diff --git a/frontend/providers/dbprovider/public/locales/zh/common.json b/frontend/providers/dbprovider/public/locales/zh/common.json index 8c5fe2582b8..7b6056a88cc 100644 --- a/frontend/providers/dbprovider/public/locales/zh/common.json +++ b/frontend/providers/dbprovider/public/locales/zh/common.json @@ -80,6 +80,7 @@ "backup_name_cannot_empty": "备份名称不能为空", "backup_processing": "备份中", "backup_running": "备份中", + "backup_settings": "备份设置", "backup_success_tip": "备份任务已经成功创建", "backup_time": "备份时间", "balance": "余额", @@ -129,6 +130,7 @@ "database_name": "新数据库名", "database_name_cannot_empty": "数据库名称不能为空", "database_name_empty": "应用名称不能为空", + "database_name_max_length": "数据库名长度不能超过 {{length}} 个字符", "database_name_regex": "字母开头,仅能包含小写字母、数字和 -", "database_name_regex_error": "数据库名只能包含小写字母、数字和 -,并且字母开头。", "database_password_empty": "缺少数据库密码", @@ -201,6 +203,7 @@ "guide_terminal_button": "便捷的终端连接方式,提升数据处理效率", "have_error": "出现异常", "hits_ratio": "命中率", + "hour": "时", "import_through_file": "文件导入", "important_tips_for_migrating": "如果 source 数据库中 source_database 和 sink 数据库中 sink_database 的数据库有重叠,应该在sink 数据库中新建 database,以免出现数据重叠", "innodb_buffer_pool": "InnoDB 缓冲池", @@ -246,6 +249,7 @@ "migration_successful": "迁移成功", "migration_task_created_successfully": "迁移任务创建成功", "min_replicas": "实例数最小为: ", + "minute": "分", "monitor_list": "实时监控", "multi_replica_redis_tip": "Redis 多副本包含 HA 节点,请悉知,预估价格已包含 HA 节点费用", "name": "名字", @@ -289,6 +293,7 @@ "start_hour": "小时", "start_minute": "分钟", "start_success": "数据库启动成功,请等待", + "start_time": "开始时间", "status": "状态", "storage": "磁盘", "storage_cannot_empty": "容量不能为空", @@ -300,6 +305,8 @@ "successfully_closed_external_network_access": "已关闭外网访问", "table_locks": "表锁", "terminated_logs": "中断前", + "termination_policy": "删除策略", + "termination_policy_tip": "删除数据库时是否保留备份", "total_price": "总价", "total_price_tip": "预估费用不包括端口费用和流量费用,以实际使用为准", "turn_on": "开启", @@ -317,5 +324,8 @@ "within_5_minutes": "五分钟内", "yaml_file": "YAML 文件", "you_have_successfully_deployed_database": "您已成功部署创建一个数据库!", - "database_name_max_length": "数据库名长度不能超过 {{length}} 个字符" + "delete_backup_with_db": "保留备份", + "delete_backup_with_db_tip": "在删除数据库时,保留其备份", + "wipeout_backup_with_db": "随数据库删除", + "wipeout_backup_with_db_tip": "在删除数据库时,删除其备份" } \ No newline at end of file diff --git a/frontend/providers/dbprovider/src/api/backup.ts b/frontend/providers/dbprovider/src/api/backup.ts index 02c0baa180e..70b8f14d151 100644 --- a/frontend/providers/dbprovider/src/api/backup.ts +++ b/frontend/providers/dbprovider/src/api/backup.ts @@ -4,20 +4,6 @@ import { adaptBackup, adaptBackupByCluster, adaptDBDetail } from '@/utils/adapt' import { AutoBackupFormType } from '@/types/backup'; import type { Props as UpdatePolicyProps } from '@/pages/api/backup/updatePolicy'; -/** - * Deprecated API: This endpoint is no longer supported. - * - * The new method for obtaining backup policies is by querying the 'cluster spec backup' endpoint - * for the specific database in the cluster. - * - * To update the auto-backup policy, use the PATCH operation on the 'cluster spec backup' resource. - * @deprecated - * @param data - Object containing information about the database, including dbName and dbType. - * @returns {Promise} - A promise resolving to the auto-backup configuration form. - */ -export const getBackupPolicy = (data: { dbName: string; dbType: string }) => - GET(`/api/backup/policy`, data); - export const createBackup = (data: CreateBackupPros) => POST('/api/backup/create', data); export const getBackupList = (dbName: string) => diff --git a/frontend/providers/dbprovider/src/components/Icon/icons/backupSettings.svg b/frontend/providers/dbprovider/src/components/Icon/icons/backupSettings.svg new file mode 100644 index 00000000000..1c399a73cc4 --- /dev/null +++ b/frontend/providers/dbprovider/src/components/Icon/icons/backupSettings.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/providers/dbprovider/src/components/Icon/index.tsx b/frontend/providers/dbprovider/src/components/Icon/index.tsx index 3e03b06897d..b4b35735a65 100644 --- a/frontend/providers/dbprovider/src/components/Icon/index.tsx +++ b/frontend/providers/dbprovider/src/components/Icon/index.tsx @@ -53,6 +53,7 @@ const map = { import: require('./icons/import.svg').default, file: require('./icons/file.svg').default, config: require('./icons/config.svg').default, + backupSettings: require('./icons/backupSettings.svg').default, monitor: require('./icons/monitor.svg').default, arrowDown: require('./icons/arrowDown.svg').default, docs: require('./icons/docs.svg').default diff --git a/frontend/providers/dbprovider/src/constants/db.ts b/frontend/providers/dbprovider/src/constants/db.ts index 2806376806b..aa1a527c74b 100644 --- a/frontend/providers/dbprovider/src/constants/db.ts +++ b/frontend/providers/dbprovider/src/constants/db.ts @@ -7,6 +7,7 @@ import { DBSourceType } from '@/types/db'; import { CpuSlideMarkList, MemorySlideMarkList } from './editApp'; +import { I18nCommonKey } from '@/types/i18next'; export const crLabelKey = 'sealos-db-provider-cr'; export const CloudMigraionLabel = 'sealos-db-provider-cr-migrate'; @@ -270,7 +271,17 @@ export const defaultDBEditValue: DBEditType = { cpu: CpuSlideMarkList[1].value, memory: MemorySlideMarkList[1].value, storage: 3, - labels: {} + labels: {}, + autoBackup: { + start: true, + type: 'day', + week: [], + hour: '23', + minute: '00', + saveTime: 7, + saveType: 'd' + }, + terminationPolicy: 'Delete' }; export const defaultDBDetail: DBDetailType = { @@ -428,3 +439,28 @@ export const DBSourceConfigs: Array<{ { key: templateDeployKey, type: 'app_store' }, { key: sealafDeployKey, type: 'sealaf' } ]; + +export const SelectTimeList = new Array(60).fill(0).map((item, i) => { + const val = i < 10 ? `0${i}` : `${i}`; + return { + id: val, + label: val + }; +}); + +export const WeekSelectList: { label: I18nCommonKey; id: string }[] = [ + { label: 'Monday', id: '1' }, + { label: 'Tuesday', id: '2' }, + { label: 'Wednesday', id: '3' }, + { label: 'Thursday', id: '4' }, + { label: 'Friday', id: '5' }, + { label: 'Saturday', id: '6' }, + { label: 'Sunday', id: '0' } +]; + +export const BackupSupportedDBTypeList: DBType[] = [ + 'postgresql', + 'mongodb', + 'apecloud-mysql', + 'redis' +]; diff --git a/frontend/providers/dbprovider/src/constants/theme.ts b/frontend/providers/dbprovider/src/constants/theme.ts index 71bb2020bcf..b8638768891 100644 --- a/frontend/providers/dbprovider/src/constants/theme.ts +++ b/frontend/providers/dbprovider/src/constants/theme.ts @@ -1,5 +1,35 @@ import { extendTheme } from '@chakra-ui/react'; import { theme as SealosTheme } from '@sealos/ui'; +import { checkboxAnatomy } from '@chakra-ui/anatomy'; +import { createMultiStyleConfigHelpers } from '@chakra-ui/react'; + +const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers( + checkboxAnatomy.keys +); + +const checkbox = defineMultiStyleConfig({ + baseStyle: { + control: { + borderWidth: '1px', + borderRadius: '4px', + _checked: { + bg: '#F0FBFF', + borderColor: '#219BF4', + boxShadow: ' 0px 0px 0px 2.4px rgba(33, 155, 244, 0.15)', + _hover: { + bg: '#F0FBFF', + borderColor: '#219BF4' + } + }, + _hover: { + bg: 'transparent' + } + }, + icon: { + color: '#219BF4' + } + } +}); export const theme = extendTheme(SealosTheme, { styles: { @@ -13,5 +43,6 @@ export const theme = extendTheme(SealosTheme, { minWidth: '700px' } } - } + }, + components: { Checkbox: checkbox } }); diff --git a/frontend/providers/dbprovider/src/pages/api/backup/policy.ts b/frontend/providers/dbprovider/src/pages/api/backup/policy.ts deleted file mode 100644 index e87b1b4c5ff..00000000000 --- a/frontend/providers/dbprovider/src/pages/api/backup/policy.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { ApiResp } from '@/services/kubernet'; -import { authSession } from '@/services/backend/auth'; -import { getK8s } from '@/services/backend/kubernetes'; -import { jsonRes } from '@/services/backend/response'; -import { adaptPolicy } from '@/utils/adapt'; -import { DBBackupPolicyNameMap, DBTypeEnum } from '@/constants/db'; - -export type Props = { - dbName: string; - dbType: `${DBTypeEnum}`; -}; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const { dbName, dbType } = req.query as Props; - - if (!dbName || !dbType) { - jsonRes(res, { - code: 500, - error: 'params error' - }); - return; - } - - const group = 'dataprotection.kubeblocks.io'; - const version = 'v1alpha1'; - const plural = 'backuppolicies'; - - try { - const { k8sCustomObjects, namespace } = await getK8s({ - kubeconfig: await authSession(req) - }); - - // get backup backupolicies.dataprotection.kubeblocks.io - const { body } = (await k8sCustomObjects.getNamespacedCustomObject( - group, - version, - namespace, - plural, - `${dbName}-${DBBackupPolicyNameMap[dbType]}-backup-policy` - )) as { body: any }; - - jsonRes(res, { - data: adaptPolicy(body) - }); - } catch (err: any) { - jsonRes(res, { - code: 500, - error: err - }); - } -} diff --git a/frontend/providers/dbprovider/src/pages/api/backup/updatePolicy.ts b/frontend/providers/dbprovider/src/pages/api/backup/updatePolicy.ts index 739845706af..33087a3da7f 100644 --- a/frontend/providers/dbprovider/src/pages/api/backup/updatePolicy.ts +++ b/frontend/providers/dbprovider/src/pages/api/backup/updatePolicy.ts @@ -1,10 +1,11 @@ import { BACKUP_REPO_DEFAULT_KEY } from '@/constants/backup'; import { DBTypeEnum } from '@/constants/db'; import { authSession } from '@/services/backend/auth'; -import { K8sApi, K8sApiDefault, getK8s } from '@/services/backend/kubernetes'; +import { K8sApiDefault, getK8s } from '@/services/backend/kubernetes'; import { jsonRes } from '@/services/backend/response'; import { ApiResp } from '@/services/kubernet'; import { BackupRepoCRItemType } from '@/types/backup'; +import { KbPgClusterType } from '@/types/cluster'; import * as k8s from '@kubernetes/client-node'; import { PatchUtils } from '@kubernetes/client-node'; import type { NextApiRequest, NextApiResponse } from 'next'; @@ -12,13 +13,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; export type Props = { dbName: string; dbType: `${DBTypeEnum}`; - autoBackup?: { - enabled: boolean; - cronExpression: string; - method: string; - retentionPeriod: string; - repoName: string; - }; + autoBackup?: KbPgClusterType['spec']['backup']; }; export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -31,66 +26,18 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< }); } - const group = 'apps.kubeblocks.io'; - const version = 'v1alpha1'; - const plural = 'clusters'; - try { const { k8sCustomObjects, namespace } = await getK8s({ kubeconfig: await authSession(req) }); - // Get cluster backup repository - const kc = K8sApiDefault(); - const backupRepos = (await kc - .makeApiClient(k8s.CustomObjectsApi) - .listClusterCustomObject('dataprotection.kubeblocks.io', 'v1alpha1', 'backuprepos')) as { - body: { - items: BackupRepoCRItemType[]; - }; - }; - - const defaultRepoItem = backupRepos?.body?.items?.find( - (item) => item.metadata.annotations[BACKUP_REPO_DEFAULT_KEY] === 'true' - ); - - const backupRepoName = defaultRepoItem?.metadata?.name; - - if (!backupRepoName) { - throw new Error('Missing backup repository'); - } - const patch = autoBackup - ? [ - { - op: 'replace', - path: '/spec/backup', - value: { - ...autoBackup, - repoName: backupRepoName - } - } - ] - : [ - { - op: 'replace', - path: '/spec/backup/enabled', - value: false - } - ]; - - // get backup backupolicies.dataprotection.kubeblocks.io - const result = await k8sCustomObjects.patchNamespacedCustomObject( - group, - version, - namespace, - plural, + const result = await updateBackupPolicyApi({ dbName, - patch, - undefined, - undefined, - undefined, - { headers: { 'Content-type': PatchUtils.PATCH_FORMAT_JSON_PATCH } } - ); + dbType, + autoBackup, + k8sCustomObjects, + namespace + }); jsonRes(res, { data: result?.body }); } catch (err: any) { @@ -100,3 +47,79 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< }); } } + +export type UpdateBackupPolicyParams = { + dbName: string; + dbType: `${DBTypeEnum}`; + autoBackup?: KbPgClusterType['spec']['backup']; + k8sCustomObjects: k8s.CustomObjectsApi; + namespace: string; +}; + +export async function updateBackupPolicyApi({ + dbName, + dbType, + autoBackup, + k8sCustomObjects, + namespace +}: UpdateBackupPolicyParams) { + const group = 'apps.kubeblocks.io'; + const version = 'v1alpha1'; + const plural = 'clusters'; + + // Get cluster backup repository + const kc = K8sApiDefault(); + const backupRepos = (await kc + .makeApiClient(k8s.CustomObjectsApi) + .listClusterCustomObject('dataprotection.kubeblocks.io', 'v1alpha1', 'backuprepos')) as { + body: { + items: BackupRepoCRItemType[]; + }; + }; + + const defaultRepoItem = backupRepos?.body?.items?.find( + (item) => item.metadata.annotations[BACKUP_REPO_DEFAULT_KEY] === 'true' + ); + + const backupRepoName = defaultRepoItem?.metadata?.name; + + if (!backupRepoName) { + throw new Error('Missing backup repository'); + } + + const patch = autoBackup + ? [ + { + op: 'replace', + path: '/spec/backup', + value: { + ...autoBackup, + repoName: backupRepoName + } + } + ] + : [ + { + op: 'replace', + path: '/spec/backup/enabled', + value: false + } + ]; + + console.log('backup patch', patch); + + const result = await k8sCustomObjects.patchNamespacedCustomObject( + group, + version, + namespace, + plural, + dbName, + patch, + undefined, + undefined, + undefined, + { headers: { 'Content-type': PatchUtils.PATCH_FORMAT_JSON_PATCH } } + ); + + return result; +} diff --git a/frontend/providers/dbprovider/src/pages/api/createDB.ts b/frontend/providers/dbprovider/src/pages/api/createDB.ts index 76126e612c9..7b94e2832ac 100644 --- a/frontend/providers/dbprovider/src/pages/api/createDB.ts +++ b/frontend/providers/dbprovider/src/pages/api/createDB.ts @@ -6,6 +6,10 @@ import { KbPgClusterType } from '@/types/cluster'; import { BackupItemType, DBEditType } from '@/types/db'; import { json2Account, json2ClusterOps, json2CreateCluster } from '@/utils/json2Yaml'; import type { NextApiRequest, NextApiResponse } from 'next'; +import { updateBackupPolicyApi } from './backup/updatePolicy'; +import { BackupSupportedDBTypeList } from '@/constants/db'; +import { convertBackupFormToSpec } from '@/utils/adapt'; +import { CustomObjectsApi, PatchUtils } from '@kubernetes/client-node'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -39,7 +43,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< 'Gi', '' ) - ) + ), + terminationPolicy: body.spec.terminationPolicy }; const opsRequests = []; @@ -77,6 +82,30 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< }); } + if (BackupSupportedDBTypeList.includes(dbForm.dbType) && dbForm?.autoBackup) { + const autoBackup = convertBackupFormToSpec({ + autoBackup: dbForm?.autoBackup, + dbType: dbForm.dbType + }); + + await updateBackupPolicyApi({ + dbName: dbForm.dbName, + dbType: dbForm.dbType, + autoBackup, + k8sCustomObjects, + namespace + }); + + if (currentConfig.terminationPolicy !== dbForm.terminationPolicy) { + await updateTerminationPolicyApi({ + dbName: dbForm.dbName, + terminationPolicy: dbForm.terminationPolicy, + k8sCustomObjects, + namespace + }); + } + } + return jsonRes(res, { data: 'success update db' }); @@ -101,6 +130,21 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< await applyYamlList([updateAccountYaml], 'replace'); + if (BackupSupportedDBTypeList.includes(dbForm.dbType) && dbForm?.autoBackup) { + const autoBackup = convertBackupFormToSpec({ + autoBackup: dbForm?.autoBackup, + dbType: dbForm.dbType + }); + + await updateBackupPolicyApi({ + dbName: dbForm.dbName, + dbType: dbForm.dbType, + autoBackup, + k8sCustomObjects, + namespace + }); + } + jsonRes(res, { data: 'success create db' }); @@ -111,3 +155,42 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< }); } } + +export async function updateTerminationPolicyApi({ + dbName, + terminationPolicy, + k8sCustomObjects, + namespace +}: { + dbName: string; + terminationPolicy: string; + k8sCustomObjects: CustomObjectsApi; + namespace: string; +}) { + const group = 'apps.kubeblocks.io'; + const version = 'v1alpha1'; + const plural = 'clusters'; + + const patch = [ + { + op: 'replace', + path: '/spec/terminationPolicy', + value: terminationPolicy + } + ]; + + const result = await k8sCustomObjects.patchNamespacedCustomObject( + group, + version, + namespace, + plural, + dbName, + patch, + undefined, + undefined, + undefined, + { headers: { 'Content-type': PatchUtils.PATCH_FORMAT_JSON_PATCH } } + ); + + return result; +} diff --git a/frontend/providers/dbprovider/src/pages/db/detail/components/BackupModal.tsx b/frontend/providers/dbprovider/src/pages/db/detail/components/BackupModal.tsx index c4275efaab1..1174a2df80a 100644 --- a/frontend/providers/dbprovider/src/pages/db/detail/components/BackupModal.tsx +++ b/frontend/providers/dbprovider/src/pages/db/detail/components/BackupModal.tsx @@ -1,6 +1,6 @@ import { createBackup, updateBackupPolicy } from '@/api/backup'; import Tip from '@/components/Tip'; -import { DBBackupMethodNameMap, DBTypeEnum } from '@/constants/db'; +import { DBBackupMethodNameMap, DBTypeEnum, SelectTimeList, WeekSelectList } from '@/constants/db'; import { useConfirm } from '@/hooks/useConfirm'; import type { AutoBackupFormType, AutoBackupType } from '@/types/backup'; import { I18nCommonKey } from '@/types/i18next'; @@ -83,31 +83,6 @@ const BackupModal = ({ defaultValues: defaultVal }); - const selectTimeList = useRef< - { - id: string; - label: string; - }[] - >( - (() => - new Array(60).fill(0).map((item, i) => { - const val = i < 10 ? `0${i}` : `${i}`; - return { - id: val, - label: val - }; - }))() - ); - const weekSelectList: MutableRefObject<{ label: I18nCommonKey; id: string }[]> = useRef([ - { label: 'Monday', id: '1' }, - { label: 'Tuesday', id: '2' }, - { label: 'Wednesday', id: '3' }, - { label: 'Thursday', id: '4' }, - { label: 'Friday', id: '5' }, - { label: 'Saturday', id: '6' }, - { label: 'Sunday', id: '0' } - ]); - const navStyle = useCallback( (nav: `${NavEnum}`) => ({ p: '8px', @@ -331,7 +306,7 @@ const BackupModal = ({ {getAutoValues('type') === 'week' && ( - {weekSelectList.current.map((item) => ( + {WeekSelectList.map((item) => ( ({ value: i.id, label: i.label }))} + list={SelectTimeList.slice(0, 24).map((i) => ({ + value: i.id, + label: i.label + }))} // icon={} onchange={(val: any) => { setAutoValue('hour', val); @@ -376,7 +352,7 @@ const BackupModal = ({ ({ + list={SelectTimeList.map((i) => ({ value: i.id, label: i.label }))} diff --git a/frontend/providers/dbprovider/src/pages/db/detail/index.tsx b/frontend/providers/dbprovider/src/pages/db/detail/index.tsx index ff545e2f854..c882761518c 100644 --- a/frontend/providers/dbprovider/src/pages/db/detail/index.tsx +++ b/frontend/providers/dbprovider/src/pages/db/detail/index.tsx @@ -22,6 +22,7 @@ import ReconfigureTable from './components/Reconfigure/index'; import useDetailDriver from '@/hooks/useDetailDriver'; import ErrorLog from '@/pages/db/detail/components/ErrorLog'; import MyIcon from '@/components/Icon'; +import { BackupSupportedDBTypeList } from '@/constants/db'; enum TabEnum { pod = 'pod', @@ -52,9 +53,7 @@ const AppDetail = ({ const { listNav } = useMemo(() => { const PublicNetMigration = ['postgresql', 'apecloud-mysql', 'mongodb'].includes(dbType); const MigrateSupported = ['postgresql', 'mongodb', 'apecloud-mysql'].includes(dbType); - const BackupSupported = - ['postgresql', 'mongodb', 'apecloud-mysql', 'redis'].includes(dbType) && - SystemEnv.BACKUP_ENABLED; + const BackupSupported = BackupSupportedDBTypeList.includes(dbType) && SystemEnv.BACKUP_ENABLED; const listNavValue = [ { diff --git a/frontend/providers/dbprovider/src/pages/db/edit/components/Form.tsx b/frontend/providers/dbprovider/src/pages/db/edit/components/Form.tsx index 83baeeb9cd9..b2bc5d6e8ff 100644 --- a/frontend/providers/dbprovider/src/pages/db/edit/components/Form.tsx +++ b/frontend/providers/dbprovider/src/pages/db/edit/components/Form.tsx @@ -3,11 +3,19 @@ import MyIcon from '@/components/Icon'; import PriceBox from '@/components/PriceBox'; import QuotaBox from '@/components/QuotaBox'; import Tip from '@/components/Tip'; -import { DBTypeEnum, DBTypeList, RedisHAConfig } from '@/constants/db'; +import { + BackupSupportedDBTypeList, + DBTypeEnum, + DBTypeList, + RedisHAConfig, + SelectTimeList, + WeekSelectList +} from '@/constants/db'; import { CpuSlideMarkList, MemorySlideMarkList } from '@/constants/editApp'; import useEnvStore from '@/store/env'; import { DBVersionMap, INSTALL_ACCOUNT } from '@/store/static'; import type { QueryType } from '@/types'; +import { AutoBackupType } from '@/types/backup'; import type { DBEditType } from '@/types/db'; import { I18nCommonKey } from '@/types/i18next'; import { InfoOutlineIcon } from '@chakra-ui/icons'; @@ -15,6 +23,8 @@ import { Box, Button, Center, + Checkbox, + Collapse, Flex, FormControl, Grid, @@ -25,6 +35,7 @@ import { NumberInput, NumberInputField, NumberInputStepper, + Switch, Text, useDisclosure, useTheme @@ -33,7 +44,7 @@ import { MySelect, MySlider, MyTooltip, RangeInput, Tabs } from '@sealos/ui'; import { throttle } from 'lodash'; import { useTranslation } from 'next-i18next'; import { useRouter } from 'next/router'; -import { useEffect, useMemo, useState } from 'react'; +import { MutableRefObject, useEffect, useMemo, useRef, useState } from 'react'; import { UseFormReturn } from 'react-hook-form'; const Form = ({ @@ -64,6 +75,11 @@ const Form = ({ id: 'baseInfo', label: 'basic', icon: 'formInfo' + }, + { + id: 'backupSettings', + label: 'backup_settings', + icon: 'backupSettings' } ]; @@ -194,7 +210,7 @@ const Form = ({ name={item.icon as any} w={'20px'} h={'20px'} - color={activeNav === item.id ? 'grayModern.600' : 'myGray.400'} + color={activeNav === item.id ? 'grayModern.900' : 'grayModern.500'} /> {t(item.label)} @@ -504,6 +520,203 @@ const Form = ({ + {BackupSupportedDBTypeList.includes(getValues('dbType')) && ( + + + + {t('backup_settings')} + { + setValue('autoBackup.start', e.target.checked); + }} + /> + + + + + {t('CronExpression')} + { + setValue('autoBackup.type', e as AutoBackupType); + }} + /> + + {getValues('autoBackup.type') === 'week' && ( + + + {WeekSelectList.map((item) => ( + + { + const val = e.target.checked; + const checkedList = [...getValues('autoBackup.week')]; + const index = checkedList.findIndex((week) => week === item.id); + if (val && index === -1) { + setValue('autoBackup.week', checkedList.concat(item.id)); + } else if (!val && index > -1) { + checkedList.splice(index, 1); + setValue('autoBackup.week', checkedList); + } + }} + > + {t(item.label)} + + + ))} + + )} + + {t('start_time')} + {getValues('autoBackup.type') !== 'hour' && ( + + ({ + value: i.id, + label: i.label + }))} + onchange={(val: any) => { + setValue('autoBackup.hour', val); + }} + /> + + {t('hour')} + + + )} + + + ({ + value: i.id, + label: i.label + }))} + onchange={(val: any) => { + setValue('autoBackup.minute', val); + }} + /> + + {t('minute')} + + + + + + {t('SaveTime')} + + { + setValue('autoBackup.saveType', val); + }} + /> + + + {t('termination_policy')} + {/* { + setValue('terminationPolicy', e.target.checked ? 'Delete' : 'WipeOut'); + }} + /> */} + + {['Delete', 'WipeOut'].map((item) => { + const isChecked = getValues('terminationPolicy') === item; + + return ( + { + setValue( + 'terminationPolicy', + getValues('terminationPolicy') === 'Delete' ? 'WipeOut' : 'Delete' + ); + }} + cursor={'pointer'} + > +
+ {isChecked && ( + + )} +
+ + + {t(`${item.toLowerCase()}_backup_with_db` as I18nCommonKey)} + + + {t(`${item.toLowerCase()}_backup_with_db_tip` as I18nCommonKey)} + + +
+ ); + })} +
+
+
+
+
+ )} diff --git a/frontend/providers/dbprovider/src/pages/db/edit/index.tsx b/frontend/providers/dbprovider/src/pages/db/edit/index.tsx index f8d72d7324e..aa9e182131f 100644 --- a/frontend/providers/dbprovider/src/pages/db/edit/index.tsx +++ b/frontend/providers/dbprovider/src/pages/db/edit/index.tsx @@ -1,5 +1,5 @@ import { adapterMongoHaConfig, applyYamlList, createDB } from '@/api/db'; -import { defaultDBEditValue } from '@/constants/db'; +import { BackupSupportedDBTypeList, defaultDBEditValue } from '@/constants/db'; import { editModeMap } from '@/constants/editApp'; import { useConfirm } from '@/hooks/useConfirm'; import { useLoading } from '@/hooks/useLoading'; @@ -9,7 +9,7 @@ import { DBVersionMap } from '@/store/static'; import { useUserStore } from '@/store/user'; import type { YamlItemType } from '@/types'; import type { DBEditType } from '@/types/db'; -import { adaptDBForm } from '@/utils/adapt'; +import { adaptDBForm, convertBackupFormToSpec } from '@/utils/adapt'; import { serviceSideProps } from '@/utils/i18n'; import { json2Account, json2CreateCluster, limitRangeYaml } from '@/utils/json2Yaml'; import { Box, Flex } from '@chakra-ui/react'; @@ -25,6 +25,7 @@ import Form from './components/Form'; import Header from './components/Header'; import Yaml from './components/Yaml'; import useDriver from '@/hooks/useDriver'; +import { updateBackupPolicy } from '@/api/backup'; const ErrorModal = dynamic(() => import('@/components/ErrorModal')); diff --git a/frontend/providers/dbprovider/src/pages/db/migrate/index.tsx b/frontend/providers/dbprovider/src/pages/db/migrate/index.tsx index 62381c5d251..216f1ba3f65 100644 --- a/frontend/providers/dbprovider/src/pages/db/migrate/index.tsx +++ b/frontend/providers/dbprovider/src/pages/db/migrate/index.tsx @@ -39,7 +39,8 @@ const defaultEdit: MigrateForm = { sourceDatabase: '', sourceDatabaseTable: ['All'], isChecked: false, - continued: false + continued: false, + terminationPolicy: 'Delete' }; const EditApp = ({ diff --git a/frontend/providers/dbprovider/src/types/backup.d.ts b/frontend/providers/dbprovider/src/types/backup.d.ts index 7468cf44e40..a0b03bdf044 100644 --- a/frontend/providers/dbprovider/src/types/backup.d.ts +++ b/frontend/providers/dbprovider/src/types/backup.d.ts @@ -59,8 +59,8 @@ export interface BackupCRItemType { export type AutoBackupType = 'day' | 'hour' | 'week'; export type AutoBackupFormType = { - start: boolean; - type: AutoType; + start: boolean; // enable auto backup + type: AutoBackupType; week: string[]; hour: string; minute: string; diff --git a/frontend/providers/dbprovider/src/types/cluster.d.ts b/frontend/providers/dbprovider/src/types/cluster.d.ts index c75d11ec549..3ca22bead0d 100644 --- a/frontend/providers/dbprovider/src/types/cluster.d.ts +++ b/frontend/providers/dbprovider/src/types/cluster.d.ts @@ -20,10 +20,18 @@ export type KbPgClusterType = { status?: KubeBlockClusterStatus; }; +/** + * DoNotTerminate: The database deletion cannot be performed. + * Halt: deletes only database resources, but retains database disks. + * Delete: deletes only the database resource, but keeps the database backup. + * WipeOut: deletes all contents of the database. + */ +export type KubeBlockClusterTerminationPolicy = 'Delete' | 'WipeOut'; + export interface KubeBlockClusterSpec { clusterDefinitionRef: `${DBTypeEnum}`; clusterVersionRef: string; - terminationPolicy: string; + terminationPolicy: KubeBlockClusterTerminationPolicy; componentSpecs: { componentDefRef: `${DBTypeEnum}`; name: `${DBTypeEnum}`; @@ -54,7 +62,7 @@ export interface KubeBlockClusterSpec { enabled: boolean; cronExpression: string; method: string; - pitrEnabled: boolean; + pitrEnabled?: boolean; repoName: string; retentionPeriod: string; }; @@ -89,6 +97,9 @@ export type KbPodType = { }; }; +/** + * @deprecated + */ export type KubeBlockBackupPolicyType = { metadata: { name: string; diff --git a/frontend/providers/dbprovider/src/types/db.d.ts b/frontend/providers/dbprovider/src/types/db.d.ts index 054612e8636..48fe96769ff 100644 --- a/frontend/providers/dbprovider/src/types/db.d.ts +++ b/frontend/providers/dbprovider/src/types/db.d.ts @@ -13,6 +13,8 @@ import type { V1ContainerStatus } from '@kubernetes/client-node'; import { I18nCommonKey } from './i18next'; +import { AutoBackupFormType } from './backup'; +import { KubeBlockClusterTerminationPolicy } from './cluster'; export type DBType = `${DBTypeEnum}`; @@ -68,6 +70,8 @@ export interface DBEditType { memory: number; storage: number; labels: { [key: string]: string }; + terminationPolicy: KubeBlockClusterTerminationPolicy; + autoBackup?: AutoBackupFormType; } export type DBSourceType = 'app_store' | 'sealaf'; diff --git a/frontend/providers/dbprovider/src/types/migrate.d.ts b/frontend/providers/dbprovider/src/types/migrate.d.ts index 8a1fab267c0..6e343407868 100644 --- a/frontend/providers/dbprovider/src/types/migrate.d.ts +++ b/frontend/providers/dbprovider/src/types/migrate.d.ts @@ -1,3 +1,4 @@ +import { KubeBlockClusterTerminationPolicy } from './cluster'; import { DBType } from './db'; export enum InternetMigrationTemplate { @@ -113,6 +114,7 @@ export type MigrateForm = { isChecked: boolean; continued: boolean; remark?: string; + terminationPolicy: KubeBlockClusterTerminationPolicy; }; export interface MigrateItemType { diff --git a/frontend/providers/dbprovider/src/utils/adapt.ts b/frontend/providers/dbprovider/src/utils/adapt.ts index d8429ec9b10..d43342f0d3a 100644 --- a/frontend/providers/dbprovider/src/utils/adapt.ts +++ b/frontend/providers/dbprovider/src/utils/adapt.ts @@ -1,17 +1,14 @@ import { BACKUP_REMARK_LABEL_KEY, BackupTypeEnum, backupStatusMap } from '@/constants/backup'; import { + DBBackupMethodNameMap, DBPreviousConfigKey, DBReconfigStatusMap, DBSourceConfigs, MigrationRemark, dbStatusMap } from '@/constants/db'; -import type { AutoBackupFormType, BackupCRItemType } from '@/types/backup'; -import type { - KbPgClusterType, - KubeBlockBackupPolicyType, - KubeBlockOpsRequestType -} from '@/types/cluster'; +import type { AutoBackupFormType, AutoBackupType, BackupCRItemType } from '@/types/backup'; +import type { KbPgClusterType, KubeBlockOpsRequestType } from '@/types/cluster'; import type { DBDetailType, DBEditType, @@ -106,41 +103,57 @@ export const adaptDBDetail = (db: KbPgClusterType): DBDetailType => { conditions: db?.status?.conditions || [], isDiskSpaceOverflow: false, labels: db.metadata.labels || {}, - source: getDBSource(db) + source: getDBSource(db), + autoBackup: adaptBackupByCluster(db), + terminationPolicy: db.spec?.terminationPolicy || 'Delete' }; }; export const adaptBackupByCluster = (db: KbPgClusterType): AutoBackupFormType => { - const backup = db.spec.backup - ? adaptPolicy({ - metadata: { - name: db.metadata.name, - uid: db.metadata.uid - }, - spec: { - retention: { - ttl: db.spec.backup.retentionPeriod - }, - schedule: { - datafile: { - cronExpression: db.spec.backup.cronExpression, - enable: db.spec.backup.enabled - } - } - } - }) - : { - start: false, - hour: '18', - minute: '00', - week: [], - type: 'day', - saveTime: 7, - saveType: 'd' - }; + const backup = + db.spec?.backup && db.spec?.backup?.cronExpression + ? adaptPolicy(db.spec.backup) + : { + start: false, + hour: '18', + minute: '00', + week: [], + type: 'day' as AutoBackupType, + saveTime: 7, + saveType: 'd' + }; return backup; }; +export const convertBackupFormToSpec = (data: { + autoBackup?: AutoBackupFormType; + dbType: DBType; +}): KbPgClusterType['spec']['backup'] => { + const cron = (() => { + if (data.autoBackup?.type === 'week') { + if (!data.autoBackup?.week?.length) { + throw new Error('Week is empty'); + } + return `${data.autoBackup.minute} ${data.autoBackup.hour} * * ${data.autoBackup.week.join( + ',' + )}`; + } + if (data.autoBackup?.type === 'day') { + return `${data.autoBackup.minute} ${data.autoBackup.hour} * * *`; + } + return `${data.autoBackup?.minute} * * * *`; + })(); + + return { + enabled: data.autoBackup?.start ?? false, + cronExpression: convertCronTime(cron, -8), + method: DBBackupMethodNameMap[data.dbType], + retentionPeriod: `${data.autoBackup?.saveTime}${data.autoBackup?.saveType}`, + repoName: '', + pitrEnabled: false + }; +}; + export const adaptDBForm = (db: DBDetailType): DBEditType => { const keys: Record = { dbType: 1, @@ -150,7 +163,9 @@ export const adaptDBForm = (db: DBDetailType): DBEditType => { memory: 1, replicas: 1, storage: 1, - labels: 1 + labels: 1, + autoBackup: 1, + terminationPolicy: 1 }; const form: any = {}; @@ -220,7 +235,7 @@ export const adaptBackup = (backup: BackupCRItemType): BackupItemType => { }; }; -export const adaptPolicy = (policy: KubeBlockBackupPolicyType): AutoBackupFormType => { +export const adaptPolicy = (policy: KbPgClusterType['spec']['backup']): AutoBackupFormType => { function parseDate(str: string) { const regex = /(\d+)([a-zA-Z]+)/; const matches = str.match(regex); @@ -234,6 +249,7 @@ export const adaptPolicy = (policy: KubeBlockBackupPolicyType): AutoBackupFormTy return { number: 7, unit: 'd' }; } + function parseCron(str: string) { const cronFields = convertCronTime(str, 8).split(' '); const minuteField = cronFields[0]; @@ -249,7 +265,6 @@ export const adaptPolicy = (policy: KubeBlockBackupPolicyType): AutoBackupFormTy type: 'week' }; } - console.log(minuteField, hourField, weekField); // every day if (hourField !== '*') { @@ -279,12 +294,12 @@ export const adaptPolicy = (policy: KubeBlockBackupPolicyType): AutoBackupFormTy }; } - const { number: saveTime, unit: saveType } = parseDate(policy.spec.retention.ttl); - const { hour, minute, week, type } = parseCron(policy.spec.schedule.datafile.cronExpression); + const { number: saveTime, unit: saveType } = parseDate(policy.retentionPeriod); + const { hour, minute, week, type } = parseCron(policy?.cronExpression ?? '0 0 * * *'); return { - start: policy.spec.schedule.datafile.enable, - type, + start: policy.enabled, + type: type as AutoBackupType, week, hour, minute, diff --git a/frontend/providers/dbprovider/src/utils/json2Yaml.ts b/frontend/providers/dbprovider/src/utils/json2Yaml.ts index 88fb8333efd..6c581cd1491 100644 --- a/frontend/providers/dbprovider/src/utils/json2Yaml.ts +++ b/frontend/providers/dbprovider/src/utils/json2Yaml.ts @@ -11,13 +11,14 @@ import { } from '@/constants/db'; import { StorageClassName } from '@/store/env'; import type { BackupItemType, DBDetailType, DBEditType, DBType } from '@/types/db'; -import { DumpForm, MigrateForm } from '@/types/migrate'; +import { MigrateForm } from '@/types/migrate'; import { encodeToHex, formatTime, str2Num } from '@/utils/tools'; import dayjs from 'dayjs'; import yaml from 'js-yaml'; import { getUserNamespace } from './user'; import { V1StatefulSet } from '@kubernetes/client-node'; import { customAlphabet } from 'nanoid'; + const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 5); /** @@ -110,7 +111,7 @@ export const json2CreateCluster = (data: DBEditType, backupInfo?: BackupItemType ] } ], - terminationPolicy, + terminationPolicy: data.terminationPolicy, tolerations: [] } } @@ -153,7 +154,7 @@ export const json2CreateCluster = (data: DBEditType, backupInfo?: BackupItemType ] } ], - terminationPolicy, + terminationPolicy: data.terminationPolicy, tolerations: [] } } @@ -199,7 +200,7 @@ export const json2CreateCluster = (data: DBEditType, backupInfo?: BackupItemType ] } ], - terminationPolicy, + terminationPolicy: data.terminationPolicy, tolerations: [] } } @@ -280,7 +281,7 @@ export const json2CreateCluster = (data: DBEditType, backupInfo?: BackupItemType : {}) } ], - terminationPolicy, + terminationPolicy: data.terminationPolicy, tolerations: [] } }