Compare commits
37 Commits
2.4.1
...
fix_issue#
Author | SHA1 | Date | |
---|---|---|---|
|
c96c4ffd54 | ||
|
ef25c94b5d | ||
|
06ae1bcf13 | ||
|
9ead4e7d8d | ||
|
994a28dd25 | ||
|
74b587e46c | ||
|
091cd882bd | ||
|
73093db467 | ||
|
66e268ce68 | ||
|
a41d1a5e97 | ||
|
b4b728fe28 | ||
|
d16462d8b7 | ||
|
de7d98c0b4 | ||
|
51332c7236 | ||
|
bf1076fe4a | ||
|
3454a98cfb | ||
|
506dcbb40e | ||
|
5ac4517187 | ||
|
761e98884b | ||
|
073654624e | ||
|
df54244ff1 | ||
|
27e9919198 | ||
|
dc8b1a5de2 | ||
|
d8a7728f1d | ||
|
82881965fb | ||
|
1bb62022f1 | ||
|
ed445a8d82 | ||
|
3626b1a97e | ||
|
32529fba9b | ||
|
a042b4fe39 | ||
|
a0631414dc | ||
|
5266cb5b88 | ||
|
c7d4bec988 | ||
|
099ddd6ca9 | ||
|
bd813174b1 | ||
|
0a43680d6e | ||
|
976c6cfe91 |
@@ -87,7 +87,7 @@ docker compose up -d
|
||||
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
|
||||
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
|
||||
```shell
|
||||
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/master/install.sh
|
||||
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/deploy_on_kylin_docker/install.sh
|
||||
sh install.sh install
|
||||
```
|
||||
|
||||
|
@@ -55,9 +55,12 @@ def cmdb_init_cache():
|
||||
for cr in ci_relations:
|
||||
relations.setdefault(cr.first_ci_id, {}).update({cr.second_ci_id: cr.second_ci.type_id})
|
||||
if cr.ancestor_ids:
|
||||
relations2.setdefault(cr.ancestor_ids, {}).update({cr.second_ci_id: cr.second_ci.type_id})
|
||||
relations2.setdefault('{},{}'.format(cr.ancestor_ids, cr.first_ci_id), {}).update(
|
||||
{cr.second_ci_id: cr.second_ci.type_id})
|
||||
for i in relations:
|
||||
relations[i] = json.dumps(relations[i])
|
||||
for i in relations2:
|
||||
relations2[i] = json.dumps(relations2[i])
|
||||
if relations:
|
||||
rd.create_or_update(relations, REDIS_PREFIX_CI_RELATION)
|
||||
if relations2:
|
||||
@@ -357,13 +360,13 @@ def cmdb_inner_secrets_unseal(address):
|
||||
"""
|
||||
unseal the secrets feature
|
||||
"""
|
||||
if not valid_address(address):
|
||||
return
|
||||
# if not valid_address(address):
|
||||
# return
|
||||
address = "{}/api/v0.1/secrets/unseal".format(address.strip("/"))
|
||||
for i in range(global_key_threshold):
|
||||
token = click.prompt(f'Enter unseal token {i + 1}', hide_input=True, confirmation_prompt=False)
|
||||
assert token is not None
|
||||
resp = requests.post(address, headers={"Unseal-Token": token})
|
||||
resp = requests.post(address, headers={"Unseal-Token": token}, timeout=5)
|
||||
if resp.status_code == 200:
|
||||
KeyManage.print_response(resp.json())
|
||||
if resp.json().get("status") in ["success", "skip"]:
|
||||
|
@@ -6,6 +6,7 @@ from werkzeug.datastructures import MultiDict
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.common_setting.employee import EmployeeAddForm, GrantEmployeeACLPerm
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.lib.common_setting.utils import CheckNewColumn
|
||||
from api.models.common_setting import Employee, Department
|
||||
|
||||
|
||||
@@ -209,57 +210,7 @@ def common_check_new_columns():
|
||||
"""
|
||||
add new columns to tables
|
||||
"""
|
||||
from api.extensions import db
|
||||
from sqlalchemy import inspect, text
|
||||
|
||||
def get_model_by_table_name(_table_name):
|
||||
registry = getattr(db.Model, 'registry', None)
|
||||
class_registry = getattr(registry, '_class_registry', None)
|
||||
for _model in class_registry.values():
|
||||
if hasattr(_model, '__tablename__') and _model.__tablename__ == _table_name:
|
||||
return _model
|
||||
return None
|
||||
|
||||
def add_new_column(target_table_name, new_column):
|
||||
column_type = new_column.type.compile(engine.dialect)
|
||||
default_value = new_column.default.arg if new_column.default else None
|
||||
|
||||
sql = "ALTER TABLE " + target_table_name + " ADD COLUMN " + f"`{new_column.name}`" + " " + column_type
|
||||
if new_column.comment:
|
||||
sql += f" comment '{new_column.comment}'"
|
||||
|
||||
if column_type == 'JSON':
|
||||
pass
|
||||
elif default_value:
|
||||
if column_type.startswith('VAR') or column_type.startswith('Text'):
|
||||
if default_value is None or len(default_value) == 0:
|
||||
pass
|
||||
else:
|
||||
sql += f" DEFAULT {default_value}"
|
||||
|
||||
sql = text(sql)
|
||||
db.session.execute(sql)
|
||||
|
||||
engine = db.get_engine()
|
||||
inspector = inspect(engine)
|
||||
table_names = inspector.get_table_names()
|
||||
for table_name in table_names:
|
||||
existed_columns = inspector.get_columns(table_name)
|
||||
existed_column_name_list = [c['name'] for c in existed_columns]
|
||||
|
||||
model = get_model_by_table_name(table_name)
|
||||
if model is None:
|
||||
continue
|
||||
|
||||
model_columns = getattr(getattr(getattr(model, '__table__'), 'columns'), '_all_columns')
|
||||
for column in model_columns:
|
||||
if column.name not in existed_column_name_list:
|
||||
try:
|
||||
add_new_column(table_name, column)
|
||||
current_app.logger.info(f"add new column [{column.name}] in table [{table_name}] success.")
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"add new column [{column.name}] in table [{table_name}] err:")
|
||||
current_app.logger.error(e)
|
||||
CheckNewColumn().run()
|
||||
|
||||
|
||||
@click.command()
|
||||
|
@@ -263,10 +263,11 @@ class CIManager(object):
|
||||
ci_ids = None
|
||||
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:
|
||||
@@ -295,6 +296,7 @@ class CIManager(object):
|
||||
_no_attribute_policy=ExistPolicy.IGNORE,
|
||||
is_auto_discovery=False,
|
||||
_is_admin=False,
|
||||
ticket_id=None,
|
||||
**ci_dict):
|
||||
"""
|
||||
add ci
|
||||
@@ -303,6 +305,7 @@ class CIManager(object):
|
||||
:param _no_attribute_policy: ignore or reject
|
||||
:param is_auto_discovery: default is False
|
||||
:param _is_admin: default is False
|
||||
:param ticket_id:
|
||||
:param ci_dict:
|
||||
:return:
|
||||
"""
|
||||
@@ -417,7 +420,7 @@ class CIManager(object):
|
||||
operate_type = OperateType.UPDATE if ci is not None else OperateType.ADD
|
||||
try:
|
||||
ci = ci or CI.create(type_id=ci_type.id, is_auto_discovery=is_auto_discovery)
|
||||
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr)
|
||||
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr, ticket_id=ticket_id)
|
||||
except BadRequest as e:
|
||||
if existed is None:
|
||||
cls.delete(ci.id)
|
||||
@@ -435,14 +438,17 @@ class CIManager(object):
|
||||
|
||||
return ci.id
|
||||
|
||||
def update(self, ci_id, _is_admin=False, **ci_dict):
|
||||
def update(self, ci_id, _is_admin=False, ticket_id=None, **ci_dict):
|
||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
ci = self.confirm_ci_existed(ci_id)
|
||||
|
||||
raw_dict = copy.deepcopy(ci_dict)
|
||||
|
||||
attrs = CITypeAttributeManager.get_all_attributes(ci.type_id)
|
||||
ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
|
||||
ci_type_attrs_alias2name = {attr.alias: attr.name for _, attr in attrs}
|
||||
ci_dict = {ci_type_attrs_alias2name[k] if k in ci_type_attrs_alias2name else k: v for k, v in ci_dict.items()}
|
||||
|
||||
raw_dict = copy.deepcopy(ci_dict)
|
||||
|
||||
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
||||
for _, attr in attrs:
|
||||
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
@@ -487,7 +493,7 @@ class CIManager(object):
|
||||
ci_dict.pop(k)
|
||||
|
||||
try:
|
||||
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr)
|
||||
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr, ticket_id=ticket_id)
|
||||
except BadRequest as e:
|
||||
raise e
|
||||
|
||||
@@ -823,6 +829,105 @@ class CIManager(object):
|
||||
|
||||
return data.get('v')
|
||||
|
||||
def baseline(self, ci_ids, before_date):
|
||||
ci_list = self.get_cis_by_ids(ci_ids, ret_key=RetKey.ALIAS)
|
||||
if not ci_list:
|
||||
return dict()
|
||||
|
||||
ci2changed = dict()
|
||||
changed = AttributeHistoryManger.get_records_for_attributes(
|
||||
before_date, None, None, 1, 100000, None, None, ci_ids=ci_ids, more=True)[1]
|
||||
for records in changed:
|
||||
for change in records[1]:
|
||||
if change['is_computed'] or change['is_password']:
|
||||
continue
|
||||
|
||||
if change.get('default') and change['default'].get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
continue
|
||||
|
||||
ci2changed.setdefault(change['ci_id'], {})
|
||||
item = (change['old'],
|
||||
change['new'],
|
||||
change.get('is_list'),
|
||||
change.get('value_type'),
|
||||
change['operate_type'])
|
||||
if change.get('is_list'):
|
||||
ci2changed[change['ci_id']].setdefault(change.get('attr_alias'), []).append(item)
|
||||
else:
|
||||
ci2changed[change['ci_id']].update({change.get('attr_alias'): item})
|
||||
|
||||
type2show_name = {}
|
||||
result = []
|
||||
for ci in ci_list:
|
||||
list_attr2item = {}
|
||||
for alias_name, v in (ci2changed.get(ci['_id']) or {}).items():
|
||||
if not alias_name:
|
||||
continue
|
||||
if alias_name == ci.get('unique_alias'):
|
||||
continue
|
||||
|
||||
if ci.get('_type') not in type2show_name:
|
||||
ci_type = CITypeCache.get(ci.get('_type'))
|
||||
show_id = ci_type.show_id or ci_type.unique_id
|
||||
type2show_name[ci['_type']] = AttributeCache.get(show_id).alias
|
||||
|
||||
if isinstance(v, list):
|
||||
for old, new, is_list, value_type, operate_type in v:
|
||||
if alias_name not in list_attr2item:
|
||||
list_attr2item[alias_name] = dict(instance=ci.get(type2show_name[ci['_type']]),
|
||||
attr_name=alias_name,
|
||||
value_type=value_type,
|
||||
is_list=is_list,
|
||||
ci_type=ci.get('ci_type'),
|
||||
unique_alias=ci.get('unique_alias'),
|
||||
unique_value=ci.get(ci['unique_alias']),
|
||||
cur=copy.deepcopy(ci.get(alias_name)),
|
||||
to=ci.get(alias_name) or [])
|
||||
|
||||
old = ValueTypeMap.deserialize[value_type](old) if old else old
|
||||
new = ValueTypeMap.deserialize[value_type](new) if new else new
|
||||
if operate_type == OperateType.ADD:
|
||||
list_attr2item[alias_name]['to'].remove(new)
|
||||
elif operate_type == OperateType.DELETE and old not in list_attr2item[alias_name]['to']:
|
||||
list_attr2item[alias_name]['to'].append(old)
|
||||
continue
|
||||
|
||||
old, value_type = v[0], v[3]
|
||||
old = ValueTypeMap.deserialize[value_type](old) if old else old
|
||||
if isinstance(old, (datetime.datetime, datetime.date)):
|
||||
old = str(old)
|
||||
if ci.get(alias_name) != old:
|
||||
item = dict(instance=ci.get(type2show_name[ci['_type']]),
|
||||
attr_name=alias_name,
|
||||
value_type=value_type,
|
||||
ci_type=ci.get('ci_type'),
|
||||
unique_alias=ci.get('unique_alias'),
|
||||
unique_value=ci.get(ci['unique_alias']),
|
||||
cur=ci.get(alias_name),
|
||||
to=old)
|
||||
result.append(item)
|
||||
|
||||
for alias_name, item in list_attr2item.items():
|
||||
if sorted(item['cur'] or []) != sorted(item['to'] or []):
|
||||
result.append(item)
|
||||
|
||||
return result
|
||||
|
||||
def rollback(self, ci_id, before_date):
|
||||
baseline_ci = self.baseline([ci_id], before_date)
|
||||
|
||||
payload = dict()
|
||||
for item in baseline_ci:
|
||||
payload[item.get('attr_name')] = item.get('to')
|
||||
|
||||
if payload:
|
||||
payload['ci_type'] = baseline_ci[0]['ci_type']
|
||||
payload[baseline_ci[0]['unique_alias']] = baseline_ci[0]['unique_value']
|
||||
|
||||
self.update(ci_id, **payload)
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
class CIRelationManager(object):
|
||||
"""
|
||||
@@ -958,7 +1063,7 @@ class CIRelationManager(object):
|
||||
return abort(400, ErrFormat.relation_constraint.format("1-N"))
|
||||
|
||||
@classmethod
|
||||
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, ancestor_ids=None):
|
||||
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, ancestor_ids=None, valid=True):
|
||||
|
||||
first_ci = CIManager.confirm_ci_existed(first_ci_id)
|
||||
second_ci = CIManager.confirm_ci_existed(second_ci_id)
|
||||
@@ -983,7 +1088,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'):
|
||||
if current_app.config.get('USE_ACL') and valid:
|
||||
resource_name = CITypeRelationManager.acl_resource_name(first_ci.ci_type.name,
|
||||
second_ci.ci_type.name)
|
||||
if not ACLManager().has_permission(
|
||||
@@ -1086,6 +1191,57 @@ class CIRelationManager(object):
|
||||
for ci_id in ci_ids:
|
||||
cls.delete_2(parent_id, ci_id, ancestor_ids=ancestor_ids)
|
||||
|
||||
@classmethod
|
||||
def build_by_attribute(cls, ci_dict):
|
||||
type_id = ci_dict['_type']
|
||||
child_items = CITypeRelation.get_by(parent_id=type_id, only_query=True).filter(
|
||||
CITypeRelation.parent_attr_id.isnot(None))
|
||||
for item in child_items:
|
||||
parent_attr = AttributeCache.get(item.parent_attr_id)
|
||||
child_attr = AttributeCache.get(item.child_attr_id)
|
||||
attr_value = ci_dict.get(parent_attr.name)
|
||||
value_table = TableMap(attr=child_attr).table
|
||||
for child in value_table.get_by(attr_id=child_attr.id, value=attr_value, only_query=True).join(
|
||||
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.child_id):
|
||||
CIRelationManager.add(ci_dict['_id'], child.ci_id, valid=False)
|
||||
|
||||
parent_items = CITypeRelation.get_by(child_id=type_id, only_query=True).filter(
|
||||
CITypeRelation.child_attr_id.isnot(None))
|
||||
for item in parent_items:
|
||||
parent_attr = AttributeCache.get(item.parent_attr_id)
|
||||
child_attr = AttributeCache.get(item.child_attr_id)
|
||||
attr_value = ci_dict.get(child_attr.name)
|
||||
value_table = TableMap(attr=parent_attr).table
|
||||
for parent in value_table.get_by(attr_id=parent_attr.id, value=attr_value, only_query=True).join(
|
||||
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.parent_id):
|
||||
CIRelationManager.add(parent.ci_id, ci_dict['_id'], valid=False)
|
||||
|
||||
@classmethod
|
||||
def rebuild_all_by_attribute(cls, ci_type_relation):
|
||||
parent_attr = AttributeCache.get(ci_type_relation['parent_attr_id'])
|
||||
child_attr = AttributeCache.get(ci_type_relation['child_attr_id'])
|
||||
if not parent_attr or not child_attr:
|
||||
return
|
||||
|
||||
parent_value_table = TableMap(attr=parent_attr).table
|
||||
child_value_table = TableMap(attr=child_attr).table
|
||||
|
||||
parent_values = parent_value_table.get_by(attr_id=parent_attr.id, only_query=True).join(
|
||||
CI, CI.id == parent_value_table.ci_id).filter(CI.type_id == ci_type_relation['parent_id'])
|
||||
child_values = child_value_table.get_by(attr_id=child_attr.id, only_query=True).join(
|
||||
CI, CI.id == child_value_table.ci_id).filter(CI.type_id == ci_type_relation['child_id'])
|
||||
|
||||
child_value2ci_ids = {}
|
||||
for child in child_values:
|
||||
child_value2ci_ids.setdefault(child.value, []).append(child.ci_id)
|
||||
|
||||
for parent in parent_values:
|
||||
for child_ci_id in child_value2ci_ids.get(parent.value, []):
|
||||
try:
|
||||
cls.add(parent.ci_id, child_ci_id, valid=False)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class CITriggerManager(object):
|
||||
@staticmethod
|
||||
|
@@ -92,6 +92,9 @@ class CITypeManager(object):
|
||||
for type_dict in ci_types:
|
||||
attr = AttributeCache.get(type_dict["unique_id"])
|
||||
type_dict["unique_key"] = attr and attr.name
|
||||
if type_dict.get('show_id'):
|
||||
attr = AttributeCache.get(type_dict["show_id"])
|
||||
type_dict["show_name"] = attr and attr.name
|
||||
type_dict['parent_ids'] = CITypeInheritanceManager.get_parents(type_dict['id'])
|
||||
if resources is None or type_dict['name'] in resources:
|
||||
res.append(type_dict)
|
||||
@@ -192,7 +195,7 @@ class CITypeManager(object):
|
||||
CITypeAttributeManager.update(type_id, [attr])
|
||||
|
||||
ci_type2 = ci_type.to_dict()
|
||||
new = ci_type.update(**kwargs)
|
||||
new = ci_type.update(**kwargs, filter_none=False)
|
||||
|
||||
CITypeCache.clean(type_id)
|
||||
|
||||
@@ -250,6 +253,13 @@ class CITypeManager(object):
|
||||
for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
try:
|
||||
from api.models.cmdb import CITypeReconciliation
|
||||
for item in CITypeReconciliation.get_by(type_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
db.session.commit()
|
||||
|
||||
ci_type.soft_delete()
|
||||
@@ -411,9 +421,6 @@ class CITypeGroupManager(object):
|
||||
existed = CITypeGroup.get_by_id(gid) or abort(
|
||||
404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid)))
|
||||
if name is not None and name != existed.name:
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
existed.update(name=name)
|
||||
|
||||
max_order = max([i.order or 0 for i in CITypeGroupItem.get_by(group_id=gid, to_dict=False)] or [0])
|
||||
@@ -672,10 +679,17 @@ class CITypeAttributeManager(object):
|
||||
item = CITypeTrigger.get_by(type_id=_type_id, attr_id=attr_id, to_dict=False, first=True)
|
||||
item and item.soft_delete(commit=False)
|
||||
|
||||
for item in (CITypeRelation.get_by(parent_id=type_id, parent_attr_id=attr_id, to_dict=False) +
|
||||
CITypeRelation.get_by(child_id=type_id, child_attr_id=attr_id, to_dict=False)):
|
||||
item.soft_delete(commit=False)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
CITypeAttributeCache.clean(type_id, attr_id)
|
||||
|
||||
if ci_type.show_id == attr_id:
|
||||
ci_type.update(show_id=None, filter_none=False)
|
||||
|
||||
CITypeHistoryManager.add(CITypeOperateType.DELETE_ATTRIBUTE, type_id, attr_id=attr.id,
|
||||
change=attr and attr.to_dict())
|
||||
|
||||
@@ -730,14 +744,19 @@ class CITypeRelationManager(object):
|
||||
@staticmethod
|
||||
def get():
|
||||
res = CITypeRelation.get_by(to_dict=False)
|
||||
type2attributes = dict()
|
||||
for idx, item in enumerate(res):
|
||||
_item = item.to_dict()
|
||||
res[idx] = _item
|
||||
res[idx]['parent'] = item.parent.to_dict()
|
||||
if item.parent_id not in type2attributes:
|
||||
type2attributes[item.parent_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.parent_id)]
|
||||
res[idx]['child'] = item.child.to_dict()
|
||||
if item.child_id not in type2attributes:
|
||||
type2attributes[item.child_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.child_id)]
|
||||
res[idx]['relation_type'] = item.relation_type.to_dict()
|
||||
|
||||
return res
|
||||
return res, type2attributes
|
||||
|
||||
@staticmethod
|
||||
def get_child_type_ids(type_id, level):
|
||||
@@ -769,6 +788,8 @@ class CITypeRelationManager(object):
|
||||
|
||||
ci_type_dict["relation_type"] = relation_inst.relation_type.name
|
||||
ci_type_dict["constraint"] = relation_inst.constraint
|
||||
ci_type_dict["parent_attr_id"] = relation_inst.parent_attr_id
|
||||
ci_type_dict["child_attr_id"] = relation_inst.child_attr_id
|
||||
|
||||
return ci_type_dict
|
||||
|
||||
@@ -813,7 +834,8 @@ class CITypeRelationManager(object):
|
||||
return "{} -> {}".format(first_name, second_name)
|
||||
|
||||
@classmethod
|
||||
def add(cls, parent, child, relation_type_id, constraint=ConstraintEnum.One2Many):
|
||||
def add(cls, parent, child, relation_type_id, constraint=ConstraintEnum.One2Many,
|
||||
parent_attr_id=None, child_attr_id=None):
|
||||
p = CITypeManager.check_is_existed(parent)
|
||||
c = CITypeManager.check_is_existed(child)
|
||||
|
||||
@@ -828,24 +850,21 @@ class CITypeRelationManager(object):
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.circular_dependency_error)
|
||||
|
||||
# if constraint == ConstraintEnum.Many2Many:
|
||||
# other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
|
||||
# to_dict=False, first=True)
|
||||
# other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
|
||||
# to_dict=False, first=True)
|
||||
# if other_c and other_c.child_id != c.id:
|
||||
# return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
|
||||
# if other_p and other_p.parent_id != p.id:
|
||||
# return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
|
||||
|
||||
old_parent_attr_id = None
|
||||
existed = cls._get(p.id, c.id)
|
||||
if existed is not None:
|
||||
existed.update(relation_type_id=relation_type_id,
|
||||
constraint=constraint)
|
||||
old_parent_attr_id = existed.parent_attr_id
|
||||
existed = existed.update(relation_type_id=relation_type_id,
|
||||
constraint=constraint,
|
||||
parent_attr_id=parent_attr_id,
|
||||
child_attr_id=child_attr_id,
|
||||
filter_none=False)
|
||||
else:
|
||||
existed = CITypeRelation.create(parent_id=p.id,
|
||||
child_id=c.id,
|
||||
relation_type_id=relation_type_id,
|
||||
parent_attr_id=parent_attr_id,
|
||||
child_attr_id=child_attr_id,
|
||||
constraint=constraint)
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
@@ -859,6 +878,11 @@ class CITypeRelationManager(object):
|
||||
current_user.username,
|
||||
ResourceTypeEnum.CI_TYPE_RELATION)
|
||||
|
||||
if parent_attr_id and parent_attr_id != old_parent_attr_id:
|
||||
if parent_attr_id and parent_attr_id != existed.parent_attr_id:
|
||||
from api.tasks.cmdb import rebuild_relation_for_attribute_changed
|
||||
rebuild_relation_for_attribute_changed.apply_async(args=(existed.to_dict()))
|
||||
|
||||
CITypeHistoryManager.add(CITypeOperateType.ADD_RELATION, p.id,
|
||||
change=dict(parent=p.to_dict(), child=c.to_dict(), relation_type_id=relation_type_id))
|
||||
|
||||
@@ -1151,6 +1175,8 @@ class CITypeTemplateManager(object):
|
||||
id2obj_dicts[added_id].get('child_id'),
|
||||
id2obj_dicts[added_id].get('relation_type_id'),
|
||||
id2obj_dicts[added_id].get('constraint'),
|
||||
id2obj_dicts[added_id].get('parent_attr_id'),
|
||||
id2obj_dicts[added_id].get('child_attr_id'),
|
||||
)
|
||||
else:
|
||||
obj = cls.create(flush=True, **id2obj_dicts[added_id])
|
||||
@@ -1211,7 +1237,10 @@ class CITypeTemplateManager(object):
|
||||
def _import_ci_types(self, ci_types, attr_id_map):
|
||||
for i in ci_types:
|
||||
i.pop("unique_key", None)
|
||||
i.pop("show_name", None)
|
||||
i['unique_id'] = attr_id_map.get(i['unique_id'], i['unique_id'])
|
||||
if i.get('show_id'):
|
||||
i['show_id'] = attr_id_map.get(i['show_id'], i['show_id'])
|
||||
i['uid'] = current_user.uid
|
||||
|
||||
return self.__import(CIType, ci_types)
|
||||
@@ -1441,7 +1470,7 @@ class CITypeTemplateManager(object):
|
||||
ci_types=CITypeManager.get_ci_types(),
|
||||
ci_type_groups=CITypeGroupManager.get(),
|
||||
relation_types=[i.to_dict() for i in RelationTypeManager.get_all()],
|
||||
ci_type_relations=CITypeRelationManager.get(),
|
||||
ci_type_relations=CITypeRelationManager.get()[0],
|
||||
ci_type_auto_discovery_rules=list(),
|
||||
type2attributes=dict(),
|
||||
type2attribute_group=dict(),
|
||||
|
@@ -55,6 +55,9 @@ class CITypeOperateType(BaseEnum):
|
||||
DELETE_UNIQUE_CONSTRAINT = "11" # 删除联合唯一
|
||||
ADD_RELATION = "12" # 新增关系
|
||||
DELETE_RELATION = "13" # 删除关系
|
||||
ADD_RECONCILIATION = "14" # 删除关系
|
||||
UPDATE_RECONCILIATION = "15" # 删除关系
|
||||
DELETE_RECONCILIATION = "16" # 删除关系
|
||||
|
||||
|
||||
class RetKey(BaseEnum):
|
||||
@@ -98,6 +101,12 @@ class AttributeDefaultValueEnum(BaseEnum):
|
||||
AUTO_INC_ID = "$auto_inc_id"
|
||||
|
||||
|
||||
class ExecuteStatusEnum(BaseEnum):
|
||||
COMPLETED = '0'
|
||||
FAILED = '1'
|
||||
RUNNING = '2'
|
||||
|
||||
|
||||
CMDB_QUEUE = "one_cmdb_async"
|
||||
REDIS_PREFIX_CI = "ONE_CMDB"
|
||||
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
||||
|
@@ -26,7 +26,7 @@ from api.models.cmdb import OperationRecord
|
||||
class AttributeHistoryManger(object):
|
||||
@staticmethod
|
||||
def get_records_for_attributes(start, end, username, page, page_size, operate_type, type_id,
|
||||
ci_id=None, attr_id=None):
|
||||
ci_id=None, attr_id=None, ci_ids=None, more=False):
|
||||
|
||||
records = db.session.query(OperationRecord, AttributeHistory).join(
|
||||
AttributeHistory, OperationRecord.id == AttributeHistory.record_id)
|
||||
@@ -48,6 +48,9 @@ class AttributeHistoryManger(object):
|
||||
if ci_id is not None:
|
||||
records = records.filter(AttributeHistory.ci_id == ci_id)
|
||||
|
||||
if ci_ids and isinstance(ci_ids, list):
|
||||
records = records.filter(AttributeHistory.ci_id.in_(ci_ids))
|
||||
|
||||
if attr_id is not None:
|
||||
records = records.filter(AttributeHistory.attr_id == attr_id)
|
||||
|
||||
@@ -62,6 +65,12 @@ class AttributeHistoryManger(object):
|
||||
if attr_hist['attr']:
|
||||
attr_hist['attr_name'] = attr_hist['attr'].name
|
||||
attr_hist['attr_alias'] = attr_hist['attr'].alias
|
||||
if more:
|
||||
attr_hist['is_list'] = attr_hist['attr'].is_list
|
||||
attr_hist['is_computed'] = attr_hist['attr'].is_computed
|
||||
attr_hist['is_password'] = attr_hist['attr'].is_password
|
||||
attr_hist['default'] = attr_hist['attr'].default
|
||||
attr_hist['value_type'] = attr_hist['attr'].value_type
|
||||
attr_hist.pop("attr")
|
||||
|
||||
if record_id not in res:
|
||||
@@ -161,12 +170,14 @@ class AttributeHistoryManger(object):
|
||||
record = i.OperationRecord
|
||||
item = dict(attr_name=attr.name,
|
||||
attr_alias=attr.alias,
|
||||
value_type=attr.value_type,
|
||||
operate_type=hist.operate_type,
|
||||
username=user and user.nickname,
|
||||
old=hist.old,
|
||||
new=hist.new,
|
||||
created_at=record.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
record_id=record.id,
|
||||
ticket_id=record.ticket_id,
|
||||
hid=hist.id
|
||||
)
|
||||
result.append(item)
|
||||
@@ -200,9 +211,9 @@ class AttributeHistoryManger(object):
|
||||
return username, timestamp, attr_dict, rel_dict
|
||||
|
||||
@staticmethod
|
||||
def add(record_id, ci_id, history_list, type_id=None, flush=False, commit=True):
|
||||
def add(record_id, ci_id, history_list, type_id=None, ticket_id=None, flush=False, commit=True):
|
||||
if record_id is None:
|
||||
record = OperationRecord.create(uid=current_user.uid, type_id=type_id)
|
||||
record = OperationRecord.create(uid=current_user.uid, type_id=type_id, ticket_id=ticket_id)
|
||||
record_id = record.id
|
||||
|
||||
for attr_id, operate_type, old, new in history_list or []:
|
||||
@@ -270,7 +281,7 @@ class CITypeHistoryManager(object):
|
||||
return numfound, result
|
||||
|
||||
@staticmethod
|
||||
def add(operate_type, type_id, attr_id=None, trigger_id=None, unique_constraint_id=None, change=None):
|
||||
def add(operate_type, type_id, attr_id=None, trigger_id=None, unique_constraint_id=None, change=None, rc_id=None):
|
||||
if type_id is None and attr_id is not None:
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
type_ids = [i.type_id for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False)]
|
||||
@@ -283,6 +294,7 @@ class CITypeHistoryManager(object):
|
||||
uid=current_user.uid,
|
||||
attr_id=attr_id,
|
||||
trigger_id=trigger_id,
|
||||
rc_id=rc_id,
|
||||
unique_constraint_id=unique_constraint_id,
|
||||
change=change)
|
||||
|
||||
|
@@ -297,6 +297,10 @@ class PreferenceManager(object):
|
||||
|
||||
for type_id in id2type:
|
||||
id2type[type_id] = CITypeCache.get(type_id).to_dict()
|
||||
id2type[type_id]['unique_name'] = AttributeCache.get(id2type[type_id]['unique_id']).name
|
||||
if id2type[type_id]['show_id']:
|
||||
show_attr = AttributeCache.get(id2type[type_id]['show_id'])
|
||||
id2type[type_id]['show_name'] = show_attr and show_attr.name
|
||||
|
||||
return result, id2type, sorted(name2id, key=lambda x: x[1])
|
||||
|
||||
|
@@ -78,6 +78,8 @@ class ErrFormat(CommonErrFormat):
|
||||
unique_constraint_invalid = _l("Uniquely constrained attributes cannot be JSON and multi-valued")
|
||||
ci_type_trigger_duplicate = _l("Duplicated trigger") # 重复的触发器
|
||||
ci_type_trigger_not_found = _l("Trigger {} does not exist") # 触发器 {} 不存在
|
||||
ci_type_reconciliation_duplicate = _l("Duplicated reconciliation rule") # 重复的校验规则
|
||||
ci_type_reconciliation_not_found = _l("Reconciliation rule {} does not exist") # 规则 {} 不存在
|
||||
|
||||
record_not_found = _l("Operation record {} does not exist") # 操作记录 {} 不存在
|
||||
cannot_delete_unique = _l("Unique identifier cannot be deleted") # 不能删除唯一标识
|
||||
|
@@ -8,6 +8,8 @@ from flask import current_app
|
||||
from flask_login import current_user
|
||||
|
||||
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 ConstraintEnum
|
||||
@@ -18,6 +20,8 @@ from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
|
||||
from api.lib.cmdb.utils import TableMap
|
||||
from api.lib.cmdb.utils import ValueTypeMap
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.models.cmdb import CI
|
||||
@@ -236,7 +240,7 @@ class Search(object):
|
||||
type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
|
||||
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
_tmp = []
|
||||
_tmp, tmp_res = [], []
|
||||
level2ids = {}
|
||||
for lv in range(1, self.level + 1):
|
||||
level2ids[lv] = []
|
||||
@@ -303,25 +307,26 @@ class Search(object):
|
||||
if key:
|
||||
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||
if type_ids and lv == self.level:
|
||||
__tmp = [[i for i in x if i[1] in type_ids and
|
||||
(not id_filter_limit or (
|
||||
key[idx] not in id_filter_limit or
|
||||
int(i[0]) in id_filter_limit[key[idx]]) or
|
||||
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
||||
tmp_res = [[i for i in x if i[1] in type_ids and
|
||||
(not id_filter_limit or (
|
||||
key[idx] not in id_filter_limit or
|
||||
int(i[0]) in id_filter_limit[key[idx]]) or
|
||||
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
||||
else:
|
||||
__tmp = [[i for i in x if (not id_filter_limit or (
|
||||
tmp_res = [[i for i in x if (not id_filter_limit or (
|
||||
key[idx] not in id_filter_limit or
|
||||
int(i[0]) in id_filter_limit[key[idx]]) or
|
||||
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
||||
int(i[0]) in id_filter_limit)] for idx, x in
|
||||
enumerate(res)]
|
||||
|
||||
if ci_filter_limit:
|
||||
__tmp = [[j for j in i if j[1] not in ci_filter_limit or
|
||||
int(j[0]) in ci_filter_limit[j[1]]] for i in __tmp]
|
||||
tmp_res = [[j for j in i if j[1] not in ci_filter_limit or
|
||||
int(j[0]) in ci_filter_limit[j[1]]] for i in tmp_res]
|
||||
else:
|
||||
__tmp = []
|
||||
tmp_res = []
|
||||
|
||||
if __tmp:
|
||||
_tmp[idx] = [j for i in __tmp for j in i]
|
||||
if tmp_res:
|
||||
_tmp[idx] = [j for i in tmp_res for j in i]
|
||||
else:
|
||||
_tmp[idx] = []
|
||||
level2ids[lv].append([])
|
||||
@@ -332,3 +337,84 @@ class Search(object):
|
||||
detail={str(_id): dict(Counter([i[1] for i in _tmp[idx]]).items()) for idx, _id in enumerate(ids)})
|
||||
|
||||
return result
|
||||
|
||||
def search_full(self, type_ids):
|
||||
def _get_id2name(_type_id):
|
||||
ci_type = CITypeCache.get(_type_id)
|
||||
|
||||
attr = AttributeCache.get(ci_type.unique_id)
|
||||
value_table = TableMap(attr=attr).table
|
||||
serializer = ValueTypeMap.serialize[attr.value_type]
|
||||
unique_value = {i.ci_id: serializer(i.value) for i in value_table.get_by(attr_id=attr.id, to_dict=False)}
|
||||
|
||||
attr = AttributeCache.get(ci_type.show_id)
|
||||
if attr:
|
||||
value_table = TableMap(attr=attr).table
|
||||
serializer = ValueTypeMap.serialize[attr.value_type]
|
||||
show_value = {i.ci_id: serializer(i.value) for i in value_table.get_by(attr_id=attr.id, to_dict=False)}
|
||||
else:
|
||||
show_value = unique_value
|
||||
|
||||
return show_value, unique_value
|
||||
|
||||
self.level = int(self.level)
|
||||
|
||||
acl = ACLManager('cmdb')
|
||||
|
||||
type2filter_perms = dict()
|
||||
if not self.is_app_admin:
|
||||
res2 = acl.get_resources(ResourceTypeEnum.CI_FILTER)
|
||||
if res2:
|
||||
type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
|
||||
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
|
||||
level_ids = [str(i) for i in ids]
|
||||
result = []
|
||||
id2children = {}
|
||||
id2name = _get_id2name(type_ids[0])
|
||||
for i in level_ids:
|
||||
item = dict(id=int(i),
|
||||
type_id=type_ids[0],
|
||||
isLeaf=False,
|
||||
title=id2name[0].get(int(i)),
|
||||
uniqueValue=id2name[1].get(int(i)),
|
||||
children=[])
|
||||
result.append(item)
|
||||
id2children[str(i)] = item['children']
|
||||
|
||||
for lv in range(1, self.level):
|
||||
|
||||
if len(type_ids or []) >= lv and type2filter_perms.get(type_ids[lv]):
|
||||
id_filter_limit, _ = self._get_ci_filter(type2filter_perms[type_ids[lv]])
|
||||
else:
|
||||
id_filter_limit = {}
|
||||
|
||||
if self.has_m2m and lv != 1:
|
||||
key, prefix = [i for i in level_ids], REDIS_PREFIX_CI_RELATION2
|
||||
else:
|
||||
key, prefix = [i.split(',')[-1] for i in level_ids], REDIS_PREFIX_CI_RELATION
|
||||
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||
res = [[i for i in x if (not id_filter_limit or (key[idx] not in id_filter_limit or
|
||||
int(i[0]) in id_filter_limit[key[idx]]) or
|
||||
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
||||
_level_ids = []
|
||||
type_id = type_ids[lv]
|
||||
id2name = _get_id2name(type_id)
|
||||
for idx, node_path in enumerate(level_ids):
|
||||
for child_id, _ in (res[idx] or []):
|
||||
item = dict(id=int(child_id),
|
||||
type_id=type_id,
|
||||
isLeaf=True if lv == self.level - 1 else False,
|
||||
title=id2name[0].get(int(child_id)),
|
||||
uniqueValue=id2name[1].get(int(child_id)),
|
||||
children=[])
|
||||
id2children[node_path].append(item)
|
||||
|
||||
_node_path = "{},{}".format(node_path, child_id)
|
||||
_level_ids.append(_node_path)
|
||||
id2children[_node_path] = item['children']
|
||||
|
||||
level_ids = _level_ids
|
||||
|
||||
return result
|
||||
|
@@ -150,9 +150,10 @@ class AttributeValueManager(object):
|
||||
return AttributeHistoryManger.add(record_id, ci_id, [(attr_id, operate_type, old, new)], type_id)
|
||||
|
||||
@staticmethod
|
||||
def write_change2(changed, record_id=None):
|
||||
def write_change2(changed, record_id=None, ticket_id=None):
|
||||
for ci_id, attr_id, operate_type, old, new, type_id in changed:
|
||||
record_id = AttributeHistoryManger.add(record_id, ci_id, [(attr_id, operate_type, old, new)], type_id,
|
||||
ticket_id=ticket_id,
|
||||
commit=False, flush=False)
|
||||
try:
|
||||
db.session.commit()
|
||||
@@ -255,12 +256,13 @@ class AttributeValueManager(object):
|
||||
|
||||
return key2attr
|
||||
|
||||
def create_or_update_attr_value(self, ci, ci_dict, key2attr):
|
||||
def create_or_update_attr_value(self, ci, ci_dict, key2attr, ticket_id=None):
|
||||
"""
|
||||
add or update attribute value, then write history
|
||||
:param ci: instance object
|
||||
:param ci_dict: attribute dict
|
||||
:param key2attr: attr key to attr
|
||||
:param ticket_id:
|
||||
:return:
|
||||
"""
|
||||
changed = []
|
||||
@@ -273,25 +275,36 @@ class AttributeValueManager(object):
|
||||
if attr.is_list:
|
||||
existed_attrs = value_table.get_by(attr_id=attr.id, ci_id=ci.id, to_dict=False)
|
||||
existed_values = [i.value for i in existed_attrs]
|
||||
added = set(value) - set(existed_values)
|
||||
deleted = set(existed_values) - set(value)
|
||||
for v in added:
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=v, flush=False, commit=False)
|
||||
changed.append((ci.id, attr.id, OperateType.ADD, None, v, ci.type_id))
|
||||
|
||||
# Comparison array starts from which position changes
|
||||
min_len = min(len(value), len(existed_values))
|
||||
index = 0
|
||||
while index < min_len:
|
||||
if value[index] != existed_values[index]:
|
||||
break
|
||||
index += 1
|
||||
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)]
|
||||
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))
|
||||
else:
|
||||
existed_attr = value_table.get_by(attr_id=attr.id, ci_id=ci.id, first=True, to_dict=False)
|
||||
existed_value = existed_attr and existed_attr.value
|
||||
existed_value = (ValueTypeMap.serialize[attr.value_type](existed_value) if
|
||||
existed_value or existed_value == 0 else existed_value)
|
||||
if existed_value is None and value is not None:
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value, flush=False, commit=False)
|
||||
|
||||
changed.append((ci.id, attr.id, OperateType.ADD, None, value, ci.type_id))
|
||||
else:
|
||||
if existed_value != value:
|
||||
if existed_value != value and existed_attr:
|
||||
if value is None:
|
||||
existed_attr.delete(flush=False, commit=False)
|
||||
else:
|
||||
@@ -306,7 +319,7 @@ class AttributeValueManager(object):
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.attribute_value_unknown_error.format(e.args[0]))
|
||||
|
||||
return self.write_change2(changed)
|
||||
return self.write_change2(changed, ticket_id=ticket_id)
|
||||
|
||||
@staticmethod
|
||||
def delete_attr_value(attr_id, ci_id, commit=True):
|
||||
|
@@ -10,6 +10,11 @@ from api.lib.perm.acl.role import RoleCRUD, RoleRelationCRUD
|
||||
from api.lib.perm.acl.user import UserCRUD
|
||||
|
||||
|
||||
def validate_app(app_id):
|
||||
app = AppCache.get(app_id)
|
||||
return app.id if app else None
|
||||
|
||||
|
||||
class ACLManager(object):
|
||||
def __init__(self, app_name='acl', uid=None):
|
||||
self.log = current_app.logger
|
||||
@@ -133,7 +138,8 @@ class ACLManager(object):
|
||||
numfound, res = ResourceCRUD.search(q, u, self.validate_app().id, rt_id, page, page_size)
|
||||
return res
|
||||
|
||||
def grant_resource(self, rid, resource_id, perms):
|
||||
@staticmethod
|
||||
def grant_resource(rid, resource_id, perms):
|
||||
PermissionCRUD.grant(rid, perms, resource_id=resource_id, group_id=None)
|
||||
|
||||
@staticmethod
|
||||
@@ -141,3 +147,7 @@ class ACLManager(object):
|
||||
rt = AppCRUD.add(**payload)
|
||||
|
||||
return rt.to_dict()
|
||||
|
||||
def role_has_perms(self, rid, resource_name, resource_type_name, perm):
|
||||
app_id = validate_app(self.app_name)
|
||||
return RoleCRUD.has_permission(rid, resource_name, resource_type_name, app_id, perm)
|
||||
|
37
cmdb-api/api/lib/common_setting/decorator.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import functools
|
||||
|
||||
from flask import abort, session
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
|
||||
|
||||
def perms_role_required(app_name, resource_type_name, resource_name, perm, role_name=None):
|
||||
def decorator_perms_role_required(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper_required(*args, **kwargs):
|
||||
acl = ACLManager(app_name)
|
||||
has_perms = False
|
||||
try:
|
||||
has_perms = acl.role_has_perms(session["acl"]['rid'], resource_name, resource_type_name, perm)
|
||||
except Exception as e:
|
||||
# resource_type not exist, continue check role
|
||||
if role_name:
|
||||
if role_name not in session.get("acl", {}).get("parentRoles", []):
|
||||
abort(403, ErrFormat.role_required.format(role_name))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
else:
|
||||
abort(403, ErrFormat.resource_no_permission.format(resource_name, perm))
|
||||
|
||||
if not has_perms:
|
||||
if role_name:
|
||||
if role_name not in session.get("acl", {}).get("parentRoles", []):
|
||||
abort(403, ErrFormat.role_required.format(role_name))
|
||||
else:
|
||||
abort(403, ErrFormat.resource_no_permission.format(resource_name, perm))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper_required
|
||||
|
||||
return decorator_perms_role_required
|
@@ -80,3 +80,5 @@ class ErrFormat(CommonErrFormat):
|
||||
ldap_test_username_required = _l("LDAP test username required") # LDAP测试用户名必填
|
||||
|
||||
company_wide = _l("Company wide") # 全公司
|
||||
|
||||
resource_no_permission = _l("No permission to access resource {}, perm {} ") # 没有权限访问 {} 资源的 {} 权限"
|
||||
|
59
cmdb-api/api/lib/common_setting/role_perm_base.py
Normal file
@@ -0,0 +1,59 @@
|
||||
class OperationPermission(object):
|
||||
|
||||
def __init__(self, resource_perms):
|
||||
for _r in resource_perms:
|
||||
setattr(self, _r['page'], _r['page'])
|
||||
for _p in _r['perms']:
|
||||
setattr(self, _p, _p)
|
||||
|
||||
|
||||
class BaseApp(object):
|
||||
resource_type_name = 'OperationPermission'
|
||||
all_resource_perms = []
|
||||
|
||||
def __init__(self):
|
||||
self.admin_name = None
|
||||
self.roles = []
|
||||
self.app_name = 'acl'
|
||||
self.require_create_resource_type = self.resource_type_name
|
||||
self.extra_create_resource_type_list = []
|
||||
|
||||
self.op = None
|
||||
|
||||
@staticmethod
|
||||
def format_role(role_name, role_type, acl_rid, resource_perms, description=''):
|
||||
return dict(
|
||||
role_name=role_name,
|
||||
role_type=role_type,
|
||||
acl_rid=acl_rid,
|
||||
description=description,
|
||||
resource_perms=resource_perms,
|
||||
)
|
||||
|
||||
|
||||
class CMDBApp(BaseApp):
|
||||
all_resource_perms = [
|
||||
{"page": "Big_Screen", "page_cn": "大屏", "perms": ["read"]},
|
||||
{"page": "Dashboard", "page_cn": "仪表盘", "perms": ["read"]},
|
||||
{"page": "Resource_Search", "page_cn": "资源搜索", "perms": ["read"]},
|
||||
{"page": "Auto_Discovery_Pool", "page_cn": "自动发现池", "perms": ["read"]},
|
||||
{"page": "My_Subscriptions", "page_cn": "我的订阅", "perms": ["read"]},
|
||||
{"page": "Bulk_Import", "page_cn": "批量导入", "perms": ["read"]},
|
||||
{"page": "Model_Configuration", "page_cn": "模型配置",
|
||||
"perms": ["read", "create_CIType", "create_CIType_group", "update_CIType_group",
|
||||
"delete_CIType_group", "download_CIType"]},
|
||||
{"page": "Backend_Management", "page_cn": "后台管理", "perms": ["read"]},
|
||||
{"page": "Customized_Dashboard", "page_cn": "定制仪表盘", "perms": ["read"]},
|
||||
{"page": "Service_Tree_Definition", "page_cn": "服务树定义", "perms": ["read"]},
|
||||
{"page": "Model_Relationships", "page_cn": "模型关系", "perms": ["read"]},
|
||||
{"page": "Operation_Audit", "page_cn": "操作审计", "perms": ["read"]},
|
||||
{"page": "Relationship_Types", "page_cn": "关系类型", "perms": ["read"]},
|
||||
{"page": "Auto_Discovery", "page_cn": "自动发现", "perms": ["read"]}]
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.admin_name = 'cmdb_admin'
|
||||
self.app_name = 'cmdb'
|
||||
|
||||
self.op = OperationPermission(self.all_resource_perms)
|
@@ -1,5 +1,10 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
from datetime import datetime
|
||||
from flask import current_app
|
||||
from sqlalchemy import inspect, text
|
||||
from sqlalchemy.dialects.mysql import ENUM
|
||||
|
||||
from api.extensions import db
|
||||
|
||||
|
||||
def get_cur_time_str(split_flag='-'):
|
||||
@@ -23,3 +28,115 @@ class BaseEnum(object):
|
||||
if not attr.startswith("_") and not callable(getattr(cls, attr))
|
||||
}
|
||||
return cls._ALL_
|
||||
|
||||
|
||||
class CheckNewColumn(object):
|
||||
|
||||
def __init__(self):
|
||||
self.engine = db.get_engine()
|
||||
self.inspector = inspect(self.engine)
|
||||
self.table_names = self.inspector.get_table_names()
|
||||
|
||||
@staticmethod
|
||||
def get_model_by_table_name(_table_name):
|
||||
registry = getattr(db.Model, 'registry', None)
|
||||
class_registry = getattr(registry, '_class_registry', None)
|
||||
for _model in class_registry.values():
|
||||
if hasattr(_model, '__tablename__') and _model.__tablename__ == _table_name:
|
||||
return _model
|
||||
return None
|
||||
|
||||
def run(self):
|
||||
for table_name in self.table_names:
|
||||
self.check_by_table(table_name)
|
||||
|
||||
def check_by_table(self, table_name):
|
||||
existed_columns = self.inspector.get_columns(table_name)
|
||||
enum_columns = []
|
||||
existed_column_name_list = []
|
||||
for c in existed_columns:
|
||||
if isinstance(c['type'], ENUM):
|
||||
enum_columns.append(c['name'])
|
||||
existed_column_name_list.append(c['name'])
|
||||
|
||||
model = self.get_model_by_table_name(table_name)
|
||||
if model is None:
|
||||
return
|
||||
model_columns = getattr(getattr(getattr(model, '__table__'), 'columns'), '_all_columns')
|
||||
for column in model_columns:
|
||||
if column.name not in existed_column_name_list:
|
||||
add_res = self.add_new_column(table_name, column)
|
||||
if not add_res:
|
||||
continue
|
||||
|
||||
current_app.logger.info(f"add new column [{column.name}] in table [{table_name}] success.")
|
||||
|
||||
if column.name in enum_columns:
|
||||
enum_columns.remove(column.name)
|
||||
|
||||
self.add_new_index(table_name, column)
|
||||
|
||||
if len(enum_columns) > 0:
|
||||
self.check_enum_column(enum_columns, existed_columns, model_columns, table_name)
|
||||
|
||||
def add_new_column(self, target_table_name, new_column):
|
||||
try:
|
||||
column_type = new_column.type.compile(self.engine.dialect)
|
||||
default_value = new_column.default.arg if new_column.default else None
|
||||
|
||||
sql = "ALTER TABLE " + target_table_name + " ADD COLUMN " + f"`{new_column.name}`" + " " + column_type
|
||||
if new_column.comment:
|
||||
sql += f" comment '{new_column.comment}'"
|
||||
|
||||
if column_type == 'JSON':
|
||||
pass
|
||||
elif default_value:
|
||||
if column_type.startswith('VAR') or column_type.startswith('Text'):
|
||||
if default_value is None or len(default_value) == 0:
|
||||
pass
|
||||
else:
|
||||
sql += f" DEFAULT {default_value}"
|
||||
|
||||
sql = text(sql)
|
||||
db.session.execute(sql)
|
||||
return True
|
||||
except Exception as e:
|
||||
err = f"add_new_column [{new_column.name}] to table [{target_table_name}] err: {e}"
|
||||
current_app.logger.error(err)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def add_new_index(target_table_name, new_column):
|
||||
try:
|
||||
if new_column.index:
|
||||
index_name = f"{target_table_name}_{new_column.name}"
|
||||
sql = "CREATE INDEX " + f"{index_name}" + " ON " + target_table_name + " (" + new_column.name + ")"
|
||||
db.session.execute(sql)
|
||||
current_app.logger.info(f"add new index [{index_name}] in table [{target_table_name}] success.")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
err = f"add_new_index [{new_column.name}] to table [{target_table_name}] err: {e}"
|
||||
current_app.logger.error(err)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def check_enum_column(enum_columns, existed_columns, model_columns, table_name):
|
||||
for column_name in enum_columns:
|
||||
try:
|
||||
enum_column = list(filter(lambda x: x['name'] == column_name, existed_columns))[0]
|
||||
old_enum_value = enum_column.get('type', {}).enums
|
||||
target_column = list(filter(lambda x: x.name == column_name, model_columns))[0]
|
||||
new_enum_value = target_column.type.enums
|
||||
|
||||
if set(old_enum_value) == set(new_enum_value):
|
||||
continue
|
||||
|
||||
enum_values_str = ','.join(["'{}'".format(value) for value in new_enum_value])
|
||||
sql = f"ALTER TABLE {table_name} MODIFY COLUMN" + f"`{column_name}`" + f" enum({enum_values_str})"
|
||||
db.session.execute(sql)
|
||||
current_app.logger.info(
|
||||
f"modify column [{column_name}] ENUM: {new_enum_value} in table [{table_name}] success.")
|
||||
except Exception as e:
|
||||
current_app.logger.error(
|
||||
f"modify column ENUM [{column_name}] in table [{table_name}] err: {e}")
|
||||
|
@@ -224,16 +224,22 @@ class RoleRelationCache(object):
|
||||
@classmethod
|
||||
@flush_db
|
||||
def rebuild(cls, rid, app_id):
|
||||
cls.clean(rid, app_id)
|
||||
|
||||
cls.get_parent_ids(rid, app_id)
|
||||
cls.get_child_ids(rid, app_id)
|
||||
resources = cls.get_resources(rid, app_id)
|
||||
if resources.get('id2perms') or resources.get('group2perms'):
|
||||
HasResourceRoleCache.add(rid, app_id)
|
||||
if app_id is None:
|
||||
app_ids = [None] + [i.id for i in App.get_by(to_dict=False)]
|
||||
else:
|
||||
HasResourceRoleCache.remove(rid, app_id)
|
||||
cls.get_resources2(rid, app_id)
|
||||
app_ids = [app_id]
|
||||
|
||||
for _app_id in app_ids:
|
||||
cls.clean(rid, _app_id)
|
||||
|
||||
cls.get_parent_ids(rid, _app_id)
|
||||
cls.get_child_ids(rid, _app_id)
|
||||
resources = cls.get_resources(rid, _app_id)
|
||||
if resources.get('id2perms') or resources.get('group2perms'):
|
||||
HasResourceRoleCache.add(rid, _app_id)
|
||||
else:
|
||||
HasResourceRoleCache.remove(rid, _app_id)
|
||||
cls.get_resources2(rid, _app_id)
|
||||
|
||||
@classmethod
|
||||
@flush_db
|
||||
|
@@ -274,12 +274,14 @@ class PermissionCRUD(object):
|
||||
perm2resource.setdefault(_perm, []).append(resource_id)
|
||||
for _perm in perm2resource:
|
||||
perm = PermissionCache.get(_perm, resource_type_id)
|
||||
existeds = RolePermission.get_by(rid=rid,
|
||||
app_id=app_id,
|
||||
perm_id=perm.id,
|
||||
__func_in___key_resource_id=perm2resource[_perm],
|
||||
to_dict=False)
|
||||
for existed in existeds:
|
||||
if perm is None:
|
||||
continue
|
||||
exists = RolePermission.get_by(rid=rid,
|
||||
app_id=app_id,
|
||||
perm_id=perm.id,
|
||||
__func_in___key_resource_id=perm2resource[_perm],
|
||||
to_dict=False)
|
||||
for existed in exists:
|
||||
existed.deleted = True
|
||||
existed.deleted_at = datetime.datetime.now()
|
||||
db.session.add(existed)
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
@@ -127,11 +126,18 @@ class ResourceTypeCRUD(object):
|
||||
existed_ids = [i.id for i in existed]
|
||||
current_ids = []
|
||||
|
||||
rebuild_rids = set()
|
||||
for i in existed:
|
||||
if i.name not in perms:
|
||||
i.soft_delete()
|
||||
i.soft_delete(commit=False)
|
||||
for rp in RolePermission.get_by(perm_id=i.id, to_dict=False):
|
||||
rp.soft_delete(commit=False)
|
||||
rebuild_rids.add((rp.app_id, rp.rid))
|
||||
else:
|
||||
current_ids.append(i.id)
|
||||
db.session.commit()
|
||||
for _app_id, _rid in rebuild_rids:
|
||||
role_rebuild.apply_async(args=(_rid, _app_id), queue=ACL_QUEUE)
|
||||
|
||||
for i in perms:
|
||||
if i not in existed_names:
|
||||
|
@@ -3,12 +3,14 @@
|
||||
|
||||
import time
|
||||
|
||||
import redis_lock
|
||||
import six
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from sqlalchemy import or_
|
||||
|
||||
from api.extensions import db
|
||||
from api.extensions import rd
|
||||
from api.lib.perm.acl.app import AppCRUD
|
||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
@@ -62,7 +64,9 @@ class RoleRelationCRUD(object):
|
||||
|
||||
id2parents = {}
|
||||
for i in res:
|
||||
id2parents.setdefault(rid2uid.get(i.child_id, i.child_id), []).append(RoleCache.get(i.parent_id).to_dict())
|
||||
parent = RoleCache.get(i.parent_id)
|
||||
if parent:
|
||||
id2parents.setdefault(rid2uid.get(i.child_id, i.child_id), []).append(parent.to_dict())
|
||||
|
||||
return id2parents
|
||||
|
||||
@@ -141,24 +145,27 @@ class RoleRelationCRUD(object):
|
||||
|
||||
@classmethod
|
||||
def add(cls, role, parent_id, child_ids, app_id):
|
||||
result = []
|
||||
for child_id in child_ids:
|
||||
existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id, app_id=app_id)
|
||||
if existed:
|
||||
continue
|
||||
with redis_lock.Lock(rd.r, "ROLE_RELATION_ADD"):
|
||||
db.session.commit()
|
||||
|
||||
RoleRelationCache.clean(parent_id, app_id)
|
||||
RoleRelationCache.clean(child_id, app_id)
|
||||
result = []
|
||||
for child_id in child_ids:
|
||||
existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id, app_id=app_id)
|
||||
if existed:
|
||||
continue
|
||||
|
||||
if parent_id in cls.recursive_child_ids(child_id, app_id):
|
||||
return abort(400, ErrFormat.inheritance_dead_loop)
|
||||
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)
|
||||
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())
|
||||
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, {}, {},
|
||||
|
@@ -1,19 +1,15 @@
|
||||
import json
|
||||
import os
|
||||
import secrets
|
||||
import sys
|
||||
from base64 import b64decode, b64encode
|
||||
import threading
|
||||
|
||||
from base64 import b64decode, b64encode
|
||||
from Cryptodome.Protocol.SecretSharing import Shamir
|
||||
from colorama import Back
|
||||
from colorama import Fore
|
||||
from colorama import Style
|
||||
from colorama import init as colorama_init
|
||||
from colorama import Back, Fore, Style, init as colorama_init
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives import padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher
|
||||
from cryptography.hazmat.primitives.ciphers import algorithms
|
||||
from cryptography.hazmat.primitives.ciphers import modes
|
||||
from cryptography.hazmat.primitives import hashes, padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
from flask import current_app
|
||||
@@ -27,11 +23,16 @@ backend_encrypt_key_name = "encrypt_key"
|
||||
backend_root_key_salt_name = "root_key_salt"
|
||||
backend_encrypt_key_salt_name = "encrypt_key_salt"
|
||||
backend_seal_key = "seal_status"
|
||||
|
||||
success = "success"
|
||||
seal_status = True
|
||||
|
||||
secrets_encrypt_key = ""
|
||||
secrets_root_key = ""
|
||||
|
||||
def string_to_bytes(value):
|
||||
if not value:
|
||||
return ""
|
||||
if isinstance(value, bytes):
|
||||
return value
|
||||
if sys.version_info.major == 2:
|
||||
@@ -44,6 +45,8 @@ def string_to_bytes(value):
|
||||
class Backend:
|
||||
def __init__(self, backend=None):
|
||||
self.backend = backend
|
||||
# cache is a redis object
|
||||
self.cache = backend.cache
|
||||
|
||||
def get(self, key):
|
||||
return self.backend.get(key)
|
||||
@@ -54,23 +57,33 @@ class Backend:
|
||||
def update(self, key, value):
|
||||
return self.backend.update(key, value)
|
||||
|
||||
def get_shares(self, key):
|
||||
return self.backend.get_shares(key)
|
||||
|
||||
def set_shares(self, key, value):
|
||||
return self.backend.set_shares(key, value)
|
||||
|
||||
|
||||
class KeyManage:
|
||||
|
||||
def __init__(self, trigger=None, backend=None):
|
||||
self.trigger = trigger
|
||||
self.backend = backend
|
||||
self.share_key = "cmdb::secret::secrets_share"
|
||||
if backend:
|
||||
self.backend = Backend(backend)
|
||||
|
||||
def init_app(self, app, backend=None):
|
||||
if (sys.argv[0].endswith("gunicorn") or
|
||||
(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()
|
||||
|
||||
self.trigger = app.config.get("INNER_TRIGGER_TOKEN")
|
||||
if not self.trigger:
|
||||
return
|
||||
|
||||
self.backend = backend
|
||||
resp = self.auto_unseal()
|
||||
self.print_response(resp)
|
||||
|
||||
@@ -124,6 +137,8 @@ class KeyManage:
|
||||
return new_shares
|
||||
|
||||
def is_valid_root_key(self, root_key):
|
||||
if not root_key:
|
||||
return False
|
||||
root_key_hash, ok = self.hash_root_key(root_key)
|
||||
if not ok:
|
||||
return root_key_hash, ok
|
||||
@@ -135,35 +150,42 @@ class KeyManage:
|
||||
else:
|
||||
return "", True
|
||||
|
||||
def auth_root_secret(self, root_key):
|
||||
msg, ok = self.is_valid_root_key(root_key)
|
||||
if not ok:
|
||||
return {
|
||||
"message": msg,
|
||||
"status": "failed"
|
||||
}
|
||||
def auth_root_secret(self, root_key, app):
|
||||
with app.app_context():
|
||||
msg, ok = self.is_valid_root_key(root_key)
|
||||
if not ok:
|
||||
return {
|
||||
"message": msg,
|
||||
"status": "failed"
|
||||
}
|
||||
|
||||
encrypt_key_aes = self.backend.get(backend_encrypt_key_name)
|
||||
if not encrypt_key_aes:
|
||||
return {
|
||||
"message": "encrypt key is empty",
|
||||
"status": "failed"
|
||||
}
|
||||
encrypt_key_aes = self.backend.get(backend_encrypt_key_name)
|
||||
if not encrypt_key_aes:
|
||||
return {
|
||||
"message": "encrypt key is empty",
|
||||
"status": "failed"
|
||||
}
|
||||
|
||||
secrets_encrypt_key, ok = InnerCrypt.aes_decrypt(string_to_bytes(root_key), encrypt_key_aes)
|
||||
if ok:
|
||||
msg, ok = self.backend.update(backend_seal_key, "open")
|
||||
secret_encrypt_key, ok = InnerCrypt.aes_decrypt(string_to_bytes(root_key), encrypt_key_aes)
|
||||
if ok:
|
||||
current_app.config["secrets_encrypt_key"] = secrets_encrypt_key
|
||||
current_app.config["secrets_root_key"] = root_key
|
||||
current_app.config["secrets_shares"] = []
|
||||
return {"message": success, "status": success}
|
||||
return {"message": msg, "status": "failed"}
|
||||
else:
|
||||
return {
|
||||
"message": secrets_encrypt_key,
|
||||
"status": "failed"
|
||||
}
|
||||
msg, ok = self.backend.update(backend_seal_key, "open")
|
||||
if ok:
|
||||
global secrets_encrypt_key, secrets_root_key
|
||||
secrets_encrypt_key = secret_encrypt_key
|
||||
secrets_root_key = root_key
|
||||
self.backend.cache.set(self.share_key, json.dumps([]))
|
||||
return {"message": success, "status": success}
|
||||
return {"message": msg, "status": "failed"}
|
||||
else:
|
||||
return {
|
||||
"message": secret_encrypt_key,
|
||||
"status": "failed"
|
||||
}
|
||||
|
||||
def parse_shares(self, shares, app):
|
||||
if len(shares) >= global_key_threshold:
|
||||
recovered_secret = Shamir.combine(shares[:global_key_threshold], False)
|
||||
return self.auth_root_secret(b64encode(recovered_secret), app)
|
||||
|
||||
def unseal(self, key):
|
||||
if not self.is_seal():
|
||||
@@ -175,14 +197,12 @@ class KeyManage:
|
||||
try:
|
||||
t = [i for i in b64decode(key)]
|
||||
v = (int("".join([chr(i) for i in t[-2:]])), bytes(t[:-2]))
|
||||
shares = current_app.config.get("secrets_shares", [])
|
||||
shares = self.backend.get_shares(self.share_key)
|
||||
if v not in shares:
|
||||
shares.append(v)
|
||||
current_app.config["secrets_shares"] = shares
|
||||
|
||||
self.set_shares(shares)
|
||||
if len(shares) >= global_key_threshold:
|
||||
recovered_secret = Shamir.combine(shares[:global_key_threshold], False)
|
||||
return self.auth_root_secret(b64encode(recovered_secret))
|
||||
return self.parse_shares(shares, current_app)
|
||||
else:
|
||||
return {
|
||||
"message": "waiting for inputting other unseal key {0}/{1}".format(len(shares),
|
||||
@@ -242,8 +262,11 @@ class KeyManage:
|
||||
msg, ok = self.backend.add(backend_seal_key, "open")
|
||||
if not ok:
|
||||
return {"message": msg, "status": "failed"}, False
|
||||
current_app.config["secrets_root_key"] = root_key
|
||||
current_app.config["secrets_encrypt_key"] = encrypt_key
|
||||
|
||||
global secrets_encrypt_key, secrets_root_key
|
||||
secrets_encrypt_key = encrypt_key
|
||||
secrets_root_key = root_key
|
||||
|
||||
self.print_token(shares, root_token=root_key)
|
||||
|
||||
return {"message": "OK",
|
||||
@@ -266,7 +289,7 @@ class KeyManage:
|
||||
}
|
||||
# TODO
|
||||
elif len(self.trigger.strip()) == 24:
|
||||
res = self.auth_root_secret(self.trigger.encode())
|
||||
res = self.auth_root_secret(self.trigger.encode(), current_app)
|
||||
if res.get("status") == success:
|
||||
return {
|
||||
"message": success,
|
||||
@@ -298,22 +321,31 @@ class KeyManage:
|
||||
"message": msg,
|
||||
"status": "failed",
|
||||
}
|
||||
current_app.config["secrets_root_key"] = ''
|
||||
current_app.config["secrets_encrypt_key"] = ''
|
||||
self.clear()
|
||||
self.backend.cache.publish(self.share_key, "clear")
|
||||
|
||||
return {
|
||||
"message": success,
|
||||
"status": success
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def clear():
|
||||
global secrets_encrypt_key, secrets_root_key
|
||||
secrets_encrypt_key = ''
|
||||
secrets_root_key = ''
|
||||
|
||||
def is_seal(self):
|
||||
"""
|
||||
If there is no initialization or the root key is inconsistent, it is considered to be in a sealed state.
|
||||
If there is no initialization or the root key is inconsistent, it is considered to be in a sealed state..
|
||||
:return:
|
||||
"""
|
||||
secrets_root_key = current_app.config.get("secrets_root_key")
|
||||
# secrets_root_key = current_app.config.get("secrets_root_key")
|
||||
if not secrets_root_key:
|
||||
return True
|
||||
msg, ok = self.is_valid_root_key(secrets_root_key)
|
||||
if not ok:
|
||||
return true
|
||||
return True
|
||||
status = self.backend.get(backend_seal_key)
|
||||
return status == "block"
|
||||
|
||||
@@ -349,22 +381,53 @@ class KeyManage:
|
||||
}
|
||||
print(status_colors.get(status, Fore.GREEN), message, Style.RESET_ALL)
|
||||
|
||||
def set_shares(self, values):
|
||||
new_value = list()
|
||||
for v in values:
|
||||
new_value.append((v[0], b64encode(v[1]).decode("utf-8")))
|
||||
self.backend.cache.publish(self.share_key, json.dumps(new_value))
|
||||
self.backend.cache.set(self.share_key, json.dumps(new_value))
|
||||
|
||||
def watch_root_key(self, app):
|
||||
pubsub = self.backend.cache.pubsub()
|
||||
pubsub.subscribe(self.share_key)
|
||||
|
||||
new_value = set()
|
||||
for message in pubsub.listen():
|
||||
if message["type"] == "message":
|
||||
if message["data"] == b"clear":
|
||||
self.clear()
|
||||
continue
|
||||
try:
|
||||
value = json.loads(message["data"].decode("utf-8"))
|
||||
for v in value:
|
||||
new_value.add((v[0], b64decode(v[1])))
|
||||
except Exception as e:
|
||||
return []
|
||||
if len(new_value) >= global_key_threshold:
|
||||
self.parse_shares(list(new_value), app)
|
||||
new_value = set()
|
||||
|
||||
|
||||
class InnerCrypt:
|
||||
def __init__(self):
|
||||
secrets_encrypt_key = current_app.config.get("secrets_encrypt_key", "")
|
||||
self.encrypt_key = b64decode(secrets_encrypt_key.encode("utf-8"))
|
||||
self.encrypt_key = b64decode(secrets_encrypt_key)
|
||||
#self.encrypt_key = b64decode(secrets_encrypt_key, "".encode("utf-8"))
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
"""
|
||||
encrypt method contain aes currently
|
||||
"""
|
||||
if not self.encrypt_key:
|
||||
return ValueError("secret is disabled, please seal firstly"), False
|
||||
return self.aes_encrypt(self.encrypt_key, plaintext)
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
"""
|
||||
decrypt method contain aes currently
|
||||
"""
|
||||
if not self.encrypt_key:
|
||||
return ValueError("secret is disabled, please seal firstly"), False
|
||||
return self.aes_decrypt(self.encrypt_key, ciphertext)
|
||||
|
||||
@classmethod
|
||||
@@ -381,6 +444,7 @@ class InnerCrypt:
|
||||
|
||||
return b64encode(iv + ciphertext).decode("utf-8"), True
|
||||
except Exception as e:
|
||||
|
||||
return str(e), False
|
||||
|
||||
@classmethod
|
||||
@@ -426,4 +490,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)
|
@@ -1,8 +1,13 @@
|
||||
import base64
|
||||
import json
|
||||
|
||||
from api.models.cmdb import InnerKV
|
||||
from api.extensions import rd
|
||||
|
||||
|
||||
class InnerKVManger(object):
|
||||
def __init__(self):
|
||||
self.cache = rd.r
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@@ -33,3 +38,26 @@ class InnerKVManger(object):
|
||||
return "success", True
|
||||
|
||||
return "update failed", True
|
||||
|
||||
@classmethod
|
||||
def get_shares(cls, key):
|
||||
new_value = list()
|
||||
v = rd.get_str(key)
|
||||
if not v:
|
||||
return new_value
|
||||
try:
|
||||
value = json.loads(v.decode("utf-8"))
|
||||
for v in value:
|
||||
new_value.append((v[0], base64.b64decode(v[1])))
|
||||
except Exception as e:
|
||||
return []
|
||||
return new_value
|
||||
|
||||
@classmethod
|
||||
def set_shares(cls, key, value):
|
||||
new_value = list()
|
||||
for v in value:
|
||||
new_value.append((v[0], base64.b64encode(v[1]).decode("utf-8")))
|
||||
rd.set_str(key, json.dumps(new_value))
|
||||
|
||||
|
||||
|
@@ -117,6 +117,23 @@ class RedisHandler(object):
|
||||
except Exception as e:
|
||||
current_app.logger.error("delete redis key error, {0}".format(str(e)))
|
||||
|
||||
def set_str(self, key, value, expired=None):
|
||||
try:
|
||||
if expired:
|
||||
self.r.setex(key, expired, value)
|
||||
else:
|
||||
self.r.set(key, value)
|
||||
except Exception as e:
|
||||
current_app.logger.error("set redis error, {0}".format(str(e)))
|
||||
|
||||
def get_str(self, key):
|
||||
try:
|
||||
value = self.r.get(key)
|
||||
except Exception as e:
|
||||
current_app.logger.error("get redis error, {0}".format(str(e)))
|
||||
return
|
||||
return value
|
||||
|
||||
|
||||
class ESHandler(object):
|
||||
def __init__(self, flask_app=None):
|
||||
|
@@ -46,13 +46,17 @@ class CIType(Model):
|
||||
name = db.Column(db.String(32), nullable=False)
|
||||
alias = db.Column(db.String(32), nullable=False)
|
||||
unique_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"), nullable=False)
|
||||
show_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
|
||||
enabled = db.Column(db.Boolean, default=True, nullable=False)
|
||||
is_attached = db.Column(db.Boolean, default=False, nullable=False)
|
||||
icon = db.Column(db.Text)
|
||||
order = db.Column(db.SmallInteger, default=0, nullable=False)
|
||||
default_order_attr = db.Column(db.String(33))
|
||||
|
||||
unique_key = db.relationship("Attribute", backref="c_ci_types.unique_id")
|
||||
unique_key = db.relationship("Attribute", backref="c_ci_types.unique_id",
|
||||
primaryjoin="Attribute.id==CIType.unique_id", foreign_keys=[unique_id])
|
||||
show_key = db.relationship("Attribute", backref="c_ci_types.show_id",
|
||||
primaryjoin="Attribute.id==CIType.show_id", foreign_keys=[show_id])
|
||||
|
||||
uid = db.Column(db.Integer, index=True)
|
||||
|
||||
@@ -75,6 +79,9 @@ class CITypeRelation(Model):
|
||||
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
|
||||
constraint = db.Column(db.Enum(*ConstraintEnum.all()), default=ConstraintEnum.One2Many)
|
||||
|
||||
parent_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
|
||||
child_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
|
||||
|
||||
parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.parent_id")
|
||||
child = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.child_id")
|
||||
relation_type = db.relationship("RelationType", backref="c_ci_type_relations.relation_type_id")
|
||||
@@ -425,6 +432,7 @@ class CITypeHistory(Model):
|
||||
|
||||
attr_id = db.Column(db.Integer)
|
||||
trigger_id = db.Column(db.Integer)
|
||||
rc_id = db.Column(db.Integer)
|
||||
unique_constraint_id = db.Column(db.Integer)
|
||||
|
||||
uid = db.Column(db.Integer, index=True)
|
||||
|
@@ -3,12 +3,13 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
from celery_once import QueueOnce
|
||||
import redis_lock
|
||||
from flask import current_app
|
||||
from werkzeug.exceptions import BadRequest
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from api.extensions import celery
|
||||
from api.extensions import rd
|
||||
from api.lib.decorator import flush_db
|
||||
from api.lib.decorator import reconnect_db
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
@@ -25,14 +26,14 @@ from api.models.acl import Role
|
||||
from api.models.acl import Trigger
|
||||
|
||||
|
||||
@celery.task(name="acl.role_rebuild",
|
||||
queue=ACL_QUEUE,)
|
||||
@celery.task(name="acl.role_rebuild", queue=ACL_QUEUE, )
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def role_rebuild(rids, app_id):
|
||||
rids = rids if isinstance(rids, list) else [rids]
|
||||
for rid in rids:
|
||||
RoleRelationCache.rebuild(rid, app_id)
|
||||
with redis_lock.Lock(rd.r, "ROLE_REBUILD_{}_{}".format(rid, app_id)):
|
||||
RoleRelationCache.rebuild(rid, app_id)
|
||||
|
||||
current_app.logger.info("Role {0} App {1} rebuild..........".format(rids, app_id))
|
||||
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
|
||||
import json
|
||||
import time
|
||||
|
||||
import redis_lock
|
||||
from flask import current_app
|
||||
@@ -33,8 +32,7 @@ from api.models.cmdb import CITypeAttribute
|
||||
@reconnect_db
|
||||
def ci_cache(ci_id, operate_type, record_id):
|
||||
from api.lib.cmdb.ci import CITriggerManager
|
||||
|
||||
time.sleep(0.01)
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
|
||||
m = api.lib.cmdb.ci.CIManager()
|
||||
ci_dict = m.get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
|
||||
@@ -52,13 +50,21 @@ def ci_cache(ci_id, operate_type, record_id):
|
||||
|
||||
CITriggerManager.fire(operate_type, ci_dict, record_id)
|
||||
|
||||
ci_dict and CIRelationManager.build_by_attribute(ci_dict)
|
||||
|
||||
|
||||
@celery.task(name="cmdb.rebuild_relation_for_attribute_changed", queue=CMDB_QUEUE)
|
||||
@reconnect_db
|
||||
def rebuild_relation_for_attribute_changed(ci_type_relation):
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
|
||||
CIRelationManager.rebuild_all_by_attribute(ci_type_relation)
|
||||
|
||||
|
||||
@celery.task(name="cmdb.batch_ci_cache", queue=CMDB_QUEUE)
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def batch_ci_cache(ci_ids, ): # only for attribute change index
|
||||
time.sleep(1)
|
||||
|
||||
for ci_id in ci_ids:
|
||||
m = api.lib.cmdb.ci.CIManager()
|
||||
ci_dict = m.get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
|
||||
@@ -87,7 +93,6 @@ def ci_delete(ci_id):
|
||||
@celery.task(name="cmdb.delete_id_filter", queue=CMDB_QUEUE)
|
||||
@reconnect_db
|
||||
def delete_id_filter(ci_id):
|
||||
|
||||
CIFilterPermsCRUD().delete_id_filter_by_ci_id(ci_id)
|
||||
|
||||
|
||||
|
@@ -16,6 +16,7 @@ from api.lib.cmdb.const import RetKey
|
||||
from api.lib.cmdb.perms import has_perm_for_ci
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.utils import get_page
|
||||
from api.lib.utils import get_page_size
|
||||
@@ -76,6 +77,7 @@ class CIView(APIView):
|
||||
@has_perm_for_ci("ci_type", ResourceTypeEnum.CI, PermEnum.ADD, lambda x: CITypeCache.get(x))
|
||||
def post(self):
|
||||
ci_type = request.values.get("ci_type")
|
||||
ticket_id = request.values.pop("ticket_id", None)
|
||||
_no_attribute_policy = request.values.get("no_attribute_policy", ExistPolicy.IGNORE)
|
||||
|
||||
exist_policy = request.values.pop('exist_policy', None)
|
||||
@@ -87,6 +89,7 @@ class CIView(APIView):
|
||||
exist_policy=exist_policy or ExistPolicy.REJECT,
|
||||
_no_attribute_policy=_no_attribute_policy,
|
||||
_is_admin=request.values.pop('__is_admin', None) or False,
|
||||
ticket_id=ticket_id,
|
||||
**ci_dict)
|
||||
|
||||
return self.jsonify(ci_id=ci_id)
|
||||
@@ -95,6 +98,7 @@ class CIView(APIView):
|
||||
def put(self, ci_id=None):
|
||||
args = request.values
|
||||
ci_type = args.get("ci_type")
|
||||
ticket_id = request.values.pop("ticket_id", None)
|
||||
_no_attribute_policy = args.get("no_attribute_policy", ExistPolicy.IGNORE)
|
||||
|
||||
ci_dict = self._wrap_ci_dict()
|
||||
@@ -102,6 +106,7 @@ class CIView(APIView):
|
||||
if ci_id is not None:
|
||||
manager.update(ci_id,
|
||||
_is_admin=request.values.pop('__is_admin', None) or False,
|
||||
ticket_id=ticket_id,
|
||||
**ci_dict)
|
||||
else:
|
||||
request.values.pop('exist_policy', None)
|
||||
@@ -109,6 +114,7 @@ class CIView(APIView):
|
||||
exist_policy=ExistPolicy.REPLACE,
|
||||
_no_attribute_policy=_no_attribute_policy,
|
||||
_is_admin=request.values.pop('__is_admin', None) or False,
|
||||
ticket_id=ticket_id,
|
||||
**ci_dict)
|
||||
|
||||
return self.jsonify(ci_id=ci_id)
|
||||
@@ -221,7 +227,6 @@ class CIHeartbeatView(APIView):
|
||||
class CIFlushView(APIView):
|
||||
url_prefix = ("/ci/flush", "/ci/<int:ci_id>/flush")
|
||||
|
||||
# @auth_abandoned
|
||||
def get(self, ci_id=None):
|
||||
from api.tasks.cmdb import ci_cache
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
@@ -250,3 +255,23 @@ class CIPasswordView(APIView):
|
||||
|
||||
def post(self, ci_id, attr_id):
|
||||
return self.get(ci_id, attr_id)
|
||||
|
||||
|
||||
class CIBaselineView(APIView):
|
||||
url_prefix = ("/ci/baseline", "/ci/<int:ci_id>/baseline/rollback")
|
||||
|
||||
@args_required("before_date")
|
||||
def get(self):
|
||||
ci_ids = handle_arg_list(request.values.get('ci_ids'))
|
||||
before_date = request.values.get('before_date')
|
||||
|
||||
return self.jsonify(CIManager().baseline(list(map(int, ci_ids)), before_date))
|
||||
|
||||
@args_required("before_date")
|
||||
def post(self, ci_id):
|
||||
if 'rollback' in request.url:
|
||||
before_date = request.values.get('before_date')
|
||||
|
||||
return self.jsonify(**CIManager().rollback(ci_id, before_date))
|
||||
|
||||
return self.get(ci_id)
|
||||
|
@@ -30,6 +30,7 @@ class CIRelationSearchView(APIView):
|
||||
level: default is 1
|
||||
facet: statistic
|
||||
"""
|
||||
|
||||
page = get_page(request.values.get("page", 1))
|
||||
count = get_page_size(request.values.get("count") or request.values.get("page_size"))
|
||||
|
||||
@@ -86,6 +87,26 @@ class CIRelationStatisticsView(APIView):
|
||||
return self.jsonify(result)
|
||||
|
||||
|
||||
class CIRelationSearchFullView(APIView):
|
||||
url_prefix = "/ci_relations/search/full"
|
||||
|
||||
def get(self):
|
||||
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
|
||||
level = request.values.get('level', 1)
|
||||
type_ids = list(map(int, handle_arg_list(request.values.get('type_ids', []))))
|
||||
has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE')
|
||||
|
||||
start = time.time()
|
||||
s = Search(root_ids, level, has_m2m=has_m2m)
|
||||
try:
|
||||
result = s.search_full(type_ids)
|
||||
except SearchError as e:
|
||||
return abort(400, str(e))
|
||||
current_app.logger.debug("search time is :{0}".format(time.time() - start))
|
||||
|
||||
return self.jsonify(result)
|
||||
|
||||
|
||||
class GetSecondCIsView(APIView):
|
||||
url_prefix = "/ci_relations/<int:first_ci_id>/second_cis"
|
||||
|
||||
|
@@ -7,7 +7,6 @@ from io import BytesIO
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
from flask import session
|
||||
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
@@ -19,10 +18,12 @@ from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.ci_type import CITypeTemplateManager
|
||||
from api.lib.cmdb.ci_type import CITypeTriggerManager
|
||||
from api.lib.cmdb.ci_type import CITypeUniqueConstraintManager
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
@@ -36,6 +37,8 @@ from api.lib.perm.auth import auth_with_app_token
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class CITypeView(APIView):
|
||||
url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>",
|
||||
@@ -116,7 +119,6 @@ class CITypeInheritanceView(APIView):
|
||||
class CITypeGroupView(APIView):
|
||||
url_prefix = ("/ci_types/groups",
|
||||
"/ci_types/groups/config",
|
||||
"/ci_types/groups/order",
|
||||
"/ci_types/groups/<int:gid>")
|
||||
|
||||
def get(self):
|
||||
@@ -125,7 +127,8 @@ class CITypeGroupView(APIView):
|
||||
|
||||
return self.jsonify(CITypeGroupManager.get(need_other, config_required))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.create_CIType_group, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_validate(CITypeGroupManager.cls)
|
||||
def post(self):
|
||||
@@ -136,15 +139,6 @@ class CITypeGroupView(APIView):
|
||||
|
||||
@args_validate(CITypeGroupManager.cls)
|
||||
def put(self, gid=None):
|
||||
if "/order" in request.url:
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
group_ids = request.values.get('group_ids')
|
||||
CITypeGroupManager.order(group_ids)
|
||||
|
||||
return self.jsonify(group_ids=group_ids)
|
||||
|
||||
name = request.values.get('name') or abort(400, ErrFormat.argument_value_required.format("name"))
|
||||
type_ids = request.values.get('type_ids')
|
||||
|
||||
@@ -152,7 +146,8 @@ class CITypeGroupView(APIView):
|
||||
|
||||
return self.jsonify(gid=gid)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.delete_CIType_group, app_cli.admin_name)
|
||||
def delete(self, gid):
|
||||
type_ids = request.values.get("type_ids")
|
||||
CITypeGroupManager.delete(gid, type_ids)
|
||||
@@ -160,6 +155,18 @@ class CITypeGroupView(APIView):
|
||||
return self.jsonify(gid=gid)
|
||||
|
||||
|
||||
class CITypeGroupOrderView(APIView):
|
||||
url_prefix = "/ci_types/groups/order"
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.update_CIType_group, app_cli.admin_name)
|
||||
def put(self):
|
||||
group_ids = request.values.get('group_ids')
|
||||
CITypeGroupManager.order(group_ids)
|
||||
|
||||
return self.jsonify(group_ids=group_ids)
|
||||
|
||||
|
||||
class CITypeQueryView(APIView):
|
||||
url_prefix = "/ci_types/query"
|
||||
|
||||
@@ -352,14 +359,16 @@ class CITypeAttributeGroupView(APIView):
|
||||
class CITypeTemplateView(APIView):
|
||||
url_prefix = ("/ci_types/template/import", "/ci_types/template/export", "/ci_types/<int:type_id>/template/export")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def get(self, type_id=None): # export
|
||||
if type_id is not None:
|
||||
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template_by_type(type_id)))
|
||||
|
||||
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template()))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def post(self): # import
|
||||
tpt = request.values.get('ci_type_template') or {}
|
||||
|
||||
@@ -379,7 +388,8 @@ class CITypeCanDefineComputed(APIView):
|
||||
class CITypeTemplateFileView(APIView):
|
||||
url_prefix = ("/ci_types/template/import/file", "/ci_types/template/export/file")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def get(self): # export
|
||||
tpt_json = CITypeTemplateManager.export_template()
|
||||
tpt_json = dict(ci_type_template=tpt_json)
|
||||
@@ -394,7 +404,8 @@ class CITypeTemplateFileView(APIView):
|
||||
mimetype='application/json',
|
||||
max_age=0)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def post(self): # import
|
||||
f = request.files.get('file')
|
||||
|
||||
|
@@ -11,6 +11,8 @@ from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
@@ -18,6 +20,8 @@ from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class GetChildrenView(APIView):
|
||||
url_prefix = ("/ci_type_relations/<int:parent_id>/children",
|
||||
@@ -41,18 +45,22 @@ class GetParentsView(APIView):
|
||||
class CITypeRelationView(APIView):
|
||||
url_prefix = ("/ci_type_relations", "/ci_type_relations/<int:parent_id>/<int:child_id>")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
res = CITypeRelationManager.get()
|
||||
res, type2attributes = CITypeRelationManager.get()
|
||||
|
||||
return self.jsonify(res)
|
||||
return self.jsonify(relations=res, type2attributes=type2attributes)
|
||||
|
||||
@has_perm_from_args("parent_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||
@args_required("relation_type_id")
|
||||
def post(self, parent_id, child_id):
|
||||
relation_type_id = request.values.get("relation_type_id")
|
||||
constraint = request.values.get("constraint")
|
||||
ctr_id = CITypeRelationManager.add(parent_id, child_id, relation_type_id, constraint)
|
||||
parent_attr_id = request.values.get("parent_attr_id")
|
||||
child_attr_id = request.values.get("child_attr_id")
|
||||
ctr_id = CITypeRelationManager.add(parent_id, child_id, relation_type_id, constraint,
|
||||
parent_attr_id, child_attr_id)
|
||||
|
||||
return self.jsonify(ctr_id=ctr_id)
|
||||
|
||||
@@ -66,7 +74,8 @@ class CITypeRelationView(APIView):
|
||||
class CITypeRelationDelete2View(APIView):
|
||||
url_prefix = "/ci_type_relations/<int:ctr_id>"
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Relationships,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def delete(self, ctr_id):
|
||||
CITypeRelationManager.delete(ctr_id)
|
||||
|
||||
|
@@ -3,14 +3,16 @@
|
||||
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.custom_dashboard import CustomDashboardManager
|
||||
from api.lib.cmdb.custom_dashboard import SystemConfigManager
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class CustomDashboardApiView(APIView):
|
||||
url_prefix = ("/custom_dashboard", "/custom_dashboard/<int:_id>", "/custom_dashboard/batch",
|
||||
@@ -19,7 +21,8 @@ class CustomDashboardApiView(APIView):
|
||||
def get(self):
|
||||
return self.jsonify(CustomDashboardManager.get())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_validate(CustomDashboardManager.cls)
|
||||
def post(self):
|
||||
if request.url.endswith("/preview"):
|
||||
@@ -32,7 +35,8 @@ class CustomDashboardApiView(APIView):
|
||||
|
||||
return self.jsonify(res)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_validate(CustomDashboardManager.cls)
|
||||
def put(self, _id=None):
|
||||
if _id is not None:
|
||||
@@ -47,7 +51,8 @@ class CustomDashboardApiView(APIView):
|
||||
|
||||
return self.jsonify(id2options=request.values.get('id2options'))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def delete(self, _id):
|
||||
CustomDashboardManager.delete(_id)
|
||||
|
||||
@@ -57,12 +62,14 @@ class CustomDashboardApiView(APIView):
|
||||
class SystemConfigApiView(APIView):
|
||||
url_prefix = ("/system_config",)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name", value_required=True)
|
||||
def get(self):
|
||||
return self.jsonify(SystemConfigManager.get(request.values['name']))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_validate(SystemConfigManager.cls)
|
||||
@args_required("name", value_required=True)
|
||||
@args_required("option", value_required=True)
|
||||
@@ -74,7 +81,8 @@ class SystemConfigApiView(APIView):
|
||||
def put(self, _id=None):
|
||||
return self.post()
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
def delete(self):
|
||||
CustomDashboardManager.delete(request.values['name'])
|
||||
|
@@ -5,28 +5,29 @@ import datetime
|
||||
|
||||
from flask import abort
|
||||
from flask import request
|
||||
from flask import session
|
||||
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.history import AttributeHistoryManger
|
||||
from api.lib.cmdb.history import CITriggerHistoryManager
|
||||
from api.lib.cmdb.history import CITypeHistoryManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.lib.utils import get_page
|
||||
from api.lib.utils import get_page_size
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class RecordView(APIView):
|
||||
url_prefix = ("/history/records/attribute", "/history/records/relation")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
page = get_page(request.values.get("page", 1))
|
||||
page_size = get_page_size(request.values.get("page_size"))
|
||||
@@ -80,18 +81,21 @@ class CIHistoryView(APIView):
|
||||
|
||||
|
||||
class CITriggerHistoryView(APIView):
|
||||
url_prefix = ("/history/ci_triggers/<int:ci_id>", "/history/ci_triggers")
|
||||
url_prefix = ("/history/ci_triggers/<int:ci_id>",)
|
||||
|
||||
@has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.READ, CIManager.get_type_name)
|
||||
def get(self, ci_id=None):
|
||||
if ci_id is not None:
|
||||
result = CITriggerHistoryManager.get_by_ci_id(ci_id)
|
||||
def get(self, ci_id):
|
||||
result = CITriggerHistoryManager.get_by_ci_id(ci_id)
|
||||
|
||||
return self.jsonify(result)
|
||||
return self.jsonify(result)
|
||||
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
class CIsTriggerHistoryView(APIView):
|
||||
url_prefix = ("/history/ci_triggers",)
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
type_id = request.values.get("type_id")
|
||||
trigger_id = request.values.get("trigger_id")
|
||||
operate_type = request.values.get("operate_type")
|
||||
@@ -115,7 +119,8 @@ class CITriggerHistoryView(APIView):
|
||||
class CITypeHistoryView(APIView):
|
||||
url_prefix = "/history/ci_types"
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
type_id = request.values.get("type_id")
|
||||
username = request.values.get("username")
|
||||
|
@@ -8,20 +8,22 @@ from flask import request
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.lib.perm.acl.acl import validate_permission
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class PreferenceShowCITypesView(APIView):
|
||||
url_prefix = ("/preference/ci_types", "/preference/ci_types2")
|
||||
@@ -104,7 +106,8 @@ class PreferenceRelationApiView(APIView):
|
||||
|
||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_required("cr_ids")
|
||||
@args_validate(PreferenceManager.pref_rel_cls)
|
||||
@@ -118,14 +121,16 @@ class PreferenceRelationApiView(APIView):
|
||||
|
||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
def put(self, _id):
|
||||
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(_id=_id, **request.values)
|
||||
|
||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
def delete(self):
|
||||
name = request.values.get("name")
|
||||
|
@@ -4,14 +4,16 @@
|
||||
from flask import abort
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.relation_type import RelationTypeManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class RelationTypeView(APIView):
|
||||
url_prefix = ("/relation_types", "/relation_types/<int:rel_id>")
|
||||
@@ -19,7 +21,8 @@ class RelationTypeView(APIView):
|
||||
def get(self):
|
||||
return self.jsonify([i.to_dict() for i in RelationTypeManager.get_all()])
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_validate(RelationTypeManager.cls)
|
||||
def post(self):
|
||||
@@ -28,7 +31,8 @@ class RelationTypeView(APIView):
|
||||
|
||||
return self.jsonify(rel.to_dict())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_validate(RelationTypeManager.cls)
|
||||
def put(self, rel_id):
|
||||
@@ -37,7 +41,8 @@ class RelationTypeView(APIView):
|
||||
|
||||
return self.jsonify(rel.to_dict())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def delete(self, rel_id):
|
||||
RelationTypeManager.delete(rel_id)
|
||||
|
||||
|
@@ -42,9 +42,11 @@
|
||||
"relation-graph": "^1.1.0",
|
||||
"snabbdom": "^3.5.1",
|
||||
"sortablejs": "1.9.0",
|
||||
"style-resources-loader": "^1.5.0",
|
||||
"viser-vue": "^2.4.8",
|
||||
"vue": "2.6.11",
|
||||
"vue-clipboard2": "^0.3.3",
|
||||
"vue-cli-plugin-style-resources-loader": "^0.1.5",
|
||||
"vue-codemirror": "^4.0.6",
|
||||
"vue-cropper": "^0.6.2",
|
||||
"vue-grid-layout": "2.3.12",
|
||||
|
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3857903 */
|
||||
src: url('iconfont.woff2?t=1711618200417') format('woff2'),
|
||||
url('iconfont.woff?t=1711618200417') format('woff'),
|
||||
url('iconfont.ttf?t=1711618200417') format('truetype');
|
||||
src: url('iconfont.woff2?t=1713840593232') format('woff2'),
|
||||
url('iconfont.woff?t=1713840593232') format('woff'),
|
||||
url('iconfont.ttf?t=1713840593232') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,54 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.ops-setting-application-selected:before {
|
||||
content: "\e919";
|
||||
}
|
||||
|
||||
.ops-setting-application:before {
|
||||
content: "\e918";
|
||||
}
|
||||
|
||||
.ops-setting-basic:before {
|
||||
content: "\e889";
|
||||
}
|
||||
|
||||
.ops-setting-basic-selected:before {
|
||||
content: "\e917";
|
||||
}
|
||||
|
||||
.ops-setting-security:before {
|
||||
content: "\e915";
|
||||
}
|
||||
|
||||
.ops-setting-theme:before {
|
||||
content: "\e916";
|
||||
}
|
||||
|
||||
.veops-show:before {
|
||||
content: "\e914";
|
||||
}
|
||||
|
||||
.itsm-duration:before {
|
||||
content: "\e913";
|
||||
}
|
||||
|
||||
.itsm-workload:before {
|
||||
content: "\e912";
|
||||
}
|
||||
|
||||
.caise-VPC:before {
|
||||
content: "\e910";
|
||||
}
|
||||
|
||||
.caise-CDN:before {
|
||||
content: "\e911";
|
||||
}
|
||||
|
||||
.caise-OOS:before {
|
||||
content: "\e90f";
|
||||
}
|
||||
|
||||
.Google_Cloud_Platform:before {
|
||||
content: "\e90b";
|
||||
}
|
||||
@@ -245,10 +293,6 @@
|
||||
content: "\e8d5";
|
||||
}
|
||||
|
||||
.ops-setting-auth-selected:before {
|
||||
content: "\e8d4";
|
||||
}
|
||||
|
||||
.itsm-knowledge2:before {
|
||||
content: "\e8d2";
|
||||
}
|
||||
@@ -477,10 +521,6 @@
|
||||
content: "\e89c";
|
||||
}
|
||||
|
||||
.ops-setting-duty-selected:before {
|
||||
content: "\e89b";
|
||||
}
|
||||
|
||||
.datainsight-sequential:before {
|
||||
content: "\e899";
|
||||
}
|
||||
@@ -645,38 +685,6 @@
|
||||
content: "\e870";
|
||||
}
|
||||
|
||||
.ops-itsm-ticketsetting-selected:before {
|
||||
content: "\e860";
|
||||
}
|
||||
|
||||
.ops-itsm-reports-selected:before {
|
||||
content: "\e861";
|
||||
}
|
||||
|
||||
.ops-itsm-servicecatalog-selected:before {
|
||||
content: "\e862";
|
||||
}
|
||||
|
||||
.ops-itsm-ticketmanage-selected:before {
|
||||
content: "\e863";
|
||||
}
|
||||
|
||||
.ops-itsm-knowledge-selected:before {
|
||||
content: "\e864";
|
||||
}
|
||||
|
||||
.ops-itsm-workstation-selected:before {
|
||||
content: "\e865";
|
||||
}
|
||||
|
||||
.ops-itsm-servicedesk-selected:before {
|
||||
content: "\e866";
|
||||
}
|
||||
|
||||
.ops-itsm-planticket-selected:before {
|
||||
content: "\e867";
|
||||
}
|
||||
|
||||
.ops-itsm-servicecatalog:before {
|
||||
content: "\e868";
|
||||
}
|
||||
@@ -1049,26 +1057,10 @@
|
||||
content: "\e816";
|
||||
}
|
||||
|
||||
.ops-cmdb-batch-selected:before {
|
||||
content: "\e803";
|
||||
}
|
||||
|
||||
.ops-cmdb-batch:before {
|
||||
content: "\e80a";
|
||||
}
|
||||
|
||||
.ops-cmdb-adc-selected:before {
|
||||
content: "\e7f7";
|
||||
}
|
||||
|
||||
.ops-cmdb-resource-selected:before {
|
||||
content: "\e7f8";
|
||||
}
|
||||
|
||||
.ops-cmdb-preference-selected:before {
|
||||
content: "\e7f9";
|
||||
}
|
||||
|
||||
.ops-cmdb-preference:before {
|
||||
content: "\e7fa";
|
||||
}
|
||||
@@ -1077,22 +1069,10 @@
|
||||
content: "\e7fb";
|
||||
}
|
||||
|
||||
.ops-cmdb-tree-selected:before {
|
||||
content: "\e7fc";
|
||||
}
|
||||
|
||||
.ops-cmdb-relation-selected:before {
|
||||
content: "\e7fd";
|
||||
}
|
||||
|
||||
.ops-cmdb-adc:before {
|
||||
content: "\e7fe";
|
||||
}
|
||||
|
||||
.ops-cmdb-search-selected:before {
|
||||
content: "\e7ff";
|
||||
}
|
||||
|
||||
.ops-cmdb-relation:before {
|
||||
content: "\e800";
|
||||
}
|
||||
@@ -1101,14 +1081,6 @@
|
||||
content: "\e801";
|
||||
}
|
||||
|
||||
.ops-cmdb-citype-selected:before {
|
||||
content: "\e802";
|
||||
}
|
||||
|
||||
.ops-cmdb-dashboard-selected:before {
|
||||
content: "\e804";
|
||||
}
|
||||
|
||||
.ops-cmdb-citype:before {
|
||||
content: "\e805";
|
||||
}
|
||||
@@ -1117,10 +1089,6 @@
|
||||
content: "\e806";
|
||||
}
|
||||
|
||||
.ops-cmdb-screen-selected:before {
|
||||
content: "\e807";
|
||||
}
|
||||
|
||||
.ops-cmdb-resource:before {
|
||||
content: "\e808";
|
||||
}
|
||||
@@ -1469,14 +1437,6 @@
|
||||
content: "\e7a6";
|
||||
}
|
||||
|
||||
.ops-setting-role-selected:before {
|
||||
content: "\e7a0";
|
||||
}
|
||||
|
||||
.ops-setting-group-selected:before {
|
||||
content: "\e7a1";
|
||||
}
|
||||
|
||||
.ops-setting-role:before {
|
||||
content: "\e7a2";
|
||||
}
|
||||
@@ -1917,18 +1877,10 @@
|
||||
content: "\e738";
|
||||
}
|
||||
|
||||
.ops-setting-notice-email-selected-copy:before {
|
||||
content: "\e889";
|
||||
}
|
||||
|
||||
.ops-setting-notice:before {
|
||||
content: "\e72f";
|
||||
}
|
||||
|
||||
.ops-setting-notice-selected:before {
|
||||
content: "\e730";
|
||||
}
|
||||
|
||||
.ops-setting-notice-email-selected:before {
|
||||
content: "\e731";
|
||||
}
|
||||
@@ -1953,10 +1905,6 @@
|
||||
content: "\e736";
|
||||
}
|
||||
|
||||
.ops-setting-companyStructure-selected:before {
|
||||
content: "\e72b";
|
||||
}
|
||||
|
||||
.ops-setting-companyStructure:before {
|
||||
content: "\e72c";
|
||||
}
|
||||
@@ -1965,10 +1913,6 @@
|
||||
content: "\e72d";
|
||||
}
|
||||
|
||||
.ops-setting-companyInfo-selected:before {
|
||||
content: "\e72e";
|
||||
}
|
||||
|
||||
.ops-email:before {
|
||||
content: "\e61a";
|
||||
}
|
||||
@@ -3009,14 +2953,6 @@
|
||||
content: "\e600";
|
||||
}
|
||||
|
||||
.ops-dag-dashboard-selected:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.ops-dag-applet-selected:before {
|
||||
content: "\e602";
|
||||
}
|
||||
|
||||
.ops-dag-applet:before {
|
||||
content: "\e603";
|
||||
}
|
||||
@@ -3025,62 +2961,26 @@
|
||||
content: "\e604";
|
||||
}
|
||||
|
||||
.ops-dag-terminal-selected:before {
|
||||
content: "\e605";
|
||||
}
|
||||
|
||||
.ops-dag-cron:before {
|
||||
content: "\e606";
|
||||
}
|
||||
|
||||
.ops-dag-cron-selected:before {
|
||||
content: "\e608";
|
||||
}
|
||||
|
||||
.ops-dag-history:before {
|
||||
content: "\e609";
|
||||
}
|
||||
|
||||
.ops-dag-history-selected:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
|
||||
.ops-dag-dags-selected:before {
|
||||
content: "\e60c";
|
||||
}
|
||||
|
||||
.ops-dag-dagreview:before {
|
||||
content: "\e60d";
|
||||
}
|
||||
|
||||
.ops-dag-dagreview-selected:before {
|
||||
content: "\e60e";
|
||||
}
|
||||
|
||||
.ops-dag-panel:before {
|
||||
content: "\e60f";
|
||||
}
|
||||
|
||||
.ops-dag-panel-selected:before {
|
||||
content: "\e615";
|
||||
}
|
||||
|
||||
.ops-dag-variables:before {
|
||||
content: "\e616";
|
||||
}
|
||||
|
||||
.ops-dag-variables-selected:before {
|
||||
content: "\e618";
|
||||
}
|
||||
|
||||
.ops-dag-appletadmin:before {
|
||||
content: "\e65c";
|
||||
}
|
||||
|
||||
.ops-dag-appletadmin-selected:before {
|
||||
content: "\e65d";
|
||||
}
|
||||
|
||||
.ops-dag-dags:before {
|
||||
content: "\e60b";
|
||||
}
|
||||
|
@@ -5,6 +5,90 @@
|
||||
"css_prefix_text": "",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "40043662",
|
||||
"name": "ops-setting-application-selected",
|
||||
"font_class": "ops-setting-application-selected",
|
||||
"unicode": "e919",
|
||||
"unicode_decimal": 59673
|
||||
},
|
||||
{
|
||||
"icon_id": "40043685",
|
||||
"name": "ops-setting-application",
|
||||
"font_class": "ops-setting-application",
|
||||
"unicode": "e918",
|
||||
"unicode_decimal": 59672
|
||||
},
|
||||
{
|
||||
"icon_id": "40043049",
|
||||
"name": "ops-setting-basic",
|
||||
"font_class": "ops-setting-basic",
|
||||
"unicode": "e889",
|
||||
"unicode_decimal": 59529
|
||||
},
|
||||
{
|
||||
"icon_id": "40043047",
|
||||
"name": "ops-setting-basic-selected",
|
||||
"font_class": "ops-setting-basic-selected",
|
||||
"unicode": "e917",
|
||||
"unicode_decimal": 59671
|
||||
},
|
||||
{
|
||||
"icon_id": "40038753",
|
||||
"name": "ops-setting-security",
|
||||
"font_class": "ops-setting-security",
|
||||
"unicode": "e915",
|
||||
"unicode_decimal": 59669
|
||||
},
|
||||
{
|
||||
"icon_id": "40038752",
|
||||
"name": "ops-setting-theme",
|
||||
"font_class": "ops-setting-theme",
|
||||
"unicode": "e916",
|
||||
"unicode_decimal": 59670
|
||||
},
|
||||
{
|
||||
"icon_id": "39948814",
|
||||
"name": "veops-show",
|
||||
"font_class": "veops-show",
|
||||
"unicode": "e914",
|
||||
"unicode_decimal": 59668
|
||||
},
|
||||
{
|
||||
"icon_id": "39926816",
|
||||
"name": "itsm-duration",
|
||||
"font_class": "itsm-duration",
|
||||
"unicode": "e913",
|
||||
"unicode_decimal": 59667
|
||||
},
|
||||
{
|
||||
"icon_id": "39926833",
|
||||
"name": "itsm-workload (1)",
|
||||
"font_class": "itsm-workload",
|
||||
"unicode": "e912",
|
||||
"unicode_decimal": 59666
|
||||
},
|
||||
{
|
||||
"icon_id": "39782649",
|
||||
"name": "VPC",
|
||||
"font_class": "caise-VPC",
|
||||
"unicode": "e910",
|
||||
"unicode_decimal": 59664
|
||||
},
|
||||
{
|
||||
"icon_id": "39782643",
|
||||
"name": "CDN",
|
||||
"font_class": "caise-CDN",
|
||||
"unicode": "e911",
|
||||
"unicode_decimal": 59665
|
||||
},
|
||||
{
|
||||
"icon_id": "39782632",
|
||||
"name": "OOS",
|
||||
"font_class": "caise-OOS",
|
||||
"unicode": "e90f",
|
||||
"unicode_decimal": 59663
|
||||
},
|
||||
{
|
||||
"icon_id": "39729980",
|
||||
"name": "Google Cloud Platform",
|
||||
@@ -411,13 +495,6 @@
|
||||
"unicode": "e8d5",
|
||||
"unicode_decimal": 59605
|
||||
},
|
||||
{
|
||||
"icon_id": "38547389",
|
||||
"name": "setting-authentication-selected",
|
||||
"font_class": "ops-setting-auth-selected",
|
||||
"unicode": "e8d4",
|
||||
"unicode_decimal": 59604
|
||||
},
|
||||
{
|
||||
"icon_id": "38533133",
|
||||
"name": "itsm-knowledge (2)",
|
||||
@@ -817,13 +894,6 @@
|
||||
"unicode": "e89c",
|
||||
"unicode_decimal": 59548
|
||||
},
|
||||
{
|
||||
"icon_id": "37940033",
|
||||
"name": "ops-setting-duty-selected",
|
||||
"font_class": "ops-setting-duty-selected",
|
||||
"unicode": "e89b",
|
||||
"unicode_decimal": 59547
|
||||
},
|
||||
{
|
||||
"icon_id": "37841524",
|
||||
"name": "datainsight-sequential",
|
||||
@@ -1111,62 +1181,6 @@
|
||||
"unicode": "e870",
|
||||
"unicode_decimal": 59504
|
||||
},
|
||||
{
|
||||
"icon_id": "35984161",
|
||||
"name": "ops-itsm-ticketsetting-selected",
|
||||
"font_class": "ops-itsm-ticketsetting-selected",
|
||||
"unicode": "e860",
|
||||
"unicode_decimal": 59488
|
||||
},
|
||||
{
|
||||
"icon_id": "35984162",
|
||||
"name": "ops-itsm-reports-selected",
|
||||
"font_class": "ops-itsm-reports-selected",
|
||||
"unicode": "e861",
|
||||
"unicode_decimal": 59489
|
||||
},
|
||||
{
|
||||
"icon_id": "35984163",
|
||||
"name": "ops-itsm-servicecatalog-selected",
|
||||
"font_class": "ops-itsm-servicecatalog-selected",
|
||||
"unicode": "e862",
|
||||
"unicode_decimal": 59490
|
||||
},
|
||||
{
|
||||
"icon_id": "35984164",
|
||||
"name": "ops-itsm-ticketmanage-selected",
|
||||
"font_class": "ops-itsm-ticketmanage-selected",
|
||||
"unicode": "e863",
|
||||
"unicode_decimal": 59491
|
||||
},
|
||||
{
|
||||
"icon_id": "35984165",
|
||||
"name": "ops-itsm-knowledge-selected",
|
||||
"font_class": "ops-itsm-knowledge-selected",
|
||||
"unicode": "e864",
|
||||
"unicode_decimal": 59492
|
||||
},
|
||||
{
|
||||
"icon_id": "35984166",
|
||||
"name": "ops-itsm-workstation-selected",
|
||||
"font_class": "ops-itsm-workstation-selected",
|
||||
"unicode": "e865",
|
||||
"unicode_decimal": 59493
|
||||
},
|
||||
{
|
||||
"icon_id": "35984167",
|
||||
"name": "ops-itsm-servicedesk-selected",
|
||||
"font_class": "ops-itsm-servicedesk-selected",
|
||||
"unicode": "e866",
|
||||
"unicode_decimal": 59494
|
||||
},
|
||||
{
|
||||
"icon_id": "35984168",
|
||||
"name": "ops-itsm-planticket-selected",
|
||||
"font_class": "ops-itsm-planticket-selected",
|
||||
"unicode": "e867",
|
||||
"unicode_decimal": 59495
|
||||
},
|
||||
{
|
||||
"icon_id": "35984169",
|
||||
"name": "ops-itsm-servicecatalog",
|
||||
@@ -1818,13 +1832,6 @@
|
||||
"unicode": "e816",
|
||||
"unicode_decimal": 59414
|
||||
},
|
||||
{
|
||||
"icon_id": "35400645",
|
||||
"name": "ops-cmdb-batch-selected",
|
||||
"font_class": "ops-cmdb-batch-selected",
|
||||
"unicode": "e803",
|
||||
"unicode_decimal": 59395
|
||||
},
|
||||
{
|
||||
"icon_id": "35400646",
|
||||
"name": "ops-cmdb-batch",
|
||||
@@ -1832,27 +1839,6 @@
|
||||
"unicode": "e80a",
|
||||
"unicode_decimal": 59402
|
||||
},
|
||||
{
|
||||
"icon_id": "35395300",
|
||||
"name": "ops-cmdb-adc-selected",
|
||||
"font_class": "ops-cmdb-adc-selected",
|
||||
"unicode": "e7f7",
|
||||
"unicode_decimal": 59383
|
||||
},
|
||||
{
|
||||
"icon_id": "35395301",
|
||||
"name": "ops-cmdb-resource-selected",
|
||||
"font_class": "ops-cmdb-resource-selected",
|
||||
"unicode": "e7f8",
|
||||
"unicode_decimal": 59384
|
||||
},
|
||||
{
|
||||
"icon_id": "35395302",
|
||||
"name": "ops-cmdb-preference-selected",
|
||||
"font_class": "ops-cmdb-preference-selected",
|
||||
"unicode": "e7f9",
|
||||
"unicode_decimal": 59385
|
||||
},
|
||||
{
|
||||
"icon_id": "35395303",
|
||||
"name": "ops-cmdb-preference",
|
||||
@@ -1867,20 +1853,6 @@
|
||||
"unicode": "e7fb",
|
||||
"unicode_decimal": 59387
|
||||
},
|
||||
{
|
||||
"icon_id": "35395305",
|
||||
"name": "ops-cmdb-tree-selected",
|
||||
"font_class": "ops-cmdb-tree-selected",
|
||||
"unicode": "e7fc",
|
||||
"unicode_decimal": 59388
|
||||
},
|
||||
{
|
||||
"icon_id": "35395306",
|
||||
"name": "ops-cmdb-relation-selected",
|
||||
"font_class": "ops-cmdb-relation-selected",
|
||||
"unicode": "e7fd",
|
||||
"unicode_decimal": 59389
|
||||
},
|
||||
{
|
||||
"icon_id": "35395307",
|
||||
"name": "ops-cmdb-adc",
|
||||
@@ -1888,13 +1860,6 @@
|
||||
"unicode": "e7fe",
|
||||
"unicode_decimal": 59390
|
||||
},
|
||||
{
|
||||
"icon_id": "35395308",
|
||||
"name": "ops-cmdb-search-selected",
|
||||
"font_class": "ops-cmdb-search-selected",
|
||||
"unicode": "e7ff",
|
||||
"unicode_decimal": 59391
|
||||
},
|
||||
{
|
||||
"icon_id": "35395309",
|
||||
"name": "ops-cmdb-relation",
|
||||
@@ -1909,20 +1874,6 @@
|
||||
"unicode": "e801",
|
||||
"unicode_decimal": 59393
|
||||
},
|
||||
{
|
||||
"icon_id": "35395311",
|
||||
"name": "ops-cmdb-citype-selected",
|
||||
"font_class": "ops-cmdb-citype-selected",
|
||||
"unicode": "e802",
|
||||
"unicode_decimal": 59394
|
||||
},
|
||||
{
|
||||
"icon_id": "35395313",
|
||||
"name": "ops-cmdb-dashboard-selected",
|
||||
"font_class": "ops-cmdb-dashboard-selected",
|
||||
"unicode": "e804",
|
||||
"unicode_decimal": 59396
|
||||
},
|
||||
{
|
||||
"icon_id": "35395314",
|
||||
"name": "ops-cmdb-citype",
|
||||
@@ -1937,13 +1888,6 @@
|
||||
"unicode": "e806",
|
||||
"unicode_decimal": 59398
|
||||
},
|
||||
{
|
||||
"icon_id": "35395316",
|
||||
"name": "ops-cmdb-screen-selected",
|
||||
"font_class": "ops-cmdb-screen-selected",
|
||||
"unicode": "e807",
|
||||
"unicode_decimal": 59399
|
||||
},
|
||||
{
|
||||
"icon_id": "35395317",
|
||||
"name": "ops-cmdb-resource",
|
||||
@@ -2553,20 +2497,6 @@
|
||||
"unicode": "e7a6",
|
||||
"unicode_decimal": 59302
|
||||
},
|
||||
{
|
||||
"icon_id": "34792792",
|
||||
"name": "ops-setting-role-selected",
|
||||
"font_class": "ops-setting-role-selected",
|
||||
"unicode": "e7a0",
|
||||
"unicode_decimal": 59296
|
||||
},
|
||||
{
|
||||
"icon_id": "34792793",
|
||||
"name": "ops-setting-group-selected",
|
||||
"font_class": "ops-setting-group-selected",
|
||||
"unicode": "e7a1",
|
||||
"unicode_decimal": 59297
|
||||
},
|
||||
{
|
||||
"icon_id": "34792794",
|
||||
"name": "ops-setting-role",
|
||||
@@ -3337,13 +3267,6 @@
|
||||
"unicode": "e738",
|
||||
"unicode_decimal": 59192
|
||||
},
|
||||
{
|
||||
"icon_id": "37575490",
|
||||
"name": "ops-setting-notice-email-selected",
|
||||
"font_class": "ops-setting-notice-email-selected-copy",
|
||||
"unicode": "e889",
|
||||
"unicode_decimal": 59529
|
||||
},
|
||||
{
|
||||
"icon_id": "34108346",
|
||||
"name": "ops-setting-notice",
|
||||
@@ -3351,13 +3274,6 @@
|
||||
"unicode": "e72f",
|
||||
"unicode_decimal": 59183
|
||||
},
|
||||
{
|
||||
"icon_id": "34108348",
|
||||
"name": "ops-setting-notice-selected",
|
||||
"font_class": "ops-setting-notice-selected",
|
||||
"unicode": "e730",
|
||||
"unicode_decimal": 59184
|
||||
},
|
||||
{
|
||||
"icon_id": "34108504",
|
||||
"name": "ops-setting-notice-email-selected",
|
||||
@@ -3400,13 +3316,6 @@
|
||||
"unicode": "e736",
|
||||
"unicode_decimal": 59190
|
||||
},
|
||||
{
|
||||
"icon_id": "34108244",
|
||||
"name": "ops-setting-companyStructure-selected",
|
||||
"font_class": "ops-setting-companyStructure-selected",
|
||||
"unicode": "e72b",
|
||||
"unicode_decimal": 59179
|
||||
},
|
||||
{
|
||||
"icon_id": "34108296",
|
||||
"name": "ops-setting-companyStructure",
|
||||
@@ -3421,13 +3330,6 @@
|
||||
"unicode": "e72d",
|
||||
"unicode_decimal": 59181
|
||||
},
|
||||
{
|
||||
"icon_id": "34108330",
|
||||
"name": "ops-setting-companyInfo-selected",
|
||||
"font_class": "ops-setting-companyInfo-selected",
|
||||
"unicode": "e72e",
|
||||
"unicode_decimal": 59182
|
||||
},
|
||||
{
|
||||
"icon_id": "34099810",
|
||||
"name": "ops-email",
|
||||
@@ -5248,20 +5150,6 @@
|
||||
"unicode": "e600",
|
||||
"unicode_decimal": 58880
|
||||
},
|
||||
{
|
||||
"icon_id": "33053294",
|
||||
"name": "ops-dag-dashboard-selected",
|
||||
"font_class": "ops-dag-dashboard-selected",
|
||||
"unicode": "e601",
|
||||
"unicode_decimal": 58881
|
||||
},
|
||||
{
|
||||
"icon_id": "33053330",
|
||||
"name": "ops-dag-applet-selected",
|
||||
"font_class": "ops-dag-applet-selected",
|
||||
"unicode": "e602",
|
||||
"unicode_decimal": 58882
|
||||
},
|
||||
{
|
||||
"icon_id": "33053531",
|
||||
"name": "ops-dag-applet",
|
||||
@@ -5276,13 +5164,6 @@
|
||||
"unicode": "e604",
|
||||
"unicode_decimal": 58884
|
||||
},
|
||||
{
|
||||
"icon_id": "33053589",
|
||||
"name": "ops-dag-terminal-selected",
|
||||
"font_class": "ops-dag-terminal-selected",
|
||||
"unicode": "e605",
|
||||
"unicode_decimal": 58885
|
||||
},
|
||||
{
|
||||
"icon_id": "33053591",
|
||||
"name": "ops-dag-cron",
|
||||
@@ -5290,13 +5171,6 @@
|
||||
"unicode": "e606",
|
||||
"unicode_decimal": 58886
|
||||
},
|
||||
{
|
||||
"icon_id": "33053609",
|
||||
"name": "ops-dag-cron-selected",
|
||||
"font_class": "ops-dag-cron-selected",
|
||||
"unicode": "e608",
|
||||
"unicode_decimal": 58888
|
||||
},
|
||||
{
|
||||
"icon_id": "33053615",
|
||||
"name": "ops-dag-history",
|
||||
@@ -5304,20 +5178,6 @@
|
||||
"unicode": "e609",
|
||||
"unicode_decimal": 58889
|
||||
},
|
||||
{
|
||||
"icon_id": "33053617",
|
||||
"name": "ops-dag-history-selected",
|
||||
"font_class": "ops-dag-history-selected",
|
||||
"unicode": "e60a",
|
||||
"unicode_decimal": 58890
|
||||
},
|
||||
{
|
||||
"icon_id": "33053681",
|
||||
"name": "ops-dag-dags-selected",
|
||||
"font_class": "ops-dag-dags-selected",
|
||||
"unicode": "e60c",
|
||||
"unicode_decimal": 58892
|
||||
},
|
||||
{
|
||||
"icon_id": "33053682",
|
||||
"name": "ops-dag-dagreview",
|
||||
@@ -5325,13 +5185,6 @@
|
||||
"unicode": "e60d",
|
||||
"unicode_decimal": 58893
|
||||
},
|
||||
{
|
||||
"icon_id": "33053684",
|
||||
"name": "ops-dag-dagreview-selected",
|
||||
"font_class": "ops-dag-dagreview-selected",
|
||||
"unicode": "e60e",
|
||||
"unicode_decimal": 58894
|
||||
},
|
||||
{
|
||||
"icon_id": "33053691",
|
||||
"name": "ops-dag-panel",
|
||||
@@ -5339,13 +5192,6 @@
|
||||
"unicode": "e60f",
|
||||
"unicode_decimal": 58895
|
||||
},
|
||||
{
|
||||
"icon_id": "33053692",
|
||||
"name": "ops-dag-panel-selected",
|
||||
"font_class": "ops-dag-panel-selected",
|
||||
"unicode": "e615",
|
||||
"unicode_decimal": 58901
|
||||
},
|
||||
{
|
||||
"icon_id": "33053707",
|
||||
"name": "ops-dag-variables",
|
||||
@@ -5353,27 +5199,6 @@
|
||||
"unicode": "e616",
|
||||
"unicode_decimal": 58902
|
||||
},
|
||||
{
|
||||
"icon_id": "33053715",
|
||||
"name": "ops-dag-variables-selected",
|
||||
"font_class": "ops-dag-variables-selected",
|
||||
"unicode": "e618",
|
||||
"unicode_decimal": 58904
|
||||
},
|
||||
{
|
||||
"icon_id": "33053718",
|
||||
"name": "ops-dag-appletadmin",
|
||||
"font_class": "ops-dag-appletadmin",
|
||||
"unicode": "e65c",
|
||||
"unicode_decimal": 58972
|
||||
},
|
||||
{
|
||||
"icon_id": "33053720",
|
||||
"name": "ops-dag-appletadmin-selected",
|
||||
"font_class": "ops-dag-appletadmin-selected",
|
||||
"unicode": "e65d",
|
||||
"unicode_decimal": 58973
|
||||
},
|
||||
{
|
||||
"icon_id": "33055163",
|
||||
"name": "ops-dag-dags",
|
||||
|
@@ -1,14 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0H2.5V1.25H1.25V2.5H0V1C0 0.447715 0.447715 0 1 0ZM0 7.5V9C0 9.55229 0.447715 10 1 10H2.5V8.75H1.25V7.5H0ZM8.75 7.5V8.75H7.5V10H9C9.55229 10 10 9.55228 10 9V7.5H8.75ZM10 2.5V1C10 0.447715 9.55228 0 9 0H7.5V1.25H8.75V2.5H10Z" fill="url(#paint0_linear_124_16807)"/>
|
||||
<rect x="2.5" y="3.125" width="5" height="3.75" fill="url(#paint1_linear_124_16807)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16807" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4F84FF"/>
|
||||
<stop offset="1" stop-color="#85CBFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_124_16807" x1="5" y1="3.125" x2="5" y2="6.875" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4F84FF"/>
|
||||
<stop offset="1" stop-color="#85CBFF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 916 B |
@@ -1,14 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.5" y="2.5" width="5" height="5" rx="0.5" fill="url(#paint0_linear_124_16808)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0C0.447715 0 0 0.447715 0 1V9C0 9.55229 0.447715 10 1 10H9C9.55229 10 10 9.55228 10 9V1C10 0.447715 9.55228 0 9 0H1ZM8.75 1.25H1.25V8.75H8.75V1.25Z" fill="url(#paint1_linear_124_16808)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16808" x1="5" y1="2.5" x2="5" y2="7.5" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5187FF"/>
|
||||
<stop offset="1" stop-color="#84C9FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_124_16808" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5187FF"/>
|
||||
<stop offset="1" stop-color="#84C9FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 840 B |
@@ -1,9 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.56845 8.8409C3.06335 8.78963 1.86719 8.05799 2.06279 6.48243C2.1538 5.75105 2.64549 5.3214 3.34457 5.16041C3.67173 5.08909 4.00806 5.06954 4.34128 5.10247C4.40203 5.10811 4.44843 5.11401 4.47689 5.11837L4.51586 5.12631C4.64379 5.15574 4.77263 5.18104 4.90218 5.20219C5.26786 5.2651 5.63914 5.28941 6.0099 5.27474C6.8046 5.23219 7.21015 4.97429 7.23092 4.41672C7.25424 3.79429 6.76332 3.29619 5.86659 2.91832C5.52815 2.77793 5.17843 2.66645 4.82117 2.58506C4.70325 2.55755 4.58482 2.53328 4.46587 2.51226C4.30323 2.94847 3.9867 3.31016 3.57591 3.5292C3.16512 3.74824 2.68841 3.80952 2.23557 3.70149C1.90324 3.61651 1.60053 3.44214 1.36029 3.1973C1.12004 2.95245 0.951447 2.64649 0.872793 2.3126C0.794138 1.97872 0.808429 1.62967 0.914116 1.30333C1.0198 0.976995 1.21285 0.685836 1.4723 0.461451C1.73176 0.237065 2.04771 0.0880244 2.38588 0.0305017C2.72404 -0.0270211 3.07151 0.00917138 3.39056 0.135152C3.70961 0.261132 3.98807 0.472088 4.19571 0.745127C4.40335 1.01817 4.53225 1.34286 4.56841 1.68397C4.6812 1.70269 4.83374 1.73217 5.01524 1.77421C5.42003 1.86601 5.81625 1.99216 6.1996 2.15131C7.38191 2.64966 8.1156 3.39463 8.07638 4.4462C8.03639 5.53187 7.23425 6.04253 6.0563 6.10533C5.62418 6.12373 5.19132 6.09614 4.76503 6.02304C4.61925 5.99997 4.47398 5.9716 4.32923 5.93793C4.30731 5.93532 4.28534 5.9331 4.26335 5.93127C4.02033 5.90687 3.77501 5.92018 3.53606 5.97075C3.15153 6.05893 2.94311 6.24146 2.90056 6.58267C2.78725 7.49504 3.47915 7.94443 5.42694 8.00416C5.44492 7.65558 5.5586 7.3187 5.75548 7.03049C5.95237 6.74229 6.22485 6.51389 6.54303 6.37039C6.8612 6.22689 7.21277 6.17383 7.55912 6.21703C7.90548 6.26023 8.23323 6.39802 8.50641 6.61528C8.77959 6.83254 8.98763 7.12086 9.10769 7.4486C9.22775 7.77634 9.25519 8.13082 9.187 8.47314C9.11881 8.81545 8.95763 9.13235 8.72114 9.38907C8.48465 9.64578 8.18201 9.83237 7.84643 9.92836C7.39921 10.0556 6.92094 10.0153 6.50129 9.81515C6.08164 9.61495 5.74941 9.26855 5.56691 8.8409H5.56845Z" fill="url(#paint0_linear_124_16804)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16804" x1="5.02318" y1="0.00390625" x2="5.02318" y2="10.0013" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#497DFF"/>
|
||||
<stop offset="1" stop-color="#8CD5FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -1,9 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.01211 4.50621L2.7769 5.74077C2.712 5.80565 2.66051 5.88268 2.62538 5.96747C2.59025 6.05225 2.57217 6.14313 2.57217 6.2349C2.57217 6.32668 2.59025 6.41755 2.62538 6.50234C2.66051 6.58712 2.712 6.66416 2.7769 6.72904L3.27085 7.223C3.33573 7.28791 3.41276 7.3394 3.49754 7.37453C3.58232 7.40966 3.67319 7.42774 3.76496 7.42774C3.85674 7.42774 3.94761 7.40966 4.03239 7.37453C4.11717 7.3394 4.1942 7.28791 4.25908 7.223L5.49394 5.98775C5.6237 6.1175 5.72663 6.27155 5.79686 6.44109C5.86708 6.61063 5.90323 6.79234 5.90323 6.97585C5.90323 7.15935 5.86708 7.34106 5.79686 7.5106C5.72663 7.68014 5.6237 7.83419 5.49394 7.96394L3.76479 9.69316C3.56827 9.88963 3.30176 10 3.02387 10C2.74599 10 2.47948 9.88963 2.28296 9.69316L0.306832 7.71696C0.110368 7.52043 0 7.25391 0 6.97602C0 6.69813 0.110368 6.43161 0.306832 6.23508L2.03599 4.50586C2.16574 4.3761 2.31978 4.27317 2.48931 4.20294C2.65884 4.13271 2.84055 4.09657 3.02405 4.09657C3.20755 4.09657 3.38925 4.13271 3.55879 4.20294C3.72832 4.27317 3.88236 4.3761 4.01211 4.50586V4.50621ZM5.98789 5.49414L7.2231 4.25923C7.288 4.19435 7.33949 4.11732 7.37462 4.03253C7.40975 3.94775 7.42783 3.85687 7.42783 3.7651C7.42783 3.67332 7.40975 3.58245 7.37462 3.49766C7.33949 3.41288 7.288 3.33584 7.2231 3.27096L6.72915 2.777C6.66428 2.71209 6.58724 2.6606 6.50246 2.62547C6.41768 2.59034 6.32681 2.57226 6.23504 2.57226C6.14326 2.57226 6.05239 2.59034 5.96761 2.62547C5.88283 2.6606 5.8058 2.71209 5.74092 2.777L4.50606 4.01225C4.3763 3.8825 4.27337 3.72845 4.20314 3.55891C4.13292 3.38937 4.09677 3.20766 4.09677 3.02415C4.09677 2.84065 4.13292 2.65894 4.20314 2.4894C4.27337 2.31986 4.3763 2.16581 4.50606 2.03606L6.23521 0.306843C6.43173 0.110371 6.69824 0 6.97613 0C7.25401 0 7.52052 0.110371 7.71704 0.306843L9.69317 2.28304C9.88963 2.47957 10 2.74609 10 3.02398C10 3.30187 9.88963 3.56839 9.69317 3.76492L7.96401 5.49414C7.83426 5.6239 7.68022 5.72683 7.51069 5.79706C7.34116 5.86729 7.15945 5.90343 6.97595 5.90343C6.79245 5.90343 6.61075 5.86729 6.44121 5.79706C6.27168 5.72683 6.11764 5.6239 5.98789 5.49414ZM3.51817 5.9881L5.98789 3.51829C6.05339 3.45274 6.14225 3.4159 6.23491 3.41586C6.32758 3.41583 6.41646 3.45261 6.48201 3.51812C6.54755 3.58362 6.5844 3.67248 6.58443 3.76515C6.58446 3.85782 6.54768 3.9467 6.48218 4.01225L4.01211 6.48206C3.94661 6.54761 3.85775 6.58445 3.76509 6.58449C3.67242 6.58452 3.58354 6.54774 3.51799 6.48223C3.45245 6.41673 3.4156 6.32787 3.41557 6.2352C3.41554 6.14253 3.45232 6.05365 3.51782 5.9881H3.51817Z" fill="url(#paint0_linear_124_16775)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16775" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5A85FF"/>
|
||||
<stop offset="1" stop-color="#8DD8FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -1,9 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.31822 4.16667H2.54549V2.5C2.54549 1.11458 3.63981 0 5.00003 0C6.36026 0 7.45458 1.11458 7.45458 2.5V4.16667H8.68185C8.90685 4.16667 9.09094 4.35417 9.09094 4.58333V9.58333C9.09094 9.8125 8.90685 10 8.68185 10H1.31822C1.09322 10 0.909124 9.8125 0.909124 9.58333V4.58333C0.909124 4.35417 1.09322 4.16667 1.31822 4.16667ZM5.00003 7.91667C5.45003 7.91667 5.81822 7.54167 5.81822 7.08333C5.81822 6.625 5.45003 6.25 5.00003 6.25C4.55003 6.25 4.18185 6.625 4.18185 7.08333C4.18185 7.54167 4.55003 7.91667 5.00003 7.91667ZM3.36367 4.16667H6.6364V2.5C6.6364 1.58333 5.90003 0.833333 5.00003 0.833333C4.10003 0.833333 3.36367 1.58333 3.36367 2.5V4.16667Z" fill="url(#paint0_linear_124_16805)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16805" x1="5.00003" y1="0" x2="5.00003" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4D82FF"/>
|
||||
<stop offset="1" stop-color="#88CFFF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 1022 B |
@@ -1,9 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.91242 9.46382C3.91242 9.57428 3.82288 9.66382 3.71242 9.66382H2.35075C2.2403 9.66382 2.15075 9.57428 2.15075 9.46382V3.55962C2.15075 3.44916 2.06121 3.35962 1.95075 3.35962H0.539905C0.354312 3.35962 0.268806 3.12879 0.40961 3.00788L3.58212 0.283626C3.71182 0.172253 3.91242 0.264405 3.91242 0.43536V9.46382ZM6.08758 0.567715C6.08758 0.457258 6.17712 0.367716 6.28758 0.367716H7.64925C7.7597 0.367716 7.84925 0.457259 7.84925 0.567716V6.4411C7.84925 6.55156 7.93879 6.6411 8.04925 6.6411H9.46001C9.64561 6.6411 9.73111 6.87195 9.59029 6.99285L6.41786 9.71645C6.28816 9.8278 6.08758 9.73565 6.08758 9.5647V0.567715Z" fill="url(#paint0_linear_124_16806)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16806" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5A85FF"/>
|
||||
<stop offset="1" stop-color="#8DD8FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 979 B |
@@ -1,9 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.51961 6.8937V10H4.48V6.8937H1.76732C1.65823 6.8937 1.56223 6.85372 1.48369 6.77237C1.40522 6.69504 1.3621 6.5915 1.36369 6.48421C1.36369 5.95891 1.52769 5.48566 1.85895 5.06411C2.18986 4.64428 2.56258 4.43334 2.97893 4.43334V1.64277C2.75966 1.64277 2.57167 1.56142 2.41022 1.39873C2.25355 1.24349 2.16738 1.0362 2.17022 0.821384C2.17022 0.598718 2.25022 0.407762 2.41022 0.244037C2.56912 0.0827244 2.7593 0 2.97893 0H7.01959C7.23885 0 7.42685 0.0813456 7.5883 0.244037C7.74721 0.406728 7.82866 0.598718 7.82866 0.821384C7.82866 1.04405 7.74866 1.23501 7.58867 1.39873C7.4283 1.5628 7.23885 1.64277 7.01959 1.64277V4.43196C7.43594 4.43196 7.81012 4.64291 8.13956 5.06273C8.46631 5.47151 8.64098 5.97137 8.63628 6.48421C8.63628 6.59486 8.59701 6.6924 8.51665 6.77237C8.43665 6.85234 8.34211 6.8937 8.23302 6.8937H5.51998H5.51961Z" fill="url(#paint0_linear_124_16803)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16803" x1="5.00001" y1="0" x2="5.00001" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5A85FF"/>
|
||||
<stop offset="1" stop-color="#8DD8FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 35 KiB |
@@ -69,12 +69,11 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.custom-drawer-close {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
background: #custom_colors[color_1];
|
||||
background: @primary-color;
|
||||
color: white;
|
||||
text-align: center;
|
||||
transition: all 0.3s;
|
||||
|
@@ -1022,17 +1022,14 @@ export const multicolorIconList = [
|
||||
value: 'caise-tomcat',
|
||||
label: 'Tomcat'
|
||||
}, {
|
||||
value: 'caise-aliyun',
|
||||
label: '阿里云'
|
||||
value: 'caise-VPC',
|
||||
label: 'VPC'
|
||||
}, {
|
||||
value: 'caise-tengxunyun',
|
||||
label: '腾讯云'
|
||||
value: 'caise-CDN',
|
||||
label: 'CDN'
|
||||
}, {
|
||||
value: 'caise-huaweiyun',
|
||||
label: '华为云'
|
||||
}, {
|
||||
value: 'caise-aws',
|
||||
label: 'AWS'
|
||||
value: 'caise-OOS',
|
||||
label: '对象存储'
|
||||
}]
|
||||
}, {
|
||||
value: 'data',
|
||||
|
@@ -230,7 +230,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
.employee-transfer {
|
||||
width: 100%;
|
||||
.vue-treeselect__multi-value-item-container {
|
||||
@@ -263,7 +262,6 @@ export default {
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.employee-transfer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -300,14 +298,14 @@ export default {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
@@ -146,7 +146,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.regex-select {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
@@ -162,7 +161,7 @@ export default {
|
||||
cursor: pointer;
|
||||
&-selected,
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,7 +179,7 @@ export default {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #000000;
|
||||
border-left: 2px solid #custom_colors[color_1];
|
||||
border-left: 2px solid @primary-color;
|
||||
padding-left: 6px;
|
||||
margin-left: -6px;
|
||||
}
|
||||
|
@@ -126,7 +126,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.role-transfer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -186,14 +185,14 @@ export default {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
@@ -60,7 +60,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.sidebar-list-item {
|
||||
.ops_popover_item();
|
||||
margin: 2px 0;
|
||||
@@ -94,7 +93,7 @@ export default {
|
||||
background-color: transparent;
|
||||
}
|
||||
.sidebar-list-item.sidebar-list-item-selected::before {
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
}
|
||||
.sidebar-list-item-dotline {
|
||||
padding-left: 20px;
|
||||
|
@@ -52,14 +52,12 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.two-column-layout {
|
||||
margin-bottom: -24px;
|
||||
width: 100%;
|
||||
.two-column-layout-sidebar {
|
||||
height: 100%;
|
||||
border-radius: 15px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.two-column-layout-main {
|
||||
|
@@ -4,34 +4,22 @@
|
||||
@click="jumpTo"
|
||||
v-if="showTitle && !collapsed"
|
||||
style="width: 100%; height: 100%; cursor: pointer"
|
||||
:src="file_name ? `/api/common-setting/v1/file/${file_name}` : require('@/assets/logo_VECMDB.png')"
|
||||
:src="require('@/assets/logo_VECMDB.png')"
|
||||
/>
|
||||
<img
|
||||
@click="jumpTo"
|
||||
v-else
|
||||
style="width: 32px; height: 32px; margin-left: 24px; cursor: pointer"
|
||||
:src="small_file_name ? `/api/common-setting/v1/file/${small_file_name}` : require('@/assets/logo.png')"
|
||||
:src="require('@/assets/logo.png')"
|
||||
/>
|
||||
<!-- <logo-svg/> -->
|
||||
<!-- <img v-if="showTitle" style="width:92px;height: 32px" src="@/assets/OneOps.png" /> -->
|
||||
<!-- <h1 v-if="showTitle">{{ title }}</h1> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import LogoSvg from '@/assets/logo.svg?inline'
|
||||
import { mapState } from 'vuex'
|
||||
export default {
|
||||
name: 'Logo',
|
||||
components: {
|
||||
// LogoSvg,
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
file_name: (state) => state.logo.file_name,
|
||||
small_file_name: (state) => state.logo.small_file_name,
|
||||
}),
|
||||
},
|
||||
components: {},
|
||||
computed: {},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
|
@@ -1,15 +1,11 @@
|
||||
<template>
|
||||
<div class="top-menu" v-if="routes.length > 2">
|
||||
<!-- <a-menu v-model="current" mode="horizontal">
|
||||
<a-menu-item :key="route.name" v-for="route in routes.slice(0, routes.length - 1)">
|
||||
<router-link :to="{ name: route.name }">{{ route.meta.title }}</router-link>
|
||||
</a-menu-item>
|
||||
</a-menu>-->
|
||||
<span
|
||||
:class="current === route.name ? 'top-menu-selected' : ''"
|
||||
v-for="route in defaultShowRoutes"
|
||||
:key="route.name"
|
||||
@click="() => handleClick(route)"
|
||||
:title="$t(route.meta.title)"
|
||||
>
|
||||
{{ route.meta.title }}
|
||||
</span>
|
||||
@@ -102,15 +98,6 @@ export default {
|
||||
|
||||
<style lang="less">
|
||||
@import '../../style/static.less';
|
||||
// .top-menu {
|
||||
// display: inline-block;
|
||||
// }
|
||||
// .ant-menu-horizontal {
|
||||
// border-bottom: 0 !important;
|
||||
// }
|
||||
// .ant-menu-horizontal > .ant-menu-item {
|
||||
// border-bottom: 0;
|
||||
// }
|
||||
|
||||
.top-menu {
|
||||
display: inline-flex;
|
||||
@@ -129,14 +116,22 @@ export default {
|
||||
margin: 0 5px;
|
||||
color: @layout-header-font-color;
|
||||
height: @layout-header-height;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
line-height: @layout-header-line-height;
|
||||
display: inline-block;
|
||||
}
|
||||
> span:hover,
|
||||
.top-menu-selected {
|
||||
font-weight: bold;
|
||||
color: @layout-header-font-selected-color;
|
||||
}
|
||||
> span::before {
|
||||
display: block;
|
||||
content: attr(title);
|
||||
font-weight: bold;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.top-menu-dropdown.ant-popover-placement-bottom .ant-popover-content {
|
||||
|
@@ -96,10 +96,9 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
.color {
|
||||
color: #custom_colors[color_1];
|
||||
background-color: #custom_colors[color_2];
|
||||
color: @primary-color;
|
||||
background-color: @primary-color_5;
|
||||
}
|
||||
.custom-user {
|
||||
.custom-user-item {
|
||||
@@ -118,7 +117,7 @@ export default {
|
||||
.locale {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -9,25 +9,11 @@
|
||||
import gridSvg from '@/assets/icons/grid.svg?inline'
|
||||
import top_agent from '@/assets/icons/top_agent.svg?inline'
|
||||
import top_acl from '@/assets/icons/top_acl.svg?inline'
|
||||
import ops_default_show from '@/assets/icons/ops-default_show.svg?inline'
|
||||
import ops_is_choice from '@/assets/icons/ops-is_choice.svg?inline'
|
||||
import ops_is_index from '@/assets/icons/ops-is_index.svg?inline'
|
||||
import ops_is_link from '@/assets/icons/ops-is_link.svg?inline'
|
||||
import ops_is_password from '@/assets/icons/ops-is_password.svg?inline'
|
||||
import ops_is_sortable from '@/assets/icons/ops-is_sortable.svg?inline'
|
||||
import ops_is_unique from '@/assets/icons/ops-is_unique.svg?inline'
|
||||
import ops_move_icon from '@/assets/icons/ops-move-icon.svg?inline'
|
||||
|
||||
export {
|
||||
gridSvg,
|
||||
top_agent,
|
||||
top_acl,
|
||||
ops_default_show,
|
||||
ops_is_choice,
|
||||
ops_is_index,
|
||||
ops_is_link,
|
||||
ops_is_password,
|
||||
ops_is_sortable,
|
||||
ops_is_unique,
|
||||
ops_move_icon
|
||||
}
|
||||
|
14
cmdb-ui/src/directive/highlight/highlight.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import './highlight.less'
|
||||
|
||||
const highlight = (el, binding) => {
|
||||
if (binding.value.value) {
|
||||
let testValue = `${binding.value.value}`
|
||||
if (['(', ')', '$'].includes(testValue)) {
|
||||
testValue = `\\${testValue}`
|
||||
}
|
||||
const regex = new RegExp(`(${testValue})`, 'gi')
|
||||
el.innerHTML = el.innerText.replace(regex, `<span class='${binding.value.class ?? 'ops-text-highlight'}'>$1</span>`)
|
||||
}
|
||||
}
|
||||
|
||||
export default highlight
|
5
cmdb-ui/src/directive/highlight/highlight.less
Normal file
@@ -0,0 +1,5 @@
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.ops-text-highlight {
|
||||
background-color: @primary-color_3;
|
||||
}
|
12
cmdb-ui/src/directive/highlight/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import hightlight from './highlight'
|
||||
|
||||
const install = function (Vue) {
|
||||
Vue.directive('hightlight', hightlight)
|
||||
}
|
||||
|
||||
if (window.Vue) {
|
||||
window.hightlight = hightlight
|
||||
Vue.use(install); // eslint-disable-line
|
||||
}
|
||||
hightlight.install = install
|
||||
export default hightlight
|
13
cmdb-ui/src/directive/waves/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import waves from './waves'
|
||||
|
||||
const install = function (Vue) {
|
||||
Vue.directive('waves', waves)
|
||||
}
|
||||
|
||||
if (window.Vue) {
|
||||
window.waves = waves
|
||||
Vue.use(install); // eslint-disable-line
|
||||
}
|
||||
|
||||
waves.install = install
|
||||
export default waves
|
26
cmdb-ui/src/directive/waves/waves.css
Normal file
@@ -0,0 +1,26 @@
|
||||
.waves-ripple {
|
||||
position: absolute;
|
||||
border-radius: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
background-clip: padding-box;
|
||||
pointer-events: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-transform: scale(0);
|
||||
-ms-transform: scale(0);
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.waves-ripple.z-active {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(2);
|
||||
-ms-transform: scale(2);
|
||||
transform: scale(2);
|
||||
-webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
|
||||
}
|
72
cmdb-ui/src/directive/waves/waves.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import './waves.css'
|
||||
|
||||
const context = '@@wavesContext'
|
||||
|
||||
function handleClick(el, binding) {
|
||||
function handle(e) {
|
||||
const customOpts = Object.assign({}, binding.value)
|
||||
const opts = Object.assign({
|
||||
ele: el, // 波纹作用元素
|
||||
type: 'hit', // hit 点击位置扩散 center中心点扩展
|
||||
color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
|
||||
},
|
||||
customOpts
|
||||
)
|
||||
const target = opts.ele
|
||||
if (target) {
|
||||
target.style.position = 'relative'
|
||||
target.style.overflow = 'hidden'
|
||||
const rect = target.getBoundingClientRect()
|
||||
let ripple = target.querySelector('.waves-ripple')
|
||||
if (!ripple) {
|
||||
ripple = document.createElement('span')
|
||||
ripple.className = 'waves-ripple'
|
||||
ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
|
||||
target.appendChild(ripple)
|
||||
} else {
|
||||
ripple.className = 'waves-ripple'
|
||||
}
|
||||
switch (opts.type) {
|
||||
case 'center':
|
||||
ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
|
||||
ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
|
||||
break
|
||||
default:
|
||||
ripple.style.top =
|
||||
(e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop ||
|
||||
document.body.scrollTop) + 'px'
|
||||
ripple.style.left =
|
||||
(e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft ||
|
||||
document.body.scrollLeft) + 'px'
|
||||
}
|
||||
ripple.style.backgroundColor = opts.color
|
||||
ripple.className = 'waves-ripple z-active'
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (!el[context]) {
|
||||
el[context] = {
|
||||
removeHandle: handle
|
||||
}
|
||||
} else {
|
||||
el[context].removeHandle = handle
|
||||
}
|
||||
|
||||
return handle
|
||||
}
|
||||
|
||||
export default {
|
||||
bind(el, binding) {
|
||||
el.addEventListener('click', handleClick(el, binding), false)
|
||||
},
|
||||
update(el, binding) {
|
||||
el.removeEventListener('click', el[context].removeHandle, false)
|
||||
el.addEventListener('click', handleClick(el, binding), false)
|
||||
},
|
||||
unbind(el) {
|
||||
el.removeEventListener('click', el[context].removeHandle, false)
|
||||
el[context] = null
|
||||
delete el[context]
|
||||
}
|
||||
}
|
@@ -31,7 +31,6 @@ router.beforeEach(async (to, from, next) => {
|
||||
store.dispatch("loadAllUsers")
|
||||
store.dispatch("loadAllEmployees")
|
||||
store.dispatch("loadAllDepartments")
|
||||
store.dispatch("getCompanyInfo")
|
||||
store.dispatch('GenerateRoutes', { roles }).then(() => {
|
||||
router.addRoutes(store.getters.appRoutes)
|
||||
const redirect = decodeURIComponent(from.query.redirect || to.path)
|
||||
|
@@ -233,7 +233,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-history {
|
||||
border-radius: @border-radius-box;
|
||||
|
@@ -32,7 +32,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-operation-history {
|
||||
border-radius: @border-radius-box;
|
||||
|
@@ -189,7 +189,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-resource-types {
|
||||
border-radius: @border-radius-box;
|
||||
|
@@ -352,7 +352,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-resources {
|
||||
border-radius: @border-radius-box;
|
||||
|
@@ -285,7 +285,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-roles {
|
||||
border-radius: @border-radius-box;
|
||||
|
@@ -88,7 +88,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-secret-key {
|
||||
background-color: #fff;
|
||||
|
@@ -320,7 +320,6 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-trigger {
|
||||
border-radius: @border-radius-box;
|
||||
|
@@ -188,7 +188,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-users {
|
||||
border-radius: @border-radius-box;
|
||||
|
@@ -73,3 +73,11 @@ export function deleteCIRelationView(firstCiId, secondCiId, data) {
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function searchCIRelationFull(params) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_relations/search/full`,
|
||||
method: 'GET',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
@@ -30,11 +30,11 @@ export function getRelationTypes(CITypeID, parameter) {
|
||||
})
|
||||
}
|
||||
|
||||
export function createRelation(parentId, childrenId, relationTypeId, constraint) {
|
||||
export function createRelation(parentId, childrenId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_type_relations/${parentId}/${childrenId}`,
|
||||
method: 'post',
|
||||
data: { relation_type_id: relationTypeId, constraint }
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ export function deleteRelation(parentId, childrenId) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_type_relations/${parentId}/${childrenId}`,
|
||||
method: 'delete'
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -54,3 +54,36 @@ export function getCiTriggersByCiId(ci_id, params) {
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getCiRelatedTickets(params) {
|
||||
return axios({
|
||||
url: `/itsm/v1/process_ticket/get_tickets_by`,
|
||||
method: 'POST',
|
||||
data: params,
|
||||
isShowMessage: false
|
||||
})
|
||||
}
|
||||
|
||||
export function judgeItsmInstalled() {
|
||||
return axios({
|
||||
url: `/itsm/v1/process_ticket/itsm_existed`,
|
||||
method: 'GET',
|
||||
isShowMessage: false
|
||||
})
|
||||
}
|
||||
|
||||
export function getCIsBaseline(params) {
|
||||
return axios({
|
||||
url: `/v0.1/ci/baseline`,
|
||||
method: 'GET',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function CIBaselineRollback(ciId, params) {
|
||||
return axios({
|
||||
url: `/v0.1/ci/${ciId}/baseline/rollback`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
BIN
cmdb-ui/src/modules/cmdb/assets/itsm_uninstalled.png
Normal file
After Width: | Height: | Size: 22 KiB |
@@ -221,7 +221,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-transfer {
|
||||
.ant-transfer-list {
|
||||
@@ -231,7 +230,7 @@ export default {
|
||||
background-color: #f9fbff;
|
||||
border-bottom: none;
|
||||
.ant-transfer-list-header-title {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -259,7 +258,7 @@ export default {
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
background-color: #fff;
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
border-radius: 4px;
|
||||
width: 12px;
|
||||
}
|
||||
@@ -272,7 +271,7 @@ export default {
|
||||
font-size: 12px;
|
||||
color: #cacdd9;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
.move-icon {
|
||||
@@ -292,8 +291,8 @@ export default {
|
||||
}
|
||||
}
|
||||
.ant-transfer-list-content-item-selected {
|
||||
background-color: #custom_colors[color_2];
|
||||
border-color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
border-color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 130}px` }">
|
||||
<div class="cmdb-grant" :style="{ }">
|
||||
<template v-if="cmdbGrantType.includes('ci_type')">
|
||||
<div class="cmdb-grant-title">{{ $t('cmdb.components.ciTypeGrant') }}</div>
|
||||
<CiTypeGrant
|
||||
@@ -311,7 +311,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.cmdb-grant {
|
||||
position: relative;
|
||||
padding: 0 20px;
|
||||
@@ -324,7 +323,6 @@ export default {
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-grant {
|
||||
.grant-button {
|
||||
|
@@ -59,7 +59,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.read-checkbox {
|
||||
.read-checkbox-half-checked {
|
||||
width: 16px;
|
||||
@@ -76,10 +75,10 @@ export default {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
// background-color: #custom_colors[color_1];
|
||||
// background-color: @primary-color;
|
||||
border-radius: 2px;
|
||||
border: 14px solid transparent;
|
||||
border-left-color: #custom_colors[color_1];
|
||||
border-left-color: @primary-color;
|
||||
transform: rotate(225deg);
|
||||
top: -16px;
|
||||
left: -17px;
|
||||
|
@@ -112,7 +112,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.notice-content {
|
||||
width: 100%;
|
||||
& &-main {
|
||||
@@ -176,8 +175,8 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
&:hover {
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,11 +185,10 @@ export default {
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.notice-content {
|
||||
.w-e-bar {
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
}
|
||||
.w-e-text-placeholder {
|
||||
line-height: 1.5;
|
||||
|
@@ -95,12 +95,12 @@
|
||||
isFocusExpression = false
|
||||
}
|
||||
"
|
||||
class="ci-searchform-expression"
|
||||
:class="{ 'ci-searchform-expression': true, 'ci-searchform-expression-has-value': expression }"
|
||||
:style="{ width }"
|
||||
:placeholder="placeholder"
|
||||
@keyup.enter="emitRefresh"
|
||||
>
|
||||
<ops-icon slot="suffix" type="veops-copy" @click="handleCopyExpression" />
|
||||
<a-icon slot="suffix" type="check-circle" @click="handleCopyExpression" />
|
||||
</a-input>
|
||||
<slot></slot>
|
||||
</a-space>
|
||||
@@ -264,7 +264,6 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
@import '../../views/index.less';
|
||||
.ci-searchform-expression {
|
||||
> input {
|
||||
@@ -285,6 +284,9 @@ export default {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.ci-searchform-expression-has-value .ant-input-suffix {
|
||||
color: @func-color_3;
|
||||
}
|
||||
.cmdb-search-form {
|
||||
.ant-form-item-label {
|
||||
overflow: hidden;
|
||||
@@ -295,8 +297,6 @@ export default {
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.search-form-bar {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
|
@@ -244,7 +244,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-subscribe-drawer {
|
||||
.cmdb-subscribe-drawer-container {
|
||||
@@ -254,7 +253,7 @@ export default {
|
||||
}
|
||||
.cmdb-subscribe-drawer-tree-header {
|
||||
border-radius: 4px;
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 12px;
|
||||
@@ -265,7 +264,7 @@ export default {
|
||||
> span {
|
||||
display: inline-block;
|
||||
background-color: #fff;
|
||||
border-left: 2px solid #custom_colors[color_1];
|
||||
border-left: 2px solid @primary-color;
|
||||
padding: 3px 12px;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
@@ -273,7 +272,7 @@ export default {
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,10 +314,9 @@ export default {
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
.cmdb-subscribe-drawer {
|
||||
.ant-tabs-bar {
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@
|
||||
>
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<span slot="description"> {{ $t('cmdb.components.noParamRequest') }} </span>
|
||||
<a-button @click="add" type="primary" size="small" icon="plus" class="ops-button-primary">
|
||||
<a-button @click="add" type="primary" size="small" icon="plus">
|
||||
{{ $t('add') }}
|
||||
</a-button>
|
||||
</a-empty>
|
||||
|
@@ -187,7 +187,17 @@ const cmdb_en = {
|
||||
downloadType: 'Download CIType',
|
||||
deleteCIType: 'Delete CIType',
|
||||
otherGroupTips: 'Non sortable within the other group',
|
||||
filterTips: 'click to show {name}'
|
||||
filterTips: 'click to show {name}',
|
||||
attributeAssociation: 'Attribute Association',
|
||||
attributeAssociationTip1: 'Automatically establish relationships through the attributes except password, json and multiple of two models',
|
||||
attributeAssociationTip2: 'Double click to edit',
|
||||
attributeAssociationTip3: 'Two Attributes must be selected',
|
||||
attributeAssociationTip4: 'Please select a attribute from Source CIType',
|
||||
attributeAssociationTip5: 'Please select a attribute from Target CIType',
|
||||
show: 'show attribute',
|
||||
setAsShow: 'Set as show attribute',
|
||||
cancelSetAsShow: 'Cancel show attribute',
|
||||
showTips: 'The names of nodes in the service tree and topology view'
|
||||
},
|
||||
components: {
|
||||
unselectAttributes: 'Unselected',
|
||||
@@ -392,7 +402,15 @@ const cmdb_en = {
|
||||
noModifications: 'No Modifications',
|
||||
attr: 'attribute',
|
||||
attrId: 'attribute id',
|
||||
changeDescription: 'attribute id: {attr_id}, {before_days} day(s) in advance, Subject: {subject}\nContent: {body}\nNotify At: {notify_at}'
|
||||
changeDescription: 'attribute id: {attr_id}, {before_days} day(s) in advance, Subject: {subject}\nContent: {body}\nNotify At: {notify_at}',
|
||||
ticketStartTime: 'Start Time',
|
||||
ticketCreator: 'Creator',
|
||||
ticketTitle: 'Title',
|
||||
ticketFinishTime: 'Finish Time',
|
||||
ticketNodeName: 'Node Name',
|
||||
itsmUninstalled: 'Please use it in combination with VE ITSM',
|
||||
applyItsm: 'Free Apply ITSM',
|
||||
ticketId: 'Ticket ID',
|
||||
},
|
||||
relation_type: {
|
||||
addRelationType: 'New',
|
||||
@@ -484,7 +502,8 @@ if __name__ == "__main__":
|
||||
copyFailed: 'Copy failed',
|
||||
noLevel: 'No hierarchical relationship!',
|
||||
batchAddRelation: 'Batch Add Relation',
|
||||
history: 'History',
|
||||
history: 'Change Logs',
|
||||
relITSM: 'Related Tickets',
|
||||
topo: 'Topology',
|
||||
table: 'Table',
|
||||
m2mTips: 'The current CIType relationship is many-to-many, please go to the SerivceTree(relation view) to add or delete',
|
||||
@@ -502,7 +521,21 @@ if __name__ == "__main__":
|
||||
newUpdateField: 'Add a Attribute',
|
||||
attributeSettings: 'Attribute Settings',
|
||||
share: 'Share',
|
||||
noPermission: 'No Permission'
|
||||
noPermission: 'No Permission',
|
||||
rollback: 'Rollback',
|
||||
rollbackHeader: 'Instance Rollback',
|
||||
rollbackTo: 'Rollback to',
|
||||
rollbackToTips: 'Please select rollback time',
|
||||
baselineDiff: 'Difference from baseline',
|
||||
instance: 'Instance',
|
||||
rollbackBefore: 'Current value',
|
||||
rollbackAfter: 'After rollback',
|
||||
noDiff: 'CI data has not changed after {baseline}',
|
||||
rollbackConfirm: 'Are you sure you want to rollback?',
|
||||
rollbackSuccess: 'Rollback successfully',
|
||||
rollbackingTips: 'Rollbacking',
|
||||
batchRollbacking: 'Deleting {total} items in total, {successNum} items successful, {errorNum} items failed',
|
||||
baselineTips: 'Changes at this point in time will also be rollbacked, Unique ID and password attributes do not support',
|
||||
},
|
||||
serviceTree: {
|
||||
remove: 'Remove',
|
||||
@@ -523,7 +556,8 @@ if __name__ == "__main__":
|
||||
peopleHasRead: 'Personnel authorized to read:',
|
||||
authorizationPolicy: 'CI Authorization Policy:',
|
||||
idAuthorizationPolicy: 'Authorized by node:',
|
||||
view: 'View permissions'
|
||||
view: 'View permissions',
|
||||
searchTips: 'Search in service tree'
|
||||
},
|
||||
tree: {
|
||||
tips1: 'Please go to Preference page first to complete your subscription!',
|
||||
|
@@ -129,7 +129,7 @@ const cmdb_zh = {
|
||||
addRelation: '新增关系',
|
||||
sourceCIType: '源模型',
|
||||
sourceCITypeTips: '请选择源模型',
|
||||
dstCIType: '目标模型名',
|
||||
dstCIType: '目标模型',
|
||||
dstCITypeTips: '请选择目标模型',
|
||||
relationType: '关联类型',
|
||||
relationTypeTips: '请选择关联类型',
|
||||
@@ -187,7 +187,17 @@ const cmdb_zh = {
|
||||
downloadType: '下载模型',
|
||||
deleteCIType: '删除模型',
|
||||
otherGroupTips: '其他分组属性不可排序',
|
||||
filterTips: '点击可仅查看{name}属性'
|
||||
filterTips: '点击可仅查看{name}属性',
|
||||
attributeAssociation: '属性关联',
|
||||
attributeAssociationTip1: '通过2个模型的属性值(除密码、json、多值)来自动建立关系',
|
||||
attributeAssociationTip2: '双击可编辑',
|
||||
attributeAssociationTip3: '属性关联必须选择两个属性',
|
||||
attributeAssociationTip4: '请选择原模型属性',
|
||||
attributeAssociationTip5: '请选择目标模型属性',
|
||||
show: '展示属性',
|
||||
setAsShow: '设置为展示属性',
|
||||
cancelSetAsShow: '取消设置为展示属性',
|
||||
showTips: '服务树和拓扑视图里节点的名称'
|
||||
},
|
||||
components: {
|
||||
unselectAttributes: '未选属性',
|
||||
@@ -392,7 +402,15 @@ const cmdb_zh = {
|
||||
noModifications: '没有修改',
|
||||
attr: '属性名',
|
||||
attrId: '属性ID',
|
||||
changeDescription: '属性ID:{attr_id},提前:{before_days}天,主题:{subject}\n内容:{body}\n通知时间:{notify_at}'
|
||||
changeDescription: '属性ID:{attr_id},提前:{before_days}天,主题:{subject}\n内容:{body}\n通知时间:{notify_at}',
|
||||
ticketStartTime: '工单发起时间',
|
||||
ticketCreator: '发起人',
|
||||
ticketTitle: '工单名称',
|
||||
ticketFinishTime: '节点完成时间',
|
||||
ticketNodeName: '节点名称',
|
||||
itsmUninstalled: '请结合维易ITSM使用',
|
||||
applyItsm: '免费申请',
|
||||
ticketId: '工单ID',
|
||||
},
|
||||
relation_type: {
|
||||
addRelationType: '新增关系类型',
|
||||
@@ -484,7 +502,8 @@ if __name__ == "__main__":
|
||||
copyFailed: '复制失败!',
|
||||
noLevel: '无层级关系!',
|
||||
batchAddRelation: '批量添加关系',
|
||||
history: '操作历史',
|
||||
history: '变更记录',
|
||||
relITSM: '关联工单',
|
||||
topo: '拓扑',
|
||||
table: '表格',
|
||||
m2mTips: '当前模型关系为多对多,请前往关系视图进行增删操作',
|
||||
@@ -502,7 +521,21 @@ if __name__ == "__main__":
|
||||
newUpdateField: '新增修改字段',
|
||||
attributeSettings: '字段设置',
|
||||
share: '分享',
|
||||
noPermission: '暂无权限'
|
||||
noPermission: '暂无权限',
|
||||
rollback: '回滚',
|
||||
rollbackHeader: '实例回滚',
|
||||
rollbackTo: '回滚至: ',
|
||||
rollbackToTips: '请选择回滚时间点',
|
||||
baselineDiff: '基线对比结果',
|
||||
instance: '实例',
|
||||
rollbackBefore: '当前值',
|
||||
rollbackAfter: '回滚后',
|
||||
noDiff: '在【{baseline}】后数据没有发生变化',
|
||||
rollbackConfirm: '确认要回滚吗 ?',
|
||||
rollbackSuccess: '回滚成功',
|
||||
rollbackingTips: '正在批量回滚中',
|
||||
batchRollbacking: '正在回滚,共{total}个,成功{successNum}个,失败{errorNum}个',
|
||||
baselineTips: '该时间点的变更也会被回滚, 唯一标识、密码属性不支持回滚',
|
||||
},
|
||||
serviceTree: {
|
||||
remove: '移除',
|
||||
@@ -523,7 +556,8 @@ if __name__ == "__main__":
|
||||
peopleHasRead: '当前有查看权限的人员:',
|
||||
authorizationPolicy: '实例授权策略:',
|
||||
idAuthorizationPolicy: '按节点授权的:',
|
||||
view: '查看权限'
|
||||
view: '查看权限',
|
||||
searchTips: '在服务树中筛选'
|
||||
},
|
||||
tree: {
|
||||
tips1: '请先到 我的订阅 页面完成订阅!',
|
||||
|
@@ -13,7 +13,7 @@ const genCmdbRoutes = async () => {
|
||||
{
|
||||
path: '/cmdb/dashboard',
|
||||
name: 'cmdb_dashboard',
|
||||
meta: { title: 'dashboard', icon: 'ops-cmdb-dashboard', selectedIcon: 'ops-cmdb-dashboard-selected', keepAlive: false },
|
||||
meta: { title: 'dashboard', icon: 'ops-cmdb-dashboard', selectedIcon: 'ops-cmdb-dashboard', keepAlive: false },
|
||||
component: () => import('../views/dashboard/index_v2.vue')
|
||||
},
|
||||
{
|
||||
@@ -25,7 +25,7 @@ const genCmdbRoutes = async () => {
|
||||
path: '/cmdb/resourceviews',
|
||||
name: 'cmdb_resource_views',
|
||||
component: RouteView,
|
||||
meta: { title: 'cmdb.menu.ciTable', icon: 'ops-cmdb-resource', selectedIcon: 'ops-cmdb-resource-selected', keepAlive: true },
|
||||
meta: { title: 'cmdb.menu.ciTable', icon: 'ops-cmdb-resource', selectedIcon: 'ops-cmdb-resource', keepAlive: true },
|
||||
hideChildrenInMenu: false,
|
||||
children: []
|
||||
},
|
||||
@@ -33,7 +33,7 @@ const genCmdbRoutes = async () => {
|
||||
path: '/cmdb/tree_views',
|
||||
component: () => import('../views/tree_views'),
|
||||
name: 'cmdb_tree_views',
|
||||
meta: { title: 'cmdb.menu.ciTree', icon: 'ops-cmdb-tree', selectedIcon: 'ops-cmdb-tree-selected', keepAlive: false },
|
||||
meta: { title: 'cmdb.menu.ciTree', icon: 'ops-cmdb-tree', selectedIcon: 'ops-cmdb-tree', keepAlive: false },
|
||||
hideChildrenInMenu: true,
|
||||
children: [
|
||||
{
|
||||
@@ -47,13 +47,13 @@ const genCmdbRoutes = async () => {
|
||||
{
|
||||
path: '/cmdb/resourcesearch',
|
||||
name: 'cmdb_resource_search',
|
||||
meta: { title: 'cmdb.menu.ciSearch', icon: 'ops-cmdb-search', selectedIcon: 'ops-cmdb-search-selected', keepAlive: false },
|
||||
meta: { title: 'cmdb.menu.ciSearch', icon: 'ops-cmdb-search', selectedIcon: 'ops-cmdb-search', keepAlive: false },
|
||||
component: () => import('../views/resource_search/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/cmdb/adc',
|
||||
name: 'cmdb_auto_discovery_ci',
|
||||
meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc-selected', keepAlive: false },
|
||||
meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc', keepAlive: false },
|
||||
component: () => import('../views/discoveryCI/index.vue')
|
||||
},
|
||||
{
|
||||
@@ -72,19 +72,19 @@ const genCmdbRoutes = async () => {
|
||||
path: '/cmdb/preference',
|
||||
component: () => import('../views/preference/index'),
|
||||
name: 'cmdb_preference',
|
||||
meta: { title: 'cmdb.menu.preference', icon: 'ops-cmdb-preference', selectedIcon: 'ops-cmdb-preference-selected', keepAlive: false }
|
||||
meta: { title: 'cmdb.menu.preference', icon: 'ops-cmdb-preference', selectedIcon: 'ops-cmdb-preference', keepAlive: false }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/batch',
|
||||
component: () => import('../views/batch'),
|
||||
name: 'cmdb_batch',
|
||||
meta: { 'title': 'cmdb.menu.batchUpload', icon: 'ops-cmdb-batch', selectedIcon: 'ops-cmdb-batch-selected', keepAlive: false }
|
||||
meta: { 'title': 'cmdb.menu.batchUpload', icon: 'ops-cmdb-batch', selectedIcon: 'ops-cmdb-batch', keepAlive: false }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/ci_types',
|
||||
name: 'ci_type',
|
||||
component: () => import('../views/ci_types/index'),
|
||||
meta: { title: 'cmdb.menu.citypeManage', icon: 'ops-cmdb-citype', selectedIcon: 'ops-cmdb-citype-selected', keepAlive: false, permission: ['cmdb_admin', 'admin'] }
|
||||
meta: { title: 'cmdb.menu.citypeManage', icon: 'ops-cmdb-citype', selectedIcon: 'ops-cmdb-citype', keepAlive: false, permission: ['cmdb_admin', 'admin'] }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/disabled3',
|
||||
@@ -166,7 +166,7 @@ const genCmdbRoutes = async () => {
|
||||
path: `/cmdb/relationviews/${item[1]}`,
|
||||
name: `cmdb_relation_views_${item[1]}`,
|
||||
component: () => import('../views/relation_views/index'),
|
||||
meta: { title: item[0], icon: 'ops-cmdb-relation', selectedIcon: 'ops-cmdb-relation-selected', keepAlive: false, name: item[0] },
|
||||
meta: { title: item[0], icon: 'ops-cmdb-relation', selectedIcon: 'ops-cmdb-relation', keepAlive: false, name: item[0] },
|
||||
}
|
||||
})
|
||||
routes.children.splice(2, 0, ...relationViews)
|
||||
|
@@ -147,7 +147,6 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
@import '../index.less';
|
||||
.cmdb-batch-upload-label {
|
||||
color: @text-color_1;
|
||||
@@ -159,7 +158,6 @@ export default {
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-batch-upload {
|
||||
margin-bottom: -24px;
|
||||
|
@@ -113,7 +113,6 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-batch-upload-table {
|
||||
height: 200px;
|
||||
|
@@ -83,7 +83,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-batch-upload-dragger {
|
||||
height: auto;
|
||||
@@ -112,7 +111,6 @@ export default {
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-batch-upload-dragger {
|
||||
position: relative;
|
||||
|
@@ -101,7 +101,6 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-batch-upload-result {
|
||||
.cmdb-batch-upload-result-content {
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<div class="ci-detail-header">{{ this.type.alias }}</div>
|
||||
<div class="ci-detail-page">
|
||||
<CiDetailTab ref="ciDetailTab" :typeId="typeId" />
|
||||
<ci-detail-tab ref="ciDetailTab" :typeId="typeId" :attributeHistoryTableHeight="windowHeight - 250" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -23,6 +23,11 @@ export default {
|
||||
attributes: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
attrList: () => {
|
||||
@@ -55,8 +60,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.ci-detail-header {
|
||||
border-left: 3px solid @primary-color;
|
||||
padding-left: 10px;
|
||||
|
@@ -68,6 +68,8 @@
|
||||
<span @click="openBatchDownload">{{ $t('download') }}</span>
|
||||
<a-divider type="vertical" />
|
||||
<span @click="batchDelete">{{ $t('delete') }}</span>
|
||||
<a-divider type="vertical" />
|
||||
<span @click="batchRollback">{{ $t('cmdb.ci.rollback') }}</span>
|
||||
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span>
|
||||
</div>
|
||||
</SearchForm>
|
||||
@@ -293,6 +295,7 @@
|
||||
<create-instance-form ref="create" @reload="reloadData" @submit="batchUpdate" />
|
||||
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
||||
<BatchDownload ref="batchDownload" @batchDownload="batchDownload" />
|
||||
<ci-rollback-form ref="ciRollbackForm" @batchRollbackAsync="batchRollbackAsync($event)" :ciIds="selectedRowKeys" />
|
||||
<MetadataDrawer ref="metadataDrawer" />
|
||||
<CMDBGrant ref="cmdbGrant" resourceTypeName="CIType" app_id="cmdb" />
|
||||
</a-spin>
|
||||
@@ -325,6 +328,8 @@ import MetadataDrawer from './modules/MetadataDrawer.vue'
|
||||
import CMDBGrant from '../../components/cmdbGrant'
|
||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||
import { getAttrPassword } from '../../api/CITypeAttr'
|
||||
import CiRollbackForm from './modules/ciRollbackForm.vue'
|
||||
import { CIBaselineRollback } from '../../api/history'
|
||||
|
||||
export default {
|
||||
name: 'InstanceList',
|
||||
@@ -340,6 +345,7 @@ export default {
|
||||
MetadataDrawer,
|
||||
CMDBGrant,
|
||||
OpsMoveIcon,
|
||||
CiRollbackForm,
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
@@ -429,6 +435,12 @@ export default {
|
||||
// window.onkeypress = (e) => {
|
||||
// this.handleKeyPress(e)
|
||||
// }
|
||||
this.$nextTick(() => {
|
||||
const loadingNode = document.getElementsByClassName('ant-drawer-mask')
|
||||
if (loadingNode?.style) {
|
||||
loadingNode.style.zIndex = 8
|
||||
}
|
||||
})
|
||||
setTimeout(() => {
|
||||
this.columnDrop()
|
||||
}, 1000)
|
||||
@@ -661,7 +673,7 @@ export default {
|
||||
message: this.$t('warning'),
|
||||
description: errorMsg,
|
||||
duration: 0,
|
||||
style: { whiteSpace: 'break-spaces' },
|
||||
style: { whiteSpace: 'break-spaces', overflow: 'auto', maxHeight: this.windowHeight - 80 + 'px' },
|
||||
})
|
||||
errorNum += 1
|
||||
})
|
||||
@@ -744,6 +756,55 @@ export default {
|
||||
},
|
||||
})
|
||||
},
|
||||
batchRollback() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.ciRollbackForm.onOpen(true)
|
||||
})
|
||||
},
|
||||
async batchRollbackAsync(params) {
|
||||
const mask = document.querySelector('.ant-drawer-mask')
|
||||
const oldValue = mask.style.zIndex
|
||||
mask.style.zIndex = 2
|
||||
let successNum = 0
|
||||
let errorNum = 0
|
||||
this.loading = true
|
||||
this.loadTip = this.$t('cmdb.ci.rollbackingTips')
|
||||
const floor = Math.ceil(this.selectedRowKeys.length / 6)
|
||||
for (let i = 0; i < floor; i++) {
|
||||
const itemList = this.selectedRowKeys.slice(6 * i, 6 * i + 6)
|
||||
const promises = itemList.map((x) => CIBaselineRollback(x, params))
|
||||
await Promise.allSettled(promises)
|
||||
.then((res) => {
|
||||
res.forEach((r) => {
|
||||
if (r.status === 'fulfilled') {
|
||||
successNum += 1
|
||||
} else {
|
||||
errorNum += 1
|
||||
}
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.loadTip = this.$t('cmdb.ci.batchRollbacking', {
|
||||
total: this.selectedRowKeys.length,
|
||||
successNum: successNum,
|
||||
errorNum: errorNum,
|
||||
})
|
||||
})
|
||||
}
|
||||
this.loading = false
|
||||
this.loadTip = ''
|
||||
mask.style.zIndex = oldValue
|
||||
this.selectedRowKeys = []
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
this.$nextTick(() => {
|
||||
if (this.currentPage === 1) {
|
||||
this.loadTableData()
|
||||
} else {
|
||||
this.currentPage = 1
|
||||
}
|
||||
})
|
||||
},
|
||||
async refreshAfterEditAttrs() {
|
||||
await this.loadPreferenceAttrList()
|
||||
await this.loadTableData()
|
||||
@@ -992,7 +1053,6 @@ export default {
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.cmdb-ci {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
|
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
width="80%"
|
||||
width="90%"
|
||||
placement="left"
|
||||
@close="
|
||||
() => {
|
||||
visible = false
|
||||
}
|
||||
"
|
||||
style="transform: translateX(0px)!important"
|
||||
:visible="visible"
|
||||
:hasTitle="false"
|
||||
:hasFooter="false"
|
||||
|
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<div :style="{ height: '100%' }" v-if="itsmInstalled">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
show-overflow
|
||||
show-header-overflow
|
||||
resizable
|
||||
border
|
||||
size="small"
|
||||
class="ops-unstripe-table"
|
||||
:span-method="mergeRowMethod"
|
||||
:data="tableData"
|
||||
v-bind="ci_id ? { height: 'auto' } : { height: `${windowHeight - 225}px` }"
|
||||
>
|
||||
<template #empty>
|
||||
<a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }">
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<span slot="description"> {{ $t('noData') }} </span>
|
||||
</a-empty>
|
||||
</template>
|
||||
<vxe-column field="ticket.ticket_id" min-width="80" :title="$t('cmdb.history.ticketId')"> </vxe-column>
|
||||
<vxe-column field="ticket.created_at" width="160" :title="$t('cmdb.history.ticketStartTime')"> </vxe-column>
|
||||
<vxe-column field="ticket.creator_name" min-width="80" :title="$t('cmdb.history.ticketCreator')"> </vxe-column>
|
||||
<vxe-column field="ticket.title" min-width="150" :title="$t('cmdb.history.ticketTitle')">
|
||||
<template slot-scope="{ row }">
|
||||
<a target="_blank" :href="row.ticket.url">{{ row.ticket.title }}</a>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="ticket.node_finish_time" width="160" :title="$t('cmdb.history.ticketFinishTime')">
|
||||
</vxe-column>
|
||||
<vxe-column field="ticket.node_name" min-width="100" :title="$t('cmdb.history.ticketNodeName')"> </vxe-column>
|
||||
<vxe-table-column
|
||||
field="operate_type"
|
||||
min-width="100"
|
||||
:filters="[
|
||||
{ value: 0, label: $t('new') },
|
||||
{ value: 1, label: $t('delete') },
|
||||
{ value: 2, label: $t('update') },
|
||||
]"
|
||||
:filter-method="filterOperateMethod"
|
||||
:title="$t('operation')"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ operateTypeMap[row.operate_type] }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="attr_alias"
|
||||
min-width="100"
|
||||
:title="$t('cmdb.attribute')"
|
||||
:filters="[]"
|
||||
:filter-method="filterAttrMethod"
|
||||
>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column field="old" min-width="100" :title="$t('cmdb.history.old')"></vxe-table-column>
|
||||
<vxe-table-column field="new" min-width="100" :title="$t('cmdb.history.new')"></vxe-table-column>
|
||||
</vxe-table>
|
||||
<div :style="{ textAlign: 'right' }" v-if="!ci_id">
|
||||
<a-pagination
|
||||
size="small"
|
||||
show-size-changer
|
||||
show-quick-jumper
|
||||
:page-size-options="pageSizeOptions"
|
||||
:current="tablePage.currentPage"
|
||||
:total="tablePage.totalResult"
|
||||
:show-total="(total, range) => $t('cmdb.history.totalItems', { total: total })"
|
||||
:page-size="tablePage.pageSize"
|
||||
:default-current="1"
|
||||
@change="pageOrSizeChange"
|
||||
@showSizeChange="pageOrSizeChange"
|
||||
>
|
||||
</a-pagination>
|
||||
</div>
|
||||
</div>
|
||||
<a-empty
|
||||
v-else
|
||||
:image-style="{
|
||||
height: '200px',
|
||||
}"
|
||||
:style="{ paddingTop: '10%' }"
|
||||
>
|
||||
<img slot="image" :src="require('@/modules/cmdb/assets/itsm_uninstalled.png')" />
|
||||
<span slot="description"> {{ $t('cmdb.history.itsmUninstalled') }} </span>
|
||||
<a-button href="https://veops.cn/apply" target="_blank" type="primary">
|
||||
{{ $t('cmdb.history.applyItsm') }}
|
||||
</a-button>
|
||||
</a-empty>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCiRelatedTickets } from '../../../api/history'
|
||||
export default {
|
||||
name: 'RelatedItsmTable',
|
||||
props: {
|
||||
ci_id: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
ciHistory: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
itsmInstalled: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
attrList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
tablePage: {
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
totalResult: 0,
|
||||
},
|
||||
pageSizeOptions: ['50', '100', '200'],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
operateTypeMap() {
|
||||
return {
|
||||
0: this.$t('new'),
|
||||
1: this.$t('delete'),
|
||||
2: this.$t('update'),
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.updateTableData()
|
||||
},
|
||||
methods: {
|
||||
updateTableData(currentPage = 1, pageSize = this.tablePage.pageSize) {
|
||||
const params = { page: currentPage, page_size: pageSize, next_todo_ids: [] }
|
||||
if (this.ci_id) {
|
||||
const tableData = []
|
||||
this.ciHistory.forEach((item) => {
|
||||
if (item.ticket_id) {
|
||||
params.next_todo_ids.push(item.ticket_id)
|
||||
tableData.push(item)
|
||||
}
|
||||
})
|
||||
if (params.next_todo_ids.length) {
|
||||
getCiRelatedTickets(params)
|
||||
.then((res) => {
|
||||
const ticketId2Detail = {}
|
||||
res.forEach((item) => {
|
||||
ticketId2Detail[item.next_todo_id] = item
|
||||
})
|
||||
this.tableData = tableData.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
ticket: ticketId2Detail[item.ticket_id],
|
||||
}
|
||||
})
|
||||
this.updateAttrFilter()
|
||||
})
|
||||
.catch((e) => {})
|
||||
}
|
||||
} else {
|
||||
}
|
||||
},
|
||||
updateAttrFilter() {
|
||||
this.$nextTick(() => {
|
||||
const $table = this.$refs.xTable
|
||||
if ($table) {
|
||||
const attrColumn = $table.getColumnByField('attr_alias')
|
||||
if (attrColumn) {
|
||||
$table.setFilter(
|
||||
attrColumn,
|
||||
this.attrList().map((attr) => {
|
||||
return { value: attr.alias || attr.name, label: attr.alias || attr.name }
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
|
||||
const fields = [
|
||||
'ticket.ticket_id',
|
||||
'ticket.created_at',
|
||||
'ticket.creator_name',
|
||||
'ticket.title',
|
||||
'ticket.node_finish_time',
|
||||
'ticket.node_name',
|
||||
]
|
||||
const cellValue1 = row.ticket.ticket_id
|
||||
const cellValue2 = row.ticket.node_name
|
||||
if (cellValue1 && cellValue2 && fields.includes(column.property)) {
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
if (prevRow && prevRow.ticket.ticket_id === cellValue1 && prevRow.ticket.node_name === cellValue2) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow.ticket.ticket_id === cellValue1 && nextRow.ticket.node_name === cellValue2) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
pageOrSizeChange(currentPage, pageSize) {
|
||||
this.updateTableData(currentPage, pageSize)
|
||||
},
|
||||
filterOperateMethod({ value, row, column }) {
|
||||
return Number(row.operate_type) === Number(value)
|
||||
},
|
||||
filterAttrMethod({ value, row, column }) {
|
||||
return row.attr_alias === value
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@@ -150,11 +150,11 @@ export default {
|
||||
computed: {
|
||||
topoData() {
|
||||
const ci_types_list = this.ci_types()
|
||||
const unique_id = this.attributes().unique_id
|
||||
const unique_name = this.attributes().unique
|
||||
const _findCiType = ci_types_list.find((item) => item.id === this.typeId)
|
||||
const unique_id = _findCiType.show_id || this.attributes().unique_id
|
||||
const unique_name = _findCiType.show_name || this.attributes().unique
|
||||
const _findUnique = this.attrList().find((attr) => attr.id === unique_id)
|
||||
const unique_alias = _findUnique?.alias || _findUnique?.name || ''
|
||||
const _findCiType = ci_types_list.find((item) => item.id === this.typeId)
|
||||
const nodes = {
|
||||
isRoot: true,
|
||||
id: `Root_${this.typeId}`,
|
||||
@@ -183,6 +183,10 @@ export default {
|
||||
this.parentCITypes.forEach((parent) => {
|
||||
const _findCiType = ci_types_list.find((item) => item.id === parent.id)
|
||||
if (this.firstCIs[parent.name]) {
|
||||
const unique_id = _findCiType.show_id || _findCiType.unique_id
|
||||
const _findUnique = parent.attributes.find((attr) => attr.id === unique_id)
|
||||
const unique_name = _findUnique?.name
|
||||
const unique_alias = _findUnique?.alias || _findUnique?.name || ''
|
||||
this.firstCIs[parent.name].forEach((parentCi) => {
|
||||
nodes.children.push({
|
||||
id: `${parentCi._id}`,
|
||||
@@ -190,9 +194,9 @@ export default {
|
||||
title: parent.alias || parent.name,
|
||||
name: parent.name,
|
||||
side: 'left',
|
||||
unique_alias: parentCi.unique_alias,
|
||||
unique_name: parentCi.unique,
|
||||
unique_value: parentCi[parentCi.unique],
|
||||
unique_alias,
|
||||
unique_name,
|
||||
unique_value: parentCi[unique_name],
|
||||
children: [],
|
||||
icon: _findCiType?.icon || '',
|
||||
endpoints: [
|
||||
@@ -222,6 +226,10 @@ export default {
|
||||
this.childCITypes.forEach((child) => {
|
||||
const _findCiType = ci_types_list.find((item) => item.id === child.id)
|
||||
if (this.secondCIs[child.name]) {
|
||||
const unique_id = _findCiType.show_id || _findCiType.unique_id
|
||||
const _findUnique = child.attributes.find((attr) => attr.id === unique_id)
|
||||
const unique_name = _findUnique?.name
|
||||
const unique_alias = _findUnique?.alias || _findUnique?.name || ''
|
||||
this.secondCIs[child.name].forEach((childCi) => {
|
||||
nodes.children.push({
|
||||
id: `${childCi._id}`,
|
||||
@@ -229,9 +237,9 @@ export default {
|
||||
title: child.alias || child.name,
|
||||
name: child.name,
|
||||
side: 'right',
|
||||
unique_alias: childCi.unique_alias,
|
||||
unique_name: childCi.unique,
|
||||
unique_value: childCi[childCi.unique],
|
||||
unique_alias,
|
||||
unique_name,
|
||||
unique_value: childCi[unique_name],
|
||||
children: [],
|
||||
icon: _findCiType?.icon || '',
|
||||
endpoints: [
|
||||
@@ -260,6 +268,24 @@ export default {
|
||||
})
|
||||
return { nodes, edges }
|
||||
},
|
||||
exsited_ci() {
|
||||
const _exsited_ci = [this.typeId]
|
||||
this.parentCITypes.forEach((parent) => {
|
||||
if (this.firstCIs[parent.name]) {
|
||||
this.firstCIs[parent.name].forEach((parentCi) => {
|
||||
_exsited_ci.push(parentCi._id)
|
||||
})
|
||||
}
|
||||
})
|
||||
this.childCITypes.forEach((child) => {
|
||||
if (this.secondCIs[child.name]) {
|
||||
this.secondCIs[child.name].forEach((childCi) => {
|
||||
_exsited_ci.push(childCi._id)
|
||||
})
|
||||
}
|
||||
})
|
||||
return _exsited_ci
|
||||
},
|
||||
},
|
||||
inject: {
|
||||
attrList: { from: 'attrList' },
|
||||
@@ -278,6 +304,7 @@ export default {
|
||||
await Promise.all([this.getParentCITypes(), this.getChildCITypes()])
|
||||
Promise.all([this.getFirstCIs(), this.getSecondCIs()]).then(() => {
|
||||
if (isFirst && this.$refs.ciDetailRelationTopo) {
|
||||
this.$refs.ciDetailRelationTopo.exsited_ci = this.exsited_ci
|
||||
this.$refs.ciDetailRelationTopo.setTopoData(this.topoData)
|
||||
}
|
||||
})
|
||||
@@ -314,7 +341,6 @@ export default {
|
||||
secondCIs[item.ci_type] = [item]
|
||||
}
|
||||
})
|
||||
console.log(_.cloneDeep(secondCIs))
|
||||
this.secondCIs = secondCIs
|
||||
})
|
||||
.catch((e) => {})
|
||||
@@ -414,6 +440,7 @@ export default {
|
||||
handleChangeActiveKey(e) {
|
||||
if (e.target.value === '1') {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.ciDetailRelationTopo.exsited_ci = this.exsited_ci
|
||||
this.$refs.ciDetailRelationTopo.setTopoData(this.topoData)
|
||||
})
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 10px;
|
||||
border-radius: 2px;
|
||||
padding: 4px 8px;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
@@ -74,13 +74,11 @@
|
||||
}
|
||||
.root {
|
||||
width: 100px;
|
||||
background: #2f54eb;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-weight: 500;
|
||||
border-color: @primary-color;
|
||||
font-weight: 700;
|
||||
padding: 4px 8px;
|
||||
.title {
|
||||
color: #fff;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|