feat(redis): support per-database alias in the Redis viewer tree

This commit is contained in:
Matt Van Horn
2026-06-21 00:33:03 -07:00
parent e7b8e78f9c
commit 4408bce159
12 changed files with 373 additions and 11 deletions

View File

@@ -1,4 +1,4 @@
import { Modal, message, type MenuProps } from 'antd';
import { Input, Modal, message, type MenuProps } from 'antd';
import {
CheckSquareOutlined,
CloudOutlined,
@@ -27,11 +27,81 @@ import {
WarningOutlined,
} from '@ant-design/icons';
import { t } from '../../i18n';
import { useStore } from '../../store';
import type { SavedConnection, SavedQuery } from '../../types';
import { getDataSourceCapabilities } from '../../utils/dataSourceCapabilities';
import { buildTableSelectQuery } from '../../utils/objectQueryTemplates';
import {
MAX_REDIS_DB_ALIAS_LENGTH,
buildRedisDbNodeLabel,
getRedisDbAlias,
} from '../../utils/redisDbAlias';
import { supportsTableTruncateAction } from '../tableDataDangerActions';
const updateTreeNodeTitle = (
nodes: any[],
targetKey: string,
title: string,
): any[] =>
nodes.map((node) => {
if (node.key === targetKey) {
return { ...node, title };
}
if (Array.isArray(node.children)) {
return { ...node, children: updateTreeNodeTitle(node.children, targetKey, title) };
}
return node;
});
const openRedisDbAliasModal = (
node: any,
context: SidebarLegacyNodeMenuContext,
): void => {
const { id, redisDB, redisKeyCount } = node.dataRef;
const { treeDataRef, setTreeData } = context;
const currentAlias = getRedisDbAlias(
useStore.getState().appearance.redisDbAliases,
id,
redisDB,
);
let draft = currentAlias;
Modal.confirm({
title: t('redis.db_alias.modal.title', { db: `db${redisDB}` }),
icon: null,
content: (
<Input
defaultValue={currentAlias}
maxLength={MAX_REDIS_DB_ALIAS_LENGTH}
placeholder={t('redis.db_alias.modal.placeholder')}
onChange={(event) => {
draft = event.target.value;
}}
onPressEnter={(event) => {
draft = (event.target as HTMLInputElement).value;
}}
/>
),
okText: t('common.confirm'),
cancelText: t('common.cancel'),
onOk: () => {
useStore.getState().setRedisDbAlias(id, redisDB, draft);
if (treeDataRef?.current && typeof setTreeData === 'function') {
const nextAlias = getRedisDbAlias(
useStore.getState().appearance.redisDbAliases,
id,
redisDB,
);
const keyCount = Number(redisKeyCount);
const suffix = Number.isFinite(keyCount) && keyCount > 0 ? ` (${keyCount})` : '';
const nextTitle = buildRedisDbNodeLabel(redisDB, nextAlias, suffix);
const nextTree = updateTreeNodeTitle(treeDataRef.current, node.key, nextTitle);
treeDataRef.current = nextTree;
setTreeData(nextTree);
}
},
});
};
type TreeNode = {
type?: string;
title?: string;
@@ -526,6 +596,12 @@ export const buildSidebarLegacyNodeMenuItems = (
redisDB: redisDB
});
}
},
{
key: 'set-db-alias',
label: t('redis.db_alias.menu.set'),
icon: <EditOutlined />,
onClick: () => openRedisDbAliasModal(node, context)
}
];
} else if (node.type === 'database') {

View File

@@ -17,6 +17,7 @@ import type { SavedConnection, SavedQuery, ExternalSQLDirectory, ExternalSQLTree
import { useStore } from '../../store';
import { t } from '../../i18n';
import { buildRpcConnectionConfig } from '../../utils/connectionRpcConfig';
import { buildRedisDbNodeLabel, getRedisDbAlias } from '../../utils/redisDbAlias';
import { buildJVMMonitoringActionDescriptors } from '../../utils/jvmSidebarActions';
import { type SidebarViewMetadataEntry } from '../../utils/sidebarMetadata';
import { buildExternalSQLRootNode, type ExternalSQLTreeNode } from '../../utils/externalSqlTree';
@@ -308,15 +309,23 @@ export const useSidebarTreeLoaders = ({
if (res.success) {
setConnectionStates(prev => ({ ...prev, [conn.id]: 'success' }));
const redisRows: any[] = Array.isArray(res.data) ? res.data : [];
let dbs = redisRows.map((db: any) => ({
title: `db${db.index}${db.keys > 0 ? ` (${db.keys})` : ''}`,
key: `${conn.id}-db${db.index}`,
icon: <DatabaseOutlined style={{ color: '#DC382D' }} />,
type: 'redis-db' as const,
dataRef: { ...conn, redisDB: db.index },
isLeaf: true,
dbIndex: db.index,
}));
const redisDbAliases = useStore.getState().appearance.redisDbAliases;
let dbs = redisRows.map((db: any) => {
const keyCount = Number(db.keys) > 0 ? Number(db.keys) : 0;
return {
title: buildRedisDbNodeLabel(
db.index,
getRedisDbAlias(redisDbAliases, conn.id, db.index),
keyCount > 0 ? ` (${keyCount})` : '',
),
key: `${conn.id}-db${db.index}`,
icon: <DatabaseOutlined style={{ color: '#DC382D' }} />,
type: 'redis-db' as const,
dataRef: { ...conn, redisDB: db.index, redisKeyCount: keyCount },
isLeaf: true,
dbIndex: db.index,
};
});
// Filter Redis databases if configured
if (conn.includeRedisDatabases && conn.includeRedisDatabases.length > 0) {
dbs = dbs.filter(db => conn.includeRedisDatabases!.includes(db.dbIndex));

View File

@@ -169,7 +169,13 @@ export const useSidebarV2ContextMenu = ({
return count > 0 ? count.toLocaleString() : '';
}
if (node.type === 'redis-db') {
const match = String(node.title || '').match(/\((\d+)\)/);
const keyCount = Number(node?.dataRef?.redisKeyCount);
if (Number.isFinite(keyCount) && keyCount > 0) {
return keyCount.toLocaleString();
}
// Fallback for nodes built before redisKeyCount was tracked; avoid
// matching an alias by only reading a trailing count suffix.
const match = String(node.title || '').match(/\((\d+)\)\s*$/);
return match?.[1] || '';
}
if (node.type === 'table') {