🐛 fix(redis): 修复超过16个数据库无法展示

- 后端改为通过 CONFIG GET databases 动态获取 Redis 数据库数量
- 放宽单机和 Sentinel 模式的 RedisDB 索引限制,支持 db16 及以上
- 前端连接配置和持久化不再裁剪高编号 Redis 数据库
- 连接测试成功后按服务端返回的真实数据库列表展示可选 DB
- 增加 Redis db31 展示、切换、保存和 URI 解析回归测试
Refs #558
This commit is contained in:
Syngnat
2026-06-12 17:22:09 +08:00
parent 70134cd77f
commit 8519748512
12 changed files with 369 additions and 78 deletions

View File

@@ -117,6 +117,7 @@ import {
MongoDiscoverMembers,
TestConnection,
RedisConnect,
RedisGetDatabases,
SelectDatabaseFile,
SelectCertificateFile,
SelectSSHKeyFile,
@@ -141,6 +142,7 @@ const MAX_URI_HOSTS = 32;
const MAX_TIMEOUT_SECONDS = 3600;
const CONNECTION_MODAL_WIDTH = 960;
const CONNECTION_MODAL_BODY_HEIGHT = 620;
const REDIS_DEFAULT_DATABASE_COUNT = 16;
const STEP1_SIDEBAR_DIVIDER_DARK = "rgba(255, 255, 255, 0.16)";
const STEP1_SIDEBAR_DIVIDER_LIGHT = "rgba(0, 0, 0, 0.08)";
const CLICKHOUSE_PROTOCOL_OPTIONS: Array<{
@@ -158,6 +160,62 @@ const OCEANBASE_PROTOCOL_OPTIONS: Array<{
{ value: "mysql", label: "MySQL" },
{ value: "oracle", label: "Oracle" },
];
const normalizeRedisDatabaseIndex = (value: unknown): number | null => {
const parsed = Number(value);
if (!Number.isFinite(parsed) || parsed < 0) return null;
return Math.trunc(parsed);
};
const buildRedisDatabaseList = (...values: unknown[]): number[] => {
const indexes = new Set<number>();
for (let i = 0; i < REDIS_DEFAULT_DATABASE_COUNT; i += 1) {
indexes.add(i);
}
const collect = (value: unknown) => {
if (Array.isArray(value)) {
value.forEach(collect);
return;
}
const index = normalizeRedisDatabaseIndex(value);
if (index !== null) {
indexes.add(index);
}
};
values.forEach(collect);
return Array.from(indexes).sort((a, b) => a - b);
};
const extractRedisDatabaseList = (value: unknown): number[] => {
if (!Array.isArray(value)) return [];
const indexes = new Set<number>();
value.forEach((row: any) => {
const index = normalizeRedisDatabaseIndex(row?.index ?? row?.Index);
if (index !== null) {
indexes.add(index);
}
});
const result = Array.from(indexes).sort((a, b) => a - b);
return result.length > 0 ? result : buildRedisDatabaseList();
};
const normalizeRedisDatabaseSelection = (
value: unknown,
supportedDbs: number[],
): number[] | undefined => {
if (!Array.isArray(value)) return undefined;
const supported = new Set(supportedDbs);
const selected = Array.from(
new Set(
value
.map(normalizeRedisDatabaseIndex)
.filter((index): index is number => index !== null)
.filter((index) => supported.size === 0 || supported.has(index)),
),
).sort((a, b) => a - b);
return selected.length > 0 ? selected : undefined;
};
const normalizeClickHouseProtocolValue = (
value: unknown,
): ClickHouseProtocolChoice => {
@@ -290,7 +348,7 @@ const ConnectionModal: React.FC<{
} | null>(null);
const [testErrorLogOpen, setTestErrorLogOpen] = useState(false);
const [dbList, setDbList] = useState<string[]>([]);
const [redisDbList, setRedisDbList] = useState<number[]>([]); // Redis databases 0-15
const [redisDbList, setRedisDbList] = useState<number[]>([]);
const [mongoMembers, setMongoMembers] = useState<MongoMemberInfo[]>([]);
const [discoveringMembers, setDiscoveringMembers] = useState(false);
const [uriFeedback, setUriFeedback] = useState<{
@@ -728,19 +786,17 @@ const ConnectionModal: React.FC<{
) {
form.setFieldValue("port", 6379);
}
const supportedDbs = Array.from({ length: 16 }, (_, i) => i);
const supportedDbs = buildRedisDatabaseList(
form.getFieldValue("redisDB"),
form.getFieldValue("includeRedisDatabases"),
);
setRedisDbList(supportedDbs);
const selectedDbsRaw = form.getFieldValue("includeRedisDatabases");
const selectedDbs = Array.isArray(selectedDbsRaw)
? selectedDbsRaw.map((entry: any) => Number(entry))
: [];
const validDbs = selectedDbs
.filter((entry: number) => Number.isFinite(entry))
.map((entry: number) => Math.trunc(entry))
.filter((entry: number) => supportedDbs.includes(entry));
form.setFieldValue(
"includeRedisDatabases",
validDbs.length > 0 ? validDbs : undefined,
normalizeRedisDatabaseSelection(
form.getFieldValue("includeRedisDatabases"),
supportedDbs,
),
);
}
if (fieldName === "proxyType") {
@@ -2472,7 +2528,12 @@ const ConnectionModal: React.FC<{
}
// 如果是 Redis 编辑模式,设置已保存的 Redis 数据库列表
if (configType === "redis") {
setRedisDbList(Array.from({ length: 16 }, (_, i) => i));
setRedisDbList(
buildRedisDatabaseList(
config.redisDB,
initialValues.includeRedisDatabases,
),
);
}
} else {
// Create mode: Start at step 1
@@ -2878,7 +2939,32 @@ const ConnectionModal: React.FC<{
void message.destroy("connection-test-failure");
setTestResult({ type: "success", message: res.message });
if (isRedisType) {
setRedisDbList(Array.from({ length: 16 }, (_, i) => i));
const dbRes = await withClientTimeout(
RedisGetDatabases(config as any),
rpcTimeoutMs,
`连接成功但拉取 Redis 数据库列表超时(>${timeoutSeconds} 秒)`,
);
if (dbRes.success) {
const supportedDbs = extractRedisDatabaseList(dbRes.data);
setRedisDbList(supportedDbs);
form.setFieldValue(
"includeRedisDatabases",
normalizeRedisDatabaseSelection(
form.getFieldValue("includeRedisDatabases"),
supportedDbs,
),
);
} else {
setRedisDbList(
buildRedisDatabaseList(
config.redisDB,
form.getFieldValue("includeRedisDatabases"),
),
);
message.warning(
`连接成功,但获取 Redis 数据库列表失败:${normalizeConnectionSecretErrorMessage(dbRes.message, "未知错误")}`,
);
}
} else if (!isJVMType) {
// Other databases: fetch database list
const dbRes = await withClientTimeout(
@@ -3419,7 +3505,7 @@ const ConnectionModal: React.FC<{
connectionParams: normalizedConnectionParams,
timeout: Number(mergedValues.timeout || 30),
redisDB: Number.isFinite(Number(mergedValues.redisDB))
? Math.max(0, Math.min(15, Math.trunc(Number(mergedValues.redisDB))))
? Math.max(0, Math.trunc(Number(mergedValues.redisDB)))
: 0,
redisSentinelMaster: redisSentinelMaster,
redisSentinelUser: redisSentinelUser,
@@ -5957,19 +6043,17 @@ const ConnectionModal: React.FC<{
) {
form.setFieldValue("port", 6379);
}
const supportedDbs = Array.from({ length: 16 }, (_, i) => i);
const supportedDbs = buildRedisDatabaseList(
form.getFieldValue("redisDB"),
form.getFieldValue("includeRedisDatabases"),
);
setRedisDbList(supportedDbs);
const selectedDbsRaw = form.getFieldValue("includeRedisDatabases");
const selectedDbs = Array.isArray(selectedDbsRaw)
? selectedDbsRaw.map((entry: any) => Number(entry))
: [];
const validDbs = selectedDbs
.filter((entry: number) => Number.isFinite(entry))
.map((entry: number) => Math.trunc(entry))
.filter((entry: number) => supportedDbs.includes(entry));
form.setFieldValue(
"includeRedisDatabases",
validDbs.length > 0 ? validDbs : undefined,
normalizeRedisDatabaseSelection(
form.getFieldValue("includeRedisDatabases"),
supportedDbs,
),
);
}
if (

View File

@@ -224,7 +224,7 @@ const ConnectionModalRedisSections: React.FC<ConnectionModalRedisSectionsProps>
>
<Select
mode="multiple"
placeholder="选择显示的数据库 (0-15)"
placeholder="选择显示的数据库"
allowClear
>
{redisDbList.map((db) => (