mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-06 20:03:05 +08:00
🐛 fix(dameng): 修复达梦数据源侧边栏无法展开数据库节点的问题
- 权限适配:取消对 SYSDBA schema 的默认过滤,并增加 `SELECT USER FROM DUAL` 兜底查询 - 树节点容错:Sidebar 当数据库为空时不再阻断加载状态,允许用户重试刷新并增加明确提示 - 类型修正:修复 RedisMonitor 组件 `NodeJS.Timeout` 在 Vite 下的编译报错 - 测试覆盖:补充达梦 SYSDBA 过滤及兜底查询逻辑的单元测试
This commit is contained in:
@@ -1 +1 @@
|
||||
f697e821b4acd5cf614d63d46453e8a4
|
||||
20168ff7047e0ecea00acb73f413f7db
|
||||
@@ -51,7 +51,7 @@ const RedisMonitor: React.FC<RedisMonitorProps> = ({ connectionId, redisDB }) =>
|
||||
// Ref to track if component is mounted to prevent state updates after unmount
|
||||
const mountedRef = useRef(true);
|
||||
// Interval ref
|
||||
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
// Previous ops counter to calculate QPS if instantaneous_ops_per_sec is not enough
|
||||
const prevMetricsRef = useRef({ prevOps: 0, prevTime: 0 });
|
||||
|
||||
|
||||
@@ -1036,13 +1036,21 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
dbs = dbs.filter(db => conn.includeDatabases!.includes(db.title));
|
||||
}
|
||||
|
||||
setTreeData(origin => updateTreeData(origin, node.key, dbs));
|
||||
if (dbs.length > 0) {
|
||||
setTreeData(origin => updateTreeData(origin, node.key, dbs));
|
||||
} else {
|
||||
// 空列表:清理 loadedKeys 以允许重新加载,不设置 children = []
|
||||
setLoadedKeys(prev => prev.filter(k => k !== node.key));
|
||||
message.warning({ content: '未获取到可见数据库/schema,请检查账号权限或右键刷新', key: `conn-${conn.id}-dbs` });
|
||||
}
|
||||
} else {
|
||||
setConnectionStates(prev => ({ ...prev, [conn.id]: 'error' }));
|
||||
setLoadedKeys(prev => prev.filter(k => k !== node.key));
|
||||
message.error({ content: res.message, key: `conn-${conn.id}-dbs` });
|
||||
}
|
||||
} catch (e: any) {
|
||||
setConnectionStates(prev => ({ ...prev, [conn.id]: 'error' }));
|
||||
setLoadedKeys(prev => prev.filter(k => k !== node.key));
|
||||
message.error({ content: '连接失败: ' + (e?.message || String(e)), key: `conn-${conn.id}-dbs` });
|
||||
} finally {
|
||||
loadingNodesRef.current.delete(loadKey);
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
)
|
||||
|
||||
var damengDatabaseQueries = []string{
|
||||
// 优先使用达梦原生系统表
|
||||
"SELECT DISTINCT OBJECT_NAME AS DATABASE_NAME FROM SYS.SYSOBJECTS WHERE TYPE$ = 'SCH' AND OBJECT_NAME NOT IN ('SYS','SYSDBA','SYSAUDITOR','SYSSSO','CTISYS','__RECYCLE_USER__') ORDER BY OBJECT_NAME",
|
||||
"SELECT SCHEMA_NAME AS DATABASE_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN ('SYS','SYSDBA','SYSAUDITOR','SYSSSO','CTISYS','INFORMATION_SCHEMA') ORDER BY SCHEMA_NAME",
|
||||
// 优先使用达梦原生系统表(SYSDBA 保留:作为默认管理员 schema,大多数用户在此创建业务表)
|
||||
"SELECT DISTINCT OBJECT_NAME AS DATABASE_NAME FROM SYS.SYSOBJECTS WHERE TYPE$ = 'SCH' AND OBJECT_NAME NOT IN ('SYS','SYSAUDITOR','SYSSSO','CTISYS','__RECYCLE_USER__') ORDER BY OBJECT_NAME",
|
||||
"SELECT SCHEMA_NAME AS DATABASE_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN ('SYS','SYSAUDITOR','SYSSSO','CTISYS','INFORMATION_SCHEMA') ORDER BY SCHEMA_NAME",
|
||||
// Oracle 兼容层
|
||||
"SELECT SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') AS DATABASE_NAME FROM DUAL",
|
||||
"SELECT SYS_CONTEXT('USERENV', 'CURRENT_USER') AS DATABASE_NAME FROM DUAL",
|
||||
@@ -21,6 +21,8 @@ var damengDatabaseQueries = []string{
|
||||
"SELECT USERNAME AS DATABASE_NAME FROM SYS.DBA_USERS ORDER BY USERNAME",
|
||||
"SELECT DISTINCT OWNER AS DATABASE_NAME FROM ALL_OBJECTS ORDER BY OWNER",
|
||||
"SELECT DISTINCT OWNER AS DATABASE_NAME FROM ALL_TABLES ORDER BY OWNER",
|
||||
// 最终兜底:获取当前连接用户作为 schema 名称
|
||||
"SELECT USER AS DATABASE_NAME FROM DUAL",
|
||||
}
|
||||
|
||||
type damengQueryFunc func(query string) ([]map[string]interface{}, []string, error)
|
||||
|
||||
@@ -71,3 +71,50 @@ func TestCollectDamengDatabaseNames_ReturnsErrorWhenNoNameResolved(t *testing.T)
|
||||
t.Fatalf("错误不符合预期: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCollectDamengDatabaseNames_IncludesSYSDBA 验证 SYSDBA(达梦默认管理员 schema)
|
||||
// 不会被系统 schema 过滤排除。
|
||||
func TestCollectDamengDatabaseNames_IncludesSYSDBA(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, err := collectDamengDatabaseNames(func(query string) ([]map[string]interface{}, []string, error) {
|
||||
switch query {
|
||||
case damengDatabaseQueries[0]:
|
||||
// 查询 0 返回 SYSDBA(之前会被排除,修复后应该返回)
|
||||
return []map[string]interface{}{{"DATABASE_NAME": "SYSDBA"}}, nil, nil
|
||||
default:
|
||||
return nil, nil, errors.New("permission denied")
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("collectDamengDatabaseNames 返回错误: %v", err)
|
||||
}
|
||||
|
||||
want := []string{"SYSDBA"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("SYSDBA 应该包含在结果中, got=%v want=%v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCollectDamengDatabaseNames_FallbackToCurrentUser 验证当所有查询都失败时
|
||||
// 兜底查询 SELECT USER FROM DUAL 能返回当前用户作为 schema。
|
||||
func TestCollectDamengDatabaseNames_FallbackToCurrentUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
lastQuery := damengDatabaseQueries[len(damengDatabaseQueries)-1]
|
||||
got, err := collectDamengDatabaseNames(func(query string) ([]map[string]interface{}, []string, error) {
|
||||
if query == lastQuery {
|
||||
return []map[string]interface{}{{"DATABASE_NAME": "SYSDBA"}}, nil, nil
|
||||
}
|
||||
// 前面所有查询要么返回空要么报错
|
||||
return []map[string]interface{}{}, nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("collectDamengDatabaseNames 返回错误: %v", err)
|
||||
}
|
||||
|
||||
want := []string{"SYSDBA"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("兜底查询应该返回当前用户, got=%v want=%v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user