mirror of
https://github.com/veops/cmdb.git
synced 2025-09-07 05:47:00 +08:00
Compare commits
48 Commits
2.4.3
...
fix_decora
Author | SHA1 | Date | |
---|---|---|---|
|
27354a3927 | ||
|
78495eb976 | ||
|
ae900c7d3b | ||
|
50134e6a0b | ||
|
65ef58dea9 | ||
|
0a2e7aa99f | ||
|
8875e75883 | ||
|
2f03639c57 | ||
|
49bc5d94a9 | ||
|
39354e1293 | ||
|
d3714f3ecf | ||
|
729a616282 | ||
|
2d3a290aa3 | ||
|
9e885a5b12 | ||
|
f5822d7cba | ||
|
21ea553e74 | ||
|
e63038d1b6 | ||
|
d56806f511 | ||
|
7ac7fdc08e | ||
|
ba11707146 | ||
|
d49dc8a067 | ||
|
6bfb34fe2a | ||
|
2c7ed8c32d | ||
|
5b275af54e | ||
|
dde7ec6246 | ||
|
9181817e96 | ||
|
46b54bb7f2 | ||
|
fe63310c4e | ||
|
27c733aa2c | ||
|
2a8e9e684e | ||
|
095190a785 | ||
|
ef25c94b5d | ||
|
06ae1bcf13 | ||
|
9ead4e7d8d | ||
|
994a28dd25 | ||
|
74b587e46c | ||
|
091cd882bd | ||
|
73093db467 | ||
|
66e268ce68 | ||
|
a41d1a5e97 | ||
|
b4b728fe28 | ||
|
d16462d8b7 | ||
|
de7d98c0b4 | ||
|
51332c7236 | ||
|
bf1076fe4a | ||
|
3454a98cfb | ||
|
506dcbb40e | ||
|
5ac4517187 |
6
.env
Normal file
6
.env
Normal file
@@ -0,0 +1,6 @@
|
||||
MYSQL_ROOT_PASSWORD='123456'
|
||||
MYSQL_HOST='mysql'
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_USER='cmdb'
|
||||
MYSQL_DATABASE='cmdb'
|
||||
MYSQL_PASSWORD='123456'
|
@@ -36,7 +36,7 @@ Flask-Caching = ">=1.0.0"
|
||||
environs = "==4.2.0"
|
||||
marshmallow = "==2.20.2"
|
||||
# async tasks
|
||||
celery = ">=5.3.1"
|
||||
celery = "==5.3.1"
|
||||
celery_once = "==3.0.1"
|
||||
more-itertools = "==5.0.0"
|
||||
kombu = ">=5.3.1"
|
||||
|
@@ -128,7 +128,7 @@ def cmdb_init_acl():
|
||||
perms = [PermEnum.READ]
|
||||
elif resource_type == ResourceTypeEnum.CI_TYPE_RELATION:
|
||||
perms = [PermEnum.ADD, PermEnum.DELETE, PermEnum.GRANT]
|
||||
elif resource_type == ResourceTypeEnum.RELATION_VIEW:
|
||||
elif resource_type in (ResourceTypeEnum.RELATION_VIEW, ResourceTypeEnum.TOPOLOGY_VIEW):
|
||||
perms = [PermEnum.READ, PermEnum.UPDATE, PermEnum.DELETE, PermEnum.GRANT]
|
||||
|
||||
ResourceTypeCRUD.add(app_id, resource_type, '', perms)
|
||||
@@ -326,7 +326,7 @@ def cmdb_inner_secrets_init(address):
|
||||
"""
|
||||
init inner secrets for password feature
|
||||
"""
|
||||
res, ok = KeyManage(backend=InnerKVManger).init()
|
||||
res, ok = KeyManage(backend=InnerKVManger()).init()
|
||||
if not ok:
|
||||
if res.get("status") == "failed":
|
||||
KeyManage.print_response(res)
|
||||
|
@@ -264,9 +264,11 @@ class CIManager(object):
|
||||
for attr_id in constraint.attr_ids:
|
||||
value_table = TableMap(attr_name=id2name[attr_id]).table
|
||||
|
||||
_ci_ids = set([i.ci_id for i in value_table.get_by(attr_id=attr_id,
|
||||
to_dict=False,
|
||||
value=ci_dict.get(id2name[attr_id]) or None)])
|
||||
values = value_table.get_by(attr_id=attr_id,
|
||||
value=ci_dict.get(id2name[attr_id]) or None,
|
||||
only_query=True).join(
|
||||
CI, CI.id == value_table.ci_id).filter(CI.type_id == type_id)
|
||||
_ci_ids = set([i.ci_id for i in values])
|
||||
if ci_ids is None:
|
||||
ci_ids = _ci_ids
|
||||
else:
|
||||
@@ -437,14 +439,18 @@ class CIManager(object):
|
||||
|
||||
return ci.id
|
||||
|
||||
def update(self, ci_id, _is_admin=False, ticket_id=None, **ci_dict):
|
||||
def update(self, ci_id, _is_admin=False, ticket_id=None, __sync=False, **ci_dict):
|
||||
current_app.logger.info((ci_id, ci_dict, __sync))
|
||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
ci = self.confirm_ci_existed(ci_id)
|
||||
|
||||
raw_dict = copy.deepcopy(ci_dict)
|
||||
|
||||
attrs = CITypeAttributeManager.get_all_attributes(ci.type_id)
|
||||
ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
|
||||
ci_type_attrs_alias2name = {attr.alias: attr.name for _, attr in attrs}
|
||||
ci_dict = {ci_type_attrs_alias2name[k] if k in ci_type_attrs_alias2name else k: v for k, v in ci_dict.items()}
|
||||
|
||||
raw_dict = copy.deepcopy(ci_dict)
|
||||
|
||||
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
||||
for _, attr in attrs:
|
||||
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
@@ -498,11 +504,17 @@ class CIManager(object):
|
||||
record_id = self.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci.type_id)
|
||||
|
||||
if record_id: # has change
|
||||
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
|
||||
if not __sync:
|
||||
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
|
||||
else:
|
||||
ci_cache((ci_id, OperateType.UPDATE, record_id))
|
||||
|
||||
ref_ci_dict = {k: v for k, v in ci_dict.items() if k.startswith("$") and "." in k}
|
||||
if ref_ci_dict:
|
||||
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id), queue=CMDB_QUEUE)
|
||||
if not __sync:
|
||||
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id), queue=CMDB_QUEUE)
|
||||
else:
|
||||
ci_relation_add((ref_ci_dict, ci.id))
|
||||
|
||||
@staticmethod
|
||||
def update_unique_value(ci_id, unique_name, unique_value):
|
||||
@@ -825,6 +837,149 @@ class CIManager(object):
|
||||
|
||||
return data.get('v')
|
||||
|
||||
def baseline(self, ci_ids, before_date):
|
||||
"""
|
||||
return CI changes
|
||||
:param ci_ids:
|
||||
:param before_date:
|
||||
:return:
|
||||
"""
|
||||
ci_list = self.get_cis_by_ids(ci_ids, ret_key=RetKey.ALIAS)
|
||||
if not ci_list:
|
||||
return dict()
|
||||
|
||||
ci2changed = dict()
|
||||
changed = AttributeHistoryManger.get_records_for_attributes(
|
||||
before_date, None, None, 1, 100000, None, None, ci_ids=ci_ids, more=True)[1]
|
||||
for records in changed:
|
||||
for change in records[1]:
|
||||
if change['is_computed'] or change['is_password']:
|
||||
continue
|
||||
|
||||
if change.get('default') and change['default'].get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
continue
|
||||
|
||||
ci2changed.setdefault(change['ci_id'], {})
|
||||
item = (change['old'],
|
||||
change['new'],
|
||||
change.get('is_list'),
|
||||
change.get('value_type'),
|
||||
change['operate_type'])
|
||||
if change.get('is_list'):
|
||||
ci2changed[change['ci_id']].setdefault(change.get('attr_alias'), []).append(item)
|
||||
else:
|
||||
ci2changed[change['ci_id']].update({change.get('attr_alias'): item})
|
||||
|
||||
type2show_name = {}
|
||||
result = []
|
||||
for ci in ci_list:
|
||||
list_attr2item = {}
|
||||
for alias_name, v in (ci2changed.get(ci['_id']) or {}).items():
|
||||
if not alias_name:
|
||||
continue
|
||||
if alias_name == ci.get('unique_alias'):
|
||||
continue
|
||||
|
||||
if ci.get('_type') not in type2show_name:
|
||||
ci_type = CITypeCache.get(ci.get('_type'))
|
||||
show_id = ci_type.show_id or ci_type.unique_id
|
||||
type2show_name[ci['_type']] = AttributeCache.get(show_id).alias
|
||||
|
||||
if isinstance(v, list):
|
||||
for old, new, is_list, value_type, operate_type in v:
|
||||
if alias_name not in list_attr2item:
|
||||
list_attr2item[alias_name] = dict(instance=ci.get(type2show_name[ci['_type']]),
|
||||
attr_name=alias_name,
|
||||
value_type=value_type,
|
||||
is_list=is_list,
|
||||
ci_type=ci.get('ci_type'),
|
||||
unique_alias=ci.get('unique_alias'),
|
||||
unique_value=ci.get(ci['unique_alias']),
|
||||
cur=copy.deepcopy(ci.get(alias_name)),
|
||||
to=ci.get(alias_name) or [])
|
||||
|
||||
old = ValueTypeMap.deserialize[value_type](old) if old else old
|
||||
new = ValueTypeMap.deserialize[value_type](new) if new else new
|
||||
if operate_type == OperateType.ADD:
|
||||
list_attr2item[alias_name]['to'].remove(new)
|
||||
elif operate_type == OperateType.DELETE and old not in list_attr2item[alias_name]['to']:
|
||||
list_attr2item[alias_name]['to'].append(old)
|
||||
continue
|
||||
|
||||
old, value_type = v[0], v[3]
|
||||
old = ValueTypeMap.deserialize[value_type](old) if old else old
|
||||
if isinstance(old, (datetime.datetime, datetime.date)):
|
||||
old = str(old)
|
||||
if ci.get(alias_name) != old:
|
||||
item = dict(instance=ci.get(type2show_name[ci['_type']]),
|
||||
attr_name=alias_name,
|
||||
value_type=value_type,
|
||||
ci_type=ci.get('ci_type'),
|
||||
unique_alias=ci.get('unique_alias'),
|
||||
unique_value=ci.get(ci['unique_alias']),
|
||||
cur=ci.get(alias_name),
|
||||
to=old)
|
||||
result.append(item)
|
||||
|
||||
for alias_name, item in list_attr2item.items():
|
||||
if sorted(item['cur'] or []) != sorted(item['to'] or []):
|
||||
result.append(item)
|
||||
|
||||
return result
|
||||
|
||||
def baseline_cis(self, ci_ids, before_date, fl=None):
|
||||
"""
|
||||
return CI changes
|
||||
:param ci_ids:
|
||||
:param before_date:
|
||||
:param fl:
|
||||
:return:
|
||||
"""
|
||||
ci_list = self.get_cis_by_ids(ci_ids, fields=fl)
|
||||
if not ci_list:
|
||||
return []
|
||||
|
||||
id2ci = {ci['_id']: ci for ci in ci_list}
|
||||
changed = AttributeHistoryManger.get_records_for_attributes(
|
||||
before_date, None, None, 1, 100000, None, None, ci_ids=ci_ids, more=True)[1]
|
||||
for records in changed:
|
||||
for change in records[1]:
|
||||
if change['is_computed'] or change['is_password']:
|
||||
continue
|
||||
|
||||
if change.get('default') and change['default'].get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
continue
|
||||
|
||||
if change['is_list']:
|
||||
old, new, value_type, operate_type, ci_id, attr_name = (
|
||||
change['old'], change['new'], change['value_type'], change['operate_type'],
|
||||
change['ci_id'], change['attr_name'])
|
||||
old = ValueTypeMap.deserialize[value_type](old) if old else old
|
||||
new = ValueTypeMap.deserialize[value_type](new) if new else new
|
||||
if operate_type == OperateType.ADD and new in (id2ci[ci_id][attr_name] or []):
|
||||
id2ci[ci_id][attr_name].remove(new)
|
||||
elif operate_type == OperateType.DELETE and old not in id2ci[ci_id][attr_name]:
|
||||
id2ci[ci_id][attr_name].append(old)
|
||||
else:
|
||||
id2ci[change['ci_id']][change['attr_name']] = change['old']
|
||||
|
||||
return list(id2ci.values())
|
||||
|
||||
def rollback(self, ci_id, before_date):
|
||||
baseline_ci = self.baseline([ci_id], before_date)
|
||||
|
||||
payload = dict()
|
||||
for item in baseline_ci:
|
||||
payload[item.get('attr_name')] = item.get('to')
|
||||
|
||||
if payload:
|
||||
payload['ci_type'] = baseline_ci[0]['ci_type']
|
||||
payload[baseline_ci[0]['unique_alias']] = baseline_ci[0]['unique_value']
|
||||
|
||||
self.update(ci_id, **payload)
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
class CIRelationManager(object):
|
||||
"""
|
||||
@@ -935,6 +1090,18 @@ class CIRelationManager(object):
|
||||
|
||||
return ci_ids, level2ids
|
||||
|
||||
@classmethod
|
||||
def get_parent_ids(cls, ci_ids):
|
||||
cis = db.session.query(CIRelation.first_ci_id, CIRelation.second_ci_id, CI.type_id).join(
|
||||
CI, CI.id == CIRelation.first_ci_id).filter(
|
||||
CIRelation.second_ci_id.in_(ci_ids)).filter(CIRelation.deleted.is_(False))
|
||||
|
||||
result = {}
|
||||
for ci in cis:
|
||||
result.setdefault(ci.second_ci_id, []).append((ci.first_ci_id, ci.type_id))
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
|
||||
db.session.commit()
|
||||
@@ -985,7 +1152,7 @@ class CIRelationManager(object):
|
||||
relation_type_id or abort(404, ErrFormat.relation_not_found.format("{} -> {}".format(
|
||||
first_ci.ci_type.name, second_ci.ci_type.name)))
|
||||
|
||||
if current_app.config.get('USE_ACL') and valid:
|
||||
if current_app.config.get('USE_ACL') and valid and current_user.username != 'worker':
|
||||
resource_name = CITypeRelationManager.acl_resource_name(first_ci.ci_type.name,
|
||||
second_ci.ci_type.name)
|
||||
if not ACLManager().has_permission(
|
||||
@@ -1019,7 +1186,7 @@ class CIRelationManager(object):
|
||||
def delete(cr_id):
|
||||
cr = CIRelation.get_by_id(cr_id) or abort(404, ErrFormat.relation_not_found.format("id={}".format(cr_id)))
|
||||
|
||||
if current_app.config.get('USE_ACL'):
|
||||
if current_app.config.get('USE_ACL') and current_user.username != 'worker':
|
||||
resource_name = CITypeRelationManager.acl_resource_name(cr.first_ci.ci_type.name, cr.second_ci.ci_type.name)
|
||||
if not ACLManager().has_permission(
|
||||
resource_name,
|
||||
@@ -1053,6 +1220,21 @@ class CIRelationManager(object):
|
||||
|
||||
return cr
|
||||
|
||||
@classmethod
|
||||
def delete_3(cls, first_ci_id, second_ci_id):
|
||||
cr = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
to_dict=False,
|
||||
first=True)
|
||||
|
||||
if cr is not None:
|
||||
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
||||
delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
|
||||
|
||||
cls.delete(cr.id)
|
||||
|
||||
return cr
|
||||
|
||||
@classmethod
|
||||
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
|
||||
"""
|
||||
@@ -1092,52 +1274,83 @@ class CIRelationManager(object):
|
||||
def build_by_attribute(cls, ci_dict):
|
||||
type_id = ci_dict['_type']
|
||||
child_items = CITypeRelation.get_by(parent_id=type_id, only_query=True).filter(
|
||||
CITypeRelation.parent_attr_id.isnot(None))
|
||||
CITypeRelation.parent_attr_ids.isnot(None))
|
||||
for item in child_items:
|
||||
parent_attr = AttributeCache.get(item.parent_attr_id)
|
||||
child_attr = AttributeCache.get(item.child_attr_id)
|
||||
attr_value = ci_dict.get(parent_attr.name)
|
||||
value_table = TableMap(attr=child_attr).table
|
||||
for child in value_table.get_by(attr_id=child_attr.id, value=attr_value, only_query=True).join(
|
||||
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.child_id):
|
||||
CIRelationManager.add(ci_dict['_id'], child.ci_id, valid=False)
|
||||
relations = None
|
||||
for parent_attr_id, child_attr_id in zip(item.parent_attr_ids, item.child_attr_ids):
|
||||
_relations = set()
|
||||
parent_attr = AttributeCache.get(parent_attr_id)
|
||||
child_attr = AttributeCache.get(child_attr_id)
|
||||
attr_value = ci_dict.get(parent_attr.name)
|
||||
value_table = TableMap(attr=child_attr).table
|
||||
for child in value_table.get_by(attr_id=child_attr.id, value=attr_value, only_query=True).join(
|
||||
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.child_id):
|
||||
_relations.add((ci_dict['_id'], child.ci_id))
|
||||
if relations is None:
|
||||
relations = _relations
|
||||
else:
|
||||
relations &= _relations
|
||||
for parent_ci_id, child_ci_id in relations:
|
||||
CIRelationManager.add(parent_ci_id, child_ci_id, valid=False)
|
||||
|
||||
parent_items = CITypeRelation.get_by(child_id=type_id, only_query=True).filter(
|
||||
CITypeRelation.child_attr_id.isnot(None))
|
||||
CITypeRelation.child_attr_ids.isnot(None))
|
||||
for item in parent_items:
|
||||
parent_attr = AttributeCache.get(item.parent_attr_id)
|
||||
child_attr = AttributeCache.get(item.child_attr_id)
|
||||
attr_value = ci_dict.get(child_attr.name)
|
||||
value_table = TableMap(attr=parent_attr).table
|
||||
for parent in value_table.get_by(attr_id=parent_attr.id, value=attr_value, only_query=True).join(
|
||||
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.parent_id):
|
||||
CIRelationManager.add(parent.ci_id, ci_dict['_id'], valid=False)
|
||||
relations = None
|
||||
for parent_attr_id, child_attr_id in zip(item.parent_attr_ids, item.child_attr_ids):
|
||||
_relations = set()
|
||||
parent_attr = AttributeCache.get(parent_attr_id)
|
||||
child_attr = AttributeCache.get(child_attr_id)
|
||||
attr_value = ci_dict.get(child_attr.name)
|
||||
value_table = TableMap(attr=parent_attr).table
|
||||
for parent in value_table.get_by(attr_id=parent_attr.id, value=attr_value, only_query=True).join(
|
||||
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.parent_id):
|
||||
_relations.add((parent.ci_id, ci_dict['_id']))
|
||||
if relations is None:
|
||||
relations = _relations
|
||||
else:
|
||||
relations &= _relations
|
||||
for parent_ci_id, child_ci_id in relations:
|
||||
CIRelationManager.add(parent_ci_id, child_ci_id, valid=False)
|
||||
|
||||
@classmethod
|
||||
def rebuild_all_by_attribute(cls, ci_type_relation):
|
||||
parent_attr = AttributeCache.get(ci_type_relation['parent_attr_id'])
|
||||
child_attr = AttributeCache.get(ci_type_relation['child_attr_id'])
|
||||
if not parent_attr or not child_attr:
|
||||
return
|
||||
relations = None
|
||||
for parent_attr_id, child_attr_id in zip(ci_type_relation['parent_attr_ids'] or [],
|
||||
ci_type_relation['child_attr_ids'] or []):
|
||||
|
||||
parent_value_table = TableMap(attr=parent_attr).table
|
||||
child_value_table = TableMap(attr=child_attr).table
|
||||
_relations = set()
|
||||
parent_attr = AttributeCache.get(parent_attr_id)
|
||||
child_attr = AttributeCache.get(child_attr_id)
|
||||
if not parent_attr or not child_attr:
|
||||
continue
|
||||
|
||||
parent_values = parent_value_table.get_by(attr_id=parent_attr.id, only_query=True).join(
|
||||
CI, CI.id == parent_value_table.ci_id).filter(CI.type_id == ci_type_relation['parent_id'])
|
||||
child_values = child_value_table.get_by(attr_id=child_attr.id, only_query=True).join(
|
||||
CI, CI.id == child_value_table.ci_id).filter(CI.type_id == ci_type_relation['child_id'])
|
||||
parent_value_table = TableMap(attr=parent_attr).table
|
||||
child_value_table = TableMap(attr=child_attr).table
|
||||
|
||||
child_value2ci_ids = {}
|
||||
for child in child_values:
|
||||
child_value2ci_ids.setdefault(child.value, []).append(child.ci_id)
|
||||
parent_values = parent_value_table.get_by(attr_id=parent_attr.id, only_query=True).join(
|
||||
CI, CI.id == parent_value_table.ci_id).filter(CI.type_id == ci_type_relation['parent_id'])
|
||||
child_values = child_value_table.get_by(attr_id=child_attr.id, only_query=True).join(
|
||||
CI, CI.id == child_value_table.ci_id).filter(CI.type_id == ci_type_relation['child_id'])
|
||||
|
||||
for parent in parent_values:
|
||||
for child_ci_id in child_value2ci_ids.get(parent.value, []):
|
||||
try:
|
||||
cls.add(parent.ci_id, child_ci_id, valid=False)
|
||||
except:
|
||||
pass
|
||||
child_value2ci_ids = {}
|
||||
for child in child_values:
|
||||
child_value2ci_ids.setdefault(child.value, []).append(child.ci_id)
|
||||
|
||||
for parent in parent_values:
|
||||
for child_ci_id in child_value2ci_ids.get(parent.value, []):
|
||||
_relations.add((parent.ci_id, child_ci_id))
|
||||
|
||||
if relations is None:
|
||||
relations = _relations
|
||||
else:
|
||||
relations &= _relations
|
||||
|
||||
for parent_ci_id, child_ci_id in (relations or []):
|
||||
try:
|
||||
cls.add(parent_ci_id, child_ci_id, valid=False)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class CITriggerManager(object):
|
||||
|
@@ -5,7 +5,6 @@ import copy
|
||||
import toposort
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import session
|
||||
from flask_login import current_user
|
||||
from toposort import toposort_flatten
|
||||
from werkzeug.exceptions import BadRequest
|
||||
@@ -28,12 +27,15 @@ from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.relation_type import RelationTypeManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.value import AttributeValueManager
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import kwargs_required
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import validate_permission
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import AutoDiscoveryCI
|
||||
from api.models.cmdb import AutoDiscoveryCIType
|
||||
from api.models.cmdb import AutoDiscoveryCITypeRelation
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIFilterPerms
|
||||
from api.models.cmdb import CIType
|
||||
@@ -53,6 +55,7 @@ from api.models.cmdb import PreferenceSearchOption
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
from api.models.cmdb import PreferenceTreeView
|
||||
from api.models.cmdb import RelationType
|
||||
from api.models.cmdb import TopologyView
|
||||
|
||||
|
||||
class CITypeManager(object):
|
||||
@@ -130,7 +133,9 @@ class CITypeManager(object):
|
||||
def add(cls, **kwargs):
|
||||
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
|
||||
if ErrFormat.ci_type_config not in {i['name'] for i in ACLManager().get_resources(ResourceTypeEnum.PAGE)}:
|
||||
return abort(403, ErrFormat.no_permission2)
|
||||
app_cli = CMDBApp()
|
||||
validate_permission(app_cli.op.Model_Configuration, app_cli.resource_type_name,
|
||||
app_cli.op.create_CIType, app_cli.app_name)
|
||||
|
||||
unique_key = kwargs.pop("unique_key", None) or kwargs.pop("unique_id", None)
|
||||
unique_key = AttributeCache.get(unique_key) or abort(404, ErrFormat.unique_key_not_define)
|
||||
@@ -247,12 +252,28 @@ class CITypeManager(object):
|
||||
for item in AutoDiscoveryCI.get_by(type_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
for item in AutoDiscoveryCITypeRelation.get_by(ad_type_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
for item in AutoDiscoveryCITypeRelation.get_by(peer_type_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
for item in CITypeInheritance.get_by(parent_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
try:
|
||||
from api.models.cmdb import CITypeReconciliation
|
||||
for item in CITypeReconciliation.get_by(type_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for item in TopologyView.get_by(central_node_type=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
ci_type.soft_delete()
|
||||
@@ -414,9 +435,6 @@ class CITypeGroupManager(object):
|
||||
existed = CITypeGroup.get_by_id(gid) or abort(
|
||||
404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid)))
|
||||
if name is not None and name != existed.name:
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
existed.update(name=name)
|
||||
|
||||
max_order = max([i.order or 0 for i in CITypeGroupItem.get_by(group_id=gid, to_dict=False)] or [0])
|
||||
@@ -675,9 +693,19 @@ class CITypeAttributeManager(object):
|
||||
item = CITypeTrigger.get_by(type_id=_type_id, attr_id=attr_id, to_dict=False, first=True)
|
||||
item and item.soft_delete(commit=False)
|
||||
|
||||
for item in (CITypeRelation.get_by(parent_id=type_id, parent_attr_id=attr_id, to_dict=False) +
|
||||
CITypeRelation.get_by(child_id=type_id, child_attr_id=attr_id, to_dict=False)):
|
||||
item.soft_delete(commit=False)
|
||||
for item in (CITypeRelation.get_by(parent_id=type_id, to_dict=False) +
|
||||
CITypeRelation.get_by(child_id=type_id, to_dict=False)):
|
||||
if item.parent_id == type_id and attr_id in (item.parent_attr_ids or []):
|
||||
item_dict = item.to_dict()
|
||||
pop_idx = item.parent_attr_ids.index(attr_id)
|
||||
elif item.child_id == type_id and attr_id in (item.child_attr_ids or []):
|
||||
item_dict = item.to_dict()
|
||||
pop_idx = item.child_attr_ids.index(attr_id)
|
||||
else:
|
||||
continue
|
||||
item.update(parent_attr_ids=item_dict['parent_attr_ids'].pop(pop_idx),
|
||||
child_attr_ids=item_dict['child_attr_ids'].pop(pop_idx),
|
||||
commit=False)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
@@ -746,10 +774,12 @@ class CITypeRelationManager(object):
|
||||
res[idx] = _item
|
||||
res[idx]['parent'] = item.parent.to_dict()
|
||||
if item.parent_id not in type2attributes:
|
||||
type2attributes[item.parent_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.parent_id)]
|
||||
type2attributes[item.parent_id] = [i[1].to_dict() for i in
|
||||
CITypeAttributeManager.get_all_attributes(item.parent_id)]
|
||||
res[idx]['child'] = item.child.to_dict()
|
||||
if item.child_id not in type2attributes:
|
||||
type2attributes[item.child_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.child_id)]
|
||||
type2attributes[item.child_id] = [i[1].to_dict() for i in
|
||||
CITypeAttributeManager.get_all_attributes(item.child_id)]
|
||||
res[idx]['relation_type'] = item.relation_type.to_dict()
|
||||
|
||||
return res, type2attributes
|
||||
@@ -784,8 +814,8 @@ class CITypeRelationManager(object):
|
||||
|
||||
ci_type_dict["relation_type"] = relation_inst.relation_type.name
|
||||
ci_type_dict["constraint"] = relation_inst.constraint
|
||||
ci_type_dict["parent_attr_id"] = relation_inst.parent_attr_id
|
||||
ci_type_dict["child_attr_id"] = relation_inst.child_attr_id
|
||||
ci_type_dict["parent_attr_ids"] = relation_inst.parent_attr_ids
|
||||
ci_type_dict["child_attr_ids"] = relation_inst.child_attr_ids
|
||||
|
||||
return ci_type_dict
|
||||
|
||||
@@ -818,6 +848,70 @@ class CITypeRelationManager(object):
|
||||
|
||||
return [cls._wrap_relation_type_dict(parent.parent_id, parent) for parent in parents]
|
||||
|
||||
@staticmethod
|
||||
def get_relations_by_type_id(type_id):
|
||||
nodes, edges = [], []
|
||||
node_ids, edge_tuples = set(), set()
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
if ci_type is None:
|
||||
return nodes, edges
|
||||
|
||||
nodes.append(ci_type.to_dict())
|
||||
node_ids.add(ci_type.id)
|
||||
|
||||
def _find(_id, lv):
|
||||
lv += 1
|
||||
for i in CITypeRelation.get_by(parent_id=_id, to_dict=False):
|
||||
if i.child_id not in node_ids:
|
||||
node_ids.add(i.child_id)
|
||||
node = i.child.to_dict()
|
||||
node.setdefault('level', []).append(lv)
|
||||
nodes.append(node)
|
||||
|
||||
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=False))
|
||||
edge_tuples.add((i.parent_id, i.child_id))
|
||||
|
||||
_find(i.child_id, lv)
|
||||
continue
|
||||
elif (i.parent_id, i.child_id) not in edge_tuples:
|
||||
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=False))
|
||||
edge_tuples.add((i.parent_id, i.child_id))
|
||||
_find(i.child_id, lv)
|
||||
|
||||
for _node in nodes:
|
||||
if _node['id'] == i.child_id and lv not in _node['level']:
|
||||
_node['level'].append(lv)
|
||||
|
||||
def _reverse_find(_id, lv):
|
||||
lv -= 1
|
||||
for i in CITypeRelation.get_by(child_id=_id, to_dict=False):
|
||||
if i.parent_id not in node_ids:
|
||||
node_ids.add(i.parent_id)
|
||||
node = i.parent.to_dict()
|
||||
node.setdefault('level', []).append(lv)
|
||||
nodes.append(node)
|
||||
|
||||
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=True))
|
||||
edge_tuples.add((i.parent_id, i.child_id))
|
||||
|
||||
_reverse_find(i.parent_id, lv)
|
||||
continue
|
||||
elif (i.parent_id, i.child_id) not in edge_tuples:
|
||||
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=True))
|
||||
edge_tuples.add((i.parent_id, i.child_id))
|
||||
_reverse_find(i.parent_id, lv)
|
||||
|
||||
for _node in nodes:
|
||||
if _node['id'] == i.child_id and lv not in _node['level']:
|
||||
_node['level'].append(lv)
|
||||
|
||||
level = 0
|
||||
_reverse_find(ci_type.id, level)
|
||||
level = 0
|
||||
_find(ci_type.id, level)
|
||||
|
||||
return nodes, edges
|
||||
|
||||
@staticmethod
|
||||
def _get(parent_id, child_id):
|
||||
return CITypeRelation.get_by(parent_id=parent_id,
|
||||
@@ -831,7 +925,7 @@ class CITypeRelationManager(object):
|
||||
|
||||
@classmethod
|
||||
def add(cls, parent, child, relation_type_id, constraint=ConstraintEnum.One2Many,
|
||||
parent_attr_id=None, child_attr_id=None):
|
||||
parent_attr_ids=None, child_attr_ids=None):
|
||||
p = CITypeManager.check_is_existed(parent)
|
||||
c = CITypeManager.check_is_existed(child)
|
||||
|
||||
@@ -846,21 +940,22 @@ class CITypeRelationManager(object):
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.circular_dependency_error)
|
||||
|
||||
old_parent_attr_id = None
|
||||
old_parent_attr_ids, old_child_attr_ids = None, None
|
||||
existed = cls._get(p.id, c.id)
|
||||
if existed is not None:
|
||||
old_parent_attr_id = existed.parent_attr_id
|
||||
old_parent_attr_ids = copy.deepcopy(existed.parent_attr_ids)
|
||||
old_child_attr_ids = copy.deepcopy(existed.child_attr_ids)
|
||||
existed = existed.update(relation_type_id=relation_type_id,
|
||||
constraint=constraint,
|
||||
parent_attr_id=parent_attr_id,
|
||||
child_attr_id=child_attr_id,
|
||||
parent_attr_ids=parent_attr_ids,
|
||||
child_attr_ids=child_attr_ids,
|
||||
filter_none=False)
|
||||
else:
|
||||
existed = CITypeRelation.create(parent_id=p.id,
|
||||
child_id=c.id,
|
||||
relation_type_id=relation_type_id,
|
||||
parent_attr_id=parent_attr_id,
|
||||
child_attr_id=child_attr_id,
|
||||
parent_attr_ids=parent_attr_ids,
|
||||
child_attr_ids=child_attr_ids,
|
||||
constraint=constraint)
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
@@ -874,10 +969,10 @@ class CITypeRelationManager(object):
|
||||
current_user.username,
|
||||
ResourceTypeEnum.CI_TYPE_RELATION)
|
||||
|
||||
if parent_attr_id and parent_attr_id != old_parent_attr_id:
|
||||
if parent_attr_id and parent_attr_id != existed.parent_attr_id:
|
||||
from api.tasks.cmdb import rebuild_relation_for_attribute_changed
|
||||
rebuild_relation_for_attribute_changed.apply_async(args=(existed.to_dict()))
|
||||
if ((parent_attr_ids and parent_attr_ids != old_parent_attr_ids) or
|
||||
(child_attr_ids and child_attr_ids != old_child_attr_ids)):
|
||||
from api.tasks.cmdb import rebuild_relation_for_attribute_changed
|
||||
rebuild_relation_for_attribute_changed.apply_async(args=(existed.to_dict(),))
|
||||
|
||||
CITypeHistoryManager.add(CITypeOperateType.ADD_RELATION, p.id,
|
||||
change=dict(parent=p.to_dict(), child=c.to_dict(), relation_type_id=relation_type_id))
|
||||
@@ -1171,8 +1266,8 @@ class CITypeTemplateManager(object):
|
||||
id2obj_dicts[added_id].get('child_id'),
|
||||
id2obj_dicts[added_id].get('relation_type_id'),
|
||||
id2obj_dicts[added_id].get('constraint'),
|
||||
id2obj_dicts[added_id].get('parent_attr_id'),
|
||||
id2obj_dicts[added_id].get('child_attr_id'),
|
||||
id2obj_dicts[added_id].get('parent_attr_ids'),
|
||||
id2obj_dicts[added_id].get('child_attr_ids'),
|
||||
)
|
||||
else:
|
||||
obj = cls.create(flush=True, **id2obj_dicts[added_id])
|
||||
|
@@ -55,6 +55,9 @@ class CITypeOperateType(BaseEnum):
|
||||
DELETE_UNIQUE_CONSTRAINT = "11" # 删除联合唯一
|
||||
ADD_RELATION = "12" # 新增关系
|
||||
DELETE_RELATION = "13" # 删除关系
|
||||
ADD_RECONCILIATION = "14" # 删除关系
|
||||
UPDATE_RECONCILIATION = "15" # 删除关系
|
||||
DELETE_RECONCILIATION = "16" # 删除关系
|
||||
|
||||
|
||||
class RetKey(BaseEnum):
|
||||
@@ -70,6 +73,7 @@ class ResourceTypeEnum(BaseEnum):
|
||||
RELATION_VIEW = "RelationView" # read/update/delete/grant
|
||||
CI_FILTER = "CIFilter" # read
|
||||
PAGE = "page" # read
|
||||
TOPOLOGY_VIEW = "TopologyView" # read/update/delete/grant
|
||||
|
||||
|
||||
class PermEnum(BaseEnum):
|
||||
@@ -98,6 +102,12 @@ class AttributeDefaultValueEnum(BaseEnum):
|
||||
AUTO_INC_ID = "$auto_inc_id"
|
||||
|
||||
|
||||
class ExecuteStatusEnum(BaseEnum):
|
||||
COMPLETED = '0'
|
||||
FAILED = '1'
|
||||
RUNNING = '2'
|
||||
|
||||
|
||||
CMDB_QUEUE = "one_cmdb_async"
|
||||
REDIS_PREFIX_CI = "ONE_CMDB"
|
||||
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
||||
|
@@ -26,7 +26,7 @@ from api.models.cmdb import OperationRecord
|
||||
class AttributeHistoryManger(object):
|
||||
@staticmethod
|
||||
def get_records_for_attributes(start, end, username, page, page_size, operate_type, type_id,
|
||||
ci_id=None, attr_id=None):
|
||||
ci_id=None, attr_id=None, ci_ids=None, more=False):
|
||||
|
||||
records = db.session.query(OperationRecord, AttributeHistory).join(
|
||||
AttributeHistory, OperationRecord.id == AttributeHistory.record_id)
|
||||
@@ -48,6 +48,9 @@ class AttributeHistoryManger(object):
|
||||
if ci_id is not None:
|
||||
records = records.filter(AttributeHistory.ci_id == ci_id)
|
||||
|
||||
if ci_ids and isinstance(ci_ids, list):
|
||||
records = records.filter(AttributeHistory.ci_id.in_(ci_ids))
|
||||
|
||||
if attr_id is not None:
|
||||
records = records.filter(AttributeHistory.attr_id == attr_id)
|
||||
|
||||
@@ -62,6 +65,12 @@ class AttributeHistoryManger(object):
|
||||
if attr_hist['attr']:
|
||||
attr_hist['attr_name'] = attr_hist['attr'].name
|
||||
attr_hist['attr_alias'] = attr_hist['attr'].alias
|
||||
if more:
|
||||
attr_hist['is_list'] = attr_hist['attr'].is_list
|
||||
attr_hist['is_computed'] = attr_hist['attr'].is_computed
|
||||
attr_hist['is_password'] = attr_hist['attr'].is_password
|
||||
attr_hist['default'] = attr_hist['attr'].default
|
||||
attr_hist['value_type'] = attr_hist['attr'].value_type
|
||||
attr_hist.pop("attr")
|
||||
|
||||
if record_id not in res:
|
||||
@@ -161,6 +170,7 @@ class AttributeHistoryManger(object):
|
||||
record = i.OperationRecord
|
||||
item = dict(attr_name=attr.name,
|
||||
attr_alias=attr.alias,
|
||||
value_type=attr.value_type,
|
||||
operate_type=hist.operate_type,
|
||||
username=user and user.nickname,
|
||||
old=hist.old,
|
||||
@@ -271,7 +281,7 @@ class CITypeHistoryManager(object):
|
||||
return numfound, result
|
||||
|
||||
@staticmethod
|
||||
def add(operate_type, type_id, attr_id=None, trigger_id=None, unique_constraint_id=None, change=None):
|
||||
def add(operate_type, type_id, attr_id=None, trigger_id=None, unique_constraint_id=None, change=None, rc_id=None):
|
||||
if type_id is None and attr_id is not None:
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
type_ids = [i.type_id for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False)]
|
||||
@@ -284,6 +294,7 @@ class CITypeHistoryManager(object):
|
||||
uid=current_user.uid,
|
||||
attr_id=attr_id,
|
||||
trigger_id=trigger_id,
|
||||
rc_id=rc_id,
|
||||
unique_constraint_id=unique_constraint_id,
|
||||
change=change)
|
||||
|
||||
|
@@ -25,6 +25,8 @@ from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.exception import AbortException
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import CITypeGroup
|
||||
from api.models.cmdb import CITypeGroupItem
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.models.cmdb import PreferenceCITypeOrder
|
||||
from api.models.cmdb import PreferenceRelationView
|
||||
@@ -43,22 +45,46 @@ class PreferenceManager(object):
|
||||
def get_types(instance=False, tree=False):
|
||||
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, to_dict=False), key=lambda x: x.order)
|
||||
|
||||
type2group = {}
|
||||
for i in db.session.query(CITypeGroupItem, CITypeGroup).join(
|
||||
CITypeGroup, CITypeGroup.id == CITypeGroupItem.group_id).filter(
|
||||
CITypeGroup.deleted.is_(False)).filter(CITypeGroupItem.deleted.is_(False)):
|
||||
type2group[i.CITypeGroupItem.type_id] = i.CITypeGroup.to_dict()
|
||||
|
||||
types = db.session.query(PreferenceShowAttributes.type_id).filter(
|
||||
PreferenceShowAttributes.uid == current_user.uid).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
||||
PreferenceShowAttributes.type_id).all() if instance else []
|
||||
types = sorted(types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
|
||||
ci_type_order) if not i.is_tree}.get(x.type_id, 1))
|
||||
group_types = []
|
||||
other_types = []
|
||||
group2idx = {}
|
||||
type_ids = set()
|
||||
for ci_type in types:
|
||||
type_id = ci_type.type_id
|
||||
type_ids.add(type_id)
|
||||
type_dict = CITypeCache.get(type_id).to_dict()
|
||||
if type_id not in type2group:
|
||||
other_types.append(type_dict)
|
||||
else:
|
||||
group = type2group[type_id]
|
||||
if group['id'] not in group2idx:
|
||||
group_types.append(type2group[type_id])
|
||||
group2idx[group['id']] = len(group_types) - 1
|
||||
group_types[group2idx[group['id']]].setdefault('ci_types', []).append(type_dict)
|
||||
if other_types:
|
||||
group_types.append(dict(ci_types=other_types))
|
||||
|
||||
tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else []
|
||||
tree_types = sorted(tree_types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
|
||||
ci_type_order) if i.is_tree}.get(x.type_id, 1))
|
||||
|
||||
type_ids = [i.type_id for i in types + tree_types]
|
||||
if types and tree_types:
|
||||
type_ids = set(type_ids)
|
||||
tree_types = [CITypeCache.get(_type.type_id).to_dict() for _type in tree_types]
|
||||
for _type in tree_types:
|
||||
type_ids.add(_type['id'])
|
||||
|
||||
return [CITypeCache.get(type_id).to_dict() for type_id in type_ids]
|
||||
return dict(group_types=group_types, tree_types=tree_types, type_ids=list(type_ids))
|
||||
|
||||
@staticmethod
|
||||
def get_types2(instance=False, tree=False):
|
||||
|
@@ -78,6 +78,8 @@ class ErrFormat(CommonErrFormat):
|
||||
unique_constraint_invalid = _l("Uniquely constrained attributes cannot be JSON and multi-valued")
|
||||
ci_type_trigger_duplicate = _l("Duplicated trigger") # 重复的触发器
|
||||
ci_type_trigger_not_found = _l("Trigger {} does not exist") # 触发器 {} 不存在
|
||||
ci_type_reconciliation_duplicate = _l("Duplicated reconciliation rule") # 重复的校验规则
|
||||
ci_type_reconciliation_not_found = _l("Reconciliation rule {} does not exist") # 规则 {} 不存在
|
||||
|
||||
record_not_found = _l("Operation record {} does not exist") # 操作记录 {} 不存在
|
||||
cannot_delete_unique = _l("Unique identifier cannot be deleted") # 不能删除唯一标识
|
||||
@@ -138,3 +140,12 @@ class ErrFormat(CommonErrFormat):
|
||||
|
||||
password_save_failed = _l("Failed to save password: {}") # 保存密码失败: {}
|
||||
password_load_failed = _l("Failed to get password: {}") # 获取密码失败: {}
|
||||
|
||||
cron_time_format_invalid = _l("Scheduling time format error") # 调度时间格式错误
|
||||
reconciliation_title = _l("CMDB data reconciliation results") # CMDB数据合规检查结果
|
||||
reconciliation_body = _l("Number of {} illegal: {}") # "{} 不合规数: {}"
|
||||
|
||||
topology_exists = _l("Topology view {} already exists") # 拓扑视图 {} 已经存在
|
||||
topology_group_exists = _l("Topology group {} already exists") # 拓扑视图分组 {} 已经存在
|
||||
# 因为该分组下定义了拓扑视图,不能删除
|
||||
topo_view_exists_cannot_delete_group = _l("The group cannot be deleted because the topology view already exists")
|
||||
|
@@ -17,10 +17,12 @@ def search(query=None,
|
||||
count=1,
|
||||
sort=None,
|
||||
excludes=None,
|
||||
use_id_filter=False):
|
||||
use_id_filter=False,
|
||||
use_ci_filter=True):
|
||||
if current_app.config.get("USE_ES"):
|
||||
s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
|
||||
else:
|
||||
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes, use_id_filter=use_id_filter)
|
||||
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes,
|
||||
use_id_filter=use_id_filter, use_ci_filter=use_ci_filter)
|
||||
|
||||
return s
|
||||
|
@@ -47,7 +47,8 @@ class Search(object):
|
||||
excludes=None,
|
||||
parent_node_perm_passed=False,
|
||||
use_id_filter=False,
|
||||
use_ci_filter=True):
|
||||
use_ci_filter=True,
|
||||
only_ids=False):
|
||||
self.orig_query = query
|
||||
self.fl = fl or []
|
||||
self.excludes = excludes or []
|
||||
@@ -64,10 +65,12 @@ class Search(object):
|
||||
self.parent_node_perm_passed = parent_node_perm_passed
|
||||
self.use_id_filter = use_id_filter
|
||||
self.use_ci_filter = use_ci_filter
|
||||
self.only_ids = only_ids
|
||||
|
||||
self.valid_type_names = []
|
||||
self.type2filter_perms = dict()
|
||||
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||
self.is_app_admin = self.is_app_admin or (not self.use_ci_filter and not self.use_id_filter)
|
||||
|
||||
@staticmethod
|
||||
def _operator_proc(key):
|
||||
@@ -590,6 +593,8 @@ class Search(object):
|
||||
def search(self):
|
||||
numfound, ci_ids = self._query_build_raw()
|
||||
ci_ids = list(map(str, ci_ids))
|
||||
if self.only_ids:
|
||||
return ci_ids
|
||||
|
||||
_fl = self._fl_build()
|
||||
|
||||
|
@@ -69,7 +69,7 @@ class Search(object):
|
||||
if _l < int(level) and c == ConstraintEnum.Many2Many:
|
||||
self.has_m2m = True
|
||||
|
||||
self.type2filter_perms = None
|
||||
self.type2filter_perms = {}
|
||||
|
||||
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||
|
||||
@@ -79,7 +79,7 @@ class Search(object):
|
||||
key = []
|
||||
_tmp = []
|
||||
for level in range(1, sorted(self.level)[-1] + 1):
|
||||
if len(self.descendant_ids) >= level and self.type2filter_perms.get(self.descendant_ids[level - 1]):
|
||||
if len(self.descendant_ids or []) >= level and self.type2filter_perms.get(self.descendant_ids[level - 1]):
|
||||
id_filter_limit, _ = self._get_ci_filter(self.type2filter_perms[self.descendant_ids[level - 1]])
|
||||
else:
|
||||
id_filter_limit = {}
|
||||
@@ -151,9 +151,9 @@ class Search(object):
|
||||
|
||||
return True
|
||||
|
||||
def search(self):
|
||||
use_ci_filter = len(self.descendant_ids) == self.level[0] - 1
|
||||
parent_node_perm_passed = self._has_read_perm_from_parent_nodes()
|
||||
def search(self, only_ids=False):
|
||||
use_ci_filter = len(self.descendant_ids or []) == self.level[0] - 1
|
||||
parent_node_perm_passed = not self.is_app_admin and self._has_read_perm_from_parent_nodes()
|
||||
|
||||
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]
|
||||
@@ -197,7 +197,8 @@ class Search(object):
|
||||
sort=self.sort,
|
||||
ci_ids=merge_ids,
|
||||
parent_node_perm_passed=parent_node_perm_passed,
|
||||
use_ci_filter=use_ci_filter).search()
|
||||
use_ci_filter=use_ci_filter,
|
||||
only_ids=only_ids).search()
|
||||
|
||||
def _get_ci_filter(self, filter_perms, ci_filters=None):
|
||||
ci_filters = ci_filters or []
|
||||
|
251
cmdb-api/api/lib/cmdb/topology.py
Normal file
251
cmdb-api/api/lib/cmdb/topology.py
Normal file
@@ -0,0 +1,251 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import json
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.models.cmdb import TopologyView
|
||||
from api.models.cmdb import TopologyViewGroup
|
||||
|
||||
|
||||
class TopologyViewManager(object):
|
||||
group_cls = TopologyViewGroup
|
||||
cls = TopologyView
|
||||
|
||||
@classmethod
|
||||
def get_name_by_id(cls, _id):
|
||||
res = cls.cls.get_by_id(_id)
|
||||
return res and res.name
|
||||
|
||||
def get_view_by_id(self, _id):
|
||||
res = self.cls.get_by_id(_id)
|
||||
|
||||
return res and res.to_dict() or {}
|
||||
|
||||
@classmethod
|
||||
def add_group(cls, name, order):
|
||||
if order is None:
|
||||
cur_max_order = cls.group_cls.get_by(only_query=True).order_by(cls.group_cls.order.desc()).first()
|
||||
cur_max_order = cur_max_order and cur_max_order.order or 0
|
||||
order = cur_max_order + 1
|
||||
|
||||
cls.group_cls.get_by(name=name, first=True, to_dict=False) and abort(
|
||||
400, ErrFormat.topology_group_exists.format(name))
|
||||
|
||||
return cls.group_cls.create(name=name, order=order)
|
||||
|
||||
def update_group(self, group_id, name, view_ids):
|
||||
existed = self.group_cls.get_by_id(group_id) or abort(404, ErrFormat.not_found)
|
||||
if name is not None and name != existed.name:
|
||||
existed.update(name=name)
|
||||
|
||||
for idx, view_id in enumerate(view_ids):
|
||||
view = self.cls.get_by_id(view_id)
|
||||
if view is not None:
|
||||
view.update(group_id=group_id, order=idx)
|
||||
|
||||
return existed.to_dict()
|
||||
|
||||
@classmethod
|
||||
def delete_group(cls, _id):
|
||||
existed = cls.group_cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
|
||||
|
||||
if cls.cls.get_by(group_id=_id, first=True):
|
||||
return abort(400, ErrFormat.topo_view_exists_cannot_delete_group)
|
||||
|
||||
existed.soft_delete()
|
||||
|
||||
@classmethod
|
||||
def group_order(cls, group_ids):
|
||||
for idx, group_id in enumerate(group_ids):
|
||||
group = cls.group_cls.get_by_id(group_id)
|
||||
group.update(order=idx + 1)
|
||||
|
||||
@classmethod
|
||||
def add(cls, name, group_id, option, order=None, **kwargs):
|
||||
cls.cls.get_by(name=name, first=True) and abort(400, ErrFormat.topology_exists.format(name))
|
||||
if order is None:
|
||||
cur_max_order = cls.cls.get_by(group_id=group_id, only_query=True).order_by(
|
||||
cls.cls.order.desc()).first()
|
||||
cur_max_order = cur_max_order and cur_max_order.order or 0
|
||||
order = cur_max_order + 1
|
||||
|
||||
inst = cls.cls.create(name=name, group_id=group_id, option=option, order=order, **kwargs).to_dict()
|
||||
if current_app.config.get('USE_ACL'):
|
||||
try:
|
||||
ACLManager().add_resource(name, ResourceTypeEnum.TOPOLOGY_VIEW)
|
||||
except BadRequest:
|
||||
pass
|
||||
|
||||
ACLManager().grant_resource_to_role(name,
|
||||
current_user.username,
|
||||
ResourceTypeEnum.TOPOLOGY_VIEW)
|
||||
|
||||
return inst
|
||||
|
||||
@classmethod
|
||||
def update(cls, _id, **kwargs):
|
||||
existed = cls.cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
|
||||
existed_name = existed.name
|
||||
|
||||
inst = existed.update(filter_none=False, **kwargs).to_dict()
|
||||
if current_app.config.get('USE_ACL') and existed_name != kwargs.get('name') and kwargs.get('name'):
|
||||
try:
|
||||
ACLManager().update_resource(existed_name, kwargs['name'], ResourceTypeEnum.TOPOLOGY_VIEW)
|
||||
except BadRequest:
|
||||
pass
|
||||
|
||||
return inst
|
||||
|
||||
@classmethod
|
||||
def delete(cls, _id):
|
||||
existed = cls.cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
|
||||
|
||||
existed.soft_delete()
|
||||
if current_app.config.get("USE_ACL"):
|
||||
ACLManager().del_resource(existed.name, ResourceTypeEnum.TOPOLOGY_VIEW)
|
||||
|
||||
@classmethod
|
||||
def group_inner_order(cls, _ids):
|
||||
for idx, _id in enumerate(_ids):
|
||||
topology = cls.cls.get_by_id(_id)
|
||||
topology.update(order=idx + 1)
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
resources = None
|
||||
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
|
||||
resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.TOPOLOGY_VIEW)])
|
||||
|
||||
groups = cls.group_cls.get_by(to_dict=True)
|
||||
groups = sorted(groups, key=lambda x: x['order'])
|
||||
group2pos = {group['id']: idx for idx, group in enumerate(groups)}
|
||||
|
||||
topo_views = sorted(cls.cls.get_by(to_dict=True), key=lambda x: x['order'])
|
||||
other_group = dict(views=[])
|
||||
for view in topo_views:
|
||||
if resources is not None and view['name'] not in resources:
|
||||
continue
|
||||
|
||||
if view['group_id']:
|
||||
groups[group2pos[view['group_id']]].setdefault('views', []).append(view)
|
||||
else:
|
||||
other_group['views'].append(view)
|
||||
|
||||
if other_group['views']:
|
||||
groups.append(other_group)
|
||||
|
||||
return groups
|
||||
|
||||
@staticmethod
|
||||
def relation_from_ci_type(type_id):
|
||||
nodes, edges = CITypeRelationManager.get_relations_by_type_id(type_id)
|
||||
|
||||
return dict(nodes=nodes, edges=edges)
|
||||
|
||||
def topology_view(self, view_id=None, preview=None):
|
||||
if view_id is not None:
|
||||
view = self.cls.get_by_id(view_id) or abort(404, ErrFormat.not_found)
|
||||
central_node_type, central_node_instances, path = (view.central_node_type,
|
||||
view.central_node_instances, view.path)
|
||||
else:
|
||||
central_node_type = preview.get('central_node_type')
|
||||
central_node_instances = preview.get('central_node_instances')
|
||||
path = preview.get('path')
|
||||
|
||||
nodes, links = [], []
|
||||
_type = CITypeCache.get(central_node_type)
|
||||
if not _type:
|
||||
return dict(nodes=nodes, links=links)
|
||||
root_ids = []
|
||||
show_key = AttributeCache.get(_type.show_id or _type.unique_id)
|
||||
|
||||
q = (central_node_instances[2:] if central_node_instances.startswith('q=') else
|
||||
central_node_instances)
|
||||
s = search(q, fl=['_id', show_key.name], use_id_filter=False, use_ci_filter=False, count=1000000)
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
except SearchError as e:
|
||||
current_app.logger.info(e)
|
||||
return dict(nodes=nodes, links=links)
|
||||
for i in response:
|
||||
root_ids.append(i['_id'])
|
||||
nodes.append(dict(id=str(i['_id']), name=i[show_key.name], type_id=central_node_type))
|
||||
if not root_ids:
|
||||
return dict(nodes=nodes, links=links)
|
||||
|
||||
prefix = REDIS_PREFIX_CI_RELATION
|
||||
key = list(map(str, root_ids))
|
||||
id2node = {}
|
||||
type2meta = {}
|
||||
for level in sorted([i for i in path.keys() if int(i) > 0]):
|
||||
type_ids = {int(i) for i in path[level]}
|
||||
|
||||
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||
new_key = []
|
||||
for idx, from_id in enumerate(key):
|
||||
for to_id, type_id in res[idx]:
|
||||
if type_id in type_ids:
|
||||
links.append({'from': from_id, 'to': to_id})
|
||||
id2node[to_id] = {'id': to_id, 'type_id': type_id}
|
||||
new_key.append(to_id)
|
||||
if type_id not in type2meta:
|
||||
type2meta[type_id] = CITypeCache.get(type_id).icon
|
||||
|
||||
key = new_key
|
||||
|
||||
ci_ids = list(map(int, root_ids))
|
||||
for level in sorted([i for i in path.keys() if int(i) < 0]):
|
||||
type_ids = {int(i) for i in path[level]}
|
||||
res = CIRelationManager.get_parent_ids(ci_ids)
|
||||
_ci_ids = []
|
||||
for to_id in res:
|
||||
for from_id, type_id in res[to_id]:
|
||||
if type_id in type_ids:
|
||||
from_id, to_id = str(from_id), str(to_id)
|
||||
links.append({'from': from_id, 'to': to_id})
|
||||
id2node[from_id] = {'id': str(from_id), 'type_id': type_id}
|
||||
_ci_ids.append(from_id)
|
||||
if type_id not in type2meta:
|
||||
type2meta[type_id] = CITypeCache.get(type_id).icon
|
||||
|
||||
ci_ids = _ci_ids
|
||||
|
||||
fl = set()
|
||||
type_ids = {t for lv in path if lv != '0' for t in path[lv]}
|
||||
type2show = {}
|
||||
for type_id in type_ids:
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
if ci_type:
|
||||
attr = AttributeCache.get(ci_type.show_id or ci_type.unique_id)
|
||||
if attr:
|
||||
fl.add(attr.name)
|
||||
type2show[type_id] = attr.name
|
||||
|
||||
if id2node:
|
||||
s = search("_id:({})".format(';'.join(id2node.keys())), fl=list(fl),
|
||||
use_id_filter=False, use_ci_filter=False, count=1000000)
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
except SearchError:
|
||||
return dict(nodes=nodes, links=links)
|
||||
for i in response:
|
||||
id2node[str(i['_id'])]['name'] = i[type2show[str(i['_type'])]]
|
||||
nodes.extend(id2node.values())
|
||||
|
||||
return dict(nodes=nodes, links=links, type2meta=type2meta)
|
@@ -274,26 +274,36 @@ class AttributeValueManager(object):
|
||||
|
||||
if attr.is_list:
|
||||
existed_attrs = value_table.get_by(attr_id=attr.id, ci_id=ci.id, to_dict=False)
|
||||
existed_values = [i.value for i in existed_attrs]
|
||||
added = set(value) - set(existed_values)
|
||||
deleted = set(existed_values) - set(value)
|
||||
for v in added:
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=v, flush=False, commit=False)
|
||||
changed.append((ci.id, attr.id, OperateType.ADD, None, v, ci.type_id))
|
||||
existed_values = [(ValueTypeMap.serialize[attr.value_type](i.value) if
|
||||
i.value or i.value == 0 else i.value) for i in existed_attrs]
|
||||
|
||||
for v in deleted:
|
||||
existed_attr = existed_attrs[existed_values.index(v)]
|
||||
# Comparison array starts from which position changes
|
||||
min_len = min(len(value), len(existed_values))
|
||||
index = 0
|
||||
while index < min_len:
|
||||
if value[index] != existed_values[index]:
|
||||
break
|
||||
index += 1
|
||||
|
||||
# Delete first and then add to ensure id sorting
|
||||
for idx in range(index, len(existed_attrs)):
|
||||
existed_attr = existed_attrs[idx]
|
||||
existed_attr.delete(flush=False, commit=False)
|
||||
changed.append((ci.id, attr.id, OperateType.DELETE, v, None, ci.type_id))
|
||||
changed.append((ci.id, attr.id, OperateType.DELETE, existed_values[idx], None, ci.type_id))
|
||||
for idx in range(index, len(value)):
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value[idx], flush=False, commit=False)
|
||||
changed.append((ci.id, attr.id, OperateType.ADD, None, value[idx], ci.type_id))
|
||||
else:
|
||||
existed_attr = value_table.get_by(attr_id=attr.id, ci_id=ci.id, first=True, to_dict=False)
|
||||
existed_value = existed_attr and existed_attr.value
|
||||
existed_value = (ValueTypeMap.serialize[attr.value_type](existed_value) if
|
||||
existed_value or existed_value == 0 else existed_value)
|
||||
if existed_value is None and value is not None:
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value, flush=False, commit=False)
|
||||
|
||||
changed.append((ci.id, attr.id, OperateType.ADD, None, value, ci.type_id))
|
||||
else:
|
||||
if existed_value != value:
|
||||
if existed_value != value and existed_attr:
|
||||
if value is None:
|
||||
existed_attr.delete(flush=False, commit=False)
|
||||
else:
|
||||
|
@@ -10,6 +10,11 @@ from api.lib.perm.acl.role import RoleCRUD, RoleRelationCRUD
|
||||
from api.lib.perm.acl.user import UserCRUD
|
||||
|
||||
|
||||
def validate_app(app_id):
|
||||
app = AppCache.get(app_id)
|
||||
return app.id if app else None
|
||||
|
||||
|
||||
class ACLManager(object):
|
||||
def __init__(self, app_name='acl', uid=None):
|
||||
self.log = current_app.logger
|
||||
@@ -133,7 +138,8 @@ class ACLManager(object):
|
||||
numfound, res = ResourceCRUD.search(q, u, self.validate_app().id, rt_id, page, page_size)
|
||||
return res
|
||||
|
||||
def grant_resource(self, rid, resource_id, perms):
|
||||
@staticmethod
|
||||
def grant_resource(rid, resource_id, perms):
|
||||
PermissionCRUD.grant(rid, perms, resource_id=resource_id, group_id=None)
|
||||
|
||||
@staticmethod
|
||||
@@ -141,3 +147,7 @@ class ACLManager(object):
|
||||
rt = AppCRUD.add(**payload)
|
||||
|
||||
return rt.to_dict()
|
||||
|
||||
def role_has_perms(self, rid, resource_name, resource_type_name, perm):
|
||||
app_id = validate_app(self.app_name)
|
||||
return RoleCRUD.has_permission(rid, resource_name, resource_type_name, app_id, perm)
|
||||
|
38
cmdb-api/api/lib/common_setting/decorator.py
Normal file
38
cmdb-api/api/lib/common_setting/decorator.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import functools
|
||||
|
||||
from flask import abort, session
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
|
||||
|
||||
def perms_role_required(app_name, resource_type_name, resource_name, perm, role_name=None):
|
||||
def decorator_perms_role_required(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper_required(*args, **kwargs):
|
||||
acl = ACLManager(app_name)
|
||||
has_perms = False
|
||||
try:
|
||||
has_perms = acl.role_has_perms(session["acl"]['rid'], resource_name, resource_type_name, perm)
|
||||
except Exception as e:
|
||||
# resource_type not exist, continue check role
|
||||
if role_name:
|
||||
if role_name not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin(app_name):
|
||||
abort(403, ErrFormat.role_required.format(role_name))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
else:
|
||||
abort(403, ErrFormat.resource_no_permission.format(resource_name, perm))
|
||||
|
||||
if not has_perms:
|
||||
if role_name:
|
||||
if role_name not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin(app_name):
|
||||
abort(403, ErrFormat.role_required.format(role_name))
|
||||
else:
|
||||
abort(403, ErrFormat.resource_no_permission.format(resource_name, perm))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper_required
|
||||
|
||||
return decorator_perms_role_required
|
@@ -80,3 +80,5 @@ class ErrFormat(CommonErrFormat):
|
||||
ldap_test_username_required = _l("LDAP test username required") # LDAP测试用户名必填
|
||||
|
||||
company_wide = _l("Company wide") # 全公司
|
||||
|
||||
resource_no_permission = _l("No permission to access resource {}, perm {} ") # 没有权限访问 {} 资源的 {} 权限"
|
||||
|
64
cmdb-api/api/lib/common_setting/role_perm_base.py
Normal file
64
cmdb-api/api/lib/common_setting/role_perm_base.py
Normal file
@@ -0,0 +1,64 @@
|
||||
class OperationPermission(object):
|
||||
|
||||
def __init__(self, resource_perms):
|
||||
for _r in resource_perms:
|
||||
setattr(self, _r['page'], _r['page'])
|
||||
for _p in _r['perms']:
|
||||
setattr(self, _p, _p)
|
||||
|
||||
|
||||
class BaseApp(object):
|
||||
resource_type_name = 'OperationPermission'
|
||||
all_resource_perms = []
|
||||
|
||||
def __init__(self):
|
||||
self.admin_name = None
|
||||
self.roles = []
|
||||
self.app_name = 'acl'
|
||||
self.require_create_resource_type = self.resource_type_name
|
||||
self.extra_create_resource_type_list = []
|
||||
|
||||
self.op = None
|
||||
|
||||
@staticmethod
|
||||
def format_role(role_name, role_type, acl_rid, resource_perms, description=''):
|
||||
return dict(
|
||||
role_name=role_name,
|
||||
role_type=role_type,
|
||||
acl_rid=acl_rid,
|
||||
description=description,
|
||||
resource_perms=resource_perms,
|
||||
)
|
||||
|
||||
|
||||
class CMDBApp(BaseApp):
|
||||
all_resource_perms = [
|
||||
{"page": "Big_Screen", "page_cn": "大屏", "perms": ["read"]},
|
||||
{"page": "Dashboard", "page_cn": "仪表盘", "perms": ["read"]},
|
||||
{"page": "Resource_Search", "page_cn": "资源搜索", "perms": ["read"]},
|
||||
{"page": "Auto_Discovery_Pool", "page_cn": "自动发现池", "perms": ["read"]},
|
||||
{"page": "My_Subscriptions", "page_cn": "我的订阅", "perms": ["read"]},
|
||||
{"page": "Bulk_Import", "page_cn": "批量导入", "perms": ["read"]},
|
||||
{"page": "Model_Configuration", "page_cn": "模型配置",
|
||||
"perms": ["read", "create_CIType", "create_CIType_group", "update_CIType_group",
|
||||
"delete_CIType_group", "download_CIType"]},
|
||||
{"page": "Backend_Management", "page_cn": "后台管理", "perms": ["read"]},
|
||||
{"page": "Customized_Dashboard", "page_cn": "定制仪表盘", "perms": ["read"]},
|
||||
{"page": "Service_Tree_Definition", "page_cn": "服务树定义", "perms": ["read"]},
|
||||
{"page": "Model_Relationships", "page_cn": "模型关系", "perms": ["read"]},
|
||||
{"page": "Operation_Audit", "page_cn": "操作审计", "perms": ["read"]},
|
||||
{"page": "Relationship_Types", "page_cn": "关系类型", "perms": ["read"]},
|
||||
{"page": "Auto_Discovery", "page_cn": "自动发现", "perms": ["read"]},
|
||||
{"page": "TopologyView", "page_cn": "拓扑视图",
|
||||
"perms": ["read", "create_topology_group", "update_topology_group", "delete_topology_group",
|
||||
"create_topology_view"],
|
||||
},
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.admin_name = 'cmdb_admin'
|
||||
self.app_name = 'cmdb'
|
||||
|
||||
self.op = OperationPermission(self.all_resource_perms)
|
@@ -153,11 +153,11 @@ class ACLManager(object):
|
||||
if resource:
|
||||
return ResourceCRUD.delete(resource.id, rebuild=rebuild)
|
||||
|
||||
def has_permission(self, resource_name, resource_type, perm, resource_id=None):
|
||||
def has_permission(self, resource_name, resource_type, perm, resource_id=None, rid=None):
|
||||
if is_app_admin(self.app_id):
|
||||
return True
|
||||
|
||||
role = self._get_role(current_user.username)
|
||||
role = self._get_role(current_user.username) if rid is None else RoleCache.get(rid)
|
||||
|
||||
role or abort(404, ErrFormat.role_not_found.format(current_user.username))
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import msgpack
|
||||
import redis_lock
|
||||
|
||||
from api.extensions import cache
|
||||
from api.extensions import db
|
||||
from api.extensions import rd
|
||||
from api.lib.decorator import flush_db
|
||||
from api.models.acl import App
|
||||
@@ -157,9 +158,10 @@ class RoleRelationCache(object):
|
||||
PREFIX_RESOURCES2 = "RoleRelationResources2::id::{0}::AppId::{1}"
|
||||
|
||||
@classmethod
|
||||
def get_parent_ids(cls, rid, app_id):
|
||||
def get_parent_ids(cls, rid, app_id, force=False):
|
||||
parent_ids = cache.get(cls.PREFIX_PARENT.format(rid, app_id))
|
||||
if not parent_ids:
|
||||
if not parent_ids or force:
|
||||
db.session.commit()
|
||||
from api.lib.perm.acl.role import RoleRelationCRUD
|
||||
parent_ids = RoleRelationCRUD.get_parent_ids(rid, app_id)
|
||||
cache.set(cls.PREFIX_PARENT.format(rid, app_id), parent_ids, timeout=0)
|
||||
@@ -167,9 +169,10 @@ class RoleRelationCache(object):
|
||||
return parent_ids
|
||||
|
||||
@classmethod
|
||||
def get_child_ids(cls, rid, app_id):
|
||||
def get_child_ids(cls, rid, app_id, force=False):
|
||||
child_ids = cache.get(cls.PREFIX_CHILDREN.format(rid, app_id))
|
||||
if not child_ids:
|
||||
if not child_ids or force:
|
||||
db.session.commit()
|
||||
from api.lib.perm.acl.role import RoleRelationCRUD
|
||||
child_ids = RoleRelationCRUD.get_child_ids(rid, app_id)
|
||||
cache.set(cls.PREFIX_CHILDREN.format(rid, app_id), child_ids, timeout=0)
|
||||
@@ -177,14 +180,16 @@ class RoleRelationCache(object):
|
||||
return child_ids
|
||||
|
||||
@classmethod
|
||||
def get_resources(cls, rid, app_id):
|
||||
def get_resources(cls, rid, app_id, force=False):
|
||||
"""
|
||||
:param rid:
|
||||
:param app_id:
|
||||
:param force:
|
||||
:return: {id2perms: {resource_id: [perm,]}, group2perms: {group_id: [perm, ]}}
|
||||
"""
|
||||
resources = cache.get(cls.PREFIX_RESOURCES.format(rid, app_id))
|
||||
if not resources:
|
||||
if not resources or force:
|
||||
db.session.commit()
|
||||
from api.lib.perm.acl.role import RoleCRUD
|
||||
resources = RoleCRUD.get_resources(rid, app_id)
|
||||
if resources['id2perms'] or resources['group2perms']:
|
||||
@@ -193,9 +198,10 @@ class RoleRelationCache(object):
|
||||
return resources or {}
|
||||
|
||||
@classmethod
|
||||
def get_resources2(cls, rid, app_id):
|
||||
def get_resources2(cls, rid, app_id, force=False):
|
||||
r_g = cache.get(cls.PREFIX_RESOURCES2.format(rid, app_id))
|
||||
if not r_g:
|
||||
if not r_g or force:
|
||||
db.session.commit()
|
||||
res = cls.get_resources(rid, app_id)
|
||||
id2perms = res['id2perms']
|
||||
group2perms = res['group2perms']
|
||||
@@ -224,22 +230,28 @@ class RoleRelationCache(object):
|
||||
@classmethod
|
||||
@flush_db
|
||||
def rebuild(cls, rid, app_id):
|
||||
cls.clean(rid, app_id)
|
||||
|
||||
cls.get_parent_ids(rid, app_id)
|
||||
cls.get_child_ids(rid, app_id)
|
||||
resources = cls.get_resources(rid, app_id)
|
||||
if resources.get('id2perms') or resources.get('group2perms'):
|
||||
HasResourceRoleCache.add(rid, app_id)
|
||||
if app_id is None:
|
||||
app_ids = [None] + [i.id for i in App.get_by(to_dict=False)]
|
||||
else:
|
||||
HasResourceRoleCache.remove(rid, app_id)
|
||||
cls.get_resources2(rid, app_id)
|
||||
app_ids = [app_id]
|
||||
|
||||
for _app_id in app_ids:
|
||||
cls.clean(rid, _app_id)
|
||||
|
||||
cls.get_parent_ids(rid, _app_id, force=True)
|
||||
cls.get_child_ids(rid, _app_id, force=True)
|
||||
resources = cls.get_resources(rid, _app_id, force=True)
|
||||
if resources.get('id2perms') or resources.get('group2perms'):
|
||||
HasResourceRoleCache.add(rid, _app_id)
|
||||
else:
|
||||
HasResourceRoleCache.remove(rid, _app_id)
|
||||
cls.get_resources2(rid, _app_id, force=True)
|
||||
|
||||
@classmethod
|
||||
@flush_db
|
||||
def rebuild2(cls, rid, app_id):
|
||||
cache.delete(cls.PREFIX_RESOURCES2.format(rid, app_id))
|
||||
cls.get_resources2(rid, app_id)
|
||||
cls.get_resources2(rid, app_id, force=True)
|
||||
|
||||
@classmethod
|
||||
def clean(cls, rid, app_id):
|
||||
|
@@ -274,12 +274,14 @@ class PermissionCRUD(object):
|
||||
perm2resource.setdefault(_perm, []).append(resource_id)
|
||||
for _perm in perm2resource:
|
||||
perm = PermissionCache.get(_perm, resource_type_id)
|
||||
existeds = RolePermission.get_by(rid=rid,
|
||||
app_id=app_id,
|
||||
perm_id=perm.id,
|
||||
__func_in___key_resource_id=perm2resource[_perm],
|
||||
to_dict=False)
|
||||
for existed in existeds:
|
||||
if perm is None:
|
||||
continue
|
||||
exists = RolePermission.get_by(rid=rid,
|
||||
app_id=app_id,
|
||||
perm_id=perm.id,
|
||||
__func_in___key_resource_id=perm2resource[_perm],
|
||||
to_dict=False)
|
||||
for existed in exists:
|
||||
existed.deleted = True
|
||||
existed.deleted_at = datetime.datetime.now()
|
||||
db.session.add(existed)
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
@@ -127,11 +126,18 @@ class ResourceTypeCRUD(object):
|
||||
existed_ids = [i.id for i in existed]
|
||||
current_ids = []
|
||||
|
||||
rebuild_rids = set()
|
||||
for i in existed:
|
||||
if i.name not in perms:
|
||||
i.soft_delete()
|
||||
i.soft_delete(commit=False)
|
||||
for rp in RolePermission.get_by(perm_id=i.id, to_dict=False):
|
||||
rp.soft_delete(commit=False)
|
||||
rebuild_rids.add((rp.app_id, rp.rid))
|
||||
else:
|
||||
current_ids.append(i.id)
|
||||
db.session.commit()
|
||||
for _app_id, _rid in rebuild_rids:
|
||||
role_rebuild.apply_async(args=(_rid, _app_id), queue=ACL_QUEUE)
|
||||
|
||||
for i in perms:
|
||||
if i not in existed_names:
|
||||
@@ -309,9 +315,12 @@ class ResourceCRUD(object):
|
||||
return resource
|
||||
|
||||
@staticmethod
|
||||
def delete(_id, rebuild=True):
|
||||
def delete(_id, rebuild=True, app_id=None):
|
||||
resource = Resource.get_by_id(_id) or abort(404, ErrFormat.resource_not_found.format("id={}".format(_id)))
|
||||
|
||||
if app_id is not None and resource.app_id != app_id:
|
||||
return abort(404, ErrFormat.resource_not_found.format("id={}".format(_id)))
|
||||
|
||||
origin = resource.to_dict()
|
||||
resource.soft_delete()
|
||||
|
||||
|
@@ -3,12 +3,14 @@
|
||||
|
||||
import time
|
||||
|
||||
import redis_lock
|
||||
import six
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from sqlalchemy import or_
|
||||
|
||||
from api.extensions import db
|
||||
from api.extensions import rd
|
||||
from api.lib.perm.acl.app import AppCRUD
|
||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
@@ -62,7 +64,9 @@ class RoleRelationCRUD(object):
|
||||
|
||||
id2parents = {}
|
||||
for i in res:
|
||||
id2parents.setdefault(rid2uid.get(i.child_id, i.child_id), []).append(RoleCache.get(i.parent_id).to_dict())
|
||||
parent = RoleCache.get(i.parent_id)
|
||||
if parent:
|
||||
id2parents.setdefault(rid2uid.get(i.child_id, i.child_id), []).append(parent.to_dict())
|
||||
|
||||
return id2parents
|
||||
|
||||
@@ -141,24 +145,27 @@ class RoleRelationCRUD(object):
|
||||
|
||||
@classmethod
|
||||
def add(cls, role, parent_id, child_ids, app_id):
|
||||
result = []
|
||||
for child_id in child_ids:
|
||||
existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id, app_id=app_id)
|
||||
if existed:
|
||||
continue
|
||||
with redis_lock.Lock(rd.r, "ROLE_RELATION_ADD"):
|
||||
db.session.commit()
|
||||
|
||||
RoleRelationCache.clean(parent_id, app_id)
|
||||
RoleRelationCache.clean(child_id, app_id)
|
||||
result = []
|
||||
for child_id in child_ids:
|
||||
existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id, app_id=app_id)
|
||||
if existed:
|
||||
continue
|
||||
|
||||
if parent_id in cls.recursive_child_ids(child_id, app_id):
|
||||
return abort(400, ErrFormat.inheritance_dead_loop)
|
||||
if parent_id in cls.recursive_child_ids(child_id, app_id):
|
||||
return abort(400, ErrFormat.inheritance_dead_loop)
|
||||
|
||||
if app_id is None:
|
||||
for app in AppCRUD.get_all():
|
||||
if app.name != "acl":
|
||||
RoleRelationCache.clean(child_id, app.id)
|
||||
result.append(RoleRelation.create(parent_id=parent_id, child_id=child_id, app_id=app_id).to_dict())
|
||||
|
||||
result.append(RoleRelation.create(parent_id=parent_id, child_id=child_id, app_id=app_id).to_dict())
|
||||
RoleRelationCache.clean(parent_id, app_id)
|
||||
RoleRelationCache.clean(child_id, app_id)
|
||||
|
||||
if app_id is None:
|
||||
for app in AppCRUD.get_all():
|
||||
if app.name != "acl":
|
||||
RoleRelationCache.clean(child_id, app.id)
|
||||
|
||||
AuditCRUD.add_role_log(app_id, AuditOperateType.role_relation_add,
|
||||
AuditScope.role_relation, role.id, {}, {},
|
||||
@@ -372,16 +379,16 @@ class RoleCRUD(object):
|
||||
resource_type_id = resource_type and resource_type.id
|
||||
|
||||
result = dict(resources=dict(), groups=dict())
|
||||
s = time.time()
|
||||
# s = time.time()
|
||||
parent_ids = RoleRelationCRUD.recursive_parent_ids(rid, app_id)
|
||||
current_app.logger.info('parent ids {0}: {1}'.format(parent_ids, time.time() - s))
|
||||
# current_app.logger.info('parent ids {0}: {1}'.format(parent_ids, time.time() - s))
|
||||
for parent_id in parent_ids:
|
||||
|
||||
_resources, _groups = cls._extend_resources(parent_id, resource_type_id, app_id)
|
||||
current_app.logger.info('middle1: {0}'.format(time.time() - s))
|
||||
# current_app.logger.info('middle1: {0}'.format(time.time() - s))
|
||||
_merge(result['resources'], _resources)
|
||||
current_app.logger.info('middle2: {0}'.format(time.time() - s))
|
||||
current_app.logger.info(len(_groups))
|
||||
# current_app.logger.info('middle2: {0}'.format(time.time() - s))
|
||||
# current_app.logger.info(len(_groups))
|
||||
if not group_flat:
|
||||
_merge(result['groups'], _groups)
|
||||
else:
|
||||
@@ -392,7 +399,7 @@ class RoleCRUD(object):
|
||||
item.setdefault('permissions', [])
|
||||
item['permissions'] = list(set(item['permissions'] + _groups[rg_id]['permissions']))
|
||||
result['resources'][item['id']] = item
|
||||
current_app.logger.info('End: {0}'.format(time.time() - s))
|
||||
# current_app.logger.info('End: {0}'.format(time.time() - s))
|
||||
|
||||
result['resources'] = list(result['resources'].values())
|
||||
result['groups'] = list(result['groups'].values())
|
||||
|
@@ -3,8 +3,8 @@ import os
|
||||
import secrets
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from base64 import b64decode, b64encode
|
||||
|
||||
from Cryptodome.Protocol.SecretSharing import Shamir
|
||||
from colorama import Back, Fore, Style, init as colorama_init
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
@@ -30,6 +30,7 @@ seal_status = True
|
||||
secrets_encrypt_key = ""
|
||||
secrets_root_key = ""
|
||||
|
||||
|
||||
def string_to_bytes(value):
|
||||
if not value:
|
||||
return ""
|
||||
@@ -78,7 +79,7 @@ class KeyManage:
|
||||
(len(sys.argv) > 1 and sys.argv[1] in ("run", "cmdb-password-data-migrate"))):
|
||||
|
||||
self.backend = backend
|
||||
threading.Thread(target=self.watch_root_key, args=(app,)).start()
|
||||
threading.Thread(target=self.watch_root_key, args=(app,), daemon=True).start()
|
||||
|
||||
self.trigger = app.config.get("INNER_TRIGGER_TOKEN")
|
||||
if not self.trigger:
|
||||
@@ -412,7 +413,7 @@ class KeyManage:
|
||||
class InnerCrypt:
|
||||
def __init__(self):
|
||||
self.encrypt_key = b64decode(secrets_encrypt_key)
|
||||
#self.encrypt_key = b64decode(secrets_encrypt_key, "".encode("utf-8"))
|
||||
# self.encrypt_key = b64decode(secrets_encrypt_key, "".encode("utf-8"))
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
"""
|
||||
@@ -490,4 +491,4 @@ if __name__ == "__main__":
|
||||
t_ciphertext, status1 = c.encrypt(t_plaintext)
|
||||
print("Ciphertext:", t_ciphertext)
|
||||
decrypted_plaintext, status2 = c.decrypt(t_ciphertext)
|
||||
print("Decrypted plaintext:", decrypted_plaintext)
|
||||
print("Decrypted plaintext:", decrypted_plaintext)
|
||||
|
@@ -88,11 +88,11 @@ def webhook_request(webhook, payload):
|
||||
|
||||
params = webhook.get('parameters') or None
|
||||
if isinstance(params, dict):
|
||||
params = json.loads(Template(json.dumps(params)).render(payload))
|
||||
params = json.loads(Template(json.dumps(params)).render(payload).encode('utf-8'))
|
||||
|
||||
headers = json.loads(Template(json.dumps(webhook.get('headers') or {})).render(payload))
|
||||
|
||||
data = Template(json.dumps(webhook.get('body', ''))).render(payload)
|
||||
data = Template(json.dumps(webhook.get('body', ''))).render(payload).encode('utf-8')
|
||||
auth = _wrap_auth(**webhook.get('authorization', {}))
|
||||
|
||||
if (webhook.get('authorization', {}).get("type") or '').lower() == 'oauth2.0':
|
||||
|
@@ -79,8 +79,11 @@ class CITypeRelation(Model):
|
||||
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
|
||||
constraint = db.Column(db.Enum(*ConstraintEnum.all()), default=ConstraintEnum.One2Many)
|
||||
|
||||
parent_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
|
||||
child_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
|
||||
parent_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id")) # CMDB > 2.4.5: deprecated
|
||||
child_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id")) # CMDB > 2.4.5: deprecated
|
||||
|
||||
parent_attr_ids = db.Column(db.JSON) # [parent_attr_id, ]
|
||||
child_attr_ids = db.Column(db.JSON) # [child_attr_id, ]
|
||||
|
||||
parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.parent_id")
|
||||
child = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.child_id")
|
||||
@@ -209,6 +212,26 @@ class CITriggerHistory(Model):
|
||||
webhook = db.Column(db.Text)
|
||||
|
||||
|
||||
class TopologyViewGroup(Model):
|
||||
__tablename__ = 'c_topology_view_groups'
|
||||
|
||||
name = db.Column(db.String(64), index=True)
|
||||
order = db.Column(db.Integer, default=0)
|
||||
|
||||
|
||||
class TopologyView(Model):
|
||||
__tablename__ = 'c_topology_views'
|
||||
|
||||
name = db.Column(db.String(64), index=True)
|
||||
group_id = db.Column(db.Integer, db.ForeignKey('c_topology_view_groups.id'))
|
||||
category = db.Column(db.String(32))
|
||||
central_node_type = db.Column(db.Integer)
|
||||
central_node_instances = db.Column(db.Text)
|
||||
path = db.Column(db.JSON)
|
||||
order = db.Column(db.Integer, default=0)
|
||||
option = db.Column(db.JSON)
|
||||
|
||||
|
||||
class CITypeUniqueConstraint(Model):
|
||||
__tablename__ = "c_c_t_u_c"
|
||||
|
||||
@@ -432,6 +455,7 @@ class CITypeHistory(Model):
|
||||
|
||||
attr_id = db.Column(db.Integer)
|
||||
trigger_id = db.Column(db.Integer)
|
||||
rc_id = db.Column(db.Integer)
|
||||
unique_constraint_id = db.Column(db.Integer)
|
||||
|
||||
uid = db.Column(db.Integer, index=True)
|
||||
|
@@ -3,12 +3,13 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
from celery_once import QueueOnce
|
||||
import redis_lock
|
||||
from flask import current_app
|
||||
from werkzeug.exceptions import BadRequest
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from api.extensions import celery
|
||||
from api.extensions import rd
|
||||
from api.lib.decorator import flush_db
|
||||
from api.lib.decorator import reconnect_db
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
@@ -25,14 +26,14 @@ from api.models.acl import Role
|
||||
from api.models.acl import Trigger
|
||||
|
||||
|
||||
@celery.task(name="acl.role_rebuild",
|
||||
queue=ACL_QUEUE,)
|
||||
@celery.task(name="acl.role_rebuild", queue=ACL_QUEUE, )
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def role_rebuild(rids, app_id):
|
||||
rids = rids if isinstance(rids, list) else [rids]
|
||||
for rid in rids:
|
||||
RoleRelationCache.rebuild(rid, app_id)
|
||||
with redis_lock.Lock(rd.r, "ROLE_REBUILD_{}_{}".format(rid, app_id)):
|
||||
RoleRelationCache.rebuild(rid, app_id)
|
||||
|
||||
current_app.logger.info("Role {0} App {1} rebuild..........".format(rids, app_id))
|
||||
|
||||
|
@@ -113,18 +113,17 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
|
||||
@reconnect_db
|
||||
def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
||||
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
||||
if ancestor_ids is None:
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
|
||||
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
|
||||
first=True, to_dict=False)
|
||||
if str(child_id) not in children:
|
||||
children[str(child_id)] = cr.second_ci.type_id
|
||||
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
|
||||
first=True, to_dict=False)
|
||||
if str(child_id) not in children:
|
||||
children[str(child_id)] = cr.second_ci.type_id
|
||||
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
|
||||
else:
|
||||
if ancestor_ids is not None:
|
||||
key = "{},{}".format(ancestor_ids, parent_id)
|
||||
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
||||
grandson = json.loads(grandson) if grandson is not None else {}
|
||||
@@ -191,16 +190,15 @@ def ci_relation_add(parent_dict, child_id, uid):
|
||||
@reconnect_db
|
||||
def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
||||
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
||||
if ancestor_ids is None:
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
|
||||
if str(child_id) in children:
|
||||
children.pop(str(child_id))
|
||||
if str(child_id) in children:
|
||||
children.pop(str(child_id))
|
||||
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
|
||||
else:
|
||||
if ancestor_ids is not None:
|
||||
key = "{},{}".format(ancestor_ids, parent_id)
|
||||
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
||||
grandson = json.loads(grandson) if grandson is not None else {}
|
||||
|
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2024-03-29 10:42+0800\n"
|
||||
"POT-Creation-Date: 2024-05-28 18:05+0800\n"
|
||||
"PO-Revision-Date: 2023-12-25 20:21+0800\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: zh\n"
|
||||
@@ -284,166 +284,198 @@ msgstr "重复的触发器"
|
||||
msgid "Trigger {} does not exist"
|
||||
msgstr "触发器 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:81
|
||||
msgid "Duplicated reconciliation rule"
|
||||
msgstr ""
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:82
|
||||
msgid "Reconciliation rule {} does not exist"
|
||||
msgstr "关系类型 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:84
|
||||
msgid "Operation record {} does not exist"
|
||||
msgstr "操作记录 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:83
|
||||
#: api/lib/cmdb/resp_format.py:85
|
||||
msgid "Unique identifier cannot be deleted"
|
||||
msgstr "不能删除唯一标识"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:84
|
||||
#: api/lib/cmdb/resp_format.py:86
|
||||
msgid "Cannot delete default sorted attributes"
|
||||
msgstr "不能删除默认排序的属性"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:86
|
||||
#: api/lib/cmdb/resp_format.py:88
|
||||
msgid "No node selected"
|
||||
msgstr "没有选择节点"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:87
|
||||
#: api/lib/cmdb/resp_format.py:89
|
||||
msgid "This search option does not exist!"
|
||||
msgstr "该搜索选项不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:88
|
||||
#: api/lib/cmdb/resp_format.py:90
|
||||
msgid "This search option has a duplicate name!"
|
||||
msgstr "该搜索选项命名重复!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:90
|
||||
#: api/lib/cmdb/resp_format.py:92
|
||||
msgid "Relationship type {} already exists"
|
||||
msgstr "关系类型 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:91
|
||||
#: api/lib/cmdb/resp_format.py:93
|
||||
msgid "Relationship type {} does not exist"
|
||||
msgstr "关系类型 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:93
|
||||
#: api/lib/cmdb/resp_format.py:95
|
||||
msgid "Invalid attribute value: {}"
|
||||
msgstr "无效的属性值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:94
|
||||
#: api/lib/cmdb/resp_format.py:96
|
||||
msgid "{} Invalid value: {}"
|
||||
msgstr "{} 无效的值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:95
|
||||
#: api/lib/cmdb/resp_format.py:97
|
||||
msgid "{} is not in the predefined values"
|
||||
msgstr "{} 不在预定义值里"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:97
|
||||
#: api/lib/cmdb/resp_format.py:99
|
||||
msgid "The value of attribute {} must be unique, {} already exists"
|
||||
msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:98
|
||||
#: api/lib/cmdb/resp_format.py:100
|
||||
msgid "Attribute {} value must exist"
|
||||
msgstr "属性 {} 值必须存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:99
|
||||
#: api/lib/cmdb/resp_format.py:101
|
||||
msgid "Out of range value, the maximum value is 2147483647"
|
||||
msgstr "超过最大值限制, 最大值是2147483647"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:101
|
||||
#: api/lib/cmdb/resp_format.py:103
|
||||
msgid "Unknown error when adding or modifying attribute value: {}"
|
||||
msgstr "新增或者修改属性值未知错误: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:103
|
||||
#: api/lib/cmdb/resp_format.py:105
|
||||
msgid "Duplicate custom name"
|
||||
msgstr "订制名重复"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:105
|
||||
#: api/lib/cmdb/resp_format.py:107
|
||||
msgid "Number of models exceeds limit: {}"
|
||||
msgstr "模型数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:106
|
||||
#: api/lib/cmdb/resp_format.py:108
|
||||
msgid "The number of CIs exceeds the limit: {}"
|
||||
msgstr "CI数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:108
|
||||
#: api/lib/cmdb/resp_format.py:110
|
||||
msgid "Auto-discovery rule: {} already exists!"
|
||||
msgstr "自动发现规则: {} 已经存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:109
|
||||
#: api/lib/cmdb/resp_format.py:111
|
||||
msgid "Auto-discovery rule: {} does not exist!"
|
||||
msgstr "自动发现规则: {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:111
|
||||
#: api/lib/cmdb/resp_format.py:113
|
||||
msgid "This auto-discovery rule is referenced by the model and cannot be deleted!"
|
||||
msgstr "该自动发现规则被模型引用, 不能删除!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:113
|
||||
#: api/lib/cmdb/resp_format.py:115
|
||||
msgid "The application of auto-discovery rules cannot be defined repeatedly!"
|
||||
msgstr "自动发现规则的应用不能重复定义!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:114
|
||||
#: api/lib/cmdb/resp_format.py:116
|
||||
msgid "The auto-discovery you want to modify: {} does not exist!"
|
||||
msgstr "您要修改的自动发现: {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:115
|
||||
#: api/lib/cmdb/resp_format.py:117
|
||||
msgid "Attribute does not include unique identifier: {}"
|
||||
msgstr "属性字段没有包括唯一标识: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:116
|
||||
#: api/lib/cmdb/resp_format.py:118
|
||||
msgid "The auto-discovery instance does not exist!"
|
||||
msgstr "自动发现的实例不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:117
|
||||
#: api/lib/cmdb/resp_format.py:119
|
||||
msgid "The model is not associated with this auto-discovery!"
|
||||
msgstr "模型并未关联该自动发现!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:118
|
||||
#: api/lib/cmdb/resp_format.py:120
|
||||
msgid "Only the creator can modify the Secret!"
|
||||
msgstr "只有创建人才能修改Secret!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:120
|
||||
#: api/lib/cmdb/resp_format.py:122
|
||||
msgid "This rule already has auto-discovery instances and cannot be deleted!"
|
||||
msgstr "该规则已经有自动发现的实例, 不能被删除!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:122
|
||||
#: api/lib/cmdb/resp_format.py:124
|
||||
msgid "The default auto-discovery rule is already referenced by model {}!"
|
||||
msgstr "该默认的自动发现规则 已经被模型 {} 引用!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:124
|
||||
#: api/lib/cmdb/resp_format.py:126
|
||||
msgid "The unique_key method must return a non-empty string!"
|
||||
msgstr "unique_key方法必须返回非空字符串!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:125
|
||||
#: api/lib/cmdb/resp_format.py:127
|
||||
msgid "The attributes method must return a list"
|
||||
msgstr "attributes方法必须返回的是list"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:127
|
||||
#: api/lib/cmdb/resp_format.py:129
|
||||
msgid "The list returned by the attributes method cannot be empty!"
|
||||
msgstr "attributes方法返回的list不能为空!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:129
|
||||
#: api/lib/cmdb/resp_format.py:131
|
||||
msgid "Only administrators can define execution targets as: all nodes!"
|
||||
msgstr "只有管理员才可以定义执行机器为: 所有节点!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:130
|
||||
#: api/lib/cmdb/resp_format.py:132
|
||||
msgid "Execute targets permission check failed: {}"
|
||||
msgstr "执行机器权限检查不通过: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:132
|
||||
#: api/lib/cmdb/resp_format.py:134
|
||||
msgid "CI filter authorization must be named!"
|
||||
msgstr "CI过滤授权 必须命名!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:133
|
||||
#: api/lib/cmdb/resp_format.py:135
|
||||
msgid "CI filter authorization is currently not supported or query"
|
||||
msgstr "CI过滤授权 暂时不支持 或 查询"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:136
|
||||
#: api/lib/cmdb/resp_format.py:138
|
||||
msgid "You do not have permission to operate attribute {}!"
|
||||
msgstr "您没有属性 {} 的操作权限!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:137
|
||||
#: api/lib/cmdb/resp_format.py:139
|
||||
msgid "You do not have permission to operate this CI!"
|
||||
msgstr "您没有该CI的操作权限!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:139
|
||||
#: api/lib/cmdb/resp_format.py:141
|
||||
msgid "Failed to save password: {}"
|
||||
msgstr "保存密码失败: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:140
|
||||
#: api/lib/cmdb/resp_format.py:142
|
||||
msgid "Failed to get password: {}"
|
||||
msgstr "获取密码失败: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:144
|
||||
msgid "Scheduling time format error"
|
||||
msgstr "{}格式错误,应该为:%Y-%m-%d %H:%M:%S"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:145
|
||||
msgid "CMDB data reconciliation results"
|
||||
msgstr ""
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:146
|
||||
msgid "Number of {} illegal: {}"
|
||||
msgstr ""
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:148
|
||||
msgid "Topology view {} already exists"
|
||||
msgstr "拓扑视图 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:149
|
||||
msgid "Topology group {} already exists"
|
||||
msgstr "拓扑视图分组 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:151
|
||||
msgid "The group cannot be deleted because the topology view already exists"
|
||||
msgstr "因为该分组下定义了拓扑视图,不能删除"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:8
|
||||
msgid "Company info already existed"
|
||||
msgstr "公司信息已存在,无法创建!"
|
||||
@@ -700,6 +732,10 @@ msgstr "LDAP测试用户名必填"
|
||||
msgid "Company wide"
|
||||
msgstr "全公司"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:84
|
||||
msgid "No permission to access resource {}, perm {} "
|
||||
msgstr "您没有资源: {} 的 {} 权限"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:9
|
||||
msgid "login successful"
|
||||
msgstr "登录成功"
|
||||
|
@@ -16,6 +16,7 @@ from api.lib.cmdb.const import RetKey
|
||||
from api.lib.cmdb.perms import has_perm_for_ci
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.utils import get_page
|
||||
from api.lib.utils import get_page_size
|
||||
@@ -254,3 +255,23 @@ class CIPasswordView(APIView):
|
||||
|
||||
def post(self, ci_id, attr_id):
|
||||
return self.get(ci_id, attr_id)
|
||||
|
||||
|
||||
class CIBaselineView(APIView):
|
||||
url_prefix = ("/ci/baseline", "/ci/<int:ci_id>/baseline/rollback")
|
||||
|
||||
@args_required("before_date")
|
||||
def get(self):
|
||||
ci_ids = handle_arg_list(request.values.get('ci_ids'))
|
||||
before_date = request.values.get('before_date')
|
||||
|
||||
return self.jsonify(CIManager().baseline(list(map(int, ci_ids)), before_date))
|
||||
|
||||
@args_required("before_date")
|
||||
def post(self, ci_id):
|
||||
if 'rollback' in request.url:
|
||||
before_date = request.values.get('before_date')
|
||||
|
||||
return self.jsonify(**CIManager().rollback(ci_id, before_date))
|
||||
|
||||
return self.get(ci_id)
|
||||
|
@@ -7,7 +7,6 @@ from io import BytesIO
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
from flask import session
|
||||
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
@@ -19,10 +18,12 @@ from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.ci_type import CITypeTemplateManager
|
||||
from api.lib.cmdb.ci_type import CITypeTriggerManager
|
||||
from api.lib.cmdb.ci_type import CITypeUniqueConstraintManager
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
@@ -36,6 +37,8 @@ from api.lib.perm.auth import auth_with_app_token
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class CITypeView(APIView):
|
||||
url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>",
|
||||
@@ -116,7 +119,6 @@ class CITypeInheritanceView(APIView):
|
||||
class CITypeGroupView(APIView):
|
||||
url_prefix = ("/ci_types/groups",
|
||||
"/ci_types/groups/config",
|
||||
"/ci_types/groups/order",
|
||||
"/ci_types/groups/<int:gid>")
|
||||
|
||||
def get(self):
|
||||
@@ -125,7 +127,8 @@ class CITypeGroupView(APIView):
|
||||
|
||||
return self.jsonify(CITypeGroupManager.get(need_other, config_required))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.create_CIType_group, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_validate(CITypeGroupManager.cls)
|
||||
def post(self):
|
||||
@@ -136,15 +139,6 @@ class CITypeGroupView(APIView):
|
||||
|
||||
@args_validate(CITypeGroupManager.cls)
|
||||
def put(self, gid=None):
|
||||
if "/order" in request.url:
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
group_ids = request.values.get('group_ids')
|
||||
CITypeGroupManager.order(group_ids)
|
||||
|
||||
return self.jsonify(group_ids=group_ids)
|
||||
|
||||
name = request.values.get('name') or abort(400, ErrFormat.argument_value_required.format("name"))
|
||||
type_ids = request.values.get('type_ids')
|
||||
|
||||
@@ -152,7 +146,8 @@ class CITypeGroupView(APIView):
|
||||
|
||||
return self.jsonify(gid=gid)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.delete_CIType_group, app_cli.admin_name)
|
||||
def delete(self, gid):
|
||||
type_ids = request.values.get("type_ids")
|
||||
CITypeGroupManager.delete(gid, type_ids)
|
||||
@@ -160,6 +155,18 @@ class CITypeGroupView(APIView):
|
||||
return self.jsonify(gid=gid)
|
||||
|
||||
|
||||
class CITypeGroupOrderView(APIView):
|
||||
url_prefix = "/ci_types/groups/order"
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.update_CIType_group, app_cli.admin_name)
|
||||
def put(self):
|
||||
group_ids = request.values.get('group_ids')
|
||||
CITypeGroupManager.order(group_ids)
|
||||
|
||||
return self.jsonify(group_ids=group_ids)
|
||||
|
||||
|
||||
class CITypeQueryView(APIView):
|
||||
url_prefix = "/ci_types/query"
|
||||
|
||||
@@ -352,14 +359,16 @@ class CITypeAttributeGroupView(APIView):
|
||||
class CITypeTemplateView(APIView):
|
||||
url_prefix = ("/ci_types/template/import", "/ci_types/template/export", "/ci_types/<int:type_id>/template/export")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def get(self, type_id=None): # export
|
||||
if type_id is not None:
|
||||
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template_by_type(type_id)))
|
||||
|
||||
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template()))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def post(self): # import
|
||||
tpt = request.values.get('ci_type_template') or {}
|
||||
|
||||
@@ -379,7 +388,8 @@ class CITypeCanDefineComputed(APIView):
|
||||
class CITypeTemplateFileView(APIView):
|
||||
url_prefix = ("/ci_types/template/import/file", "/ci_types/template/export/file")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def get(self): # export
|
||||
tpt_json = CITypeTemplateManager.export_template()
|
||||
tpt_json = dict(ci_type_template=tpt_json)
|
||||
@@ -394,7 +404,8 @@ class CITypeTemplateFileView(APIView):
|
||||
mimetype='application/json',
|
||||
max_age=0)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def post(self): # import
|
||||
f = request.files.get('file')
|
||||
|
||||
|
@@ -11,6 +11,8 @@ 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.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
@@ -18,6 +20,8 @@ from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class GetChildrenView(APIView):
|
||||
url_prefix = ("/ci_type_relations/<int:parent_id>/children",
|
||||
@@ -41,7 +45,8 @@ class GetParentsView(APIView):
|
||||
class CITypeRelationView(APIView):
|
||||
url_prefix = ("/ci_type_relations", "/ci_type_relations/<int:parent_id>/<int:child_id>")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
res, type2attributes = CITypeRelationManager.get()
|
||||
|
||||
@@ -52,10 +57,10 @@ class CITypeRelationView(APIView):
|
||||
def post(self, parent_id, child_id):
|
||||
relation_type_id = request.values.get("relation_type_id")
|
||||
constraint = request.values.get("constraint")
|
||||
parent_attr_id = request.values.get("parent_attr_id")
|
||||
child_attr_id = request.values.get("child_attr_id")
|
||||
parent_attr_ids = request.values.get("parent_attr_ids")
|
||||
child_attr_ids = request.values.get("child_attr_ids")
|
||||
ctr_id = CITypeRelationManager.add(parent_id, child_id, relation_type_id, constraint,
|
||||
parent_attr_id, child_attr_id)
|
||||
parent_attr_ids, child_attr_ids)
|
||||
|
||||
return self.jsonify(ctr_id=ctr_id)
|
||||
|
||||
@@ -69,7 +74,8 @@ class CITypeRelationView(APIView):
|
||||
class CITypeRelationDelete2View(APIView):
|
||||
url_prefix = "/ci_type_relations/<int:ctr_id>"
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Relationships,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def delete(self, ctr_id):
|
||||
CITypeRelationManager.delete(ctr_id)
|
||||
|
||||
|
@@ -3,14 +3,16 @@
|
||||
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.custom_dashboard import CustomDashboardManager
|
||||
from api.lib.cmdb.custom_dashboard import SystemConfigManager
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class CustomDashboardApiView(APIView):
|
||||
url_prefix = ("/custom_dashboard", "/custom_dashboard/<int:_id>", "/custom_dashboard/batch",
|
||||
@@ -19,7 +21,8 @@ class CustomDashboardApiView(APIView):
|
||||
def get(self):
|
||||
return self.jsonify(CustomDashboardManager.get())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_validate(CustomDashboardManager.cls)
|
||||
def post(self):
|
||||
if request.url.endswith("/preview"):
|
||||
@@ -32,7 +35,8 @@ class CustomDashboardApiView(APIView):
|
||||
|
||||
return self.jsonify(res)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_validate(CustomDashboardManager.cls)
|
||||
def put(self, _id=None):
|
||||
if _id is not None:
|
||||
@@ -47,7 +51,8 @@ class CustomDashboardApiView(APIView):
|
||||
|
||||
return self.jsonify(id2options=request.values.get('id2options'))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def delete(self, _id):
|
||||
CustomDashboardManager.delete(_id)
|
||||
|
||||
@@ -57,12 +62,14 @@ class CustomDashboardApiView(APIView):
|
||||
class SystemConfigApiView(APIView):
|
||||
url_prefix = ("/system_config",)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name", value_required=True)
|
||||
def get(self):
|
||||
return self.jsonify(SystemConfigManager.get(request.values['name']))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_validate(SystemConfigManager.cls)
|
||||
@args_required("name", value_required=True)
|
||||
@args_required("option", value_required=True)
|
||||
@@ -74,7 +81,8 @@ class SystemConfigApiView(APIView):
|
||||
def put(self, _id=None):
|
||||
return self.post()
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
def delete(self):
|
||||
CustomDashboardManager.delete(request.values['name'])
|
||||
|
@@ -5,28 +5,29 @@ import datetime
|
||||
|
||||
from flask import abort
|
||||
from flask import request
|
||||
from flask import session
|
||||
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
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.history import AttributeHistoryManger
|
||||
from api.lib.cmdb.history import CITriggerHistoryManager
|
||||
from api.lib.cmdb.history import CITypeHistoryManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.lib.utils import get_page
|
||||
from api.lib.utils import get_page_size
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class RecordView(APIView):
|
||||
url_prefix = ("/history/records/attribute", "/history/records/relation")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
page = get_page(request.values.get("page", 1))
|
||||
page_size = get_page_size(request.values.get("page_size"))
|
||||
@@ -80,18 +81,21 @@ class CIHistoryView(APIView):
|
||||
|
||||
|
||||
class CITriggerHistoryView(APIView):
|
||||
url_prefix = ("/history/ci_triggers/<int:ci_id>", "/history/ci_triggers")
|
||||
url_prefix = ("/history/ci_triggers/<int:ci_id>",)
|
||||
|
||||
@has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.READ, CIManager.get_type_name)
|
||||
def get(self, ci_id=None):
|
||||
if ci_id is not None:
|
||||
result = CITriggerHistoryManager.get_by_ci_id(ci_id)
|
||||
def get(self, ci_id):
|
||||
result = CITriggerHistoryManager.get_by_ci_id(ci_id)
|
||||
|
||||
return self.jsonify(result)
|
||||
return self.jsonify(result)
|
||||
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
class CIsTriggerHistoryView(APIView):
|
||||
url_prefix = ("/history/ci_triggers",)
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
type_id = request.values.get("type_id")
|
||||
trigger_id = request.values.get("trigger_id")
|
||||
operate_type = request.values.get("operate_type")
|
||||
@@ -115,7 +119,8 @@ class CITriggerHistoryView(APIView):
|
||||
class CITypeHistoryView(APIView):
|
||||
url_prefix = "/history/ci_types"
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
type_id = request.values.get("type_id")
|
||||
username = request.values.get("username")
|
||||
|
@@ -8,20 +8,22 @@ from flask import request
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
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.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.lib.perm.acl.acl import validate_permission
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class PreferenceShowCITypesView(APIView):
|
||||
url_prefix = ("/preference/ci_types", "/preference/ci_types2")
|
||||
@@ -104,7 +106,8 @@ class PreferenceRelationApiView(APIView):
|
||||
|
||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_required("cr_ids")
|
||||
@args_validate(PreferenceManager.pref_rel_cls)
|
||||
@@ -118,14 +121,16 @@ class PreferenceRelationApiView(APIView):
|
||||
|
||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
def put(self, _id):
|
||||
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(_id=_id, **request.values)
|
||||
|
||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
def delete(self):
|
||||
name = request.values.get("name")
|
||||
|
@@ -4,14 +4,16 @@
|
||||
from flask import abort
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.relation_type import RelationTypeManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class RelationTypeView(APIView):
|
||||
url_prefix = ("/relation_types", "/relation_types/<int:rel_id>")
|
||||
@@ -19,7 +21,8 @@ class RelationTypeView(APIView):
|
||||
def get(self):
|
||||
return self.jsonify([i.to_dict() for i in RelationTypeManager.get_all()])
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_validate(RelationTypeManager.cls)
|
||||
def post(self):
|
||||
@@ -28,7 +31,8 @@ class RelationTypeView(APIView):
|
||||
|
||||
return self.jsonify(rel.to_dict())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_validate(RelationTypeManager.cls)
|
||||
def put(self, rel_id):
|
||||
@@ -37,7 +41,8 @@ class RelationTypeView(APIView):
|
||||
|
||||
return self.jsonify(rel.to_dict())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def delete(self, rel_id):
|
||||
RelationTypeManager.delete(rel_id)
|
||||
|
||||
|
178
cmdb-api/api/views/cmdb/topology.py
Normal file
178
cmdb-api/api/views/cmdb/topology.py
Normal file
@@ -0,0 +1,178 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import abort
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.topology import TopologyViewManager
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class TopologyGroupView(APIView):
|
||||
url_prefix = ('/topology_views/groups', '/topology_views/groups/<int:group_id>')
|
||||
|
||||
@args_required('name')
|
||||
@args_validate(TopologyViewManager.group_cls)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.create_topology_group, app_cli.admin_name)
|
||||
def post(self):
|
||||
name = request.values.get('name')
|
||||
order = request.values.get('order')
|
||||
|
||||
group = TopologyViewManager.add_group(name, order)
|
||||
|
||||
return self.jsonify(group.to_dict())
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.update_topology_group, app_cli.admin_name)
|
||||
def put(self, group_id):
|
||||
name = request.values.get('name')
|
||||
view_ids = request.values.get('view_ids')
|
||||
group = TopologyViewManager().update_group(group_id, name, view_ids)
|
||||
|
||||
return self.jsonify(**group)
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.delete_topology_group, app_cli.admin_name)
|
||||
def delete(self, group_id):
|
||||
TopologyViewManager.delete_group(group_id)
|
||||
|
||||
return self.jsonify(group_id=group_id)
|
||||
|
||||
|
||||
class TopologyGroupOrderView(APIView):
|
||||
url_prefix = ('/topology_views/groups/order',)
|
||||
|
||||
@args_required('group_ids')
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.update_topology_group, app_cli.admin_name)
|
||||
def post(self):
|
||||
group_ids = request.values.get('group_ids')
|
||||
|
||||
TopologyViewManager.group_order(group_ids)
|
||||
|
||||
return self.jsonify(group_ids=group_ids)
|
||||
|
||||
def put(self):
|
||||
return self.post()
|
||||
|
||||
|
||||
class TopologyView(APIView):
|
||||
url_prefix = ('/topology_views', '/topology_views/relations/ci_types/<int:type_id>', '/topology_views/<int:_id>')
|
||||
|
||||
def get(self, type_id=None, _id=None):
|
||||
if type_id is not None:
|
||||
return self.jsonify(TopologyViewManager.relation_from_ci_type(type_id))
|
||||
|
||||
if _id is not None:
|
||||
return self.jsonify(TopologyViewManager().get_view_by_id(_id))
|
||||
|
||||
return self.jsonify(TopologyViewManager.get_all())
|
||||
|
||||
@args_required('name', 'central_node_type', 'central_node_instances', 'path', 'group_id')
|
||||
@args_validate(TopologyViewManager.cls)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.create_topology_view, app_cli.admin_name)
|
||||
def post(self):
|
||||
name = request.values.pop('name')
|
||||
group_id = request.values.pop('group_id', None)
|
||||
option = request.values.pop('option', None)
|
||||
order = request.values.pop('order', None)
|
||||
|
||||
topo_view = TopologyViewManager.add(name, group_id, option, order, **request.values)
|
||||
|
||||
return self.jsonify(topo_view)
|
||||
|
||||
@args_validate(TopologyViewManager.cls)
|
||||
@has_perm_from_args("_id", ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.UPDATE, TopologyViewManager.get_name_by_id)
|
||||
def put(self, _id):
|
||||
topo_view = TopologyViewManager.update(_id, **request.values)
|
||||
|
||||
return self.jsonify(topo_view)
|
||||
|
||||
@has_perm_from_args("_id", ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.DELETE, TopologyViewManager.get_name_by_id)
|
||||
def delete(self, _id):
|
||||
TopologyViewManager.delete(_id)
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
|
||||
class TopologyOrderView(APIView):
|
||||
url_prefix = ('/topology_views/order',)
|
||||
|
||||
@args_required('view_ids')
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.create_topology_view, app_cli.admin_name)
|
||||
def post(self):
|
||||
view_ids = request.values.get('view_ids')
|
||||
|
||||
TopologyViewManager.group_inner_order(view_ids)
|
||||
|
||||
return self.jsonify(view_ids=view_ids)
|
||||
|
||||
def put(self):
|
||||
return self.post()
|
||||
|
||||
|
||||
class TopologyViewPreview(APIView):
|
||||
url_prefix = ('/topology_views/preview', '/topology_views/<int:_id>/view')
|
||||
|
||||
def get(self, _id=None):
|
||||
if _id is not None:
|
||||
acl = ACLManager('cmdb')
|
||||
resource_name = TopologyViewManager.get_name_by_id(_id)
|
||||
if (not acl.has_permission(resource_name, ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.READ) and
|
||||
not is_app_admin('cmdb')):
|
||||
return abort(403, ErrFormat.no_permission.format(resource_name, PermEnum.READ))
|
||||
|
||||
return self.jsonify(TopologyViewManager().topology_view(view_id=_id))
|
||||
else:
|
||||
return self.jsonify(TopologyViewManager().topology_view(preview=request.values))
|
||||
|
||||
def post(self, _id=None):
|
||||
return self.get(_id)
|
||||
|
||||
|
||||
class TopologyViewGrantView(APIView):
|
||||
url_prefix = "/topology_views/<int:view_id>/roles/<int:rid>/grant"
|
||||
|
||||
def post(self, view_id, rid):
|
||||
perms = request.values.pop('perms', None)
|
||||
|
||||
view_name = TopologyViewManager.get_name_by_id(view_id) or abort(404, ErrFormat.not_found)
|
||||
acl = ACLManager('cmdb')
|
||||
if not acl.has_permission(view_name, ResourceTypeEnum.TOPOLOGY_VIEW,
|
||||
PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||
return abort(403, ErrFormat.no_permission.format(view_name, PermEnum.GRANT))
|
||||
|
||||
acl.grant_resource_to_role_by_rid(view_name, rid, ResourceTypeEnum.TOPOLOGY_VIEW, perms, rebuild=True)
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
|
||||
class TopologyViewRevokeView(APIView):
|
||||
url_prefix = "/topology_views/<int:view_id>/roles/<int:rid>/revoke"
|
||||
|
||||
@args_required('perms')
|
||||
def post(self, view_id, rid):
|
||||
perms = request.values.pop('perms', None)
|
||||
|
||||
view_name = TopologyViewManager.get_name_by_id(view_id) or abort(404, ErrFormat.not_found)
|
||||
acl = ACLManager('cmdb')
|
||||
if not acl.has_permission(view_name, ResourceTypeEnum.TOPOLOGY_VIEW,
|
||||
PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||
return abort(403, ErrFormat.no_permission.format(view_name, PermEnum.GRANT))
|
||||
|
||||
acl.revoke_resource_from_role_by_rid(view_name, rid, ResourceTypeEnum.TOPOLOGY_VIEW, perms, rebuild=True)
|
||||
|
||||
return self.jsonify(code=200)
|
@@ -1,7 +1,7 @@
|
||||
-i https://mirrors.aliyun.com/pypi/simple
|
||||
alembic==1.7.7
|
||||
bs4==0.0.1
|
||||
celery>=5.3.1
|
||||
celery==5.3.1
|
||||
celery-once==3.0.1
|
||||
click==8.1.3
|
||||
elasticsearch==7.17.9
|
||||
@@ -53,4 +53,4 @@ shamir~=17.12.0
|
||||
pycryptodomex>=3.19.0
|
||||
colorama>=0.4.6
|
||||
lz4>=4.3.2
|
||||
python-magic==0.4.27
|
||||
python-magic==0.4.27
|
||||
|
@@ -20,10 +20,16 @@ DEBUG_TB_INTERCEPT_REDIRECTS = False
|
||||
|
||||
ERROR_CODES = [400, 401, 403, 404, 405, 500, 502]
|
||||
|
||||
MYSQL_USER = env.str('MYSQL_USER', default='cmdb')
|
||||
MYSQL_PASSWORD = env.str('MYSQL_PASSWORD', default='123456')
|
||||
MYSQL_HOST = env.str('MYSQL_HOST', default='127.0.0.1')
|
||||
MYSQL_PORT = env.int('MYSQL_PORT', default=3306)
|
||||
MYSQL_DATABASE = env.str('MYSQL_DATABASE', default='cmdb')
|
||||
# # database
|
||||
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
|
||||
SQLALCHEMY_DATABASE_URI = f'mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@' \
|
||||
f'{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}?charset=utf8'
|
||||
SQLALCHEMY_BINDS = {
|
||||
'user': 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
|
||||
'user': SQLALCHEMY_DATABASE_URI
|
||||
}
|
||||
SQLALCHEMY_ECHO = False
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
|
@@ -39,7 +39,7 @@
|
||||
"md5": "^2.2.1",
|
||||
"moment": "^2.24.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"relation-graph": "^1.1.0",
|
||||
"relation-graph": "^2.1.42",
|
||||
"snabbdom": "^3.5.1",
|
||||
"sortablejs": "1.9.0",
|
||||
"style-resources-loader": "^1.5.0",
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3857903 */
|
||||
src: url('iconfont.woff2?t=1713335725699') format('woff2'),
|
||||
url('iconfont.woff?t=1713335725699') format('woff'),
|
||||
url('iconfont.ttf?t=1713335725699') format('truetype');
|
||||
src: url('iconfont.woff2?t=1716896994700') format('woff2'),
|
||||
url('iconfont.woff?t=1716896994700') format('woff'),
|
||||
url('iconfont.ttf?t=1716896994700') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,194 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.ops-topology_view:before {
|
||||
content: "\e92b";
|
||||
}
|
||||
|
||||
.monitor-host_analysis:before {
|
||||
content: "\e92a";
|
||||
}
|
||||
|
||||
.a-Group427319324:before {
|
||||
content: "\e929";
|
||||
}
|
||||
|
||||
.monitor-native:before {
|
||||
content: "\e928";
|
||||
}
|
||||
|
||||
.veops-filter2:before {
|
||||
content: "\e927";
|
||||
}
|
||||
|
||||
.ops-cmdb-data_companies-selected:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.ops-cmdb-data_companies:before {
|
||||
content: "\e926";
|
||||
}
|
||||
|
||||
.monitor-threshold_value:before {
|
||||
content: "\e921";
|
||||
}
|
||||
|
||||
.monitor-disposition:before {
|
||||
content: "\e922";
|
||||
}
|
||||
|
||||
.monitor-automatic_discovery:before {
|
||||
content: "\e923";
|
||||
}
|
||||
|
||||
.monitor-grouping_list:before {
|
||||
content: "\e924";
|
||||
}
|
||||
|
||||
.monitor-node_list:before {
|
||||
content: "\e925";
|
||||
}
|
||||
|
||||
.monitor-general_view:before {
|
||||
content: "\e920";
|
||||
}
|
||||
|
||||
.monitor-network_topology:before {
|
||||
content: "\e91b";
|
||||
}
|
||||
|
||||
.monitor-node_management:before {
|
||||
content: "\e91c";
|
||||
}
|
||||
|
||||
.monitor-alarm_policy:before {
|
||||
content: "\e91d";
|
||||
}
|
||||
|
||||
.monitor-alarm:before {
|
||||
content: "\e91e";
|
||||
}
|
||||
|
||||
.monitor-healing:before {
|
||||
content: "\e91f";
|
||||
}
|
||||
|
||||
.monitor-data_acquisition:before {
|
||||
content: "\e8d4";
|
||||
}
|
||||
|
||||
.monitor-analysis:before {
|
||||
content: "\e91a";
|
||||
}
|
||||
|
||||
.monitor-index:before {
|
||||
content: "\e89b";
|
||||
}
|
||||
|
||||
.monitor-user_defined:before {
|
||||
content: "\e867";
|
||||
}
|
||||
|
||||
.monitor-database:before {
|
||||
content: "\e861";
|
||||
}
|
||||
|
||||
.monitor-common:before {
|
||||
content: "\e865";
|
||||
}
|
||||
|
||||
.veops-edit:before {
|
||||
content: "\e866";
|
||||
}
|
||||
|
||||
.veops-empower:before {
|
||||
content: "\e863";
|
||||
}
|
||||
|
||||
.veops-share:before {
|
||||
content: "\e864";
|
||||
}
|
||||
|
||||
.veops-export:before {
|
||||
content: "\e862";
|
||||
}
|
||||
|
||||
.a-veops-import1:before {
|
||||
content: "\e860";
|
||||
}
|
||||
|
||||
.monitor-ip:before {
|
||||
content: "\e807";
|
||||
}
|
||||
|
||||
.monitor-director:before {
|
||||
content: "\e803";
|
||||
}
|
||||
|
||||
.monitor-host:before {
|
||||
content: "\e804";
|
||||
}
|
||||
|
||||
.a-cmdb-log1:before {
|
||||
content: "\e802";
|
||||
}
|
||||
|
||||
.monitor-add:before {
|
||||
content: "\e7ff";
|
||||
}
|
||||
|
||||
.monitor-down:before {
|
||||
content: "\e7fc";
|
||||
}
|
||||
|
||||
.monitor-up:before {
|
||||
content: "\e7fd";
|
||||
}
|
||||
|
||||
.itsm-unfold:before {
|
||||
content: "\e7f9";
|
||||
}
|
||||
|
||||
.itsm-stretch:before {
|
||||
content: "\e7f8";
|
||||
}
|
||||
|
||||
.monitor-data_comaparison2:before {
|
||||
content: "\e7a1";
|
||||
}
|
||||
|
||||
.monitor-data_comaparison1:before {
|
||||
content: "\e7f7";
|
||||
}
|
||||
|
||||
.a-monitor-online1:before {
|
||||
content: "\e7a0";
|
||||
}
|
||||
|
||||
.ops-setting-application-selected:before {
|
||||
content: "\e919";
|
||||
}
|
||||
|
||||
.ops-setting-application:before {
|
||||
content: "\e918";
|
||||
}
|
||||
|
||||
.ops-setting-basic:before {
|
||||
content: "\e889";
|
||||
}
|
||||
|
||||
.ops-setting-basic-selected:before {
|
||||
content: "\e917";
|
||||
}
|
||||
|
||||
.ops-setting-security:before {
|
||||
content: "\e915";
|
||||
}
|
||||
|
||||
.ops-setting-theme:before {
|
||||
content: "\e916";
|
||||
}
|
||||
|
||||
.veops-show:before {
|
||||
content: "\e914";
|
||||
}
|
||||
@@ -21,7 +209,7 @@
|
||||
content: "\e913";
|
||||
}
|
||||
|
||||
.a-itsm-workload1:before {
|
||||
.itsm-workload:before {
|
||||
content: "\e912";
|
||||
}
|
||||
|
||||
@@ -269,10 +457,6 @@
|
||||
content: "\e8d5";
|
||||
}
|
||||
|
||||
.ops-setting-auth-selected:before {
|
||||
content: "\e8d4";
|
||||
}
|
||||
|
||||
.itsm-knowledge2:before {
|
||||
content: "\e8d2";
|
||||
}
|
||||
@@ -501,10 +685,6 @@
|
||||
content: "\e89c";
|
||||
}
|
||||
|
||||
.ops-setting-duty-selected:before {
|
||||
content: "\e89b";
|
||||
}
|
||||
|
||||
.datainsight-sequential:before {
|
||||
content: "\e899";
|
||||
}
|
||||
@@ -669,38 +849,6 @@
|
||||
content: "\e870";
|
||||
}
|
||||
|
||||
.ops-itsm-ticketsetting-selected:before {
|
||||
content: "\e860";
|
||||
}
|
||||
|
||||
.ops-itsm-reports-selected:before {
|
||||
content: "\e861";
|
||||
}
|
||||
|
||||
.ops-itsm-servicecatalog-selected:before {
|
||||
content: "\e862";
|
||||
}
|
||||
|
||||
.ops-itsm-ticketmanage-selected:before {
|
||||
content: "\e863";
|
||||
}
|
||||
|
||||
.ops-itsm-knowledge-selected:before {
|
||||
content: "\e864";
|
||||
}
|
||||
|
||||
.ops-itsm-workstation-selected:before {
|
||||
content: "\e865";
|
||||
}
|
||||
|
||||
.ops-itsm-servicedesk-selected:before {
|
||||
content: "\e866";
|
||||
}
|
||||
|
||||
.ops-itsm-planticket-selected:before {
|
||||
content: "\e867";
|
||||
}
|
||||
|
||||
.ops-itsm-servicecatalog:before {
|
||||
content: "\e868";
|
||||
}
|
||||
@@ -1073,26 +1221,10 @@
|
||||
content: "\e816";
|
||||
}
|
||||
|
||||
.ops-cmdb-batch-selected:before {
|
||||
content: "\e803";
|
||||
}
|
||||
|
||||
.ops-cmdb-batch:before {
|
||||
content: "\e80a";
|
||||
}
|
||||
|
||||
.ops-cmdb-adc-selected:before {
|
||||
content: "\e7f7";
|
||||
}
|
||||
|
||||
.ops-cmdb-resource-selected:before {
|
||||
content: "\e7f8";
|
||||
}
|
||||
|
||||
.ops-cmdb-preference-selected:before {
|
||||
content: "\e7f9";
|
||||
}
|
||||
|
||||
.ops-cmdb-preference:before {
|
||||
content: "\e7fa";
|
||||
}
|
||||
@@ -1101,22 +1233,10 @@
|
||||
content: "\e7fb";
|
||||
}
|
||||
|
||||
.ops-cmdb-tree-selected:before {
|
||||
content: "\e7fc";
|
||||
}
|
||||
|
||||
.ops-cmdb-relation-selected:before {
|
||||
content: "\e7fd";
|
||||
}
|
||||
|
||||
.ops-cmdb-adc:before {
|
||||
content: "\e7fe";
|
||||
}
|
||||
|
||||
.ops-cmdb-search-selected:before {
|
||||
content: "\e7ff";
|
||||
}
|
||||
|
||||
.ops-cmdb-relation:before {
|
||||
content: "\e800";
|
||||
}
|
||||
@@ -1125,14 +1245,6 @@
|
||||
content: "\e801";
|
||||
}
|
||||
|
||||
.ops-cmdb-citype-selected:before {
|
||||
content: "\e802";
|
||||
}
|
||||
|
||||
.ops-cmdb-dashboard-selected:before {
|
||||
content: "\e804";
|
||||
}
|
||||
|
||||
.ops-cmdb-citype:before {
|
||||
content: "\e805";
|
||||
}
|
||||
@@ -1141,10 +1253,6 @@
|
||||
content: "\e806";
|
||||
}
|
||||
|
||||
.ops-cmdb-screen-selected:before {
|
||||
content: "\e807";
|
||||
}
|
||||
|
||||
.ops-cmdb-resource:before {
|
||||
content: "\e808";
|
||||
}
|
||||
@@ -1493,14 +1601,6 @@
|
||||
content: "\e7a6";
|
||||
}
|
||||
|
||||
.ops-setting-role-selected:before {
|
||||
content: "\e7a0";
|
||||
}
|
||||
|
||||
.ops-setting-group-selected:before {
|
||||
content: "\e7a1";
|
||||
}
|
||||
|
||||
.ops-setting-role:before {
|
||||
content: "\e7a2";
|
||||
}
|
||||
@@ -1941,18 +2041,10 @@
|
||||
content: "\e738";
|
||||
}
|
||||
|
||||
.ops-setting-notice-email-selected-copy:before {
|
||||
content: "\e889";
|
||||
}
|
||||
|
||||
.ops-setting-notice:before {
|
||||
content: "\e72f";
|
||||
}
|
||||
|
||||
.ops-setting-notice-selected:before {
|
||||
content: "\e730";
|
||||
}
|
||||
|
||||
.ops-setting-notice-email-selected:before {
|
||||
content: "\e731";
|
||||
}
|
||||
@@ -1977,10 +2069,6 @@
|
||||
content: "\e736";
|
||||
}
|
||||
|
||||
.ops-setting-companyStructure-selected:before {
|
||||
content: "\e72b";
|
||||
}
|
||||
|
||||
.ops-setting-companyStructure:before {
|
||||
content: "\e72c";
|
||||
}
|
||||
@@ -1989,10 +2077,6 @@
|
||||
content: "\e72d";
|
||||
}
|
||||
|
||||
.ops-setting-companyInfo-selected:before {
|
||||
content: "\e72e";
|
||||
}
|
||||
|
||||
.ops-email:before {
|
||||
content: "\e61a";
|
||||
}
|
||||
@@ -3033,14 +3117,6 @@
|
||||
content: "\e600";
|
||||
}
|
||||
|
||||
.ops-dag-dashboard-selected:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.ops-dag-applet-selected:before {
|
||||
content: "\e602";
|
||||
}
|
||||
|
||||
.ops-dag-applet:before {
|
||||
content: "\e603";
|
||||
}
|
||||
@@ -3049,62 +3125,26 @@
|
||||
content: "\e604";
|
||||
}
|
||||
|
||||
.ops-dag-terminal-selected:before {
|
||||
content: "\e605";
|
||||
}
|
||||
|
||||
.ops-dag-cron:before {
|
||||
content: "\e606";
|
||||
}
|
||||
|
||||
.ops-dag-cron-selected:before {
|
||||
content: "\e608";
|
||||
}
|
||||
|
||||
.ops-dag-history:before {
|
||||
content: "\e609";
|
||||
}
|
||||
|
||||
.ops-dag-history-selected:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
|
||||
.ops-dag-dags-selected:before {
|
||||
content: "\e60c";
|
||||
}
|
||||
|
||||
.ops-dag-dagreview:before {
|
||||
content: "\e60d";
|
||||
}
|
||||
|
||||
.ops-dag-dagreview-selected:before {
|
||||
content: "\e60e";
|
||||
}
|
||||
|
||||
.ops-dag-panel:before {
|
||||
content: "\e60f";
|
||||
}
|
||||
|
||||
.ops-dag-panel-selected:before {
|
||||
content: "\e615";
|
||||
}
|
||||
|
||||
.ops-dag-variables:before {
|
||||
content: "\e616";
|
||||
}
|
||||
|
||||
.ops-dag-variables-selected:before {
|
||||
content: "\e618";
|
||||
}
|
||||
|
||||
.ops-dag-appletadmin:before {
|
||||
content: "\e65c";
|
||||
}
|
||||
|
||||
.ops-dag-appletadmin-selected:before {
|
||||
content: "\e65d";
|
||||
}
|
||||
|
||||
.ops-dag-dags:before {
|
||||
content: "\e60b";
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@@ -5,6 +5,335 @@
|
||||
"css_prefix_text": "",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "40499246",
|
||||
"name": "ops-topology_view",
|
||||
"font_class": "ops-topology_view",
|
||||
"unicode": "e92b",
|
||||
"unicode_decimal": 59691
|
||||
},
|
||||
{
|
||||
"icon_id": "40411336",
|
||||
"name": "monitor-host_analysis",
|
||||
"font_class": "monitor-host_analysis",
|
||||
"unicode": "e92a",
|
||||
"unicode_decimal": 59690
|
||||
},
|
||||
{
|
||||
"icon_id": "40372105",
|
||||
"name": "monitor-add2",
|
||||
"font_class": "a-Group427319324",
|
||||
"unicode": "e929",
|
||||
"unicode_decimal": 59689
|
||||
},
|
||||
{
|
||||
"icon_id": "40368097",
|
||||
"name": "monitor-native",
|
||||
"font_class": "monitor-native",
|
||||
"unicode": "e928",
|
||||
"unicode_decimal": 59688
|
||||
},
|
||||
{
|
||||
"icon_id": "40357355",
|
||||
"name": "veops-filter2",
|
||||
"font_class": "veops-filter2",
|
||||
"unicode": "e927",
|
||||
"unicode_decimal": 59687
|
||||
},
|
||||
{
|
||||
"icon_id": "40356229",
|
||||
"name": "ops-cmdb-data_companies-selected",
|
||||
"font_class": "ops-cmdb-data_companies-selected",
|
||||
"unicode": "e601",
|
||||
"unicode_decimal": 58881
|
||||
},
|
||||
{
|
||||
"icon_id": "40343814",
|
||||
"name": "ops-cmdb-data_companies",
|
||||
"font_class": "ops-cmdb-data_companies",
|
||||
"unicode": "e926",
|
||||
"unicode_decimal": 59686
|
||||
},
|
||||
{
|
||||
"icon_id": "40326916",
|
||||
"name": "monitor-threshold_value",
|
||||
"font_class": "monitor-threshold_value",
|
||||
"unicode": "e921",
|
||||
"unicode_decimal": 59681
|
||||
},
|
||||
{
|
||||
"icon_id": "40326913",
|
||||
"name": "monitor-disposition",
|
||||
"font_class": "monitor-disposition",
|
||||
"unicode": "e922",
|
||||
"unicode_decimal": 59682
|
||||
},
|
||||
{
|
||||
"icon_id": "40326911",
|
||||
"name": "monitor-automatic_discovery",
|
||||
"font_class": "monitor-automatic_discovery",
|
||||
"unicode": "e923",
|
||||
"unicode_decimal": 59683
|
||||
},
|
||||
{
|
||||
"icon_id": "40326907",
|
||||
"name": "monitor-grouping_list",
|
||||
"font_class": "monitor-grouping_list",
|
||||
"unicode": "e924",
|
||||
"unicode_decimal": 59684
|
||||
},
|
||||
{
|
||||
"icon_id": "40326906",
|
||||
"name": "monitor-node_list",
|
||||
"font_class": "monitor-node_list",
|
||||
"unicode": "e925",
|
||||
"unicode_decimal": 59685
|
||||
},
|
||||
{
|
||||
"icon_id": "40326667",
|
||||
"name": "monitor-general_view",
|
||||
"font_class": "monitor-general_view",
|
||||
"unicode": "e920",
|
||||
"unicode_decimal": 59680
|
||||
},
|
||||
{
|
||||
"icon_id": "40326683",
|
||||
"name": "monitor-network_topology",
|
||||
"font_class": "monitor-network_topology",
|
||||
"unicode": "e91b",
|
||||
"unicode_decimal": 59675
|
||||
},
|
||||
{
|
||||
"icon_id": "40326682",
|
||||
"name": "monitor-node_management",
|
||||
"font_class": "monitor-node_management",
|
||||
"unicode": "e91c",
|
||||
"unicode_decimal": 59676
|
||||
},
|
||||
{
|
||||
"icon_id": "40326681",
|
||||
"name": "monitor-alarm_policy",
|
||||
"font_class": "monitor-alarm_policy",
|
||||
"unicode": "e91d",
|
||||
"unicode_decimal": 59677
|
||||
},
|
||||
{
|
||||
"icon_id": "40326680",
|
||||
"name": "monitor-alarm",
|
||||
"font_class": "monitor-alarm",
|
||||
"unicode": "e91e",
|
||||
"unicode_decimal": 59678
|
||||
},
|
||||
{
|
||||
"icon_id": "40326679",
|
||||
"name": "monitor-healing",
|
||||
"font_class": "monitor-healing",
|
||||
"unicode": "e91f",
|
||||
"unicode_decimal": 59679
|
||||
},
|
||||
{
|
||||
"icon_id": "40326685",
|
||||
"name": "monitor-data_acquisition",
|
||||
"font_class": "monitor-data_acquisition",
|
||||
"unicode": "e8d4",
|
||||
"unicode_decimal": 59604
|
||||
},
|
||||
{
|
||||
"icon_id": "40326684",
|
||||
"name": "monitor-analysis",
|
||||
"font_class": "monitor-analysis",
|
||||
"unicode": "e91a",
|
||||
"unicode_decimal": 59674
|
||||
},
|
||||
{
|
||||
"icon_id": "40308359",
|
||||
"name": "monitor-index",
|
||||
"font_class": "monitor-index",
|
||||
"unicode": "e89b",
|
||||
"unicode_decimal": 59547
|
||||
},
|
||||
{
|
||||
"icon_id": "40307829",
|
||||
"name": "monitor-user_defined",
|
||||
"font_class": "monitor-user_defined",
|
||||
"unicode": "e867",
|
||||
"unicode_decimal": 59495
|
||||
},
|
||||
{
|
||||
"icon_id": "40307835",
|
||||
"name": "monitor-database",
|
||||
"font_class": "monitor-database",
|
||||
"unicode": "e861",
|
||||
"unicode_decimal": 59489
|
||||
},
|
||||
{
|
||||
"icon_id": "40307833",
|
||||
"name": "monitor-common",
|
||||
"font_class": "monitor-common",
|
||||
"unicode": "e865",
|
||||
"unicode_decimal": 59493
|
||||
},
|
||||
{
|
||||
"icon_id": "40307035",
|
||||
"name": "veops-edit",
|
||||
"font_class": "veops-edit",
|
||||
"unicode": "e866",
|
||||
"unicode_decimal": 59494
|
||||
},
|
||||
{
|
||||
"icon_id": "40307027",
|
||||
"name": "veops-empower",
|
||||
"font_class": "veops-empower",
|
||||
"unicode": "e863",
|
||||
"unicode_decimal": 59491
|
||||
},
|
||||
{
|
||||
"icon_id": "40307026",
|
||||
"name": "veops-share",
|
||||
"font_class": "veops-share",
|
||||
"unicode": "e864",
|
||||
"unicode_decimal": 59492
|
||||
},
|
||||
{
|
||||
"icon_id": "40306997",
|
||||
"name": "veops-export",
|
||||
"font_class": "veops-export",
|
||||
"unicode": "e862",
|
||||
"unicode_decimal": 59490
|
||||
},
|
||||
{
|
||||
"icon_id": "40306881",
|
||||
"name": "veops-import",
|
||||
"font_class": "a-veops-import1",
|
||||
"unicode": "e860",
|
||||
"unicode_decimal": 59488
|
||||
},
|
||||
{
|
||||
"icon_id": "40262335",
|
||||
"name": "monitor-ip (1)",
|
||||
"font_class": "monitor-ip",
|
||||
"unicode": "e807",
|
||||
"unicode_decimal": 59399
|
||||
},
|
||||
{
|
||||
"icon_id": "40262337",
|
||||
"name": "monitor-director",
|
||||
"font_class": "monitor-director",
|
||||
"unicode": "e803",
|
||||
"unicode_decimal": 59395
|
||||
},
|
||||
{
|
||||
"icon_id": "40262336",
|
||||
"name": "monitor-host",
|
||||
"font_class": "monitor-host",
|
||||
"unicode": "e804",
|
||||
"unicode_decimal": 59396
|
||||
},
|
||||
{
|
||||
"icon_id": "40262216",
|
||||
"name": "cmdb-log",
|
||||
"font_class": "a-cmdb-log1",
|
||||
"unicode": "e802",
|
||||
"unicode_decimal": 59394
|
||||
},
|
||||
{
|
||||
"icon_id": "40261712",
|
||||
"name": "monitor-add",
|
||||
"font_class": "monitor-add",
|
||||
"unicode": "e7ff",
|
||||
"unicode_decimal": 59391
|
||||
},
|
||||
{
|
||||
"icon_id": "40248186",
|
||||
"name": "monitor-down",
|
||||
"font_class": "monitor-down",
|
||||
"unicode": "e7fc",
|
||||
"unicode_decimal": 59388
|
||||
},
|
||||
{
|
||||
"icon_id": "40248184",
|
||||
"name": "monitor-up",
|
||||
"font_class": "monitor-up",
|
||||
"unicode": "e7fd",
|
||||
"unicode_decimal": 59389
|
||||
},
|
||||
{
|
||||
"icon_id": "40235502",
|
||||
"name": "itsm-unfold",
|
||||
"font_class": "itsm-unfold",
|
||||
"unicode": "e7f9",
|
||||
"unicode_decimal": 59385
|
||||
},
|
||||
{
|
||||
"icon_id": "40235453",
|
||||
"name": "itsm-shrink",
|
||||
"font_class": "itsm-stretch",
|
||||
"unicode": "e7f8",
|
||||
"unicode_decimal": 59384
|
||||
},
|
||||
{
|
||||
"icon_id": "40217123",
|
||||
"name": "monitor-data_comaparison2",
|
||||
"font_class": "monitor-data_comaparison2",
|
||||
"unicode": "e7a1",
|
||||
"unicode_decimal": 59297
|
||||
},
|
||||
{
|
||||
"icon_id": "40217122",
|
||||
"name": "monitor-data_comaparison1",
|
||||
"font_class": "monitor-data_comaparison1",
|
||||
"unicode": "e7f7",
|
||||
"unicode_decimal": 59383
|
||||
},
|
||||
{
|
||||
"icon_id": "40176936",
|
||||
"name": "monitor-online",
|
||||
"font_class": "a-monitor-online1",
|
||||
"unicode": "e7a0",
|
||||
"unicode_decimal": 59296
|
||||
},
|
||||
{
|
||||
"icon_id": "40043662",
|
||||
"name": "ops-setting-application-selected",
|
||||
"font_class": "ops-setting-application-selected",
|
||||
"unicode": "e919",
|
||||
"unicode_decimal": 59673
|
||||
},
|
||||
{
|
||||
"icon_id": "40043685",
|
||||
"name": "ops-setting-application",
|
||||
"font_class": "ops-setting-application",
|
||||
"unicode": "e918",
|
||||
"unicode_decimal": 59672
|
||||
},
|
||||
{
|
||||
"icon_id": "40043049",
|
||||
"name": "ops-setting-basic",
|
||||
"font_class": "ops-setting-basic",
|
||||
"unicode": "e889",
|
||||
"unicode_decimal": 59529
|
||||
},
|
||||
{
|
||||
"icon_id": "40043047",
|
||||
"name": "ops-setting-basic-selected",
|
||||
"font_class": "ops-setting-basic-selected",
|
||||
"unicode": "e917",
|
||||
"unicode_decimal": 59671
|
||||
},
|
||||
{
|
||||
"icon_id": "40038753",
|
||||
"name": "ops-setting-security",
|
||||
"font_class": "ops-setting-security",
|
||||
"unicode": "e915",
|
||||
"unicode_decimal": 59669
|
||||
},
|
||||
{
|
||||
"icon_id": "40038752",
|
||||
"name": "ops-setting-theme",
|
||||
"font_class": "ops-setting-theme",
|
||||
"unicode": "e916",
|
||||
"unicode_decimal": 59670
|
||||
},
|
||||
{
|
||||
"icon_id": "39948814",
|
||||
"name": "veops-show",
|
||||
@@ -22,7 +351,7 @@
|
||||
{
|
||||
"icon_id": "39926833",
|
||||
"name": "itsm-workload (1)",
|
||||
"font_class": "a-itsm-workload1",
|
||||
"font_class": "itsm-workload",
|
||||
"unicode": "e912",
|
||||
"unicode_decimal": 59666
|
||||
},
|
||||
@@ -453,13 +782,6 @@
|
||||
"unicode": "e8d5",
|
||||
"unicode_decimal": 59605
|
||||
},
|
||||
{
|
||||
"icon_id": "38547389",
|
||||
"name": "setting-authentication-selected",
|
||||
"font_class": "ops-setting-auth-selected",
|
||||
"unicode": "e8d4",
|
||||
"unicode_decimal": 59604
|
||||
},
|
||||
{
|
||||
"icon_id": "38533133",
|
||||
"name": "itsm-knowledge (2)",
|
||||
@@ -859,13 +1181,6 @@
|
||||
"unicode": "e89c",
|
||||
"unicode_decimal": 59548
|
||||
},
|
||||
{
|
||||
"icon_id": "37940033",
|
||||
"name": "ops-setting-duty-selected",
|
||||
"font_class": "ops-setting-duty-selected",
|
||||
"unicode": "e89b",
|
||||
"unicode_decimal": 59547
|
||||
},
|
||||
{
|
||||
"icon_id": "37841524",
|
||||
"name": "datainsight-sequential",
|
||||
@@ -1153,62 +1468,6 @@
|
||||
"unicode": "e870",
|
||||
"unicode_decimal": 59504
|
||||
},
|
||||
{
|
||||
"icon_id": "35984161",
|
||||
"name": "ops-itsm-ticketsetting-selected",
|
||||
"font_class": "ops-itsm-ticketsetting-selected",
|
||||
"unicode": "e860",
|
||||
"unicode_decimal": 59488
|
||||
},
|
||||
{
|
||||
"icon_id": "35984162",
|
||||
"name": "ops-itsm-reports-selected",
|
||||
"font_class": "ops-itsm-reports-selected",
|
||||
"unicode": "e861",
|
||||
"unicode_decimal": 59489
|
||||
},
|
||||
{
|
||||
"icon_id": "35984163",
|
||||
"name": "ops-itsm-servicecatalog-selected",
|
||||
"font_class": "ops-itsm-servicecatalog-selected",
|
||||
"unicode": "e862",
|
||||
"unicode_decimal": 59490
|
||||
},
|
||||
{
|
||||
"icon_id": "35984164",
|
||||
"name": "ops-itsm-ticketmanage-selected",
|
||||
"font_class": "ops-itsm-ticketmanage-selected",
|
||||
"unicode": "e863",
|
||||
"unicode_decimal": 59491
|
||||
},
|
||||
{
|
||||
"icon_id": "35984165",
|
||||
"name": "ops-itsm-knowledge-selected",
|
||||
"font_class": "ops-itsm-knowledge-selected",
|
||||
"unicode": "e864",
|
||||
"unicode_decimal": 59492
|
||||
},
|
||||
{
|
||||
"icon_id": "35984166",
|
||||
"name": "ops-itsm-workstation-selected",
|
||||
"font_class": "ops-itsm-workstation-selected",
|
||||
"unicode": "e865",
|
||||
"unicode_decimal": 59493
|
||||
},
|
||||
{
|
||||
"icon_id": "35984167",
|
||||
"name": "ops-itsm-servicedesk-selected",
|
||||
"font_class": "ops-itsm-servicedesk-selected",
|
||||
"unicode": "e866",
|
||||
"unicode_decimal": 59494
|
||||
},
|
||||
{
|
||||
"icon_id": "35984168",
|
||||
"name": "ops-itsm-planticket-selected",
|
||||
"font_class": "ops-itsm-planticket-selected",
|
||||
"unicode": "e867",
|
||||
"unicode_decimal": 59495
|
||||
},
|
||||
{
|
||||
"icon_id": "35984169",
|
||||
"name": "ops-itsm-servicecatalog",
|
||||
@@ -1860,13 +2119,6 @@
|
||||
"unicode": "e816",
|
||||
"unicode_decimal": 59414
|
||||
},
|
||||
{
|
||||
"icon_id": "35400645",
|
||||
"name": "ops-cmdb-batch-selected",
|
||||
"font_class": "ops-cmdb-batch-selected",
|
||||
"unicode": "e803",
|
||||
"unicode_decimal": 59395
|
||||
},
|
||||
{
|
||||
"icon_id": "35400646",
|
||||
"name": "ops-cmdb-batch",
|
||||
@@ -1874,27 +2126,6 @@
|
||||
"unicode": "e80a",
|
||||
"unicode_decimal": 59402
|
||||
},
|
||||
{
|
||||
"icon_id": "35395300",
|
||||
"name": "ops-cmdb-adc-selected",
|
||||
"font_class": "ops-cmdb-adc-selected",
|
||||
"unicode": "e7f7",
|
||||
"unicode_decimal": 59383
|
||||
},
|
||||
{
|
||||
"icon_id": "35395301",
|
||||
"name": "ops-cmdb-resource-selected",
|
||||
"font_class": "ops-cmdb-resource-selected",
|
||||
"unicode": "e7f8",
|
||||
"unicode_decimal": 59384
|
||||
},
|
||||
{
|
||||
"icon_id": "35395302",
|
||||
"name": "ops-cmdb-preference-selected",
|
||||
"font_class": "ops-cmdb-preference-selected",
|
||||
"unicode": "e7f9",
|
||||
"unicode_decimal": 59385
|
||||
},
|
||||
{
|
||||
"icon_id": "35395303",
|
||||
"name": "ops-cmdb-preference",
|
||||
@@ -1909,20 +2140,6 @@
|
||||
"unicode": "e7fb",
|
||||
"unicode_decimal": 59387
|
||||
},
|
||||
{
|
||||
"icon_id": "35395305",
|
||||
"name": "ops-cmdb-tree-selected",
|
||||
"font_class": "ops-cmdb-tree-selected",
|
||||
"unicode": "e7fc",
|
||||
"unicode_decimal": 59388
|
||||
},
|
||||
{
|
||||
"icon_id": "35395306",
|
||||
"name": "ops-cmdb-relation-selected",
|
||||
"font_class": "ops-cmdb-relation-selected",
|
||||
"unicode": "e7fd",
|
||||
"unicode_decimal": 59389
|
||||
},
|
||||
{
|
||||
"icon_id": "35395307",
|
||||
"name": "ops-cmdb-adc",
|
||||
@@ -1930,13 +2147,6 @@
|
||||
"unicode": "e7fe",
|
||||
"unicode_decimal": 59390
|
||||
},
|
||||
{
|
||||
"icon_id": "35395308",
|
||||
"name": "ops-cmdb-search-selected",
|
||||
"font_class": "ops-cmdb-search-selected",
|
||||
"unicode": "e7ff",
|
||||
"unicode_decimal": 59391
|
||||
},
|
||||
{
|
||||
"icon_id": "35395309",
|
||||
"name": "ops-cmdb-relation",
|
||||
@@ -1951,20 +2161,6 @@
|
||||
"unicode": "e801",
|
||||
"unicode_decimal": 59393
|
||||
},
|
||||
{
|
||||
"icon_id": "35395311",
|
||||
"name": "ops-cmdb-citype-selected",
|
||||
"font_class": "ops-cmdb-citype-selected",
|
||||
"unicode": "e802",
|
||||
"unicode_decimal": 59394
|
||||
},
|
||||
{
|
||||
"icon_id": "35395313",
|
||||
"name": "ops-cmdb-dashboard-selected",
|
||||
"font_class": "ops-cmdb-dashboard-selected",
|
||||
"unicode": "e804",
|
||||
"unicode_decimal": 59396
|
||||
},
|
||||
{
|
||||
"icon_id": "35395314",
|
||||
"name": "ops-cmdb-citype",
|
||||
@@ -1979,13 +2175,6 @@
|
||||
"unicode": "e806",
|
||||
"unicode_decimal": 59398
|
||||
},
|
||||
{
|
||||
"icon_id": "35395316",
|
||||
"name": "ops-cmdb-screen-selected",
|
||||
"font_class": "ops-cmdb-screen-selected",
|
||||
"unicode": "e807",
|
||||
"unicode_decimal": 59399
|
||||
},
|
||||
{
|
||||
"icon_id": "35395317",
|
||||
"name": "ops-cmdb-resource",
|
||||
@@ -2595,20 +2784,6 @@
|
||||
"unicode": "e7a6",
|
||||
"unicode_decimal": 59302
|
||||
},
|
||||
{
|
||||
"icon_id": "34792792",
|
||||
"name": "ops-setting-role-selected",
|
||||
"font_class": "ops-setting-role-selected",
|
||||
"unicode": "e7a0",
|
||||
"unicode_decimal": 59296
|
||||
},
|
||||
{
|
||||
"icon_id": "34792793",
|
||||
"name": "ops-setting-group-selected",
|
||||
"font_class": "ops-setting-group-selected",
|
||||
"unicode": "e7a1",
|
||||
"unicode_decimal": 59297
|
||||
},
|
||||
{
|
||||
"icon_id": "34792794",
|
||||
"name": "ops-setting-role",
|
||||
@@ -3379,13 +3554,6 @@
|
||||
"unicode": "e738",
|
||||
"unicode_decimal": 59192
|
||||
},
|
||||
{
|
||||
"icon_id": "37575490",
|
||||
"name": "ops-setting-notice-email-selected",
|
||||
"font_class": "ops-setting-notice-email-selected-copy",
|
||||
"unicode": "e889",
|
||||
"unicode_decimal": 59529
|
||||
},
|
||||
{
|
||||
"icon_id": "34108346",
|
||||
"name": "ops-setting-notice",
|
||||
@@ -3393,13 +3561,6 @@
|
||||
"unicode": "e72f",
|
||||
"unicode_decimal": 59183
|
||||
},
|
||||
{
|
||||
"icon_id": "34108348",
|
||||
"name": "ops-setting-notice-selected",
|
||||
"font_class": "ops-setting-notice-selected",
|
||||
"unicode": "e730",
|
||||
"unicode_decimal": 59184
|
||||
},
|
||||
{
|
||||
"icon_id": "34108504",
|
||||
"name": "ops-setting-notice-email-selected",
|
||||
@@ -3442,13 +3603,6 @@
|
||||
"unicode": "e736",
|
||||
"unicode_decimal": 59190
|
||||
},
|
||||
{
|
||||
"icon_id": "34108244",
|
||||
"name": "ops-setting-companyStructure-selected",
|
||||
"font_class": "ops-setting-companyStructure-selected",
|
||||
"unicode": "e72b",
|
||||
"unicode_decimal": 59179
|
||||
},
|
||||
{
|
||||
"icon_id": "34108296",
|
||||
"name": "ops-setting-companyStructure",
|
||||
@@ -3463,13 +3617,6 @@
|
||||
"unicode": "e72d",
|
||||
"unicode_decimal": 59181
|
||||
},
|
||||
{
|
||||
"icon_id": "34108330",
|
||||
"name": "ops-setting-companyInfo-selected",
|
||||
"font_class": "ops-setting-companyInfo-selected",
|
||||
"unicode": "e72e",
|
||||
"unicode_decimal": 59182
|
||||
},
|
||||
{
|
||||
"icon_id": "34099810",
|
||||
"name": "ops-email",
|
||||
@@ -5290,20 +5437,6 @@
|
||||
"unicode": "e600",
|
||||
"unicode_decimal": 58880
|
||||
},
|
||||
{
|
||||
"icon_id": "33053294",
|
||||
"name": "ops-dag-dashboard-selected",
|
||||
"font_class": "ops-dag-dashboard-selected",
|
||||
"unicode": "e601",
|
||||
"unicode_decimal": 58881
|
||||
},
|
||||
{
|
||||
"icon_id": "33053330",
|
||||
"name": "ops-dag-applet-selected",
|
||||
"font_class": "ops-dag-applet-selected",
|
||||
"unicode": "e602",
|
||||
"unicode_decimal": 58882
|
||||
},
|
||||
{
|
||||
"icon_id": "33053531",
|
||||
"name": "ops-dag-applet",
|
||||
@@ -5318,13 +5451,6 @@
|
||||
"unicode": "e604",
|
||||
"unicode_decimal": 58884
|
||||
},
|
||||
{
|
||||
"icon_id": "33053589",
|
||||
"name": "ops-dag-terminal-selected",
|
||||
"font_class": "ops-dag-terminal-selected",
|
||||
"unicode": "e605",
|
||||
"unicode_decimal": 58885
|
||||
},
|
||||
{
|
||||
"icon_id": "33053591",
|
||||
"name": "ops-dag-cron",
|
||||
@@ -5332,13 +5458,6 @@
|
||||
"unicode": "e606",
|
||||
"unicode_decimal": 58886
|
||||
},
|
||||
{
|
||||
"icon_id": "33053609",
|
||||
"name": "ops-dag-cron-selected",
|
||||
"font_class": "ops-dag-cron-selected",
|
||||
"unicode": "e608",
|
||||
"unicode_decimal": 58888
|
||||
},
|
||||
{
|
||||
"icon_id": "33053615",
|
||||
"name": "ops-dag-history",
|
||||
@@ -5346,20 +5465,6 @@
|
||||
"unicode": "e609",
|
||||
"unicode_decimal": 58889
|
||||
},
|
||||
{
|
||||
"icon_id": "33053617",
|
||||
"name": "ops-dag-history-selected",
|
||||
"font_class": "ops-dag-history-selected",
|
||||
"unicode": "e60a",
|
||||
"unicode_decimal": 58890
|
||||
},
|
||||
{
|
||||
"icon_id": "33053681",
|
||||
"name": "ops-dag-dags-selected",
|
||||
"font_class": "ops-dag-dags-selected",
|
||||
"unicode": "e60c",
|
||||
"unicode_decimal": 58892
|
||||
},
|
||||
{
|
||||
"icon_id": "33053682",
|
||||
"name": "ops-dag-dagreview",
|
||||
@@ -5367,13 +5472,6 @@
|
||||
"unicode": "e60d",
|
||||
"unicode_decimal": 58893
|
||||
},
|
||||
{
|
||||
"icon_id": "33053684",
|
||||
"name": "ops-dag-dagreview-selected",
|
||||
"font_class": "ops-dag-dagreview-selected",
|
||||
"unicode": "e60e",
|
||||
"unicode_decimal": 58894
|
||||
},
|
||||
{
|
||||
"icon_id": "33053691",
|
||||
"name": "ops-dag-panel",
|
||||
@@ -5381,13 +5479,6 @@
|
||||
"unicode": "e60f",
|
||||
"unicode_decimal": 58895
|
||||
},
|
||||
{
|
||||
"icon_id": "33053692",
|
||||
"name": "ops-dag-panel-selected",
|
||||
"font_class": "ops-dag-panel-selected",
|
||||
"unicode": "e615",
|
||||
"unicode_decimal": 58901
|
||||
},
|
||||
{
|
||||
"icon_id": "33053707",
|
||||
"name": "ops-dag-variables",
|
||||
@@ -5395,27 +5486,6 @@
|
||||
"unicode": "e616",
|
||||
"unicode_decimal": 58902
|
||||
},
|
||||
{
|
||||
"icon_id": "33053715",
|
||||
"name": "ops-dag-variables-selected",
|
||||
"font_class": "ops-dag-variables-selected",
|
||||
"unicode": "e618",
|
||||
"unicode_decimal": 58904
|
||||
},
|
||||
{
|
||||
"icon_id": "33053718",
|
||||
"name": "ops-dag-appletadmin",
|
||||
"font_class": "ops-dag-appletadmin",
|
||||
"unicode": "e65c",
|
||||
"unicode_decimal": 58972
|
||||
},
|
||||
{
|
||||
"icon_id": "33053720",
|
||||
"name": "ops-dag-appletadmin-selected",
|
||||
"font_class": "ops-dag-appletadmin-selected",
|
||||
"unicode": "e65d",
|
||||
"unicode_decimal": 58973
|
||||
},
|
||||
{
|
||||
"icon_id": "33055163",
|
||||
"name": "ops-dag-dags",
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -9,7 +9,7 @@
|
||||
:bodyStyle="{ padding: '24px 12px' }"
|
||||
:placement="placement"
|
||||
>
|
||||
<ResourceSearch :fromCronJob="true" @copySuccess="copySuccess" />
|
||||
<ResourceSearch ref="resourceSearch" :fromCronJob="true" :type="type" :typeId="typeId" @copySuccess="copySuccess" />
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
@@ -23,6 +23,14 @@ export default {
|
||||
type: String,
|
||||
default: 'right',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'resourceSearch'
|
||||
},
|
||||
typeId: {
|
||||
type: Number,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@@ -73,7 +73,7 @@ export default {
|
||||
.custom-drawer-close {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
background: #custom_colors[color_1];
|
||||
background: @primary-color;
|
||||
color: white;
|
||||
text-align: center;
|
||||
transition: all 0.3s;
|
||||
|
@@ -298,14 +298,14 @@ export default {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
@@ -182,8 +182,8 @@ export default {
|
||||
<tag {...{ props, attrs }}>
|
||||
{this.renderIcon({ icon: menu.meta.icon, customIcon: menu.meta.customIcon, name: menu.meta.name, typeId: menu.meta.typeId, routeName: menu.name, selectedIcon: menu.meta.selectedIcon, })}
|
||||
<span>
|
||||
<span class={this.renderI18n(menu.meta.title).length > 10 ? 'scroll' : ''}>{this.renderI18n(menu.meta.title)}</span>
|
||||
{isShowDot &&
|
||||
<span style={menu.meta.style} class={this.renderI18n(menu.meta.title).length > 10 ? 'scroll' : ''}>{this.renderI18n(menu.meta.title)}</span>
|
||||
{isShowDot && !menu.meta.disabled &&
|
||||
<a-popover
|
||||
overlayClassName="custom-menu-extra-submenu"
|
||||
placement="rightTop"
|
||||
|
@@ -161,7 +161,7 @@ export default {
|
||||
cursor: pointer;
|
||||
&-selected,
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +179,7 @@ export default {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #000000;
|
||||
border-left: 2px solid #custom_colors[color_1];
|
||||
border-left: 2px solid @primary-color;
|
||||
padding-left: 6px;
|
||||
margin-left: -6px;
|
||||
}
|
||||
|
@@ -185,14 +185,14 @@ export default {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
@@ -93,7 +93,7 @@ export default {
|
||||
background-color: transparent;
|
||||
}
|
||||
.sidebar-list-item.sidebar-list-item-selected::before {
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
}
|
||||
.sidebar-list-item-dotline {
|
||||
padding-left: 20px;
|
||||
|
@@ -81,7 +81,7 @@ export default {
|
||||
if (route.name === 'cmdb') {
|
||||
const preference = await getPreference()
|
||||
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
|
||||
if (lastTypeId && preference.some((item) => item.id === Number(lastTypeId))) {
|
||||
if (lastTypeId && preference.type_ids.some((item) => item === Number(lastTypeId))) {
|
||||
this.$router.push(`/cmdb/instances/types/${lastTypeId}`)
|
||||
} else {
|
||||
this.$router.push('/cmdb/dashboard')
|
||||
|
@@ -97,8 +97,8 @@ export default {
|
||||
|
||||
<style lang="less">
|
||||
.color {
|
||||
color: #custom_colors[color_1];
|
||||
background-color: #custom_colors[color_2];
|
||||
color: @primary-color;
|
||||
background-color: @primary-color_5;
|
||||
}
|
||||
.custom-user {
|
||||
.custom-user-item {
|
||||
@@ -117,7 +117,7 @@ export default {
|
||||
.locale {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -2,11 +2,12 @@ import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v0.1'
|
||||
|
||||
export function searchCI(params) {
|
||||
export function searchCI(params, isShowMessage = true) {
|
||||
return axios({
|
||||
url: urlPrefix + `/ci/s`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
params: params,
|
||||
isShowMessage
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -54,3 +54,36 @@ export function getCiTriggersByCiId(ci_id, params) {
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getCiRelatedTickets(params) {
|
||||
return axios({
|
||||
url: `/itsm/v1/process_ticket/get_tickets_by`,
|
||||
method: 'POST',
|
||||
data: params,
|
||||
isShowMessage: false
|
||||
})
|
||||
}
|
||||
|
||||
export function judgeItsmInstalled() {
|
||||
return axios({
|
||||
url: `/itsm/v1/process_ticket/itsm_existed`,
|
||||
method: 'GET',
|
||||
isShowMessage: false
|
||||
})
|
||||
}
|
||||
|
||||
export function getCIsBaseline(params) {
|
||||
return axios({
|
||||
url: `/v0.1/ci/baseline`,
|
||||
method: 'GET',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function CIBaselineRollback(ciId, params) {
|
||||
return axios({
|
||||
url: `/v0.1/ci/${ciId}/baseline/rollback`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
110
cmdb-ui/src/modules/cmdb/api/topology.js
Normal file
110
cmdb-ui/src/modules/cmdb/api/topology.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v0.1'
|
||||
|
||||
export function getTopoGroups() {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function getTopoView(_id) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/${_id}`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function postTopoGroup(data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/groups`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function putTopoGroupByGId(gid, data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/groups/${gid}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function putTopoGroupsOrder(data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/groups/order`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteTopoGroup(gid, data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/groups/${gid}`,
|
||||
method: 'DELETE',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function addTopoView(data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateTopoView(_id, data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/${_id}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteTopoView(_id) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/${_id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
export function getRelationsByTypeId(_id) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/relations/ci_types/${_id}`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function previewTopoView(params) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/preview`,
|
||||
method: 'POST',
|
||||
data: params,
|
||||
})
|
||||
}
|
||||
|
||||
export function showTopoView(_id) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/${_id}/view`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function grantTopologyView(viewId, rid, data) {
|
||||
return axios({
|
||||
url: `/v0.1/topology_views/${viewId}/roles/${rid}/grant`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function revokeTopologyView(viewId, rid, data) {
|
||||
return axios({
|
||||
url: `/v0.1/topology_views/${viewId}/roles/${rid}/revoke`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
BIN
cmdb-ui/src/modules/cmdb/assets/itsm_uninstalled.png
Normal file
BIN
cmdb-ui/src/modules/cmdb/assets/itsm_uninstalled.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
@@ -230,7 +230,7 @@ export default {
|
||||
background-color: #f9fbff;
|
||||
border-bottom: none;
|
||||
.ant-transfer-list-header-title {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -258,7 +258,7 @@ export default {
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
background-color: #fff;
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
border-radius: 4px;
|
||||
width: 12px;
|
||||
}
|
||||
@@ -271,7 +271,7 @@ export default {
|
||||
font-size: 12px;
|
||||
color: #cacdd9;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
.move-icon {
|
||||
@@ -291,8 +291,8 @@ export default {
|
||||
}
|
||||
}
|
||||
.ant-transfer-list-content-item-selected {
|
||||
background-color: #custom_colors[color_2];
|
||||
border-color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
border-color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 130}px` }">
|
||||
<div class="cmdb-grant" :style="{ }">
|
||||
<template v-if="cmdbGrantType.includes('ci_type')">
|
||||
<div class="cmdb-grant-title">{{ $t('cmdb.components.ciTypeGrant') }}</div>
|
||||
<CiTypeGrant
|
||||
@@ -57,6 +57,20 @@
|
||||
:addedRids="addedRids"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="cmdbGrantType.includes('TopologyView')">
|
||||
<div class="cmdb-grant-title">{{ resourceTypeName }}{{ $t('cmdb.components.perm') }}</div>
|
||||
<TopologyViewGrant
|
||||
:resourceTypeName="resourceTypeName"
|
||||
:tableData="tableData"
|
||||
:viewId="CITypeId"
|
||||
grantType="TopologyView"
|
||||
@grantDepart="grantDepart"
|
||||
@grantRole="grantRole"
|
||||
@getTableData="getTableData"
|
||||
ref="grantTopologyView"
|
||||
:addedRids="addedRids"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<GrantModal ref="grantModal" @handleOk="handleOk" />
|
||||
<ReadGrantModal ref="readGrantModal" :CITypeId="CITypeId" @updateTableDataRead="updateTableDataRead" />
|
||||
@@ -72,11 +86,12 @@ import { getResourcePerms } from '@/modules/acl/api/permission'
|
||||
import GrantModal from './grantModal.vue'
|
||||
import ReadGrantModal from './readGrantModal'
|
||||
import RelationViewGrant from './relationViewGrant.vue'
|
||||
import TopologyViewGrant from './topologyViewGrant.vue'
|
||||
import { getCITypeGroupById, ciTypeFilterPermissions } from '../../api/CIType'
|
||||
|
||||
export default {
|
||||
name: 'GrantComp',
|
||||
components: { CiTypeGrant, TypeRelationGrant, RelationViewGrant, GrantModal, ReadGrantModal },
|
||||
components: { CiTypeGrant, TypeRelationGrant, RelationViewGrant, TopologyViewGrant, GrantModal, ReadGrantModal },
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
@@ -291,6 +306,20 @@ export default {
|
||||
})
|
||||
)
|
||||
}
|
||||
if (grantType === 'TopologyView') {
|
||||
this.tableData.unshift(
|
||||
...rids.map(({ rid, name }) => {
|
||||
return {
|
||||
rid,
|
||||
name,
|
||||
read: false,
|
||||
update: false,
|
||||
delete: false,
|
||||
grant: false,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
this.addedRids = rids
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
|
@@ -75,10 +75,10 @@ export default {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
// background-color: #custom_colors[color_1];
|
||||
// background-color: @primary-color;
|
||||
border-radius: 2px;
|
||||
border: 14px solid transparent;
|
||||
border-left-color: #custom_colors[color_1];
|
||||
border-left-color: @primary-color;
|
||||
transform: rotate(225deg);
|
||||
top: -16px;
|
||||
left: -17px;
|
||||
|
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div class="topology-view-grant">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
size="mini"
|
||||
stripe
|
||||
class="ops-stripe-table"
|
||||
:data="tableData"
|
||||
:max-height="`${tableHeight}px`"
|
||||
:scroll-y="{enabled: true}"
|
||||
:row-style="(params) => getCurrentRowStyle(params, addedRids)"
|
||||
>
|
||||
<vxe-column field="name"></vxe-column>
|
||||
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
|
||||
<template #default="{row}">
|
||||
<a-checkbox @change="(e) => handleChange(e, col, row)" v-model="row[col]"></a-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<a-space>
|
||||
<span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
|
||||
<span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { permMap } from './constants.js'
|
||||
import { grantTopologyView, revokeTopologyView } from '@/modules/cmdb/api/topology.js'
|
||||
import { getCurrentRowStyle } from './utils'
|
||||
export default {
|
||||
name: 'TopologyViewGrant',
|
||||
inject: ['loading', 'isModal'],
|
||||
props: {
|
||||
viewId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
resourceTypeName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
grantType: {
|
||||
type: String,
|
||||
default: 'relation_view',
|
||||
},
|
||||
addedRids: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
columns: ['read', 'update', 'delete', 'grant'],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableHeight() {
|
||||
if (this.isModal) {
|
||||
return (this.windowHeight - 104) / 2
|
||||
}
|
||||
return (this.windowHeight - 104) / 2 - 116
|
||||
},
|
||||
permMap() {
|
||||
return permMap()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCurrentRowStyle,
|
||||
grantDepart() {
|
||||
this.$emit('grantDepart', this.grantType)
|
||||
},
|
||||
grantRole() {
|
||||
this.$emit('grantRole', this.grantType)
|
||||
},
|
||||
handleChange(e, col, row) {
|
||||
if (e.target.checked) {
|
||||
grantTopologyView(this.viewId, row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
} else {
|
||||
revokeTopologyView(this.viewId, row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.topology-view-grant {
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
@@ -49,6 +49,8 @@
|
||||
import _ from 'lodash'
|
||||
import '@wangeditor/editor/dist/css/style.css'
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||
import { i18nChangeLanguage } from '@wangeditor/editor'
|
||||
|
||||
export default {
|
||||
name: 'NoticeContent',
|
||||
components: { Editor, Toolbar },
|
||||
@@ -76,6 +78,10 @@ export default {
|
||||
if (editor == null) return
|
||||
editor.destroy() // When the component is destroyed, destroy the editor in time
|
||||
},
|
||||
beforeCreate() {
|
||||
const locale = this.$i18n.locale === 'zh' ? 'zh-CN' : 'en'
|
||||
i18nChangeLanguage(locale)
|
||||
},
|
||||
methods: {
|
||||
onCreated(editor) {
|
||||
this.editor = Object.seal(editor) // Be sure to use Object.seal(), otherwise an error will be reported
|
||||
@@ -175,8 +181,8 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
&:hover {
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,7 +194,7 @@ export default {
|
||||
|
||||
.notice-content {
|
||||
.w-e-bar {
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
}
|
||||
.w-e-text-placeholder {
|
||||
line-height: 1.5;
|
||||
|
@@ -198,6 +198,9 @@ export default {
|
||||
if (this.type === 'resourceSearch') {
|
||||
this.getCITypeGroups()
|
||||
}
|
||||
if (this.typeId) {
|
||||
this.currenCiType = [this.typeId]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// toggleAdvanced() {
|
||||
|
@@ -253,7 +253,7 @@ export default {
|
||||
}
|
||||
.cmdb-subscribe-drawer-tree-header {
|
||||
border-radius: 4px;
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 12px;
|
||||
@@ -264,7 +264,7 @@ export default {
|
||||
> span {
|
||||
display: inline-block;
|
||||
background-color: #fff;
|
||||
border-left: 2px solid #custom_colors[color_1];
|
||||
border-left: 2px solid @primary-color;
|
||||
padding: 3px 12px;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
@@ -272,7 +272,7 @@ export default {
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,7 +316,7 @@ export default {
|
||||
<style lang="less">
|
||||
.cmdb-subscribe-drawer {
|
||||
.ant-tabs-bar {
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@
|
||||
>
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<span slot="description"> {{ $t('cmdb.components.noParamRequest') }} </span>
|
||||
<a-button @click="add" type="primary" size="small" icon="plus" class="ops-button-primary">
|
||||
<a-button @click="add" type="primary" size="small" icon="plus">
|
||||
{{ $t('add') }}
|
||||
</a-button>
|
||||
</a-empty>
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -13,19 +13,25 @@ const genCmdbRoutes = async () => {
|
||||
{
|
||||
path: '/cmdb/dashboard',
|
||||
name: 'cmdb_dashboard',
|
||||
meta: { title: 'dashboard', icon: 'ops-cmdb-dashboard', selectedIcon: 'ops-cmdb-dashboard-selected', keepAlive: false },
|
||||
meta: { title: 'dashboard', icon: 'ops-cmdb-dashboard', selectedIcon: 'ops-cmdb-dashboard', keepAlive: false },
|
||||
component: () => import('../views/dashboard/index_v2.vue')
|
||||
},
|
||||
{
|
||||
path: '/cmdb/topoviews',
|
||||
name: 'cmdb_topology_views',
|
||||
meta: { title: 'cmdb.menu.topologyView', appName: 'cmdb', icon: 'ops-topology_view', selectedIcon: 'ops-topology_view', keepAlive: false },
|
||||
component: () => import('../views/topology_view/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/cmdb/disabled1',
|
||||
name: 'cmdb_disabled1',
|
||||
meta: { title: 'cmdb.menu.views', disabled: true },
|
||||
meta: { title: 'cmdb.menu.resources', disabled: true },
|
||||
},
|
||||
{
|
||||
path: '/cmdb/resourceviews',
|
||||
name: 'cmdb_resource_views',
|
||||
component: RouteView,
|
||||
meta: { title: 'cmdb.menu.ciTable', icon: 'ops-cmdb-resource', selectedIcon: 'ops-cmdb-resource-selected', keepAlive: true },
|
||||
meta: { title: 'cmdb.menu.ciTable', icon: 'ops-cmdb-resource', selectedIcon: 'ops-cmdb-resource', keepAlive: true },
|
||||
hideChildrenInMenu: false,
|
||||
children: []
|
||||
},
|
||||
@@ -33,7 +39,7 @@ const genCmdbRoutes = async () => {
|
||||
path: '/cmdb/tree_views',
|
||||
component: () => import('../views/tree_views'),
|
||||
name: 'cmdb_tree_views',
|
||||
meta: { title: 'cmdb.menu.ciTree', icon: 'ops-cmdb-tree', selectedIcon: 'ops-cmdb-tree-selected', keepAlive: false },
|
||||
meta: { title: 'cmdb.menu.ciTree', icon: 'ops-cmdb-tree', selectedIcon: 'ops-cmdb-tree', keepAlive: false },
|
||||
hideChildrenInMenu: true,
|
||||
children: [
|
||||
{
|
||||
@@ -47,13 +53,13 @@ const genCmdbRoutes = async () => {
|
||||
{
|
||||
path: '/cmdb/resourcesearch',
|
||||
name: 'cmdb_resource_search',
|
||||
meta: { title: 'cmdb.menu.ciSearch', icon: 'ops-cmdb-search', selectedIcon: 'ops-cmdb-search-selected', keepAlive: false },
|
||||
meta: { title: 'cmdb.menu.ciSearch', icon: 'ops-cmdb-search', selectedIcon: 'ops-cmdb-search', keepAlive: false },
|
||||
component: () => import('../views/resource_search/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/cmdb/adc',
|
||||
name: 'cmdb_auto_discovery_ci',
|
||||
meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc-selected', keepAlive: false },
|
||||
meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc', keepAlive: false },
|
||||
component: () => import('../views/discoveryCI/index.vue')
|
||||
},
|
||||
{
|
||||
@@ -72,19 +78,19 @@ const genCmdbRoutes = async () => {
|
||||
path: '/cmdb/preference',
|
||||
component: () => import('../views/preference/index'),
|
||||
name: 'cmdb_preference',
|
||||
meta: { title: 'cmdb.menu.preference', icon: 'ops-cmdb-preference', selectedIcon: 'ops-cmdb-preference-selected', keepAlive: false }
|
||||
meta: { title: 'cmdb.menu.preference', icon: 'ops-cmdb-preference', selectedIcon: 'ops-cmdb-preference', keepAlive: false }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/batch',
|
||||
component: () => import('../views/batch'),
|
||||
name: 'cmdb_batch',
|
||||
meta: { 'title': 'cmdb.menu.batchUpload', icon: 'ops-cmdb-batch', selectedIcon: 'ops-cmdb-batch-selected', keepAlive: false }
|
||||
meta: { 'title': 'cmdb.menu.batchUpload', icon: 'ops-cmdb-batch', selectedIcon: 'ops-cmdb-batch', keepAlive: false }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/ci_types',
|
||||
name: 'ci_type',
|
||||
component: () => import('../views/ci_types/index'),
|
||||
meta: { title: 'cmdb.menu.citypeManage', icon: 'ops-cmdb-citype', selectedIcon: 'ops-cmdb-citype-selected', keepAlive: false, permission: ['cmdb_admin', 'admin'] }
|
||||
meta: { title: 'cmdb.menu.citypeManage', icon: 'ops-cmdb-citype', selectedIcon: 'ops-cmdb-citype', keepAlive: false, permission: ['cmdb_admin', 'admin'] }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/disabled3',
|
||||
@@ -143,21 +149,30 @@ const genCmdbRoutes = async () => {
|
||||
}
|
||||
// Dynamically add subscription items and business relationships
|
||||
const [preference, relation] = await Promise.all([getPreference(), getRelationView()])
|
||||
|
||||
preference.forEach(item => {
|
||||
routes.children[2].children.push({
|
||||
path: `/cmdb/instances/types/${item.id}`,
|
||||
component: () => import(`../views/ci/index`),
|
||||
name: `cmdb_${item.id}`,
|
||||
meta: { title: item.alias, keepAlive: false, typeId: item.id, name: item.name, customIcon: item.icon },
|
||||
// hideChildrenInMenu: true // Force display of MenuItem instead of SubMenu
|
||||
const resourceViewsIndex = routes.children.findIndex(item => item.name === 'cmdb_resource_views')
|
||||
preference.group_types.forEach(group => {
|
||||
if (preference.group_types.length > 1) {
|
||||
routes.children[resourceViewsIndex].children.push({
|
||||
path: `/cmdb/instances/types/group${group.id}`,
|
||||
name: `cmdb_instances_group_${group.id}`,
|
||||
meta: { title: group.name || 'other', disabled: true, style: 'margin-left: 12px' },
|
||||
})
|
||||
}
|
||||
group.ci_types.forEach(item => {
|
||||
routes.children[resourceViewsIndex].children.push({
|
||||
path: `/cmdb/instances/types/${item.id}`,
|
||||
component: () => import(`../views/ci/index`),
|
||||
name: `cmdb_${item.id}`,
|
||||
meta: { title: item.alias, keepAlive: false, typeId: item.id, name: item.name, customIcon: item.icon },
|
||||
// hideChildrenInMenu: true // Force display of MenuItem instead of SubMenu
|
||||
})
|
||||
})
|
||||
})
|
||||
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
|
||||
if (lastTypeId && preference.some(item => item.id === Number(lastTypeId))) {
|
||||
if (lastTypeId && preference.type_ids.some(item => item === Number(lastTypeId))) {
|
||||
routes.redirect = `/cmdb/instances/types/${lastTypeId}`
|
||||
} else if (routes.children[2]?.children?.length > 0) {
|
||||
routes.redirect = routes.children[2].children.find(item => !item.hidden)?.path
|
||||
} else if (routes.children[resourceViewsIndex]?.children?.length > 0) {
|
||||
routes.redirect = routes.children[resourceViewsIndex].children.find(item => !item.hidden && !item.meta.disabled)?.path
|
||||
} else {
|
||||
routes.redirect = '/cmdb/dashboard'
|
||||
}
|
||||
@@ -166,10 +181,10 @@ const genCmdbRoutes = async () => {
|
||||
path: `/cmdb/relationviews/${item[1]}`,
|
||||
name: `cmdb_relation_views_${item[1]}`,
|
||||
component: () => import('../views/relation_views/index'),
|
||||
meta: { title: item[0], icon: 'ops-cmdb-relation', selectedIcon: 'ops-cmdb-relation-selected', keepAlive: false, name: item[0] },
|
||||
meta: { title: item[0], icon: 'ops-cmdb-relation', selectedIcon: 'ops-cmdb-relation', keepAlive: false, name: item[0] },
|
||||
}
|
||||
})
|
||||
routes.children.splice(2, 0, ...relationViews)
|
||||
routes.children.splice(resourceViewsIndex, 0, ...relationViews)
|
||||
return routes
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<div class="ci-detail-header">{{ this.type.alias }}</div>
|
||||
<div class="ci-detail-page">
|
||||
<CiDetailTab ref="ciDetailTab" :typeId="typeId" />
|
||||
<ci-detail-tab ref="ciDetailTab" :typeId="typeId" :attributeHistoryTableHeight="windowHeight - 250" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -23,6 +23,11 @@ export default {
|
||||
attributes: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
attrList: () => {
|
||||
@@ -55,7 +60,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.ci-detail-header {
|
||||
border-left: 3px solid @primary-color;
|
||||
padding-left: 10px;
|
||||
|
@@ -68,6 +68,8 @@
|
||||
<span @click="openBatchDownload">{{ $t('download') }}</span>
|
||||
<a-divider type="vertical" />
|
||||
<span @click="batchDelete">{{ $t('delete') }}</span>
|
||||
<a-divider type="vertical" />
|
||||
<span @click="batchRollback">{{ $t('cmdb.ci.rollback') }}</span>
|
||||
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span>
|
||||
</div>
|
||||
</SearchForm>
|
||||
@@ -293,6 +295,7 @@
|
||||
<create-instance-form ref="create" @reload="reloadData" @submit="batchUpdate" />
|
||||
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
||||
<BatchDownload ref="batchDownload" @batchDownload="batchDownload" />
|
||||
<ci-rollback-form ref="ciRollbackForm" @batchRollbackAsync="batchRollbackAsync($event)" :ciIds="selectedRowKeys" />
|
||||
<MetadataDrawer ref="metadataDrawer" />
|
||||
<CMDBGrant ref="cmdbGrant" resourceTypeName="CIType" app_id="cmdb" />
|
||||
</a-spin>
|
||||
@@ -325,6 +328,8 @@ import MetadataDrawer from './modules/MetadataDrawer.vue'
|
||||
import CMDBGrant from '../../components/cmdbGrant'
|
||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||
import { getAttrPassword } from '../../api/CITypeAttr'
|
||||
import CiRollbackForm from './modules/ciRollbackForm.vue'
|
||||
import { CIBaselineRollback } from '../../api/history'
|
||||
|
||||
export default {
|
||||
name: 'InstanceList',
|
||||
@@ -340,6 +345,7 @@ export default {
|
||||
MetadataDrawer,
|
||||
CMDBGrant,
|
||||
OpsMoveIcon,
|
||||
CiRollbackForm,
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
@@ -429,6 +435,12 @@ export default {
|
||||
// window.onkeypress = (e) => {
|
||||
// this.handleKeyPress(e)
|
||||
// }
|
||||
this.$nextTick(() => {
|
||||
const loadingNode = document.getElementsByClassName('ant-drawer-mask')
|
||||
if (loadingNode?.style) {
|
||||
loadingNode.style.zIndex = 8
|
||||
}
|
||||
})
|
||||
setTimeout(() => {
|
||||
this.columnDrop()
|
||||
}, 1000)
|
||||
@@ -661,7 +673,7 @@ export default {
|
||||
message: this.$t('warning'),
|
||||
description: errorMsg,
|
||||
duration: 0,
|
||||
style: { whiteSpace: 'break-spaces' },
|
||||
style: { whiteSpace: 'break-spaces', overflow: 'auto', maxHeight: this.windowHeight - 80 + 'px' },
|
||||
})
|
||||
errorNum += 1
|
||||
})
|
||||
@@ -744,6 +756,55 @@ export default {
|
||||
},
|
||||
})
|
||||
},
|
||||
batchRollback() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.ciRollbackForm.onOpen(true)
|
||||
})
|
||||
},
|
||||
async batchRollbackAsync(params) {
|
||||
const mask = document.querySelector('.ant-drawer-mask')
|
||||
const oldValue = mask.style.zIndex
|
||||
mask.style.zIndex = 2
|
||||
let successNum = 0
|
||||
let errorNum = 0
|
||||
this.loading = true
|
||||
this.loadTip = this.$t('cmdb.ci.rollbackingTips')
|
||||
const floor = Math.ceil(this.selectedRowKeys.length / 6)
|
||||
for (let i = 0; i < floor; i++) {
|
||||
const itemList = this.selectedRowKeys.slice(6 * i, 6 * i + 6)
|
||||
const promises = itemList.map((x) => CIBaselineRollback(x, params))
|
||||
await Promise.allSettled(promises)
|
||||
.then((res) => {
|
||||
res.forEach((r) => {
|
||||
if (r.status === 'fulfilled') {
|
||||
successNum += 1
|
||||
} else {
|
||||
errorNum += 1
|
||||
}
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.loadTip = this.$t('cmdb.ci.batchRollbacking', {
|
||||
total: this.selectedRowKeys.length,
|
||||
successNum: successNum,
|
||||
errorNum: errorNum,
|
||||
})
|
||||
})
|
||||
}
|
||||
this.loading = false
|
||||
this.loadTip = ''
|
||||
mask.style.zIndex = oldValue
|
||||
this.selectedRowKeys = []
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
this.$nextTick(() => {
|
||||
if (this.currentPage === 1) {
|
||||
this.loadTableData()
|
||||
} else {
|
||||
this.currentPage = 1
|
||||
}
|
||||
})
|
||||
},
|
||||
async refreshAfterEditAttrs() {
|
||||
await this.loadPreferenceAttrList()
|
||||
await this.loadTableData()
|
||||
|
@@ -107,6 +107,7 @@
|
||||
v-decorator="[list.name, { rules: [{ required: false }] }]"
|
||||
style="width: 100%"
|
||||
:format="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
|
||||
:valueFormat="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
|
||||
v-if="getFieldType(list.name) === '4' || getFieldType(list.name) === '3'"
|
||||
:showTime="getFieldType(list.name) === '4' ? false : { format: 'HH:mm:ss' }"
|
||||
/>
|
||||
@@ -370,7 +371,7 @@ export default {
|
||||
return 'select%%multiple'
|
||||
}
|
||||
return 'select'
|
||||
} else if (_find.value_type === '0' || _find.value_type === '1') {
|
||||
} else if ((_find.value_type === '0' || _find.value_type === '1') && !_find.is_list) {
|
||||
return 'input_number'
|
||||
} else if (_find.value_type === '4' || _find.value_type === '3') {
|
||||
return _find.value_type
|
||||
|
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
width="80%"
|
||||
width="90%"
|
||||
placement="left"
|
||||
@close="
|
||||
() => {
|
||||
visible = false
|
||||
}
|
||||
"
|
||||
style="transform: translateX(0px)!important"
|
||||
:visible="visible"
|
||||
:hasTitle="false"
|
||||
:hasFooter="false"
|
||||
|
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<div :style="{ height: '100%' }" v-if="itsmInstalled">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
show-overflow
|
||||
show-header-overflow
|
||||
resizable
|
||||
border
|
||||
size="small"
|
||||
class="ops-unstripe-table"
|
||||
:span-method="mergeRowMethod"
|
||||
:data="tableData"
|
||||
v-bind="ci_id ? { height: 'auto' } : { height: `${windowHeight - 225}px` }"
|
||||
>
|
||||
<template #empty>
|
||||
<a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }">
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<span slot="description"> {{ $t('noData') }} </span>
|
||||
</a-empty>
|
||||
</template>
|
||||
<vxe-column field="ticket.ticket_id" min-width="80" :title="$t('cmdb.history.ticketId')"> </vxe-column>
|
||||
<vxe-column field="ticket.created_at" width="160" :title="$t('cmdb.history.ticketStartTime')"> </vxe-column>
|
||||
<vxe-column field="ticket.creator_name" min-width="80" :title="$t('cmdb.history.ticketCreator')"> </vxe-column>
|
||||
<vxe-column field="ticket.title" min-width="150" :title="$t('cmdb.history.ticketTitle')">
|
||||
<template slot-scope="{ row }">
|
||||
<a target="_blank" :href="row.ticket.url">{{ row.ticket.title }}</a>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="ticket.node_finish_time" width="160" :title="$t('cmdb.history.ticketFinishTime')">
|
||||
</vxe-column>
|
||||
<vxe-column field="ticket.node_name" min-width="100" :title="$t('cmdb.history.ticketNodeName')"> </vxe-column>
|
||||
<vxe-table-column
|
||||
field="operate_type"
|
||||
min-width="100"
|
||||
:filters="[
|
||||
{ value: 0, label: $t('new') },
|
||||
{ value: 1, label: $t('delete') },
|
||||
{ value: 2, label: $t('update') },
|
||||
]"
|
||||
:filter-method="filterOperateMethod"
|
||||
:title="$t('operation')"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ operateTypeMap[row.operate_type] }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="attr_alias"
|
||||
min-width="100"
|
||||
:title="$t('cmdb.attribute')"
|
||||
:filters="[]"
|
||||
:filter-method="filterAttrMethod"
|
||||
>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column field="old" min-width="100" :title="$t('cmdb.history.old')"></vxe-table-column>
|
||||
<vxe-table-column field="new" min-width="100" :title="$t('cmdb.history.new')"></vxe-table-column>
|
||||
</vxe-table>
|
||||
<div :style="{ textAlign: 'right' }" v-if="!ci_id">
|
||||
<a-pagination
|
||||
size="small"
|
||||
show-size-changer
|
||||
show-quick-jumper
|
||||
:page-size-options="pageSizeOptions"
|
||||
:current="tablePage.currentPage"
|
||||
:total="tablePage.totalResult"
|
||||
:show-total="(total, range) => $t('cmdb.history.totalItems', { total: total })"
|
||||
:page-size="tablePage.pageSize"
|
||||
:default-current="1"
|
||||
@change="pageOrSizeChange"
|
||||
@showSizeChange="pageOrSizeChange"
|
||||
>
|
||||
</a-pagination>
|
||||
</div>
|
||||
</div>
|
||||
<a-empty
|
||||
v-else
|
||||
:image-style="{
|
||||
height: '200px',
|
||||
}"
|
||||
:style="{ paddingTop: '10%' }"
|
||||
>
|
||||
<img slot="image" :src="require('@/modules/cmdb/assets/itsm_uninstalled.png')" />
|
||||
<span slot="description"> {{ $t('cmdb.history.itsmUninstalled') }} </span>
|
||||
<a-button href="https://veops.cn/apply" target="_blank" type="primary">
|
||||
{{ $t('cmdb.history.applyItsm') }}
|
||||
</a-button>
|
||||
</a-empty>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCiRelatedTickets } from '../../../api/history'
|
||||
export default {
|
||||
name: 'RelatedItsmTable',
|
||||
props: {
|
||||
ci_id: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
ciHistory: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
itsmInstalled: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
attrList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
tablePage: {
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
totalResult: 0,
|
||||
},
|
||||
pageSizeOptions: ['50', '100', '200'],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
operateTypeMap() {
|
||||
return {
|
||||
0: this.$t('new'),
|
||||
1: this.$t('delete'),
|
||||
2: this.$t('update'),
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.updateTableData()
|
||||
},
|
||||
methods: {
|
||||
updateTableData(currentPage = 1, pageSize = this.tablePage.pageSize) {
|
||||
const params = { page: currentPage, page_size: pageSize, next_todo_ids: [] }
|
||||
if (this.ci_id) {
|
||||
const tableData = []
|
||||
this.ciHistory.forEach((item) => {
|
||||
if (item.ticket_id) {
|
||||
params.next_todo_ids.push(item.ticket_id)
|
||||
tableData.push(item)
|
||||
}
|
||||
})
|
||||
if (params.next_todo_ids.length) {
|
||||
getCiRelatedTickets(params)
|
||||
.then((res) => {
|
||||
const ticketId2Detail = {}
|
||||
res.forEach((item) => {
|
||||
ticketId2Detail[item.next_todo_id] = item
|
||||
})
|
||||
this.tableData = tableData.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
ticket: ticketId2Detail[item.ticket_id],
|
||||
}
|
||||
})
|
||||
this.updateAttrFilter()
|
||||
})
|
||||
.catch((e) => {})
|
||||
}
|
||||
} else {
|
||||
}
|
||||
},
|
||||
updateAttrFilter() {
|
||||
this.$nextTick(() => {
|
||||
const $table = this.$refs.xTable
|
||||
if ($table) {
|
||||
const attrColumn = $table.getColumnByField('attr_alias')
|
||||
if (attrColumn) {
|
||||
$table.setFilter(
|
||||
attrColumn,
|
||||
this.attrList().map((attr) => {
|
||||
return { value: attr.alias || attr.name, label: attr.alias || attr.name }
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
|
||||
const fields = [
|
||||
'ticket.ticket_id',
|
||||
'ticket.created_at',
|
||||
'ticket.creator_name',
|
||||
'ticket.title',
|
||||
'ticket.node_finish_time',
|
||||
'ticket.node_name',
|
||||
]
|
||||
const cellValue1 = row.ticket.ticket_id
|
||||
const cellValue2 = row.ticket.node_name
|
||||
if (cellValue1 && cellValue2 && fields.includes(column.property)) {
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
if (prevRow && prevRow.ticket.ticket_id === cellValue1 && prevRow.ticket.node_name === cellValue2) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow.ticket.ticket_id === cellValue1 && nextRow.ticket.node_name === cellValue2) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
pageOrSizeChange(currentPage, pageSize) {
|
||||
this.updateTableData(currentPage, pageSize)
|
||||
},
|
||||
filterOperateMethod({ value, row, column }) {
|
||||
return Number(row.operate_type) === Number(value)
|
||||
},
|
||||
filterAttrMethod({ value, row, column }) {
|
||||
return row.attr_alias === value
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@@ -182,7 +182,7 @@ export default {
|
||||
const edges = []
|
||||
this.parentCITypes.forEach((parent) => {
|
||||
const _findCiType = ci_types_list.find((item) => item.id === parent.id)
|
||||
if (this.firstCIs[parent.name]) {
|
||||
if (this.firstCIs[parent.name] && _findCiType) {
|
||||
const unique_id = _findCiType.show_id || _findCiType.unique_id
|
||||
const _findUnique = parent.attributes.find((attr) => attr.id === unique_id)
|
||||
const unique_name = _findUnique?.name
|
||||
@@ -225,7 +225,7 @@ export default {
|
||||
})
|
||||
this.childCITypes.forEach((child) => {
|
||||
const _findCiType = ci_types_list.find((item) => item.id === child.id)
|
||||
if (this.secondCIs[child.name]) {
|
||||
if (this.secondCIs[child.name] && _findCiType) {
|
||||
const unique_id = _findCiType.show_id || _findCiType.unique_id
|
||||
const _findUnique = child.attributes.find((attr) => attr.id === unique_id)
|
||||
const unique_name = _findUnique?.name
|
||||
|
@@ -20,7 +20,7 @@
|
||||
:key="attr.name"
|
||||
v-for="attr in group.attributes"
|
||||
>
|
||||
<CiDetailAttrContent :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" />
|
||||
<ci-detail-attr-content :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" />
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
@@ -28,22 +28,39 @@
|
||||
<a-tab-pane key="tab_2">
|
||||
<span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
|
||||
<div :style="{ height: '100%', padding: '24px', overflow: 'auto' }">
|
||||
<CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
|
||||
<ci-detail-relation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_3">
|
||||
<span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span>
|
||||
<div :style="{ padding: '24px', height: '100%' }">
|
||||
<a-space :style="{ 'margin-bottom': '10px', display: 'flex' }">
|
||||
<a-button type="primary" class="ops-button-ghost" ghost @click="handleRollbackCI()">
|
||||
<ops-icon type="shishizhuangtai" />{{ $t('cmdb.ci.rollback') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
<ci-rollback-form ref="ciRollbackForm" :ciIds="[ciId]" @getCIHistory="getCIHistory" />
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
show-overflow
|
||||
show-header-overflow
|
||||
:data="ciHistory"
|
||||
size="small"
|
||||
height="auto"
|
||||
:height="tableHeight"
|
||||
highlight-hover-row
|
||||
:span-method="mergeRowMethod"
|
||||
:scroll-y="{ enabled: false, gt: 20 }"
|
||||
:scroll-x="{ enabled: false, gt: 0 }"
|
||||
border
|
||||
:scroll-y="{ enabled: false }"
|
||||
class="ops-stripe-table"
|
||||
resizable
|
||||
class="ops-unstripe-table"
|
||||
>
|
||||
<template #empty>
|
||||
<a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }">
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<span slot="description"> {{ $t('noData') }} </span>
|
||||
</a-empty>
|
||||
</template>
|
||||
<vxe-table-column sortable field="created_at" :title="$t('created_at')"></vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="username"
|
||||
@@ -56,7 +73,7 @@
|
||||
:filters="[
|
||||
{ value: 0, label: $t('new') },
|
||||
{ value: 1, label: $t('delete') },
|
||||
{ value: 3, label: $t('update') },
|
||||
{ value: 2, label: $t('update') },
|
||||
]"
|
||||
:filter-method="filterOperateMethod"
|
||||
:title="$t('operation')"
|
||||
@@ -71,8 +88,18 @@
|
||||
:filters="[]"
|
||||
:filter-method="filterAttrMethod"
|
||||
></vxe-table-column>
|
||||
<vxe-table-column field="old" :title="$t('cmdb.history.old')"></vxe-table-column>
|
||||
<vxe-table-column field="new" :title="$t('cmdb.history.new')"></vxe-table-column>
|
||||
<vxe-table-column field="old" :title="$t('cmdb.history.old')">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.value_type === '6'">{{ JSON.parse(row.old) }}</span>
|
||||
<span v-else>{{ row.old }}</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column field="new" :title="$t('cmdb.history.new')">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.value_type === '6'">{{ JSON.parse(row.new) }}</span>
|
||||
<span v-else>{{ row.new }}</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</vxe-table>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
@@ -82,6 +109,12 @@
|
||||
<TriggerTable :ci_id="ci._id" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_5">
|
||||
<span slot="tab"><ops-icon type="itsm-association" />{{ $t('cmdb.ci.relITSM') }}</span>
|
||||
<div :style="{ padding: '24px', height: '100%' }">
|
||||
<RelatedItsmTable ref="relatedITSMTable" :ci_id="ci._id" :ciHistory="ciHistory" :itsmInstalled="itsmInstalled" :attrList="attrList" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<a-empty
|
||||
v-else
|
||||
@@ -100,11 +133,13 @@
|
||||
import _ from 'lodash'
|
||||
import { Descriptions, DescriptionsItem } from 'element-ui'
|
||||
import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getCIHistory } from '@/modules/cmdb/api/history'
|
||||
import { getCIHistory, judgeItsmInstalled } from '@/modules/cmdb/api/history'
|
||||
import { getCIById } from '@/modules/cmdb/api/ci'
|
||||
import CiDetailAttrContent from './ciDetailAttrContent.vue'
|
||||
import CiDetailRelation from './ciDetailRelation.vue'
|
||||
import TriggerTable from '../../operation_history/modules/triggerTable.vue'
|
||||
import RelatedItsmTable from './ciDetailRelatedItsmTable.vue'
|
||||
import CiRollbackForm from './ciRollbackForm.vue'
|
||||
export default {
|
||||
name: 'CiDetailTab',
|
||||
components: {
|
||||
@@ -113,6 +148,8 @@ export default {
|
||||
CiDetailAttrContent,
|
||||
CiDetailRelation,
|
||||
TriggerTable,
|
||||
RelatedItsmTable,
|
||||
CiRollbackForm,
|
||||
},
|
||||
props: {
|
||||
typeId: {
|
||||
@@ -123,10 +160,15 @@ export default {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
attributeHistoryTableHeight: {
|
||||
type: Number,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ci: {},
|
||||
item: [],
|
||||
attributeGroups: [],
|
||||
activeTabKey: 'tab_1',
|
||||
rowSpanMap: {},
|
||||
@@ -134,6 +176,8 @@ export default {
|
||||
ciId: null,
|
||||
ci_types: [],
|
||||
hasPermission: true,
|
||||
itsmInstalled: true,
|
||||
tableHeight: this.attributeHistoryTableHeight || (this.$store.state.windowHeight - 120),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -179,6 +223,7 @@ export default {
|
||||
}
|
||||
this.ciId = ciId
|
||||
await this.getCI()
|
||||
await this.judgeItsmInstalled()
|
||||
if (this.hasPermission) {
|
||||
this.getAttributes()
|
||||
this.getCIHistory()
|
||||
@@ -203,7 +248,16 @@ export default {
|
||||
this.hasPermission = false
|
||||
}
|
||||
})
|
||||
.catch((e) => {})
|
||||
.catch((e) => {
|
||||
if (e.response.status === 404) {
|
||||
this.itsmInstalled = false
|
||||
}
|
||||
})
|
||||
},
|
||||
async judgeItsmInstalled() {
|
||||
await judgeItsmInstalled().catch((e) => {
|
||||
this.itsmInstalled = false
|
||||
})
|
||||
},
|
||||
|
||||
getCIHistory() {
|
||||
@@ -343,6 +397,11 @@ export default {
|
||||
this.$message.error(this.$t('cmdb.ci.copyFailed'))
|
||||
})
|
||||
},
|
||||
handleRollbackCI() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.ciRollbackForm.onOpen()
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
165
cmdb-ui/src/modules/cmdb/views/ci/modules/ciRollbackForm.vue
Normal file
165
cmdb-ui/src/modules/cmdb/views/ci/modules/ciRollbackForm.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:closable="true"
|
||||
:title="drawerTitle"
|
||||
:visible="drawerVisible"
|
||||
@close="onClose"
|
||||
placement="right"
|
||||
width="800"
|
||||
:bodyStyle="{ paddingTop: 0 }"
|
||||
>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="onClose">{{ $t('cancel') }}</a-button>
|
||||
<a-button type="primary" @click="handleSubmit" :loading="loading" :disabled="!hasDiff">{{
|
||||
$t('submit')
|
||||
}}</a-button>
|
||||
</div>
|
||||
<a-form :form="form" :style="{ paddingTop: '20px' }">
|
||||
<a-form-item :label="$t('cmdb.ci.rollbackTo')" required :help="$t('cmdb.ci.baselineTips')">
|
||||
<a-date-picker
|
||||
:style="{ width: '278px' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
valueFormat="YYYY-MM-DD HH:mm:ss"
|
||||
@ok="getBaselineDiff"
|
||||
:show-time="{ format: 'HH:mm:ss' }"
|
||||
:placeholder="$t('cmdb.ci.rollbackToTips')"
|
||||
v-decorator="['before_date', { rules: [{ required: true, message: $t('cmdb.ci.rollbackToTips') }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
<span :style="{ fontWeight: 'bold' }">{{ $t('cmdb.ci.baselineDiff') }}</span>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
show-overflow
|
||||
show-header-overflow
|
||||
resizable
|
||||
border
|
||||
size="small"
|
||||
:span-method="mergeRowMethod"
|
||||
:data="tableData"
|
||||
:scroll-y="{ enabled: false, gt: 20 }"
|
||||
:scroll-x="{ enabled: false, gt: 0 }"
|
||||
class="ops-unstripe-table"
|
||||
>
|
||||
<template #empty>
|
||||
<a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }">
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<span slot="description"> {{ dataLoad }} </span>
|
||||
</a-empty>
|
||||
</template>
|
||||
<vxe-column field="instance" min-width="80" :title="$t('cmdb.ci.instance')"> </vxe-column>
|
||||
<vxe-column field="attr_name" min-width="80" :title="$t('cmdb.attribute')"> </vxe-column>
|
||||
<vxe-column field="cur" min-width="80" :title="$t('cmdb.ci.rollbackBefore')">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.value_type === '6'">{{ JSON.stringify(row.cur) }}</span>
|
||||
<span v-else>{{ row.cur }}</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="to" min-width="80" :title="$t('cmdb.ci.rollbackAfter')">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.value_type === '6'">{{ JSON.stringify(row.to) }}</span>
|
||||
<span v-else>{{ row.to }}</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
</a-form>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCIsBaseline, CIBaselineRollback } from '../../../api/history'
|
||||
export default {
|
||||
name: 'CiRollbackForm',
|
||||
props: {
|
||||
ciIds: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: this.$form.createForm(this),
|
||||
drawerTitle: this.$t('cmdb.ci.rollbackHeader'),
|
||||
drawerVisible: false,
|
||||
|
||||
formLayout: 'horizontal',
|
||||
|
||||
tableData: [],
|
||||
dataLoad: this.$t('noData'),
|
||||
loading: false,
|
||||
hasDiff: false,
|
||||
batched: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClose() {
|
||||
this.drawerVisible = false
|
||||
this.form.resetFields()
|
||||
this.tableData = []
|
||||
this.dataLoad = this.$t('noData')
|
||||
},
|
||||
onOpen(batched = false) {
|
||||
this.drawerTitle = this.$t('cmdb.ci.rollbackHeader')
|
||||
this.drawerVisible = true
|
||||
this.batched = batched
|
||||
},
|
||||
handleSubmit() {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: that.$t('warning'),
|
||||
content: that.$t('cmdb.ci.rollbackConfirm'),
|
||||
onOk() {
|
||||
if (that.batched) {
|
||||
that.$emit('batchRollbackAsync', values)
|
||||
} else {
|
||||
that.rollbackCI(values)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
rollbackCI(params) {
|
||||
CIBaselineRollback(this.ciIds[0], params).then((res) => {
|
||||
this.$message.success(this.$t('cmdb.ci.rollbackSuccess'))
|
||||
this.form.resetFields()
|
||||
this.$emit('getCIHistory')
|
||||
})
|
||||
},
|
||||
getBaselineDiff(value) {
|
||||
this.dataLoad = 'loading...'
|
||||
this.loading = true
|
||||
this.hasDiff = false
|
||||
getCIsBaseline({ ci_ids: this.ciIds.join(','), before_date: value }).then((res) => {
|
||||
this.tableData = res
|
||||
this.loading = false
|
||||
if (!res.length) {
|
||||
this.dataLoad = this.$t('cmdb.ci.noDiff', { baseline: value })
|
||||
} else {
|
||||
this.hasDiff = true
|
||||
}
|
||||
})
|
||||
},
|
||||
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
|
||||
const fields = ['instance']
|
||||
const cellValue1 = row.instance
|
||||
if (cellValue1 && fields.includes(column.property)) {
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
if (prevRow && prevRow.instance === cellValue1) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow.instance === cellValue1) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
@@ -50,7 +50,6 @@
|
||||
type="primary"
|
||||
size="small"
|
||||
icon="plus"
|
||||
class="ops-button-primary"
|
||||
>
|
||||
{{ $t('add') }}
|
||||
</a-button>
|
||||
@@ -214,7 +213,7 @@ export default {
|
||||
line-height: 32px;
|
||||
padding-left: 10px;
|
||||
margin-bottom: 20px;
|
||||
border-left: 4px solid #custom_colors[color_1];
|
||||
border-left: 4px solid @primary-color;
|
||||
font-size: 16px;
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
|
@@ -171,7 +171,7 @@ export default {
|
||||
margin-right: 60px;
|
||||
.ant-input-group.ant-input-group-compact > *:first-child,
|
||||
.ant-input-group.ant-input-group-compact > .ant-select:first-child > .ant-select-selection {
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
|
@@ -63,7 +63,7 @@
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip v-if="index !== CITypeGroups.length - 1">
|
||||
<template slot="title">{{ $t('cmdb.ciType.up') }}</template>
|
||||
<template slot="title">{{ $t('cmdb.ciType.down') }}</template>
|
||||
<a
|
||||
><a-icon
|
||||
type="arrow-down"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="ci-types-wrap" :style="{ height: `${windowHeight - 66}px` }">
|
||||
<div class="ci-types-wrap" :style="{ height: `${windowHeight - 96}px` }">
|
||||
<div v-if="!CITypeGroups.length" class="ci-types-empty">
|
||||
<a-empty :image="emptyImage" description=""></a-empty>
|
||||
<a-button icon="plus" size="small" type="primary" @click="handleClickAddGroup">{{
|
||||
@@ -15,6 +15,13 @@
|
||||
:triggerLength="18"
|
||||
>
|
||||
<template #one>
|
||||
<a-input
|
||||
:placeholder="$t('cmdb.preference.searchPlaceholder')"
|
||||
class="cmdb-ci-types-left-input"
|
||||
@pressEnter="handleSearch"
|
||||
>
|
||||
<a-icon slot="prefix" type="search" />
|
||||
</a-input>
|
||||
<div class="ci-types-left">
|
||||
<div class="ci-types-left-title">
|
||||
<a-button
|
||||
@@ -62,8 +69,8 @@
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</div>
|
||||
<draggable class="ci-types-left-content" :list="CITypeGroups" @end="handleChangeGroups" filter=".undraggable">
|
||||
<div v-for="g in CITypeGroups" :key="g.id || g.name">
|
||||
<draggable class="ci-types-left-content" :list="computedCITypeGroups" @end="handleChangeGroups" filter=".undraggable">
|
||||
<div v-for="g in computedCITypeGroups" :key="g.id || g.name">
|
||||
<div
|
||||
:class="
|
||||
`${currentGId === g.id && !currentCId ? 'selected' : ''} ci-types-left-group ${
|
||||
@@ -74,6 +81,7 @@
|
||||
>
|
||||
<div>
|
||||
<OpsMoveIcon
|
||||
v-if="g.id !== -1"
|
||||
style="width: 17px; height: 17px; display: none; position: absolute; left: -3px; top: 10px"
|
||||
/>
|
||||
<span style="font-weight: 700">{{ g.name || $t('other') }}</span>
|
||||
@@ -103,6 +111,7 @@
|
||||
@start="start(g)"
|
||||
@end="end(g)"
|
||||
@add="add(g)"
|
||||
filter=".undraggable"
|
||||
>
|
||||
<div
|
||||
v-for="ci in g.ci_types"
|
||||
@@ -110,8 +119,9 @@
|
||||
:class="`${currentCId === ci.id ? 'selected' : ''} ci-types-left-detail`"
|
||||
@click="handleClickCIType(g.id, ci.id, ci.name)"
|
||||
>
|
||||
<div>
|
||||
<div :class="`${ g.id === -1 ? 'undraggable' : '' }`">
|
||||
<OpsMoveIcon
|
||||
v-if="g.id !== -1"
|
||||
style="width: 17px; height: 17px; display: none; position: absolute; left: -1px; top: 8px"
|
||||
/>
|
||||
<span class="ci-types-left-detail-icon">
|
||||
@@ -223,10 +233,10 @@
|
||||
<a-form-item :label="$t('cmdb.ciType.isInherit')">
|
||||
<a-radio-group v-model="isInherit">
|
||||
<a-radio :value="true">
|
||||
是
|
||||
{{ $t('yes') }}
|
||||
</a-radio>
|
||||
<a-radio :value="false">
|
||||
否
|
||||
{{ $t('no') }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
@@ -464,6 +474,8 @@ export default {
|
||||
isInherit: false,
|
||||
|
||||
unique_id: null,
|
||||
|
||||
searchValue: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -536,6 +548,16 @@ export default {
|
||||
}
|
||||
return _showIdSelectOptions
|
||||
},
|
||||
computedCITypeGroups() {
|
||||
if (this.searchValue) {
|
||||
const ciTypes = _.cloneDeep(this.CITypeGroups)
|
||||
ciTypes.forEach((item) => {
|
||||
item.ci_types = item.ci_types.filter((_item) => _item.alias.toLowerCase().includes(this.searchValue.toLowerCase()))
|
||||
})
|
||||
return ciTypes
|
||||
}
|
||||
return this.CITypeGroups
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
@@ -565,6 +587,9 @@ export default {
|
||||
this.allTreeDepAndEmp = res
|
||||
})
|
||||
},
|
||||
handleSearch(e) {
|
||||
this.searchValue = e.target.value
|
||||
},
|
||||
async loadCITypes(isResetCurrentId = false) {
|
||||
const groups = await getCITypeGroupsConfig({ need_other: true })
|
||||
let alreadyReset = false
|
||||
@@ -664,7 +689,7 @@ export default {
|
||||
content: that.$t('cmdb.ciType.confirmDeleteGroup', { groupName: `${g.name}` }),
|
||||
onOk() {
|
||||
deleteCITypeGroup(g.id).then((res) => {
|
||||
that.$message.info(that.$t('deleteSuccess'))
|
||||
that.$message.success(that.$t('deleteSuccess'))
|
||||
that.loadCITypes(true)
|
||||
})
|
||||
},
|
||||
@@ -1021,6 +1046,14 @@ export default {
|
||||
top: 40%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
/deep/.cmdb-ci-types-left-input {
|
||||
input {
|
||||
background-color: transparent;
|
||||
}
|
||||
.ant-input:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
.ci-types-left {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
|
@@ -1,12 +1,11 @@
|
||||
<template>
|
||||
<div :style="{ padding: '0 20px 20px' }">
|
||||
<div class="relation-table" :style="{ padding: '0 20px 20px' }">
|
||||
<a-button
|
||||
v-if="!isInGrantComp"
|
||||
style="margin-bottom: 10px"
|
||||
@click="handleCreate"
|
||||
type="primary"
|
||||
size="small"
|
||||
class="ops-button-primary"
|
||||
icon="plus"
|
||||
>{{ $t('cmdb.ciType.addRelation') }}</a-button
|
||||
>
|
||||
@@ -18,8 +17,8 @@
|
||||
show-header-overflow
|
||||
highlight-hover-row
|
||||
keep-source
|
||||
:height="windowHeight - 190"
|
||||
class="ops-stripe-table"
|
||||
min-height="500"
|
||||
:row-class-name="rowClass"
|
||||
:edit-config="{ trigger: 'dblclick', mode: 'cell', showIcon: false }"
|
||||
resizable
|
||||
@@ -38,14 +37,14 @@
|
||||
<template #default="{row}">
|
||||
<span v-if="row.isParent && constraintMap[row.constraint]">{{
|
||||
constraintMap[row.constraint]
|
||||
.split('')
|
||||
.split(' ')
|
||||
.reverse()
|
||||
.join('')
|
||||
.join(' ')
|
||||
}}</span>
|
||||
<span v-else>{{ constraintMap[row.constraint] }}</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column :width="250" field="attributeAssociation" :edit-render="{}">
|
||||
<vxe-column :width="300" field="attributeAssociation" :edit-render="{}">
|
||||
<template #header>
|
||||
<span>
|
||||
<a-tooltip :title="$t('cmdb.ciType.attributeAssociationTip1')">
|
||||
@@ -58,43 +57,73 @@
|
||||
</span>
|
||||
</template>
|
||||
<template #default="{row}">
|
||||
<span
|
||||
v-if="row.parent_attr_id && row.child_attr_id"
|
||||
>{{ getAttrNameById(row.isParent ? row.attributes : attributes, row.parent_attr_id) }}=>
|
||||
{{ getAttrNameById(row.isParent ? attributes : row.attributes, row.child_attr_id) }}</span
|
||||
<template
|
||||
v-for="item in row.parentAndChildAttrList"
|
||||
>
|
||||
<div
|
||||
:key="item.id"
|
||||
v-if="item.parentAttrId && item.childAttrId"
|
||||
>
|
||||
{{ getAttrNameById(row.isParent ? row.attributes : attributes, item.parentAttrId) }}=>
|
||||
{{ getAttrNameById(row.isParent ? attributes : row.attributes, item.childAttrId) }}
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template #edit="{ row }">
|
||||
<div style="display:inline-flex;align-items:center;">
|
||||
<div
|
||||
v-for="item in tableAttrList"
|
||||
:key="item.id"
|
||||
class="table-attribute-row"
|
||||
>
|
||||
<a-select
|
||||
allowClear
|
||||
size="small"
|
||||
v-model="parent_attr_id"
|
||||
v-model="item.parentAttrId"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
:style="{ width: '100px' }"
|
||||
show-search
|
||||
optionFilterProp="title"
|
||||
>
|
||||
<a-select-option
|
||||
<a-select-option
|
||||
v-for="attr in filterAttributes(row.isParent ? row.attributes : attributes)"
|
||||
:key="attr.id"
|
||||
:value="attr.id"
|
||||
:title="attr.alias || attr.name"
|
||||
>
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
=>
|
||||
<span class="table-attribute-row-link">=></span>
|
||||
<a-select
|
||||
allowClear
|
||||
size="small"
|
||||
v-model="child_attr_id"
|
||||
v-model="item.childAttrId"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
:style="{ width: '100px' }"
|
||||
show-search
|
||||
optionFilterProp="title"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="attr in filterAttributes(row.isParent ? attributes : row.attributes)"
|
||||
:key="attr.id"
|
||||
:value="attr.id"
|
||||
:title="attr.alias || attr.name"
|
||||
>
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a
|
||||
class="table-attribute-row-action"
|
||||
@click="removeTableAttr(item.id)"
|
||||
>
|
||||
<a-icon type="minus-circle" />
|
||||
</a>
|
||||
<a
|
||||
class="table-attribute-row-action"
|
||||
@click="addTableAttr"
|
||||
>
|
||||
<a-icon type="plus-circle" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</vxe-column>
|
||||
@@ -181,13 +210,16 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('cmdb.ciType.attributeAssociation')">
|
||||
<a-row>
|
||||
<a-col :span="11">
|
||||
<a-row
|
||||
v-for="item in modalAttrList"
|
||||
:key="item.id"
|
||||
>
|
||||
<a-col :span="10">
|
||||
<a-form-item>
|
||||
<a-select
|
||||
:placeholder="$t('cmdb.ciType.attributeAssociationTip4')"
|
||||
allowClear
|
||||
v-decorator="['parent_attr_id', { rules: [{ required: false }] }]"
|
||||
v-model="item.parentAttrId"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(attributes)" :key="attr.id">
|
||||
{{ attr.alias || attr.name }}
|
||||
@@ -198,12 +230,12 @@
|
||||
<a-col :span="2" :style="{ textAlign: 'center' }">
|
||||
=>
|
||||
</a-col>
|
||||
<a-col :span="11">
|
||||
<a-col :span="9">
|
||||
<a-form-item>
|
||||
<a-select
|
||||
:placeholder="$t('cmdb.ciType.attributeAssociationTip5')"
|
||||
allowClear
|
||||
v-decorator="['child_attr_id', { rules: [{ required: false }] }]"
|
||||
v-model="item.childAttrId"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(modalChildAttributes)" :key="attr.id">
|
||||
{{ attr.alias || attr.name }}
|
||||
@@ -211,6 +243,20 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="3">
|
||||
<a
|
||||
class="modal-attribute-action"
|
||||
@click="removeModalAttr(item.id)"
|
||||
>
|
||||
<a-icon type="minus-circle" />
|
||||
</a>
|
||||
<a
|
||||
class="modal-attribute-action"
|
||||
@click="addModalAttr"
|
||||
>
|
||||
<a-icon type="plus-circle" />
|
||||
</a>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
@@ -229,6 +275,7 @@ import {
|
||||
} from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import CMDBGrant from '../../components/cmdbGrant'
|
||||
|
||||
@@ -261,8 +308,8 @@ export default {
|
||||
tableData: [],
|
||||
parentTableData: [],
|
||||
attributes: [],
|
||||
parent_attr_id: undefined,
|
||||
child_attr_id: undefined,
|
||||
tableAttrList: [],
|
||||
modalAttrList: [],
|
||||
modalChildAttributes: [],
|
||||
}
|
||||
},
|
||||
@@ -299,8 +346,11 @@ export default {
|
||||
async getCITypeParent() {
|
||||
await getCITypeParent(this.CITypeId).then((res) => {
|
||||
this.parentTableData = res.parents.map((item) => {
|
||||
const parentAndChildAttrList = this.handleAttrList(item)
|
||||
|
||||
return {
|
||||
...item,
|
||||
parentAndChildAttrList,
|
||||
source_ci_type_name: this.CITypeName,
|
||||
source_ci_type_id: this.CITypeId,
|
||||
isParent: true,
|
||||
@@ -311,8 +361,11 @@ export default {
|
||||
getCITypeChildren() {
|
||||
getCITypeChildren(this.CITypeId).then((res) => {
|
||||
const data = res.children.map((obj) => {
|
||||
const parentAndChildAttrList = this.handleAttrList(obj)
|
||||
|
||||
return {
|
||||
...obj,
|
||||
parentAndChildAttrList,
|
||||
source_ci_type_name: this.CITypeName,
|
||||
source_ci_type_id: this.CITypeId,
|
||||
}
|
||||
@@ -324,6 +377,20 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
handleAttrList(data) {
|
||||
const length = Math.min(data?.parent_attr_ids?.length || 0, data.child_attr_ids?.length || 0)
|
||||
const parentAndChildAttrList = []
|
||||
for (let i = 0; i < length; i++) {
|
||||
parentAndChildAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: data?.parent_attr_ids?.[i] ?? '',
|
||||
childAttrId: data?.child_attr_ids?.[i] ?? ''
|
||||
})
|
||||
}
|
||||
return parentAndChildAttrList
|
||||
},
|
||||
|
||||
getCITypes() {
|
||||
getCITypes().then((res) => {
|
||||
this.CITypes = res.ci_types
|
||||
@@ -344,6 +411,13 @@ export default {
|
||||
handleCreate() {
|
||||
this.drawerTitle = this.$t('cmdb.ciType.addRelation')
|
||||
this.visible = true
|
||||
this.$set(this, 'modalAttrList', [
|
||||
{
|
||||
id: uuidv4(),
|
||||
parentAttrId: undefined,
|
||||
childAttrId: undefined
|
||||
}
|
||||
])
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
source_ci_type_id: this.CITypeId,
|
||||
@@ -367,19 +441,22 @@ export default {
|
||||
ci_type_id,
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id = undefined,
|
||||
child_attr_id = undefined,
|
||||
} = values
|
||||
|
||||
if ((!parent_attr_id && child_attr_id) || (parent_attr_id && !child_attr_id)) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
const {
|
||||
parent_attr_ids,
|
||||
child_attr_ids,
|
||||
validate
|
||||
} = this.handleValidateAttrList(this.modalAttrList)
|
||||
if (!validate) {
|
||||
return
|
||||
}
|
||||
|
||||
createRelation(source_ci_type_id, ci_type_id, {
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id,
|
||||
child_attr_id,
|
||||
parent_attr_ids,
|
||||
child_attr_ids,
|
||||
}).then((res) => {
|
||||
this.$message.success(this.$t('addSuccess'))
|
||||
this.onClose()
|
||||
@@ -388,6 +465,37 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 校验属性列表
|
||||
* @param {*} attrList
|
||||
*/
|
||||
handleValidateAttrList(attrList) {
|
||||
const parent_attr_ids = []
|
||||
const child_attr_ids = []
|
||||
attrList.map((attr) => {
|
||||
if (attr.parentAttrId) {
|
||||
parent_attr_ids.push(attr.parentAttrId)
|
||||
}
|
||||
if (attr.childAttrId) {
|
||||
child_attr_ids.push(attr.childAttrId)
|
||||
}
|
||||
})
|
||||
|
||||
if (parent_attr_ids.length !== child_attr_ids.length) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
return {
|
||||
validate: false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
validate: true,
|
||||
parent_attr_ids,
|
||||
child_attr_ids
|
||||
}
|
||||
},
|
||||
|
||||
handleOpenGrant(record) {
|
||||
this.$refs.cmdbGrant.open({
|
||||
name: `${record.source_ci_type_name} -> ${record.name}`,
|
||||
@@ -403,23 +511,45 @@ export default {
|
||||
if (row.isParent) return 'relation-table-parent'
|
||||
},
|
||||
handleEditActived({ row }) {
|
||||
this.parent_attr_id = row?.parent_attr_id ?? undefined
|
||||
this.child_attr_id = row?.child_attr_id ?? undefined
|
||||
const tableAttrList = []
|
||||
|
||||
const length = Math.min(row?.parent_attr_ids?.length || 0, row.child_attr_ids?.length || 0)
|
||||
if (length) {
|
||||
for (let i = 0; i < length; i++) {
|
||||
tableAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: row?.parent_attr_ids?.[i] ?? undefined,
|
||||
childAttrId: row?.child_attr_ids?.[i] ?? undefined
|
||||
})
|
||||
}
|
||||
} else {
|
||||
tableAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: undefined,
|
||||
childAttrId: undefined
|
||||
})
|
||||
}
|
||||
this.$set(this, 'tableAttrList', tableAttrList)
|
||||
},
|
||||
async handleEditClose({ row }) {
|
||||
const { source_ci_type_id: parentId, id: childrenId, constraint, relation_type } = row
|
||||
const { parent_attr_id, child_attr_id } = this
|
||||
const _find = this.relationTypes.find((item) => item.name === relation_type)
|
||||
const relation_type_id = _find?.id
|
||||
if ((!parent_attr_id && child_attr_id) || (parent_attr_id && !child_attr_id)) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
|
||||
const {
|
||||
parent_attr_ids,
|
||||
child_attr_ids,
|
||||
validate
|
||||
} = this.handleValidateAttrList(this.tableAttrList)
|
||||
if (!validate) {
|
||||
return
|
||||
}
|
||||
|
||||
await createRelation(row.isParent ? childrenId : parentId, row.isParent ? parentId : childrenId, {
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id,
|
||||
child_attr_id,
|
||||
parent_attr_ids,
|
||||
child_attr_ids,
|
||||
}).finally(() => {
|
||||
this.getData()
|
||||
})
|
||||
@@ -429,7 +559,9 @@ export default {
|
||||
return _find?.alias ?? _find?.name ?? id
|
||||
},
|
||||
changeChild(value) {
|
||||
this.form.setFieldsValue({ child_attr_id: undefined })
|
||||
this.modalAttrList.forEach((item) => {
|
||||
item.childAttrId = undefined
|
||||
})
|
||||
getCITypeAttributesById(value).then((res) => {
|
||||
this.modalChildAttributes = res?.attributes ?? []
|
||||
})
|
||||
@@ -438,10 +570,75 @@ export default {
|
||||
// filter password/json/is_list
|
||||
return attributes.filter((attr) => !attr.is_password && !attr.is_list && attr.value_type !== '6')
|
||||
},
|
||||
addTableAttr() {
|
||||
this.tableAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: undefined,
|
||||
childAttrId: undefined
|
||||
})
|
||||
},
|
||||
removeTableAttr(id) {
|
||||
if (this.tableAttrList.length <= 1) {
|
||||
this.$message.error(this.$t('cmdb.ciType.attributeAssociationTip6'))
|
||||
return
|
||||
}
|
||||
const index = this.tableAttrList.findIndex((item) => item.id === id)
|
||||
if (index !== -1) {
|
||||
this.tableAttrList.splice(index, 1)
|
||||
}
|
||||
},
|
||||
|
||||
addModalAttr() {
|
||||
this.modalAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: undefined,
|
||||
childAttrId: undefined
|
||||
})
|
||||
},
|
||||
|
||||
removeModalAttr(id) {
|
||||
if (this.modalAttrList.length <= 1) {
|
||||
this.$message.error(this.$t('cmdb.ciType.attributeAssociationTip6'))
|
||||
return
|
||||
}
|
||||
const index = this.modalAttrList.findIndex((item) => item.id === id)
|
||||
if (index !== -1) {
|
||||
this.modalAttrList.splice(index, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.relation-table {
|
||||
/deep/ .vxe-cell {
|
||||
max-height: max-content !important;
|
||||
}
|
||||
}
|
||||
.table-attribute-row {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-top: 5px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
&-link {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
&-action {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-attribute-action {
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.ops-stripe-table .vxe-body--row.row--stripe.relation-table-divider {
|
||||
background-color: #b1b8d3 !important;
|
||||
|
@@ -157,7 +157,6 @@
|
||||
"
|
||||
type="primary"
|
||||
size="small"
|
||||
class="ops-button-primary"
|
||||
>{{ `${showCustomEmail ? $t('delete') : $t('add')}` }}{{ $t('cmdb.ciType.customEmail') }}</a-button
|
||||
>
|
||||
</div>
|
||||
|
@@ -5,7 +5,6 @@
|
||||
type="primary"
|
||||
@click="handleAddTrigger"
|
||||
size="small"
|
||||
class="ops-button-primary"
|
||||
icon="plus"
|
||||
>{{ $t('cmdb.ciType.newTrigger') }}</a-button
|
||||
>
|
||||
|
@@ -309,7 +309,4 @@ export default {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.ops-button-primary:hover {
|
||||
background-color: #2f54eb !important;
|
||||
}
|
||||
</style>
|
||||
|
@@ -153,7 +153,7 @@ export default {
|
||||
line-height: 32px;
|
||||
padding-left: 10px;
|
||||
margin-bottom: 20px;
|
||||
border-left: 4px solid #custom_colors[color_1];
|
||||
border-left: 4px solid @primary-color;
|
||||
justify-content: space-between;
|
||||
> div {
|
||||
font-weight: bold;
|
||||
|
@@ -70,13 +70,16 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('cmdb.ciType.attributeAssociation')">
|
||||
<a-row>
|
||||
<a-col :span="11">
|
||||
<a-row
|
||||
v-for="item in modalAttrList"
|
||||
:key="item.id"
|
||||
>
|
||||
<a-col :span="10">
|
||||
<a-form-item>
|
||||
<a-select
|
||||
:placeholder="$t('cmdb.ciType.attributeAssociationTip4')"
|
||||
allowClear
|
||||
v-decorator="['parent_attr_id', { rules: [{ required: false }] }]"
|
||||
v-model="item.parentAttrId"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(modalParentAttributes)" :key="attr.id">
|
||||
{{ attr.alias || attr.name }}
|
||||
@@ -87,12 +90,12 @@
|
||||
<a-col :span="2" :style="{ textAlign: 'center' }">
|
||||
=>
|
||||
</a-col>
|
||||
<a-col :span="11">
|
||||
<a-col :span="9">
|
||||
<a-form-item>
|
||||
<a-select
|
||||
:placeholder="$t('cmdb.ciType.attributeAssociationTip5')"
|
||||
allowClear
|
||||
v-decorator="['child_attr_id', { rules: [{ required: false }] }]"
|
||||
v-model="item.childAttrId"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(modalChildAttributes)" :key="attr.id">
|
||||
{{ attr.alias || attr.name }}
|
||||
@@ -100,6 +103,20 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="3">
|
||||
<a
|
||||
class="modal-attribute-action"
|
||||
@click="removeModalAttr(item.id)"
|
||||
>
|
||||
<a-icon type="minus-circle" />
|
||||
</a>
|
||||
<a
|
||||
class="modal-attribute-action"
|
||||
@click="addModalAttr"
|
||||
>
|
||||
<a-icon type="plus-circle" />
|
||||
</a>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
@@ -114,6 +131,7 @@ import { getCITypeGroupsConfig } from '@/modules/cmdb/api/ciTypeGroup'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { createRelation, deleteRelation, getCITypeChildren, getRelationTypes } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export default {
|
||||
name: 'Index',
|
||||
@@ -139,6 +157,7 @@ export default {
|
||||
|
||||
modalParentAttributes: [],
|
||||
modalChildAttributes: [],
|
||||
modalAttrList: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -228,6 +247,13 @@ export default {
|
||||
handleCreate() {
|
||||
this.drawerTitle = this.$t('cmdb.ciType.addRelation')
|
||||
this.visible = true
|
||||
this.$set(this, 'modalAttrList', [
|
||||
{
|
||||
id: uuidv4(),
|
||||
parentAttrId: undefined,
|
||||
childAttrId: undefined
|
||||
}
|
||||
])
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
source_ci_type_id: this.sourceCITypeId,
|
||||
@@ -249,19 +275,22 @@ export default {
|
||||
ci_type_id,
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id = undefined,
|
||||
child_attr_id = undefined,
|
||||
} = values
|
||||
|
||||
if ((!parent_attr_id && child_attr_id) || (parent_attr_id && !child_attr_id)) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
const {
|
||||
parent_attr_ids,
|
||||
child_attr_ids,
|
||||
validate
|
||||
} = this.handleValidateAttrList(this.modalAttrList)
|
||||
if (!validate) {
|
||||
return
|
||||
}
|
||||
|
||||
createRelation(source_ci_type_id, ci_type_id, {
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id,
|
||||
child_attr_id,
|
||||
parent_attr_ids,
|
||||
child_attr_ids,
|
||||
}).then((res) => {
|
||||
this.$message.success(this.$t('addSuccess'))
|
||||
this.onClose()
|
||||
@@ -272,6 +301,37 @@ export default {
|
||||
this.sourceCITypeId = undefined
|
||||
this.targetCITypeId = undefined
|
||||
},
|
||||
|
||||
/**
|
||||
* 校验属性列表
|
||||
* @param {*} attrList
|
||||
*/
|
||||
handleValidateAttrList(attrList) {
|
||||
const parent_attr_ids = []
|
||||
const child_attr_ids = []
|
||||
attrList.map((attr) => {
|
||||
if (attr.parentAttrId) {
|
||||
parent_attr_ids.push(attr.parentAttrId)
|
||||
}
|
||||
if (attr.childAttrId) {
|
||||
child_attr_ids.push(attr.childAttrId)
|
||||
}
|
||||
})
|
||||
|
||||
if (parent_attr_ids.length !== child_attr_ids.length) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
return {
|
||||
validate: false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
validate: true,
|
||||
parent_attr_ids,
|
||||
child_attr_ids
|
||||
}
|
||||
},
|
||||
|
||||
handleOk() {
|
||||
this.$refs.table.refresh()
|
||||
},
|
||||
@@ -284,14 +344,18 @@ export default {
|
||||
},
|
||||
handleSourceTypeChange(value) {
|
||||
this.sourceCITypeId = value
|
||||
this.form.setFieldsValue({ parent_attr_id: undefined })
|
||||
this.modalAttrList.forEach((item) => {
|
||||
item.parentAttrId = undefined
|
||||
})
|
||||
getCITypeAttributesById(value).then((res) => {
|
||||
this.modalParentAttributes = res?.attributes ?? []
|
||||
})
|
||||
},
|
||||
handleTargetTypeChange(value) {
|
||||
this.targetCITypeId = value
|
||||
this.form.setFieldsValue({ child_attr_id: undefined })
|
||||
this.modalAttrList.forEach((item) => {
|
||||
item.childAttrId = undefined
|
||||
})
|
||||
getCITypeAttributesById(value).then((res) => {
|
||||
this.modalChildAttributes = res?.attributes ?? []
|
||||
})
|
||||
@@ -303,12 +367,30 @@ export default {
|
||||
// filter password/json/is_list
|
||||
return attributes.filter((attr) => !attr.is_password && !attr.is_list && attr.value_type !== '6')
|
||||
},
|
||||
|
||||
addModalAttr() {
|
||||
this.modalAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: undefined,
|
||||
childAttrId: undefined
|
||||
})
|
||||
},
|
||||
|
||||
removeModalAttr(id) {
|
||||
if (this.modalAttrList.length <= 1) {
|
||||
this.$message.error(this.$t('cmdb.ciType.attributeAssociationTip6'))
|
||||
return
|
||||
}
|
||||
const index = this.modalAttrList.findIndex((item) => item.id === id)
|
||||
if (index !== -1) {
|
||||
this.modalAttrList.splice(index, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.model-relation {
|
||||
background-color: #fff;
|
||||
border-radius: @border-radius-box;
|
||||
@@ -316,4 +398,8 @@ export default {
|
||||
height: calc(100vh - 64px);
|
||||
margin-bottom: -24px;
|
||||
}
|
||||
|
||||
.modal-attribute-action {
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="model-relation-table">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
stripe
|
||||
@@ -7,6 +7,7 @@
|
||||
show-header-overflow
|
||||
show-overflow
|
||||
resizable
|
||||
:scroll-y="{enabled: false}"
|
||||
:height="`${windowHeight - 160}px`"
|
||||
:data="tableData"
|
||||
:sort-config="{ defaultSort: { field: 'created_at', order: 'desc' } }"
|
||||
@@ -34,7 +35,7 @@
|
||||
{{ handleConstraint(row.constraint) }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column :width="250" field="attributeAssociation" :edit-render="{}">
|
||||
<vxe-column :width="300" field="attributeAssociation" :edit-render="{}">
|
||||
<template #header>
|
||||
<span>
|
||||
<a-tooltip :title="$t('cmdb.ciType.attributeAssociationTip1')">
|
||||
@@ -47,37 +48,73 @@
|
||||
</span>
|
||||
</template>
|
||||
<template #default="{row}">
|
||||
<span
|
||||
v-if="row.parent_attr_id && row.child_attr_id"
|
||||
>{{ getAttrNameById(type2attributes[row.parent_id], row.parent_attr_id) }}=>
|
||||
{{ getAttrNameById(type2attributes[row.child_id], row.child_attr_id) }}</span
|
||||
<template
|
||||
v-for="item in row.parentAndChildAttrList"
|
||||
>
|
||||
<div
|
||||
:key="item.id"
|
||||
v-if="item.parentAttrId && item.childAttrId"
|
||||
>
|
||||
{{ getAttrNameById(type2attributes[row.parent_id], item.parentAttrId) }}=>
|
||||
{{ getAttrNameById(type2attributes[row.child_id], item.childAttrId) }}
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template #edit="{ row }">
|
||||
<div style="display:inline-flex;align-items:center;">
|
||||
<div
|
||||
v-for="item in tableAttrList"
|
||||
:key="item.id"
|
||||
class="table-attribute-row"
|
||||
>
|
||||
<a-select
|
||||
allowClear
|
||||
size="small"
|
||||
v-model="parent_attr_id"
|
||||
v-model="item.parentAttrId"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
:style="{ width: '100px' }"
|
||||
show-search
|
||||
optionFilterProp="title"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(type2attributes[row.parent_id])" :key="attr.id">
|
||||
<a-select-option
|
||||
v-for="attr in filterAttributes(type2attributes[row.parent_id])"
|
||||
:key="attr.id"
|
||||
:value="attr.id"
|
||||
:title="attr.alias || attr.name"
|
||||
>
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
=>
|
||||
<span class="table-attribute-row-link">=></span>
|
||||
<a-select
|
||||
allowClear
|
||||
size="small"
|
||||
v-model="child_attr_id"
|
||||
v-model="item.childAttrId"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
:style="{ width: '100px' }"
|
||||
show-search
|
||||
optionFilterProp="title"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(type2attributes[row.child_id])" :key="attr.id">
|
||||
<a-select-option
|
||||
v-for="attr in filterAttributes(type2attributes[row.child_id])"
|
||||
:key="attr.id"
|
||||
:value="attr.id"
|
||||
:title="attr.alias || attr.name"
|
||||
>
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a
|
||||
class="table-attribute-row-action"
|
||||
@click="removeTableAttr(item.id)"
|
||||
>
|
||||
<a-icon type="minus-circle" />
|
||||
</a>
|
||||
<a
|
||||
class="table-attribute-row-action"
|
||||
@click="addTableAttr"
|
||||
>
|
||||
<a-icon type="plus-circle" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</vxe-column>
|
||||
@@ -97,6 +134,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { getCITypeRelations, deleteRelation, createRelation } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getRelationTypes } from '@/modules/cmdb/api/relationType'
|
||||
import CMDBGrant from '../../../components/cmdbGrant'
|
||||
@@ -108,8 +146,7 @@ export default {
|
||||
tableData: [],
|
||||
relationTypeList: null,
|
||||
type2attributes: {},
|
||||
parent_attr_id: undefined,
|
||||
child_attr_id: undefined,
|
||||
tableAttrList: [],
|
||||
}
|
||||
},
|
||||
components: {
|
||||
@@ -137,9 +174,29 @@ export default {
|
||||
},
|
||||
async getMainData() {
|
||||
const { relations, type2attributes } = await getCITypeRelations()
|
||||
this.tableData = relations
|
||||
this.tableData = relations.map((item) => {
|
||||
const parentAndChildAttrList = this.handleAttrList(item)
|
||||
return {
|
||||
...item,
|
||||
parentAndChildAttrList
|
||||
}
|
||||
})
|
||||
this.type2attributes = type2attributes
|
||||
},
|
||||
|
||||
handleAttrList(data) {
|
||||
const length = Math.min(data?.parent_attr_ids?.length || 0, data.child_attr_ids?.length || 0)
|
||||
const parentAndChildAttrList = []
|
||||
for (let i = 0; i < length; i++) {
|
||||
parentAndChildAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: data?.parent_attr_ids?.[i] ?? '',
|
||||
childAttrId: data?.child_attr_ids?.[i] ?? ''
|
||||
})
|
||||
}
|
||||
return parentAndChildAttrList
|
||||
},
|
||||
|
||||
// 获取关系
|
||||
async getRelationTypes() {
|
||||
const res = await getRelationTypes()
|
||||
@@ -171,21 +228,75 @@ export default {
|
||||
})
|
||||
},
|
||||
handleEditActived({ row }) {
|
||||
this.parent_attr_id = row?.parent_attr_id ?? undefined
|
||||
this.child_attr_id = row?.child_attr_id ?? undefined
|
||||
const tableAttrList = []
|
||||
|
||||
const length = Math.min(row?.parent_attr_ids?.length || 0, row.child_attr_ids?.length || 0)
|
||||
if (length) {
|
||||
for (let i = 0; i < length; i++) {
|
||||
tableAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: row?.parent_attr_ids?.[i] ?? undefined,
|
||||
childAttrId: row?.child_attr_ids?.[i] ?? undefined
|
||||
})
|
||||
}
|
||||
} else {
|
||||
tableAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: undefined,
|
||||
childAttrId: undefined
|
||||
})
|
||||
}
|
||||
console.log('handleEditActived', tableAttrList)
|
||||
this.$set(this, 'tableAttrList', tableAttrList)
|
||||
},
|
||||
|
||||
/**
|
||||
* 校验属性列表
|
||||
* @param {*} attrList
|
||||
*/
|
||||
handleValidateAttrList(attrList) {
|
||||
const parent_attr_ids = []
|
||||
const child_attr_ids = []
|
||||
attrList.map((attr) => {
|
||||
if (attr.parentAttrId) {
|
||||
parent_attr_ids.push(attr.parentAttrId)
|
||||
}
|
||||
if (attr.childAttrId) {
|
||||
child_attr_ids.push(attr.childAttrId)
|
||||
}
|
||||
})
|
||||
|
||||
if (parent_attr_ids.length !== child_attr_ids.length) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
return {
|
||||
validate: false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
validate: true,
|
||||
parent_attr_ids,
|
||||
child_attr_ids
|
||||
}
|
||||
},
|
||||
|
||||
async handleEditClose({ row }) {
|
||||
const { parent_id, child_id, constraint, relation_type_id } = row
|
||||
const { parent_attr_id = undefined, child_attr_id = undefined } = this
|
||||
if ((!parent_attr_id && child_attr_id) || (parent_attr_id && !child_attr_id)) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
|
||||
const {
|
||||
parent_attr_ids,
|
||||
child_attr_ids,
|
||||
validate
|
||||
} = this.handleValidateAttrList(this.tableAttrList)
|
||||
if (!validate) {
|
||||
return
|
||||
}
|
||||
|
||||
await createRelation(parent_id, child_id, {
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id,
|
||||
child_attr_id,
|
||||
parent_attr_ids,
|
||||
child_attr_ids,
|
||||
}).finally(() => {
|
||||
this.getMainData()
|
||||
})
|
||||
@@ -198,8 +309,49 @@ export default {
|
||||
// filter password/json/is_list
|
||||
return attributes.filter((attr) => !attr.is_password && !attr.is_list && attr.value_type !== '6')
|
||||
},
|
||||
|
||||
addTableAttr() {
|
||||
this.tableAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: undefined,
|
||||
childAttrId: undefined
|
||||
})
|
||||
},
|
||||
removeTableAttr(id) {
|
||||
if (this.tableAttrList.length <= 1) {
|
||||
this.$message.error(this.$t('cmdb.ciType.attributeAssociationTip6'))
|
||||
return
|
||||
}
|
||||
const index = this.tableAttrList.findIndex((item) => item.id === id)
|
||||
if (index !== -1) {
|
||||
this.tableAttrList.splice(index, 1)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style lang="less" scoped>
|
||||
.relation-table {
|
||||
/deep/ .vxe-cell {
|
||||
max-height: max-content !important;
|
||||
}
|
||||
}
|
||||
.table-attribute-row {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-top: 5px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
&-link {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
&-action {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -5,10 +5,16 @@
|
||||
show-header-overflow
|
||||
stripe
|
||||
size="small"
|
||||
class="ops-stripe-table"
|
||||
class="ops-unstripe-table"
|
||||
:data="tableData"
|
||||
v-bind="ci_id ? { height: 'auto' } : { height: `${windowHeight - 225}px` }"
|
||||
>
|
||||
<template #empty>
|
||||
<a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }">
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<span slot="description"> {{ $t('noData') }} </span>
|
||||
</a-empty>
|
||||
</template>
|
||||
<vxe-column field="trigger_name" :title="$t('cmdb.history.triggerName')"> </vxe-column>
|
||||
<vxe-column field="type" :title="$t('type')">
|
||||
<template #default="{ row }">
|
||||
|
@@ -32,62 +32,77 @@
|
||||
}"
|
||||
/></span>
|
||||
</div>
|
||||
<div class="cmdb-preference-group" v-for="(group, index) in myPreferences" :key="group.name">
|
||||
<div class="cmdb-preference-group" v-for="(subType, index) in myPreferences" :key="subType.name">
|
||||
<div class="cmdb-preference-group-title">
|
||||
<span> <ops-icon :style="{ marginRight: '10px' }" :type="group.icon" />{{ group.name }} </span>
|
||||
<span> <ops-icon :style="{ marginRight: '10px' }" :type="subType.icon" />{{ subType.name }}</span>
|
||||
</div>
|
||||
<draggable
|
||||
v-model="group.ci_types"
|
||||
:animation="300"
|
||||
@change="
|
||||
(e) => {
|
||||
orderChange(e, group)
|
||||
}
|
||||
"
|
||||
>
|
||||
<div class="cmdb-preference-group-content" v-for="ciType in group.ci_types" :key="ciType.id">
|
||||
<OpsMoveIcon class="cmdb-preference-move-icon" />
|
||||
<draggable class="ci-types-left-content" :list="subType.groups" @end="handleChangeGroups" filter=".undraggable">
|
||||
<div v-for="group in subType.groups" :key="group.id || group.name">
|
||||
<div
|
||||
:class="{
|
||||
'cmdb-preference-avatar': true,
|
||||
'cmdb-preference-avatar-noicon': !ciType.icon,
|
||||
}"
|
||||
:style="{ width: '30px', height: '30px', marginRight: '10px' }"
|
||||
:class="
|
||||
`${group.id === undefined ? 'undraggable' : ''}`
|
||||
"
|
||||
>
|
||||
<template v-if="ciType.icon">
|
||||
<img
|
||||
v-if="ciType.icon.split('$$')[2]"
|
||||
:src="`/api/common-setting/v1/file/${ciType.icon.split('$$')[3]}`"
|
||||
:style="{ maxHeight: '30px', maxWidth: '30px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{
|
||||
color: ciType.icon.split('$$')[1],
|
||||
fontSize: '14px',
|
||||
}"
|
||||
:type="ciType.icon.split('$$')[0]"
|
||||
/>
|
||||
</template>
|
||||
<span v-else :style="{ fontSize: '20px' }">{{ ciType.name[0].toUpperCase() }}</span>
|
||||
<div v-if="index === 0 && subType.groups.length > 1" class="cmdb-preference-group-content">
|
||||
<OpsMoveIcon class="cmdb-preference-move-icon" v-if="group.name"/>
|
||||
<span style="font-weight: 500; color: #a5a9bc"><ellipsis :length="25" tooltip>{{ group.name || $t('other') }}</ellipsis></span>
|
||||
<span :style="{ color: '#c3cdd7' }">({{ group.ci_types.length }})</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="cmdb-preference-group-content-title">{{ ciType.alias || ciType.name }}</span>
|
||||
<span class="cmdb-preference-group-content-action">
|
||||
<a-tooltip :title="$t('cmdb.preference.cancelSub')">
|
||||
<span
|
||||
@click="unsubscribe(ciType, group.type)"
|
||||
><ops-icon type="cmdb-preference-cancel-subscribe" />
|
||||
<draggable
|
||||
v-model="group.ci_types"
|
||||
:animation="300"
|
||||
@change="
|
||||
(e) => {
|
||||
orderChange(e, group, index === 1)
|
||||
}
|
||||
"
|
||||
>
|
||||
<div class="cmdb-preference-group-content" v-for="ciType in group.ci_types" :key="ciType.id">
|
||||
<OpsMoveIcon class="cmdb-preference-move-icon" />
|
||||
<div
|
||||
:class="{
|
||||
'cmdb-preference-avatar': true,
|
||||
'cmdb-preference-avatar-noicon': !ciType.icon,
|
||||
}"
|
||||
:style="{ width: '30px', height: '30px', marginRight: '10px' }"
|
||||
>
|
||||
<template v-if="ciType.icon">
|
||||
<img
|
||||
v-if="ciType.icon.split('$$')[2]"
|
||||
:src="`/api/common-setting/v1/file/${ciType.icon.split('$$')[3]}`"
|
||||
:style="{ maxHeight: '30px', maxWidth: '30px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{
|
||||
color: ciType.icon.split('$$')[1],
|
||||
fontSize: '14px',
|
||||
}"
|
||||
:type="ciType.icon.split('$$')[0]"
|
||||
/>
|
||||
</template>
|
||||
<span v-else :style="{ fontSize: '20px' }">{{ ciType.name[0].toUpperCase() }}</span>
|
||||
</div>
|
||||
<span class="cmdb-preference-group-content-title">{{ ciType.alias || ciType.name }}</span>
|
||||
<span class="cmdb-preference-group-content-action">
|
||||
<a-tooltip :title="$t('cmdb.preference.cancelSub')">
|
||||
<span
|
||||
@click="unsubscribe(ciType, group.type)"
|
||||
><ops-icon type="cmdb-preference-cancel-subscribe" />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-divider type="vertical" :style="{ margin: '0 3px' }" />
|
||||
<a-tooltip :title="$t('cmdb.preference.editSub')">
|
||||
<span
|
||||
@click="openSubscribeSetting(ciType, `${index + 1}`)"
|
||||
><ops-icon
|
||||
type="cmdb-preference-subscribe"
|
||||
/></span>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-divider type="vertical" :style="{ margin: '0 3px' }" />
|
||||
<a-tooltip :title="$t('cmdb.preference.editSub')">
|
||||
<span
|
||||
@click="openSubscribeSetting(ciType, `${index + 1}`)"
|
||||
><ops-icon
|
||||
type="cmdb-preference-subscribe"
|
||||
/></span>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
@@ -197,6 +212,7 @@ import store from '@/store'
|
||||
import { mapState } from 'vuex'
|
||||
import moment from 'moment'
|
||||
import draggable from 'vuedraggable'
|
||||
import { Ellipsis } from '@/components'
|
||||
import { getCITypeGroups } from '../../api/ciTypeGroup'
|
||||
import {
|
||||
getPreference,
|
||||
@@ -212,7 +228,7 @@ import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||
|
||||
export default {
|
||||
name: 'Preference',
|
||||
components: { CollapseTransition, SubscribeSetting, draggable, OpsMoveIcon },
|
||||
components: { CollapseTransition, SubscribeSetting, draggable, OpsMoveIcon, Ellipsis },
|
||||
data() {
|
||||
return {
|
||||
citypeData: [],
|
||||
@@ -261,7 +277,7 @@ export default {
|
||||
ciTypeGroup.forEach((group) => {
|
||||
if (group.ci_types && group.ci_types.length) {
|
||||
group.ci_types.forEach((type) => {
|
||||
const idx = pref.findIndex((p) => p.id === type.id)
|
||||
const idx = pref.type_ids.findIndex((p) => p === type.id)
|
||||
if (idx > -1) {
|
||||
type.is_subscribed = true
|
||||
}
|
||||
@@ -283,19 +299,18 @@ export default {
|
||||
const _myPreferences = [
|
||||
{
|
||||
name: this.$t('cmdb.menu.ciTable'),
|
||||
ci_types: self.instance.map((item) => {
|
||||
const _find = pref.find((ci) => ci.id === item)
|
||||
return _find
|
||||
}),
|
||||
groups: pref.group_types,
|
||||
icon: 'cmdb-ci',
|
||||
type: 'ci',
|
||||
},
|
||||
{
|
||||
name: this.$t('cmdb.menu.ciTree'),
|
||||
ci_types: self.tree.map((item) => {
|
||||
const _find = pref.find((ci) => ci.id === item)
|
||||
return _find
|
||||
}),
|
||||
groups: [
|
||||
{
|
||||
ci_types: pref.tree_types,
|
||||
name: null,
|
||||
}
|
||||
],
|
||||
icon: 'cmdb-tree',
|
||||
type: 'tree',
|
||||
},
|
||||
@@ -377,10 +392,41 @@ export default {
|
||||
this.expandKeys.push(group.id)
|
||||
}
|
||||
},
|
||||
orderChange(e, group) {
|
||||
preferenceCitypeOrder({ type_ids: group.ci_types.map((type) => type.id), is_tree: group.type !== 'ci' })
|
||||
async handleChangeGroups() {
|
||||
const typeIds = []
|
||||
this.myPreferences[0].groups.forEach(groupTypes => {
|
||||
groupTypes.ci_types.forEach(ciType => {
|
||||
typeIds.push(ciType.id)
|
||||
})
|
||||
})
|
||||
preferenceCitypeOrder({ type_ids: typeIds, is_tree: false })
|
||||
.then(() => {
|
||||
if (group.type === 'ci') {
|
||||
this.resetRoute()
|
||||
})
|
||||
.catch(() => {
|
||||
this.getCITypes(false)
|
||||
})
|
||||
},
|
||||
orderChange(e, group, isTree) {
|
||||
let typeIds = []
|
||||
if (!isTree) {
|
||||
this.myPreferences[0].groups.forEach(groupTypes => {
|
||||
if (group.id === groupTypes.id) {
|
||||
group.ci_types.forEach(ciType => {
|
||||
typeIds.push(ciType.id)
|
||||
})
|
||||
} else {
|
||||
groupTypes.ci_types.forEach(ciType => {
|
||||
typeIds.push(ciType.id)
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
typeIds = group.ci_types.map(item => item.id)
|
||||
}
|
||||
preferenceCitypeOrder({ type_ids: typeIds, is_tree: isTree })
|
||||
.then(() => {
|
||||
if (!isTree) {
|
||||
this.resetRoute()
|
||||
}
|
||||
})
|
||||
@@ -442,6 +488,23 @@ export default {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.cmdb-preference-group {
|
||||
.ci-types-left-content {
|
||||
max-height: calc(100% - 45px);
|
||||
overflow: hidden;
|
||||
&:hover {
|
||||
overflow: auto;
|
||||
}
|
||||
.undraggable{
|
||||
.cmdb-preference-group-content {
|
||||
cursor: default;
|
||||
margin-left: 20px;
|
||||
&:hover {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.cmdb-preference-group-title {
|
||||
text-align: center;
|
||||
margin-bottom: 5px;
|
||||
@@ -494,7 +557,7 @@ export default {
|
||||
.cmdb-preference-group-content-action {
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
}
|
||||
@@ -592,7 +655,7 @@ export default {
|
||||
.cmdb-preference-footor-unsubscribed {
|
||||
text-align: center;
|
||||
> span {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -605,7 +668,7 @@ export default {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
> span:nth-child(2) {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@
|
||||
:triggerLength="18"
|
||||
>
|
||||
<template #one>
|
||||
<div class="relation-views-left" :style="{ height: `${windowHeight - 115}px` }">
|
||||
<div class="relation-views-left" :style="{ height: `${windowHeight - 64}px` }">
|
||||
<div class="relation-views-left-header" :title="$route.meta.name">{{ $route.meta.name }}</div>
|
||||
<a-input
|
||||
:placeholder="$t('cmdb.serviceTree.searchTips')"
|
||||
@@ -607,7 +607,7 @@ export default {
|
||||
this.reload()
|
||||
},
|
||||
pageNo: function(newPage, oldPage) {
|
||||
this.loadData({ params: { pageNo: newPage }, refreshType: undefined, sortByTable: this.sortByTable })
|
||||
this.loadData({ parameter: { pageNo: newPage }, refreshType: undefined, sortByTable: this.sortByTable })
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1193,7 +1193,7 @@ export default {
|
||||
that.$refs.xTable.clearCheckboxRow()
|
||||
that.$refs.xTable.clearCheckboxReserve()
|
||||
that.selectedRowKeys = []
|
||||
that.loadData({ params: {}, refreshType: 'refreshNumber' })
|
||||
that.loadData({ parameter: {}, refreshType: 'refreshNumber' })
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -1241,7 +1241,7 @@ export default {
|
||||
this.sortByTable = sortByTable
|
||||
this.$nextTick(() => {
|
||||
if (this.pageNo === 1) {
|
||||
this.loadData({ params: {}, refreshType: undefined, sortByTable })
|
||||
this.loadData({ parameter: {}, refreshType: undefined, sortByTable })
|
||||
} else {
|
||||
this.pageNo = 1
|
||||
}
|
||||
@@ -1400,7 +1400,7 @@ export default {
|
||||
onOk() {
|
||||
deleteCI(record.ci_id || record._id).then((res) => {
|
||||
that.$message.success(that.$t('deleteSuccess'))
|
||||
that.loadData({ params: {}, refreshType: 'refreshNumber' })
|
||||
that.loadData({ parameter: {}, refreshType: 'refreshNumber' })
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -1420,7 +1420,7 @@ export default {
|
||||
}
|
||||
addCIRelationView(first_ci_id, ci_id, { ancestor_ids }).then((res) => {
|
||||
setTimeout(() => {
|
||||
this.loadData({ params: {}, refreshType: 'refreshNumber' })
|
||||
this.loadData({ parameter: {}, refreshType: 'refreshNumber' })
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
@@ -1458,7 +1458,7 @@ export default {
|
||||
.finally(() => {
|
||||
that.loading = false
|
||||
setTimeout(() => {
|
||||
that.loadData({ params: {} })
|
||||
that.loadData({ parameter: {} })
|
||||
}, 800)
|
||||
})
|
||||
},
|
||||
@@ -1521,7 +1521,7 @@ export default {
|
||||
that.selectedRowKeys = []
|
||||
that.$refs.xTable.clearCheckboxRow()
|
||||
that.$refs.xTable.clearCheckboxReserve()
|
||||
that.loadData({ params: {}, refreshType: 'refreshNumber' })
|
||||
that.loadData({ parameter: {}, refreshType: 'refreshNumber' })
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -1539,7 +1539,7 @@ export default {
|
||||
this.$refs.xTable.refreshColumn()
|
||||
},
|
||||
relationViewRefreshNumber() {
|
||||
this.loadData({ params: {}, refreshType: 'refreshNumber' })
|
||||
this.loadData({ parameter: {}, refreshType: 'refreshNumber' })
|
||||
},
|
||||
onShowSizeChange(current, pageSize) {
|
||||
this.pageSize = pageSize
|
||||
@@ -1762,12 +1762,29 @@ export default {
|
||||
}
|
||||
if (node.children) {
|
||||
node.children = node.children.filter((child) => {
|
||||
if (predicateCiIds.some((id) => child.key.includes(String(id)))) {
|
||||
if (
|
||||
predicateCiIds.some(
|
||||
(id) =>
|
||||
child.key
|
||||
.split('@^@')
|
||||
.map((item) => Number(item.split('%')[0]))
|
||||
.indexOf(id) > -1
|
||||
)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return filterTree(child, predicate)
|
||||
})
|
||||
if (node.children.length && !predicateCiIds.some((id) => node.key.includes(String(id)))) {
|
||||
if (
|
||||
node.children.length &&
|
||||
!predicateCiIds.some(
|
||||
(id) =>
|
||||
node.key
|
||||
.split('@^@')
|
||||
.map((item) => Number(item.split('%')[0]))
|
||||
.indexOf(id) > -1
|
||||
)
|
||||
) {
|
||||
_expandedKeys.push(node.key)
|
||||
}
|
||||
return node.children.length > 0
|
||||
|
File diff suppressed because it is too large
Load Diff
1150
cmdb-ui/src/modules/cmdb/views/topology_view/index.vue
Normal file
1150
cmdb-ui/src/modules/cmdb/views/topology_view/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -57,47 +57,37 @@ export const generatorDynamicRouter = async () => {
|
||||
{
|
||||
path: '/setting/companyinfo',
|
||||
name: 'company_info',
|
||||
meta: { title: 'cs.menu.companyInfo', appName: 'backend', icon: 'ops-setting-companyInfo', selectedIcon: 'ops-setting-companyInfo-selected', permission: ['公司信息', 'backend_admin'] },
|
||||
meta: { title: 'cs.menu.companyInfo', appName: 'backend', icon: 'ops-setting-companyInfo', selectedIcon: 'ops-setting-companyInfo', permission: ['公司信息', 'backend_admin'] },
|
||||
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/companyInfo/index')
|
||||
},
|
||||
{
|
||||
path: '/setting/companystructure',
|
||||
name: 'company_structure',
|
||||
meta: { title: 'cs.menu.companyStructure', appName: 'backend', icon: 'ops-setting-companyStructure', selectedIcon: 'ops-setting-companyStructure-selected', permission: ['公司架构', 'backend_admin'] },
|
||||
meta: { title: 'cs.menu.companyStructure', appName: 'backend', icon: 'ops-setting-companyStructure', selectedIcon: 'ops-setting-companyStructure', permission: ['公司架构', 'backend_admin'] },
|
||||
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/companyStructure/index')
|
||||
},
|
||||
{
|
||||
path: '/setting/notice',
|
||||
name: 'notice',
|
||||
component: RouteView,
|
||||
meta: { title: 'cs.menu.notice', appName: 'backend', icon: 'ops-setting-notice', selectedIcon: 'ops-setting-notice-selected', permission: ['通知设置', 'backend_admin'] },
|
||||
meta: { title: 'cs.menu.notice', appName: 'backend', icon: 'ops-setting-notice', selectedIcon: 'ops-setting-notice', permission: ['通知设置', 'backend_admin'] },
|
||||
redirect: '/setting/notice/email',
|
||||
children: [{
|
||||
path: '/setting/notice/basic',
|
||||
name: 'notice_basic',
|
||||
meta: { title: 'cs.menu.basic', icon: 'ops-setting-basic', selectedIcon: 'ops-setting-basic-selected' },
|
||||
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/notice/basic')
|
||||
}, {
|
||||
path: '/setting/notice/email',
|
||||
name: 'notice_email',
|
||||
meta: { title: 'cs.menu.email', icon: 'ops-setting-notice-email', selectedIcon: 'ops-setting-notice-email-selected' },
|
||||
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/notice/email/index')
|
||||
}, {
|
||||
path: '/setting/notice/wx',
|
||||
name: 'notice_wx',
|
||||
meta: { title: 'cs.menu.wx', icon: 'ops-setting-notice-wx', selectedIcon: 'ops-setting-notice-wx-selected' },
|
||||
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/notice/wx')
|
||||
}, {
|
||||
path: '/setting/notice/dingding',
|
||||
name: 'notice_dingding',
|
||||
meta: { title: 'cs.menu.dingding', icon: 'ops-setting-notice-dingding', selectedIcon: 'ops-setting-notice-dingding-selected' },
|
||||
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/notice/dingding')
|
||||
}, {
|
||||
path: '/setting/notice/feishu',
|
||||
name: 'notice_feishu',
|
||||
meta: { title: 'cs.menu.feishu', icon: 'ops-setting-notice-feishu', selectedIcon: 'ops-setting-notice-feishu-selected' },
|
||||
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/notice/feishu')
|
||||
}]
|
||||
},
|
||||
{
|
||||
path: '/setting/auth',
|
||||
name: 'company_auth',
|
||||
meta: { title: 'cs.menu.auth', appName: 'backend', icon: 'ops-setting-auth', selectedIcon: 'ops-setting-auth-selected', permission: ['acl_admin'] },
|
||||
meta: { title: 'cs.menu.auth', appName: 'backend', icon: 'ops-setting-auth', selectedIcon: 'ops-setting-auth', permission: ['acl_admin'] },
|
||||
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/auth/index')
|
||||
},
|
||||
]
|
||||
|
@@ -1152,13 +1152,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
// button
|
||||
.ops-button-primary {
|
||||
background-color: @primary-color_4;
|
||||
border-color: @primary-color_4;
|
||||
color: @primary-color;
|
||||
box-shadow: none;
|
||||
}
|
||||
// button
|
||||
.ops-button-ghost.ant-btn-background-ghost.ant-btn-primary {
|
||||
background-color: @primary-color_5!important;
|
||||
|
@@ -46,11 +46,6 @@
|
||||
@layout-sidebar-selected-font-color: @primary-color;
|
||||
@layout-sidebar-disabled-font-color: @text-color_4;
|
||||
|
||||
#custom_colors() {
|
||||
color_1: #2f54eb; //primary color
|
||||
color_2: #f0f5ff; //light background color
|
||||
color_3: #d2e2ff;
|
||||
}
|
||||
|
||||
.ops_display_wrapper(@backgroundColor:@primary-color_5) {
|
||||
cursor: pointer;
|
||||
|
@@ -309,10 +309,10 @@ export default {
|
||||
.notice-center-left:hover,
|
||||
.notice-center-left-select {
|
||||
background-color: #f0f5ff;
|
||||
border-color: #custom_colors[color_1];
|
||||
border-color: @primary-color;
|
||||
> span:nth-child(2) {
|
||||
background-color: #fff;
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
.notice-center-header {
|
||||
@@ -329,7 +329,7 @@ export default {
|
||||
}
|
||||
> .notice-center-header-app:hover,
|
||||
.notice-center-header-app-selected {
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
color: #fff;
|
||||
}
|
||||
.notice-center-categories {
|
||||
@@ -342,7 +342,7 @@ export default {
|
||||
> span:hover,
|
||||
.notice-center-categories-selected {
|
||||
color: #fff;
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user