type StoredSecretPlaceholderOptions = { hasStoredSecret?: boolean; emptyPlaceholder: string; retainedLabel: string; }; type ConnectionTestFailureKind = | 'validation' | 'runtime' | 'driver_unavailable' | 'secret_blocked'; type ConnectionTestFailureFeedback = { message: string; shouldToast: boolean; }; export type ConnectionConfigSectionKey = | 'identity' | 'uri' | 'target' | 'fileTarget' | 'connectionMode' | 'oceanBaseProtocol' | 'mongoDiscovery' | 'replica' | 'service' | 'mongoPolicy' | 'credentials' | 'databaseScope' | 'customDriver' | 'customDsn' | 'jvmRuntime'; export type ConnectionConfigLayoutKind = | 'mysql-compatible' | 'mongodb' | 'redis' | 'postgres-compatible' | 'oracle' | 'file' | 'search' | 'vector' | 'custom' | 'jvm' | 'generic-sql'; export type ConnectionConfigLayout = { kind: ConnectionConfigLayoutKind; sections: ConnectionConfigSectionKey[]; }; type ConnectionConfigSectionCopy = { title: string; description: string; }; const mysqlCompatibleTypes = new Set([ 'mysql', 'mariadb', 'oceanbase', 'doris', 'diros', 'starrocks', 'sphinx', ]); const postgresCompatibleTypes = new Set([ 'postgres', 'kingbase', 'highgo', 'vastbase', 'opengauss', ]); const fileDatabaseTypes = new Set(['sqlite', 'duckdb']); const CONNECTION_CONFIG_SECTION_COPY: Record< ConnectionConfigSectionKey, ConnectionConfigSectionCopy > = { identity: { title: '基础身份', description: '连接名称和连接树中展示的基础信息。', }, uri: { title: '连接 URI', description: '适合复制粘贴完整连接串,也可以和下方参数互相生成、解析。', }, target: { title: '目标地址', description: '数据库服务的主机、端口或网关入口,是连通性测试的主目标。', }, fileTarget: { title: '数据库文件', description: 'SQLite / DuckDB 使用本地数据库文件路径,不需要端口和网络隧道。', }, connectionMode: { title: '连接模式', description: '选择单机、主从、副本集或集群等拓扑模式。', }, oceanBaseProtocol: { title: 'OceanBase 协议', description: '明确选择 MySQL 或 Oracle 租户兼容协议。', }, mongoDiscovery: { title: 'MongoDB 寻址', description: '选择标准 host:port 或 mongodb+srv DNS 发现方式。', }, replica: { title: '多节点配置', description: '补充从库、种子节点、副本集成员或独立认证信息。', }, service: { title: '数据库服务', description: '默认数据库、Oracle Service Name 等服务级定位参数。', }, mongoPolicy: { title: 'MongoDB 策略', description: '认证库、读偏好等 MongoDB 专属策略。', }, credentials: { title: '认证凭据', description: '用户名、密码和密文保留策略;留空会按已保存密文规则处理。', }, databaseScope: { title: '数据库范围', description: '连接成功后可限制连接树展示的数据库或 Redis DB。', }, customDriver: { title: '自定义驱动', description: '指定驱动名称,用于匹配已安装或可动态导入的数据库驱动。', }, customDsn: { title: '连接字符串', description: '直接填写驱动要求的 DSN,适合非内置数据源或特殊参数。', }, jvmRuntime: { title: 'JVM 运行时', description: 'JVM 目标、接入模式、JMX、Endpoint、Agent 与诊断增强。', }, }; export const getConnectionConfigSectionCopy = ( key: ConnectionConfigSectionKey, ): ConnectionConfigSectionCopy => CONNECTION_CONFIG_SECTION_COPY[key]; export const getConnectionConfigLayoutKindLabel = ( kind: ConnectionConfigLayoutKind, ): string => { switch (kind) { case 'mysql-compatible': return 'MySQL 兼容'; case 'mongodb': return '文档数据库'; case 'redis': return '键值数据库'; case 'postgres-compatible': return 'PostgreSQL 兼容'; case 'oracle': return 'Oracle 服务'; case 'file': return '文件型数据库'; case 'search': return '搜索引擎'; case 'vector': return '向量数据库'; case 'custom': return '自定义连接'; case 'jvm': return 'JVM 运行时'; case 'generic-sql': default: return '标准 SQL'; } }; export const resolveConnectionConfigLayout = ( rawType: string, ): ConnectionConfigLayout => { const type = String(rawType || '').trim().toLowerCase(); if (type === 'jvm') { return { kind: 'jvm', sections: ['identity', 'jvmRuntime'], }; } if (type === 'custom') { return { kind: 'custom', sections: ['identity', 'customDriver', 'customDsn'], }; } if (fileDatabaseTypes.has(type)) { return { kind: 'file', sections: ['identity', 'uri', 'fileTarget'], }; } if (mysqlCompatibleTypes.has(type)) { return { kind: 'mysql-compatible', sections: [ 'identity', 'uri', 'target', 'connectionMode', 'replica', 'credentials', 'databaseScope', ], }; } if (type === 'mongodb') { return { kind: 'mongodb', sections: [ 'identity', 'uri', 'target', 'connectionMode', 'mongoDiscovery', 'replica', 'mongoPolicy', 'credentials', 'databaseScope', ], }; } if (type === 'redis') { return { kind: 'redis', sections: [ 'identity', 'uri', 'target', 'connectionMode', 'credentials', 'databaseScope', ], }; } if (type === 'elasticsearch') { return { kind: 'search', sections: [ 'identity', 'uri', 'target', 'service', 'credentials', 'databaseScope', ], }; } if (type === 'chroma') { return { kind: 'vector', sections: [ 'identity', 'uri', 'target', 'service', 'credentials', 'databaseScope', ], }; } if (postgresCompatibleTypes.has(type)) { return { kind: 'postgres-compatible', sections: [ 'identity', 'uri', 'target', 'service', 'credentials', 'databaseScope', ], }; } if (type === 'oracle') { return { kind: 'oracle', sections: [ 'identity', 'uri', 'target', 'service', 'credentials', 'databaseScope', ], }; } return { kind: 'generic-sql', sections: ['identity', 'uri', 'target', 'credentials', 'databaseScope'], }; }; const normalizeText = (value: unknown, fallback = ''): string => { const text = String(value ?? '').trim(); if (!text || text === 'undefined' || text === 'null') { return fallback; } return text; }; export const getStoredSecretPlaceholder = ({ hasStoredSecret, emptyPlaceholder, retainedLabel, }: StoredSecretPlaceholderOptions): string => ( hasStoredSecret ? `••••••(留空表示继续沿用${retainedLabel})` : emptyPlaceholder ); export const normalizeConnectionSecretErrorMessage = ( value: unknown, fallback = '', ): string => { const text = normalizeText(value, fallback); const lower = text.toLowerCase(); if (lower.includes('saved connection not found:')) { return '未找到当前连接对应的已保存密文,请重新填写密码并保存后再试'; } if (lower.includes('secret store unavailable')) { return '系统密文存储当前不可用,请检查系统钥匙串或凭据管理器后再试'; } return text; }; export const summarizeConnectionTestFailureMessage = ( value: unknown, fallback = '', ): string => { const text = normalizeConnectionSecretErrorMessage(value, fallback); const [firstLine] = text .split(/\r?\n/) .map((item) => item.trim()) .filter((item) => item !== ''); return firstLine || text; }; export const resolveConnectionTestFailureFeedback = ({ kind, reason, fallback, }: { kind: ConnectionTestFailureKind; reason: unknown; fallback: string; }): ConnectionTestFailureFeedback => { if (kind === 'validation') { return { message: '测试失败: 请先完善必填项后再测试连接', shouldToast: false, }; } return { message: `测试失败: ${normalizeConnectionSecretErrorMessage(reason, fallback)}`, shouldToast: false, }; }; export type { ConnectionTestFailureFeedback, ConnectionTestFailureKind, };