mirror of
https://github.com/veops/cmdb.git
synced 2025-09-03 03:06:56 +08:00
Compare commits
27 Commits
fix_issue#
...
2.4.5
Author | SHA1 | Date | |
---|---|---|---|
|
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 |
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,7 +439,8 @@ 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)
|
||||
|
||||
@@ -501,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):
|
||||
@@ -829,6 +838,12 @@ 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()
|
||||
@@ -912,6 +927,44 @@ class CIManager(object):
|
||||
|
||||
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)
|
||||
|
||||
@@ -1037,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()
|
||||
@@ -1087,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(
|
||||
@@ -1121,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,
|
||||
@@ -1155,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):
|
||||
"""
|
||||
|
@@ -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,9 +27,11 @@ 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
|
||||
@@ -48,6 +49,7 @@ from api.models.cmdb import CITypeTrigger
|
||||
from api.models.cmdb import CITypeUniqueConstraint
|
||||
from api.models.cmdb import CustomDashboard
|
||||
from api.models.cmdb import PreferenceCITypeOrder
|
||||
from api.models.cmdb import TopologyView
|
||||
from api.models.cmdb import PreferenceRelationView
|
||||
from api.models.cmdb import PreferenceSearchOption
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
@@ -130,7 +132,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)
|
||||
@@ -260,6 +264,9 @@ class CITypeManager(object):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for item in TopologyView.get_by(central_node_type=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
ci_type.soft_delete()
|
||||
@@ -822,6 +829,70 @@ class CITypeRelationManager(object):
|
||||
|
||||
return [cls._wrap_relation_type_dict(parent.parent_id, parent) for parent in parents]
|
||||
|
||||
@staticmethod
|
||||
def get_relations_by_type_id(type_id):
|
||||
nodes, edges = [], []
|
||||
node_ids, edge_tuples = set(), set()
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
if ci_type is None:
|
||||
return nodes, edges
|
||||
|
||||
nodes.append(ci_type.to_dict())
|
||||
node_ids.add(ci_type.id)
|
||||
|
||||
def _find(_id, lv):
|
||||
lv += 1
|
||||
for i in CITypeRelation.get_by(parent_id=_id, to_dict=False):
|
||||
if i.child_id not in node_ids:
|
||||
node_ids.add(i.child_id)
|
||||
node = i.child.to_dict()
|
||||
node.setdefault('level', []).append(lv)
|
||||
nodes.append(node)
|
||||
|
||||
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=False))
|
||||
edge_tuples.add((i.parent_id, i.child_id))
|
||||
|
||||
_find(i.child_id, lv)
|
||||
continue
|
||||
elif (i.parent_id, i.child_id) not in edge_tuples:
|
||||
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=False))
|
||||
edge_tuples.add((i.parent_id, i.child_id))
|
||||
_find(i.child_id, lv)
|
||||
|
||||
for _node in nodes:
|
||||
if _node['id'] == i.child_id and lv not in _node['level']:
|
||||
_node['level'].append(lv)
|
||||
|
||||
def _reverse_find(_id, lv):
|
||||
lv -= 1
|
||||
for i in CITypeRelation.get_by(child_id=_id, to_dict=False):
|
||||
if i.parent_id not in node_ids:
|
||||
node_ids.add(i.parent_id)
|
||||
node = i.parent.to_dict()
|
||||
node.setdefault('level', []).append(lv)
|
||||
nodes.append(node)
|
||||
|
||||
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=True))
|
||||
edge_tuples.add((i.parent_id, i.child_id))
|
||||
|
||||
_reverse_find(i.parent_id, lv)
|
||||
continue
|
||||
elif (i.parent_id, i.child_id) not in edge_tuples:
|
||||
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=True))
|
||||
edge_tuples.add((i.parent_id, i.child_id))
|
||||
_reverse_find(i.parent_id, lv)
|
||||
|
||||
for _node in nodes:
|
||||
if _node['id'] == i.child_id and lv not in _node['level']:
|
||||
_node['level'].append(lv)
|
||||
|
||||
level = 0
|
||||
_reverse_find(ci_type.id, level)
|
||||
level = 0
|
||||
_find(ci_type.id, level)
|
||||
|
||||
return nodes, edges
|
||||
|
||||
@staticmethod
|
||||
def _get(parent_id, child_id):
|
||||
return CITypeRelation.get_by(parent_id=parent_id,
|
||||
|
@@ -73,6 +73,7 @@ class ResourceTypeEnum(BaseEnum):
|
||||
RELATION_VIEW = "RelationView" # read/update/delete/grant
|
||||
CI_FILTER = "CIFilter" # read
|
||||
PAGE = "page" # read
|
||||
TOPOLOGY_VIEW = "TopologyView" # read/update/delete/grant
|
||||
|
||||
|
||||
class PermEnum(BaseEnum):
|
||||
|
@@ -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):
|
||||
|
@@ -140,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,7 +274,8 @@ 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]
|
||||
existed_values = [(ValueTypeMap.serialize[attr.value_type](i.value) if
|
||||
i.value or i.value == 0 else i.value) for i in existed_attrs]
|
||||
|
||||
# Comparison array starts from which position changes
|
||||
min_len = min(len(value), len(existed_values))
|
||||
@@ -283,17 +284,15 @@ class AttributeValueManager(object):
|
||||
if value[index] != existed_values[index]:
|
||||
break
|
||||
index += 1
|
||||
added = value[index:]
|
||||
deleted = existed_values[index:]
|
||||
|
||||
# Delete first and then add to ensure id sorting
|
||||
for v in deleted:
|
||||
existed_attr = existed_attrs[existed_values.index(v)]
|
||||
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))
|
||||
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))
|
||||
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
|
||||
|
@@ -48,7 +48,12 @@ class CMDBApp(BaseApp):
|
||||
{"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": "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__()
|
||||
|
@@ -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']
|
||||
@@ -232,20 +238,20 @@ class RoleRelationCache(object):
|
||||
for _app_id in app_ids:
|
||||
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)
|
||||
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)
|
||||
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):
|
||||
|
@@ -315,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()
|
||||
|
||||
|
@@ -154,19 +154,19 @@ class RoleRelationCRUD(object):
|
||||
if existed:
|
||||
continue
|
||||
|
||||
RoleRelationCache.clean(parent_id, app_id)
|
||||
RoleRelationCache.clean(child_id, app_id)
|
||||
|
||||
if parent_id in cls.recursive_child_ids(child_id, app_id):
|
||||
return abort(400, ErrFormat.inheritance_dead_loop)
|
||||
|
||||
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)
|
||||
|
||||
result.append(RoleRelation.create(parent_id=parent_id, child_id=child_id, app_id=app_id).to_dict())
|
||||
|
||||
AuditCRUD.add_role_log(app_id, AuditOperateType.role_relation_add,
|
||||
AuditScope.role_relation, role.id, {}, {},
|
||||
{'child_ids': list(child_ids), 'parent_ids': [parent_id], }
|
||||
@@ -379,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:
|
||||
@@ -399,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':
|
||||
|
@@ -209,6 +209,26 @@ class CITriggerHistory(Model):
|
||||
webhook = db.Column(db.Text)
|
||||
|
||||
|
||||
class TopologyViewGroup(Model):
|
||||
__tablename__ = 'c_topology_view_groups'
|
||||
|
||||
name = db.Column(db.String(64), index=True)
|
||||
order = db.Column(db.Integer, default=0)
|
||||
|
||||
|
||||
class TopologyView(Model):
|
||||
__tablename__ = 'c_topology_views'
|
||||
|
||||
name = db.Column(db.String(64), index=True)
|
||||
group_id = db.Column(db.Integer, db.ForeignKey('c_topology_view_groups.id'))
|
||||
category = db.Column(db.String(32))
|
||||
central_node_type = db.Column(db.Integer)
|
||||
central_node_instances = db.Column(db.Text)
|
||||
path = db.Column(db.JSON)
|
||||
order = db.Column(db.Integer, default=0)
|
||||
option = db.Column(db.JSON)
|
||||
|
||||
|
||||
class CITypeUniqueConstraint(Model):
|
||||
__tablename__ = "c_c_t_u_c"
|
||||
|
||||
|
@@ -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 "登录成功"
|
||||
|
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",
|
||||
|
@@ -54,6 +54,252 @@
|
||||
<div class="content unicode" style="display: block;">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">ops-topology_view</div>
|
||||
<div class="code-name">&#xe92b;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-host_analysis</div>
|
||||
<div class="code-name">&#xe92a;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-add2</div>
|
||||
<div class="code-name">&#xe929;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-native</div>
|
||||
<div class="code-name">&#xe928;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-filter2</div>
|
||||
<div class="code-name">&#xe927;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">ops-cmdb-data_companies-selected</div>
|
||||
<div class="code-name">&#xe601;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">ops-cmdb-data_companies</div>
|
||||
<div class="code-name">&#xe926;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-threshold_value</div>
|
||||
<div class="code-name">&#xe921;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-disposition</div>
|
||||
<div class="code-name">&#xe922;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-automatic_discovery</div>
|
||||
<div class="code-name">&#xe923;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-grouping_list</div>
|
||||
<div class="code-name">&#xe924;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-node_list</div>
|
||||
<div class="code-name">&#xe925;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-general_view</div>
|
||||
<div class="code-name">&#xe920;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-network_topology</div>
|
||||
<div class="code-name">&#xe91b;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-node_management</div>
|
||||
<div class="code-name">&#xe91c;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-alarm_policy</div>
|
||||
<div class="code-name">&#xe91d;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-alarm</div>
|
||||
<div class="code-name">&#xe91e;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-healing</div>
|
||||
<div class="code-name">&#xe91f;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-data_acquisition</div>
|
||||
<div class="code-name">&#xe8d4;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-analysis</div>
|
||||
<div class="code-name">&#xe91a;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-index</div>
|
||||
<div class="code-name">&#xe89b;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-user_defined</div>
|
||||
<div class="code-name">&#xe867;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-database</div>
|
||||
<div class="code-name">&#xe861;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-common</div>
|
||||
<div class="code-name">&#xe865;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-edit</div>
|
||||
<div class="code-name">&#xe866;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-empower</div>
|
||||
<div class="code-name">&#xe863;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-share</div>
|
||||
<div class="code-name">&#xe864;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-export</div>
|
||||
<div class="code-name">&#xe862;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-import</div>
|
||||
<div class="code-name">&#xe860;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-ip (1)</div>
|
||||
<div class="code-name">&#xe807;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-director</div>
|
||||
<div class="code-name">&#xe803;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-host</div>
|
||||
<div class="code-name">&#xe804;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">cmdb-log</div>
|
||||
<div class="code-name">&#xe802;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-add</div>
|
||||
<div class="code-name">&#xe7ff;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-down</div>
|
||||
<div class="code-name">&#xe7fc;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-up</div>
|
||||
<div class="code-name">&#xe7fd;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">itsm-unfold</div>
|
||||
<div class="code-name">&#xe7f9;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">itsm-shrink</div>
|
||||
<div class="code-name">&#xe7f8;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-data_comaparison2</div>
|
||||
<div class="code-name">&#xe7a1;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-data_comaparison1</div>
|
||||
<div class="code-name">&#xe7f7;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">monitor-online</div>
|
||||
<div class="code-name">&#xe7a0;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">ops-setting-application-selected</div>
|
||||
@@ -4620,9 +4866,9 @@
|
||||
<pre><code class="language-css"
|
||||
>@font-face {
|
||||
font-family: 'iconfont';
|
||||
src: url('iconfont.woff2?t=1713840593232') format('woff2'),
|
||||
url('iconfont.woff?t=1713840593232') format('woff'),
|
||||
url('iconfont.ttf?t=1713840593232') format('truetype');
|
||||
src: url('iconfont.woff2?t=1716896994700') format('woff2'),
|
||||
url('iconfont.woff?t=1716896994700') format('woff'),
|
||||
url('iconfont.ttf?t=1716896994700') format('truetype');
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||
@@ -4648,6 +4894,375 @@
|
||||
<div class="content font-class">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont ops-topology_view"></span>
|
||||
<div class="name">
|
||||
ops-topology_view
|
||||
</div>
|
||||
<div class="code-name">.ops-topology_view
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-host_analysis"></span>
|
||||
<div class="name">
|
||||
monitor-host_analysis
|
||||
</div>
|
||||
<div class="code-name">.monitor-host_analysis
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont a-Group427319324"></span>
|
||||
<div class="name">
|
||||
monitor-add2
|
||||
</div>
|
||||
<div class="code-name">.a-Group427319324
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-native"></span>
|
||||
<div class="name">
|
||||
monitor-native
|
||||
</div>
|
||||
<div class="code-name">.monitor-native
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont veops-filter2"></span>
|
||||
<div class="name">
|
||||
veops-filter2
|
||||
</div>
|
||||
<div class="code-name">.veops-filter2
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont ops-cmdb-data_companies-selected"></span>
|
||||
<div class="name">
|
||||
ops-cmdb-data_companies-selected
|
||||
</div>
|
||||
<div class="code-name">.ops-cmdb-data_companies-selected
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont ops-cmdb-data_companies"></span>
|
||||
<div class="name">
|
||||
ops-cmdb-data_companies
|
||||
</div>
|
||||
<div class="code-name">.ops-cmdb-data_companies
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-threshold_value"></span>
|
||||
<div class="name">
|
||||
monitor-threshold_value
|
||||
</div>
|
||||
<div class="code-name">.monitor-threshold_value
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-disposition"></span>
|
||||
<div class="name">
|
||||
monitor-disposition
|
||||
</div>
|
||||
<div class="code-name">.monitor-disposition
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-automatic_discovery"></span>
|
||||
<div class="name">
|
||||
monitor-automatic_discovery
|
||||
</div>
|
||||
<div class="code-name">.monitor-automatic_discovery
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-grouping_list"></span>
|
||||
<div class="name">
|
||||
monitor-grouping_list
|
||||
</div>
|
||||
<div class="code-name">.monitor-grouping_list
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-node_list"></span>
|
||||
<div class="name">
|
||||
monitor-node_list
|
||||
</div>
|
||||
<div class="code-name">.monitor-node_list
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-general_view"></span>
|
||||
<div class="name">
|
||||
monitor-general_view
|
||||
</div>
|
||||
<div class="code-name">.monitor-general_view
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-network_topology"></span>
|
||||
<div class="name">
|
||||
monitor-network_topology
|
||||
</div>
|
||||
<div class="code-name">.monitor-network_topology
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-node_management"></span>
|
||||
<div class="name">
|
||||
monitor-node_management
|
||||
</div>
|
||||
<div class="code-name">.monitor-node_management
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-alarm_policy"></span>
|
||||
<div class="name">
|
||||
monitor-alarm_policy
|
||||
</div>
|
||||
<div class="code-name">.monitor-alarm_policy
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-alarm"></span>
|
||||
<div class="name">
|
||||
monitor-alarm
|
||||
</div>
|
||||
<div class="code-name">.monitor-alarm
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-healing"></span>
|
||||
<div class="name">
|
||||
monitor-healing
|
||||
</div>
|
||||
<div class="code-name">.monitor-healing
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-data_acquisition"></span>
|
||||
<div class="name">
|
||||
monitor-data_acquisition
|
||||
</div>
|
||||
<div class="code-name">.monitor-data_acquisition
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-analysis"></span>
|
||||
<div class="name">
|
||||
monitor-analysis
|
||||
</div>
|
||||
<div class="code-name">.monitor-analysis
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-index"></span>
|
||||
<div class="name">
|
||||
monitor-index
|
||||
</div>
|
||||
<div class="code-name">.monitor-index
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-user_defined"></span>
|
||||
<div class="name">
|
||||
monitor-user_defined
|
||||
</div>
|
||||
<div class="code-name">.monitor-user_defined
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-database"></span>
|
||||
<div class="name">
|
||||
monitor-database
|
||||
</div>
|
||||
<div class="code-name">.monitor-database
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-common"></span>
|
||||
<div class="name">
|
||||
monitor-common
|
||||
</div>
|
||||
<div class="code-name">.monitor-common
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont veops-edit"></span>
|
||||
<div class="name">
|
||||
veops-edit
|
||||
</div>
|
||||
<div class="code-name">.veops-edit
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont veops-empower"></span>
|
||||
<div class="name">
|
||||
veops-empower
|
||||
</div>
|
||||
<div class="code-name">.veops-empower
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont veops-share"></span>
|
||||
<div class="name">
|
||||
veops-share
|
||||
</div>
|
||||
<div class="code-name">.veops-share
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont veops-export"></span>
|
||||
<div class="name">
|
||||
veops-export
|
||||
</div>
|
||||
<div class="code-name">.veops-export
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont a-veops-import1"></span>
|
||||
<div class="name">
|
||||
veops-import
|
||||
</div>
|
||||
<div class="code-name">.a-veops-import1
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-ip"></span>
|
||||
<div class="name">
|
||||
monitor-ip (1)
|
||||
</div>
|
||||
<div class="code-name">.monitor-ip
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-director"></span>
|
||||
<div class="name">
|
||||
monitor-director
|
||||
</div>
|
||||
<div class="code-name">.monitor-director
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-host"></span>
|
||||
<div class="name">
|
||||
monitor-host
|
||||
</div>
|
||||
<div class="code-name">.monitor-host
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont a-cmdb-log1"></span>
|
||||
<div class="name">
|
||||
cmdb-log
|
||||
</div>
|
||||
<div class="code-name">.a-cmdb-log1
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-add"></span>
|
||||
<div class="name">
|
||||
monitor-add
|
||||
</div>
|
||||
<div class="code-name">.monitor-add
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-down"></span>
|
||||
<div class="name">
|
||||
monitor-down
|
||||
</div>
|
||||
<div class="code-name">.monitor-down
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-up"></span>
|
||||
<div class="name">
|
||||
monitor-up
|
||||
</div>
|
||||
<div class="code-name">.monitor-up
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont itsm-unfold"></span>
|
||||
<div class="name">
|
||||
itsm-unfold
|
||||
</div>
|
||||
<div class="code-name">.itsm-unfold
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont itsm-stretch"></span>
|
||||
<div class="name">
|
||||
itsm-shrink
|
||||
</div>
|
||||
<div class="code-name">.itsm-stretch
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-data_comaparison2"></span>
|
||||
<div class="name">
|
||||
monitor-data_comaparison2
|
||||
</div>
|
||||
<div class="code-name">.monitor-data_comaparison2
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont monitor-data_comaparison1"></span>
|
||||
<div class="name">
|
||||
monitor-data_comaparison1
|
||||
</div>
|
||||
<div class="code-name">.monitor-data_comaparison1
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont a-monitor-online1"></span>
|
||||
<div class="name">
|
||||
monitor-online
|
||||
</div>
|
||||
<div class="code-name">.a-monitor-online1
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont ops-setting-application-selected"></span>
|
||||
<div class="name">
|
||||
@@ -11497,6 +12112,334 @@
|
||||
<div class="content symbol">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#ops-topology_view"></use>
|
||||
</svg>
|
||||
<div class="name">ops-topology_view</div>
|
||||
<div class="code-name">#ops-topology_view</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-host_analysis"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-host_analysis</div>
|
||||
<div class="code-name">#monitor-host_analysis</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#a-Group427319324"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-add2</div>
|
||||
<div class="code-name">#a-Group427319324</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-native"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-native</div>
|
||||
<div class="code-name">#monitor-native</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#veops-filter2"></use>
|
||||
</svg>
|
||||
<div class="name">veops-filter2</div>
|
||||
<div class="code-name">#veops-filter2</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#ops-cmdb-data_companies-selected"></use>
|
||||
</svg>
|
||||
<div class="name">ops-cmdb-data_companies-selected</div>
|
||||
<div class="code-name">#ops-cmdb-data_companies-selected</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#ops-cmdb-data_companies"></use>
|
||||
</svg>
|
||||
<div class="name">ops-cmdb-data_companies</div>
|
||||
<div class="code-name">#ops-cmdb-data_companies</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-threshold_value"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-threshold_value</div>
|
||||
<div class="code-name">#monitor-threshold_value</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-disposition"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-disposition</div>
|
||||
<div class="code-name">#monitor-disposition</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-automatic_discovery"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-automatic_discovery</div>
|
||||
<div class="code-name">#monitor-automatic_discovery</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-grouping_list"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-grouping_list</div>
|
||||
<div class="code-name">#monitor-grouping_list</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-node_list"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-node_list</div>
|
||||
<div class="code-name">#monitor-node_list</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-general_view"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-general_view</div>
|
||||
<div class="code-name">#monitor-general_view</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-network_topology"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-network_topology</div>
|
||||
<div class="code-name">#monitor-network_topology</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-node_management"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-node_management</div>
|
||||
<div class="code-name">#monitor-node_management</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-alarm_policy"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-alarm_policy</div>
|
||||
<div class="code-name">#monitor-alarm_policy</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-alarm"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-alarm</div>
|
||||
<div class="code-name">#monitor-alarm</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-healing"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-healing</div>
|
||||
<div class="code-name">#monitor-healing</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-data_acquisition"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-data_acquisition</div>
|
||||
<div class="code-name">#monitor-data_acquisition</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-analysis"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-analysis</div>
|
||||
<div class="code-name">#monitor-analysis</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-index"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-index</div>
|
||||
<div class="code-name">#monitor-index</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-user_defined"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-user_defined</div>
|
||||
<div class="code-name">#monitor-user_defined</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-database"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-database</div>
|
||||
<div class="code-name">#monitor-database</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-common"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-common</div>
|
||||
<div class="code-name">#monitor-common</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#veops-edit"></use>
|
||||
</svg>
|
||||
<div class="name">veops-edit</div>
|
||||
<div class="code-name">#veops-edit</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#veops-empower"></use>
|
||||
</svg>
|
||||
<div class="name">veops-empower</div>
|
||||
<div class="code-name">#veops-empower</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#veops-share"></use>
|
||||
</svg>
|
||||
<div class="name">veops-share</div>
|
||||
<div class="code-name">#veops-share</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#veops-export"></use>
|
||||
</svg>
|
||||
<div class="name">veops-export</div>
|
||||
<div class="code-name">#veops-export</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#a-veops-import1"></use>
|
||||
</svg>
|
||||
<div class="name">veops-import</div>
|
||||
<div class="code-name">#a-veops-import1</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-ip"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-ip (1)</div>
|
||||
<div class="code-name">#monitor-ip</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-director"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-director</div>
|
||||
<div class="code-name">#monitor-director</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-host"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-host</div>
|
||||
<div class="code-name">#monitor-host</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#a-cmdb-log1"></use>
|
||||
</svg>
|
||||
<div class="name">cmdb-log</div>
|
||||
<div class="code-name">#a-cmdb-log1</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-add"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-add</div>
|
||||
<div class="code-name">#monitor-add</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-down"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-down</div>
|
||||
<div class="code-name">#monitor-down</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-up"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-up</div>
|
||||
<div class="code-name">#monitor-up</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#itsm-unfold"></use>
|
||||
</svg>
|
||||
<div class="name">itsm-unfold</div>
|
||||
<div class="code-name">#itsm-unfold</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#itsm-stretch"></use>
|
||||
</svg>
|
||||
<div class="name">itsm-shrink</div>
|
||||
<div class="code-name">#itsm-stretch</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-data_comaparison2"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-data_comaparison2</div>
|
||||
<div class="code-name">#monitor-data_comaparison2</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#monitor-data_comaparison1"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-data_comaparison1</div>
|
||||
<div class="code-name">#monitor-data_comaparison1</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#a-monitor-online1"></use>
|
||||
</svg>
|
||||
<div class="name">monitor-online</div>
|
||||
<div class="code-name">#a-monitor-online1</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#ops-setting-application-selected"></use>
|
||||
|
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3857903 */
|
||||
src: url('iconfont.woff2?t=1713840593232') format('woff2'),
|
||||
url('iconfont.woff?t=1713840593232') format('woff'),
|
||||
url('iconfont.ttf?t=1713840593232') 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,170 @@
|
||||
-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";
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@@ -5,6 +5,293 @@
|
||||
"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",
|
||||
|
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 {
|
||||
|
@@ -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"
|
||||
|
@@ -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')
|
||||
|
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
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
|
||||
})
|
||||
}
|
@@ -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(() => {
|
||||
|
@@ -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
|
||||
|
@@ -198,6 +198,9 @@ export default {
|
||||
if (this.type === 'resourceSearch') {
|
||||
this.getCITypeGroups()
|
||||
}
|
||||
if (this.typeId) {
|
||||
this.currenCiType = [this.typeId]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// toggleAdvanced() {
|
||||
|
@@ -4,6 +4,8 @@ const cmdb_en = {
|
||||
configTable: 'Config Table',
|
||||
menu: {
|
||||
views: 'Views',
|
||||
topologyView: 'Topology Views',
|
||||
resources: 'Resources',
|
||||
config: 'Configuration',
|
||||
backend: 'Management',
|
||||
ciTable: 'Resource Views',
|
||||
@@ -69,9 +71,9 @@ const cmdb_en = {
|
||||
adAutoInLib: 'Save as CI auto',
|
||||
adInterval: 'Collection frequency',
|
||||
byInterval: 'by interval',
|
||||
allNodes: 'All nodes',
|
||||
specifyNodes: 'Specify Node',
|
||||
specifyNodesTips: 'Please fill in the specify node!',
|
||||
allNodes: 'All Instances',
|
||||
specifyNodes: 'Specify Instance',
|
||||
specifyNodesTips: 'Please fill in the specify instance!',
|
||||
username: 'Username',
|
||||
password: 'Password',
|
||||
link: 'Link',
|
||||
@@ -144,7 +146,7 @@ const cmdb_en = {
|
||||
triggerDataChange: 'Data changes',
|
||||
triggerDate: 'Date attribute',
|
||||
triggerEnable: 'Turn on',
|
||||
descInput: 'Please enter remarks',
|
||||
descInput: 'Please enter descriptions',
|
||||
triggerCondition: 'Triggering conditions',
|
||||
addInstance: 'Add new instance',
|
||||
deleteInstance: 'Delete instance',
|
||||
@@ -562,6 +564,30 @@ if __name__ == "__main__":
|
||||
tree: {
|
||||
tips1: 'Please go to Preference page first to complete your subscription!',
|
||||
subSettings: 'Settings',
|
||||
}
|
||||
},
|
||||
topo: {
|
||||
addTopoView: 'Add Topology View',
|
||||
editTopoView: 'Edit Topology View',
|
||||
addTopoViewInGroup: 'Define topology view under grouping',
|
||||
groupRequired: 'Please select a group first or create a group',
|
||||
viewName: 'Title',
|
||||
viewNamePlaceholder: 'Please enter a title for the topology view',
|
||||
inputNameTips: 'Title is required',
|
||||
centralNodeType: 'Central Node Model',
|
||||
filterInstances: 'Central Node Instances',
|
||||
typeRequired: 'Central Node Model is required',
|
||||
instancesRequired: 'instances are required',
|
||||
path: 'Path',
|
||||
aggregationCount: 'Aggregation Count',
|
||||
aggreationCountTip: 'When the number of child nodes > the number of aggregations, paging display',
|
||||
preview: 'Preivew',
|
||||
noData: 'No data',
|
||||
edit: 'Edit',
|
||||
delete: 'Delete',
|
||||
searchPlaceholder: 'Search topology view',
|
||||
confirmDeleteView: 'Are you sure you want to delete this view ?',
|
||||
noInstancePerm: 'You do not have read permissions for this instance',
|
||||
noPreferenceAttributes: 'This instance has no subscription attributes or no default displayed attributes',
|
||||
},
|
||||
}
|
||||
export default cmdb_en
|
||||
|
@@ -4,8 +4,10 @@ const cmdb_zh = {
|
||||
configTable: '配置表格',
|
||||
menu: {
|
||||
views: '视图',
|
||||
topologyView: '拓扑视图',
|
||||
resources: '资源',
|
||||
config: '配置',
|
||||
backend: '管理端',
|
||||
backend: '管理',
|
||||
ciTable: '资源数据',
|
||||
ciTree: '资源层级',
|
||||
ciSearch: '资源搜索',
|
||||
@@ -69,9 +71,9 @@ const cmdb_zh = {
|
||||
adAutoInLib: '自动入库',
|
||||
adInterval: '采集频率',
|
||||
byInterval: '按间隔',
|
||||
allNodes: '所有节点',
|
||||
specifyNodes: '指定节点',
|
||||
specifyNodesTips: '请填写指定节点!',
|
||||
allNodes: '所有实例',
|
||||
specifyNodes: '指定实例',
|
||||
specifyNodesTips: '请填写指定实例!',
|
||||
username: '用户名',
|
||||
password: '密码',
|
||||
link: '链接',
|
||||
@@ -144,7 +146,7 @@ const cmdb_zh = {
|
||||
triggerDataChange: '数据变更',
|
||||
triggerDate: '日期属性',
|
||||
triggerEnable: '开启',
|
||||
descInput: '请输入备注',
|
||||
descInput: '请输入描述',
|
||||
triggerCondition: '触发条件',
|
||||
addInstance: '新增实例',
|
||||
deleteInstance: '删除实例',
|
||||
@@ -562,6 +564,30 @@ if __name__ == "__main__":
|
||||
tree: {
|
||||
tips1: '请先到 我的订阅 页面完成订阅!',
|
||||
subSettings: '订阅设置',
|
||||
}
|
||||
},
|
||||
topo: {
|
||||
addTopoView: '新增拓扑视图',
|
||||
editTopoView: '编辑拓扑视图',
|
||||
addTopoViewInGroup: '在分组下定义拓扑视图',
|
||||
groupRequired: '请先选择分组或者创建分组',
|
||||
viewName: '标题',
|
||||
viewNamePlaceholder: '请输入拓扑视图的标题',
|
||||
inputNameTips: '必须输入标题',
|
||||
centralNodeType: '中心节点模型',
|
||||
filterInstances: '中心节点实例',
|
||||
typeRequired: '必须要选择中心节点模型',
|
||||
instancesRequired: '实例必须要选择',
|
||||
path: '路径选择',
|
||||
aggregationCount: '聚合数',
|
||||
aggreationCountTip: '当子节点数 > 聚合数 则进行分页展示',
|
||||
preview: '预览',
|
||||
noData: '没有数据',
|
||||
edit: '编辑',
|
||||
delete: '删除',
|
||||
searchPlaceholder: '搜索拓扑视图',
|
||||
confirmDeleteView: '您确定要删除该视图吗?',
|
||||
noInstancePerm: '您没有该实例的查看权限',
|
||||
noPreferenceAttributes: '该实例没有订阅属性或者没有默认展示的属性',
|
||||
},
|
||||
}
|
||||
export default cmdb_zh
|
||||
|
@@ -16,10 +16,16 @@ const genCmdbRoutes = async () => {
|
||||
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',
|
||||
@@ -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'
|
||||
}
|
||||
@@ -169,7 +184,7 @@ const genCmdbRoutes = async () => {
|
||||
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
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -112,7 +112,7 @@
|
||||
<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%' }">
|
||||
<related-itsm-table :ci_id="ci._id" :ciHistory="ciHistory" :itsmInstalled="itsmInstalled" />
|
||||
<RelatedItsmTable ref="relatedITSMTable" :ci_id="ci._id" :ciHistory="ciHistory" :itsmInstalled="itsmInstalled" :attrList="attrList" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
@@ -140,8 +140,6 @@ import CiDetailRelation from './ciDetailRelation.vue'
|
||||
import TriggerTable from '../../operation_history/modules/triggerTable.vue'
|
||||
import RelatedItsmTable from './ciDetailRelatedItsmTable.vue'
|
||||
import CiRollbackForm from './ciRollbackForm.vue'
|
||||
import { sleep } from '@/utils/util'
|
||||
|
||||
export default {
|
||||
name: 'CiDetailTab',
|
||||
components: {
|
||||
|
@@ -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">
|
||||
@@ -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;
|
||||
|
@@ -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;
|
||||
|
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
@@ -1,21 +1,19 @@
|
||||
version: '3.5'
|
||||
version: '2.19'
|
||||
|
||||
services:
|
||||
cmdb-db:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-db:2.3
|
||||
container_name: cmdb-db
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
MYSQL_ROOT_PASSWORD: '123456'
|
||||
MYSQL_DATABASE: 'cmdb'
|
||||
MYSQL_USER: 'cmdb'
|
||||
MYSQL_PASSWORD: '123456'
|
||||
volumes:
|
||||
- db-data:/var/lib/mysql
|
||||
- ./docs/mysqld.cnf:/etc/mysql/conf.d/mysqld.cnf
|
||||
- ./docs/cmdb.sql:/docker-entrypoint-initdb.d/cmdb.sql
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
|
||||
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost", "-p$MYSQL_ROOT_PASSWORD"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
@@ -45,11 +43,10 @@ services:
|
||||
- redis
|
||||
|
||||
cmdb-api:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.4.4
|
||||
# build:
|
||||
# context: .
|
||||
# target: cmdb-api
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.4.5
|
||||
container_name: cmdb-api
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
WAIT_HOSTS: cmdb-db:3306, cmdb-cache:6379
|
||||
@@ -77,17 +74,13 @@ services:
|
||||
flask init-import-user-from-acl
|
||||
flask init-department
|
||||
flask cmdb-counter > counter.log 2>&1
|
||||
|
||||
networks:
|
||||
new:
|
||||
aliases:
|
||||
- cmdb-api
|
||||
|
||||
cmdb-ui:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.4.4
|
||||
# build:
|
||||
# context: .
|
||||
# target: cmdb-ui
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.4.5
|
||||
container_name: cmdb-ui
|
||||
depends_on:
|
||||
- cmdb-api
|
||||
|
@@ -492,7 +492,7 @@ CREATE TABLE `acl_role_relations` (
|
||||
|
||||
LOCK TABLES `acl_role_relations` WRITE;
|
||||
/*!40000 ALTER TABLE `acl_role_relations` DISABLE KEYS */;
|
||||
INSERT INTO `acl_role_relations` VALUES (NULL,0,NULL,NULL,1,1,2,NULL);
|
||||
INSERT INTO `acl_role_relations` VALUES (NULL,0,NULL,NULL,1,1,2,NULL),(NULL,0,NULL,NULL,2,3,2,2);
|
||||
/*!40000 ALTER TABLE `acl_role_relations` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
|
@@ -5,14 +5,31 @@
|
||||
|
||||
## Install
|
||||
|
||||
- 启动 mysql 服务, redis 服务
|
||||
> mysql一定要设置sql_mode, root进入mysql执行:
|
||||
>
|
||||
> `set global sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';`
|
||||
>
|
||||
> `set session sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';`
|
||||
- 启动 mysql 服务, redis 服务,此处以 docker 为例
|
||||
|
||||
```bash
|
||||
mkdir ~/cmdb_db # 用于持久化存储mysql数据
|
||||
docker run -d -p 3306:3306 --name mysql-cmdb -e MYSQL_ROOT_PASSWORD=Root_321 -v ~/cmdb_db:/var/lib/mysql mysql
|
||||
docker run -d --name redis -p 6379:6379 redis
|
||||
```
|
||||
|
||||
- mysql需要先设置sql_mode, 进入容器,使用root账号,进入mysql执行:
|
||||
```bash
|
||||
docker exec -it mysql-cmdb bash
|
||||
mysql -uroot -pRoot_321
|
||||
```
|
||||
|
||||
```sql
|
||||
`set global sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';`
|
||||
`set session sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';`
|
||||
```
|
||||
|
||||
- 创建数据库 cmdb
|
||||
|
||||
```sql
|
||||
create database cmdb;
|
||||
```
|
||||
|
||||
- 拉取代码
|
||||
|
||||
```bash
|
||||
@@ -26,7 +43,8 @@ cp cmdb-api/settings.example.py cmdb-api/settings.py
|
||||
- 安装库
|
||||
- 后端: `cd cmdb-api && pipenv run pipenv install && cd ..`
|
||||
- 前端: `cd cmdb-ui && yarn install && cd ..`
|
||||
- 可以将 docs/cmdb.sql 导入到数据库里,登录用户和密码分别是:demo/123456
|
||||
- node推荐使用14.x版本,推荐使用nvm进行nodejs版本管理:`nvm install 14 && nvm use 14`
|
||||
- 可以将 docs/cmdb.sql 导入到数据库里,登录用户和密码分别是:demo/123456
|
||||
- 创建数据库表: 进入**cmdb-api**目录执行 `pipenv run flask db-setup && pipenv run flask common-check-new-columns && pipenv run flask cmdb-init-cache`
|
||||
- 启动服务
|
||||
|
||||
|
Reference in New Issue
Block a user