mirror of
https://github.com/veops/cmdb.git
synced 2025-09-03 03:06:56 +08:00
Compare commits
10 Commits
dependabot
...
deploy_on_
Author | SHA1 | Date | |
---|---|---|---|
|
c1a6adc32c | ||
|
7f49ae3dfb | ||
|
5fe27f8678 | ||
|
0924b8846f | ||
|
62a669159a | ||
|
2933bf1efa | ||
|
981f8b0145 | ||
|
213bda671c | ||
|
837aabfe77 | ||
|
cc599d414a |
26
README.md
26
README.md
@@ -73,20 +73,34 @@
|
||||
## 安装
|
||||
|
||||
### Docker 一键快速构建
|
||||
- 进入主目录(先安装 docker 环境, 注意要clone整个项目)
|
||||
|
||||
> 方法一
|
||||
- 第一步: 先安装 docker 环境, 以及docker-compose
|
||||
- 第二步: 拷贝项目
|
||||
```shell
|
||||
git clone https://github.com/veops/cmdb.git
|
||||
```
|
||||
- 第三步:进入主目录,执行:
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
- 浏览器打开: [http://127.0.0.1:8000](http://127.0.0.1:8000)
|
||||
- username: demo 或者 admin
|
||||
- password: 123456
|
||||
> 方法二, 该方法适用于linux系统
|
||||
- 第一步: 先安装 docker 环境, 以及docker-compose
|
||||
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
|
||||
```shell
|
||||
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/deploy_on_kylin_docker/install.sh
|
||||
sh install.sh install
|
||||
```
|
||||
|
||||
### [本地开发环境搭建](docs/local.md)
|
||||
|
||||
### [Makefile 安装](docs/makefile.md)
|
||||
|
||||
## 验证
|
||||
- 浏览器打开: [http://127.0.0.1:8000](http://127.0.0.1:8000)
|
||||
- username: demo 或者 admin
|
||||
- password: 123456
|
||||
|
||||
|
||||
---
|
||||
|
||||
_**欢迎关注公众号(维易科技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 REDIS_PREFIX_CI
|
||||
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 RoleEnum
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
@@ -49,12 +50,17 @@ def cmdb_init_cache():
|
||||
|
||||
ci_relations = CIRelation.get_by(to_dict=False)
|
||||
relations = dict()
|
||||
relations2 = dict()
|
||||
for cr in ci_relations:
|
||||
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:
|
||||
relations[i] = json.dumps(relations[i])
|
||||
if relations:
|
||||
rd.create_or_update(relations, REDIS_PREFIX_CI_RELATION)
|
||||
if relations2:
|
||||
rd.create_or_update(relations2, REDIS_PREFIX_CI_RELATION2)
|
||||
|
||||
es = None
|
||||
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
|
||||
|
||||
ci_type = CITypeCache.get(ci.type_id)
|
||||
if not ci_type:
|
||||
return res
|
||||
|
||||
res["ci_type"] = ci_type.name
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
ad_ci = AutoDiscoveryCI.get_by(ci_id=ci_id, to_dict=False, first=True)
|
||||
@@ -886,12 +891,14 @@ class CIRelationManager(object):
|
||||
|
||||
@classmethod
|
||||
def get_ancestor_ids(cls, ci_ids, level=1):
|
||||
for _ in range(level):
|
||||
cis = db.session.query(CIRelation.first_ci_id).filter(
|
||||
level2ids = dict()
|
||||
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))
|
||||
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
|
||||
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"))
|
||||
|
||||
@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)
|
||||
second_ci = CIManager.confirm_ci_existed(second_ci_id)
|
||||
|
||||
existed = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
ancestor_ids=ancestor_ids,
|
||||
to_dict=False,
|
||||
first=True)
|
||||
if existed is not None:
|
||||
@@ -960,11 +968,12 @@ class CIRelationManager(object):
|
||||
|
||||
existed = CIRelation.create(first_ci_id=first_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)
|
||||
|
||||
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:
|
||||
existed.upadte(more=more)
|
||||
@@ -988,53 +997,56 @@ class CIRelationManager(object):
|
||||
his_manager = CIRelationHistoryManager()
|
||||
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
|
||||
|
||||
@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,
|
||||
second_ci_id=second_ci_id,
|
||||
ancestor_ids=ancestor_ids,
|
||||
to_dict=False,
|
||||
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
|
||||
def batch_update(cls, ci_ids, parents, children):
|
||||
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
|
||||
"""
|
||||
only for many to one
|
||||
:param ci_ids:
|
||||
:param parents:
|
||||
:param children:
|
||||
:param ancestor_ids:
|
||||
:return:
|
||||
"""
|
||||
if isinstance(parents, list):
|
||||
for parent_id in parents:
|
||||
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):
|
||||
for child_id in children:
|
||||
for ci_id in ci_ids:
|
||||
cls.add(ci_id, child_id)
|
||||
cls.add(ci_id, child_id, ancestor_ids=ancestor_ids)
|
||||
|
||||
@classmethod
|
||||
def batch_delete(cls, ci_ids, parents):
|
||||
def batch_delete(cls, ci_ids, parents, ancestor_ids=None):
|
||||
"""
|
||||
only for many to one
|
||||
:param ci_ids:
|
||||
:param parents:
|
||||
:param ancestor_ids:
|
||||
:return:
|
||||
"""
|
||||
|
||||
if isinstance(parents, list):
|
||||
for parent_id in parents:
|
||||
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):
|
||||
|
@@ -637,6 +637,16 @@ class CITypeRelationManager(object):
|
||||
current_app.logger.warning(str(e))
|
||||
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)
|
||||
if existed is not None:
|
||||
existed.update(relation_type_id=relation_type_id,
|
||||
@@ -686,6 +696,24 @@ class CITypeRelationManager(object):
|
||||
|
||||
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):
|
||||
cls = CITypeAttributeGroup
|
||||
|
@@ -100,6 +100,7 @@ class AttributeDefaultValueEnum(BaseEnum):
|
||||
CMDB_QUEUE = "one_cmdb_async"
|
||||
REDIS_PREFIX_CI = "ONE_CMDB"
|
||||
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
||||
REDIS_PREFIX_CI_RELATION2 = "CMDB_CI_RELATION2"
|
||||
|
||||
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 CITypeAttributesCache
|
||||
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.resp_format import ErrFormat
|
||||
from api.lib.exception import AbortException
|
||||
@@ -229,14 +232,28 @@ class PreferenceManager(object):
|
||||
if not parents:
|
||||
return
|
||||
|
||||
for l in leaf:
|
||||
_find_parent(l)
|
||||
for _l in leaf:
|
||||
_find_parent(_l)
|
||||
|
||||
for node_id in node2show_types:
|
||||
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))),
|
||||
topo_flatten=list(toposort.toposort_flatten(topo)),
|
||||
topo_flatten=topo_flatten,
|
||||
level2constraint=level2constraint,
|
||||
leaf=leaf,
|
||||
leaf2show_types=leaf2show_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):
|
||||
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 = "主键字段 {} 缺失"
|
||||
ci_is_already_existed = "CI 已经存在!"
|
||||
relation_constraint = "关系约束: {}, 校验失败 "
|
||||
m2m_relation_constraint = "多对多关系 限制: 模型 {} <-> {} 已经存在多对多关系!"
|
||||
relation_not_found = "CI关系: {} 不存在"
|
||||
ci_search_Parentheses_invalid = "搜索表达式里小括号前不支持: 或、非"
|
||||
|
||||
|
@@ -1,6 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import json
|
||||
from collections import Counter
|
||||
|
||||
@@ -10,11 +8,14 @@ from flask import current_app
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
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_RELATION2
|
||||
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.es.search import Search as SearchFromES
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
|
||||
|
||||
class Search(object):
|
||||
@@ -26,7 +27,8 @@ class Search(object):
|
||||
page=1,
|
||||
count=None,
|
||||
sort=None,
|
||||
reverse=False):
|
||||
reverse=False,
|
||||
ancestor_ids=None):
|
||||
self.orig_query = query
|
||||
self.fl = fl
|
||||
self.facet_field = facet_field
|
||||
@@ -38,25 +40,81 @@ class Search(object):
|
||||
self.level = level or 0
|
||||
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 = []
|
||||
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):
|
||||
_tmp = list(map(lambda x: list(json.loads(x).keys()),
|
||||
filter(lambda x: x is not None, rd.get(ids, REDIS_PREFIX_CI_RELATION) or [])))
|
||||
if not self.has_m2m:
|
||||
_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]
|
||||
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:
|
||||
merge_ids.extend(ids)
|
||||
|
||||
return merge_ids
|
||||
|
||||
def _get_reverse_ids(self):
|
||||
def _get_reverse_ids(self, 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):
|
||||
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:
|
||||
merge_ids.extend(ids)
|
||||
if level in level2ids and level2ids[level]:
|
||||
merge_ids.extend(set(ids) & set(level2ids[level]))
|
||||
else:
|
||||
merge_ids.extend(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
|
||||
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
|
||||
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))
|
||||
else:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
# cis, counter, total, self.page, numfound, facet_
|
||||
@@ -105,35 +163,65 @@ class Search(object):
|
||||
|
||||
def statistics(self, type_ids):
|
||||
self.level = int(self.level)
|
||||
_tmp = []
|
||||
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
for lv in range(0, self.level):
|
||||
if not lv:
|
||||
if type_ids and lv == self.level - 1:
|
||||
_tmp = []
|
||||
level2ids = {}
|
||||
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],
|
||||
(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:
|
||||
_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:
|
||||
for idx, item in enumerate(_tmp):
|
||||
if item:
|
||||
if type_ids and lv == self.level - 1:
|
||||
__tmp = list(
|
||||
map(lambda x: [(_id, type_id) for _id, type_id in json.loads(x).items()
|
||||
if type_id in type_ids],
|
||||
filter(lambda x: x is not None,
|
||||
rd.get([i[0] for i in item], REDIS_PREFIX_CI_RELATION) or [])))
|
||||
if not self.has_m2m:
|
||||
key, prefix = [i[0] for i in item], REDIS_PREFIX_CI_RELATION
|
||||
else:
|
||||
key = list(set(['{},{}'.format(j, i[0]) for i in item for j in level2ids[lv - 1][idx]]))
|
||||
prefix = REDIS_PREFIX_CI_RELATION2
|
||||
|
||||
__tmp = list(map(lambda x: list(json.loads(x).items()),
|
||||
filter(lambda x: x is not None,
|
||||
rd.get([i[0] for i in item], REDIS_PREFIX_CI_RELATION) or [])))
|
||||
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],
|
||||
filter(lambda x: x is not None,
|
||||
rd.get(key, prefix) or []))
|
||||
else:
|
||||
__tmp = map(lambda x: list(json.loads(x).items()),
|
||||
filter(lambda x: x is not None,
|
||||
rd.get(key, prefix) or []))
|
||||
else:
|
||||
__tmp = []
|
||||
|
||||
_tmp[idx] = [j for i in __tmp for j in i]
|
||||
else:
|
||||
_tmp[idx] = []
|
||||
level2ids[lv].append([])
|
||||
|
||||
result = {str(_id): len(_tmp[idx]) for idx, _id in enumerate(ids)}
|
||||
|
||||
|
@@ -38,7 +38,6 @@ def string_to_bytes(value):
|
||||
byte_string = value
|
||||
else:
|
||||
byte_string = value.encode("utf-8")
|
||||
|
||||
return byte_string
|
||||
|
||||
|
||||
@@ -314,7 +313,7 @@ class KeyManage:
|
||||
secrets_root_key = current_app.config.get("secrets_root_key")
|
||||
msg, ok = self.is_valid_root_key(secrets_root_key)
|
||||
if not ok:
|
||||
return {"message": msg, "status": "failed"}
|
||||
return true
|
||||
status = self.backend.get(backend_seal_key)
|
||||
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)
|
||||
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")
|
||||
second_ci = db.relationship("CI", primaryjoin="CI.id==CIRelation.second_ci_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 REDIS_PREFIX_CI
|
||||
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 reconnect_db
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
@@ -97,16 +98,30 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
|
||||
@celery.task(name="cmdb.ci_relation_cache", queue=CMDB_QUEUE)
|
||||
@flush_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)):
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
if ancestor_ids is None:
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
|
||||
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, first=True, to_dict=False)
|
||||
if str(child_id) not in children:
|
||||
children[str(child_id)] = cr.second_ci.type_id
|
||||
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
|
||||
first=True, to_dict=False)
|
||||
if str(child_id) not in children:
|
||||
children[str(child_id)] = cr.second_ci.type_id
|
||||
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
|
||||
else:
|
||||
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))
|
||||
|
||||
@@ -156,20 +171,31 @@ def ci_relation_add(parent_dict, child_id, uid):
|
||||
try:
|
||||
db.session.commit()
|
||||
except:
|
||||
pass
|
||||
db.session.rollback()
|
||||
|
||||
|
||||
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
|
||||
@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)):
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
if ancestor_ids is None:
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
|
||||
if str(child_id) in children:
|
||||
children.pop(str(child_id))
|
||||
if str(child_id) in children:
|
||||
children.pop(str(child_id))
|
||||
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
|
||||
else:
|
||||
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))
|
||||
|
||||
|
@@ -35,6 +35,7 @@ class CIRelationSearchView(APIView):
|
||||
count = get_page_size(request.values.get("count") or request.values.get("page_size"))
|
||||
|
||||
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'))))
|
||||
|
||||
query = request.values.get('q', "")
|
||||
@@ -44,7 +45,7 @@ class CIRelationSearchView(APIView):
|
||||
reverse = request.values.get("reverse") in current_app.config.get('BOOL_TRUE')
|
||||
|
||||
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:
|
||||
response, counter, total, page, numfound, facet = s.search()
|
||||
except SearchError as e:
|
||||
@@ -67,9 +68,10 @@ class CIRelationStatisticsView(APIView):
|
||||
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
|
||||
level = request.values.get('level', 1)
|
||||
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()
|
||||
s = Search(root_ids, level)
|
||||
s = Search(root_ids, level, ancestor_ids=ancestor_ids)
|
||||
try:
|
||||
result = s.statistics(type_ids)
|
||||
except SearchError as e:
|
||||
@@ -121,14 +123,18 @@ class CIRelationView(APIView):
|
||||
url_prefix = "/ci_relations/<int:first_ci_id>/<int:second_ci_id>"
|
||||
|
||||
def post(self, first_ci_id, second_ci_id):
|
||||
ancestor_ids = request.values.get('ancestor_ids') or None
|
||||
|
||||
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)
|
||||
|
||||
def delete(self, first_ci_id, second_ci_id):
|
||||
ancestor_ids = request.values.get('ancestor_ids') or None
|
||||
|
||||
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")
|
||||
|
||||
@@ -151,8 +157,9 @@ class BatchCreateOrUpdateCIRelationView(APIView):
|
||||
ci_ids = list(map(int, request.values.get('ci_ids')))
|
||||
parents = list(map(int, request.values.get('parents', [])))
|
||||
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)
|
||||
|
||||
@@ -166,7 +173,8 @@ class BatchCreateOrUpdateCIRelationView(APIView):
|
||||
def delete(self):
|
||||
ci_ids = list(map(int, request.values.get('ci_ids')))
|
||||
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)
|
||||
|
@@ -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 ResourceTypeEnum
|
||||
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.decorator import args_required
|
||||
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)
|
||||
|
||||
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"
|
||||
row-id="id"
|
||||
show-overflow
|
||||
resizable
|
||||
>
|
||||
<!-- 1 -->
|
||||
<vxe-table-column type="checkbox" fixed="left" :width="45"></vxe-table-column>
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
export function getFirstCIs(ciId) {
|
||||
export function getFirstCIsByCiId(ciId) {
|
||||
return axios({
|
||||
url: '/v0.1/ci_relations/' + ciId + '/first_cis',
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function getSecondCIs(ciId) {
|
||||
export function getSecondCIsByCiId(ciId) {
|
||||
return axios({
|
||||
url: '/v0.1/ci_relations/' + ciId + '/second_cis',
|
||||
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({
|
||||
url: '/v0.1/ci_relations/batch',
|
||||
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({
|
||||
url: '/v0.1/ci_relations/batch',
|
||||
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({
|
||||
url: `/v0.1/ci_relations/${firstCiId}/${secondCiId}`,
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 单个删除
|
||||
export function deleteCIRelationView(firstCiId, secondCiId) {
|
||||
export function deleteCIRelationView(firstCiId, secondCiId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_relations/${firstCiId}/${secondCiId}`,
|
||||
method: 'DELETE',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
@@ -68,3 +68,10 @@ export function getRecursive_level2children(type_id) {
|
||||
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>
|
||||
<br />
|
||||
<a-checkbox-group v-model="checkedAttrs">
|
||||
<a-checkbox-group style="width:100%" v-model="checkedAttrs">
|
||||
<a-row>
|
||||
<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">
|
||||
@@ -87,10 +87,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { downloadExcel } from '../../../utils/helper'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { getCITypeParent } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
|
||||
|
||||
export default {
|
||||
name: 'CiTypeChoice',
|
||||
@@ -107,6 +108,7 @@ export default {
|
||||
parentsType: [],
|
||||
parentsForm: {},
|
||||
checkedParents: [],
|
||||
canEdit: {},
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
@@ -143,8 +145,17 @@ export default {
|
||||
},
|
||||
|
||||
openModal() {
|
||||
getCITypeParent(this.selectNum).then((res) => {
|
||||
this.parentsType = res.parents
|
||||
getCITypeParent(this.selectNum).then(async (res) => {
|
||||
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 = {}
|
||||
res.parents.forEach((item) => {
|
||||
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-form>
|
||||
</template>
|
||||
<!-- </a-form> -->
|
||||
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import moment from 'moment'
|
||||
import { Select, Option } from 'element-ui'
|
||||
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 { valueTypeMap } from '../../../utils/const'
|
||||
import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue'
|
||||
import { getCITypeParent } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
|
||||
|
||||
export default {
|
||||
name: 'CreateInstanceForm',
|
||||
@@ -166,6 +166,7 @@ export default {
|
||||
attributesByGroup: [],
|
||||
parentsType: [],
|
||||
parentsForm: {},
|
||||
canEdit: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -300,8 +301,16 @@ export default {
|
||||
this.batchUpdateLists = [{ name: this.attributeList[0].name }]
|
||||
})
|
||||
if (action === 'create') {
|
||||
getCITypeParent(this.typeId).then((res) => {
|
||||
this.parentsType = res.parents
|
||||
getCITypeParent(this.typeId).then(async (res) => {
|
||||
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 = {}
|
||||
res.parents.forEach((item) => {
|
||||
const _find = item.attributes.find((attr) => attr.id === item.unique_id)
|
||||
|
@@ -15,6 +15,7 @@
|
||||
<div class="ci-detail-relation-table-title">
|
||||
{{ parent.alias || parent.name }}
|
||||
<a
|
||||
:disabled="!canEdit[parent.id]"
|
||||
@click="
|
||||
() => {
|
||||
$refs.addTableModal.openModal({ [`${ci.unique}`]: ci[ci.unique] }, ci._id, parent.id, 'parents')
|
||||
@@ -23,6 +24,7 @@
|
||||
><a-icon
|
||||
type="plus-square"
|
||||
/></a>
|
||||
<span v-if="!canEdit[parent.id]">(当前模型关系为多对多,请前往关系视图进行增删操作)</span>
|
||||
</div>
|
||||
<vxe-grid
|
||||
v-if="firstCIs[parent.name]"
|
||||
@@ -38,7 +40,14 @@
|
||||
>
|
||||
<template #operation_default="{ row }">
|
||||
<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>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
@@ -50,6 +59,7 @@
|
||||
<div class="ci-detail-relation-table-title">
|
||||
{{ child.alias || child.name }}
|
||||
<a
|
||||
:disabled="!canEdit[child.id]"
|
||||
@click="
|
||||
() => {
|
||||
$refs.addTableModal.openModal({ [`${ci.unique}`]: ci[ci.unique] }, ci._id, child.id, 'children')
|
||||
@@ -58,6 +68,7 @@
|
||||
><a-icon
|
||||
type="plus-square"
|
||||
/></a>
|
||||
<span v-if="!canEdit[child.id]">(当前模型关系为多对多,请前往关系视图进行增删操作)</span>
|
||||
</div>
|
||||
<vxe-grid
|
||||
v-if="secondCIs[child.name]"
|
||||
@@ -72,7 +83,14 @@
|
||||
>
|
||||
<template #operation_default="{ row }">
|
||||
<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>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
@@ -85,7 +103,7 @@
|
||||
|
||||
<script>
|
||||
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 CiDetailRelationTopo from './ciDetailRelationTopo/index.vue'
|
||||
import Node from './ciDetailRelationTopo/node.js'
|
||||
@@ -118,6 +136,7 @@ export default {
|
||||
secondCIColumns: {},
|
||||
firstCIJsonAttr: {},
|
||||
secondCIJsonAttr: {},
|
||||
canEdit: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -293,76 +312,85 @@ export default {
|
||||
.catch((e) => {})
|
||||
},
|
||||
async getParentCITypes() {
|
||||
await getCITypeParent(this.typeId)
|
||||
.then((res) => {
|
||||
this.parentCITypes = res.parents
|
||||
|
||||
const firstCIColumns = {}
|
||||
const firstCIJsonAttr = {}
|
||||
res.parents.forEach((item) => {
|
||||
const columns = []
|
||||
const jsonAttr = []
|
||||
item.attributes.forEach((attr) => {
|
||||
columns.push({ key: 'p_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
|
||||
if (attr.value_type === '6') {
|
||||
jsonAttr.push(attr.name)
|
||||
}
|
||||
})
|
||||
firstCIJsonAttr[item.id] = jsonAttr
|
||||
firstCIColumns[item.id] = columns
|
||||
firstCIColumns[item.id].push({
|
||||
key: 'p_operation',
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
width: '60px',
|
||||
fixed: 'right',
|
||||
slots: {
|
||||
default: 'operation_default',
|
||||
},
|
||||
align: 'center',
|
||||
})
|
||||
})
|
||||
|
||||
this.firstCIColumns = firstCIColumns
|
||||
this.firstCIJsonAttr = firstCIJsonAttr
|
||||
const res = await getCITypeParent(this.typeId)
|
||||
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,
|
||||
}
|
||||
})
|
||||
.catch((e) => {})
|
||||
}
|
||||
const firstCIColumns = {}
|
||||
const firstCIJsonAttr = {}
|
||||
res.parents.forEach((item) => {
|
||||
const columns = []
|
||||
const jsonAttr = []
|
||||
item.attributes.forEach((attr) => {
|
||||
columns.push({ key: 'p_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
|
||||
if (attr.value_type === '6') {
|
||||
jsonAttr.push(attr.name)
|
||||
}
|
||||
})
|
||||
firstCIJsonAttr[item.id] = jsonAttr
|
||||
firstCIColumns[item.id] = columns
|
||||
firstCIColumns[item.id].push({
|
||||
key: 'p_operation',
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
width: '60px',
|
||||
fixed: 'right',
|
||||
slots: {
|
||||
default: 'operation_default',
|
||||
},
|
||||
align: 'center',
|
||||
})
|
||||
})
|
||||
|
||||
this.firstCIColumns = firstCIColumns
|
||||
this.firstCIJsonAttr = firstCIJsonAttr
|
||||
},
|
||||
async getChildCITypes() {
|
||||
await getCITypeChildren(this.typeId)
|
||||
.then((res) => {
|
||||
this.childCITypes = res.children
|
||||
const res = await getCITypeChildren(this.typeId)
|
||||
|
||||
const secondCIColumns = {}
|
||||
const secondCIJsonAttr = {}
|
||||
res.children.forEach((item) => {
|
||||
const columns = []
|
||||
const jsonAttr = []
|
||||
item.attributes.forEach((attr) => {
|
||||
columns.push({ key: 'c_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
|
||||
if (attr.value_type === '6') {
|
||||
jsonAttr.push(attr.name)
|
||||
}
|
||||
})
|
||||
secondCIJsonAttr[item.id] = jsonAttr
|
||||
secondCIColumns[item.id] = columns
|
||||
secondCIColumns[item.id].push({
|
||||
key: 'c_operation',
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
width: '60px',
|
||||
fixed: 'right',
|
||||
slots: {
|
||||
default: 'operation_default',
|
||||
},
|
||||
align: 'center',
|
||||
})
|
||||
})
|
||||
|
||||
this.secondCIColumns = secondCIColumns
|
||||
this.secondCIJsonAttr = secondCIJsonAttr
|
||||
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,
|
||||
}
|
||||
})
|
||||
.catch((e) => {})
|
||||
}
|
||||
const secondCIColumns = {}
|
||||
const secondCIJsonAttr = {}
|
||||
res.children.forEach((item) => {
|
||||
const columns = []
|
||||
const jsonAttr = []
|
||||
item.attributes.forEach((attr) => {
|
||||
columns.push({ key: 'c_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
|
||||
if (attr.value_type === '6') {
|
||||
jsonAttr.push(attr.name)
|
||||
}
|
||||
})
|
||||
secondCIJsonAttr[item.id] = jsonAttr
|
||||
secondCIColumns[item.id] = columns
|
||||
secondCIColumns[item.id].push({
|
||||
key: 'c_operation',
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
width: '60px',
|
||||
fixed: 'right',
|
||||
slots: {
|
||||
default: 'operation_default',
|
||||
},
|
||||
align: 'center',
|
||||
})
|
||||
})
|
||||
|
||||
this.secondCIColumns = secondCIColumns
|
||||
this.secondCIJsonAttr = secondCIJsonAttr
|
||||
},
|
||||
reload() {
|
||||
this.init()
|
||||
|
@@ -372,7 +372,7 @@ export default {
|
||||
width: 3,
|
||||
fontColor: '#ffffff',
|
||||
bgColor: ['#6ABFFE', '#5375EB'],
|
||||
chartColor: '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD', // 图表颜色
|
||||
chartColor: '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF', // 图表颜色
|
||||
isShowPreview: false,
|
||||
filterExp: undefined,
|
||||
previewData: null,
|
||||
@@ -410,7 +410,7 @@ export default {
|
||||
this.width = width
|
||||
this.chartType = chartType
|
||||
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
|
||||
|
||||
if (chartType === 'count') {
|
||||
|
@@ -24,7 +24,7 @@ export const category_1_bar_options = (data, options) => {
|
||||
})
|
||||
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: {
|
||||
top: 15,
|
||||
left: 'left',
|
||||
@@ -83,7 +83,7 @@ export const category_1_bar_options = (data, options) => {
|
||||
export const category_1_line_options = (data, options) => {
|
||||
const xData = Object.keys(data)
|
||||
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: {
|
||||
top: 15,
|
||||
left: 'left',
|
||||
@@ -117,7 +117,7 @@ export const category_1_line_options = (data, options) => {
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
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% 处的颜色
|
||||
}],
|
||||
@@ -131,7 +131,7 @@ export const category_1_line_options = (data, options) => {
|
||||
|
||||
export const category_1_pie_options = (data, options) => {
|
||||
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: {
|
||||
top: 10,
|
||||
left: 'left',
|
||||
@@ -181,7 +181,7 @@ export const category_2_bar_options = (data, options, chartType) => {
|
||||
})
|
||||
const legend = [...new Set(_legend)]
|
||||
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: {
|
||||
top: 15,
|
||||
left: 'left',
|
||||
@@ -249,7 +249,7 @@ export const category_2_bar_options = (data, options, chartType) => {
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
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% 处的颜色
|
||||
}],
|
||||
@@ -269,7 +269,7 @@ export const category_2_pie_options = (data, options) => {
|
||||
})
|
||||
})
|
||||
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: {
|
||||
top: 15,
|
||||
left: 'left',
|
||||
|
@@ -24,10 +24,8 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
list: [
|
||||
'#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD',
|
||||
'#C1A9DC,#E2B5CD,#EE8EBC,#8483C3,#4D66BD,#213764,#D9B6E9,#DD88EB',
|
||||
'#6FC4DF,#9FE8CE,#16B4BE,#86E6FB,#1871A3,#E1BF8D,#ED8D8D,#DD88EB',
|
||||
'#F8B751,#FC9054,#FFE380,#DF963F,#AB5200,#EA9387,#FFBB7C,#D27467',
|
||||
'#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF',
|
||||
'#9BA1F9,#0F2BA8,#A2EBFE,#4982F6,#FEB09C,#6C78E8,#FFDDAB,#4D66BD',
|
||||
],
|
||||
}
|
||||
},
|
||||
|
@@ -392,6 +392,7 @@ export default {
|
||||
origShowTypes: [],
|
||||
leaf2showTypes: {},
|
||||
node2ShowTypes: {},
|
||||
level2constraint: {},
|
||||
leaf: [],
|
||||
typeId: null,
|
||||
viewId: null,
|
||||
@@ -595,6 +596,17 @@ export default {
|
||||
}
|
||||
} else {
|
||||
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])
|
||||
|
||||
let level = []
|
||||
@@ -649,7 +661,19 @@ export default {
|
||||
|
||||
if (refreshType === 'refreshNumber') {
|
||||
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({
|
||||
ancestor_ids,
|
||||
root_ids: key.split('%')[0],
|
||||
level: this.treeKeys.length - index,
|
||||
type_ids: this.showTypes.map((type) => type.id).join(','),
|
||||
@@ -780,16 +804,36 @@ export default {
|
||||
const index = topo_flatten.findIndex((id) => id === typeId)
|
||||
const _type = topo_flatten[index + 1]
|
||||
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 ciIds = []
|
||||
res.result.forEach((item) => {
|
||||
facet.push([item[item.unique], 0, item._id, item._type, item.unique])
|
||||
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) => {
|
||||
if (_level > 1) {
|
||||
return statisticsCIRelation({
|
||||
ancestor_ids,
|
||||
root_ids: ciIds.join(','),
|
||||
level: _level - 1,
|
||||
type_ids: this.showTypes.map((type) => type.id).join(','),
|
||||
@@ -889,6 +933,7 @@ export default {
|
||||
this.origShowTypeIds = showTypeIds
|
||||
this.leaf2showTypes = this.relationViews.views[this.viewName].leaf2show_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.currentView = `${this.viewId}`
|
||||
this.typeId = this.levels[0][0]
|
||||
@@ -923,6 +968,17 @@ export default {
|
||||
const _tempTree = splitTreeKey[splitTreeKey.length - 1].split('%')
|
||||
const firstCIObj = JSON.parse(_tempTree[2])
|
||||
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') {
|
||||
const _tempTreeParent = splitTreeKey[splitTreeKey.length - 2].split('%')
|
||||
const that = this
|
||||
@@ -935,16 +991,17 @@ export default {
|
||||
</div>
|
||||
),
|
||||
onOk() {
|
||||
deleteCIRelationView(_tempTreeParent[0], _tempTree[0]).then((res) => {
|
||||
deleteCIRelationView(_tempTreeParent[0], _tempTree[0], { ancestor_ids }).then((res) => {
|
||||
that.$message.success('删除成功!')
|
||||
that.reload()
|
||||
setTimeout(() => {
|
||||
that.reload()
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
})
|
||||
} else {
|
||||
const childTypeId = menuKey
|
||||
console.log(menuKey)
|
||||
this.$refs.addTableModal.openModal(firstCIObj, firstCIId, childTypeId, 'children')
|
||||
this.$refs.addTableModal.openModal(firstCIObj, firstCIId, childTypeId, 'children', ancestor_ids)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -964,9 +1021,21 @@ export default {
|
||||
onOk() {
|
||||
const _tempTree = that.treeKeys[that.treeKeys.length - 1].split('%')
|
||||
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(
|
||||
that.selectedRowKeys.map((item) => item._id),
|
||||
[first_ci_id]
|
||||
[first_ci_id],
|
||||
ancestor_ids
|
||||
).then((res) => {
|
||||
that.$refs.xTable.clearCheckboxRow()
|
||||
that.$refs.xTable.clearCheckboxReserve()
|
||||
@@ -1130,7 +1199,10 @@ export default {
|
||||
const $table = this.$refs['xTable']
|
||||
const data = {}
|
||||
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
|
||||
}
|
||||
})
|
||||
@@ -1180,7 +1252,18 @@ export default {
|
||||
},
|
||||
sumbitFromCreateInstance({ ci_id }) {
|
||||
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(() => {
|
||||
this.loadData({}, 'refreshNumber')
|
||||
}, 500)
|
||||
@@ -1270,9 +1353,7 @@ export default {
|
||||
})
|
||||
Promise.all(promises)
|
||||
.then((res) => {
|
||||
that.$message.success({
|
||||
message: '删除成功',
|
||||
})
|
||||
that.$message.success('删除成功')
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
|
@@ -86,6 +86,7 @@ export default {
|
||||
isFocusExpression: false,
|
||||
type: 'children',
|
||||
preferenceAttrList: [],
|
||||
ancestor_ids: undefined,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -101,13 +102,13 @@ export default {
|
||||
},
|
||||
watch: {},
|
||||
methods: {
|
||||
async openModal(ciObj, ciId, addTypeId, type) {
|
||||
console.log(ciObj, addTypeId)
|
||||
async openModal(ciObj, ciId, addTypeId, type, ancestor_ids = undefined) {
|
||||
this.visible = true
|
||||
this.ciObj = ciObj
|
||||
this.ciId = ciId
|
||||
this.addTypeId = addTypeId
|
||||
this.type = type
|
||||
this.ancestor_ids = ancestor_ids
|
||||
await getSubscribeAttributes(addTypeId).then((res) => {
|
||||
this.preferenceAttrList = res.attributes // 已经订阅的全部列
|
||||
})
|
||||
@@ -168,13 +169,15 @@ export default {
|
||||
const ciIds = [...selectRecordsCurrent, ...selectRecordsReserved].map((record) => record._id)
|
||||
if (ciIds.length) {
|
||||
if (this.type === 'children') {
|
||||
await batchUpdateCIRelationChildren(ciIds, [this.ciId])
|
||||
await batchUpdateCIRelationChildren(ciIds, [this.ciId], this.ancestor_ids)
|
||||
} else {
|
||||
await batchUpdateCIRelationParents(ciIds, [this.ciId])
|
||||
}
|
||||
this.$message.success('添加成功!')
|
||||
this.handleClose()
|
||||
this.$emit('reload')
|
||||
setTimeout(() => {
|
||||
this.$message.success('添加成功!')
|
||||
this.handleClose()
|
||||
this.$emit('reload')
|
||||
}, 500)
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
|
@@ -24,6 +24,8 @@ services:
|
||||
cmdb-cache:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-cache:3.0
|
||||
container_name: cmdb-cache
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
networks:
|
||||
new:
|
||||
aliases:
|
||||
@@ -48,8 +50,8 @@ services:
|
||||
flask common-check-new-columns
|
||||
gunicorn --workers=3 autoapp:app -b 0.0.0.0:5000 -D
|
||||
|
||||
celery -A celery_worker.celery worker -E -Q one_cmdb_async --autoscale=5,2 --logfile=one_cmdb_async.log -D
|
||||
celery -A celery_worker.celery worker -E -Q acl_async --logfile=one_acl_async.log --concurrency=2 -D
|
||||
nohup celery -A celery_worker.celery worker -E -Q one_cmdb_async --autoscale=2,5 > one_cmdb_async.log 2>&1 &
|
||||
nohup celery -A celery_worker.celery worker -E -Q acl_async --concurrency=2 > one_acl_async.log 2>&1 &
|
||||
|
||||
nohup flask cmdb-trigger > trigger.log 2>&1 &
|
||||
flask cmdb-init-cache
|
||||
|
@@ -68,20 +68,35 @@
|
||||
|
||||
### One-Click Docker Quick Build
|
||||
|
||||
- Prepare: install docker and docker-compose
|
||||
- In directory cmdb
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
- View: [http://127.0.0.1:8000](http://127.0.0.1:8000)
|
||||
- username: demo or admin
|
||||
- password: 123456
|
||||
> Method 1
|
||||
- step 1: **Prepare: install docker and docker-compose**
|
||||
- step 2: copy the repository
|
||||
```shell
|
||||
git clone https://github.com/veops/cmdb.git
|
||||
```
|
||||
- step 3: In directory cmdb:
|
||||
```
|
||||
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)
|
||||
|
||||
### [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
|
||||
|
||||
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-ui && yarn install && cd ..`
|
||||
- 可以将 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`
|
||||
|
@@ -17,7 +17,7 @@
|
||||
- frontend: `cd cmdb-ui && yarn install && cd ..`
|
||||
- Suggest step: (default: user:demo,password:123456)
|
||||
- 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`
|
||||
|
||||
|
@@ -46,10 +46,11 @@ read_rnd_buffer_size=512K
|
||||
skip-name-resolve
|
||||
max_connections=1000
|
||||
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
|
||||
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
|
||||
# By default we only accept connections from localhost
|
||||
#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 -b deploy_on_kylin_docker --single-branch $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