mirror of https://github.com/veops/cmdb.git
feat(api): topology view (#523)
* feat(api): topology views crud * feat(api): topology view * feat(api): topology view api done * feat(api): topology view is done
This commit is contained in:
parent
e727391fed
commit
d276b3122e
|
@ -128,7 +128,7 @@ def cmdb_init_acl():
|
||||||
perms = [PermEnum.READ]
|
perms = [PermEnum.READ]
|
||||||
elif resource_type == ResourceTypeEnum.CI_TYPE_RELATION:
|
elif resource_type == ResourceTypeEnum.CI_TYPE_RELATION:
|
||||||
perms = [PermEnum.ADD, PermEnum.DELETE, PermEnum.GRANT]
|
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]
|
perms = [PermEnum.READ, PermEnum.UPDATE, PermEnum.DELETE, PermEnum.GRANT]
|
||||||
|
|
||||||
ResourceTypeCRUD.add(app_id, resource_type, '', perms)
|
ResourceTypeCRUD.add(app_id, resource_type, '', perms)
|
||||||
|
|
|
@ -1090,6 +1090,18 @@ class CIRelationManager(object):
|
||||||
|
|
||||||
return ci_ids, level2ids
|
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
|
@staticmethod
|
||||||
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
|
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
|
@ -49,6 +49,7 @@ from api.models.cmdb import CITypeTrigger
|
||||||
from api.models.cmdb import CITypeUniqueConstraint
|
from api.models.cmdb import CITypeUniqueConstraint
|
||||||
from api.models.cmdb import CustomDashboard
|
from api.models.cmdb import CustomDashboard
|
||||||
from api.models.cmdb import PreferenceCITypeOrder
|
from api.models.cmdb import PreferenceCITypeOrder
|
||||||
|
from api.models.cmdb import TopologyView
|
||||||
from api.models.cmdb import PreferenceRelationView
|
from api.models.cmdb import PreferenceRelationView
|
||||||
from api.models.cmdb import PreferenceSearchOption
|
from api.models.cmdb import PreferenceSearchOption
|
||||||
from api.models.cmdb import PreferenceShowAttributes
|
from api.models.cmdb import PreferenceShowAttributes
|
||||||
|
@ -263,6 +264,9 @@ class CITypeManager(object):
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
for item in TopologyView.get_by(central_node_type=type_id, to_dict=False):
|
||||||
|
item.delete(commit=False)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
ci_type.soft_delete()
|
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]
|
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
|
@staticmethod
|
||||||
def _get(parent_id, child_id):
|
def _get(parent_id, child_id):
|
||||||
return CITypeRelation.get_by(parent_id=parent_id,
|
return CITypeRelation.get_by(parent_id=parent_id,
|
||||||
|
|
|
@ -73,6 +73,7 @@ class ResourceTypeEnum(BaseEnum):
|
||||||
RELATION_VIEW = "RelationView" # read/update/delete/grant
|
RELATION_VIEW = "RelationView" # read/update/delete/grant
|
||||||
CI_FILTER = "CIFilter" # read
|
CI_FILTER = "CIFilter" # read
|
||||||
PAGE = "page" # read
|
PAGE = "page" # read
|
||||||
|
TOPOLOGY_VIEW = "TopologyView" # read/update/delete/grant
|
||||||
|
|
||||||
|
|
||||||
class PermEnum(BaseEnum):
|
class PermEnum(BaseEnum):
|
||||||
|
|
|
@ -25,14 +25,15 @@ from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.exception import AbortException
|
from api.lib.exception import AbortException
|
||||||
from api.lib.perm.acl.acl import ACLManager
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
from api.models.cmdb import CITypeAttribute
|
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 CITypeRelation
|
||||||
from api.models.cmdb import PreferenceCITypeOrder
|
from api.models.cmdb import PreferenceCITypeOrder
|
||||||
from api.models.cmdb import PreferenceRelationView
|
from api.models.cmdb import PreferenceRelationView
|
||||||
from api.models.cmdb import PreferenceSearchOption
|
from api.models.cmdb import PreferenceSearchOption
|
||||||
from api.models.cmdb import PreferenceShowAttributes
|
from api.models.cmdb import PreferenceShowAttributes
|
||||||
from api.models.cmdb import PreferenceTreeView
|
from api.models.cmdb import PreferenceTreeView
|
||||||
from api.models.cmdb import CITypeGroup
|
|
||||||
from api.models.cmdb import CITypeGroupItem
|
|
||||||
|
|
||||||
class PreferenceManager(object):
|
class PreferenceManager(object):
|
||||||
pref_attr_cls = PreferenceShowAttributes
|
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))
|
return dict(group_types=group_types, tree_types=tree_types, type_ids=list(type_ids))
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_types2(instance=False, tree=False):
|
def get_types2(instance=False, tree=False):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -144,3 +144,8 @@ class ErrFormat(CommonErrFormat):
|
||||||
cron_time_format_invalid = _l("Scheduling time format error") # 调度时间格式错误
|
cron_time_format_invalid = _l("Scheduling time format error") # 调度时间格式错误
|
||||||
reconciliation_title = _l("CMDB data reconciliation results") # CMDB数据合规检查结果
|
reconciliation_title = _l("CMDB data reconciliation results") # CMDB数据合规检查结果
|
||||||
reconciliation_body = _l("Number of {} illegal: {}") # "{} 不合规数: {}"
|
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")
|
||||||
|
|
|
@ -17,10 +17,12 @@ def search(query=None,
|
||||||
count=1,
|
count=1,
|
||||||
sort=None,
|
sort=None,
|
||||||
excludes=None,
|
excludes=None,
|
||||||
use_id_filter=False):
|
use_id_filter=False,
|
||||||
|
use_ci_filter=True):
|
||||||
if current_app.config.get("USE_ES"):
|
if current_app.config.get("USE_ES"):
|
||||||
s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
|
s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
|
||||||
else:
|
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
|
return s
|
||||||
|
|
|
@ -70,6 +70,7 @@ class Search(object):
|
||||||
self.valid_type_names = []
|
self.valid_type_names = []
|
||||||
self.type2filter_perms = dict()
|
self.type2filter_perms = dict()
|
||||||
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
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
|
@staticmethod
|
||||||
def _operator_proc(key):
|
def _operator_proc(key):
|
||||||
|
|
|
@ -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)
|
|
@ -209,6 +209,26 @@ class CITriggerHistory(Model):
|
||||||
webhook = db.Column(db.Text)
|
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):
|
class CITypeUniqueConstraint(Model):
|
||||||
__tablename__ = "c_c_t_u_c"
|
__tablename__ = "c_c_t_u_c"
|
||||||
|
|
||||||
|
|
|
@ -113,7 +113,6 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
|
||||||
@reconnect_db
|
@reconnect_db
|
||||||
def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
||||||
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
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 = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||||
children = json.loads(children) if children is not None else {}
|
children = json.loads(children) if children is not None else {}
|
||||||
|
|
||||||
|
@ -124,7 +123,7 @@ def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
||||||
|
|
||||||
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)
|
key = "{},{}".format(ancestor_ids, parent_id)
|
||||||
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
||||||
grandson = json.loads(grandson) if grandson is not None else {}
|
grandson = json.loads(grandson) if grandson is not None else {}
|
||||||
|
@ -191,7 +190,6 @@ def ci_relation_add(parent_dict, child_id, uid):
|
||||||
@reconnect_db
|
@reconnect_db
|
||||||
def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
||||||
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
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 = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||||
children = json.loads(children) if children is not None else {}
|
children = json.loads(children) if children is not None else {}
|
||||||
|
|
||||||
|
@ -200,7 +198,7 @@ def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
||||||
|
|
||||||
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)
|
key = "{},{}".format(ancestor_ids, parent_id)
|
||||||
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
||||||
grandson = json.loads(grandson) if grandson is not None else {}
|
grandson = json.loads(grandson) if grandson is not None else {}
|
||||||
|
|
|
@ -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/<int:group_id>')
|
||||||
|
|
||||||
|
@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/<int:type_id>', '/topology_views/<int:_id>')
|
||||||
|
|
||||||
|
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/<int:_id>/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/<int:view_id>/roles/<int:rid>/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/<int:view_id>/roles/<int:rid>/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)
|
Loading…
Reference in New Issue