diff --git a/cmdb-api/api/commands/click_cmdb.py b/cmdb-api/api/commands/click_cmdb.py index 14fb0c1..9b2264e 100644 --- a/cmdb-api/api/commands/click_cmdb.py +++ b/cmdb-api/api/commands/click_cmdb.py @@ -128,7 +128,7 @@ def cmdb_init_acl(): perms = [PermEnum.READ] elif resource_type == ResourceTypeEnum.CI_TYPE_RELATION: perms = [PermEnum.ADD, PermEnum.DELETE, PermEnum.GRANT] - elif resource_type == ResourceTypeEnum.RELATION_VIEW: + elif resource_type in (ResourceTypeEnum.RELATION_VIEW, ResourceTypeEnum.TOPOLOGY_VIEW): perms = [PermEnum.READ, PermEnum.UPDATE, PermEnum.DELETE, PermEnum.GRANT] ResourceTypeCRUD.add(app_id, resource_type, '', perms) diff --git a/cmdb-api/api/lib/cmdb/ci.py b/cmdb-api/api/lib/cmdb/ci.py index 62290cd..d3f0875 100644 --- a/cmdb-api/api/lib/cmdb/ci.py +++ b/cmdb-api/api/lib/cmdb/ci.py @@ -1090,6 +1090,18 @@ class CIRelationManager(object): return ci_ids, level2ids + @classmethod + def get_parent_ids(cls, ci_ids): + cis = db.session.query(CIRelation.first_ci_id, CIRelation.second_ci_id, CI.type_id).join( + CI, CI.id == CIRelation.first_ci_id).filter( + CIRelation.second_ci_id.in_(ci_ids)).filter(CIRelation.deleted.is_(False)) + + result = {} + for ci in cis: + result.setdefault(ci.second_ci_id, []).append((ci.first_ci_id, ci.type_id)) + + return result + @staticmethod def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation): db.session.commit() diff --git a/cmdb-api/api/lib/cmdb/ci_type.py b/cmdb-api/api/lib/cmdb/ci_type.py index 06d4eb3..a0cf7d7 100644 --- a/cmdb-api/api/lib/cmdb/ci_type.py +++ b/cmdb-api/api/lib/cmdb/ci_type.py @@ -49,6 +49,7 @@ from api.models.cmdb import CITypeTrigger from api.models.cmdb import CITypeUniqueConstraint from api.models.cmdb import CustomDashboard from api.models.cmdb import PreferenceCITypeOrder +from api.models.cmdb import TopologyView from api.models.cmdb import PreferenceRelationView from api.models.cmdb import PreferenceSearchOption from api.models.cmdb import PreferenceShowAttributes @@ -263,6 +264,9 @@ class CITypeManager(object): except Exception: pass + for item in TopologyView.get_by(central_node_type=type_id, to_dict=False): + item.delete(commit=False) + db.session.commit() ci_type.soft_delete() @@ -825,6 +829,70 @@ class CITypeRelationManager(object): return [cls._wrap_relation_type_dict(parent.parent_id, parent) for parent in parents] + @staticmethod + def get_relations_by_type_id(type_id): + nodes, edges = [], [] + node_ids, edge_tuples = set(), set() + ci_type = CITypeCache.get(type_id) + if ci_type is None: + return nodes, edges + + nodes.append(ci_type.to_dict()) + node_ids.add(ci_type.id) + + def _find(_id, lv): + lv += 1 + for i in CITypeRelation.get_by(parent_id=_id, to_dict=False): + if i.child_id not in node_ids: + node_ids.add(i.child_id) + node = i.child.to_dict() + node.setdefault('level', []).append(lv) + nodes.append(node) + + edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=False)) + edge_tuples.add((i.parent_id, i.child_id)) + + _find(i.child_id, lv) + continue + elif (i.parent_id, i.child_id) not in edge_tuples: + edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=False)) + edge_tuples.add((i.parent_id, i.child_id)) + _find(i.child_id, lv) + + for _node in nodes: + if _node['id'] == i.child_id and lv not in _node['level']: + _node['level'].append(lv) + + def _reverse_find(_id, lv): + lv -= 1 + for i in CITypeRelation.get_by(child_id=_id, to_dict=False): + if i.parent_id not in node_ids: + node_ids.add(i.parent_id) + node = i.parent.to_dict() + node.setdefault('level', []).append(lv) + nodes.append(node) + + edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=True)) + edge_tuples.add((i.parent_id, i.child_id)) + + _reverse_find(i.parent_id, lv) + continue + elif (i.parent_id, i.child_id) not in edge_tuples: + edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=True)) + edge_tuples.add((i.parent_id, i.child_id)) + _reverse_find(i.parent_id, lv) + + for _node in nodes: + if _node['id'] == i.child_id and lv not in _node['level']: + _node['level'].append(lv) + + level = 0 + _reverse_find(ci_type.id, level) + level = 0 + _find(ci_type.id, level) + + return nodes, edges + @staticmethod def _get(parent_id, child_id): return CITypeRelation.get_by(parent_id=parent_id, diff --git a/cmdb-api/api/lib/cmdb/const.py b/cmdb-api/api/lib/cmdb/const.py index 376eafb..6e44438 100644 --- a/cmdb-api/api/lib/cmdb/const.py +++ b/cmdb-api/api/lib/cmdb/const.py @@ -73,6 +73,7 @@ class ResourceTypeEnum(BaseEnum): RELATION_VIEW = "RelationView" # read/update/delete/grant CI_FILTER = "CIFilter" # read PAGE = "page" # read + TOPOLOGY_VIEW = "TopologyView" # read/update/delete/grant class PermEnum(BaseEnum): diff --git a/cmdb-api/api/lib/cmdb/preference.py b/cmdb-api/api/lib/cmdb/preference.py index abd9cc6..9d8d0ba 100644 --- a/cmdb-api/api/lib/cmdb/preference.py +++ b/cmdb-api/api/lib/cmdb/preference.py @@ -25,14 +25,15 @@ from api.lib.cmdb.resp_format import ErrFormat from api.lib.exception import AbortException from api.lib.perm.acl.acl import ACLManager from api.models.cmdb import CITypeAttribute +from api.models.cmdb import CITypeGroup +from api.models.cmdb import CITypeGroupItem from api.models.cmdb import CITypeRelation from api.models.cmdb import PreferenceCITypeOrder from api.models.cmdb import PreferenceRelationView from api.models.cmdb import PreferenceSearchOption from api.models.cmdb import PreferenceShowAttributes from api.models.cmdb import PreferenceTreeView -from api.models.cmdb import CITypeGroup -from api.models.cmdb import CITypeGroupItem + class PreferenceManager(object): pref_attr_cls = PreferenceShowAttributes @@ -85,7 +86,6 @@ class PreferenceManager(object): return dict(group_types=group_types, tree_types=tree_types, type_ids=list(type_ids)) - @staticmethod def get_types2(instance=False, tree=False): """ diff --git a/cmdb-api/api/lib/cmdb/resp_format.py b/cmdb-api/api/lib/cmdb/resp_format.py index 98c11c9..39418f5 100644 --- a/cmdb-api/api/lib/cmdb/resp_format.py +++ b/cmdb-api/api/lib/cmdb/resp_format.py @@ -144,3 +144,8 @@ class ErrFormat(CommonErrFormat): cron_time_format_invalid = _l("Scheduling time format error") # 调度时间格式错误 reconciliation_title = _l("CMDB data reconciliation results") # CMDB数据合规检查结果 reconciliation_body = _l("Number of {} illegal: {}") # "{} 不合规数: {}" + + topology_exists = _l("Topology view {} already exists") # 拓扑视图 {} 已经存在 + topology_group_exists = _l("Topology group {} already exists") # 拓扑视图分组 {} 已经存在 + # 因为该分组下定义了拓扑视图,不能删除 + topo_view_exists_cannot_delete_group = _l("The group cannot be deleted because the topology view already exists") diff --git a/cmdb-api/api/lib/cmdb/search/ci/__init__.py b/cmdb-api/api/lib/cmdb/search/ci/__init__.py index f4a2b18..c5ec155 100644 --- a/cmdb-api/api/lib/cmdb/search/ci/__init__.py +++ b/cmdb-api/api/lib/cmdb/search/ci/__init__.py @@ -17,10 +17,12 @@ def search(query=None, count=1, sort=None, excludes=None, - use_id_filter=False): + use_id_filter=False, + use_ci_filter=True): if current_app.config.get("USE_ES"): s = SearchFromES(query, fl, facet, page, ret_key, count, sort) else: - s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes, use_id_filter=use_id_filter) + s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes, + use_id_filter=use_id_filter, use_ci_filter=use_ci_filter) return s diff --git a/cmdb-api/api/lib/cmdb/search/ci/db/search.py b/cmdb-api/api/lib/cmdb/search/ci/db/search.py index 4fb3b50..766413c 100644 --- a/cmdb-api/api/lib/cmdb/search/ci/db/search.py +++ b/cmdb-api/api/lib/cmdb/search/ci/db/search.py @@ -70,6 +70,7 @@ class Search(object): self.valid_type_names = [] self.type2filter_perms = dict() self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker" + self.is_app_admin = self.is_app_admin or (not self.use_ci_filter and not self.use_id_filter) @staticmethod def _operator_proc(key): diff --git a/cmdb-api/api/lib/cmdb/topology.py b/cmdb-api/api/lib/cmdb/topology.py new file mode 100644 index 0000000..134f00c --- /dev/null +++ b/cmdb-api/api/lib/cmdb/topology.py @@ -0,0 +1,251 @@ +# -*- coding:utf-8 -*- + +import json + +from flask import abort +from flask import current_app +from flask_login import current_user +from werkzeug.exceptions import BadRequest + +from api.extensions import rd +from api.lib.cmdb.cache import AttributeCache +from api.lib.cmdb.cache import CITypeCache +from api.lib.cmdb.ci import CIRelationManager +from api.lib.cmdb.ci_type import CITypeRelationManager +from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION +from api.lib.cmdb.const import ResourceTypeEnum +from api.lib.cmdb.resp_format import ErrFormat +from api.lib.cmdb.search import SearchError +from api.lib.cmdb.search.ci import search +from api.lib.perm.acl.acl import ACLManager +from api.lib.perm.acl.acl import is_app_admin +from api.models.cmdb import TopologyView +from api.models.cmdb import TopologyViewGroup + + +class TopologyViewManager(object): + group_cls = TopologyViewGroup + cls = TopologyView + + @classmethod + def get_name_by_id(cls, _id): + res = cls.cls.get_by_id(_id) + return res and res.name + + def get_view_by_id(self, _id): + res = self.cls.get_by_id(_id) + + return res and res.to_dict() or {} + + @classmethod + def add_group(cls, name, order): + if order is None: + cur_max_order = cls.group_cls.get_by(only_query=True).order_by(cls.group_cls.order.desc()).first() + cur_max_order = cur_max_order and cur_max_order.order or 0 + order = cur_max_order + 1 + + cls.group_cls.get_by(name=name, first=True, to_dict=False) and abort( + 400, ErrFormat.topology_group_exists.format(name)) + + return cls.group_cls.create(name=name, order=order) + + def update_group(self, group_id, name, view_ids): + existed = self.group_cls.get_by_id(group_id) or abort(404, ErrFormat.not_found) + if name is not None and name != existed.name: + existed.update(name=name) + + for idx, view_id in enumerate(view_ids): + view = self.cls.get_by_id(view_id) + if view is not None: + view.update(group_id=group_id, order=idx) + + return existed.to_dict() + + @classmethod + def delete_group(cls, _id): + existed = cls.group_cls.get_by_id(_id) or abort(404, ErrFormat.not_found) + + if cls.cls.get_by(group_id=_id, first=True): + return abort(400, ErrFormat.topo_view_exists_cannot_delete_group) + + existed.soft_delete() + + @classmethod + def group_order(cls, group_ids): + for idx, group_id in enumerate(group_ids): + group = cls.group_cls.get_by_id(group_id) + group.update(order=idx + 1) + + @classmethod + def add(cls, name, group_id, option, order=None, **kwargs): + cls.cls.get_by(name=name, first=True) and abort(400, ErrFormat.topology_exists.format(name)) + if order is None: + cur_max_order = cls.cls.get_by(group_id=group_id, only_query=True).order_by( + cls.cls.order.desc()).first() + cur_max_order = cur_max_order and cur_max_order.order or 0 + order = cur_max_order + 1 + + inst = cls.cls.create(name=name, group_id=group_id, option=option, order=order, **kwargs).to_dict() + if current_app.config.get('USE_ACL'): + try: + ACLManager().add_resource(name, ResourceTypeEnum.TOPOLOGY_VIEW) + except BadRequest: + pass + + ACLManager().grant_resource_to_role(name, + current_user.username, + ResourceTypeEnum.TOPOLOGY_VIEW) + + return inst + + @classmethod + def update(cls, _id, **kwargs): + existed = cls.cls.get_by_id(_id) or abort(404, ErrFormat.not_found) + existed_name = existed.name + + inst = existed.update(filter_none=False, **kwargs).to_dict() + if current_app.config.get('USE_ACL') and existed_name != kwargs.get('name') and kwargs.get('name'): + try: + ACLManager().update_resource(existed_name, kwargs['name'], ResourceTypeEnum.TOPOLOGY_VIEW) + except BadRequest: + pass + + return inst + + @classmethod + def delete(cls, _id): + existed = cls.cls.get_by_id(_id) or abort(404, ErrFormat.not_found) + + existed.soft_delete() + if current_app.config.get("USE_ACL"): + ACLManager().del_resource(existed.name, ResourceTypeEnum.TOPOLOGY_VIEW) + + @classmethod + def group_inner_order(cls, _ids): + for idx, _id in enumerate(_ids): + topology = cls.cls.get_by_id(_id) + topology.update(order=idx + 1) + + @classmethod + def get_all(cls): + resources = None + if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'): + resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.TOPOLOGY_VIEW)]) + + groups = cls.group_cls.get_by(to_dict=True) + groups = sorted(groups, key=lambda x: x['order']) + group2pos = {group['id']: idx for idx, group in enumerate(groups)} + + topo_views = sorted(cls.cls.get_by(to_dict=True), key=lambda x: x['order']) + other_group = dict(views=[]) + for view in topo_views: + if resources is not None and view['name'] not in resources: + continue + + if view['group_id']: + groups[group2pos[view['group_id']]].setdefault('views', []).append(view) + else: + other_group['views'].append(view) + + if other_group['views']: + groups.append(other_group) + + return groups + + @staticmethod + def relation_from_ci_type(type_id): + nodes, edges = CITypeRelationManager.get_relations_by_type_id(type_id) + + return dict(nodes=nodes, edges=edges) + + def topology_view(self, view_id=None, preview=None): + if view_id is not None: + view = self.cls.get_by_id(view_id) or abort(404, ErrFormat.not_found) + central_node_type, central_node_instances, path = (view.central_node_type, + view.central_node_instances, view.path) + else: + central_node_type = preview.get('central_node_type') + central_node_instances = preview.get('central_node_instances') + path = preview.get('path') + + nodes, links = [], [] + _type = CITypeCache.get(central_node_type) + if not _type: + return dict(nodes=nodes, links=links) + root_ids = [] + show_key = AttributeCache.get(_type.show_id or _type.unique_id) + + q = (central_node_instances[2:] if central_node_instances.startswith('q=') else + central_node_instances) + s = search(q, fl=['_id', show_key.name], use_id_filter=False, use_ci_filter=False, count=1000000) + try: + response, _, _, _, _, _ = s.search() + except SearchError as e: + current_app.logger.info(e) + return dict(nodes=nodes, links=links) + for i in response: + root_ids.append(i['_id']) + nodes.append(dict(id=str(i['_id']), name=i[show_key.name], type_id=central_node_type)) + if not root_ids: + return dict(nodes=nodes, links=links) + + prefix = REDIS_PREFIX_CI_RELATION + key = list(map(str, root_ids)) + id2node = {} + type2meta = {} + for level in sorted([i for i in path.keys() if int(i) > 0]): + type_ids = {int(i) for i in path[level]} + + res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]] + new_key = [] + for idx, from_id in enumerate(key): + for to_id, type_id in res[idx]: + if type_id in type_ids: + links.append({'from': from_id, 'to': to_id}) + id2node[to_id] = {'id': to_id, 'type_id': type_id} + new_key.append(to_id) + if type_id not in type2meta: + type2meta[type_id] = CITypeCache.get(type_id).icon + + key = new_key + + ci_ids = list(map(int, root_ids)) + for level in sorted([i for i in path.keys() if int(i) < 0]): + type_ids = {int(i) for i in path[level]} + res = CIRelationManager.get_parent_ids(ci_ids) + _ci_ids = [] + for to_id in res: + for from_id, type_id in res[to_id]: + if type_id in type_ids: + from_id, to_id = str(from_id), str(to_id) + links.append({'from': from_id, 'to': to_id}) + id2node[from_id] = {'id': str(from_id), 'type_id': type_id} + _ci_ids.append(from_id) + if type_id not in type2meta: + type2meta[type_id] = CITypeCache.get(type_id).icon + + ci_ids = _ci_ids + + fl = set() + type_ids = {t for lv in path if lv != '0' for t in path[lv]} + type2show = {} + for type_id in type_ids: + ci_type = CITypeCache.get(type_id) + if ci_type: + attr = AttributeCache.get(ci_type.show_id or ci_type.unique_id) + if attr: + fl.add(attr.name) + type2show[type_id] = attr.name + + if id2node: + s = search("_id:({})".format(';'.join(id2node.keys())), fl=list(fl), + use_id_filter=False, use_ci_filter=False, count=1000000) + try: + response, _, _, _, _, _ = s.search() + except SearchError: + return dict(nodes=nodes, links=links) + for i in response: + id2node[str(i['_id'])]['name'] = i[type2show[str(i['_type'])]] + nodes.extend(id2node.values()) + + return dict(nodes=nodes, links=links, type2meta=type2meta) diff --git a/cmdb-api/api/models/cmdb.py b/cmdb-api/api/models/cmdb.py index f47ed1a..2289ef3 100644 --- a/cmdb-api/api/models/cmdb.py +++ b/cmdb-api/api/models/cmdb.py @@ -209,6 +209,26 @@ class CITriggerHistory(Model): webhook = db.Column(db.Text) +class TopologyViewGroup(Model): + __tablename__ = 'c_topology_view_groups' + + name = db.Column(db.String(64), index=True) + order = db.Column(db.Integer, default=0) + + +class TopologyView(Model): + __tablename__ = 'c_topology_views' + + name = db.Column(db.String(64), index=True) + group_id = db.Column(db.Integer, db.ForeignKey('c_topology_view_groups.id')) + category = db.Column(db.String(32)) + central_node_type = db.Column(db.Integer) + central_node_instances = db.Column(db.Text) + path = db.Column(db.JSON) + order = db.Column(db.Integer, default=0) + option = db.Column(db.JSON) + + class CITypeUniqueConstraint(Model): __tablename__ = "c_c_t_u_c" diff --git a/cmdb-api/api/tasks/cmdb.py b/cmdb-api/api/tasks/cmdb.py index 6586909..78c1788 100644 --- a/cmdb-api/api/tasks/cmdb.py +++ b/cmdb-api/api/tasks/cmdb.py @@ -113,18 +113,17 @@ def ci_delete_trigger(trigger, operate_type, ci_dict): @reconnect_db def ci_relation_cache(parent_id, child_id, ancestor_ids): with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)): - if ancestor_ids is None: - children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0] - children = json.loads(children) if children is not None else {} + children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0] + children = json.loads(children) if children is not None else {} - cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids, - first=True, to_dict=False) - if str(child_id) not in children: - children[str(child_id)] = cr.second_ci.type_id + cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids, + first=True, to_dict=False) + if str(child_id) not in children: + children[str(child_id)] = cr.second_ci.type_id - rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION) + rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION) - else: + if ancestor_ids is not None: key = "{},{}".format(ancestor_ids, parent_id) grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0] grandson = json.loads(grandson) if grandson is not None else {} @@ -191,16 +190,15 @@ def ci_relation_add(parent_dict, child_id, uid): @reconnect_db def ci_relation_delete(parent_id, child_id, ancestor_ids): with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)): - if ancestor_ids is None: - children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0] - children = json.loads(children) if children is not None else {} + children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0] + children = json.loads(children) if children is not None else {} - if str(child_id) in children: - children.pop(str(child_id)) + if str(child_id) in children: + children.pop(str(child_id)) - rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION) + rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION) - else: + if ancestor_ids is not None: key = "{},{}".format(ancestor_ids, parent_id) grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0] grandson = json.loads(grandson) if grandson is not None else {} diff --git a/cmdb-api/api/views/cmdb/topology.py b/cmdb-api/api/views/cmdb/topology.py new file mode 100644 index 0000000..988e005 --- /dev/null +++ b/cmdb-api/api/views/cmdb/topology.py @@ -0,0 +1,174 @@ +# -*- coding:utf-8 -*- + +from flask import abort +from flask import request + +from api.lib.cmdb.const import PermEnum, ResourceTypeEnum +from api.lib.cmdb.resp_format import ErrFormat +from api.lib.cmdb.topology import TopologyViewManager +from api.lib.common_setting.decorator import perms_role_required +from api.lib.common_setting.role_perm_base import CMDBApp +from api.lib.decorator import args_required +from api.lib.decorator import args_validate +from api.lib.perm.acl.acl import ACLManager +from api.lib.perm.acl.acl import has_perm_from_args +from api.lib.perm.acl.acl import is_app_admin +from api.resource import APIView + +app_cli = CMDBApp() + + +class TopologyGroupView(APIView): + url_prefix = ('/topology_views/groups', '/topology_views/groups/') + + @args_required('name') + @args_validate(TopologyViewManager.group_cls) + @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView, + app_cli.op.create_topology_group, app_cli.admin_name) + def post(self): + name = request.values.get('name') + order = request.values.get('order') + + group = TopologyViewManager.add_group(name, order) + + return self.jsonify(group.to_dict()) + + @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView, + app_cli.op.update_topology_group, app_cli.admin_name) + def put(self, group_id): + name = request.values.get('name') + view_ids = request.values.get('view_ids') + group = TopologyViewManager().update_group(group_id, name, view_ids) + + return self.jsonify(**group) + + @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView, + app_cli.op.delete_topology_group, app_cli.admin_name) + def delete(self, group_id): + TopologyViewManager.delete_group(group_id) + + return self.jsonify(group_id=group_id) + + +class TopologyGroupOrderView(APIView): + url_prefix = ('/topology_views/groups/order',) + + @args_required('group_ids') + @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView, + app_cli.op.update_topology_group, app_cli.admin_name) + def post(self): + group_ids = request.values.get('group_ids') + + TopologyViewManager.group_order(group_ids) + + return self.jsonify(group_ids=group_ids) + + def put(self): + return self.post() + + +class TopologyView(APIView): + url_prefix = ('/topology_views', '/topology_views/relations/ci_types/', '/topology_views/') + + def get(self, type_id=None, _id=None): + if type_id is not None: + return self.jsonify(TopologyViewManager.relation_from_ci_type(type_id)) + + if _id is not None: + return self.jsonify(TopologyViewManager().get_view_by_id(_id)) + + return self.jsonify(TopologyViewManager.get_all()) + + @args_required('name', 'central_node_type', 'central_node_instances', 'path', 'group_id') + @args_validate(TopologyViewManager.cls) + @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView, + app_cli.op.create_topology_view, app_cli.admin_name) + def post(self): + name = request.values.pop('name') + group_id = request.values.pop('group_id', None) + option = request.values.pop('option', None) + order = request.values.pop('order', None) + + topo_view = TopologyViewManager.add(name, group_id, option, order, **request.values) + + return self.jsonify(topo_view) + + @args_validate(TopologyViewManager.cls) + @has_perm_from_args("_id", ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.UPDATE, TopologyViewManager.get_name_by_id) + def put(self, _id): + topo_view = TopologyViewManager.update(_id, **request.values) + + return self.jsonify(topo_view) + + @has_perm_from_args("_id", ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.DELETE, TopologyViewManager.get_name_by_id) + def delete(self, _id): + TopologyViewManager.delete(_id) + + return self.jsonify(code=200) + + +class TopologyOrderView(APIView): + url_prefix = ('/topology_views/order',) + + @args_required('view_ids') + @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView, + app_cli.op.create_topology_view, app_cli.admin_name) + def post(self): + view_ids = request.values.get('view_ids') + + TopologyViewManager.group_inner_order(view_ids) + + return self.jsonify(view_ids=view_ids) + + def put(self): + return self.post() + + +class TopologyViewPreview(APIView): + url_prefix = ('/topology_views/preview', '/topology_views//view') + + @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView, + app_cli.op.read, app_cli.admin_name) + def get(self, _id=None): + if _id is not None: + return self.jsonify(TopologyViewManager().topology_view(view_id=_id)) + else: + return self.jsonify(TopologyViewManager().topology_view(preview=request.values)) + + def post(self, _id=None): + return self.get(_id) + + +class TopologyViewGrantView(APIView): + url_prefix = "/topology_views//roles//grant" + + def post(self, view_id, rid): + perms = request.values.pop('perms', None) + + view_name = TopologyViewManager.get_name_by_id(view_id) or abort(404, ErrFormat.not_found) + acl = ACLManager('cmdb') + if not acl.has_permission(view_name, ResourceTypeEnum.TOPOLOGY_VIEW, + PermEnum.GRANT) and not is_app_admin('cmdb'): + return abort(403, ErrFormat.no_permission.format(view_name, PermEnum.GRANT)) + + acl.grant_resource_to_role_by_rid(view_name, rid, ResourceTypeEnum.TOPOLOGY_VIEW, perms, rebuild=True) + + return self.jsonify(code=200) + + +class TopologyViewRevokeView(APIView): + url_prefix = "/topology_views//roles//revoke" + + @args_required('perms') + def post(self, view_id, rid): + perms = request.values.pop('perms', None) + + view_name = TopologyViewManager.get_name_by_id(view_id) or abort(404, ErrFormat.not_found) + acl = ACLManager('cmdb') + if not acl.has_permission(view_name, ResourceTypeEnum.TOPOLOGY_VIEW, + PermEnum.GRANT) and not is_app_admin('cmdb'): + return abort(403, ErrFormat.no_permission.format(view_name, PermEnum.GRANT)) + + acl.revoke_resource_from_role_by_rid(view_name, rid, ResourceTypeEnum.TOPOLOGY_VIEW, perms, rebuild=True) + + return self.jsonify(code=200)