fix(dbprovider): support Redis parameter configuration (#7181)

* fix(dbprovider): show redis parameter config in detail

* fix(dbprovider): read redis params from configuration CR

* fix(dbprovider): fall back to redis config map parsing

* fix(dbprovider): show unsupported redis data import state
This commit is contained in:
Alex Lee
2026-08-10 10:35:37 +08:00
committed by GitHub
parent d78998cb29
commit ac3fadeb02
9 changed files with 116 additions and 24 deletions
@@ -157,6 +157,7 @@
"current_connections": "Current Connections",
"manage_data_redirect_failed": "Failed to open data management",
"data_import": "Data Import",
"data_import_unsupported_for_redis": "Redis does not support data import yet",
"data_migration_config": "Data Migration Settings",
"database": "database",
"database_config": "parameters",
@@ -156,6 +156,7 @@
"manage_data_redirect_failed": "打开数据管理失败",
"current_connections": "当前连接数",
"data_import": "数据导入",
"data_import_unsupported_for_redis": "Redis 暂不支持数据导入",
"data_migration_config": "数据迁移配置",
"database": "数据库",
"database_config": "数据库参数",
@@ -440,10 +440,10 @@ export const DBReconfigureMap: Partial<
},
redis: {
type: 'ini',
configMapName: '',
configMapKey: '',
reconfigureName: '',
reconfigureKey: ''
configMapName: '-redis-redis-replication-config',
configMapKey: 'redis.conf',
reconfigureName: 'redis-replication-config',
reconfigureKey: 'redis.conf'
},
kafka: {
type: 'ini',
@@ -639,5 +639,11 @@ export const ParameterFieldMetadataMap: Partial<
default: {
// No params are allowed to be modified for MongoDB
}
},
redis: {
default: {
maxclients: { editable: true },
maxmemory: { editable: true }
}
}
};
@@ -14,7 +14,7 @@ import {
ParameterConfigField,
ParameterFieldMetadata
} from '@/types/db';
import { parseConfig, flattenObject } from '@/utils/tools';
import { parseConfig, parseRedisConfig, flattenObject } from '@/utils/tools';
import type { NextApiRequest, NextApiResponse } from 'next';
/**
@@ -91,13 +91,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
}
const key = name + dbConfig.configMapName;
if (!key || !dbConfig.configMapName) {
return jsonRes(res, {
data: null
});
}
let dbVersion: string | undefined;
try {
const { body: clusterData } = (await k8sCustomObjects.getNamespacedCustomObject(
@@ -112,19 +105,71 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
console.warn('Failed to get cluster version, using default config:', error);
}
const { body } = await k8sCore.readNamespacedConfigMap(key, namespace);
let parsedConfig: Record<string, any> | null = null;
const configData = body?.data && body?.data[dbConfig.configMapKey];
if (!configData) {
if (dbType === 'redis') {
const redisConfigMapKey = name + dbConfig.configMapName;
let redisConfigMapData = '';
if (redisConfigMapKey && dbConfig.configMapName) {
try {
const { body } = await k8sCore.readNamespacedConfigMap(redisConfigMapKey, namespace);
redisConfigMapData = body?.data?.[dbConfig.configMapKey] || '';
} catch (error) {
console.warn('Failed to get redis config map, falling back to configuration CR:', error);
}
}
try {
const { body: configurationBody } = (await k8sCustomObjects.getNamespacedCustomObject(
'apps.kubeblocks.io',
'v1alpha1',
namespace,
'configurations',
`${name}-redis`
)) as { body: any };
const redisConfigItem = configurationBody?.spec?.configItemDetails?.find(
(item: any) => item.name === dbConfig.reconfigureName
);
const redisConfigParams = redisConfigItem?.configFileParams?.['redis.conf']?.parameters || {};
const mergedRedisConfig = {
...(redisConfigMapData ? parseRedisConfig(redisConfigMapData) : {}),
...redisConfigParams
};
parsedConfig = Object.keys(mergedRedisConfig).length > 0 ? mergedRedisConfig : null;
} catch (error) {
console.warn('Failed to get redis configuration, using config map fallback if available:', error);
parsedConfig = redisConfigMapData ? parseRedisConfig(redisConfigMapData) : null;
}
} else {
const key = name + dbConfig.configMapName;
if (!key || !dbConfig.configMapName) {
return jsonRes(res, {
data: null
});
}
const { body } = await k8sCore.readNamespacedConfigMap(key, namespace);
const configData = body?.data && body?.data[dbConfig.configMapKey];
if (!configData) {
return jsonRes(res, {
data: null
});
}
parsedConfig = parseConfig({
configString: configData,
type: dbConfig.type
});
}
if (!parsedConfig) {
return jsonRes(res, {
data: null
});
}
const parsedConfig = parseConfig({
configString: configData,
type: dbConfig.type
});
const flattenedConfig = flattenObject(parsedConfig);
const versionedOverrides = ParameterFieldOverrides[dbType] || {};
@@ -1,11 +1,12 @@
import { DBDetailType } from '@/types/db';
import { Box, Button, Flex } from '@chakra-ui/react';
import { Box, Button, Flex, Text } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { useState } from 'react';
import { MigrateTable } from './Migrate/Table';
import DumpImport from './DumpImport';
import useEnvStore from '@/store/env';
import { useRouter } from 'next/router';
import MyIcon from '@/components/Icon';
enum MenuType {
DumpImport = 'DumpImport',
@@ -20,6 +21,17 @@ export default function DataImport({ db }: { db?: DBDetailType }) {
const { SystemEnv } = useEnvStore();
const router = useRouter();
if (db.dbType === 'redis') {
return (
<Flex h={'full'} alignItems={'center'} justifyContent={'center'} flexDirection={'column'}>
<MyIcon name={'noEvents'} color={'transparent'} width={'36px'} height={'36px'} />
<Text pt={'8px'} color={'grayModern.600'} fontSize={'14px'}>
{t('data_import_unsupported_for_redis')}
</Text>
</Flex>
);
}
return (
<Flex flexDirection={'column'} h={'full'}>
<Flex justifyContent={'space-between'} alignItems={'center'} mb={'16px'}>
@@ -56,7 +56,9 @@ const AppDetail = ({
const [isSmallScreen] = useMediaQuery('(max-width: 1180px)');
const { isDataImportSupported, listNav } = useMemo(() => {
const PublicNetMigration = ['postgresql', 'apecloud-mysql', 'mongodb'].includes(dbType);
const PublicNetMigration = ['postgresql', 'apecloud-mysql', 'mongodb', 'redis'].includes(
dbType
);
const MigrateSupported = ['postgresql', 'mongodb', 'apecloud-mysql'].includes(dbType);
const BackupSupported = BackupSupportedDBTypeList.includes(dbType) && SystemEnv.BACKUP_ENABLED;
const MonitorSupported = dbType !== 'polardbx';
@@ -333,7 +335,7 @@ export async function getServerSideProps(context: any) {
const queryListType = context.query?.listType || TabEnum.Overview;
const dataImportEnabled = process.env.DATA_IMPORT_ENABLED !== 'false';
const dataImportSupported =
['postgresql', 'apecloud-mysql', 'mongodb'].includes(dbType) && dataImportEnabled;
['postgresql', 'apecloud-mysql', 'mongodb', 'redis'].includes(dbType) && dataImportEnabled;
const listType =
queryListType === TabEnum.DataImport && !dataImportSupported ? TabEnum.Overview : queryListType;
@@ -241,6 +241,15 @@ export async function getServerSideProps(context: any) {
const dbType = context?.query?.dbType || '';
const tabType = context?.query?.type || 'form';
if (dbType === 'redis') {
return {
redirect: {
destination: `/db/detail?name=${dbName}&dbType=${dbType}&listType=dataImport`,
permanent: false
}
};
}
return {
props: { ...(await serviceSideProps(context)), dbName, tabType, dbType }
};
@@ -583,7 +583,7 @@ export const json2MigrateCR = (data: MigrateForm) => {
};
const templateName = templateByDB[data.dbType];
if (templateName === undefined) {
if (!templateName) {
throw new Error(`Migration is not supported for database type: ${data.dbType}`);
}
@@ -463,6 +463,22 @@ export const parseConfig = ({
}
};
export const parseRedisConfig = (configString: string): Record<string, string> => {
const result: Record<string, string> = {};
configString
.split('\n')
.map((line) => line.trim())
.filter((line) => line && !line.startsWith('#'))
.forEach((line) => {
const [key, ...rest] = line.split(/\s+/);
if (!key) return;
result[key] = rest.join(' ');
});
return result;
};
export const flattenObject = (ob: any, prefix: string = ''): { key: string; value: string }[] => {
const result: { key: string; value: string }[] = [];
@@ -483,7 +499,7 @@ export const adjustDifferencesForIni = (
type: 'ini' | 'yaml',
dbType: DBType
): { path: string; newValue: string; oldValue: string }[] => {
if (type !== 'ini' || dbType === 'postgresql') {
if (type !== 'ini' || dbType !== 'apecloud-mysql') {
return differences;
}
return differences.map((diff) => {