mirror of
https://github.com/veops/cmdb.git
synced 2025-09-18 02:26:53 +08:00
Compare commits
9 Commits
dependabot
...
2.3.7
Author | SHA1 | Date | |
---|---|---|---|
|
05453becf9 | ||
|
5fe27f8678 | ||
|
0924b8846f | ||
|
62a669159a | ||
|
2933bf1efa | ||
|
981f8b0145 | ||
|
213bda671c | ||
|
837aabfe77 | ||
|
cc599d414a |
26
README.md
26
README.md
@@ -73,20 +73,34 @@
|
|||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
### Docker 一键快速构建
|
### Docker 一键快速构建
|
||||||
- 进入主目录(先安装 docker 环境, 注意要clone整个项目)
|
> 方法一
|
||||||
|
- 第一步: 先安装 docker 环境, 以及docker-compose
|
||||||
|
- 第二步: 拷贝项目
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/veops/cmdb.git
|
||||||
|
```
|
||||||
|
- 第三步:进入主目录,执行:
|
||||||
```
|
```
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
|
> 方法二, 该方法适用于linux系统
|
||||||
- 浏览器打开: [http://127.0.0.1:8000](http://127.0.0.1:8000)
|
- 第一步: 先安装 docker 环境, 以及docker-compose
|
||||||
- username: demo 或者 admin
|
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
|
||||||
- password: 123456
|
```shell
|
||||||
|
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/master/install.sh
|
||||||
|
sh install.sh install
|
||||||
|
```
|
||||||
|
|
||||||
### [本地开发环境搭建](docs/local.md)
|
### [本地开发环境搭建](docs/local.md)
|
||||||
|
|
||||||
### [Makefile 安装](docs/makefile.md)
|
### [Makefile 安装](docs/makefile.md)
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
- 浏览器打开: [http://127.0.0.1:8000](http://127.0.0.1:8000)
|
||||||
|
- username: demo 或者 admin
|
||||||
|
- password: 123456
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
_**欢迎关注公众号(维易科技OneOps),关注后可加入微信群,进行产品和技术交流。**_
|
_**欢迎关注公众号(维易科技OneOps),关注后可加入微信群,进行产品和技术交流。**_
|
||||||
|
@@ -19,6 +19,7 @@ from api.lib.cmdb.cache import AttributeCache
|
|||||||
from api.lib.cmdb.const import PermEnum
|
from api.lib.cmdb.const import PermEnum
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||||
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
from api.lib.cmdb.const import RoleEnum
|
from api.lib.cmdb.const import RoleEnum
|
||||||
from api.lib.cmdb.const import ValueTypeEnum
|
from api.lib.cmdb.const import ValueTypeEnum
|
||||||
@@ -49,12 +50,17 @@ def cmdb_init_cache():
|
|||||||
|
|
||||||
ci_relations = CIRelation.get_by(to_dict=False)
|
ci_relations = CIRelation.get_by(to_dict=False)
|
||||||
relations = dict()
|
relations = dict()
|
||||||
|
relations2 = dict()
|
||||||
for cr in ci_relations:
|
for cr in ci_relations:
|
||||||
relations.setdefault(cr.first_ci_id, {}).update({cr.second_ci_id: cr.second_ci.type_id})
|
relations.setdefault(cr.first_ci_id, {}).update({cr.second_ci_id: cr.second_ci.type_id})
|
||||||
|
if cr.ancestor_ids:
|
||||||
|
relations2.setdefault(cr.ancestor_ids, {}).update({cr.second_ci_id: cr.second_ci.type_id})
|
||||||
for i in relations:
|
for i in relations:
|
||||||
relations[i] = json.dumps(relations[i])
|
relations[i] = json.dumps(relations[i])
|
||||||
if relations:
|
if relations:
|
||||||
rd.create_or_update(relations, REDIS_PREFIX_CI_RELATION)
|
rd.create_or_update(relations, REDIS_PREFIX_CI_RELATION)
|
||||||
|
if relations2:
|
||||||
|
rd.create_or_update(relations2, REDIS_PREFIX_CI_RELATION2)
|
||||||
|
|
||||||
es = None
|
es = None
|
||||||
if current_app.config.get("USE_ES"):
|
if current_app.config.get("USE_ES"):
|
||||||
|
@@ -182,6 +182,9 @@ class CIManager(object):
|
|||||||
need_children and res.update(CIRelationManager.get_children(ci_id, ret_key=ret_key)) # one floor
|
need_children and res.update(CIRelationManager.get_children(ci_id, ret_key=ret_key)) # one floor
|
||||||
|
|
||||||
ci_type = CITypeCache.get(ci.type_id)
|
ci_type = CITypeCache.get(ci.type_id)
|
||||||
|
if not ci_type:
|
||||||
|
return res
|
||||||
|
|
||||||
res["ci_type"] = ci_type.name
|
res["ci_type"] = ci_type.name
|
||||||
|
|
||||||
fields = CITypeAttributeManager.get_attr_names_by_type_id(ci.type_id) if not fields else fields
|
fields = CITypeAttributeManager.get_attr_names_by_type_id(ci.type_id) if not fields else fields
|
||||||
@@ -518,11 +521,13 @@ class CIManager(object):
|
|||||||
item.delete(commit=False)
|
item.delete(commit=False)
|
||||||
|
|
||||||
for item in CIRelation.get_by(first_ci_id=ci_id, to_dict=False):
|
for item in CIRelation.get_by(first_ci_id=ci_id, to_dict=False):
|
||||||
ci_relation_delete.apply_async(args=(item.first_ci_id, item.second_ci_id), queue=CMDB_QUEUE)
|
ci_relation_delete.apply_async(
|
||||||
|
args=(item.first_ci_id, item.second_ci_id, item.ancestor_ids), queue=CMDB_QUEUE)
|
||||||
item.delete(commit=False)
|
item.delete(commit=False)
|
||||||
|
|
||||||
for item in CIRelation.get_by(second_ci_id=ci_id, to_dict=False):
|
for item in CIRelation.get_by(second_ci_id=ci_id, to_dict=False):
|
||||||
ci_relation_delete.apply_async(args=(item.first_ci_id, item.second_ci_id), queue=CMDB_QUEUE)
|
ci_relation_delete.apply_async(
|
||||||
|
args=(item.first_ci_id, item.second_ci_id, item.ancestor_ids), queue=CMDB_QUEUE)
|
||||||
item.delete(commit=False)
|
item.delete(commit=False)
|
||||||
|
|
||||||
ad_ci = AutoDiscoveryCI.get_by(ci_id=ci_id, to_dict=False, first=True)
|
ad_ci = AutoDiscoveryCI.get_by(ci_id=ci_id, to_dict=False, first=True)
|
||||||
@@ -886,12 +891,14 @@ class CIRelationManager(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_ancestor_ids(cls, ci_ids, level=1):
|
def get_ancestor_ids(cls, ci_ids, level=1):
|
||||||
for _ in range(level):
|
level2ids = dict()
|
||||||
cis = db.session.query(CIRelation.first_ci_id).filter(
|
for _level in range(1, level + 1):
|
||||||
|
cis = db.session.query(CIRelation.first_ci_id, CIRelation.ancestor_ids).filter(
|
||||||
CIRelation.second_ci_id.in_(ci_ids)).filter(CIRelation.deleted.is_(False))
|
CIRelation.second_ci_id.in_(ci_ids)).filter(CIRelation.deleted.is_(False))
|
||||||
ci_ids = [i.first_ci_id for i in cis]
|
ci_ids = [i.first_ci_id for i in cis]
|
||||||
|
level2ids[_level + 1] = {int(i.ancestor_ids.split(',')[-1]) for i in cis if i.ancestor_ids}
|
||||||
|
|
||||||
return ci_ids
|
return ci_ids, level2ids
|
||||||
|
|
||||||
@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):
|
||||||
@@ -918,13 +925,14 @@ class CIRelationManager(object):
|
|||||||
return abort(400, ErrFormat.relation_constraint.format("1-N"))
|
return abort(400, ErrFormat.relation_constraint.format("1-N"))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None):
|
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, ancestor_ids=None):
|
||||||
|
|
||||||
first_ci = CIManager.confirm_ci_existed(first_ci_id)
|
first_ci = CIManager.confirm_ci_existed(first_ci_id)
|
||||||
second_ci = CIManager.confirm_ci_existed(second_ci_id)
|
second_ci = CIManager.confirm_ci_existed(second_ci_id)
|
||||||
|
|
||||||
existed = CIRelation.get_by(first_ci_id=first_ci_id,
|
existed = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||||
second_ci_id=second_ci_id,
|
second_ci_id=second_ci_id,
|
||||||
|
ancestor_ids=ancestor_ids,
|
||||||
to_dict=False,
|
to_dict=False,
|
||||||
first=True)
|
first=True)
|
||||||
if existed is not None:
|
if existed is not None:
|
||||||
@@ -960,11 +968,12 @@ class CIRelationManager(object):
|
|||||||
|
|
||||||
existed = CIRelation.create(first_ci_id=first_ci_id,
|
existed = CIRelation.create(first_ci_id=first_ci_id,
|
||||||
second_ci_id=second_ci_id,
|
second_ci_id=second_ci_id,
|
||||||
relation_type_id=relation_type_id)
|
relation_type_id=relation_type_id,
|
||||||
|
ancestor_ids=ancestor_ids)
|
||||||
|
|
||||||
CIRelationHistoryManager().add(existed, OperateType.ADD)
|
CIRelationHistoryManager().add(existed, OperateType.ADD)
|
||||||
|
|
||||||
ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id), queue=CMDB_QUEUE)
|
ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
if more is not None:
|
if more is not None:
|
||||||
existed.upadte(more=more)
|
existed.upadte(more=more)
|
||||||
@@ -988,53 +997,56 @@ class CIRelationManager(object):
|
|||||||
his_manager = CIRelationHistoryManager()
|
his_manager = CIRelationHistoryManager()
|
||||||
his_manager.add(cr, operate_type=OperateType.DELETE)
|
his_manager.add(cr, operate_type=OperateType.DELETE)
|
||||||
|
|
||||||
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id), queue=CMDB_QUEUE)
|
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
return cr_id
|
return cr_id
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete_2(cls, first_ci_id, second_ci_id):
|
def delete_2(cls, first_ci_id, second_ci_id, ancestor_ids=None):
|
||||||
cr = CIRelation.get_by(first_ci_id=first_ci_id,
|
cr = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||||
second_ci_id=second_ci_id,
|
second_ci_id=second_ci_id,
|
||||||
|
ancestor_ids=ancestor_ids,
|
||||||
to_dict=False,
|
to_dict=False,
|
||||||
first=True)
|
first=True)
|
||||||
|
|
||||||
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id), queue=CMDB_QUEUE)
|
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
return cls.delete(cr.id)
|
return cr and cls.delete(cr.id)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def batch_update(cls, ci_ids, parents, children):
|
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
|
||||||
"""
|
"""
|
||||||
only for many to one
|
only for many to one
|
||||||
:param ci_ids:
|
:param ci_ids:
|
||||||
:param parents:
|
:param parents:
|
||||||
:param children:
|
:param children:
|
||||||
|
:param ancestor_ids:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if isinstance(parents, list):
|
if isinstance(parents, list):
|
||||||
for parent_id in parents:
|
for parent_id in parents:
|
||||||
for ci_id in ci_ids:
|
for ci_id in ci_ids:
|
||||||
cls.add(parent_id, ci_id)
|
cls.add(parent_id, ci_id, ancestor_ids=ancestor_ids)
|
||||||
|
|
||||||
if isinstance(children, list):
|
if isinstance(children, list):
|
||||||
for child_id in children:
|
for child_id in children:
|
||||||
for ci_id in ci_ids:
|
for ci_id in ci_ids:
|
||||||
cls.add(ci_id, child_id)
|
cls.add(ci_id, child_id, ancestor_ids=ancestor_ids)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def batch_delete(cls, ci_ids, parents):
|
def batch_delete(cls, ci_ids, parents, ancestor_ids=None):
|
||||||
"""
|
"""
|
||||||
only for many to one
|
only for many to one
|
||||||
:param ci_ids:
|
:param ci_ids:
|
||||||
:param parents:
|
:param parents:
|
||||||
|
:param ancestor_ids:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isinstance(parents, list):
|
if isinstance(parents, list):
|
||||||
for parent_id in parents:
|
for parent_id in parents:
|
||||||
for ci_id in ci_ids:
|
for ci_id in ci_ids:
|
||||||
cls.delete_2(parent_id, ci_id)
|
cls.delete_2(parent_id, ci_id, ancestor_ids=ancestor_ids)
|
||||||
|
|
||||||
|
|
||||||
class CITriggerManager(object):
|
class CITriggerManager(object):
|
||||||
|
@@ -637,6 +637,16 @@ class CITypeRelationManager(object):
|
|||||||
current_app.logger.warning(str(e))
|
current_app.logger.warning(str(e))
|
||||||
return abort(400, ErrFormat.circular_dependency_error)
|
return abort(400, ErrFormat.circular_dependency_error)
|
||||||
|
|
||||||
|
if constraint == ConstraintEnum.Many2Many:
|
||||||
|
other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
|
||||||
|
to_dict=False, first=True)
|
||||||
|
other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
|
||||||
|
to_dict=False, first=True)
|
||||||
|
if other_c and other_c.child_id != c.id:
|
||||||
|
return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
|
||||||
|
if other_p and other_p.parent_id != p.id:
|
||||||
|
return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
|
||||||
|
|
||||||
existed = cls._get(p.id, c.id)
|
existed = cls._get(p.id, c.id)
|
||||||
if existed is not None:
|
if existed is not None:
|
||||||
existed.update(relation_type_id=relation_type_id,
|
existed.update(relation_type_id=relation_type_id,
|
||||||
@@ -686,6 +696,24 @@ class CITypeRelationManager(object):
|
|||||||
|
|
||||||
cls.delete(ctr.id)
|
cls.delete(ctr.id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_level2constraint(root_id, level):
|
||||||
|
level = level + 1 if level == 1 else level
|
||||||
|
ci = CI.get_by_id(root_id)
|
||||||
|
if ci is None:
|
||||||
|
return dict()
|
||||||
|
|
||||||
|
root_id = ci.type_id
|
||||||
|
level2constraint = dict()
|
||||||
|
for lv in range(1, int(level) + 1):
|
||||||
|
for i in CITypeRelation.get_by(parent_id=root_id, to_dict=False):
|
||||||
|
if i.constraint == ConstraintEnum.Many2Many:
|
||||||
|
root_id = i.child_id
|
||||||
|
level2constraint[lv] = ConstraintEnum.Many2Many
|
||||||
|
break
|
||||||
|
|
||||||
|
return level2constraint
|
||||||
|
|
||||||
|
|
||||||
class CITypeAttributeGroupManager(object):
|
class CITypeAttributeGroupManager(object):
|
||||||
cls = CITypeAttributeGroup
|
cls = CITypeAttributeGroup
|
||||||
|
@@ -100,6 +100,7 @@ class AttributeDefaultValueEnum(BaseEnum):
|
|||||||
CMDB_QUEUE = "one_cmdb_async"
|
CMDB_QUEUE = "one_cmdb_async"
|
||||||
REDIS_PREFIX_CI = "ONE_CMDB"
|
REDIS_PREFIX_CI = "ONE_CMDB"
|
||||||
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
||||||
|
REDIS_PREFIX_CI_RELATION2 = "CMDB_CI_RELATION2"
|
||||||
|
|
||||||
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type'}
|
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type'}
|
||||||
|
|
||||||
|
@@ -14,7 +14,10 @@ from api.lib.cmdb.attribute import AttributeManager
|
|||||||
from api.lib.cmdb.cache import AttributeCache
|
from api.lib.cmdb.cache import AttributeCache
|
||||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||||
from api.lib.cmdb.cache import CITypeCache
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum
|
from api.lib.cmdb.const import ConstraintEnum
|
||||||
|
from api.lib.cmdb.const import PermEnum
|
||||||
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
|
from api.lib.cmdb.const import RoleEnum
|
||||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.exception import AbortException
|
from api.lib.exception import AbortException
|
||||||
@@ -229,14 +232,28 @@ class PreferenceManager(object):
|
|||||||
if not parents:
|
if not parents:
|
||||||
return
|
return
|
||||||
|
|
||||||
for l in leaf:
|
for _l in leaf:
|
||||||
_find_parent(l)
|
_find_parent(_l)
|
||||||
|
|
||||||
for node_id in node2show_types:
|
for node_id in node2show_types:
|
||||||
node2show_types[node_id] = [CITypeCache.get(i).to_dict() for i in set(node2show_types[node_id])]
|
node2show_types[node_id] = [CITypeCache.get(i).to_dict() for i in set(node2show_types[node_id])]
|
||||||
|
|
||||||
|
topo_flatten = list(toposort.toposort_flatten(topo))
|
||||||
|
level2constraint = {}
|
||||||
|
for i, _ in enumerate(topo_flatten[1:]):
|
||||||
|
ctr = CITypeRelation.get_by(
|
||||||
|
parent_id=topo_flatten[i], child_id=topo_flatten[i + 1], first=True, to_dict=False)
|
||||||
|
level2constraint[i + 1] = ctr and ctr.constraint
|
||||||
|
|
||||||
|
if leaf2show_types.get(topo_flatten[-1]):
|
||||||
|
ctr = CITypeRelation.get_by(
|
||||||
|
parent_id=topo_flatten[-1],
|
||||||
|
child_id=leaf2show_types[topo_flatten[-1]][0], first=True, to_dict=False)
|
||||||
|
level2constraint[len(topo_flatten)] = ctr and ctr.constraint
|
||||||
|
|
||||||
result[view_name] = dict(topo=list(map(list, toposort.toposort(topo))),
|
result[view_name] = dict(topo=list(map(list, toposort.toposort(topo))),
|
||||||
topo_flatten=list(toposort.toposort_flatten(topo)),
|
topo_flatten=topo_flatten,
|
||||||
|
level2constraint=level2constraint,
|
||||||
leaf=leaf,
|
leaf=leaf,
|
||||||
leaf2show_types=leaf2show_types,
|
leaf2show_types=leaf2show_types,
|
||||||
node2show_types=node2show_types,
|
node2show_types=node2show_types,
|
||||||
@@ -338,3 +355,29 @@ class PreferenceManager(object):
|
|||||||
|
|
||||||
for i in PreferenceTreeView.get_by(type_id=type_id, uid=uid, to_dict=False):
|
for i in PreferenceTreeView.get_by(type_id=type_id, uid=uid, to_dict=False):
|
||||||
i.soft_delete()
|
i.soft_delete()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def can_edit_relation(parent_id, child_id):
|
||||||
|
views = PreferenceRelationView.get_by(to_dict=False)
|
||||||
|
for view in views:
|
||||||
|
has_m2m = False
|
||||||
|
last_node_id = None
|
||||||
|
for cr in view.cr_ids:
|
||||||
|
_rel = CITypeRelation.get_by(parent_id=cr['parent_id'], child_id=cr['child_id'],
|
||||||
|
first=True, to_dict=False)
|
||||||
|
if _rel and _rel.constraint == ConstraintEnum.Many2Many:
|
||||||
|
has_m2m = True
|
||||||
|
|
||||||
|
if parent_id == _rel.parent_id and child_id == _rel.child_id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if _rel:
|
||||||
|
last_node_id = _rel.child_id
|
||||||
|
|
||||||
|
if parent_id == last_node_id:
|
||||||
|
rels = CITypeRelation.get_by(parent_id=last_node_id, to_dict=False)
|
||||||
|
for rel in rels:
|
||||||
|
if rel.child_id == child_id and has_m2m:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
@@ -31,6 +31,7 @@ class ErrFormat(CommonErrFormat):
|
|||||||
unique_key_required = "主键字段 {} 缺失"
|
unique_key_required = "主键字段 {} 缺失"
|
||||||
ci_is_already_existed = "CI 已经存在!"
|
ci_is_already_existed = "CI 已经存在!"
|
||||||
relation_constraint = "关系约束: {}, 校验失败 "
|
relation_constraint = "关系约束: {}, 校验失败 "
|
||||||
|
m2m_relation_constraint = "多对多关系 限制: 模型 {} <-> {} 已经存在多对多关系!"
|
||||||
relation_not_found = "CI关系: {} 不存在"
|
relation_not_found = "CI关系: {} 不存在"
|
||||||
ci_search_Parentheses_invalid = "搜索表达式里小括号前不支持: 或、非"
|
ci_search_Parentheses_invalid = "搜索表达式里小括号前不支持: 或、非"
|
||||||
|
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
|
||||||
@@ -10,11 +8,14 @@ from flask import current_app
|
|||||||
from api.extensions import rd
|
from api.extensions import rd
|
||||||
from api.lib.cmdb.ci import CIRelationManager
|
from api.lib.cmdb.ci import CIRelationManager
|
||||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||||
|
from api.lib.cmdb.const import ConstraintEnum
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||||
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||||
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
|
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
|
||||||
from api.models.cmdb import CI
|
from api.models.cmdb import CI
|
||||||
|
from api.models.cmdb import CIRelation
|
||||||
|
|
||||||
|
|
||||||
class Search(object):
|
class Search(object):
|
||||||
@@ -26,7 +27,8 @@ class Search(object):
|
|||||||
page=1,
|
page=1,
|
||||||
count=None,
|
count=None,
|
||||||
sort=None,
|
sort=None,
|
||||||
reverse=False):
|
reverse=False,
|
||||||
|
ancestor_ids=None):
|
||||||
self.orig_query = query
|
self.orig_query = query
|
||||||
self.fl = fl
|
self.fl = fl
|
||||||
self.facet_field = facet_field
|
self.facet_field = facet_field
|
||||||
@@ -38,24 +40,80 @@ class Search(object):
|
|||||||
self.level = level or 0
|
self.level = level or 0
|
||||||
self.reverse = reverse
|
self.reverse = reverse
|
||||||
|
|
||||||
def _get_ids(self):
|
self.level2constraint = CITypeRelationManager.get_level2constraint(
|
||||||
|
root_id[0] if root_id and isinstance(root_id, list) else root_id,
|
||||||
|
level[0] if isinstance(level, list) and level else level)
|
||||||
|
|
||||||
|
self.ancestor_ids = ancestor_ids
|
||||||
|
self.has_m2m = False
|
||||||
|
if self.ancestor_ids:
|
||||||
|
self.has_m2m = True
|
||||||
|
else:
|
||||||
|
level = level[0] if isinstance(level, list) and level else level
|
||||||
|
for _l, c in self.level2constraint.items():
|
||||||
|
if _l < int(level) and c == ConstraintEnum.Many2Many:
|
||||||
|
self.has_m2m = True
|
||||||
|
|
||||||
|
def _get_ids(self, ids):
|
||||||
|
if self.level[-1] == 1 and len(ids) == 1:
|
||||||
|
if self.ancestor_ids is None:
|
||||||
|
return [i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0], to_dict=False)]
|
||||||
|
|
||||||
|
else:
|
||||||
|
seconds = {i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0],
|
||||||
|
ancestor_ids=self.ancestor_ids,
|
||||||
|
to_dict=False)}
|
||||||
|
|
||||||
|
return list(seconds)
|
||||||
|
|
||||||
merge_ids = []
|
merge_ids = []
|
||||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
key = []
|
||||||
|
_tmp = []
|
||||||
for level in range(1, sorted(self.level)[-1] + 1):
|
for level in range(1, sorted(self.level)[-1] + 1):
|
||||||
_tmp = list(map(lambda x: list(json.loads(x).keys()),
|
if not self.has_m2m:
|
||||||
filter(lambda x: x is not None, rd.get(ids, REDIS_PREFIX_CI_RELATION) or [])))
|
_tmp = map(lambda x: json.loads(x).keys(),
|
||||||
|
filter(lambda x: x is not None, rd.get(ids, REDIS_PREFIX_CI_RELATION) or []))
|
||||||
ids = [j for i in _tmp for j in i]
|
ids = [j for i in _tmp for j in i]
|
||||||
|
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
||||||
|
|
||||||
|
else:
|
||||||
|
if not self.ancestor_ids:
|
||||||
|
if level == 1:
|
||||||
|
key, prefix = list(map(str, ids)), REDIS_PREFIX_CI_RELATION
|
||||||
|
else:
|
||||||
|
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
|
||||||
|
prefix = REDIS_PREFIX_CI_RELATION2
|
||||||
|
else:
|
||||||
|
if level == 1:
|
||||||
|
key, prefix = ["{},{}".format(self.ancestor_ids, i) for i in ids], REDIS_PREFIX_CI_RELATION2
|
||||||
|
else:
|
||||||
|
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
|
||||||
|
prefix = REDIS_PREFIX_CI_RELATION2
|
||||||
|
|
||||||
|
if not key:
|
||||||
|
return []
|
||||||
|
|
||||||
|
_tmp = list(map(lambda x: json.loads(x).keys() if x else [], rd.get(key, prefix) or []))
|
||||||
|
ids = [j for i in _tmp for j in i]
|
||||||
|
|
||||||
if level in self.level:
|
if level in self.level:
|
||||||
merge_ids.extend(ids)
|
merge_ids.extend(ids)
|
||||||
|
|
||||||
return merge_ids
|
return merge_ids
|
||||||
|
|
||||||
def _get_reverse_ids(self):
|
def _get_reverse_ids(self, ids):
|
||||||
merge_ids = []
|
merge_ids = []
|
||||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
level2ids = {}
|
||||||
for level in range(1, sorted(self.level)[-1] + 1):
|
for level in range(1, sorted(self.level)[-1] + 1):
|
||||||
ids = CIRelationManager.get_ancestor_ids(ids, 1)
|
ids, _level2ids = CIRelationManager.get_ancestor_ids(ids, 1)
|
||||||
|
|
||||||
|
if _level2ids.get(2):
|
||||||
|
level2ids[level + 1] = _level2ids[2]
|
||||||
|
|
||||||
if level in self.level:
|
if level in self.level:
|
||||||
|
if level in level2ids and level2ids[level]:
|
||||||
|
merge_ids.extend(set(ids) & set(level2ids[level]))
|
||||||
|
else:
|
||||||
merge_ids.extend(ids)
|
merge_ids.extend(ids)
|
||||||
|
|
||||||
return merge_ids
|
return merge_ids
|
||||||
@@ -64,7 +122,7 @@ class Search(object):
|
|||||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||||
cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids]
|
cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids]
|
||||||
|
|
||||||
merge_ids = self._get_ids() if not self.reverse else self._get_reverse_ids()
|
merge_ids = self._get_ids(ids) if not self.reverse else self._get_reverse_ids(ids)
|
||||||
|
|
||||||
if not self.orig_query or ("_type:" not in self.orig_query
|
if not self.orig_query or ("_type:" not in self.orig_query
|
||||||
and "type_id:" not in self.orig_query
|
and "type_id:" not in self.orig_query
|
||||||
@@ -76,11 +134,11 @@ class Search(object):
|
|||||||
type_ids.extend(CITypeRelationManager.get_child_type_ids(ci.type_id, level))
|
type_ids.extend(CITypeRelationManager.get_child_type_ids(ci.type_id, level))
|
||||||
else:
|
else:
|
||||||
type_ids.extend(CITypeRelationManager.get_parent_type_ids(ci.type_id, level))
|
type_ids.extend(CITypeRelationManager.get_parent_type_ids(ci.type_id, level))
|
||||||
type_ids = list(set(type_ids))
|
type_ids = set(type_ids)
|
||||||
if self.orig_query:
|
if self.orig_query:
|
||||||
self.orig_query = "_type:({0}),{1}".format(";".join(list(map(str, type_ids))), self.orig_query)
|
self.orig_query = "_type:({0}),{1}".format(";".join(map(str, type_ids)), self.orig_query)
|
||||||
else:
|
else:
|
||||||
self.orig_query = "_type:({0})".format(";".join(list(map(str, type_ids))))
|
self.orig_query = "_type:({0})".format(";".join(map(str, type_ids)))
|
||||||
|
|
||||||
if not merge_ids:
|
if not merge_ids:
|
||||||
# cis, counter, total, self.page, numfound, facet_
|
# cis, counter, total, self.page, numfound, facet_
|
||||||
@@ -105,35 +163,65 @@ class Search(object):
|
|||||||
|
|
||||||
def statistics(self, type_ids):
|
def statistics(self, type_ids):
|
||||||
self.level = int(self.level)
|
self.level = int(self.level)
|
||||||
_tmp = []
|
|
||||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||||
for lv in range(0, self.level):
|
_tmp = []
|
||||||
if not lv:
|
level2ids = {}
|
||||||
if type_ids and lv == self.level - 1:
|
for lv in range(1, self.level + 1):
|
||||||
|
level2ids[lv] = []
|
||||||
|
|
||||||
|
if lv == 1:
|
||||||
|
if not self.has_m2m:
|
||||||
|
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
||||||
|
else:
|
||||||
|
if not self.ancestor_ids:
|
||||||
|
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
||||||
|
else:
|
||||||
|
key = ["{},{}".format(self.ancestor_ids, _id) for _id in ids]
|
||||||
|
prefix = REDIS_PREFIX_CI_RELATION2
|
||||||
|
|
||||||
|
level2ids[lv] = [[i] for i in key]
|
||||||
|
|
||||||
|
if not key:
|
||||||
|
_tmp = []
|
||||||
|
continue
|
||||||
|
|
||||||
|
if type_ids and lv == self.level:
|
||||||
_tmp = list(map(lambda x: [i for i in x if i[1] in type_ids],
|
_tmp = list(map(lambda x: [i for i in x if i[1] in type_ids],
|
||||||
(map(lambda x: list(json.loads(x).items()),
|
(map(lambda x: list(json.loads(x).items()),
|
||||||
[i or '{}' for i in rd.get(ids, REDIS_PREFIX_CI_RELATION) or []]))))
|
[i or '{}' for i in rd.get(key, prefix) or []]))))
|
||||||
else:
|
else:
|
||||||
_tmp = list(map(lambda x: list(json.loads(x).items()),
|
_tmp = list(map(lambda x: list(json.loads(x).items()),
|
||||||
[i or '{}' for i in rd.get(ids, REDIS_PREFIX_CI_RELATION) or []]))
|
[i or '{}' for i in rd.get(key, prefix) or []]))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for idx, item in enumerate(_tmp):
|
for idx, item in enumerate(_tmp):
|
||||||
if item:
|
if item:
|
||||||
if type_ids and lv == self.level - 1:
|
if not self.has_m2m:
|
||||||
__tmp = list(
|
key, prefix = [i[0] for i in item], REDIS_PREFIX_CI_RELATION
|
||||||
map(lambda x: [(_id, type_id) for _id, type_id in json.loads(x).items()
|
else:
|
||||||
|
key = list(set(['{},{}'.format(j, i[0]) for i in item for j in level2ids[lv - 1][idx]]))
|
||||||
|
prefix = REDIS_PREFIX_CI_RELATION2
|
||||||
|
|
||||||
|
level2ids[lv].append(key)
|
||||||
|
|
||||||
|
if key:
|
||||||
|
if type_ids and lv == self.level:
|
||||||
|
__tmp = map(lambda x: [(_id, type_id) for _id, type_id in json.loads(x).items()
|
||||||
if type_id in type_ids],
|
if type_id in type_ids],
|
||||||
filter(lambda x: x is not None,
|
filter(lambda x: x is not None,
|
||||||
rd.get([i[0] for i in item], REDIS_PREFIX_CI_RELATION) or [])))
|
rd.get(key, prefix) or []))
|
||||||
else:
|
else:
|
||||||
|
__tmp = map(lambda x: list(json.loads(x).items()),
|
||||||
__tmp = list(map(lambda x: list(json.loads(x).items()),
|
|
||||||
filter(lambda x: x is not None,
|
filter(lambda x: x is not None,
|
||||||
rd.get([i[0] for i in item], REDIS_PREFIX_CI_RELATION) or [])))
|
rd.get(key, prefix) or []))
|
||||||
|
else:
|
||||||
|
__tmp = []
|
||||||
|
|
||||||
_tmp[idx] = [j for i in __tmp for j in i]
|
_tmp[idx] = [j for i in __tmp for j in i]
|
||||||
else:
|
else:
|
||||||
_tmp[idx] = []
|
_tmp[idx] = []
|
||||||
|
level2ids[lv].append([])
|
||||||
|
|
||||||
result = {str(_id): len(_tmp[idx]) for idx, _id in enumerate(ids)}
|
result = {str(_id): len(_tmp[idx]) for idx, _id in enumerate(ids)}
|
||||||
|
|
||||||
|
@@ -38,7 +38,6 @@ def string_to_bytes(value):
|
|||||||
byte_string = value
|
byte_string = value
|
||||||
else:
|
else:
|
||||||
byte_string = value.encode("utf-8")
|
byte_string = value.encode("utf-8")
|
||||||
|
|
||||||
return byte_string
|
return byte_string
|
||||||
|
|
||||||
|
|
||||||
@@ -314,7 +313,7 @@ class KeyManage:
|
|||||||
secrets_root_key = current_app.config.get("secrets_root_key")
|
secrets_root_key = current_app.config.get("secrets_root_key")
|
||||||
msg, ok = self.is_valid_root_key(secrets_root_key)
|
msg, ok = self.is_valid_root_key(secrets_root_key)
|
||||||
if not ok:
|
if not ok:
|
||||||
return {"message": msg, "status": "failed"}
|
return true
|
||||||
status = self.backend.get(backend_seal_key)
|
status = self.backend.get(backend_seal_key)
|
||||||
return status == "block"
|
return status == "block"
|
||||||
|
|
||||||
|
@@ -218,6 +218,8 @@ class CIRelation(Model):
|
|||||||
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
|
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
|
||||||
more = db.Column(db.Integer, db.ForeignKey("c_cis.id"))
|
more = db.Column(db.Integer, db.ForeignKey("c_cis.id"))
|
||||||
|
|
||||||
|
ancestor_ids = db.Column(db.String(128), index=True)
|
||||||
|
|
||||||
first_ci = db.relationship("CI", primaryjoin="CI.id==CIRelation.first_ci_id")
|
first_ci = db.relationship("CI", primaryjoin="CI.id==CIRelation.first_ci_id")
|
||||||
second_ci = db.relationship("CI", primaryjoin="CI.id==CIRelation.second_ci_id")
|
second_ci = db.relationship("CI", primaryjoin="CI.id==CIRelation.second_ci_id")
|
||||||
relation_type = db.relationship("RelationType", backref="c_ci_relations.relation_type_id")
|
relation_type = db.relationship("RelationType", backref="c_ci_relations.relation_type_id")
|
||||||
|
@@ -16,6 +16,7 @@ from api.lib.cmdb.cache import CITypeAttributesCache
|
|||||||
from api.lib.cmdb.const import CMDB_QUEUE
|
from api.lib.cmdb.const import CMDB_QUEUE
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||||
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||||
from api.lib.decorator import flush_db
|
from api.lib.decorator import flush_db
|
||||||
from api.lib.decorator import reconnect_db
|
from api.lib.decorator import reconnect_db
|
||||||
from api.lib.perm.acl.cache import UserCache
|
from api.lib.perm.acl.cache import UserCache
|
||||||
@@ -97,17 +98,31 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
|
|||||||
@celery.task(name="cmdb.ci_relation_cache", queue=CMDB_QUEUE)
|
@celery.task(name="cmdb.ci_relation_cache", queue=CMDB_QUEUE)
|
||||||
@flush_db
|
@flush_db
|
||||||
@reconnect_db
|
@reconnect_db
|
||||||
def ci_relation_cache(parent_id, child_id):
|
def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
||||||
with Lock("CIRelation_{}".format(parent_id)):
|
with Lock("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 {}
|
||||||
|
|
||||||
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, first=True, to_dict=False)
|
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:
|
if str(child_id) not in children:
|
||||||
children[str(child_id)] = cr.second_ci.type_id
|
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:
|
||||||
|
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 {}
|
||||||
|
|
||||||
|
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
|
||||||
|
first=True, to_dict=False)
|
||||||
|
if cr and str(cr.second_ci_id) not in grandson:
|
||||||
|
grandson[str(cr.second_ci_id)] = cr.second_ci.type_id
|
||||||
|
|
||||||
|
rd.create_or_update({key: json.dumps(grandson)}, REDIS_PREFIX_CI_RELATION2)
|
||||||
|
|
||||||
current_app.logger.info("ADD ci relation cache: {0} -> {1}".format(parent_id, child_id))
|
current_app.logger.info("ADD ci relation cache: {0} -> {1}".format(parent_id, child_id))
|
||||||
|
|
||||||
|
|
||||||
@@ -156,13 +171,14 @@ def ci_relation_add(parent_dict, child_id, uid):
|
|||||||
try:
|
try:
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
except:
|
except:
|
||||||
pass
|
db.session.rollback()
|
||||||
|
|
||||||
|
|
||||||
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
|
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
|
||||||
@reconnect_db
|
@reconnect_db
|
||||||
def ci_relation_delete(parent_id, child_id):
|
def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
||||||
with Lock("CIRelation_{}".format(parent_id)):
|
with Lock("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 {}
|
||||||
|
|
||||||
@@ -171,6 +187,16 @@ def ci_relation_delete(parent_id, 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:
|
||||||
|
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 {}
|
||||||
|
|
||||||
|
if str(child_id) in grandson:
|
||||||
|
grandson.pop(str(child_id))
|
||||||
|
|
||||||
|
rd.create_or_update({key: json.dumps(grandson)}, REDIS_PREFIX_CI_RELATION2)
|
||||||
|
|
||||||
current_app.logger.info("DELETE ci relation cache: {0} -> {1}".format(parent_id, child_id))
|
current_app.logger.info("DELETE ci relation cache: {0} -> {1}".format(parent_id, child_id))
|
||||||
|
|
||||||
|
|
||||||
|
@@ -35,6 +35,7 @@ class CIRelationSearchView(APIView):
|
|||||||
count = get_page_size(request.values.get("count") or request.values.get("page_size"))
|
count = get_page_size(request.values.get("count") or request.values.get("page_size"))
|
||||||
|
|
||||||
root_id = request.values.get('root_id')
|
root_id = request.values.get('root_id')
|
||||||
|
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
|
||||||
level = list(map(int, handle_arg_list(request.values.get('level', '1'))))
|
level = list(map(int, handle_arg_list(request.values.get('level', '1'))))
|
||||||
|
|
||||||
query = request.values.get('q', "")
|
query = request.values.get('q', "")
|
||||||
@@ -44,7 +45,7 @@ class CIRelationSearchView(APIView):
|
|||||||
reverse = request.values.get("reverse") in current_app.config.get('BOOL_TRUE')
|
reverse = request.values.get("reverse") in current_app.config.get('BOOL_TRUE')
|
||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
s = Search(root_id, level, query, fl, facet, page, count, sort, reverse)
|
s = Search(root_id, level, query, fl, facet, page, count, sort, reverse, ancestor_ids=ancestor_ids)
|
||||||
try:
|
try:
|
||||||
response, counter, total, page, numfound, facet = s.search()
|
response, counter, total, page, numfound, facet = s.search()
|
||||||
except SearchError as e:
|
except SearchError as e:
|
||||||
@@ -67,9 +68,10 @@ class CIRelationStatisticsView(APIView):
|
|||||||
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
|
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
|
||||||
level = request.values.get('level', 1)
|
level = request.values.get('level', 1)
|
||||||
type_ids = set(map(int, handle_arg_list(request.values.get('type_ids', []))))
|
type_ids = set(map(int, handle_arg_list(request.values.get('type_ids', []))))
|
||||||
|
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
|
||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
s = Search(root_ids, level)
|
s = Search(root_ids, level, ancestor_ids=ancestor_ids)
|
||||||
try:
|
try:
|
||||||
result = s.statistics(type_ids)
|
result = s.statistics(type_ids)
|
||||||
except SearchError as e:
|
except SearchError as e:
|
||||||
@@ -121,14 +123,18 @@ class CIRelationView(APIView):
|
|||||||
url_prefix = "/ci_relations/<int:first_ci_id>/<int:second_ci_id>"
|
url_prefix = "/ci_relations/<int:first_ci_id>/<int:second_ci_id>"
|
||||||
|
|
||||||
def post(self, first_ci_id, second_ci_id):
|
def post(self, first_ci_id, second_ci_id):
|
||||||
|
ancestor_ids = request.values.get('ancestor_ids') or None
|
||||||
|
|
||||||
manager = CIRelationManager()
|
manager = CIRelationManager()
|
||||||
res = manager.add(first_ci_id, second_ci_id)
|
res = manager.add(first_ci_id, second_ci_id, ancestor_ids=ancestor_ids)
|
||||||
|
|
||||||
return self.jsonify(cr_id=res)
|
return self.jsonify(cr_id=res)
|
||||||
|
|
||||||
def delete(self, first_ci_id, second_ci_id):
|
def delete(self, first_ci_id, second_ci_id):
|
||||||
|
ancestor_ids = request.values.get('ancestor_ids') or None
|
||||||
|
|
||||||
manager = CIRelationManager()
|
manager = CIRelationManager()
|
||||||
manager.delete_2(first_ci_id, second_ci_id)
|
manager.delete_2(first_ci_id, second_ci_id, ancestor_ids=ancestor_ids)
|
||||||
|
|
||||||
return self.jsonify(message="CIType Relation is deleted")
|
return self.jsonify(message="CIType Relation is deleted")
|
||||||
|
|
||||||
@@ -151,8 +157,9 @@ class BatchCreateOrUpdateCIRelationView(APIView):
|
|||||||
ci_ids = list(map(int, request.values.get('ci_ids')))
|
ci_ids = list(map(int, request.values.get('ci_ids')))
|
||||||
parents = list(map(int, request.values.get('parents', [])))
|
parents = list(map(int, request.values.get('parents', [])))
|
||||||
children = list(map(int, request.values.get('children', [])))
|
children = list(map(int, request.values.get('children', [])))
|
||||||
|
ancestor_ids = request.values.get('ancestor_ids') or None
|
||||||
|
|
||||||
CIRelationManager.batch_update(ci_ids, parents, children)
|
CIRelationManager.batch_update(ci_ids, parents, children, ancestor_ids=ancestor_ids)
|
||||||
|
|
||||||
return self.jsonify(code=200)
|
return self.jsonify(code=200)
|
||||||
|
|
||||||
@@ -166,7 +173,8 @@ class BatchCreateOrUpdateCIRelationView(APIView):
|
|||||||
def delete(self):
|
def delete(self):
|
||||||
ci_ids = list(map(int, request.values.get('ci_ids')))
|
ci_ids = list(map(int, request.values.get('ci_ids')))
|
||||||
parents = list(map(int, request.values.get('parents', [])))
|
parents = list(map(int, request.values.get('parents', [])))
|
||||||
|
ancestor_ids = request.values.get('ancestor_ids') or None
|
||||||
|
|
||||||
CIRelationManager.batch_delete(ci_ids, parents)
|
CIRelationManager.batch_delete(ci_ids, parents, ancestor_ids=ancestor_ids)
|
||||||
|
|
||||||
return self.jsonify(code=200)
|
return self.jsonify(code=200)
|
||||||
|
@@ -9,6 +9,7 @@ from api.lib.cmdb.ci_type import CITypeRelationManager
|
|||||||
from api.lib.cmdb.const import PermEnum
|
from api.lib.cmdb.const import PermEnum
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
from api.lib.cmdb.const import RoleEnum
|
from api.lib.cmdb.const import RoleEnum
|
||||||
|
from api.lib.cmdb.preference import PreferenceManager
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.decorator import args_required
|
from api.lib.decorator import args_required
|
||||||
from api.lib.perm.acl.acl import ACLManager
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
@@ -109,3 +110,10 @@ class CITypeRelationRevokeView(APIView):
|
|||||||
acl.revoke_resource_from_role_by_rid(resource_name, rid, ResourceTypeEnum.CI_TYPE_RELATION, perms)
|
acl.revoke_resource_from_role_by_rid(resource_name, rid, ResourceTypeEnum.CI_TYPE_RELATION, perms)
|
||||||
|
|
||||||
return self.jsonify(code=200)
|
return self.jsonify(code=200)
|
||||||
|
|
||||||
|
|
||||||
|
class CITypeRelationCanEditView(APIView):
|
||||||
|
url_prefix = "/ci_type_relations/<int:parent_id>/<int:child_id>/can_edit"
|
||||||
|
|
||||||
|
def get(self, parent_id, child_id):
|
||||||
|
return self.jsonify(result=PreferenceManager.can_edit_relation(parent_id, child_id))
|
||||||
|
@@ -68,6 +68,7 @@
|
|||||||
ref="xTable"
|
ref="xTable"
|
||||||
row-id="id"
|
row-id="id"
|
||||||
show-overflow
|
show-overflow
|
||||||
|
resizable
|
||||||
>
|
>
|
||||||
<!-- 1 -->
|
<!-- 1 -->
|
||||||
<vxe-table-column type="checkbox" fixed="left" :width="45"></vxe-table-column>
|
<vxe-table-column type="checkbox" fixed="left" :width="45"></vxe-table-column>
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
import { axios } from '@/utils/request'
|
import { axios } from '@/utils/request'
|
||||||
|
|
||||||
export function getFirstCIs(ciId) {
|
export function getFirstCIsByCiId(ciId) {
|
||||||
return axios({
|
return axios({
|
||||||
url: '/v0.1/ci_relations/' + ciId + '/first_cis',
|
url: '/v0.1/ci_relations/' + ciId + '/first_cis',
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSecondCIs(ciId) {
|
export function getSecondCIsByCiId(ciId) {
|
||||||
return axios({
|
return axios({
|
||||||
url: '/v0.1/ci_relations/' + ciId + '/second_cis',
|
url: '/v0.1/ci_relations/' + ciId + '/second_cis',
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
@@ -30,11 +30,11 @@ export function statisticsCIRelation(params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 批量添加子节点
|
// 批量添加子节点
|
||||||
export function batchUpdateCIRelationChildren(ciIds, parents) {
|
export function batchUpdateCIRelationChildren(ciIds, parents, ancestor_ids = undefined) {
|
||||||
return axios({
|
return axios({
|
||||||
url: '/v0.1/ci_relations/batch',
|
url: '/v0.1/ci_relations/batch',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: { ci_ids: ciIds, parents: parents }
|
data: { ci_ids: ciIds, parents, ancestor_ids }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,26 +48,28 @@ export function batchUpdateCIRelationParents(ciIds, children) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 批量删除
|
// 批量删除
|
||||||
export function batchDeleteCIRelation(ciIds, parents) {
|
export function batchDeleteCIRelation(ciIds, parents, ancestor_ids = undefined) {
|
||||||
return axios({
|
return axios({
|
||||||
url: '/v0.1/ci_relations/batch',
|
url: '/v0.1/ci_relations/batch',
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
data: { ci_ids: ciIds, parents: parents }
|
data: { ci_ids: ciIds, parents, ancestor_ids }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 单个添加
|
// 单个添加
|
||||||
export function addCIRelationView(firstCiId, secondCiId) {
|
export function addCIRelationView(firstCiId, secondCiId, data) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_relations/${firstCiId}/${secondCiId}`,
|
url: `/v0.1/ci_relations/${firstCiId}/${secondCiId}`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 单个删除
|
// 单个删除
|
||||||
export function deleteCIRelationView(firstCiId, secondCiId) {
|
export function deleteCIRelationView(firstCiId, secondCiId, data) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_relations/${firstCiId}/${secondCiId}`,
|
url: `/v0.1/ci_relations/${firstCiId}/${secondCiId}`,
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
|
data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -68,3 +68,10 @@ export function getRecursive_level2children(type_id) {
|
|||||||
method: 'GET'
|
method: 'GET'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCanEditByParentIdChildId(parent_id, child_id) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ci_type_relations/${parent_id}/${child_id}/can_edit`,
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -40,7 +40,7 @@
|
|||||||
全选
|
全选
|
||||||
</a-checkbox>
|
</a-checkbox>
|
||||||
<br />
|
<br />
|
||||||
<a-checkbox-group v-model="checkedAttrs">
|
<a-checkbox-group style="width:100%" v-model="checkedAttrs">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="6" v-for="item in selectCiTypeAttrList.attributes" :key="item.alias || item.name">
|
<a-col :span="6" v-for="item in selectCiTypeAttrList.attributes" :key="item.alias || item.name">
|
||||||
<a-checkbox :disabled="item.name === selectCiTypeAttrList.unique" :value="item.alias || item.name">
|
<a-checkbox :disabled="item.name === selectCiTypeAttrList.unique" :value="item.alias || item.name">
|
||||||
@@ -87,10 +87,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import _ from 'lodash'
|
||||||
import { downloadExcel } from '../../../utils/helper'
|
import { downloadExcel } from '../../../utils/helper'
|
||||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||||
import { getCITypeParent } from '@/modules/cmdb/api/CITypeRelation'
|
import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CiTypeChoice',
|
name: 'CiTypeChoice',
|
||||||
@@ -107,6 +108,7 @@ export default {
|
|||||||
parentsType: [],
|
parentsType: [],
|
||||||
parentsForm: {},
|
parentsForm: {},
|
||||||
checkedParents: [],
|
checkedParents: [],
|
||||||
|
canEdit: {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: function() {
|
created: function() {
|
||||||
@@ -143,8 +145,17 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
openModal() {
|
openModal() {
|
||||||
getCITypeParent(this.selectNum).then((res) => {
|
getCITypeParent(this.selectNum).then(async (res) => {
|
||||||
this.parentsType = res.parents
|
for (let i = 0; i < res.parents.length; i++) {
|
||||||
|
await getCanEditByParentIdChildId(res.parents[i].id, this.selectNum).then((p_res) => {
|
||||||
|
this.canEdit = {
|
||||||
|
..._.cloneDeep(this.canEdit),
|
||||||
|
[res.parents[i].id]: p_res.result,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.parentsType = res.parents.filter((parent) => this.canEdit[parent.id])
|
||||||
|
|
||||||
const _parentsForm = {}
|
const _parentsForm = {}
|
||||||
res.parents.forEach((item) => {
|
res.parents.forEach((item) => {
|
||||||
const _find = item.attributes.find((attr) => attr.id === item.unique_id)
|
const _find = item.attributes.find((attr) => attr.id === item.unique_id)
|
||||||
|
@@ -122,12 +122,12 @@
|
|||||||
<a-button type="primary" ghost icon="plus" @click="handleAdd">新增修改字段</a-button>
|
<a-button type="primary" ghost icon="plus" @click="handleAdd">新增修改字段</a-button>
|
||||||
</a-form>
|
</a-form>
|
||||||
</template>
|
</template>
|
||||||
<!-- </a-form> -->
|
|
||||||
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
||||||
</CustomDrawer>
|
</CustomDrawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import _ from 'lodash'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { Select, Option } from 'element-ui'
|
import { Select, Option } from 'element-ui'
|
||||||
import { getCIType, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
|
import { getCIType, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
|
||||||
@@ -135,7 +135,7 @@ import { addCI } from '@/modules/cmdb/api/ci'
|
|||||||
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
|
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
|
||||||
import { valueTypeMap } from '../../../utils/const'
|
import { valueTypeMap } from '../../../utils/const'
|
||||||
import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue'
|
import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue'
|
||||||
import { getCITypeParent } from '@/modules/cmdb/api/CITypeRelation'
|
import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CreateInstanceForm',
|
name: 'CreateInstanceForm',
|
||||||
@@ -166,6 +166,7 @@ export default {
|
|||||||
attributesByGroup: [],
|
attributesByGroup: [],
|
||||||
parentsType: [],
|
parentsType: [],
|
||||||
parentsForm: {},
|
parentsForm: {},
|
||||||
|
canEdit: {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -300,8 +301,16 @@ export default {
|
|||||||
this.batchUpdateLists = [{ name: this.attributeList[0].name }]
|
this.batchUpdateLists = [{ name: this.attributeList[0].name }]
|
||||||
})
|
})
|
||||||
if (action === 'create') {
|
if (action === 'create') {
|
||||||
getCITypeParent(this.typeId).then((res) => {
|
getCITypeParent(this.typeId).then(async (res) => {
|
||||||
this.parentsType = res.parents
|
for (let i = 0; i < res.parents.length; i++) {
|
||||||
|
await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => {
|
||||||
|
this.canEdit = {
|
||||||
|
..._.cloneDeep(this.canEdit),
|
||||||
|
[res.parents[i].id]: p_res.result,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.parentsType = res.parents.filter((parent) => this.canEdit[parent.id])
|
||||||
const _parentsForm = {}
|
const _parentsForm = {}
|
||||||
res.parents.forEach((item) => {
|
res.parents.forEach((item) => {
|
||||||
const _find = item.attributes.find((attr) => attr.id === item.unique_id)
|
const _find = item.attributes.find((attr) => attr.id === item.unique_id)
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
<div class="ci-detail-relation-table-title">
|
<div class="ci-detail-relation-table-title">
|
||||||
{{ parent.alias || parent.name }}
|
{{ parent.alias || parent.name }}
|
||||||
<a
|
<a
|
||||||
|
:disabled="!canEdit[parent.id]"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
$refs.addTableModal.openModal({ [`${ci.unique}`]: ci[ci.unique] }, ci._id, parent.id, 'parents')
|
$refs.addTableModal.openModal({ [`${ci.unique}`]: ci[ci.unique] }, ci._id, parent.id, 'parents')
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
><a-icon
|
><a-icon
|
||||||
type="plus-square"
|
type="plus-square"
|
||||||
/></a>
|
/></a>
|
||||||
|
<span v-if="!canEdit[parent.id]">(当前模型关系为多对多,请前往关系视图进行增删操作)</span>
|
||||||
</div>
|
</div>
|
||||||
<vxe-grid
|
<vxe-grid
|
||||||
v-if="firstCIs[parent.name]"
|
v-if="firstCIs[parent.name]"
|
||||||
@@ -38,7 +40,14 @@
|
|||||||
>
|
>
|
||||||
<template #operation_default="{ row }">
|
<template #operation_default="{ row }">
|
||||||
<a-popconfirm arrowPointAtCenter title="确认删除关系?" @confirm="deleteRelation(row._id, ciId)">
|
<a-popconfirm arrowPointAtCenter title="确认删除关系?" @confirm="deleteRelation(row._id, ciId)">
|
||||||
<a :style="{ color: 'red' }"><a-icon type="delete"/></a>
|
<a
|
||||||
|
:disabled="!canEdit[parent.id]"
|
||||||
|
:style="{
|
||||||
|
color: !canEdit[parent.id] ? 'rgba(0, 0, 0, 0.25)' : 'red',
|
||||||
|
}"
|
||||||
|
><a-icon
|
||||||
|
type="delete"
|
||||||
|
/></a>
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</template>
|
</template>
|
||||||
</vxe-grid>
|
</vxe-grid>
|
||||||
@@ -50,6 +59,7 @@
|
|||||||
<div class="ci-detail-relation-table-title">
|
<div class="ci-detail-relation-table-title">
|
||||||
{{ child.alias || child.name }}
|
{{ child.alias || child.name }}
|
||||||
<a
|
<a
|
||||||
|
:disabled="!canEdit[child.id]"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
$refs.addTableModal.openModal({ [`${ci.unique}`]: ci[ci.unique] }, ci._id, child.id, 'children')
|
$refs.addTableModal.openModal({ [`${ci.unique}`]: ci[ci.unique] }, ci._id, child.id, 'children')
|
||||||
@@ -58,6 +68,7 @@
|
|||||||
><a-icon
|
><a-icon
|
||||||
type="plus-square"
|
type="plus-square"
|
||||||
/></a>
|
/></a>
|
||||||
|
<span v-if="!canEdit[child.id]">(当前模型关系为多对多,请前往关系视图进行增删操作)</span>
|
||||||
</div>
|
</div>
|
||||||
<vxe-grid
|
<vxe-grid
|
||||||
v-if="secondCIs[child.name]"
|
v-if="secondCIs[child.name]"
|
||||||
@@ -72,7 +83,14 @@
|
|||||||
>
|
>
|
||||||
<template #operation_default="{ row }">
|
<template #operation_default="{ row }">
|
||||||
<a-popconfirm arrowPointAtCenter title="确认删除关系?" @confirm="deleteRelation(ciId, row._id)">
|
<a-popconfirm arrowPointAtCenter title="确认删除关系?" @confirm="deleteRelation(ciId, row._id)">
|
||||||
<a :style="{ color: 'red' }"><a-icon type="delete"/></a>
|
<a
|
||||||
|
:disabled="!canEdit[child.id]"
|
||||||
|
:style="{
|
||||||
|
color: !canEdit[child.id] ? 'rgba(0, 0, 0, 0.25)' : 'red',
|
||||||
|
}"
|
||||||
|
><a-icon
|
||||||
|
type="delete"
|
||||||
|
/></a>
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</template>
|
</template>
|
||||||
</vxe-grid>
|
</vxe-grid>
|
||||||
@@ -85,7 +103,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { getCITypeChildren, getCITypeParent } from '@/modules/cmdb/api/CITypeRelation'
|
import { getCITypeChildren, getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
|
||||||
import { searchCIRelation, deleteCIRelationView } from '@/modules/cmdb/api/CIRelation'
|
import { searchCIRelation, deleteCIRelationView } from '@/modules/cmdb/api/CIRelation'
|
||||||
import CiDetailRelationTopo from './ciDetailRelationTopo/index.vue'
|
import CiDetailRelationTopo from './ciDetailRelationTopo/index.vue'
|
||||||
import Node from './ciDetailRelationTopo/node.js'
|
import Node from './ciDetailRelationTopo/node.js'
|
||||||
@@ -118,6 +136,7 @@ export default {
|
|||||||
secondCIColumns: {},
|
secondCIColumns: {},
|
||||||
firstCIJsonAttr: {},
|
firstCIJsonAttr: {},
|
||||||
secondCIJsonAttr: {},
|
secondCIJsonAttr: {},
|
||||||
|
canEdit: {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -293,10 +312,16 @@ export default {
|
|||||||
.catch((e) => {})
|
.catch((e) => {})
|
||||||
},
|
},
|
||||||
async getParentCITypes() {
|
async getParentCITypes() {
|
||||||
await getCITypeParent(this.typeId)
|
const res = await getCITypeParent(this.typeId)
|
||||||
.then((res) => {
|
|
||||||
this.parentCITypes = res.parents
|
this.parentCITypes = res.parents
|
||||||
|
for (let i = 0; i < res.parents.length; i++) {
|
||||||
|
await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => {
|
||||||
|
this.canEdit = {
|
||||||
|
..._.cloneDeep(this.canEdit),
|
||||||
|
[res.parents[i].id]: p_res.result,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
const firstCIColumns = {}
|
const firstCIColumns = {}
|
||||||
const firstCIJsonAttr = {}
|
const firstCIJsonAttr = {}
|
||||||
res.parents.forEach((item) => {
|
res.parents.forEach((item) => {
|
||||||
@@ -325,14 +350,19 @@ export default {
|
|||||||
|
|
||||||
this.firstCIColumns = firstCIColumns
|
this.firstCIColumns = firstCIColumns
|
||||||
this.firstCIJsonAttr = firstCIJsonAttr
|
this.firstCIJsonAttr = firstCIJsonAttr
|
||||||
})
|
|
||||||
.catch((e) => {})
|
|
||||||
},
|
},
|
||||||
async getChildCITypes() {
|
async getChildCITypes() {
|
||||||
await getCITypeChildren(this.typeId)
|
const res = await getCITypeChildren(this.typeId)
|
||||||
.then((res) => {
|
|
||||||
this.childCITypes = res.children
|
|
||||||
|
|
||||||
|
this.childCITypes = res.children
|
||||||
|
for (let i = 0; i < res.children.length; i++) {
|
||||||
|
await getCanEditByParentIdChildId(this.typeId, res.children[i].id).then((c_res) => {
|
||||||
|
this.canEdit = {
|
||||||
|
..._.cloneDeep(this.canEdit),
|
||||||
|
[res.children[i].id]: c_res.result,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
const secondCIColumns = {}
|
const secondCIColumns = {}
|
||||||
const secondCIJsonAttr = {}
|
const secondCIJsonAttr = {}
|
||||||
res.children.forEach((item) => {
|
res.children.forEach((item) => {
|
||||||
@@ -361,8 +391,6 @@ export default {
|
|||||||
|
|
||||||
this.secondCIColumns = secondCIColumns
|
this.secondCIColumns = secondCIColumns
|
||||||
this.secondCIJsonAttr = secondCIJsonAttr
|
this.secondCIJsonAttr = secondCIJsonAttr
|
||||||
})
|
|
||||||
.catch((e) => {})
|
|
||||||
},
|
},
|
||||||
reload() {
|
reload() {
|
||||||
this.init()
|
this.init()
|
||||||
|
@@ -372,7 +372,7 @@ export default {
|
|||||||
width: 3,
|
width: 3,
|
||||||
fontColor: '#ffffff',
|
fontColor: '#ffffff',
|
||||||
bgColor: ['#6ABFFE', '#5375EB'],
|
bgColor: ['#6ABFFE', '#5375EB'],
|
||||||
chartColor: '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD', // 图表颜色
|
chartColor: '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF', // 图表颜色
|
||||||
isShowPreview: false,
|
isShowPreview: false,
|
||||||
filterExp: undefined,
|
filterExp: undefined,
|
||||||
previewData: null,
|
previewData: null,
|
||||||
@@ -410,7 +410,7 @@ export default {
|
|||||||
this.width = width
|
this.width = width
|
||||||
this.chartType = chartType
|
this.chartType = chartType
|
||||||
this.filterExp = item?.options?.filter ?? ''
|
this.filterExp = item?.options?.filter ?? ''
|
||||||
this.chartColor = item?.options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD'
|
this.chartColor = item?.options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF'
|
||||||
this.isShadow = item?.options?.isShadow ?? false
|
this.isShadow = item?.options?.isShadow ?? false
|
||||||
|
|
||||||
if (chartType === 'count') {
|
if (chartType === 'count') {
|
||||||
|
@@ -24,7 +24,7 @@ export const category_1_bar_options = (data, options) => {
|
|||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
|
|
||||||
color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(','),
|
color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(','),
|
||||||
grid: {
|
grid: {
|
||||||
top: 15,
|
top: 15,
|
||||||
left: 'left',
|
left: 'left',
|
||||||
@@ -83,7 +83,7 @@ export const category_1_bar_options = (data, options) => {
|
|||||||
export const category_1_line_options = (data, options) => {
|
export const category_1_line_options = (data, options) => {
|
||||||
const xData = Object.keys(data)
|
const xData = Object.keys(data)
|
||||||
return {
|
return {
|
||||||
color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(','),
|
color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(','),
|
||||||
grid: {
|
grid: {
|
||||||
top: 15,
|
top: 15,
|
||||||
left: 'left',
|
left: 'left',
|
||||||
@@ -117,7 +117,7 @@ export const category_1_line_options = (data, options) => {
|
|||||||
x2: 0,
|
x2: 0,
|
||||||
y2: 1,
|
y2: 1,
|
||||||
colorStops: [{
|
colorStops: [{
|
||||||
offset: 0, color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(',')[0] // 0% 处的颜色
|
offset: 0, color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(',')[0] // 0% 处的颜色
|
||||||
}, {
|
}, {
|
||||||
offset: 1, color: '#ffffff' // 100% 处的颜色
|
offset: 1, color: '#ffffff' // 100% 处的颜色
|
||||||
}],
|
}],
|
||||||
@@ -131,7 +131,7 @@ export const category_1_line_options = (data, options) => {
|
|||||||
|
|
||||||
export const category_1_pie_options = (data, options) => {
|
export const category_1_pie_options = (data, options) => {
|
||||||
return {
|
return {
|
||||||
color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(','),
|
color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(','),
|
||||||
grid: {
|
grid: {
|
||||||
top: 10,
|
top: 10,
|
||||||
left: 'left',
|
left: 'left',
|
||||||
@@ -181,7 +181,7 @@ export const category_2_bar_options = (data, options, chartType) => {
|
|||||||
})
|
})
|
||||||
const legend = [...new Set(_legend)]
|
const legend = [...new Set(_legend)]
|
||||||
return {
|
return {
|
||||||
color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(','),
|
color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(','),
|
||||||
grid: {
|
grid: {
|
||||||
top: 15,
|
top: 15,
|
||||||
left: 'left',
|
left: 'left',
|
||||||
@@ -249,7 +249,7 @@ export const category_2_bar_options = (data, options, chartType) => {
|
|||||||
x2: 0,
|
x2: 0,
|
||||||
y2: 1,
|
y2: 1,
|
||||||
colorStops: [{
|
colorStops: [{
|
||||||
offset: 0, color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(',')[index % 8] // 0% 处的颜色
|
offset: 0, color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(',')[index % 8] // 0% 处的颜色
|
||||||
}, {
|
}, {
|
||||||
offset: 1, color: '#ffffff' // 100% 处的颜色
|
offset: 1, color: '#ffffff' // 100% 处的颜色
|
||||||
}],
|
}],
|
||||||
@@ -269,7 +269,7 @@ export const category_2_pie_options = (data, options) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(','),
|
color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(','),
|
||||||
grid: {
|
grid: {
|
||||||
top: 15,
|
top: 15,
|
||||||
left: 'left',
|
left: 'left',
|
||||||
|
@@ -24,10 +24,8 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
list: [
|
list: [
|
||||||
'#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD',
|
'#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF',
|
||||||
'#C1A9DC,#E2B5CD,#EE8EBC,#8483C3,#4D66BD,#213764,#D9B6E9,#DD88EB',
|
'#9BA1F9,#0F2BA8,#A2EBFE,#4982F6,#FEB09C,#6C78E8,#FFDDAB,#4D66BD',
|
||||||
'#6FC4DF,#9FE8CE,#16B4BE,#86E6FB,#1871A3,#E1BF8D,#ED8D8D,#DD88EB',
|
|
||||||
'#F8B751,#FC9054,#FFE380,#DF963F,#AB5200,#EA9387,#FFBB7C,#D27467',
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -392,6 +392,7 @@ export default {
|
|||||||
origShowTypes: [],
|
origShowTypes: [],
|
||||||
leaf2showTypes: {},
|
leaf2showTypes: {},
|
||||||
node2ShowTypes: {},
|
node2ShowTypes: {},
|
||||||
|
level2constraint: {},
|
||||||
leaf: [],
|
leaf: [],
|
||||||
typeId: null,
|
typeId: null,
|
||||||
viewId: null,
|
viewId: null,
|
||||||
@@ -595,6 +596,17 @@ export default {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
q += `&root_id=${this.treeKeys[this.treeKeys.length - 1].split('%')[0]}`
|
q += `&root_id=${this.treeKeys[this.treeKeys.length - 1].split('%')[0]}`
|
||||||
|
|
||||||
|
if (
|
||||||
|
Object.keys(this.level2constraint).some(
|
||||||
|
(le) => le < Object.keys(this.level2constraint).length && this.level2constraint[le] === '2'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
q += `&ancestor_ids=${this.treeKeys
|
||||||
|
.slice(0, this.treeKeys.length - 1)
|
||||||
|
.map((item) => item.split('%')[0])
|
||||||
|
.join(',')}`
|
||||||
|
}
|
||||||
const typeId = parseInt(this.treeKeys[this.treeKeys.length - 1].split('%')[1])
|
const typeId = parseInt(this.treeKeys[this.treeKeys.length - 1].split('%')[1])
|
||||||
|
|
||||||
let level = []
|
let level = []
|
||||||
@@ -649,7 +661,19 @@ export default {
|
|||||||
|
|
||||||
if (refreshType === 'refreshNumber') {
|
if (refreshType === 'refreshNumber') {
|
||||||
const promises = this.treeKeys.map((key, index) => {
|
const promises = this.treeKeys.map((key, index) => {
|
||||||
|
let ancestor_ids
|
||||||
|
if (
|
||||||
|
Object.keys(this.level2constraint).some(
|
||||||
|
(le) => le < Object.keys(this.level2constraint).length && this.level2constraint[le] === '2'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ancestor_ids = `${this.treeKeys
|
||||||
|
.slice(0, index)
|
||||||
|
.map((item) => item.split('%')[0])
|
||||||
|
.join(',')}`
|
||||||
|
}
|
||||||
statisticsCIRelation({
|
statisticsCIRelation({
|
||||||
|
ancestor_ids,
|
||||||
root_ids: key.split('%')[0],
|
root_ids: key.split('%')[0],
|
||||||
level: this.treeKeys.length - index,
|
level: this.treeKeys.length - index,
|
||||||
type_ids: this.showTypes.map((type) => type.id).join(','),
|
type_ids: this.showTypes.map((type) => type.id).join(','),
|
||||||
@@ -780,16 +804,36 @@ export default {
|
|||||||
const index = topo_flatten.findIndex((id) => id === typeId)
|
const index = topo_flatten.findIndex((id) => id === typeId)
|
||||||
const _type = topo_flatten[index + 1]
|
const _type = topo_flatten[index + 1]
|
||||||
if (_type) {
|
if (_type) {
|
||||||
searchCIRelation(`q=_type:${_type}&root_id=${rootId}&level=1&count=10000`).then(async (res) => {
|
let q = `q=_type:${_type}&root_id=${rootId}&level=1&count=10000`
|
||||||
|
if (
|
||||||
|
Object.keys(this.level2constraint).some(
|
||||||
|
(le) => le < Object.keys(this.level2constraint).length && this.level2constraint[le] === '2'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
q += `&ancestor_ids=${this.treeKeys
|
||||||
|
.slice(0, this.treeKeys.length - 1)
|
||||||
|
.map((item) => item.split('%')[0])
|
||||||
|
.join(',')}`
|
||||||
|
}
|
||||||
|
searchCIRelation(q).then(async (res) => {
|
||||||
const facet = []
|
const facet = []
|
||||||
const ciIds = []
|
const ciIds = []
|
||||||
res.result.forEach((item) => {
|
res.result.forEach((item) => {
|
||||||
facet.push([item[item.unique], 0, item._id, item._type, item.unique])
|
facet.push([item[item.unique], 0, item._id, item._type, item.unique])
|
||||||
ciIds.push(item._id)
|
ciIds.push(item._id)
|
||||||
})
|
})
|
||||||
|
let ancestor_ids
|
||||||
|
if (
|
||||||
|
Object.keys(this.level2constraint).some(
|
||||||
|
(le) => le < Object.keys(this.level2constraint).length && this.level2constraint[le] === '2'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ancestor_ids = `${this.treeKeys.map((item) => item.split('%')[0]).join(',')}`
|
||||||
|
}
|
||||||
const promises = level.map((_level) => {
|
const promises = level.map((_level) => {
|
||||||
if (_level > 1) {
|
if (_level > 1) {
|
||||||
return statisticsCIRelation({
|
return statisticsCIRelation({
|
||||||
|
ancestor_ids,
|
||||||
root_ids: ciIds.join(','),
|
root_ids: ciIds.join(','),
|
||||||
level: _level - 1,
|
level: _level - 1,
|
||||||
type_ids: this.showTypes.map((type) => type.id).join(','),
|
type_ids: this.showTypes.map((type) => type.id).join(','),
|
||||||
@@ -889,6 +933,7 @@ export default {
|
|||||||
this.origShowTypeIds = showTypeIds
|
this.origShowTypeIds = showTypeIds
|
||||||
this.leaf2showTypes = this.relationViews.views[this.viewName].leaf2show_types
|
this.leaf2showTypes = this.relationViews.views[this.viewName].leaf2show_types
|
||||||
this.node2ShowTypes = this.relationViews.views[this.viewName].node2show_types
|
this.node2ShowTypes = this.relationViews.views[this.viewName].node2show_types
|
||||||
|
this.level2constraint = this.relationViews.views[this.viewName].level2constraint
|
||||||
this.leaf = this.relationViews.views[this.viewName].leaf
|
this.leaf = this.relationViews.views[this.viewName].leaf
|
||||||
this.currentView = `${this.viewId}`
|
this.currentView = `${this.viewId}`
|
||||||
this.typeId = this.levels[0][0]
|
this.typeId = this.levels[0][0]
|
||||||
@@ -923,6 +968,17 @@ export default {
|
|||||||
const _tempTree = splitTreeKey[splitTreeKey.length - 1].split('%')
|
const _tempTree = splitTreeKey[splitTreeKey.length - 1].split('%')
|
||||||
const firstCIObj = JSON.parse(_tempTree[2])
|
const firstCIObj = JSON.parse(_tempTree[2])
|
||||||
const firstCIId = _tempTree[0]
|
const firstCIId = _tempTree[0]
|
||||||
|
let ancestor_ids
|
||||||
|
if (
|
||||||
|
Object.keys(this.level2constraint).some(
|
||||||
|
(le) => le < Object.keys(this.level2constraint).length && this.level2constraint[le] === '2'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const ancestor = treeKey
|
||||||
|
.split('@^@')
|
||||||
|
.slice(0, menuKey === 'delete' ? treeKey.split('@^@').length - 2 : treeKey.split('@^@').length - 1)
|
||||||
|
ancestor_ids = ancestor.map((item) => item.split('%')[0]).join(',')
|
||||||
|
}
|
||||||
if (menuKey === 'delete') {
|
if (menuKey === 'delete') {
|
||||||
const _tempTreeParent = splitTreeKey[splitTreeKey.length - 2].split('%')
|
const _tempTreeParent = splitTreeKey[splitTreeKey.length - 2].split('%')
|
||||||
const that = this
|
const that = this
|
||||||
@@ -935,16 +991,17 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
onOk() {
|
onOk() {
|
||||||
deleteCIRelationView(_tempTreeParent[0], _tempTree[0]).then((res) => {
|
deleteCIRelationView(_tempTreeParent[0], _tempTree[0], { ancestor_ids }).then((res) => {
|
||||||
that.$message.success('删除成功!')
|
that.$message.success('删除成功!')
|
||||||
|
setTimeout(() => {
|
||||||
that.reload()
|
that.reload()
|
||||||
|
}, 500)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const childTypeId = menuKey
|
const childTypeId = menuKey
|
||||||
console.log(menuKey)
|
this.$refs.addTableModal.openModal(firstCIObj, firstCIId, childTypeId, 'children', ancestor_ids)
|
||||||
this.$refs.addTableModal.openModal(firstCIObj, firstCIId, childTypeId, 'children')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -964,9 +1021,21 @@ export default {
|
|||||||
onOk() {
|
onOk() {
|
||||||
const _tempTree = that.treeKeys[that.treeKeys.length - 1].split('%')
|
const _tempTree = that.treeKeys[that.treeKeys.length - 1].split('%')
|
||||||
const first_ci_id = Number(_tempTree[0])
|
const first_ci_id = Number(_tempTree[0])
|
||||||
|
let ancestor_ids
|
||||||
|
if (
|
||||||
|
Object.keys(that.level2constraint).some(
|
||||||
|
(le) => le < Object.keys(that.level2constraint).length && that.level2constraint[le] === '2'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ancestor_ids = `${that.treeKeys
|
||||||
|
.slice(0, that.treeKeys.length - 1)
|
||||||
|
.map((item) => item.split('%')[0])
|
||||||
|
.join(',')}`
|
||||||
|
}
|
||||||
batchDeleteCIRelation(
|
batchDeleteCIRelation(
|
||||||
that.selectedRowKeys.map((item) => item._id),
|
that.selectedRowKeys.map((item) => item._id),
|
||||||
[first_ci_id]
|
[first_ci_id],
|
||||||
|
ancestor_ids
|
||||||
).then((res) => {
|
).then((res) => {
|
||||||
that.$refs.xTable.clearCheckboxRow()
|
that.$refs.xTable.clearCheckboxRow()
|
||||||
that.$refs.xTable.clearCheckboxReserve()
|
that.$refs.xTable.clearCheckboxReserve()
|
||||||
@@ -1130,7 +1199,10 @@ export default {
|
|||||||
const $table = this.$refs['xTable']
|
const $table = this.$refs['xTable']
|
||||||
const data = {}
|
const data = {}
|
||||||
this.columns.forEach((item) => {
|
this.columns.forEach((item) => {
|
||||||
if (!(item.field in this.initialPasswordValue) && !_.isEqual(row[item.field], this.initialInstanceList[rowIndex][item.field])) {
|
if (
|
||||||
|
!(item.field in this.initialPasswordValue) &&
|
||||||
|
!_.isEqual(row[item.field], this.initialInstanceList[rowIndex][item.field])
|
||||||
|
) {
|
||||||
data[item.field] = row[item.field] ?? null
|
data[item.field] = row[item.field] ?? null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1180,7 +1252,18 @@ export default {
|
|||||||
},
|
},
|
||||||
sumbitFromCreateInstance({ ci_id }) {
|
sumbitFromCreateInstance({ ci_id }) {
|
||||||
const first_ci_id = this.treeKeys[this.treeKeys.length - 1].split('%')[0]
|
const first_ci_id = this.treeKeys[this.treeKeys.length - 1].split('%')[0]
|
||||||
addCIRelationView(first_ci_id, ci_id).then((res) => {
|
let ancestor_ids
|
||||||
|
if (
|
||||||
|
Object.keys(this.level2constraint).some(
|
||||||
|
(le) => le < Object.keys(this.level2constraint).length && this.level2constraint[le] === '2'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ancestor_ids = `${this.treeKeys
|
||||||
|
.slice(0, this.treeKeys.length - 1)
|
||||||
|
.map((item) => item.split('%')[0])
|
||||||
|
.join(',')}`
|
||||||
|
}
|
||||||
|
addCIRelationView(first_ci_id, ci_id, { ancestor_ids }).then((res) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.loadData({}, 'refreshNumber')
|
this.loadData({}, 'refreshNumber')
|
||||||
}, 500)
|
}, 500)
|
||||||
@@ -1270,9 +1353,7 @@ export default {
|
|||||||
})
|
})
|
||||||
Promise.all(promises)
|
Promise.all(promises)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
that.$message.success({
|
that.$message.success('删除成功')
|
||||||
message: '删除成功',
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
|
@@ -86,6 +86,7 @@ export default {
|
|||||||
isFocusExpression: false,
|
isFocusExpression: false,
|
||||||
type: 'children',
|
type: 'children',
|
||||||
preferenceAttrList: [],
|
preferenceAttrList: [],
|
||||||
|
ancestor_ids: undefined,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -101,13 +102,13 @@ export default {
|
|||||||
},
|
},
|
||||||
watch: {},
|
watch: {},
|
||||||
methods: {
|
methods: {
|
||||||
async openModal(ciObj, ciId, addTypeId, type) {
|
async openModal(ciObj, ciId, addTypeId, type, ancestor_ids = undefined) {
|
||||||
console.log(ciObj, addTypeId)
|
|
||||||
this.visible = true
|
this.visible = true
|
||||||
this.ciObj = ciObj
|
this.ciObj = ciObj
|
||||||
this.ciId = ciId
|
this.ciId = ciId
|
||||||
this.addTypeId = addTypeId
|
this.addTypeId = addTypeId
|
||||||
this.type = type
|
this.type = type
|
||||||
|
this.ancestor_ids = ancestor_ids
|
||||||
await getSubscribeAttributes(addTypeId).then((res) => {
|
await getSubscribeAttributes(addTypeId).then((res) => {
|
||||||
this.preferenceAttrList = res.attributes // 已经订阅的全部列
|
this.preferenceAttrList = res.attributes // 已经订阅的全部列
|
||||||
})
|
})
|
||||||
@@ -168,13 +169,15 @@ export default {
|
|||||||
const ciIds = [...selectRecordsCurrent, ...selectRecordsReserved].map((record) => record._id)
|
const ciIds = [...selectRecordsCurrent, ...selectRecordsReserved].map((record) => record._id)
|
||||||
if (ciIds.length) {
|
if (ciIds.length) {
|
||||||
if (this.type === 'children') {
|
if (this.type === 'children') {
|
||||||
await batchUpdateCIRelationChildren(ciIds, [this.ciId])
|
await batchUpdateCIRelationChildren(ciIds, [this.ciId], this.ancestor_ids)
|
||||||
} else {
|
} else {
|
||||||
await batchUpdateCIRelationParents(ciIds, [this.ciId])
|
await batchUpdateCIRelationParents(ciIds, [this.ciId])
|
||||||
}
|
}
|
||||||
|
setTimeout(() => {
|
||||||
this.$message.success('添加成功!')
|
this.$message.success('添加成功!')
|
||||||
this.handleClose()
|
this.handleClose()
|
||||||
this.$emit('reload')
|
this.$emit('reload')
|
||||||
|
}, 500)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleSearch() {
|
handleSearch() {
|
||||||
|
@@ -24,13 +24,15 @@ services:
|
|||||||
cmdb-cache:
|
cmdb-cache:
|
||||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-cache:3.0
|
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-cache:3.0
|
||||||
container_name: cmdb-cache
|
container_name: cmdb-cache
|
||||||
|
environment:
|
||||||
|
TZ: Asia/Shanghai
|
||||||
networks:
|
networks:
|
||||||
new:
|
new:
|
||||||
aliases:
|
aliases:
|
||||||
- redis
|
- redis
|
||||||
|
|
||||||
cmdb-api:
|
cmdb-api:
|
||||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.3.6
|
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.3.7
|
||||||
# build:
|
# build:
|
||||||
# context: .
|
# context: .
|
||||||
# target: cmdb-api
|
# target: cmdb-api
|
||||||
@@ -65,7 +67,7 @@ services:
|
|||||||
- cmdb-api
|
- cmdb-api
|
||||||
|
|
||||||
cmdb-ui:
|
cmdb-ui:
|
||||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.3.6
|
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.3.7
|
||||||
# build:
|
# build:
|
||||||
# context: .
|
# context: .
|
||||||
# target: cmdb-ui
|
# target: cmdb-ui
|
||||||
|
@@ -68,20 +68,35 @@
|
|||||||
|
|
||||||
### One-Click Docker Quick Build
|
### One-Click Docker Quick Build
|
||||||
|
|
||||||
- Prepare: install docker and docker-compose
|
> Method 1
|
||||||
- In directory cmdb
|
- step 1: **Prepare: install docker and docker-compose**
|
||||||
```
|
- step 2: copy the repository
|
||||||
docker-compose up -d
|
```shell
|
||||||
```
|
git clone https://github.com/veops/cmdb.git
|
||||||
- View: [http://127.0.0.1:8000](http://127.0.0.1:8000)
|
```
|
||||||
- username: demo or admin
|
- step 3: In directory cmdb:
|
||||||
- password: 123456
|
```
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
> Method 2 Usefull for linux os.
|
||||||
|
- step 1: **Prepare: install docker and docker-compose**
|
||||||
|
- step 2: directly use the install.sh file in the project's root directory to `install`, `start`, `pause`, `status`, `delete`, and `uninstall` the application.
|
||||||
|
```shell
|
||||||
|
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/master/install.sh
|
||||||
|
sh install.sh install
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### [Local Setup](local_en.md)
|
### [Local Setup](local_en.md)
|
||||||
|
|
||||||
### [Installation with Makefile](makefile_en.md)
|
### [Installation with Makefile](makefile_en.md)
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
- View: [http://127.0.0.1:8000](http://127.0.0.1:8000)
|
||||||
|
- username: demo or admin
|
||||||
|
- password: 123456
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
1. Fork it
|
1. Fork it
|
||||||
|
@@ -22,7 +22,7 @@ cp cmdb-api/settings.example.py cmdb-api/settings.py
|
|||||||
- 后端: `cd cmdb-api && pipenv run pipenv install && cd ..`
|
- 后端: `cd cmdb-api && pipenv run pipenv install && cd ..`
|
||||||
- 前端: `cd cmdb-ui && yarn install && cd ..`
|
- 前端: `cd cmdb-ui && yarn install && cd ..`
|
||||||
- 可以将 docs/cmdb.sql 导入到数据库里,登录用户和密码分别是:demo/123456
|
- 可以将 docs/cmdb.sql 导入到数据库里,登录用户和密码分别是:demo/123456
|
||||||
- 创建数据库表: 进入**cmdb-api**目录执行 `pipenv run flask db-setup && pipenv run flask cmdb-init-cache`
|
- 创建数据库表: 进入**cmdb-api**目录执行 `pipenv run flask db-setup && pipenv run flask common-check-new-columns && pipenv run flask cmdb-init-cache`
|
||||||
- 启动服务
|
- 启动服务
|
||||||
|
|
||||||
- 后端: 进入**cmdb-api**目录执行 `pipenv run flask run -h 0.0.0.0`
|
- 后端: 进入**cmdb-api**目录执行 `pipenv run flask run -h 0.0.0.0`
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
- frontend: `cd cmdb-ui && yarn install && cd ..`
|
- frontend: `cd cmdb-ui && yarn install && cd ..`
|
||||||
- Suggest step: (default: user:demo,password:123456)
|
- Suggest step: (default: user:demo,password:123456)
|
||||||
- Create tables of cmdb database:
|
- Create tables of cmdb database:
|
||||||
in **cmdb-api** directory: `pipenv run flask db-setup && pipenv run flask cmdb-init-cache`
|
in **cmdb-api** directory: `pipenv run flask db-setup && pipenv run flask common-check-new-columns && pipenv run flask cmdb-init-cache`
|
||||||
|
|
||||||
` source docs/cmdb.sql`
|
` source docs/cmdb.sql`
|
||||||
|
|
||||||
|
@@ -46,10 +46,11 @@ read_rnd_buffer_size=512K
|
|||||||
skip-name-resolve
|
skip-name-resolve
|
||||||
max_connections=1000
|
max_connections=1000
|
||||||
slow_query_log = ON
|
slow_query_log = ON
|
||||||
slow_query_log_file = /var/log/mysql_slow.log
|
slow_query_log_file = /tmp/mysql_slow.log
|
||||||
long_query_time = 1
|
long_query_time = 1
|
||||||
sql_mode="STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
|
sql_mode="STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
|
||||||
#log-error = /var/log/mysql/error.log
|
#log-error = /var/log/mysql/error.log
|
||||||
# By default we only accept connections from localhost
|
# By default we only accept connections from localhost
|
||||||
#bind-address = 127.0.0.1
|
#bind-address = 127.0.0.1
|
||||||
# Disabling symbolic-links is recommended to prevent assorted security risks
|
# Disabling symbolic-links is recommended to prevent assorted security risks
|
||||||
|
log_timestamps = SYSTEM
|
||||||
|
157
install.sh
Normal file
157
install.sh
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
current_path=$(pwd)
|
||||||
|
cmdb_dir=$(cd ~ && pwd)/apps
|
||||||
|
|
||||||
|
check_docker() {
|
||||||
|
docker info >/dev/null 2>&1
|
||||||
|
if ! [ $? -eq 0 ]; then
|
||||||
|
echo "error: please install and start docker firstly"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_docker_compose() {
|
||||||
|
docker-compose --version >/dev/null 2>&1
|
||||||
|
if ! [ $? -eq 0 ]; then
|
||||||
|
echo "error: please install docker-compose firstly"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
clone_repo() {
|
||||||
|
local repo_url=$1
|
||||||
|
git clone $repo_url || {
|
||||||
|
echo "error: failed to clone $repo_url"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
change_directory() {
|
||||||
|
local new_dir=$1
|
||||||
|
if ! mkdir -p "$new_dir"; then
|
||||||
|
echo "error: failed to create directory $new_dir"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cd "$new_dir" || exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
install_service() {
|
||||||
|
echo ""
|
||||||
|
echo "Installing the service $1..."
|
||||||
|
change_directory "$cmdb_dir"
|
||||||
|
|
||||||
|
if [ -d "${cmdb_dir}/cmdb" ]; then
|
||||||
|
echo "directory ${cmdb_dir}/cmdb already exist"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
clone_repo "https://githubfast.com/veops/cmdb.git" || clone_repo "https://github.com/veops/cmdb.git"
|
||||||
|
cd ${cmdb_dir}/cmdb || exit 1
|
||||||
|
docker-compose pull
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "successfully install package in directory: ${cmdb_dir}/cmdb"
|
||||||
|
fi
|
||||||
|
cd $current_path || exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
start_service() {
|
||||||
|
echo "Starting the service $1..."
|
||||||
|
cd ${cmdb_dir}/cmdb
|
||||||
|
docker-compose up -d
|
||||||
|
cd $current_path
|
||||||
|
}
|
||||||
|
|
||||||
|
pause_service() {
|
||||||
|
case $2 in
|
||||||
|
"" | cmdb-api | cmdb-ui | cmdb-db | cmdb-cache)
|
||||||
|
echo "Pausing the service ..."
|
||||||
|
|
||||||
|
cd ${cmdb_dir}/cmdb || exit 1
|
||||||
|
docker-compose stop $2
|
||||||
|
|
||||||
|
cd $current_path || exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Please input invalid service name: [cmdb-api|cmdb-ui|cmdb-db|cmdb-cache]"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_service() {
|
||||||
|
echo "Deleting the service ..."
|
||||||
|
cd ${cmdb_dir}/cmdb || exit 1
|
||||||
|
docker-compose down
|
||||||
|
cd $current_path || exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
status_service() {
|
||||||
|
cd ${cmdb_dir}/cmdb || exit 1
|
||||||
|
docker-compose ps
|
||||||
|
cd $current_path || exit 1
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall_service() {
|
||||||
|
if ! [ -d "${cmdb_dir}/cmdb" ]; then
|
||||||
|
echo "directory ${cmdb_dir}/cmdb already not exist"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -p "Are you sure to uninstall the all the application and data? y/n:" input
|
||||||
|
if [ $input = "y" ]; then
|
||||||
|
echo "Uninstalling the service ..."
|
||||||
|
|
||||||
|
cd ${cmdb_dir}/cmdb || exit 1
|
||||||
|
docker-compose down -v
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
rm -fr ${cmdb_dir}/cmdb
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd $current_path || exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Welcome to the CMDB service management script!"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
check_depend() {
|
||||||
|
check_docker
|
||||||
|
check_docker_compose
|
||||||
|
}
|
||||||
|
|
||||||
|
case $1 in
|
||||||
|
install)
|
||||||
|
check_depend
|
||||||
|
install_service $2
|
||||||
|
;;
|
||||||
|
start)
|
||||||
|
check_depend
|
||||||
|
start_service $2
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
check_depend
|
||||||
|
status_service $2
|
||||||
|
;;
|
||||||
|
pause)
|
||||||
|
check_depend
|
||||||
|
pause_service $2
|
||||||
|
;;
|
||||||
|
delete)
|
||||||
|
check_depend
|
||||||
|
delete_service $2
|
||||||
|
;;
|
||||||
|
uninstall)
|
||||||
|
check_depend
|
||||||
|
uninstall_service $2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 [install|start|pause|uninstall]"
|
||||||
|
echo "install Used to install the application"
|
||||||
|
echo "start Used to start the application"
|
||||||
|
echo "status Used to show status of the application"
|
||||||
|
echo "pause Used to pause the application"
|
||||||
|
echo "delete Used to delete the application"
|
||||||
|
echo "uninstall Used to uninstall the application, include all data"
|
||||||
|
;;
|
||||||
|
esac
|
Reference in New Issue
Block a user