diff --git a/dbm-ui/backend/db_services/cmdb/biz.py b/dbm-ui/backend/db_services/cmdb/biz.py index 33845aeee6..5b93a53f57 100644 --- a/dbm-ui/backend/db_services/cmdb/biz.py +++ b/dbm-ui/backend/db_services/cmdb/biz.py @@ -12,9 +12,11 @@ import logging from typing import Dict, List +from django.db.models import Count + from backend.components import CCApi from backend.components.dbconfig.constants import DEPLOY_FILE_NAME, ConfType, LevelName -from backend.db_meta.models import AppCache, DBModule +from backend.db_meta.models import AppCache, Cluster, DBModule from backend.db_services.cmdb.exceptions import BkAppAttrAlreadyExistException from backend.db_services.dbconfig.dataclass import DBBaseConfig, DBConfigLevelData from backend.db_services.dbconfig.handlers import DBConfigHandler @@ -221,3 +223,63 @@ def get_or_create_set_with_name(bk_biz_id: int, bk_set_name: str) -> int: raise err else: return bk_set_id + + +def filter_by_biz_name(data: list, biz_name: str) -> list: + return [biz for biz in data if biz["bk_biz_name"] == biz_name] + + +def filter_by_module_name(data: list, module_name: str) -> list: + result = [] + for biz in data: + filtered_modules = [module for module in biz["modules"] if module["module_name"] == module_name] + if filtered_modules: + new_biz = biz.copy() + new_biz["modules"] = filtered_modules + result.append(new_biz) + return result + + +def list_biz_module_trees(cluster_types: str, bk_biz_name: str, module_name: str) -> List[Dict]: + """ + 获取业务与模块维度集群数量 + """ + + clusters = ( + Cluster.objects.filter(cluster_type__in=cluster_types.split(",")) + .values("db_module_id", "bk_biz_id") + .annotate(count=Count("db_module_id")) + .order_by("-count") + ) + + db_module_map = DBModule.db_module_map() + id_to_name = AppCache.id_to_name() + + nested_data = collections.defaultdict(lambda: {"count": 0, "modules": collections.defaultdict(int)}) + for cluster in clusters: + bk_biz_id = cluster["bk_biz_id"] + db_module_id = cluster["db_module_id"] + count = cluster["count"] + nested_data[bk_biz_id]["count"] += count + nested_data[bk_biz_id]["modules"][db_module_id] = count + + final_data = [] + for bk_biz_id, data in nested_data.items(): + modules = [ + {"module_name": db_module_map.get(module_id), "module_id": module_id, "count": count} + for module_id, count in data["modules"].items() + ] + final_data.append( + { + "bk_biz_name": id_to_name.get(bk_biz_id), + "bk_biz_id": bk_biz_id, + "count": data["count"], + "modules": modules, + } + ) + + if bk_biz_name: + final_data = filter_by_biz_name(final_data, bk_biz_name) + if module_name: + final_data = filter_by_module_name(final_data, module_name) + return final_data diff --git a/dbm-ui/backend/db_services/cmdb/serializers.py b/dbm-ui/backend/db_services/cmdb/serializers.py index aa9e6a078d..f2b4ffd371 100644 --- a/dbm-ui/backend/db_services/cmdb/serializers.py +++ b/dbm-ui/backend/db_services/cmdb/serializers.py @@ -79,3 +79,21 @@ class ListNodesSerializer(TopoSerializer): page = serializers.IntegerField(help_text=_("页数")) module_id = serializers.IntegerField(help_text=_("模块ID"), required=False) set_id = serializers.IntegerField(help_text=_("集群ID"), required=False) + + +class ListBIZModulesSLZ(serializers.Serializer): + cluster_types = serializers.CharField(help_text=_("集群类型(逗号分隔)")) + bk_biz_name = serializers.CharField(help_text=_("业务名称"), required=False) + module_name = serializers.CharField(help_text=_("模块名称"), required=False) + + +class BIZModuleSLZ(serializers.Serializer): + class ModuleClusterCountSLZ(serializers.Serializer): + module_name = serializers.CharField(help_text=_("模块名")) + module_id = serializers.IntegerField(help_text=_("模块ID")) + count = serializers.IntegerField(help_text=_("集群数量")) + + bk_biz_name = serializers.CharField(help_text=_("业务名")) + bk_biz_id = serializers.IntegerField(help_text=_("业务ID")) + count = serializers.IntegerField(help_text=_("集群数量")) + modules = serializers.ListField(help_text=_("模块信息"), child=ModuleClusterCountSLZ()) diff --git a/dbm-ui/backend/db_services/cmdb/urls.py b/dbm-ui/backend/db_services/cmdb/urls.py index 3bbc662c09..bfb5c6e2da 100644 --- a/dbm-ui/backend/db_services/cmdb/urls.py +++ b/dbm-ui/backend/db_services/cmdb/urls.py @@ -16,6 +16,7 @@ urlpatterns = [ path("bizs/", CMDBViewSet.as_view({"get": "list_bizs"})), path("bizs//modules/", CMDBViewSet.as_view({"get": "list_modules"})), + path("biz_module_trees/", CMDBViewSet.as_view({"get": "list_biz_module_trees"})), ] routers = DefaultRouter(trailing_slash=True) diff --git a/dbm-ui/backend/db_services/cmdb/views.py b/dbm-ui/backend/db_services/cmdb/views.py index d56b0f90ed..fcc503cc4a 100644 --- a/dbm-ui/backend/db_services/cmdb/views.py +++ b/dbm-ui/backend/db_services/cmdb/views.py @@ -98,3 +98,19 @@ def set_db_app_abbr(self, request, bk_biz_id): @action(methods=["GET"], detail=True) def list_cc_obj_user(self, request, bk_biz_id): return Response(biz.list_cc_obj_user(bk_biz_id)) + + @common_swagger_auto_schema( + operation_summary=_("业务模块树信息"), + query_serializer=serializers.ListBIZModulesSLZ(), + responses={status.HTTP_200_OK: serializers.BIZModuleSLZ(label=_("业务模块树信息"), many=True)}, + tags=[SWAGGER_TAG], + ) + @action(methods=["GET"], detail=False, serializer_class=serializers.ListBIZModulesSLZ) + def list_biz_module_trees(self, request): + cluster_types = self.params_validate(self.get_serializer_class()).get("cluster_types") + bk_biz_name = self.params_validate(self.get_serializer_class()).get("bk_biz_name") + module_name = self.params_validate(self.get_serializer_class()).get("module_name") + serializer = serializers.BIZModuleSLZ( + biz.list_biz_module_trees(cluster_types, bk_biz_name, module_name), many=True + ) + return Response(serializer.data) diff --git a/dbm-ui/backend/db_services/dbbase/resources/serializers.py b/dbm-ui/backend/db_services/dbbase/resources/serializers.py index 9a7860af51..4942acfc8e 100644 --- a/dbm-ui/backend/db_services/dbbase/resources/serializers.py +++ b/dbm-ui/backend/db_services/dbbase/resources/serializers.py @@ -155,3 +155,10 @@ class ListTendbClusterMachineResourceSLZ(ListMachineSLZ): spider_role = serializers.ChoiceField( help_text=_("spider角色"), choices=TenDBClusterSpiderRole.get_choices(), required=False ) + + +class SearchMySQLResourceSLZ(serializers.Serializer): + cluster_type = serializers.ChoiceField(help_text=_("集群类型"), choices=ClusterType.get_choices(), required=False) + immute_domain = serializers.CharField(help_text=_("访问入口"), required=False) + status = serializers.ChoiceField(help_text=_("状态"), choices=ClusterStatus.get_choices(), required=False) + db_module_id = serializers.IntegerField(help_text=_("模块ID"), required=False) diff --git a/dbm-ui/backend/db_services/mysql/resources/urls.py b/dbm-ui/backend/db_services/mysql/resources/urls.py index 902625f7c8..5cd75c1bef 100644 --- a/dbm-ui/backend/db_services/mysql/resources/urls.py +++ b/dbm-ui/backend/db_services/mysql/resources/urls.py @@ -14,7 +14,7 @@ from .tendbcluster.views import SpiderViewSet from .tendbha.views import DBHAViewSet from .tendbsingle.views import DBSingleViewSet -from .views import ListResourceViewSet, ResourceTreeViewSet +from .views import ListResourceViewSet, ResourceTreeViewSet, TendbResourceViewSet router = DefaultRouter(trailing_slash=True) @@ -26,6 +26,7 @@ # 提供资源(集群)通用属性的查询, 如集群名, 集群创建者等 path("resources/", ListResourceViewSet.as_view({"get": "list"})), path("resource_tree/", ResourceTreeViewSet.as_view({"get": "get_resource_tree"})), + path("tendb_resource/", TendbResourceViewSet.as_view({"get": "get_tendb_resource"})), ] urlpatterns += router.urls diff --git a/dbm-ui/backend/db_services/mysql/resources/views.py b/dbm-ui/backend/db_services/mysql/resources/views.py index cef3294c12..73ced5b13a 100644 --- a/dbm-ui/backend/db_services/mysql/resources/views.py +++ b/dbm-ui/backend/db_services/mysql/resources/views.py @@ -17,10 +17,11 @@ from backend.bk_web.swagger import common_swagger_auto_schema from backend.bk_web.viewsets import SystemViewSet +from backend.db_meta.enums import ClusterType from backend.db_meta.models.cluster import Cluster from backend.db_meta.models.db_module import DBModule from backend.db_services.dbbase.resources.constants import ResourceNodeType -from backend.db_services.dbbase.resources.serializers import SearchResourceTreeSLZ +from backend.db_services.dbbase.resources.serializers import SearchMySQLResourceSLZ, SearchResourceTreeSLZ from backend.db_services.dbbase.resources.views import BaseListResourceViewSet from backend.db_services.dbbase.resources.yasg_slz import ResourceTreeSLZ from backend.db_services.mysql.resources import constants @@ -84,3 +85,37 @@ def get_resource_tree(self, request, bk_biz_id): for db_module in db_module_qset ] return Response(tree) + + +class TendbResourceViewSet(SystemViewSet): + pagination_class = None + + action_permission_map = {} + default_permission_class = [DBManagePermission()] + + @common_swagger_auto_schema( + operation_summary=_("获取tendb集群"), + query_serializer=SearchMySQLResourceSLZ(), + tags=[constants.RESOURCE_TAG], + ) + def get_tendb_resource(self, request, bk_biz_id): + cluster_type = self.params_validate(SearchMySQLResourceSLZ).get("cluster_type") + immute_domain = self.params_validate(SearchMySQLResourceSLZ).get("immute_domain") + status = self.params_validate(SearchMySQLResourceSLZ).get("status") + db_module_id = self.params_validate(SearchMySQLResourceSLZ).get("db_module_id") + + query_params = {"bk_biz_id": bk_biz_id} + query_params.update({"cluster_type": cluster_type}) if cluster_type else None + query_params.update({"immute_domain": immute_domain}) if immute_domain else None + query_params.update({"status": status}) if status else None + query_params.update({"db_module_id": db_module_id}) if db_module_id else None + + cluster_qset = Cluster.objects.filter(cluster_type__in=[ClusterType.TenDBSingle, ClusterType.TenDBHA]).filter( + **query_params + ) + db_module_map = DBModule.db_module_map() + data_list = [cluster.to_dict() for cluster in cluster_qset] + for data in data_list: + data["db_module_name"] = db_module_map.get(data["db_module_id"]) + + return Response(data_list)