diff --git a/sql/db_diagnostic.py b/sql/db_diagnostic.py index ca01b0bc96..b163c09b95 100644 --- a/sql/db_diagnostic.py +++ b/sql/db_diagnostic.py @@ -22,6 +22,9 @@ def process(request): instance_name = request.POST.get("instance_name") command_type = request.POST.get("command_type") + request_kwargs = { + key: value for key, value in request.POST.items() if key != "command_type" + } try: instance = user_instances(request.user).get(instance_name=instance_name) @@ -31,21 +34,8 @@ def process(request): query_engine = get_engine(instance=instance) query_result = None - if instance.db_type == "mysql": - query_result = query_engine.processlist(command_type) - - elif instance.db_type == "mongo": - query_result = query_engine.current_op(command_type) - elif instance.db_type == "oracle": - query_result = query_engine.session_list(command_type) - else: - result = { - "status": 1, - "msg": "暂时不支持{}类型数据库的进程列表查询".format(instance.db_type), - "data": [], - } - return HttpResponse(json.dumps(result), content_type="application/json") - + # processlist方法已提升为父类方法,简化此处的逻辑。进程添加新数据库支持时,改前端即可。 + query_result = query_engine.processlist(command_type=command_type, **request_kwargs) if query_result: if not query_result.error: processlist = query_result.to_dict() diff --git a/sql/engines/__init__.py b/sql/engines/__init__.py index 09ee6f1e2b..939a100b13 100644 --- a/sql/engines/__init__.py +++ b/sql/engines/__init__.py @@ -103,6 +103,10 @@ def server_version(self): """返回引擎服务器版本,返回对象为tuple (x,y,z)""" return tuple() + def processlist(self, command_type, **kwargs) -> ResultSet: + """获取连接信息""" + return ResultSet() + def kill_connection(self, thread_id): """终止数据库连接""" diff --git a/sql/engines/cloud/aliyun_rds.py b/sql/engines/cloud/aliyun_rds.py index efb13eaf41..4f4b81f1cc 100644 --- a/sql/engines/cloud/aliyun_rds.py +++ b/sql/engines/cloud/aliyun_rds.py @@ -15,7 +15,7 @@ def __init__(self, instance=None): self.instance_name = instance.instance_name # 将sql/aliyun_rds.py的函数迁移值此 - def processlist(self, command_type): + def processlist(self, command_type, **kwargs): if command_type is None or command_type == "": command_type = "Query" diff --git a/sql/engines/mongo.py b/sql/engines/mongo.py index a989067941..fe491c6057 100644 --- a/sql/engines/mongo.py +++ b/sql/engines/mongo.py @@ -1210,7 +1210,7 @@ def fill_query_columns(cursor, columns): cols.append(key) return cols - def current_op(self, command_type): + def processlist(self, command_type, **kwargs): """ 获取当前连接信息 diff --git a/sql/engines/mysql.py b/sql/engines/mysql.py index 369931791b..d47b2b8a92 100644 --- a/sql/engines/mysql.py +++ b/sql/engines/mysql.py @@ -762,7 +762,7 @@ def osc_control(self, **kwargs): """ return self.inc_engine.osc_control(**kwargs) - def processlist(self, command_type): + def processlist(self, command_type, **kwargs): """获取连接信息""" base_sql = "select id, user, host, db, command, time, state, ifnull(info,'') as info from information_schema.processlist" # escape diff --git a/sql/engines/oracle.py b/sql/engines/oracle.py index 747f84f8d1..4345431e1e 100644 --- a/sql/engines/oracle.py +++ b/sql/engines/oracle.py @@ -1451,7 +1451,7 @@ def execute(self, db_name=None, sql="", close_conn=True, parameters=None): self.close() return result - def session_list(self, command_type): + def processlist(self, command_type, **kwargs): """获取会话信息""" base_sql = """select s.sid, diff --git a/sql/engines/redis.py b/sql/engines/redis.py index c22cf73b75..1f839988b2 100644 --- a/sql/engines/redis.py +++ b/sql/engines/redis.py @@ -131,6 +131,21 @@ def query_check(self, db_name=None, sql="", limit_num=0): result["msg"] = "禁止执行该命令!" return result + def processlist(self, command_type, **kwargs): + """获取连接信息""" + sql = "client list" + result_set = ResultSet(full_sql=sql) + conn = self.get_connection(db_name=0) + clients = conn.client_list() + # 根据空闲时间排序 + sort_by = "idle" + reverse = False + clients = sorted( + clients, key=lambda client: client.get(sort_by), reverse=reverse + ) + result_set.rows = clients + return result_set + def query(self, db_name=None, sql="", limit_num=0, close_conn=True, **kwargs): """返回 ResultSet""" result_set = ResultSet(full_sql=sql) diff --git a/sql/engines/tests.py b/sql/engines/tests.py index eea86f31c3..4638708213 100644 --- a/sql/engines/tests.py +++ b/sql/engines/tests.py @@ -487,6 +487,43 @@ def test_execute_workflow_success(self, _execute_command): self.assertIsInstance(execute_result, ReviewSet) self.assertEqual(execute_result.rows[0].__dict__.keys(), row.__dict__.keys()) + @patch("sql.engines.redis.RedisEngine.get_connection") + def test_processlist(self, mock_get_connection): + """测试 processlist 方法,模拟获取连接并返回客户端列表""" + + # 模拟 Redis 连接的客户端列表 + mock_conn = Mock() + + return_value_mock = [ + {"id": "1", "idle": 10, "name": "client_1"}, + {"id": "2", "idle": 5, "name": "client_2"}, + {"id": "3", "idle": 20, "name": "client_3"}, + ] + mock_conn.client_list.return_value = return_value_mock + + # 设置 get_connection 返回模拟连接 + mock_get_connection.return_value = mock_conn + + # 创建 RedisEngine 实例 + new_engine = RedisEngine(instance=self.ins) + + # 调用 processlist 方法并测试其返回值 + command_types = ["All"] # 假设支持的命令类型 + for command_type in command_types: + result_set = new_engine.processlist(command_type=command_type) + + # 验证返回值是 ResultSet 实例 + self.assertIsInstance(result_set, ResultSet) + + # 验证返回的客户端列表被正确排序 + sorted_clients = sorted( + return_value_mock, key=lambda client: client.get("idle"), reverse=False + ) + self.assertEqual(result_set.rows, sorted_clients) + + # 验证 get_connection 是否被调用 + mock_get_connection.assert_called() + class TestPgSQL(TestCase): @classmethod @@ -1497,11 +1534,11 @@ def test_execute(self, _connect, _cursor, _execute): self.assertIsInstance(execute_result, ResultSet) @patch("sql.engines.oracle.OracleEngine.query") - def test_session_list(self, _query): + def test_processlist(self, _query): new_engine = OracleEngine(instance=self.ins) _query.return_value = ResultSet() for command_type in ["All", "Active", "Others"]: - r = new_engine.session_list(command_type) + r = new_engine.processlist(command_type) self.assertIsInstance(r, ResultSet) @patch("sql.engines.oracle.OracleEngine.query") @@ -1803,7 +1840,7 @@ def test_fill_query_columns(self): self.assertEqual(cols, ["_id", "title", "tags", "likes", "text", "author"]) @patch("sql.engines.mongo.MongoEngine.get_connection") - def test_current_op(self, mock_get_connection): + def test_processlist(self, mock_get_connection): class Aggregate: def __enter__(self): yield {"client": "single_client"} @@ -1817,7 +1854,7 @@ def __exit__(self, *arg, **kwargs): mock_get_connection.return_value = mock_conn command_types = ["Full", "All", "Inner", "Active"] for command_type in command_types: - result_set = self.engine.current_op(command_type) + result_set = self.engine.processlist(command_type) self.assertIsInstance(result_set, ResultSet) @patch("sql.engines.mongo.MongoEngine.get_connection") diff --git a/sql/templates/dbdiagnostic.html b/sql/templates/dbdiagnostic.html index eab4ee6933..76eb34aaf1 100644 --- a/sql/templates/dbdiagnostic.html +++ b/sql/templates/dbdiagnostic.html @@ -27,6 +27,7 @@ +
@@ -326,6 +327,111 @@ return html.join(''); } ] + ,[ + 'redis', + ["All"], + [{ + title: '', // 用于多选框 + field: 'checkbox', + checkbox: true + }, { + title: 'Id', + field: 'id', + sortable: true + }, { + title: '远程地址', + field: 'addr', + sortable: true + }, { + title: '本地地址', + field: 'laddr', + sortable: true + }, { + title: '客户端名称', + field: 'name', + sortable: true + }, { + title: '用户', + field: 'user', + sortable: true + }, + { + title: '数据库', + field: 'db', + sortable: true + }, { + title: '连接耗时(秒)', + field: 'age', + sortable: true + }, { + title: '空闲时间(秒)', + field: 'idle', + sortable: true + }, { + title: '命令', + field: 'cmd', + sortable: true + }, { + title: '总内存', + field: 'tot-mem', + sortable: true + },{ + title: '输出内存', + field: 'omem', + sortable: true + }, { + title: '标志', + field: 'flags', + sortable: true + },{ + title: '文件描述符', + field: 'fd', + sortable: true + },{ + title: '订阅数', + field: 'sub', + sortable: true + }, { + title: '模式订阅数', + field: 'psub', + sortable: true + }, { + title: 'MULTI 队列长度', + field: 'multi', + sortable: true + }, { + title: '查询缓冲区', + field: 'qbuf', + sortable: true + }, { + title: '查询缓冲区空闲', + field: 'qbuf-free', + sortable: true + }, { + title: '参数内存', + field: 'argv-mem', + sortable: true + }, { + title: '输出缓冲区长度', + field: 'obl', + sortable: true + }, { + title: '输出链长度', + field: 'oll', + sortable: true + }, { + title: '事件文件', + field: 'events', + sortable: true + }, { + title: '重定向', + field: 'redir', + sortable: true + }], + function (index, row) { + var html = []; + } + ] ] @@ -931,12 +1037,14 @@ $(document).ready(function () { //获取用户实例列表 $(function () { + // 会话管理-支持的数据库类型 + supportedDbType=['mysql','mongo', 'oracle','redis'] $.ajax({ type: "get", url: "/group/user_all_instances/", dataType: "json", data: { - db_type: ['mysql','mongo', 'oracle'] + db_type: supportedDbType }, complete: function () { //如果已选择instance_name,进入页面自动填充,并且重置激活id @@ -951,20 +1059,13 @@ if (data.status === 0) { let result = data['data']; allInstances = result; - // $("#instance_name").empty(); - $("#optgroup-mysql").empty(); - $("#optgroup-mongo").empty(); - $("#optgroup-oracle").empty(); + supportedDbType.forEach(function(db) { + $("#optgroup-" + db).empty(); + }); for (let i = 0; i < result.length; i++) { let instance = ""; - // $("#instance_name").append(instance); - if (result[i]['db_type'] === 'mysql') { - $("#optgroup-mysql").append(instance); - } else if (result[i]['db_type'] === 'mongo') { - $("#optgroup-mongo").append(instance); - } else if (result[i]['db_type'] === 'oracle') { - $("#optgroup-oracle").append(instance); - } + var dbType = result[i]['db_type']; + $("#optgroup-" + dbType).append(instance); } $('#instance_name').selectpicker('render'); $('#instance_name').selectpicker('refresh');