diff --git a/frontend/src/components/ConnectionModal.edit-password.test.tsx b/frontend/src/components/ConnectionModal.edit-password.test.tsx index 2e6b86c..5408548 100644 --- a/frontend/src/components/ConnectionModal.edit-password.test.tsx +++ b/frontend/src/components/ConnectionModal.edit-password.test.tsx @@ -4,7 +4,8 @@ import { readFileSync } from 'node:fs'; const connectionModalSource = readFileSync(new URL('./ConnectionModal.tsx', import.meta.url), 'utf8'); const redisSectionsSource = readFileSync(new URL('./ConnectionModalRedisSections.tsx', import.meta.url), 'utf8'); const mongoSectionsSource = readFileSync(new URL('./ConnectionModalMongoSections.tsx', import.meta.url), 'utf8'); -const source = `${connectionModalSource}\n${redisSectionsSource}\n${mongoSectionsSource}`; +const connectionTypeCatalogSource = readFileSync(new URL('../utils/connectionTypeCatalog.ts', import.meta.url), 'utf8'); +const source = `${connectionModalSource}\n${redisSectionsSource}\n${mongoSectionsSource}\n${connectionTypeCatalogSource}`; describe('ConnectionModal edit password behavior', () => { it('keeps the prefilled primary password masked by default', () => { @@ -22,14 +23,14 @@ describe('ConnectionModal edit password behavior', () => { describe('ConnectionModal data source registry', () => { it('exposes Elasticsearch in the create-connection picker with HTTP defaults', () => { - expect(source).toContain('case "elasticsearch":'); + expect(source).toContain("case 'elasticsearch':"); expect(source).toContain('return 9200;'); expect(source).toContain('elasticsearch: ["http", "https"]'); - expect(source).toContain('key: "elasticsearch"'); - expect(source).toContain('name: "Elasticsearch"'); - expect(source).toContain('getDbIcon("elasticsearch", undefined, 36)'); + expect(source).toContain("key: 'elasticsearch'"); + expect(source).toContain("name: 'Elasticsearch'"); + expect(source).toContain('icon: getDbIcon(item.key, undefined, 36)'); expect(source).toContain('type === "elasticsearch"'); - expect(source).toContain('return "支持索引浏览、Mapping 检查、JSON DSL 和 query_string 查询";'); + expect(source).toContain("return '支持索引浏览、Mapping 检查、JSON DSL 和 query_string 查询';"); expect(source).toContain( 'type === "clickhouse" ? "default" : (type === "redis" || type === "elasticsearch") ? "" : "root";', ); diff --git a/frontend/src/components/ConnectionModal.tsx b/frontend/src/components/ConnectionModal.tsx index f923bc4..758299e 100644 --- a/frontend/src/components/ConnectionModal.tsx +++ b/frontend/src/components/ConnectionModal.tsx @@ -63,6 +63,12 @@ import { resolveConnectionSecretDraft } from "../utils/connectionSecretDraft"; import { getCustomConnectionDsnValidationMessage } from "../utils/customConnectionDsn"; import { mergeParsedUriValuesForForm } from "../utils/connectionUriMerge"; import { buildRpcConnectionConfig } from "../utils/connectionRpcConfig"; +import { + CONNECTION_TYPE_GROUPS, + getAllConnectionTypeCatalogItems, + getConnectionTypeDefaultPort as getDefaultPortByType, + getConnectionTypeHint, +} from "../utils/connectionTypeCatalog"; import { CUSTOM_CONNECTION_DRIVER_HELP } from "../utils/driverImportGuidance"; import { describeUnsupportedOceanBaseProtocol, @@ -230,58 +236,6 @@ const resolveInitialSecretFieldValue = ( } }; -const getDefaultPortByType = (type: string) => { - switch (type) { - case "jvm": - return 9010; - case "mysql": - return 3306; - case "oceanbase": - return 2881; - case "doris": - case "diros": - case "starrocks": - return 9030; - case "sphinx": - return 9306; - case "clickhouse": - return 9000; - case "postgres": - case "opengauss": - return 5432; - case "redis": - return 6379; - case "tdengine": - return 6041; - case "oracle": - return 1521; - case "dameng": - return 5236; - case "kingbase": - return 54321; - case "sqlserver": - return 1433; - case "iris": - return 1972; - case "mongodb": - return 27017; - case "elasticsearch": - return 9200; - case "highgo": - return 5866; - case "mariadb": - return 3306; - case "vastbase": - return 5432; - case "sqlite": - return 0; - case "duckdb": - return 0; - default: - return 3306; - } -}; - const singleHostUriSchemesByType: Record = { postgres: ["postgresql", "postgres"], opengauss: ["opengauss", "jdbc:opengauss", "postgresql", "postgres"], @@ -4018,176 +3972,19 @@ const ConnectionModal: React.FC<{ const driverStatusChecking = hasCurrentDriverType && !driverStatusLoaded && step === 2; - const dbTypeGroups = [ - { - label: "关系型数据库", - items: [ - { - key: "mysql", - name: "MySQL", - icon: getDbIcon("mysql", undefined, 36), - }, - { - key: "mariadb", - name: "MariaDB", - icon: getDbIcon("mariadb", undefined, 36), - }, - { - key: "diros", - name: "Doris", - icon: getDbIcon("diros", undefined, 36), - }, - { - key: "starrocks", - name: "StarRocks", - icon: getDbIcon("starrocks", undefined, 36), - }, - { - key: "sphinx", - name: "Sphinx", - icon: getDbIcon("sphinx", undefined, 36), - }, - { - key: "clickhouse", - name: "ClickHouse", - icon: getDbIcon("clickhouse", undefined, 36), - }, - { - key: "postgres", - name: "PostgreSQL", - icon: getDbIcon("postgres", undefined, 36), - }, - { - key: "sqlserver", - name: "SQL Server", - icon: getDbIcon("sqlserver", undefined, 36), - }, - { - key: "iris", - name: "InterSystems IRIS", - icon: getDbIcon("iris", undefined, 36), - }, - { - key: "sqlite", - name: "SQLite", - icon: getDbIcon("sqlite", undefined, 36), - }, - { - key: "duckdb", - name: "DuckDB", - icon: getDbIcon("duckdb", undefined, 36), - }, - { - key: "oracle", - name: "Oracle", - icon: getDbIcon("oracle", undefined, 36), - }, - ], - }, - { - label: "国产数据库", - items: [ - { - key: "oceanbase", - name: "OceanBase", - icon: getDbIcon("oceanbase", undefined, 36), - }, - { - key: "dameng", - name: "Dameng (达梦)", - icon: getDbIcon("dameng", undefined, 36), - }, - { - key: "kingbase", - name: "Kingbase (人大金仓)", - icon: getDbIcon("kingbase", undefined, 36), - }, - { - key: "highgo", - name: "HighGo (瀚高)", - icon: getDbIcon("highgo", undefined, 36), - }, - { - key: "vastbase", - name: "Vastbase (海量)", - icon: getDbIcon("vastbase", undefined, 36), - }, - { - key: "opengauss", - name: "OpenGauss", - icon: getDbIcon("opengauss", undefined, 36), - }, - ], - }, - { - label: "NoSQL", - items: [ - { - key: "mongodb", - name: "MongoDB", - icon: getDbIcon("mongodb", undefined, 36), - }, - { - key: "redis", - name: "Redis", - icon: getDbIcon("redis", undefined, 36), - }, - { - key: "elasticsearch", - name: "Elasticsearch", - icon: getDbIcon("elasticsearch", undefined, 36), - }, - ], - }, - { - label: "时序数据库", - items: [ - { - key: "tdengine", - name: "TDengine", - icon: getDbIcon("tdengine", undefined, 36), - }, - ], - }, - { - label: "其他", - items: [ - { - key: "jvm", - name: "JVM Runtime", - icon: getDbIcon("jvm", undefined, 36), - }, - { - key: "custom", - name: "Custom (自定义)", - icon: getDbIcon("custom", undefined, 36), - }, - ], - }, - ]; + const dbTypeGroups = useMemo( + () => + CONNECTION_TYPE_GROUPS.map((group) => ({ + ...group, + items: group.items.map((item) => ({ + ...item, + icon: getDbIcon(item.key, undefined, 36), + })), + })), + [], + ); - const dbTypes = dbTypeGroups.flatMap((g) => g.items); - const getDbTypeHint = (type: string) => { - switch (type) { - case "jvm": - return "JMX / Endpoint / Agent"; - case "custom": - return "自定义驱动与 DSN"; - case "redis": - return "单机 / 集群"; - case "mongodb": - return "单机 / 副本集"; - case "elasticsearch": - return "支持索引浏览、Mapping 检查、JSON DSL 和 query_string 查询"; - case "oceanbase": - return "MySQL / Oracle 租户"; - case "sqlite": - case "duckdb": - return "本地文件连接"; - default: - return "标准连接配置"; - } - }; + const dbTypes = getAllConnectionTypeCatalogItems(); const renderStep1 = () => (
- {getDbTypeHint(item.key)} + {getConnectionTypeHint(item.key)}
diff --git a/frontend/src/utils/connectionTypeCatalog.test.ts b/frontend/src/utils/connectionTypeCatalog.test.ts new file mode 100644 index 0000000..6e3e1f1 --- /dev/null +++ b/frontend/src/utils/connectionTypeCatalog.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from 'vitest'; + +import { + CONNECTION_TYPE_GROUPS, + getAllConnectionTypeCatalogItems, + getConnectionTypeDefaultPort, + getConnectionTypeHint, +} from './connectionTypeCatalog'; + +describe('connectionTypeCatalog', () => { + it('keeps supported connection types grouped for the creation modal', () => { + expect(CONNECTION_TYPE_GROUPS.map((group) => group.label)).toEqual([ + '关系型数据库', + '国产数据库', + 'NoSQL', + '时序数据库', + '其他', + ]); + + const keys = getAllConnectionTypeCatalogItems().map((item) => item.key); + expect(keys).toContain('mysql'); + expect(keys).toContain('oceanbase'); + expect(keys).toContain('mongodb'); + expect(keys).toContain('redis'); + expect(keys).toContain('elasticsearch'); + expect(keys).toContain('jvm'); + expect(keys).toContain('custom'); + expect(new Set(keys).size).toBe(keys.length); + }); + + it('returns the existing default port mapping for supported connection types', () => { + expect(getConnectionTypeDefaultPort('mysql')).toBe(3306); + expect(getConnectionTypeDefaultPort('oceanbase')).toBe(2881); + expect(getConnectionTypeDefaultPort('diros')).toBe(9030); + expect(getConnectionTypeDefaultPort('postgres')).toBe(5432); + expect(getConnectionTypeDefaultPort('redis')).toBe(6379); + expect(getConnectionTypeDefaultPort('oracle')).toBe(1521); + expect(getConnectionTypeDefaultPort('mongodb')).toBe(27017); + expect(getConnectionTypeDefaultPort('elasticsearch')).toBe(9200); + expect(getConnectionTypeDefaultPort('sqlite')).toBe(0); + expect(getConnectionTypeDefaultPort('duckdb')).toBe(0); + expect(getConnectionTypeDefaultPort('unknown')).toBe(3306); + }); + + it('keeps concise localized hints for special connection types', () => { + expect(getConnectionTypeHint('redis')).toBe('单机 / 集群'); + expect(getConnectionTypeHint('mongodb')).toBe('单机 / 副本集'); + expect(getConnectionTypeHint('elasticsearch')).toContain('Mapping'); + expect(getConnectionTypeHint('oceanbase')).toBe('MySQL / Oracle 租户'); + expect(getConnectionTypeHint('duckdb')).toBe('本地文件连接'); + expect(getConnectionTypeHint('mysql')).toBe('标准连接配置'); + }); +}); diff --git a/frontend/src/utils/connectionTypeCatalog.ts b/frontend/src/utils/connectionTypeCatalog.ts new file mode 100644 index 0000000..de8e023 --- /dev/null +++ b/frontend/src/utils/connectionTypeCatalog.ts @@ -0,0 +1,137 @@ +export type ConnectionTypeCatalogItem = { + key: string; + name: string; +}; + +export type ConnectionTypeCatalogGroup = { + label: string; + items: ConnectionTypeCatalogItem[]; +}; + +export const CONNECTION_TYPE_GROUPS: ConnectionTypeCatalogGroup[] = [ + { + label: '关系型数据库', + items: [ + { key: 'mysql', name: 'MySQL' }, + { key: 'mariadb', name: 'MariaDB' }, + { key: 'diros', name: 'Doris' }, + { key: 'starrocks', name: 'StarRocks' }, + { key: 'sphinx', name: 'Sphinx' }, + { key: 'clickhouse', name: 'ClickHouse' }, + { key: 'postgres', name: 'PostgreSQL' }, + { key: 'sqlserver', name: 'SQL Server' }, + { key: 'iris', name: 'InterSystems IRIS' }, + { key: 'sqlite', name: 'SQLite' }, + { key: 'duckdb', name: 'DuckDB' }, + { key: 'oracle', name: 'Oracle' }, + ], + }, + { + label: '国产数据库', + items: [ + { key: 'oceanbase', name: 'OceanBase' }, + { key: 'dameng', name: 'Dameng (达梦)' }, + { key: 'kingbase', name: 'Kingbase (人大金仓)' }, + { key: 'highgo', name: 'HighGo (瀚高)' }, + { key: 'vastbase', name: 'Vastbase (海量)' }, + { key: 'opengauss', name: 'OpenGauss' }, + ], + }, + { + label: 'NoSQL', + items: [ + { key: 'mongodb', name: 'MongoDB' }, + { key: 'redis', name: 'Redis' }, + { key: 'elasticsearch', name: 'Elasticsearch' }, + ], + }, + { + label: '时序数据库', + items: [ + { key: 'tdengine', name: 'TDengine' }, + ], + }, + { + label: '其他', + items: [ + { key: 'jvm', name: 'JVM Runtime' }, + { key: 'custom', name: 'Custom (自定义)' }, + ], + }, +]; + +export const getConnectionTypeDefaultPort = (type: string): number => { + switch (String(type || '').trim().toLowerCase()) { + case 'jvm': + return 9010; + case 'mysql': + return 3306; + case 'oceanbase': + return 2881; + case 'doris': + case 'diros': + case 'starrocks': + return 9030; + case 'sphinx': + return 9306; + case 'clickhouse': + return 9000; + case 'postgres': + case 'opengauss': + return 5432; + case 'redis': + return 6379; + case 'tdengine': + return 6041; + case 'oracle': + return 1521; + case 'dameng': + return 5236; + case 'kingbase': + return 54321; + case 'sqlserver': + return 1433; + case 'iris': + return 1972; + case 'mongodb': + return 27017; + case 'elasticsearch': + return 9200; + case 'highgo': + return 5866; + case 'mariadb': + return 3306; + case 'vastbase': + return 5432; + case 'sqlite': + case 'duckdb': + return 0; + default: + return 3306; + } +}; + +export const getConnectionTypeHint = (type: string): string => { + switch (String(type || '').trim().toLowerCase()) { + case 'jvm': + return 'JMX / Endpoint / Agent'; + case 'custom': + return '自定义驱动与 DSN'; + case 'redis': + return '单机 / 集群'; + case 'mongodb': + return '单机 / 副本集'; + case 'elasticsearch': + return '支持索引浏览、Mapping 检查、JSON DSL 和 query_string 查询'; + case 'oceanbase': + return 'MySQL / Oracle 租户'; + case 'sqlite': + case 'duckdb': + return '本地文件连接'; + default: + return '标准连接配置'; + } +}; + +export const getAllConnectionTypeCatalogItems = (): ConnectionTypeCatalogItem[] => + CONNECTION_TYPE_GROUPS.flatMap((group) => group.items);