Compare commits

..

20 Commits
2.4.3 ... 2.4.4

Author SHA1 Message Date
pycook
27c733aa2c docs: update sql 2024-05-16 20:59:30 +08:00
pycook
2a8e9e684e fix(ui): issue#490 2024-05-02 21:28:06 +08:00
pycook
095190a785 fix(api): unique constraint (#505) 2024-05-02 21:22:40 +08:00
pycook
ef25c94b5d fix(api): permissions for CIType group editing 2024-04-29 15:18:47 +08:00
pycook
06ae1bcf13 docs: update build_api_key 2024-04-29 15:11:12 +08:00
pycook
9ead4e7d8d chore: release v2.4.4 2024-04-29 14:44:33 +08:00
pycook
994a28dd25 feat(ui): baseline rollback (#502) 2024-04-29 10:10:07 +08:00
simontigers
74b587e46c Merge pull request #501 from simontigers/common_decorator_perms
fix: role base app perm
2024-04-29 09:27:36 +08:00
hu.sima
091cd882bd fix: role base app perm 2024-04-29 09:26:23 +08:00
simontigers
73093db467 Merge pull request #500 from simontigers/common_decorator_perms
fix(api): decorator_perms_role_required
2024-04-28 19:43:22 +08:00
hu.sima
66e268ce68 fix(api): decorator_perms_role_required 2024-04-28 19:41:50 +08:00
simontigers
a41d1a5e97 Merge pull request #499 from simontigers/common_decorator_perms
feat(api): role perm
2024-04-28 19:22:43 +08:00
hu.sima
b4b728fe28 feat(api): role perm 2024-04-28 19:22:10 +08:00
pycook
d16462d8b7 feat(api): ci baseline rollback (#498) 2024-04-28 19:19:14 +08:00
kdyq007
de7d98c0b4 feat(api): Add sorting function to ci list attribute (#495)
Co-authored-by: sherlock <sherlock@gmail.com>
2024-04-27 09:20:24 +08:00
pycook
51332c7236 feat(ui): CI change logs related itsm 2024-04-24 20:09:59 +08:00
dagongren
bf1076fe4a feat:update cs && update style (#488) 2024-04-23 12:20:27 +08:00
dagongren
3454a98cfb fix(cmdb-ui):service tree search (#487) 2024-04-19 13:32:12 +08:00
dagongren
506dcbb40e fix(cmdb-ui):fix service tree change table page (#486) 2024-04-19 11:46:51 +08:00
dagongren
5ac4517187 style (#482) 2024-04-18 10:49:39 +08:00
89 changed files with 1561 additions and 2197 deletions

View File

@@ -263,10 +263,11 @@ class CIManager(object):
ci_ids = None ci_ids = None
for attr_id in constraint.attr_ids: for attr_id in constraint.attr_ids:
value_table = TableMap(attr_name=id2name[attr_id]).table value_table = TableMap(attr_name=id2name[attr_id]).table
values = value_table.get_by(attr_id=attr_id,
_ci_ids = set([i.ci_id for i in value_table.get_by(attr_id=attr_id, value=ci_dict.get(id2name[attr_id]) or None,
to_dict=False, only_query=True).join(
value=ci_dict.get(id2name[attr_id]) or None)]) 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: if ci_ids is None:
ci_ids = _ci_ids ci_ids = _ci_ids
else: else:
@@ -441,10 +442,13 @@ class CIManager(object):
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
ci = self.confirm_ci_existed(ci_id) ci = self.confirm_ci_existed(ci_id)
raw_dict = copy.deepcopy(ci_dict)
attrs = CITypeAttributeManager.get_all_attributes(ci.type_id) attrs = CITypeAttributeManager.get_all_attributes(ci.type_id)
ci_type_attrs_name = {attr.name: attr for _, attr in attrs} 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} ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
for _, attr in attrs: for _, attr in attrs:
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT: if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
@@ -825,6 +829,105 @@ class CIManager(object):
return data.get('v') 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): class CIRelationManager(object):
""" """

View File

@@ -253,6 +253,13 @@ class CITypeManager(object):
for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False): for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False):
item.delete(commit=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() db.session.commit()
ci_type.soft_delete() ci_type.soft_delete()
@@ -414,9 +421,6 @@ class CITypeGroupManager(object):
existed = CITypeGroup.get_by_id(gid) or abort( existed = CITypeGroup.get_by_id(gid) or abort(
404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid))) 404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid)))
if name is not None and name != existed.name: 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) 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]) max_order = max([i.order or 0 for i in CITypeGroupItem.get_by(group_id=gid, to_dict=False)] or [0])

View File

@@ -55,6 +55,9 @@ class CITypeOperateType(BaseEnum):
DELETE_UNIQUE_CONSTRAINT = "11" # 删除联合唯一 DELETE_UNIQUE_CONSTRAINT = "11" # 删除联合唯一
ADD_RELATION = "12" # 新增关系 ADD_RELATION = "12" # 新增关系
DELETE_RELATION = "13" # 删除关系 DELETE_RELATION = "13" # 删除关系
ADD_RECONCILIATION = "14" # 删除关系
UPDATE_RECONCILIATION = "15" # 删除关系
DELETE_RECONCILIATION = "16" # 删除关系
class RetKey(BaseEnum): class RetKey(BaseEnum):
@@ -98,6 +101,12 @@ class AttributeDefaultValueEnum(BaseEnum):
AUTO_INC_ID = "$auto_inc_id" AUTO_INC_ID = "$auto_inc_id"
class ExecuteStatusEnum(BaseEnum):
COMPLETED = '0'
FAILED = '1'
RUNNING = '2'
CMDB_QUEUE = "one_cmdb_async" CMDB_QUEUE = "one_cmdb_async"
REDIS_PREFIX_CI = "ONE_CMDB" REDIS_PREFIX_CI = "ONE_CMDB"
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION" REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"

View File

@@ -26,7 +26,7 @@ from api.models.cmdb import OperationRecord
class AttributeHistoryManger(object): class AttributeHistoryManger(object):
@staticmethod @staticmethod
def get_records_for_attributes(start, end, username, page, page_size, operate_type, type_id, 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( records = db.session.query(OperationRecord, AttributeHistory).join(
AttributeHistory, OperationRecord.id == AttributeHistory.record_id) AttributeHistory, OperationRecord.id == AttributeHistory.record_id)
@@ -48,6 +48,9 @@ class AttributeHistoryManger(object):
if ci_id is not None: if ci_id is not None:
records = records.filter(AttributeHistory.ci_id == ci_id) 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: if attr_id is not None:
records = records.filter(AttributeHistory.attr_id == attr_id) records = records.filter(AttributeHistory.attr_id == attr_id)
@@ -62,6 +65,12 @@ class AttributeHistoryManger(object):
if attr_hist['attr']: if attr_hist['attr']:
attr_hist['attr_name'] = attr_hist['attr'].name attr_hist['attr_name'] = attr_hist['attr'].name
attr_hist['attr_alias'] = attr_hist['attr'].alias 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") attr_hist.pop("attr")
if record_id not in res: if record_id not in res:
@@ -161,6 +170,7 @@ class AttributeHistoryManger(object):
record = i.OperationRecord record = i.OperationRecord
item = dict(attr_name=attr.name, item = dict(attr_name=attr.name,
attr_alias=attr.alias, attr_alias=attr.alias,
value_type=attr.value_type,
operate_type=hist.operate_type, operate_type=hist.operate_type,
username=user and user.nickname, username=user and user.nickname,
old=hist.old, old=hist.old,
@@ -271,7 +281,7 @@ class CITypeHistoryManager(object):
return numfound, result return numfound, result
@staticmethod @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: if type_id is None and attr_id is not None:
from api.models.cmdb import CITypeAttribute from api.models.cmdb import CITypeAttribute
type_ids = [i.type_id for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False)] type_ids = [i.type_id for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False)]
@@ -284,6 +294,7 @@ class CITypeHistoryManager(object):
uid=current_user.uid, uid=current_user.uid,
attr_id=attr_id, attr_id=attr_id,
trigger_id=trigger_id, trigger_id=trigger_id,
rc_id=rc_id,
unique_constraint_id=unique_constraint_id, unique_constraint_id=unique_constraint_id,
change=change) change=change)

View File

@@ -78,6 +78,8 @@ class ErrFormat(CommonErrFormat):
unique_constraint_invalid = _l("Uniquely constrained attributes cannot be JSON and multi-valued") unique_constraint_invalid = _l("Uniquely constrained attributes cannot be JSON and multi-valued")
ci_type_trigger_duplicate = _l("Duplicated trigger") # 重复的触发器 ci_type_trigger_duplicate = _l("Duplicated trigger") # 重复的触发器
ci_type_trigger_not_found = _l("Trigger {} does not exist") # 触发器 {} 不存在 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") # 操作记录 {} 不存在 record_not_found = _l("Operation record {} does not exist") # 操作记录 {} 不存在
cannot_delete_unique = _l("Unique identifier cannot be deleted") # 不能删除唯一标识 cannot_delete_unique = _l("Unique identifier cannot be deleted") # 不能删除唯一标识

View File

@@ -275,25 +275,36 @@ class AttributeValueManager(object):
if attr.is_list: if attr.is_list:
existed_attrs = value_table.get_by(attr_id=attr.id, ci_id=ci.id, to_dict=False) existed_attrs = value_table.get_by(attr_id=attr.id, ci_id=ci.id, to_dict=False)
existed_values = [i.value for i in existed_attrs] existed_values = [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: for v in deleted:
existed_attr = existed_attrs[existed_values.index(v)] existed_attr = existed_attrs[existed_values.index(v)]
existed_attr.delete(flush=False, commit=False) existed_attr.delete(flush=False, commit=False)
changed.append((ci.id, attr.id, OperateType.DELETE, v, None, ci.type_id)) changed.append((ci.id, attr.id, OperateType.DELETE, 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: else:
existed_attr = value_table.get_by(attr_id=attr.id, ci_id=ci.id, first=True, to_dict=False) 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 = 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: 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) 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)) changed.append((ci.id, attr.id, OperateType.ADD, None, value, ci.type_id))
else: else:
if existed_value != value: if existed_value != value and existed_attr:
if value is None: if value is None:
existed_attr.delete(flush=False, commit=False) existed_attr.delete(flush=False, commit=False)
else: else:

View File

@@ -10,6 +10,11 @@ from api.lib.perm.acl.role import RoleCRUD, RoleRelationCRUD
from api.lib.perm.acl.user import UserCRUD 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): class ACLManager(object):
def __init__(self, app_name='acl', uid=None): def __init__(self, app_name='acl', uid=None):
self.log = current_app.logger 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) numfound, res = ResourceCRUD.search(q, u, self.validate_app().id, rt_id, page, page_size)
return res 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) PermissionCRUD.grant(rid, perms, resource_id=resource_id, group_id=None)
@staticmethod @staticmethod
@@ -141,3 +147,7 @@ class ACLManager(object):
rt = AppCRUD.add(**payload) rt = AppCRUD.add(**payload)
return rt.to_dict() 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)

View 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

View File

@@ -80,3 +80,5 @@ class ErrFormat(CommonErrFormat):
ldap_test_username_required = _l("LDAP test username required") # LDAP测试用户名必填 ldap_test_username_required = _l("LDAP test username required") # LDAP测试用户名必填
company_wide = _l("Company wide") # 全公司 company_wide = _l("Company wide") # 全公司
resource_no_permission = _l("No permission to access resource {}, perm {} ") # 没有权限访问 {} 资源的 {} 权限"

View 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)

View File

@@ -224,16 +224,22 @@ class RoleRelationCache(object):
@classmethod @classmethod
@flush_db @flush_db
def rebuild(cls, rid, app_id): def rebuild(cls, rid, app_id):
cls.clean(rid, app_id) if app_id is None:
app_ids = [None] + [i.id for i in App.get_by(to_dict=False)]
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: else:
HasResourceRoleCache.remove(rid, app_id) app_ids = [app_id]
cls.get_resources2(rid, 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 @classmethod
@flush_db @flush_db

View File

@@ -274,12 +274,14 @@ class PermissionCRUD(object):
perm2resource.setdefault(_perm, []).append(resource_id) perm2resource.setdefault(_perm, []).append(resource_id)
for _perm in perm2resource: for _perm in perm2resource:
perm = PermissionCache.get(_perm, resource_type_id) perm = PermissionCache.get(_perm, resource_type_id)
existeds = RolePermission.get_by(rid=rid, if perm is None:
app_id=app_id, continue
perm_id=perm.id, exists = RolePermission.get_by(rid=rid,
__func_in___key_resource_id=perm2resource[_perm], app_id=app_id,
to_dict=False) perm_id=perm.id,
for existed in existeds: __func_in___key_resource_id=perm2resource[_perm],
to_dict=False)
for existed in exists:
existed.deleted = True existed.deleted = True
existed.deleted_at = datetime.datetime.now() existed.deleted_at = datetime.datetime.now()
db.session.add(existed) db.session.add(existed)

View File

@@ -2,7 +2,6 @@
from flask import abort from flask import abort
from flask import current_app
from api.extensions import db from api.extensions import db
from api.lib.perm.acl.audit import AuditCRUD from api.lib.perm.acl.audit import AuditCRUD
@@ -127,11 +126,18 @@ class ResourceTypeCRUD(object):
existed_ids = [i.id for i in existed] existed_ids = [i.id for i in existed]
current_ids = [] current_ids = []
rebuild_rids = set()
for i in existed: for i in existed:
if i.name not in perms: 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: else:
current_ids.append(i.id) 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: for i in perms:
if i not in existed_names: if i not in existed_names:

View File

@@ -3,12 +3,14 @@
import time import time
import redis_lock
import six import six
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from sqlalchemy import or_ from sqlalchemy import or_
from api.extensions import db from api.extensions import db
from api.extensions import rd
from api.lib.perm.acl.app import AppCRUD from api.lib.perm.acl.app import AppCRUD
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
from api.lib.perm.acl.cache import AppCache from api.lib.perm.acl.cache import AppCache
@@ -62,7 +64,9 @@ class RoleRelationCRUD(object):
id2parents = {} id2parents = {}
for i in res: 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 return id2parents
@@ -141,24 +145,27 @@ class RoleRelationCRUD(object):
@classmethod @classmethod
def add(cls, role, parent_id, child_ids, app_id): def add(cls, role, parent_id, child_ids, app_id):
result = [] with redis_lock.Lock(rd.r, "ROLE_RELATION_ADD"):
for child_id in child_ids: db.session.commit()
existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id, app_id=app_id)
if existed:
continue
RoleRelationCache.clean(parent_id, app_id) result = []
RoleRelationCache.clean(child_id, app_id) 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): RoleRelationCache.clean(parent_id, app_id)
return abort(400, ErrFormat.inheritance_dead_loop) RoleRelationCache.clean(child_id, app_id)
if app_id is None: if parent_id in cls.recursive_child_ids(child_id, app_id):
for app in AppCRUD.get_all(): return abort(400, ErrFormat.inheritance_dead_loop)
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()) 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, AuditCRUD.add_role_log(app_id, AuditOperateType.role_relation_add,
AuditScope.role_relation, role.id, {}, {}, AuditScope.role_relation, role.id, {}, {},

View File

@@ -432,6 +432,7 @@ class CITypeHistory(Model):
attr_id = db.Column(db.Integer) attr_id = db.Column(db.Integer)
trigger_id = db.Column(db.Integer) trigger_id = db.Column(db.Integer)
rc_id = db.Column(db.Integer)
unique_constraint_id = db.Column(db.Integer) unique_constraint_id = db.Column(db.Integer)
uid = db.Column(db.Integer, index=True) uid = db.Column(db.Integer, index=True)

View File

@@ -3,12 +3,13 @@
import json import json
import re import re
from celery_once import QueueOnce import redis_lock
from flask import current_app from flask import current_app
from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequest
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
from api.extensions import celery from api.extensions import celery
from api.extensions import rd
from api.lib.decorator import flush_db from api.lib.decorator import flush_db
from api.lib.decorator import reconnect_db from api.lib.decorator import reconnect_db
from api.lib.perm.acl.audit import AuditCRUD from api.lib.perm.acl.audit import AuditCRUD
@@ -25,14 +26,14 @@ from api.models.acl import Role
from api.models.acl import Trigger from api.models.acl import Trigger
@celery.task(name="acl.role_rebuild", @celery.task(name="acl.role_rebuild", queue=ACL_QUEUE, )
queue=ACL_QUEUE,)
@flush_db @flush_db
@reconnect_db @reconnect_db
def role_rebuild(rids, app_id): def role_rebuild(rids, app_id):
rids = rids if isinstance(rids, list) else [rids] rids = rids if isinstance(rids, list) else [rids]
for rid in 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)) current_app.logger.info("Role {0} App {1} rebuild..........".format(rids, app_id))

View File

@@ -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.perms import has_perm_for_ci
from api.lib.cmdb.search import SearchError from api.lib.cmdb.search import SearchError
from api.lib.cmdb.search.ci import search 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.perm.acl.acl import has_perm_from_args
from api.lib.utils import get_page from api.lib.utils import get_page
from api.lib.utils import get_page_size from api.lib.utils import get_page_size
@@ -254,3 +255,23 @@ class CIPasswordView(APIView):
def post(self, ci_id, attr_id): def post(self, ci_id, attr_id):
return self.get(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)

View File

@@ -7,7 +7,6 @@ from io import BytesIO
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask import request from flask import request
from flask import session
from api.lib.cmdb.cache import AttributeCache from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeCache 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 CITypeTemplateManager
from api.lib.cmdb.ci_type import CITypeTriggerManager from api.lib.cmdb.ci_type import CITypeTriggerManager
from api.lib.cmdb.ci_type import CITypeUniqueConstraintManager 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.perms import CIFilterPermsCRUD
from api.lib.cmdb.preference import PreferenceManager from api.lib.cmdb.preference import PreferenceManager
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.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_required
from api.lib.decorator import args_validate from api.lib.decorator import args_validate
from api.lib.perm.acl.acl import ACLManager 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.lib.utils import handle_arg_list
from api.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class CITypeView(APIView): class CITypeView(APIView):
url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>", url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>",
@@ -116,7 +119,6 @@ class CITypeInheritanceView(APIView):
class CITypeGroupView(APIView): class CITypeGroupView(APIView):
url_prefix = ("/ci_types/groups", url_prefix = ("/ci_types/groups",
"/ci_types/groups/config", "/ci_types/groups/config",
"/ci_types/groups/order",
"/ci_types/groups/<int:gid>") "/ci_types/groups/<int:gid>")
def get(self): def get(self):
@@ -125,7 +127,8 @@ class CITypeGroupView(APIView):
return self.jsonify(CITypeGroupManager.get(need_other, config_required)) 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_required("name")
@args_validate(CITypeGroupManager.cls) @args_validate(CITypeGroupManager.cls)
def post(self): def post(self):
@@ -136,15 +139,6 @@ class CITypeGroupView(APIView):
@args_validate(CITypeGroupManager.cls) @args_validate(CITypeGroupManager.cls)
def put(self, gid=None): 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")) name = request.values.get('name') or abort(400, ErrFormat.argument_value_required.format("name"))
type_ids = request.values.get('type_ids') type_ids = request.values.get('type_ids')
@@ -152,7 +146,8 @@ class CITypeGroupView(APIView):
return self.jsonify(gid=gid) 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): def delete(self, gid):
type_ids = request.values.get("type_ids") type_ids = request.values.get("type_ids")
CITypeGroupManager.delete(gid, type_ids) CITypeGroupManager.delete(gid, type_ids)
@@ -160,6 +155,18 @@ class CITypeGroupView(APIView):
return self.jsonify(gid=gid) 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): class CITypeQueryView(APIView):
url_prefix = "/ci_types/query" url_prefix = "/ci_types/query"
@@ -352,14 +359,16 @@ class CITypeAttributeGroupView(APIView):
class CITypeTemplateView(APIView): class CITypeTemplateView(APIView):
url_prefix = ("/ci_types/template/import", "/ci_types/template/export", "/ci_types/<int:type_id>/template/export") 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 def get(self, type_id=None): # export
if type_id is not None: 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_by_type(type_id)))
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template())) 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 def post(self): # import
tpt = request.values.get('ci_type_template') or {} tpt = request.values.get('ci_type_template') or {}
@@ -379,7 +388,8 @@ class CITypeCanDefineComputed(APIView):
class CITypeTemplateFileView(APIView): class CITypeTemplateFileView(APIView):
url_prefix = ("/ci_types/template/import/file", "/ci_types/template/export/file") 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 def get(self): # export
tpt_json = CITypeTemplateManager.export_template() tpt_json = CITypeTemplateManager.export_template()
tpt_json = dict(ci_type_template=tpt_json) tpt_json = dict(ci_type_template=tpt_json)
@@ -394,7 +404,8 @@ class CITypeTemplateFileView(APIView):
mimetype='application/json', mimetype='application/json',
max_age=0) 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 def post(self): # import
f = request.files.get('file') f = request.files.get('file')

View File

@@ -11,6 +11,8 @@ from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.preference import PreferenceManager from api.lib.cmdb.preference import PreferenceManager
from api.lib.cmdb.resp_format import ErrFormat 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_required
from api.lib.perm.acl.acl import ACLManager 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 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.lib.perm.acl.acl import role_required
from api.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class GetChildrenView(APIView): class GetChildrenView(APIView):
url_prefix = ("/ci_type_relations/<int:parent_id>/children", url_prefix = ("/ci_type_relations/<int:parent_id>/children",
@@ -41,7 +45,8 @@ class GetParentsView(APIView):
class CITypeRelationView(APIView): class CITypeRelationView(APIView):
url_prefix = ("/ci_type_relations", "/ci_type_relations/<int:parent_id>/<int:child_id>") 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): def get(self):
res, type2attributes = CITypeRelationManager.get() res, type2attributes = CITypeRelationManager.get()
@@ -69,7 +74,8 @@ class CITypeRelationView(APIView):
class CITypeRelationDelete2View(APIView): class CITypeRelationDelete2View(APIView):
url_prefix = "/ci_type_relations/<int:ctr_id>" 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): def delete(self, ctr_id):
CITypeRelationManager.delete(ctr_id) CITypeRelationManager.delete(ctr_id)

View File

@@ -3,14 +3,16 @@
from flask import request 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 CustomDashboardManager
from api.lib.cmdb.custom_dashboard import SystemConfigManager 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_required
from api.lib.decorator import args_validate from api.lib.decorator import args_validate
from api.lib.perm.acl.acl import role_required
from api.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class CustomDashboardApiView(APIView): class CustomDashboardApiView(APIView):
url_prefix = ("/custom_dashboard", "/custom_dashboard/<int:_id>", "/custom_dashboard/batch", url_prefix = ("/custom_dashboard", "/custom_dashboard/<int:_id>", "/custom_dashboard/batch",
@@ -19,7 +21,8 @@ class CustomDashboardApiView(APIView):
def get(self): def get(self):
return self.jsonify(CustomDashboardManager.get()) 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) @args_validate(CustomDashboardManager.cls)
def post(self): def post(self):
if request.url.endswith("/preview"): if request.url.endswith("/preview"):
@@ -32,7 +35,8 @@ class CustomDashboardApiView(APIView):
return self.jsonify(res) 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) @args_validate(CustomDashboardManager.cls)
def put(self, _id=None): def put(self, _id=None):
if _id is not None: if _id is not None:
@@ -47,7 +51,8 @@ class CustomDashboardApiView(APIView):
return self.jsonify(id2options=request.values.get('id2options')) 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): def delete(self, _id):
CustomDashboardManager.delete(_id) CustomDashboardManager.delete(_id)
@@ -57,12 +62,14 @@ class CustomDashboardApiView(APIView):
class SystemConfigApiView(APIView): class SystemConfigApiView(APIView):
url_prefix = ("/system_config",) 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) @args_required("name", value_required=True)
def get(self): def get(self):
return self.jsonify(SystemConfigManager.get(request.values['name'])) 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_validate(SystemConfigManager.cls)
@args_required("name", value_required=True) @args_required("name", value_required=True)
@args_required("option", value_required=True) @args_required("option", value_required=True)
@@ -74,7 +81,8 @@ class SystemConfigApiView(APIView):
def put(self, _id=None): def put(self, _id=None):
return self.post() 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") @args_required("name")
def delete(self): def delete(self):
CustomDashboardManager.delete(request.values['name']) CustomDashboardManager.delete(request.values['name'])

View File

@@ -5,28 +5,29 @@ import datetime
from flask import abort from flask import abort
from flask import request from flask import request
from flask import session
from api.lib.cmdb.ci import CIManager from api.lib.cmdb.ci import CIManager
from api.lib.cmdb.const import PermEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.history import AttributeHistoryManger from api.lib.cmdb.history import AttributeHistoryManger
from api.lib.cmdb.history import CITriggerHistoryManager from api.lib.cmdb.history import CITriggerHistoryManager
from api.lib.cmdb.history import CITypeHistoryManager from api.lib.cmdb.history import CITypeHistoryManager
from api.lib.cmdb.resp_format import ErrFormat 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 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
from api.lib.utils import get_page_size from api.lib.utils import get_page_size
from api.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class RecordView(APIView): class RecordView(APIView):
url_prefix = ("/history/records/attribute", "/history/records/relation") 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): def get(self):
page = get_page(request.values.get("page", 1)) page = get_page(request.values.get("page", 1))
page_size = get_page_size(request.values.get("page_size")) page_size = get_page_size(request.values.get("page_size"))
@@ -80,18 +81,21 @@ class CIHistoryView(APIView):
class CITriggerHistoryView(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) @has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.READ, CIManager.get_type_name)
def get(self, ci_id=None): def get(self, ci_id):
if ci_id is not None: result = CITriggerHistoryManager.get_by_ci_id(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") type_id = request.values.get("type_id")
trigger_id = request.values.get("trigger_id") trigger_id = request.values.get("trigger_id")
operate_type = request.values.get("operate_type") operate_type = request.values.get("operate_type")
@@ -115,7 +119,8 @@ class CITriggerHistoryView(APIView):
class CITypeHistoryView(APIView): class CITypeHistoryView(APIView):
url_prefix = "/history/ci_types" 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): def get(self):
type_id = request.values.get("type_id") type_id = request.values.get("type_id")
username = request.values.get("username") username = request.values.get("username")

View File

@@ -8,20 +8,22 @@ from flask import request
from api.lib.cmdb.ci_type import CITypeManager from api.lib.cmdb.ci_type import CITypeManager
from api.lib.cmdb.const import PermEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.perms import CIFilterPermsCRUD from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.cmdb.preference import PreferenceManager from api.lib.cmdb.preference import PreferenceManager
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.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_required
from api.lib.decorator import args_validate from api.lib.decorator import args_validate
from api.lib.perm.acl.acl import ACLManager 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 has_perm_from_args
from api.lib.perm.acl.acl import is_app_admin 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.perm.acl.acl import validate_permission
from api.lib.utils import handle_arg_list from api.lib.utils import handle_arg_list
from api.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class PreferenceShowCITypesView(APIView): class PreferenceShowCITypesView(APIView):
url_prefix = ("/preference/ci_types", "/preference/ci_types2") url_prefix = ("/preference/ci_types", "/preference/ci_types2")
@@ -104,7 +106,8 @@ class PreferenceRelationApiView(APIView):
return self.jsonify(views=views, id2type=id2type, name2id=name2id) 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("name")
@args_required("cr_ids") @args_required("cr_ids")
@args_validate(PreferenceManager.pref_rel_cls) @args_validate(PreferenceManager.pref_rel_cls)
@@ -118,14 +121,16 @@ class PreferenceRelationApiView(APIView):
return self.jsonify(views=views, id2type=id2type, name2id=name2id) 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("name")
def put(self, _id): def put(self, _id):
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(_id=_id, **request.values) views, id2type, name2id = PreferenceManager.create_or_update_relation_view(_id=_id, **request.values)
return self.jsonify(views=views, id2type=id2type, name2id=name2id) 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("name")
def delete(self): def delete(self):
name = request.values.get("name") name = request.values.get("name")

View File

@@ -4,14 +4,16 @@
from flask import abort from flask import abort
from flask import request from flask import request
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.relation_type import RelationTypeManager from api.lib.cmdb.relation_type import RelationTypeManager
from api.lib.cmdb.resp_format import ErrFormat 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_required
from api.lib.decorator import args_validate from api.lib.decorator import args_validate
from api.lib.perm.acl.acl import role_required
from api.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class RelationTypeView(APIView): class RelationTypeView(APIView):
url_prefix = ("/relation_types", "/relation_types/<int:rel_id>") url_prefix = ("/relation_types", "/relation_types/<int:rel_id>")
@@ -19,7 +21,8 @@ class RelationTypeView(APIView):
def get(self): def get(self):
return self.jsonify([i.to_dict() for i in RelationTypeManager.get_all()]) 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_required("name")
@args_validate(RelationTypeManager.cls) @args_validate(RelationTypeManager.cls)
def post(self): def post(self):
@@ -28,7 +31,8 @@ class RelationTypeView(APIView):
return self.jsonify(rel.to_dict()) 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_required("name")
@args_validate(RelationTypeManager.cls) @args_validate(RelationTypeManager.cls)
def put(self, rel_id): def put(self, rel_id):
@@ -37,7 +41,8 @@ class RelationTypeView(APIView):
return self.jsonify(rel.to_dict()) 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): def delete(self, rel_id):
RelationTypeManager.delete(rel_id) RelationTypeManager.delete(rel_id)

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 3857903 */ font-family: "iconfont"; /* Project id 3857903 */
src: url('iconfont.woff2?t=1713335725699') format('woff2'), src: url('iconfont.woff2?t=1713840593232') format('woff2'),
url('iconfont.woff?t=1713335725699') format('woff'), url('iconfont.woff?t=1713840593232') format('woff'),
url('iconfont.ttf?t=1713335725699') format('truetype'); url('iconfont.ttf?t=1713840593232') format('truetype');
} }
.iconfont { .iconfont {
@@ -13,6 +13,30 @@
-moz-osx-font-smoothing: grayscale; -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 { .veops-show:before {
content: "\e914"; content: "\e914";
} }
@@ -21,7 +45,7 @@
content: "\e913"; content: "\e913";
} }
.a-itsm-workload1:before { .itsm-workload:before {
content: "\e912"; content: "\e912";
} }
@@ -269,10 +293,6 @@
content: "\e8d5"; content: "\e8d5";
} }
.ops-setting-auth-selected:before {
content: "\e8d4";
}
.itsm-knowledge2:before { .itsm-knowledge2:before {
content: "\e8d2"; content: "\e8d2";
} }
@@ -501,10 +521,6 @@
content: "\e89c"; content: "\e89c";
} }
.ops-setting-duty-selected:before {
content: "\e89b";
}
.datainsight-sequential:before { .datainsight-sequential:before {
content: "\e899"; content: "\e899";
} }
@@ -669,38 +685,6 @@
content: "\e870"; 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 { .ops-itsm-servicecatalog:before {
content: "\e868"; content: "\e868";
} }
@@ -1073,26 +1057,10 @@
content: "\e816"; content: "\e816";
} }
.ops-cmdb-batch-selected:before {
content: "\e803";
}
.ops-cmdb-batch:before { .ops-cmdb-batch:before {
content: "\e80a"; 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 { .ops-cmdb-preference:before {
content: "\e7fa"; content: "\e7fa";
} }
@@ -1101,22 +1069,10 @@
content: "\e7fb"; content: "\e7fb";
} }
.ops-cmdb-tree-selected:before {
content: "\e7fc";
}
.ops-cmdb-relation-selected:before {
content: "\e7fd";
}
.ops-cmdb-adc:before { .ops-cmdb-adc:before {
content: "\e7fe"; content: "\e7fe";
} }
.ops-cmdb-search-selected:before {
content: "\e7ff";
}
.ops-cmdb-relation:before { .ops-cmdb-relation:before {
content: "\e800"; content: "\e800";
} }
@@ -1125,14 +1081,6 @@
content: "\e801"; content: "\e801";
} }
.ops-cmdb-citype-selected:before {
content: "\e802";
}
.ops-cmdb-dashboard-selected:before {
content: "\e804";
}
.ops-cmdb-citype:before { .ops-cmdb-citype:before {
content: "\e805"; content: "\e805";
} }
@@ -1141,10 +1089,6 @@
content: "\e806"; content: "\e806";
} }
.ops-cmdb-screen-selected:before {
content: "\e807";
}
.ops-cmdb-resource:before { .ops-cmdb-resource:before {
content: "\e808"; content: "\e808";
} }
@@ -1493,14 +1437,6 @@
content: "\e7a6"; content: "\e7a6";
} }
.ops-setting-role-selected:before {
content: "\e7a0";
}
.ops-setting-group-selected:before {
content: "\e7a1";
}
.ops-setting-role:before { .ops-setting-role:before {
content: "\e7a2"; content: "\e7a2";
} }
@@ -1941,18 +1877,10 @@
content: "\e738"; content: "\e738";
} }
.ops-setting-notice-email-selected-copy:before {
content: "\e889";
}
.ops-setting-notice:before { .ops-setting-notice:before {
content: "\e72f"; content: "\e72f";
} }
.ops-setting-notice-selected:before {
content: "\e730";
}
.ops-setting-notice-email-selected:before { .ops-setting-notice-email-selected:before {
content: "\e731"; content: "\e731";
} }
@@ -1977,10 +1905,6 @@
content: "\e736"; content: "\e736";
} }
.ops-setting-companyStructure-selected:before {
content: "\e72b";
}
.ops-setting-companyStructure:before { .ops-setting-companyStructure:before {
content: "\e72c"; content: "\e72c";
} }
@@ -1989,10 +1913,6 @@
content: "\e72d"; content: "\e72d";
} }
.ops-setting-companyInfo-selected:before {
content: "\e72e";
}
.ops-email:before { .ops-email:before {
content: "\e61a"; content: "\e61a";
} }
@@ -3033,14 +2953,6 @@
content: "\e600"; content: "\e600";
} }
.ops-dag-dashboard-selected:before {
content: "\e601";
}
.ops-dag-applet-selected:before {
content: "\e602";
}
.ops-dag-applet:before { .ops-dag-applet:before {
content: "\e603"; content: "\e603";
} }
@@ -3049,62 +2961,26 @@
content: "\e604"; content: "\e604";
} }
.ops-dag-terminal-selected:before {
content: "\e605";
}
.ops-dag-cron:before { .ops-dag-cron:before {
content: "\e606"; content: "\e606";
} }
.ops-dag-cron-selected:before {
content: "\e608";
}
.ops-dag-history:before { .ops-dag-history:before {
content: "\e609"; content: "\e609";
} }
.ops-dag-history-selected:before {
content: "\e60a";
}
.ops-dag-dags-selected:before {
content: "\e60c";
}
.ops-dag-dagreview:before { .ops-dag-dagreview:before {
content: "\e60d"; content: "\e60d";
} }
.ops-dag-dagreview-selected:before {
content: "\e60e";
}
.ops-dag-panel:before { .ops-dag-panel:before {
content: "\e60f"; content: "\e60f";
} }
.ops-dag-panel-selected:before {
content: "\e615";
}
.ops-dag-variables:before { .ops-dag-variables:before {
content: "\e616"; 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 { .ops-dag-dags:before {
content: "\e60b"; content: "\e60b";
} }

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,48 @@
"css_prefix_text": "", "css_prefix_text": "",
"description": "", "description": "",
"glyphs": [ "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", "icon_id": "39948814",
"name": "veops-show", "name": "veops-show",
@@ -22,7 +64,7 @@
{ {
"icon_id": "39926833", "icon_id": "39926833",
"name": "itsm-workload (1)", "name": "itsm-workload (1)",
"font_class": "a-itsm-workload1", "font_class": "itsm-workload",
"unicode": "e912", "unicode": "e912",
"unicode_decimal": 59666 "unicode_decimal": 59666
}, },
@@ -453,13 +495,6 @@
"unicode": "e8d5", "unicode": "e8d5",
"unicode_decimal": 59605 "unicode_decimal": 59605
}, },
{
"icon_id": "38547389",
"name": "setting-authentication-selected",
"font_class": "ops-setting-auth-selected",
"unicode": "e8d4",
"unicode_decimal": 59604
},
{ {
"icon_id": "38533133", "icon_id": "38533133",
"name": "itsm-knowledge (2)", "name": "itsm-knowledge (2)",
@@ -859,13 +894,6 @@
"unicode": "e89c", "unicode": "e89c",
"unicode_decimal": 59548 "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", "icon_id": "37841524",
"name": "datainsight-sequential", "name": "datainsight-sequential",
@@ -1153,62 +1181,6 @@
"unicode": "e870", "unicode": "e870",
"unicode_decimal": 59504 "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", "icon_id": "35984169",
"name": "ops-itsm-servicecatalog", "name": "ops-itsm-servicecatalog",
@@ -1860,13 +1832,6 @@
"unicode": "e816", "unicode": "e816",
"unicode_decimal": 59414 "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", "icon_id": "35400646",
"name": "ops-cmdb-batch", "name": "ops-cmdb-batch",
@@ -1874,27 +1839,6 @@
"unicode": "e80a", "unicode": "e80a",
"unicode_decimal": 59402 "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", "icon_id": "35395303",
"name": "ops-cmdb-preference", "name": "ops-cmdb-preference",
@@ -1909,20 +1853,6 @@
"unicode": "e7fb", "unicode": "e7fb",
"unicode_decimal": 59387 "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", "icon_id": "35395307",
"name": "ops-cmdb-adc", "name": "ops-cmdb-adc",
@@ -1930,13 +1860,6 @@
"unicode": "e7fe", "unicode": "e7fe",
"unicode_decimal": 59390 "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", "icon_id": "35395309",
"name": "ops-cmdb-relation", "name": "ops-cmdb-relation",
@@ -1951,20 +1874,6 @@
"unicode": "e801", "unicode": "e801",
"unicode_decimal": 59393 "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", "icon_id": "35395314",
"name": "ops-cmdb-citype", "name": "ops-cmdb-citype",
@@ -1979,13 +1888,6 @@
"unicode": "e806", "unicode": "e806",
"unicode_decimal": 59398 "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", "icon_id": "35395317",
"name": "ops-cmdb-resource", "name": "ops-cmdb-resource",
@@ -2595,20 +2497,6 @@
"unicode": "e7a6", "unicode": "e7a6",
"unicode_decimal": 59302 "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", "icon_id": "34792794",
"name": "ops-setting-role", "name": "ops-setting-role",
@@ -3379,13 +3267,6 @@
"unicode": "e738", "unicode": "e738",
"unicode_decimal": 59192 "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", "icon_id": "34108346",
"name": "ops-setting-notice", "name": "ops-setting-notice",
@@ -3393,13 +3274,6 @@
"unicode": "e72f", "unicode": "e72f",
"unicode_decimal": 59183 "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", "icon_id": "34108504",
"name": "ops-setting-notice-email-selected", "name": "ops-setting-notice-email-selected",
@@ -3442,13 +3316,6 @@
"unicode": "e736", "unicode": "e736",
"unicode_decimal": 59190 "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", "icon_id": "34108296",
"name": "ops-setting-companyStructure", "name": "ops-setting-companyStructure",
@@ -3463,13 +3330,6 @@
"unicode": "e72d", "unicode": "e72d",
"unicode_decimal": 59181 "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", "icon_id": "34099810",
"name": "ops-email", "name": "ops-email",
@@ -5290,20 +5150,6 @@
"unicode": "e600", "unicode": "e600",
"unicode_decimal": 58880 "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", "icon_id": "33053531",
"name": "ops-dag-applet", "name": "ops-dag-applet",
@@ -5318,13 +5164,6 @@
"unicode": "e604", "unicode": "e604",
"unicode_decimal": 58884 "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", "icon_id": "33053591",
"name": "ops-dag-cron", "name": "ops-dag-cron",
@@ -5332,13 +5171,6 @@
"unicode": "e606", "unicode": "e606",
"unicode_decimal": 58886 "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", "icon_id": "33053615",
"name": "ops-dag-history", "name": "ops-dag-history",
@@ -5346,20 +5178,6 @@
"unicode": "e609", "unicode": "e609",
"unicode_decimal": 58889 "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", "icon_id": "33053682",
"name": "ops-dag-dagreview", "name": "ops-dag-dagreview",
@@ -5367,13 +5185,6 @@
"unicode": "e60d", "unicode": "e60d",
"unicode_decimal": 58893 "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", "icon_id": "33053691",
"name": "ops-dag-panel", "name": "ops-dag-panel",
@@ -5381,13 +5192,6 @@
"unicode": "e60f", "unicode": "e60f",
"unicode_decimal": 58895 "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", "icon_id": "33053707",
"name": "ops-dag-variables", "name": "ops-dag-variables",
@@ -5395,27 +5199,6 @@
"unicode": "e616", "unicode": "e616",
"unicode_decimal": 58902 "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", "icon_id": "33055163",
"name": "ops-dag-dags", "name": "ops-dag-dags",

Binary file not shown.

View File

@@ -73,7 +73,7 @@ export default {
.custom-drawer-close { .custom-drawer-close {
position: absolute; position: absolute;
cursor: pointer; cursor: pointer;
background: #custom_colors[color_1]; background: @primary-color;
color: white; color: white;
text-align: center; text-align: center;
transition: all 0.3s; transition: all 0.3s;

View File

@@ -298,14 +298,14 @@ export default {
width: 20px; width: 20px;
height: 20px; height: 20px;
border-radius: 2px; border-radius: 2px;
background-color: #custom_colors[color_2]; background-color: @primary-color_5;
color: #custom_colors[color_1]; color: @primary-color;
display: inline-flex; display: inline-flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background-color: #custom_colors[color_1]; background-color: @primary-color;
color: #fff; color: #fff;
} }
} }

View File

@@ -161,7 +161,7 @@ export default {
cursor: pointer; cursor: pointer;
&-selected, &-selected,
&:hover { &:hover {
color: #custom_colors[color_1]; color: @primary-color;
} }
} }
} }
@@ -179,7 +179,7 @@ export default {
font-weight: 400; font-weight: 400;
font-size: 14px; font-size: 14px;
color: #000000; color: #000000;
border-left: 2px solid #custom_colors[color_1]; border-left: 2px solid @primary-color;
padding-left: 6px; padding-left: 6px;
margin-left: -6px; margin-left: -6px;
} }

View File

@@ -185,14 +185,14 @@ export default {
width: 20px; width: 20px;
height: 20px; height: 20px;
border-radius: 2px; border-radius: 2px;
background-color: #custom_colors[color_2]; background-color: @primary-color_5;
color: #custom_colors[color_1]; color: @primary-color;
display: inline-flex; display: inline-flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background-color: #custom_colors[color_1]; background-color: @primary-color;
color: #fff; color: #fff;
} }
} }

View File

@@ -93,7 +93,7 @@ export default {
background-color: transparent; background-color: transparent;
} }
.sidebar-list-item.sidebar-list-item-selected::before { .sidebar-list-item.sidebar-list-item-selected::before {
background-color: #custom_colors[color_1]; background-color: @primary-color;
} }
.sidebar-list-item-dotline { .sidebar-list-item-dotline {
padding-left: 20px; padding-left: 20px;

View File

@@ -97,8 +97,8 @@ export default {
<style lang="less"> <style lang="less">
.color { .color {
color: #custom_colors[color_1]; color: @primary-color;
background-color: #custom_colors[color_2]; background-color: @primary-color_5;
} }
.custom-user { .custom-user {
.custom-user-item { .custom-user-item {
@@ -117,7 +117,7 @@ export default {
.locale { .locale {
cursor: pointer; cursor: pointer;
&:hover { &:hover {
color: #custom_colors[color_1]; color: @primary-color;
} }
} }
</style> </style>

View File

@@ -54,3 +54,36 @@ export function getCiTriggersByCiId(ci_id, params) {
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
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -230,7 +230,7 @@ export default {
background-color: #f9fbff; background-color: #f9fbff;
border-bottom: none; border-bottom: none;
.ant-transfer-list-header-title { .ant-transfer-list-header-title {
color: #custom_colors[color_1]; color: @primary-color;
font-weight: 400; font-weight: 400;
font-size: 14px; font-size: 14px;
} }
@@ -258,7 +258,7 @@ export default {
cursor: pointer; cursor: pointer;
font-size: 12px; font-size: 12px;
background-color: #fff; background-color: #fff;
color: #custom_colors[color_1]; color: @primary-color;
border-radius: 4px; border-radius: 4px;
width: 12px; width: 12px;
} }
@@ -271,7 +271,7 @@ export default {
font-size: 12px; font-size: 12px;
color: #cacdd9; color: #cacdd9;
&:hover { &:hover {
color: #custom_colors[color_1]; color: @primary-color;
} }
} }
.move-icon { .move-icon {
@@ -291,8 +291,8 @@ export default {
} }
} }
.ant-transfer-list-content-item-selected { .ant-transfer-list-content-item-selected {
background-color: #custom_colors[color_2]; background-color: @primary-color_5;
border-color: #custom_colors[color_1]; border-color: @primary-color;
} }
} }
} }

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 130}px` }"> <div class="cmdb-grant" :style="{ }">
<template v-if="cmdbGrantType.includes('ci_type')"> <template v-if="cmdbGrantType.includes('ci_type')">
<div class="cmdb-grant-title">{{ $t('cmdb.components.ciTypeGrant') }}</div> <div class="cmdb-grant-title">{{ $t('cmdb.components.ciTypeGrant') }}</div>
<CiTypeGrant <CiTypeGrant

View File

@@ -75,10 +75,10 @@ export default {
position: absolute; position: absolute;
width: 0; width: 0;
height: 0; height: 0;
// background-color: #custom_colors[color_1]; // background-color: @primary-color;
border-radius: 2px; border-radius: 2px;
border: 14px solid transparent; border: 14px solid transparent;
border-left-color: #custom_colors[color_1]; border-left-color: @primary-color;
transform: rotate(225deg); transform: rotate(225deg);
top: -16px; top: -16px;
left: -17px; left: -17px;

View File

@@ -175,8 +175,8 @@ export default {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
&:hover { &:hover {
background-color: #custom_colors[color_2]; background-color: @primary-color_5;
color: #custom_colors[color_1]; color: @primary-color;
} }
} }
} }
@@ -188,7 +188,7 @@ export default {
.notice-content { .notice-content {
.w-e-bar { .w-e-bar {
background-color: #custom_colors[color_2]; background-color: @primary-color_5;
} }
.w-e-text-placeholder { .w-e-text-placeholder {
line-height: 1.5; line-height: 1.5;

View File

@@ -253,7 +253,7 @@ export default {
} }
.cmdb-subscribe-drawer-tree-header { .cmdb-subscribe-drawer-tree-header {
border-radius: 4px; border-radius: 4px;
background-color: #custom_colors[color_2]; background-color: @primary-color_5;
color: rgba(0, 0, 0, 0.4); color: rgba(0, 0, 0, 0.4);
padding: 8px 12px; padding: 8px 12px;
margin-bottom: 12px; margin-bottom: 12px;
@@ -264,7 +264,7 @@ export default {
> span { > span {
display: inline-block; display: inline-block;
background-color: #fff; background-color: #fff;
border-left: 2px solid #custom_colors[color_1]; border-left: 2px solid @primary-color;
padding: 3px 12px; padding: 3px 12px;
position: relative; position: relative;
white-space: nowrap; white-space: nowrap;
@@ -272,7 +272,7 @@ export default {
cursor: pointer; cursor: pointer;
font-size: 12px; font-size: 12px;
&:hover { &:hover {
color: #custom_colors[color_1]; color: @primary-color;
} }
} }
} }
@@ -316,7 +316,7 @@ export default {
<style lang="less"> <style lang="less">
.cmdb-subscribe-drawer { .cmdb-subscribe-drawer {
.ant-tabs-bar { .ant-tabs-bar {
background-color: #custom_colors[color_2]; background-color: @primary-color_5;
border-bottom: none; border-bottom: none;
} }
} }

View File

@@ -39,7 +39,7 @@
> >
<img slot="image" :src="require('@/assets/data_empty.png')" /> <img slot="image" :src="require('@/assets/data_empty.png')" />
<span slot="description"> {{ $t('cmdb.components.noParamRequest') }} </span> <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') }} {{ $t('add') }}
</a-button> </a-button>
</a-empty> </a-empty>

View File

@@ -402,7 +402,15 @@ const cmdb_en = {
noModifications: 'No Modifications', noModifications: 'No Modifications',
attr: 'attribute', attr: 'attribute',
attrId: 'attribute id', 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: { relation_type: {
addRelationType: 'New', addRelationType: 'New',
@@ -494,7 +502,8 @@ if __name__ == "__main__":
copyFailed: 'Copy failed', copyFailed: 'Copy failed',
noLevel: 'No hierarchical relationship!', noLevel: 'No hierarchical relationship!',
batchAddRelation: 'Batch Add Relation', batchAddRelation: 'Batch Add Relation',
history: 'History', history: 'Change Logs',
relITSM: 'Related Tickets',
topo: 'Topology', topo: 'Topology',
table: 'Table', table: 'Table',
m2mTips: 'The current CIType relationship is many-to-many, please go to the SerivceTree(relation view) to add or delete', m2mTips: 'The current CIType relationship is many-to-many, please go to the SerivceTree(relation view) to add or delete',
@@ -512,7 +521,21 @@ if __name__ == "__main__":
newUpdateField: 'Add a Attribute', newUpdateField: 'Add a Attribute',
attributeSettings: 'Attribute Settings', attributeSettings: 'Attribute Settings',
share: 'Share', 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: { serviceTree: {
remove: 'Remove', remove: 'Remove',

View File

@@ -402,7 +402,15 @@ const cmdb_zh = {
noModifications: '没有修改', noModifications: '没有修改',
attr: '属性名', attr: '属性名',
attrId: '属性ID', 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: { relation_type: {
addRelationType: '新增关系类型', addRelationType: '新增关系类型',
@@ -494,7 +502,8 @@ if __name__ == "__main__":
copyFailed: '复制失败!', copyFailed: '复制失败!',
noLevel: '无层级关系!', noLevel: '无层级关系!',
batchAddRelation: '批量添加关系', batchAddRelation: '批量添加关系',
history: '操作历史', history: '变更记录',
relITSM: '关联工单',
topo: '拓扑', topo: '拓扑',
table: '表格', table: '表格',
m2mTips: '当前模型关系为多对多,请前往关系视图进行增删操作', m2mTips: '当前模型关系为多对多,请前往关系视图进行增删操作',
@@ -512,7 +521,21 @@ if __name__ == "__main__":
newUpdateField: '新增修改字段', newUpdateField: '新增修改字段',
attributeSettings: '字段设置', attributeSettings: '字段设置',
share: '分享', share: '分享',
noPermission: '暂无权限' noPermission: '暂无权限',
rollback: '回滚',
rollbackHeader: '实例回滚',
rollbackTo: '回滚至: ',
rollbackToTips: '请选择回滚时间点',
baselineDiff: '基线对比结果',
instance: '实例',
rollbackBefore: '当前值',
rollbackAfter: '回滚后',
noDiff: '在【{baseline}】后数据没有发生变化',
rollbackConfirm: '确认要回滚吗 ',
rollbackSuccess: '回滚成功',
rollbackingTips: '正在批量回滚中',
batchRollbacking: '正在回滚,共{total}个,成功{successNum}个,失败{errorNum}个',
baselineTips: '该时间点的变更也会被回滚, 唯一标识、密码属性不支持回滚',
}, },
serviceTree: { serviceTree: {
remove: '移除', remove: '移除',

View File

@@ -13,7 +13,7 @@ const genCmdbRoutes = async () => {
{ {
path: '/cmdb/dashboard', path: '/cmdb/dashboard',
name: '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') component: () => import('../views/dashboard/index_v2.vue')
}, },
{ {
@@ -25,7 +25,7 @@ const genCmdbRoutes = async () => {
path: '/cmdb/resourceviews', path: '/cmdb/resourceviews',
name: 'cmdb_resource_views', name: 'cmdb_resource_views',
component: RouteView, 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, hideChildrenInMenu: false,
children: [] children: []
}, },
@@ -33,7 +33,7 @@ const genCmdbRoutes = async () => {
path: '/cmdb/tree_views', path: '/cmdb/tree_views',
component: () => import('../views/tree_views'), component: () => import('../views/tree_views'),
name: 'cmdb_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, hideChildrenInMenu: true,
children: [ children: [
{ {
@@ -47,13 +47,13 @@ const genCmdbRoutes = async () => {
{ {
path: '/cmdb/resourcesearch', path: '/cmdb/resourcesearch',
name: 'cmdb_resource_search', 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') component: () => import('../views/resource_search/index.vue')
}, },
{ {
path: '/cmdb/adc', path: '/cmdb/adc',
name: 'cmdb_auto_discovery_ci', 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') component: () => import('../views/discoveryCI/index.vue')
}, },
{ {
@@ -72,19 +72,19 @@ const genCmdbRoutes = async () => {
path: '/cmdb/preference', path: '/cmdb/preference',
component: () => import('../views/preference/index'), component: () => import('../views/preference/index'),
name: 'cmdb_preference', 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', path: '/cmdb/batch',
component: () => import('../views/batch'), component: () => import('../views/batch'),
name: 'cmdb_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', path: '/cmdb/ci_types',
name: 'ci_type', name: 'ci_type',
component: () => import('../views/ci_types/index'), 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', path: '/cmdb/disabled3',
@@ -166,7 +166,7 @@ const genCmdbRoutes = async () => {
path: `/cmdb/relationviews/${item[1]}`, path: `/cmdb/relationviews/${item[1]}`,
name: `cmdb_relation_views_${item[1]}`, name: `cmdb_relation_views_${item[1]}`,
component: () => import('../views/relation_views/index'), component: () => import('../views/relation_views/index'),
meta: { title: item[0], icon: 'ops-cmdb-relation', selectedIcon: 'ops-cmdb-relation-selected', keepAlive: false, name: item[0] }, meta: { title: item[0], icon: 'ops-cmdb-relation', selectedIcon: 'ops-cmdb-relation', keepAlive: false, name: item[0] },
} }
}) })
routes.children.splice(2, 0, ...relationViews) routes.children.splice(2, 0, ...relationViews)

View File

@@ -2,7 +2,7 @@
<div> <div>
<div class="ci-detail-header">{{ this.type.alias }}</div> <div class="ci-detail-header">{{ this.type.alias }}</div>
<div class="ci-detail-page"> <div class="ci-detail-page">
<CiDetailTab ref="ciDetailTab" :typeId="typeId" /> <ci-detail-tab ref="ciDetailTab" :typeId="typeId" :attributeHistoryTableHeight="windowHeight - 250" />
</div> </div>
</div> </div>
</template> </template>
@@ -23,6 +23,11 @@ export default {
attributes: {}, attributes: {},
} }
}, },
computed: {
windowHeight() {
return this.$store.state.windowHeight
},
},
provide() { provide() {
return { return {
attrList: () => { attrList: () => {
@@ -55,7 +60,6 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.ci-detail-header { .ci-detail-header {
border-left: 3px solid @primary-color; border-left: 3px solid @primary-color;
padding-left: 10px; padding-left: 10px;

View File

@@ -68,6 +68,8 @@
<span @click="openBatchDownload">{{ $t('download') }}</span> <span @click="openBatchDownload">{{ $t('download') }}</span>
<a-divider type="vertical" /> <a-divider type="vertical" />
<span @click="batchDelete">{{ $t('delete') }}</span> <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> <span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span>
</div> </div>
</SearchForm> </SearchForm>
@@ -293,6 +295,7 @@
<create-instance-form ref="create" @reload="reloadData" @submit="batchUpdate" /> <create-instance-form ref="create" @reload="reloadData" @submit="batchUpdate" />
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" /> <JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
<BatchDownload ref="batchDownload" @batchDownload="batchDownload" /> <BatchDownload ref="batchDownload" @batchDownload="batchDownload" />
<ci-rollback-form ref="ciRollbackForm" @batchRollbackAsync="batchRollbackAsync($event)" :ciIds="selectedRowKeys" />
<MetadataDrawer ref="metadataDrawer" /> <MetadataDrawer ref="metadataDrawer" />
<CMDBGrant ref="cmdbGrant" resourceTypeName="CIType" app_id="cmdb" /> <CMDBGrant ref="cmdbGrant" resourceTypeName="CIType" app_id="cmdb" />
</a-spin> </a-spin>
@@ -325,6 +328,8 @@ import MetadataDrawer from './modules/MetadataDrawer.vue'
import CMDBGrant from '../../components/cmdbGrant' import CMDBGrant from '../../components/cmdbGrant'
import { ops_move_icon as OpsMoveIcon } from '@/core/icons' import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
import { getAttrPassword } from '../../api/CITypeAttr' import { getAttrPassword } from '../../api/CITypeAttr'
import CiRollbackForm from './modules/ciRollbackForm.vue'
import { CIBaselineRollback } from '../../api/history'
export default { export default {
name: 'InstanceList', name: 'InstanceList',
@@ -340,6 +345,7 @@ export default {
MetadataDrawer, MetadataDrawer,
CMDBGrant, CMDBGrant,
OpsMoveIcon, OpsMoveIcon,
CiRollbackForm,
}, },
computed: { computed: {
windowHeight() { windowHeight() {
@@ -429,6 +435,12 @@ export default {
// window.onkeypress = (e) => { // window.onkeypress = (e) => {
// this.handleKeyPress(e) // this.handleKeyPress(e)
// } // }
this.$nextTick(() => {
const loadingNode = document.getElementsByClassName('ant-drawer-mask')
if (loadingNode?.style) {
loadingNode.style.zIndex = 8
}
})
setTimeout(() => { setTimeout(() => {
this.columnDrop() this.columnDrop()
}, 1000) }, 1000)
@@ -661,7 +673,7 @@ export default {
message: this.$t('warning'), message: this.$t('warning'),
description: errorMsg, description: errorMsg,
duration: 0, duration: 0,
style: { whiteSpace: 'break-spaces' }, style: { whiteSpace: 'break-spaces', overflow: 'auto', maxHeight: this.windowHeight - 80 + 'px' },
}) })
errorNum += 1 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() { async refreshAfterEditAttrs() {
await this.loadPreferenceAttrList() await this.loadPreferenceAttrList()
await this.loadTableData() await this.loadTableData()

View File

@@ -1,12 +1,13 @@
<template> <template>
<CustomDrawer <CustomDrawer
width="80%" width="90%"
placement="left" placement="left"
@close=" @close="
() => { () => {
visible = false visible = false
} }
" "
style="transform: translateX(0px)!important"
:visible="visible" :visible="visible"
:hasTitle="false" :hasTitle="false"
:hasFooter="false" :hasFooter="false"

View File

@@ -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>

View File

@@ -20,7 +20,7 @@
:key="attr.name" :key="attr.name"
v-for="attr in group.attributes" v-for="attr in group.attributes"
> >
<CiDetailAttrContent :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" /> <ci-detail-attr-content :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" />
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</div> </div>
@@ -28,22 +28,39 @@
<a-tab-pane key="tab_2"> <a-tab-pane key="tab_2">
<span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span> <span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
<div :style="{ height: '100%', padding: '24px', overflow: 'auto' }"> <div :style="{ height: '100%', padding: '24px', overflow: 'auto' }">
<CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" /> <ci-detail-relation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
</div> </div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="tab_3"> <a-tab-pane key="tab_3">
<span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span> <span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span>
<div :style="{ padding: '24px', height: '100%' }"> <div :style="{ padding: '24px', height: '100%' }">
<a-space :style="{ 'margin-bottom': '10px', display: 'flex' }">
<a-button type="primary" class="ops-button-ghost" ghost @click="handleRollbackCI()">
<ops-icon type="shishizhuangtai" />{{ $t('cmdb.ci.rollback') }}
</a-button>
</a-space>
<ci-rollback-form ref="ciRollbackForm" :ciIds="[ciId]" @getCIHistory="getCIHistory" />
<vxe-table <vxe-table
ref="xTable" ref="xTable"
show-overflow
show-header-overflow
:data="ciHistory" :data="ciHistory"
size="small" size="small"
height="auto" :height="tableHeight"
highlight-hover-row
:span-method="mergeRowMethod" :span-method="mergeRowMethod"
:scroll-y="{ enabled: false, gt: 20 }"
:scroll-x="{ enabled: false, gt: 0 }"
border border
:scroll-y="{ enabled: false }" resizable
class="ops-stripe-table" class="ops-unstripe-table"
> >
<template #empty>
<a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }">
<img slot="image" :src="require('@/assets/data_empty.png')" />
<span slot="description"> {{ $t('noData') }} </span>
</a-empty>
</template>
<vxe-table-column sortable field="created_at" :title="$t('created_at')"></vxe-table-column> <vxe-table-column sortable field="created_at" :title="$t('created_at')"></vxe-table-column>
<vxe-table-column <vxe-table-column
field="username" field="username"
@@ -56,7 +73,7 @@
:filters="[ :filters="[
{ value: 0, label: $t('new') }, { value: 0, label: $t('new') },
{ value: 1, label: $t('delete') }, { value: 1, label: $t('delete') },
{ value: 3, label: $t('update') }, { value: 2, label: $t('update') },
]" ]"
:filter-method="filterOperateMethod" :filter-method="filterOperateMethod"
:title="$t('operation')" :title="$t('operation')"
@@ -71,8 +88,18 @@
:filters="[]" :filters="[]"
:filter-method="filterAttrMethod" :filter-method="filterAttrMethod"
></vxe-table-column> ></vxe-table-column>
<vxe-table-column field="old" :title="$t('cmdb.history.old')"></vxe-table-column> <vxe-table-column field="old" :title="$t('cmdb.history.old')">
<vxe-table-column field="new" :title="$t('cmdb.history.new')"></vxe-table-column> <template #default="{ row }">
<span v-if="row.value_type === '6'">{{ JSON.parse(row.old) }}</span>
<span v-else>{{ row.old }}</span>
</template>
</vxe-table-column>
<vxe-table-column field="new" :title="$t('cmdb.history.new')">
<template #default="{ row }">
<span v-if="row.value_type === '6'">{{ JSON.parse(row.new) }}</span>
<span v-else>{{ row.new }}</span>
</template>
</vxe-table-column>
</vxe-table> </vxe-table>
</div> </div>
</a-tab-pane> </a-tab-pane>
@@ -82,6 +109,12 @@
<TriggerTable :ci_id="ci._id" /> <TriggerTable :ci_id="ci._id" />
</div> </div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="tab_5">
<span slot="tab"><ops-icon type="itsm-association" />{{ $t('cmdb.ci.relITSM') }}</span>
<div :style="{ padding: '24px', height: '100%' }">
<related-itsm-table :ci_id="ci._id" :ciHistory="ciHistory" :itsmInstalled="itsmInstalled" />
</div>
</a-tab-pane>
</a-tabs> </a-tabs>
<a-empty <a-empty
v-else v-else
@@ -100,11 +133,15 @@
import _ from 'lodash' import _ from 'lodash'
import { Descriptions, DescriptionsItem } from 'element-ui' import { Descriptions, DescriptionsItem } from 'element-ui'
import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType' import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
import { getCIHistory } from '@/modules/cmdb/api/history' import { getCIHistory, judgeItsmInstalled } from '@/modules/cmdb/api/history'
import { getCIById } from '@/modules/cmdb/api/ci' import { getCIById } from '@/modules/cmdb/api/ci'
import CiDetailAttrContent from './ciDetailAttrContent.vue' import CiDetailAttrContent from './ciDetailAttrContent.vue'
import CiDetailRelation from './ciDetailRelation.vue' import CiDetailRelation from './ciDetailRelation.vue'
import TriggerTable from '../../operation_history/modules/triggerTable.vue' import TriggerTable from '../../operation_history/modules/triggerTable.vue'
import RelatedItsmTable from './ciDetailRelatedItsmTable.vue'
import CiRollbackForm from './ciRollbackForm.vue'
import { sleep } from '@/utils/util'
export default { export default {
name: 'CiDetailTab', name: 'CiDetailTab',
components: { components: {
@@ -113,6 +150,8 @@ export default {
CiDetailAttrContent, CiDetailAttrContent,
CiDetailRelation, CiDetailRelation,
TriggerTable, TriggerTable,
RelatedItsmTable,
CiRollbackForm,
}, },
props: { props: {
typeId: { typeId: {
@@ -123,10 +162,15 @@ export default {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
attributeHistoryTableHeight: {
type: Number,
default: null
}
}, },
data() { data() {
return { return {
ci: {}, ci: {},
item: [],
attributeGroups: [], attributeGroups: [],
activeTabKey: 'tab_1', activeTabKey: 'tab_1',
rowSpanMap: {}, rowSpanMap: {},
@@ -134,6 +178,8 @@ export default {
ciId: null, ciId: null,
ci_types: [], ci_types: [],
hasPermission: true, hasPermission: true,
itsmInstalled: true,
tableHeight: this.attributeHistoryTableHeight || (this.$store.state.windowHeight - 120),
} }
}, },
computed: { computed: {
@@ -179,6 +225,7 @@ export default {
} }
this.ciId = ciId this.ciId = ciId
await this.getCI() await this.getCI()
await this.judgeItsmInstalled()
if (this.hasPermission) { if (this.hasPermission) {
this.getAttributes() this.getAttributes()
this.getCIHistory() this.getCIHistory()
@@ -203,7 +250,16 @@ export default {
this.hasPermission = false this.hasPermission = false
} }
}) })
.catch((e) => {}) .catch((e) => {
if (e.response.status === 404) {
this.itsmInstalled = false
}
})
},
async judgeItsmInstalled() {
await judgeItsmInstalled().catch((e) => {
this.itsmInstalled = false
})
}, },
getCIHistory() { getCIHistory() {
@@ -343,6 +399,11 @@ export default {
this.$message.error(this.$t('cmdb.ci.copyFailed')) this.$message.error(this.$t('cmdb.ci.copyFailed'))
}) })
}, },
handleRollbackCI() {
this.$nextTick(() => {
this.$refs.ciRollbackForm.onOpen()
})
},
}, },
} }
</script> </script>

View File

@@ -0,0 +1,165 @@
<template>
<CustomDrawer
:closable="true"
:title="drawerTitle"
:visible="drawerVisible"
@close="onClose"
placement="right"
width="800"
:bodyStyle="{ paddingTop: 0 }"
>
<div class="custom-drawer-bottom-action">
<a-button @click="onClose">{{ $t('cancel') }}</a-button>
<a-button type="primary" @click="handleSubmit" :loading="loading" :disabled="!hasDiff">{{
$t('submit')
}}</a-button>
</div>
<a-form :form="form" :style="{ paddingTop: '20px' }">
<a-form-item :label="$t('cmdb.ci.rollbackTo')" required :help="$t('cmdb.ci.baselineTips')">
<a-date-picker
:style="{ width: '278px' }"
format="YYYY-MM-DD HH:mm:ss"
valueFormat="YYYY-MM-DD HH:mm:ss"
@ok="getBaselineDiff"
:show-time="{ format: 'HH:mm:ss' }"
:placeholder="$t('cmdb.ci.rollbackToTips')"
v-decorator="['before_date', { rules: [{ required: true, message: $t('cmdb.ci.rollbackToTips') }] }]"
/>
</a-form-item>
<span :style="{ fontWeight: 'bold' }">{{ $t('cmdb.ci.baselineDiff') }}</span>
<vxe-table
ref="xTable"
show-overflow
show-header-overflow
resizable
border
size="small"
:span-method="mergeRowMethod"
:data="tableData"
:scroll-y="{ enabled: false, gt: 20 }"
:scroll-x="{ enabled: false, gt: 0 }"
class="ops-unstripe-table"
>
<template #empty>
<a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }">
<img slot="image" :src="require('@/assets/data_empty.png')" />
<span slot="description"> {{ dataLoad }} </span>
</a-empty>
</template>
<vxe-column field="instance" min-width="80" :title="$t('cmdb.ci.instance')"> </vxe-column>
<vxe-column field="attr_name" min-width="80" :title="$t('cmdb.attribute')"> </vxe-column>
<vxe-column field="cur" min-width="80" :title="$t('cmdb.ci.rollbackBefore')">
<template #default="{ row }">
<span v-if="row.value_type === '6'">{{ JSON.stringify(row.cur) }}</span>
<span v-else>{{ row.cur }}</span>
</template>
</vxe-column>
<vxe-column field="to" min-width="80" :title="$t('cmdb.ci.rollbackAfter')">
<template #default="{ row }">
<span v-if="row.value_type === '6'">{{ JSON.stringify(row.to) }}</span>
<span v-else>{{ row.to }}</span>
</template>
</vxe-column>
</vxe-table>
</a-form>
</CustomDrawer>
</template>
<script>
import { getCIsBaseline, CIBaselineRollback } from '../../../api/history'
export default {
name: 'CiRollbackForm',
props: {
ciIds: {
type: Array,
default: () => [],
},
},
data() {
return {
form: this.$form.createForm(this),
drawerTitle: this.$t('cmdb.ci.rollbackHeader'),
drawerVisible: false,
formLayout: 'horizontal',
tableData: [],
dataLoad: this.$t('noData'),
loading: false,
hasDiff: false,
batched: false,
}
},
methods: {
onClose() {
this.drawerVisible = false
this.form.resetFields()
this.tableData = []
this.dataLoad = this.$t('noData')
},
onOpen(batched = false) {
this.drawerTitle = this.$t('cmdb.ci.rollbackHeader')
this.drawerVisible = true
this.batched = batched
},
handleSubmit() {
this.form.validateFields((err, values) => {
if (!err) {
const that = this
this.$confirm({
title: that.$t('warning'),
content: that.$t('cmdb.ci.rollbackConfirm'),
onOk() {
if (that.batched) {
that.$emit('batchRollbackAsync', values)
} else {
that.rollbackCI(values)
}
},
})
}
})
},
rollbackCI(params) {
CIBaselineRollback(this.ciIds[0], params).then((res) => {
this.$message.success(this.$t('cmdb.ci.rollbackSuccess'))
this.form.resetFields()
this.$emit('getCIHistory')
})
},
getBaselineDiff(value) {
this.dataLoad = 'loading...'
this.loading = true
this.hasDiff = false
getCIsBaseline({ ci_ids: this.ciIds.join(','), before_date: value }).then((res) => {
this.tableData = res
this.loading = false
if (!res.length) {
this.dataLoad = this.$t('cmdb.ci.noDiff', { baseline: value })
} else {
this.hasDiff = true
}
})
},
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
const fields = ['instance']
const cellValue1 = row.instance
if (cellValue1 && fields.includes(column.property)) {
const prevRow = visibleData[_rowIndex - 1]
let nextRow = visibleData[_rowIndex + 1]
if (prevRow && prevRow.instance === cellValue1) {
return { rowspan: 0, colspan: 0 }
} else {
let countRowspan = 1
while (nextRow && nextRow.instance === cellValue1) {
nextRow = visibleData[++countRowspan + _rowIndex]
}
if (countRowspan > 1) {
return { rowspan: countRowspan, colspan: 1 }
}
}
}
},
},
}
</script>

View File

@@ -50,7 +50,6 @@
type="primary" type="primary"
size="small" size="small"
icon="plus" icon="plus"
class="ops-button-primary"
> >
{{ $t('add') }} {{ $t('add') }}
</a-button> </a-button>
@@ -214,7 +213,7 @@ export default {
line-height: 32px; line-height: 32px;
padding-left: 10px; padding-left: 10px;
margin-bottom: 20px; margin-bottom: 20px;
border-left: 4px solid #custom_colors[color_1]; border-left: 4px solid @primary-color;
font-size: 16px; font-size: 16px;
color: rgba(0, 0, 0, 0.75); color: rgba(0, 0, 0, 0.75);
} }

View File

@@ -171,7 +171,7 @@ export default {
margin-right: 60px; margin-right: 60px;
.ant-input-group.ant-input-group-compact > *:first-child, .ant-input-group.ant-input-group-compact > *:first-child,
.ant-input-group.ant-input-group-compact > .ant-select:first-child > .ant-select-selection { .ant-input-group.ant-input-group-compact > .ant-select:first-child > .ant-select-selection {
background-color: #custom_colors[color_1]; background-color: @primary-color;
color: #fff; color: #fff;
border: none; border: none;
} }

View File

@@ -63,7 +63,7 @@
</a-tooltip> </a-tooltip>
<a-tooltip v-if="index !== CITypeGroups.length - 1"> <a-tooltip v-if="index !== CITypeGroups.length - 1">
<template slot="title">{{ $t('cmdb.ciType.up') }}</template> <template slot="title">{{ $t('cmdb.ciType.down') }}</template>
<a <a
><a-icon ><a-icon
type="arrow-down" type="arrow-down"

View File

@@ -223,10 +223,10 @@
<a-form-item :label="$t('cmdb.ciType.isInherit')"> <a-form-item :label="$t('cmdb.ciType.isInherit')">
<a-radio-group v-model="isInherit"> <a-radio-group v-model="isInherit">
<a-radio :value="true"> <a-radio :value="true">
{{ $t('yes') }}
</a-radio> </a-radio>
<a-radio :value="false"> <a-radio :value="false">
{{ $t('no') }}
</a-radio> </a-radio>
</a-radio-group> </a-radio-group>
</a-form-item> </a-form-item>

View File

@@ -6,7 +6,6 @@
@click="handleCreate" @click="handleCreate"
type="primary" type="primary"
size="small" size="small"
class="ops-button-primary"
icon="plus" icon="plus"
>{{ $t('cmdb.ciType.addRelation') }}</a-button >{{ $t('cmdb.ciType.addRelation') }}</a-button
> >
@@ -18,7 +17,6 @@
show-header-overflow show-header-overflow
highlight-hover-row highlight-hover-row
keep-source keep-source
:height="windowHeight - 190"
class="ops-stripe-table" class="ops-stripe-table"
:row-class-name="rowClass" :row-class-name="rowClass"
:edit-config="{ trigger: 'dblclick', mode: 'cell', showIcon: false }" :edit-config="{ trigger: 'dblclick', mode: 'cell', showIcon: false }"
@@ -38,9 +36,9 @@
<template #default="{row}"> <template #default="{row}">
<span v-if="row.isParent && constraintMap[row.constraint]">{{ <span v-if="row.isParent && constraintMap[row.constraint]">{{
constraintMap[row.constraint] constraintMap[row.constraint]
.split('') .split(' ')
.reverse() .reverse()
.join('') .join(' ')
}}</span> }}</span>
<span v-else>{{ constraintMap[row.constraint] }}</span> <span v-else>{{ constraintMap[row.constraint] }}</span>
</template> </template>

View File

@@ -157,7 +157,6 @@
" "
type="primary" type="primary"
size="small" size="small"
class="ops-button-primary"
>{{ `${showCustomEmail ? $t('delete') : $t('add')}` }}{{ $t('cmdb.ciType.customEmail') }}</a-button >{{ `${showCustomEmail ? $t('delete') : $t('add')}` }}{{ $t('cmdb.ciType.customEmail') }}</a-button
> >
</div> </div>

View File

@@ -5,7 +5,6 @@
type="primary" type="primary"
@click="handleAddTrigger" @click="handleAddTrigger"
size="small" size="small"
class="ops-button-primary"
icon="plus" icon="plus"
>{{ $t('cmdb.ciType.newTrigger') }}</a-button >{{ $t('cmdb.ciType.newTrigger') }}</a-button
> >

View File

@@ -309,7 +309,4 @@ export default {
margin-right: 5px; margin-right: 5px;
} }
} }
.ops-button-primary:hover {
background-color: #2f54eb !important;
}
</style> </style>

View File

@@ -153,7 +153,7 @@ export default {
line-height: 32px; line-height: 32px;
padding-left: 10px; padding-left: 10px;
margin-bottom: 20px; margin-bottom: 20px;
border-left: 4px solid #custom_colors[color_1]; border-left: 4px solid @primary-color;
justify-content: space-between; justify-content: space-between;
> div { > div {
font-weight: bold; font-weight: bold;

View File

@@ -5,10 +5,16 @@
show-header-overflow show-header-overflow
stripe stripe
size="small" size="small"
class="ops-stripe-table" class="ops-unstripe-table"
:data="tableData" :data="tableData"
v-bind="ci_id ? { height: 'auto' } : { height: `${windowHeight - 225}px` }" v-bind="ci_id ? { height: 'auto' } : { height: `${windowHeight - 225}px` }"
> >
<template #empty>
<a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }">
<img slot="image" :src="require('@/assets/data_empty.png')" />
<span slot="description"> {{ $t('noData') }} </span>
</a-empty>
</template>
<vxe-column field="trigger_name" :title="$t('cmdb.history.triggerName')"> </vxe-column> <vxe-column field="trigger_name" :title="$t('cmdb.history.triggerName')"> </vxe-column>
<vxe-column field="type" :title="$t('type')"> <vxe-column field="type" :title="$t('type')">
<template #default="{ row }"> <template #default="{ row }">

View File

@@ -494,7 +494,7 @@ export default {
.cmdb-preference-group-content-action { .cmdb-preference-group-content-action {
margin-left: auto; margin-left: auto;
font-size: 12px; font-size: 12px;
color: #custom_colors[color_1]; color: @primary-color;
cursor: pointer; cursor: pointer;
display: none; display: none;
} }
@@ -592,7 +592,7 @@ export default {
.cmdb-preference-footor-unsubscribed { .cmdb-preference-footor-unsubscribed {
text-align: center; text-align: center;
> span { > span {
color: #custom_colors[color_1]; color: @primary-color;
cursor: pointer; cursor: pointer;
font-size: 12px; font-size: 12px;
} }
@@ -605,7 +605,7 @@ export default {
color: rgba(0, 0, 0, 0.45); color: rgba(0, 0, 0, 0.45);
} }
> span:nth-child(2) { > span:nth-child(2) {
color: #custom_colors[color_1]; color: @primary-color;
cursor: pointer; cursor: pointer;
} }
} }

View File

@@ -9,7 +9,7 @@
:triggerLength="18" :triggerLength="18"
> >
<template #one> <template #one>
<div class="relation-views-left" :style="{ height: `${windowHeight - 115}px` }"> <div class="relation-views-left" :style="{ height: `${windowHeight - 64}px` }">
<div class="relation-views-left-header" :title="$route.meta.name">{{ $route.meta.name }}</div> <div class="relation-views-left-header" :title="$route.meta.name">{{ $route.meta.name }}</div>
<a-input <a-input
:placeholder="$t('cmdb.serviceTree.searchTips')" :placeholder="$t('cmdb.serviceTree.searchTips')"
@@ -607,7 +607,7 @@ export default {
this.reload() this.reload()
}, },
pageNo: function(newPage, oldPage) { pageNo: function(newPage, oldPage) {
this.loadData({ params: { pageNo: newPage }, refreshType: undefined, sortByTable: this.sortByTable }) this.loadData({ parameter: { pageNo: newPage }, refreshType: undefined, sortByTable: this.sortByTable })
}, },
}, },
@@ -1193,7 +1193,7 @@ export default {
that.$refs.xTable.clearCheckboxRow() that.$refs.xTable.clearCheckboxRow()
that.$refs.xTable.clearCheckboxReserve() that.$refs.xTable.clearCheckboxReserve()
that.selectedRowKeys = [] that.selectedRowKeys = []
that.loadData({ params: {}, refreshType: 'refreshNumber' }) that.loadData({ parameter: {}, refreshType: 'refreshNumber' })
}) })
}, },
}) })
@@ -1241,7 +1241,7 @@ export default {
this.sortByTable = sortByTable this.sortByTable = sortByTable
this.$nextTick(() => { this.$nextTick(() => {
if (this.pageNo === 1) { if (this.pageNo === 1) {
this.loadData({ params: {}, refreshType: undefined, sortByTable }) this.loadData({ parameter: {}, refreshType: undefined, sortByTable })
} else { } else {
this.pageNo = 1 this.pageNo = 1
} }
@@ -1400,7 +1400,7 @@ export default {
onOk() { onOk() {
deleteCI(record.ci_id || record._id).then((res) => { deleteCI(record.ci_id || record._id).then((res) => {
that.$message.success(that.$t('deleteSuccess')) that.$message.success(that.$t('deleteSuccess'))
that.loadData({ params: {}, refreshType: 'refreshNumber' }) that.loadData({ parameter: {}, refreshType: 'refreshNumber' })
}) })
}, },
}) })
@@ -1420,7 +1420,7 @@ export default {
} }
addCIRelationView(first_ci_id, ci_id, { ancestor_ids }).then((res) => { addCIRelationView(first_ci_id, ci_id, { ancestor_ids }).then((res) => {
setTimeout(() => { setTimeout(() => {
this.loadData({ params: {}, refreshType: 'refreshNumber' }) this.loadData({ parameter: {}, refreshType: 'refreshNumber' })
}, 500) }, 500)
}) })
}, },
@@ -1458,7 +1458,7 @@ export default {
.finally(() => { .finally(() => {
that.loading = false that.loading = false
setTimeout(() => { setTimeout(() => {
that.loadData({ params: {} }) that.loadData({ parameter: {} })
}, 800) }, 800)
}) })
}, },
@@ -1521,7 +1521,7 @@ export default {
that.selectedRowKeys = [] that.selectedRowKeys = []
that.$refs.xTable.clearCheckboxRow() that.$refs.xTable.clearCheckboxRow()
that.$refs.xTable.clearCheckboxReserve() that.$refs.xTable.clearCheckboxReserve()
that.loadData({ params: {}, refreshType: 'refreshNumber' }) that.loadData({ parameter: {}, refreshType: 'refreshNumber' })
}) })
}, },
}) })
@@ -1539,7 +1539,7 @@ export default {
this.$refs.xTable.refreshColumn() this.$refs.xTable.refreshColumn()
}, },
relationViewRefreshNumber() { relationViewRefreshNumber() {
this.loadData({ params: {}, refreshType: 'refreshNumber' }) this.loadData({ parameter: {}, refreshType: 'refreshNumber' })
}, },
onShowSizeChange(current, pageSize) { onShowSizeChange(current, pageSize) {
this.pageSize = pageSize this.pageSize = pageSize
@@ -1762,12 +1762,29 @@ export default {
} }
if (node.children) { if (node.children) {
node.children = node.children.filter((child) => { node.children = node.children.filter((child) => {
if (predicateCiIds.some((id) => child.key.includes(String(id)))) { if (
predicateCiIds.some(
(id) =>
child.key
.split('@^@')
.map((item) => Number(item.split('%')[0]))
.indexOf(id) > -1
)
) {
return true return true
} }
return filterTree(child, predicate) return filterTree(child, predicate)
}) })
if (node.children.length && !predicateCiIds.some((id) => node.key.includes(String(id)))) { if (
node.children.length &&
!predicateCiIds.some(
(id) =>
node.key
.split('@^@')
.map((item) => Number(item.split('%')[0]))
.indexOf(id) > -1
)
) {
_expandedKeys.push(node.key) _expandedKeys.push(node.key)
} }
return node.children.length > 0 return node.children.length > 0

View File

@@ -57,47 +57,37 @@ export const generatorDynamicRouter = async () => {
{ {
path: '/setting/companyinfo', path: '/setting/companyinfo',
name: 'company_info', name: 'company_info',
meta: { title: 'cs.menu.companyInfo', appName: 'backend', icon: 'ops-setting-companyInfo', selectedIcon: 'ops-setting-companyInfo-selected', permission: ['公司信息', 'backend_admin'] }, meta: { title: 'cs.menu.companyInfo', appName: 'backend', icon: 'ops-setting-companyInfo', selectedIcon: 'ops-setting-companyInfo', permission: ['公司信息', 'backend_admin'] },
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/companyInfo/index') component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/companyInfo/index')
}, },
{ {
path: '/setting/companystructure', path: '/setting/companystructure',
name: 'company_structure', name: 'company_structure',
meta: { title: 'cs.menu.companyStructure', appName: 'backend', icon: 'ops-setting-companyStructure', selectedIcon: 'ops-setting-companyStructure-selected', permission: ['公司架构', 'backend_admin'] }, meta: { title: 'cs.menu.companyStructure', appName: 'backend', icon: 'ops-setting-companyStructure', selectedIcon: 'ops-setting-companyStructure', permission: ['公司架构', 'backend_admin'] },
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/companyStructure/index') component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/companyStructure/index')
}, },
{ {
path: '/setting/notice', path: '/setting/notice',
name: 'notice', name: 'notice',
component: RouteView, component: RouteView,
meta: { title: 'cs.menu.notice', appName: 'backend', icon: 'ops-setting-notice', selectedIcon: 'ops-setting-notice-selected', permission: ['通知设置', 'backend_admin'] }, meta: { title: 'cs.menu.notice', appName: 'backend', icon: 'ops-setting-notice', selectedIcon: 'ops-setting-notice', permission: ['通知设置', 'backend_admin'] },
redirect: '/setting/notice/email', redirect: '/setting/notice/email',
children: [{ children: [{
path: '/setting/notice/basic',
name: 'notice_basic',
meta: { title: 'cs.menu.basic', icon: 'ops-setting-basic', selectedIcon: 'ops-setting-basic-selected' },
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/notice/basic')
}, {
path: '/setting/notice/email', path: '/setting/notice/email',
name: 'notice_email', name: 'notice_email',
meta: { title: 'cs.menu.email', icon: 'ops-setting-notice-email', selectedIcon: 'ops-setting-notice-email-selected' }, meta: { title: 'cs.menu.email', icon: 'ops-setting-notice-email', selectedIcon: 'ops-setting-notice-email-selected' },
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/notice/email/index') component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/notice/email/index')
}, {
path: '/setting/notice/wx',
name: 'notice_wx',
meta: { title: 'cs.menu.wx', icon: 'ops-setting-notice-wx', selectedIcon: 'ops-setting-notice-wx-selected' },
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/notice/wx')
}, {
path: '/setting/notice/dingding',
name: 'notice_dingding',
meta: { title: 'cs.menu.dingding', icon: 'ops-setting-notice-dingding', selectedIcon: 'ops-setting-notice-dingding-selected' },
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/notice/dingding')
}, {
path: '/setting/notice/feishu',
name: 'notice_feishu',
meta: { title: 'cs.menu.feishu', icon: 'ops-setting-notice-feishu', selectedIcon: 'ops-setting-notice-feishu-selected' },
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/notice/feishu')
}] }]
}, },
{ {
path: '/setting/auth', path: '/setting/auth',
name: 'company_auth', name: 'company_auth',
meta: { title: 'cs.menu.auth', appName: 'backend', icon: 'ops-setting-auth', selectedIcon: 'ops-setting-auth-selected', permission: ['acl_admin'] }, meta: { title: 'cs.menu.auth', appName: 'backend', icon: 'ops-setting-auth', selectedIcon: 'ops-setting-auth', permission: ['acl_admin'] },
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/auth/index') component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/auth/index')
}, },
] ]

View File

@@ -1152,13 +1152,6 @@ body {
} }
} }
// button
.ops-button-primary {
background-color: @primary-color_4;
border-color: @primary-color_4;
color: @primary-color;
box-shadow: none;
}
// button // button
.ops-button-ghost.ant-btn-background-ghost.ant-btn-primary { .ops-button-ghost.ant-btn-background-ghost.ant-btn-primary {
background-color: @primary-color_5!important; background-color: @primary-color_5!important;

View File

@@ -46,11 +46,6 @@
@layout-sidebar-selected-font-color: @primary-color; @layout-sidebar-selected-font-color: @primary-color;
@layout-sidebar-disabled-font-color: @text-color_4; @layout-sidebar-disabled-font-color: @text-color_4;
#custom_colors() {
color_1: #2f54eb; //primary color
color_2: #f0f5ff; //light background color
color_3: #d2e2ff;
}
.ops_display_wrapper(@backgroundColor:@primary-color_5) { .ops_display_wrapper(@backgroundColor:@primary-color_5) {
cursor: pointer; cursor: pointer;

View File

@@ -309,10 +309,10 @@ export default {
.notice-center-left:hover, .notice-center-left:hover,
.notice-center-left-select { .notice-center-left-select {
background-color: #f0f5ff; background-color: #f0f5ff;
border-color: #custom_colors[color_1]; border-color: @primary-color;
> span:nth-child(2) { > span:nth-child(2) {
background-color: #fff; background-color: #fff;
color: #custom_colors[color_1]; color: @primary-color;
} }
} }
.notice-center-header { .notice-center-header {
@@ -329,7 +329,7 @@ export default {
} }
> .notice-center-header-app:hover, > .notice-center-header-app:hover,
.notice-center-header-app-selected { .notice-center-header-app-selected {
background-color: #custom_colors[color_1]; background-color: @primary-color;
color: #fff; color: #fff;
} }
.notice-center-categories { .notice-center-categories {
@@ -342,7 +342,7 @@ export default {
> span:hover, > span:hover,
.notice-center-categories-selected { .notice-center-categories-selected {
color: #fff; color: #fff;
background-color: #custom_colors[color_1]; background-color: @primary-color;
} }
} }
} }

View File

@@ -1,37 +1,34 @@
<template> <template>
<a-tabs type="card" class="ops-tab" v-model="activeKey" @change="changeActiveKey"> <div class="ops-setting-auth">
<a-tab-pane v-for="item in authList" :key="item.value"> <a-tabs class="ops-tab" v-model="activeKey" @change="changeActiveKey">
<span slot="tab"> <a-tab-pane v-for="item in authList" :key="item.value">
{{ item.label }} <span slot="tab">
<a-icon {{ item.label }}
v-if="enable_list.find((en) => en.auth_type === item.value)" <a-icon v-if="enable_list.find((en) => en.auth_type === item.value)" type="check-circle" theme="filled" />
type="check-circle" </span>
theme="filled" <div class="setting-auth">
style="color:#2f54eb" <components :ref="item.value" :is="item.value === 'OIDC' ? 'OAUTH2' : item.value" :data_type="item.value" />
/> <a-row>
</span> <a-col :offset="item.value === 'AuthCommonConfig' ? 5 : 3">
<div class="setting-auth"> <a-space>
<components :ref="item.value" :is="item.value === 'OIDC' ? 'OAUTH2' : item.value" :data_type="item.value" /> <a-button :loading="loading" type="primary" @click="handleSave">{{ $t('save') }}</a-button>
<a-row> <template v-if="item.value === 'LDAP'">
<a-col :offset="item.value === 'AuthCommonConfig' ? 5 : 3"> <a-button :loading="loading" ghost type="primary" @click="handleTest('connect')">{{
<a-space> $t('cs.auth.testConnect')
<a-button :loading="loading" type="primary" @click="handleSave">{{ $t('save') }}</a-button> }}</a-button>
<template v-if="item.value === 'LDAP'"> <a-button :loading="loading" ghost type="primary" @click="handleTest('login')">{{
<a-button :loading="loading" ghost type="primary" @click="handleTest('connect')">{{ $t('cs.auth.testLogin')
$t('cs.auth.testConnect') }}</a-button>
}}</a-button> </template>
<a-button :loading="loading" ghost type="primary" @click="handleTest('login')">{{ <a-button :loading="loading" @click="handleReset">{{ $t('reset') }}</a-button>
$t('cs.auth.testLogin') </a-space>
}}</a-button> </a-col>
</template> </a-row>
<a-button :loading="loading" @click="handleReset">{{ $t('reset') }}</a-button> </div>
</a-space> <LoginModal v-if="item.value === 'LDAP'" ref="loginModal" @handleOK="(values) => handleTest('login', values)" />
</a-col> </a-tab-pane>
</a-row> </a-tabs>
</div> </div>
<LoginModal v-if="item.value === 'LDAP'" ref="loginModal" @handleOK="(values) => handleTest('login', values)" />
</a-tab-pane>
</a-tabs>
</template> </template>
<script> <script>
@@ -148,12 +145,20 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.ops-setting-auth {
padding: 20px;
padding-top: 0;
background-color: #fff;
border-radius: @border-radius-box;
overflow: auto;
margin-bottom: -24px;
height: calc(100vh - 64px);
}
.setting-auth { .setting-auth {
background-color: #fff; background-color: #fff;
height: calc(100vh - 128px); height: calc(100vh - 150px);
overflow: auto; overflow: auto;
border-radius: 0 5px 5px 5px; border-radius: 0 5px 5px 5px;
padding-top: 24px;
} }
</style> </style>
@@ -161,10 +166,10 @@ export default {
.setting-auth { .setting-auth {
.jsoneditor-outer { .jsoneditor-outer {
height: var(--custom-height) !important; height: var(--custom-height) !important;
border: 1px solid #2f54eb; border: 1px solid @primary-color;
} }
div.jsoneditor-menu { div.jsoneditor-menu {
background-color: #2f54eb; background-color: @primary-color;
} }
.jsoneditor-modes { .jsoneditor-modes {
display: none; display: none;

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="ops-setting-companyinfo" :style="{ height: `${windowHeight - 64}px` }"> <div class="ops-setting-companyinfo">
<a-form-model ref="infoData" :model="infoData" :label-col="labelCol" :wrapper-col="wrapperCol" :rules="rule"> <a-form-model ref="infoData" :model="infoData" :label-col="labelCol" :wrapper-col="wrapperCol" :rules="rule">
<SpanTitle>{{ $t('cs.companyInfo.spanCompany') }}</SpanTitle> <SpanTitle>{{ $t('cs.companyInfo.spanCompany') }}</SpanTitle>
<a-form-model-item :label="$t('cs.companyInfo.name')" prop="name"> <a-form-model-item :label="$t('cs.companyInfo.name')" prop="name">
@@ -35,9 +35,6 @@
<a-input v-model="infoData.email" :disabled="!isEditable" /> <a-input v-model="infoData.email" :disabled="!isEditable" />
</a-form-model-item> </a-form-model-item>
<SpanTitle>{{ $t('cs.companyInfo.spanLogo') }}</SpanTitle> <SpanTitle>{{ $t('cs.companyInfo.spanLogo') }}</SpanTitle>
<a-form-model-item :label="$t('cs.companyInfo.messenger')" prop="messenger">
<a-input v-model="infoData.messenger" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item :label="$t('cs.companyInfo.domainName')" prop="domainName"> <a-form-model-item :label="$t('cs.companyInfo.domainName')" prop="domainName">
<a-input v-model="infoData.domainName" :disabled="!isEditable" /> <a-input v-model="infoData.domainName" :disabled="!isEditable" />
</a-form-model-item> </a-form-model-item>
@@ -51,7 +48,6 @@
<script> <script>
import { getCompanyInfo, postCompanyInfo, putCompanyInfo } from '@/api/company' import { getCompanyInfo, postCompanyInfo, putCompanyInfo } from '@/api/company'
import { mapState } from 'vuex'
import SpanTitle from '../components/spanTitle.vue' import SpanTitle from '../components/spanTitle.vue'
import { mixinPermissions } from '@/utils/mixin' import { mixinPermissions } from '@/utils/mixin'
@@ -74,7 +70,6 @@ export default {
phone: '', phone: '',
faxCode: '', faxCode: '',
email: '', email: '',
messenger: '',
domainName: '', domainName: '',
}, },
getId: -1, getId: -1,
@@ -90,9 +85,6 @@ export default {
} }
}, },
computed: { computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
isEditable() { isEditable() {
return this.hasDetailPermission('backend', '公司信息', ['update']) return this.hasDetailPermission('backend', '公司信息', ['update'])
}, },
@@ -157,7 +149,6 @@ export default {
phone: '', phone: '',
faxCode: '', faxCode: '',
email: '', email: '',
messenger: '',
domainName: '', domainName: '',
} }
}, },
@@ -167,56 +158,15 @@ export default {
<style lang="less"> <style lang="less">
.ops-setting-companyinfo { .ops-setting-companyinfo {
padding-top: 15px; padding: 20px;
background-color: #fff; background-color: #fff;
border-radius: @border-radius-box; border-radius: @border-radius-box;
overflow: auto; overflow: auto;
margin-bottom: -24px; margin-bottom: -24px;
.ops-setting-companyinfo-upload-show { height: calc(100vh - 64px);
position: relative;
width: 290px;
height: 100px;
max-height: 100px;
img {
width: 100%;
height: 100%;
}
.delete-icon {
display: none;
}
}
.ant-upload:hover .delete-icon {
display: block;
position: absolute;
top: 5px;
right: 5px;
color: rgb(247, 85, 85);
}
.ant-form-item { .ant-form-item {
margin-bottom: 10px; margin-bottom: 10px;
} }
.ant-form-item label {
padding-right: 10px;
}
.avatar-uploader > .ant-upload {
// max-width: 100px;
max-height: 100px;
}
// .ant-upload.ant-upload-select-picture-card {
// width: 100%;
// > .ant-upload {
// padding: 0px;
.ant-upload-picture-card-wrapper {
height: 100px;
.ant-upload.ant-upload-select-picture-card {
width: 100%;
height: 100%;
margin: 0;
> .ant-upload {
padding: 0px;
}
}
}
} }
</style> </style>

View File

@@ -173,9 +173,9 @@ li {
color: rgba(0, 0, 0, 0.7); color: rgba(0, 0, 0, 0.7);
font-size: 14px; font-size: 14px;
.ops-setting-companystructure-sidebar-group-tree-info:hover { .ops-setting-companystructure-sidebar-group-tree-info:hover {
color: #custom_colors[color_1]; color: @primary-color;
> .ops-setting-companystructure-sidebar-group-tree-info::before { > .ops-setting-companystructure-sidebar-group-tree-info::before {
background-color: #custom_colors[color_1]; background-color: @primary-color;
} }
} }
// .ops-setting-companystructure-sidebar-group-tree-info:first-child::before { // .ops-setting-companystructure-sidebar-group-tree-info:first-child::before {
@@ -313,7 +313,7 @@ li {
} }
} }
> .ops-setting-companystructure-sidebar-group-tree-info::before { > .ops-setting-companystructure-sidebar-group-tree-info::before {
background-color: #custom_colors[color_1]; background-color: @primary-color;
} }
} }
} }

View File

@@ -6,7 +6,6 @@
:max="500" :max="500"
:paneLengthPixel.sync="paneLengthPixel" :paneLengthPixel.sync="paneLengthPixel"
appName="setting-structure" appName="setting-structure"
triggerColor="#F0F5FF"
:triggerLength="18" :triggerLength="18"
> >
<template #one> <template #one>
@@ -396,11 +395,6 @@ export default {
title: this.$t('cs.companyStructure.employee'), title: this.$t('cs.companyStructure.employee'),
icon: 'user', icon: 'user',
}, },
{
id: 1,
title: this.$t('cs.companyStructure.departmentName'),
icon: 'apartment',
},
] ]
} }
}, },
@@ -884,7 +878,7 @@ export default {
&:hover { &:hover {
background-color: #e1efff; background-color: #e1efff;
.ops-setting-structure-sidebar-group-header-title { .ops-setting-structure-sidebar-group-header-title {
color: #custom_colors[color_1]; color: @primary-color;
} }
} }
.ops-setting-structure-sidebar-group-header-avatar { .ops-setting-structure-sidebar-group-header-avatar {
@@ -925,9 +919,9 @@ export default {
font-size: 14px; font-size: 14px;
width: 100%; width: 100%;
&:hover { &:hover {
color: #custom_colors[color_1]; color: @primary-color;
&::before { &::before {
background-color: #custom_colors[color_1]; background-color: @primary-color;
} }
} }
&::before { &::before {
@@ -1004,19 +998,19 @@ export default {
} }
} }
.item-selected { .item-selected {
color: #custom_colors[color_1]; color: @primary-color;
&::before { &::before {
background-color: #custom_colors[color_1]; background-color: @primary-color;
} }
} }
} }
.group-selected { .group-selected {
background-color: #e1efff; background-color: #e1efff;
.ops-setting-structure-sidebar-group-header-avatar { .ops-setting-structure-sidebar-group-header-avatar {
background-color: #custom_colors[color_1]; background-color: @primary-color;
} }
.ops-setting-structure-sidebar-group-header-title { .ops-setting-structure-sidebar-group-header-title {
color: #custom_colors[color_1]; color: @primary-color;
} }
} }
} }
@@ -1042,7 +1036,7 @@ export default {
display: inline-block; display: inline-block;
.ops_display_wrapper(#fff); .ops_display_wrapper(#fff);
.screening-box-scene-icon { .screening-box-scene-icon {
color: #custom_colors[color_1]; color: @primary-color;
font-size: 12px; font-size: 12px;
} }
.history-scene-item { .history-scene-item {
@@ -1054,7 +1048,7 @@ export default {
.search-form-bar-filter { .search-form-bar-filter {
.ops_display_wrapper(); .ops_display_wrapper();
.search-form-bar-filter-icon { .search-form-bar-filter-icon {
color: #custom_colors[color_1]; color: @primary-color;
font-size: 12px; font-size: 12px;
} }

View File

@@ -68,7 +68,7 @@ name: 'SearchForm',
background-color: rgb(240, 245, 255); background-color: rgb(240, 245, 255);
.ops_display_wrapper(); .ops_display_wrapper();
.search-form-bar-filter-icon { .search-form-bar-filter-icon {
color: #custom_colors[color_1]; color: @primary-color;
font-size: 12px; font-size: 12px;
} }
.search-form-bar-filter-icon_selected{ .search-form-bar-filter-icon_selected{

View File

@@ -23,10 +23,9 @@ export default {
height: 28px; height: 28px;
margin-bottom: 12px; margin-bottom: 12px;
line-height: 28px; line-height: 28px;
padding-left: 24px; padding-left: 12px;
border-radius: 0px 20px 20px 0px;
font-weight: 700; font-weight: 700;
color: #0637bf; color: #000;
background-color: #e0e9ff; border-left: 3px solid @primary-color;
} }
</style> </style>

View File

@@ -1,20 +1,25 @@
const cs_en = { const cs_en = {
menu: { menu: {
person: 'My Profile', person: 'My Profile',
companyInfo: 'Company Info', companyInfo: 'Company Info',
companyStructure: 'Company Structure', companyStructure: 'Company Structure',
notice: 'Notification Settings', notice: 'Notification Settings',
email: 'Email Settings', email: 'Email Settings',
wx: 'WeChat Work', wx: 'WeChat Work',
dingding: 'DingTalk', dingding: 'DingTalk',
feishu: 'Feishu', feishu: 'Feishu',
auth: 'Auth Settings', auth: 'Auth Settings',
role: 'Role Management', authority: 'Authority Management',
sys: 'System Role', sys: 'System Role',
technician: 'Technician Role', technician: 'Technician Role',
user: 'User Role', user: 'User Role',
group: 'User Group', group: 'User Group',
duty: 'Duty Management', duty: 'Duty Management',
role: 'Role Management',
app: 'APP Authority',
basic: 'Basic Settings',
theme: 'Theme Settings',
security: 'Security Settings'
}, },
companyInfo: { companyInfo: {
spanCompany: 'Description', spanCompany: 'Description',
@@ -37,11 +42,6 @@ const cs_en = {
emailValidate: 'Please enter a valid email address', emailValidate: 'Please enter a valid email address',
messenger: 'Messenger Address', messenger: 'Messenger Address',
domainName: 'Deployment Domain', domainName: 'Deployment Domain',
logo: 'Company Logo',
upload: 'Upload',
editCompanyLogo: 'Edit Company Logo',
editCompanyLogoSmall: 'Edit Company Logo Thumbnail',
imageSizeLimit2MB: 'Image size does not exceed 2MB',
checkInputCorrect: 'Please check if the input content is correct', checkInputCorrect: 'Please check if the input content is correct',
}, },
companyStructure: { companyStructure: {

View File

@@ -9,12 +9,17 @@ const cs_zh = {
dingding: '钉钉', dingding: '钉钉',
feishu: '飞书', feishu: '飞书',
auth: '认证设置', auth: '认证设置',
role: '角色管理', authority: '权限管理',
sys: '系统角色', sys: '系统角色',
technician: '技术员角色', technician: '技术员角色',
user: '用户角色', user: '用户角色',
group: '用户分组', group: '用户分组',
duty: '值班管理', duty: '值班管理',
role: '角色管理',
app: '应用权限',
basic: '基础设置',
theme: '主题配置',
security: '安全配置'
}, },
companyInfo: { companyInfo: {
spanCompany: '公司描述', spanCompany: '公司描述',
@@ -37,11 +42,6 @@ const cs_zh = {
emailValidate: '请输入正确的邮箱地址', emailValidate: '请输入正确的邮箱地址',
messenger: 'Messenger地址', messenger: 'Messenger地址',
domainName: '部署域名', domainName: '部署域名',
logo: '公司logo',
upload: '上传',
editCompanyLogo: '编辑公司logo',
editCompanyLogoSmall: '编辑公司logo缩略图',
imageSizeLimit2MB: '图片大小不超过2MB',
checkInputCorrect: '请检查输入内容是否正确', checkInputCorrect: '请检查输入内容是否正确',
}, },
companyStructure: { companyStructure: {

View File

@@ -0,0 +1,80 @@
<template>
<div class="ops-setting-notice-basic">
<a-form-model ref="infoData" :model="infoData" :label-col="labelCol" :wrapper-col="wrapperCol">
<a-form-model-item :label="$t('cs.companyInfo.messenger')" prop="messenger">
<a-input v-model="infoData.messenger" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item :wrapper-col="{ span: 14, offset: 3 }" v-if="isEditable">
<a-button type="primary" @click="onSubmit"> {{ $t('save') }}</a-button>
<a-button ghost type="primary" style="margin-left: 28px" @click="resetForm"> {{ $t('reset') }}</a-button>
</a-form-model-item>
</a-form-model>
</div>
</template>
<script>
import { getCompanyInfo, postCompanyInfo, putCompanyInfo } from '@/api/company'
import { mixinPermissions } from '@/utils/mixin'
export default {
name: 'CompanyInfo',
mixins: [mixinPermissions],
data() {
return {
labelCol: { span: 3 },
wrapperCol: { span: 10 },
infoData: {
messenger: '',
},
getId: -1,
}
},
async mounted() {
const res = await getCompanyInfo()
if (!res.id) {
this.getId = -1
} else {
this.infoData = res.info
this.getId = res.id
}
},
computed: {
isEditable() {
return this.hasDetailPermission('backend', '公司信息', ['update'])
},
},
methods: {
async onSubmit() {
this.$refs.infoData.validate(async (valid) => {
if (valid) {
if (this.getId === -1) {
await postCompanyInfo(this.infoData)
} else {
await putCompanyInfo(this.getId, this.infoData)
}
this.$message.success(this.$t('saveSuccess'))
} else {
this.$message.warning(this.$t('cs.companyInfo.checkInputCorrect'))
return false
}
})
},
resetForm() {
this.infoData = {
messenger: '',
}
},
},
}
</script>
<style lang="less">
.ops-setting-notice-basic {
padding-top: 20px;
background-color: #fff;
border-radius: @border-radius-box;
overflow: auto;
margin-bottom: -24px;
height: calc(100vh - 64px);
}
</style>

View File

@@ -1,106 +0,0 @@
<template>
<div>
<vxe-table
ref="xTable"
:data="tableData"
size="mini"
stripe
class="ops-stripe-table"
show-overflow
:edit-config="{ showIcon: false, trigger: 'manual', mode: 'row' }"
>
<vxe-column v-for="col in columns" :key="col.field" :field="col.field" :title="col.title" :edit-render="{}">
<template #header> <span v-if="col.required" :style="{ color: 'red' }">* </span>{{ col.title }} </template>
<template #edit="{ row }">
<vxe-input v-model="row[col.field]" type="text"></vxe-input>
</template>
</vxe-column>
<vxe-column :title="$t('operation')" width="80" v-if="!disabled">
<template #default="{ row }">
<template v-if="$refs.xTable.isActiveByRow(row)">
<a @click="saveRowEvent(row)"><a-icon type="save"/></a>
</template>
<a-space v-else>
<a @click="editRowEvent(row)"><ops-icon type="icon-xianxing-edit"/></a>
<a style="color:red" @click="deleteRowEvent(row)"><ops-icon type="icon-xianxing-delete"/></a>
</a-space>
</template>
</vxe-column>
</vxe-table>
<div :style="{ color: '#f5222d' }" v-if="errorFlag">{{ $t('cs.notice.robotConfigErrorTips') }}</div>
<a-button v-if="!disabled" icon="plus-circle" class="ops-button-primary" type="primary" @click="insertEvent">{{
$t('add')
}}</a-button>
</div>
</template>
<script>
export default {
name: 'Bot',
props: {
columns: {
type: Array,
default: () => [],
},
disabled: {
type: Boolean,
default: false,
},
},
data() {
return {
tableData: [],
errorFlag: false,
}
},
methods: {
async insertEvent() {
const $table = this.$refs.xTable
const record = {
name: '',
url: '',
}
const { row: newRow } = await $table.insertAt(record, -1)
await $table.setActiveRow(newRow)
},
saveRowEvent(row) {
const $table = this.$refs.xTable
$table.clearActived()
},
editRowEvent(row) {
const $table = this.$refs.xTable
$table.setActiveRow(row)
},
deleteRowEvent(row) {
const $table = this.$refs.xTable
$table.remove(row)
},
getData(callback) {
const $table = this.$refs.xTable
const { fullData: _tableData } = $table.getTableData()
const requiredObj = {}
this.columns.forEach((col) => {
if (col.required) {
requiredObj[col.field] = true
}
})
let flag = true
_tableData.forEach((td) => {
Object.keys(requiredObj).forEach((key) => {
if (requiredObj[key]) {
flag = !!(flag && td[`${key}`])
}
})
})
this.errorFlag = !flag
callback(flag, _tableData)
},
setData(value) {
this.tableData = value
this.errorFlag = false
},
},
}
</script>
<style></style>

View File

@@ -1,151 +0,0 @@
<template>
<div class="notice-dingding-wrapper" :style="{ height: `${windowHeight - 64}px` }">
<a-form-model ref="dingdingForm" :model="dingdingData" :label-col="labelCol" :wrapper-col="wrapperCol">
<SpanTitle>{{ $t('cs.duty.basicSetting') }}</SpanTitle>
<a-form-model-item :label="$t('cs.notice.appKey')">
<a-input v-model="dingdingData.appKey" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item :label="$t('cs.notice.appSecret')">
<a-input v-model="dingdingData.appSecret" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item :label="$t('cs.notice.robotCode')">
<a-input v-model="dingdingData.robotCode" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item :label="$t('cs.notice.robot')">
<Bot
ref="bot"
:disabled="!isEditable"
:columns="[
{
field: 'name',
title: $t('cs.notice.title'),
required: true,
},
{
field: 'url',
title: $t('cs.notice.webhookAddress'),
required: true,
},
{
field: 'token',
title: 'token',
required: false,
},
]"
/>
</a-form-model-item>
<!-- <a-form-model-item :label="测试邮件设置">
<a-button type="primary" ghost>测试回收箱</a-button>
<br />
<span
class="notice-dingding-wrapper-tips"
><ops-icon type="icon-shidi-quxiao" :style="{ color: '#D81E06' }" /> 邮件接收失败</span
>
<br />
<span>邮箱服务器未配置请配置一个邮箱服务器 | <a>故障诊断</a></span>
</a-form-model-item> -->
<a-row v-if="isEditable">
<a-col :span="16" :offset="3">
<a-form-model-item :label-col="labelCol" :wrapper-col="wrapperCol">
<a-button type="primary" @click="onSubmit"> {{ $t('save') }} </a-button>
<a-button ghost type="primary" style="margin-left: 28px;" @click="resetForm"> {{ $t('reset') }} </a-button>
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
</div>
</template>
<script>
import { mapState } from 'vuex'
import SpanTitle from '../components/spanTitle.vue'
import { getNoticeConfigByPlatform, postNoticeConfigByPlatform, putNoticeConfigByPlatform } from '@/api/noticeSetting'
import { mixinPermissions } from '@/utils/mixin'
import Bot from './bot.vue'
export default {
name: 'NoticeDingding',
components: { SpanTitle, Bot },
mixins: [mixinPermissions],
data() {
return {
labelCol: { lg: 3, md: 5, sm: 8 },
wrapperCol: { lg: 15, md: 19, sm: 16 },
id: null,
dingdingData: {
appKey: '',
appSecret: '',
robotCode: '',
},
}
},
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
isEditable() {
return this.hasDetailPermission('backend', '通知设置', ['update'])
},
},
mounted() {
this.getData()
},
methods: {
getData() {
getNoticeConfigByPlatform({ platform: 'dingdingApp' }).then((res) => {
this.id = res?.id ?? null
if (this.id) {
this.dingdingData = res.info
this.$refs.bot.setData(res?.info?.bot)
}
})
},
onSubmit() {
this.$refs.dingdingForm.validate(async (valid) => {
if (valid) {
this.$refs.bot.getData(async (flag, bot) => {
if (flag) {
if (this.id) {
await putNoticeConfigByPlatform(this.id, { info: { ...this.dingdingData, bot, label: this.$t('cs.person.dingdingApp') } })
} else {
await postNoticeConfigByPlatform({
platform: 'dingdingApp',
info: { ...this.dingdingData, bot, label: this.$t('cs.person.dingdingApp') },
})
}
this.$message.success(this.$t('saveSuccess'))
this.getData()
}
})
}
})
},
resetForm() {
this.dingdingData = {
appKey: '',
appSecret: '',
robotCode: '',
}
},
},
}
</script>
<style lang="less" scoped>
.notice-dingding-wrapper {
background-color: #fff;
padding-top: 15px;
overflow: auto;
margin-bottom: -24px;
border-radius: @border-radius-box;
.notice-dingding-wrapper-tips {
display: inline-block;
background-color: #ffdfdf;
border-radius: 4px;
padding: 0 12px;
width: 300px;
color: #000000;
margin-top: 8px;
}
}
</style>

View File

@@ -1,6 +1,5 @@
.notice-email-wrapper { .notice-email-wrapper {
background-color: #fff; background-color: #fff;
padding-top: 24px;
overflow: auto; overflow: auto;
.notice-email-error-tips { .notice-email-error-tips {
display: inline-block; display: inline-block;

View File

@@ -1,6 +1,6 @@
<template> <template>
<div :style="{ marginBottom: '-24px' }"> <div class="ops-setting-notice-mail">
<a-tabs :activeKey="activeKey" @change="changeTab" class="ops-tab" type="card"> <a-tabs :activeKey="activeKey" @change="changeTab" class="ops-tab">
<!-- <a-tab-pane key="1" tab="接收服务器"> <!-- <a-tab-pane key="1" tab="接收服务器">
<Receive /> <Receive />
</a-tab-pane> --> </a-tab-pane> -->
@@ -30,4 +30,14 @@ export default {
} }
</script> </script>
<style></style> <style lang="less" scoped>
.ops-setting-notice-mail {
padding: 20px;
padding-top: 0;
background-color: #fff;
border-radius: @border-radius-box;
overflow: auto;
margin-bottom: -24px;
height: calc(100vh - 64px);
}
</style>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="notice-email-wrapper" :style="{ height: `${windowHeight - 104}px` }"> <div class="notice-email-wrapper">
<a-form-model ref="sendForm" :model="settingData" :label-col="labelCol" :rules="rules" :wrapper-col="wrapperCol"> <a-form-model ref="sendForm" :model="settingData" :label-col="labelCol" :rules="rules" :wrapper-col="wrapperCol">
<SpanTitle>{{ $t('cs.duty.basicSetting') }}</SpanTitle> <SpanTitle>{{ $t('cs.duty.basicSetting') }}</SpanTitle>
<a-form-model-item :label="$t('cs.notice.isEncrypted')"> <a-form-model-item :label="$t('cs.notice.isEncrypted')">

View File

@@ -1,131 +0,0 @@
<template>
<div class="notice-feishu-wrapper" :style="{ height: `${windowHeight - 64}px` }">
<a-form-model ref="feishuForm" :model="feishuData" :label-col="labelCol" :wrapper-col="wrapperCol">
<SpanTitle>{{ $t('cs.duty.basicSetting') }}</SpanTitle>
<a-form-model-item :label="$t('cs.notice.appKey')">
<a-input v-model="feishuData.id" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item :label="$t('cs.notice.appSecret')">
<a-input v-model="feishuData.password" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item :label="$t('cs.notice.robot')">
<Bot
ref="bot"
:disabled="!isEditable"
:columns="[
{
field: 'name',
title: $t('cs.notice.title'),
required: true,
},
{
field: 'url',
title: $t('cs.notice.webhookAddress'),
required: true,
},
]"
/>
</a-form-model-item>
<a-row v-if="isEditable">
<a-col :span="16" :offset="3">
<a-form-model-item :label-col="labelCol" :wrapper-col="wrapperCol">
<a-button type="primary" @click="onSubmit"> {{ $t('save') }} </a-button>
<a-button ghost type="primary" style="margin-left: 28px;" @click="resetForm"> {{ $t('reset') }} </a-button>
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
</div>
</template>
<script>
import { mapState } from 'vuex'
import SpanTitle from '../components/spanTitle.vue'
import { getNoticeConfigByPlatform, postNoticeConfigByPlatform, putNoticeConfigByPlatform } from '@/api/noticeSetting'
import { mixinPermissions } from '@/utils/mixin'
import Bot from './bot.vue'
export default {
name: 'NoticeFeishu',
components: { SpanTitle, Bot },
mixins: [mixinPermissions],
data() {
return {
labelCol: { lg: 3, md: 5, sm: 8 },
wrapperCol: { lg: 15, md: 19, sm: 16 },
id: null,
feishuData: {
id: '',
password: '',
},
}
},
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
isEditable() {
return this.hasDetailPermission('backend', '通知设置', ['update'])
},
},
mounted() {
this.getData()
},
methods: {
getData() {
getNoticeConfigByPlatform({ platform: 'feishuApp' }).then((res) => {
this.id = res?.id ?? null
if (this.id) {
this.feishuData = res.info
this.$refs.bot.setData(res?.info?.bot)
}
})
},
onSubmit() {
this.$refs.feishuForm.validate(async (valid) => {
if (valid) {
this.$refs.bot.getData(async (flag, bot) => {
if (flag) {
if (this.id) {
await putNoticeConfigByPlatform(this.id, { info: { ...this.feishuData, bot, label: this.$t('cs.person.feishuApp') } })
} else {
await postNoticeConfigByPlatform({
platform: 'feishuApp',
info: { ...this.feishuData, bot, label: this.$t('cs.person.feishuApp') },
})
}
this.$message.success(this.$t('saveSuccess'))
this.getData()
}
})
}
})
},
resetForm() {
this.feishuData = {
id: '',
password: '',
}
},
},
}
</script>
<style lang="less" scoped>
.notice-feishu-wrapper {
background-color: #fff;
padding-top: 15px;
overflow: auto;
margin-bottom: -24px;
border-radius: @border-radius-box;
.notice-feishu-wrapper-tips {
display: inline-block;
background-color: #ffdfdf;
border-radius: 4px;
padding: 0 12px;
width: 300px;
color: #000000;
margin-top: 8px;
}
}
</style>

View File

@@ -1,152 +0,0 @@
<template>
<div class="notice-wx-wrapper" :style="{ height: `${windowHeight - 64}px` }">
<a-form-model ref="wxForm" :model="wxData" :label-col="labelCol" :wrapper-col="wrapperCol">
<SpanTitle>{{ $t('cs.duty.basicSetting') }}</SpanTitle>
<a-form-model-item :label="$t('cs.notice.corpid')">
<a-input v-model="wxData.corpid" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item :label="$t('cs.notice.agentid')">
<a-input v-model="wxData.agentid" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item :label="$t('cs.notice.corpsecret')">
<a-input-password v-model="wxData.corpsecret" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="ITSM AppId">
<a-input v-model="wxData.itsm_app_id" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item :label="$t('cs.notice.robot')">
<Bot
ref="bot"
:disabled="!isEditable"
:columns="[
{
field: 'name',
title: $t('cs.notice.title'),
required: true,
},
{
field: 'url',
title: $t('cs.notice.webhookAddress'),
required: true,
},
]"
/>
</a-form-model-item>
<!-- <a-form-model-item :label="测试邮件设置">
<a-button type="primary" ghost>测试回收箱</a-button>
<br />
<span
class="notice-wx-wrapper-tips"
><ops-icon type="icon-shidi-quxiao" :style="{ color: '#D81E06' }" /> 邮件接收失败</span
>
<br />
<span>邮箱服务器未配置请配置一个邮箱服务器 | <a>故障诊断</a></span>
</a-form-model-item> -->
<a-row v-if="isEditable">
<a-col :span="16" :offset="3">
<a-form-model-item :label-col="labelCol" :wrapper-col="wrapperCol">
<a-button type="primary" @click="onSubmit"> {{ $t('save') }} </a-button>
<a-button ghost type="primary" style="margin-left: 28px;" @click="resetForm"> {{ $t('reset') }} </a-button>
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
</div>
</template>
<script>
import { mapState } from 'vuex'
import SpanTitle from '../components/spanTitle.vue'
import { getNoticeConfigByPlatform, postNoticeConfigByPlatform, putNoticeConfigByPlatform } from '@/api/noticeSetting'
import { mixinPermissions } from '@/utils/mixin'
import Bot from './bot.vue'
export default {
name: 'NoticeWx',
mixins: [mixinPermissions],
components: { SpanTitle, Bot },
data() {
return {
labelCol: { lg: 3, md: 5, sm: 8 },
wrapperCol: { lg: 15, md: 19, sm: 16 },
id: null,
wxData: {
corpid: '',
agentid: '',
corpsecret: '',
itsm_app_id: '',
},
}
},
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
isEditable() {
return this.hasDetailPermission('backend', '通知设置', ['update'])
},
},
mounted() {
this.getData()
},
methods: {
getData() {
getNoticeConfigByPlatform({ platform: 'wechatApp' }).then((res) => {
this.id = res?.id ?? null
if (this.id) {
this.wxData = res.info
this.$refs.bot.setData(res?.info?.bot)
}
})
},
onSubmit() {
this.$refs.wxForm.validate(async (valid) => {
if (valid) {
this.$refs.bot.getData(async (flag, bot) => {
if (flag) {
if (this.id) {
await putNoticeConfigByPlatform(this.id, {
info: { ...this.wxData, bot, label: this.$t('cs.person.wechatApp') },
})
} else {
await postNoticeConfigByPlatform({
platform: 'wechatApp',
info: { ...this.wxData, bot, label: this.$t('cs.person.wechatApp') },
})
}
this.$message.success(this.$t('saveSuccess'))
this.getData()
}
})
}
})
},
resetForm() {
this.wxData = {
corpid: '',
agentid: '',
corpsecret: '',
itsm_app_id: '',
}
},
},
}
</script>
<style lang="less" scoped>
.notice-wx-wrapper {
background-color: #fff;
padding-top: 15px;
overflow: auto;
margin-bottom: -24px;
border-radius: @border-radius-box;
.notice-wx-wrapper-tips {
display: inline-block;
background-color: #ffdfdf;
border-radius: 4px;
padding: 0 12px;
width: 300px;
color: #000000;
margin-top: 8px;
}
}
</style>

View File

@@ -372,7 +372,7 @@ export default {
margin-bottom: 5px; margin-bottom: 5px;
&:hover { &:hover {
.ops_popover_item_selected(); .ops_popover_item_selected();
border-color: #custom_colors[color_1]; border-color: @primary-color;
} }
> i { > i {
margin-right: 10px; margin-right: 10px;
@@ -380,7 +380,7 @@ export default {
} }
.setting-person-left-item-selected { .setting-person-left-item-selected {
.ops_popover_item_selected(); .ops_popover_item_selected();
border-color: #custom_colors[color_1]; border-color: @primary-color;
} }
} }
.setting-person-right { .setting-person-right {
@@ -390,7 +390,7 @@ export default {
border-radius: @border-radius-box; border-radius: @border-radius-box;
padding: 24px 48px; padding: 24px 48px;
.setting-person-right-disabled { .setting-person-right-disabled {
background-color: #custom_colors[color_2]; background-color: @primary-color_5;
border-radius: 4px; border-radius: 4px;
height: 30px; height: 30px;
line-height: 30px; line-height: 30px;

View File

@@ -45,7 +45,7 @@ services:
- redis - redis
cmdb-api: cmdb-api:
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.4.3 image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.4.4
# build: # build:
# context: . # context: .
# target: cmdb-api # target: cmdb-api
@@ -84,7 +84,7 @@ services:
- cmdb-api - cmdb-api
cmdb-ui: cmdb-ui:
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.4.3 image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.4.4
# build: # build:
# context: . # context: .
# target: cmdb-ui # target: cmdb-ui

View File

@@ -492,7 +492,7 @@ CREATE TABLE `acl_role_relations` (
LOCK TABLES `acl_role_relations` WRITE; LOCK TABLES `acl_role_relations` WRITE;
/*!40000 ALTER TABLE `acl_role_relations` DISABLE KEYS */; /*!40000 ALTER TABLE `acl_role_relations` DISABLE KEYS */;
INSERT INTO `acl_role_relations` VALUES (NULL,0,NULL,NULL,1,1,2,NULL); INSERT INTO `acl_role_relations` VALUES (NULL,0,NULL,NULL,1,1,2,NULL),(NULL,0,NULL,NULL,2,3,2,2);
/*!40000 ALTER TABLE `acl_role_relations` ENABLE KEYS */; /*!40000 ALTER TABLE `acl_role_relations` ENABLE KEYS */;
UNLOCK TABLES; UNLOCK TABLES;

View File

@@ -342,8 +342,8 @@ key = "Your API key"
secret = "Your API secret" secret = "Your API secret"
def build_api_key(path, params): def build_api_key(path, params):
values = "".join([str(params[k]) for k in sorted(params.keys()) values = "".join([str(params[k]) for k in sorted((params or {}).keys())
if params[k] is not None and not k.startswith('_')]) if params.keys() else "" if k not in ("_key", "_secret") and not isinstance(params[k], (dict, list))])
_secret = "".join([path, secret, values]).encode("utf-8") _secret = "".join([path, secret, values]).encode("utf-8")
params["_secret"] = hashlib.sha1(_secret).hexdigest() params["_secret"] = hashlib.sha1(_secret).hexdigest()
params["_key"] = key params["_key"] = key
@@ -365,8 +365,8 @@ SECRET = "Your API secret"
def build_api_key(path, params): def build_api_key(path, params):
values = "".join([str(params[k]) for k in sorted(params.keys()) values = "".join([str(params[k]) for k in sorted((params or {}).keys())
if params[k] is not None and not k.startswith('_')]) if params.keys() else "" if k not in ("_key", "_secret") and not isinstance(params[k], (dict, list))])
_secret = "".join([path, SECRET, values]).encode("utf-8") _secret = "".join([path, SECRET, values]).encode("utf-8")
params["_secret"] = hashlib.sha1(_secret).hexdigest() params["_secret"] = hashlib.sha1(_secret).hexdigest()
params["_key"] = KEY params["_key"] = KEY
@@ -395,8 +395,8 @@ SECRET = "Your API secret"
def build_api_key(path, params): def build_api_key(path, params):
values = "".join([str(params[k]) for k in sorted(params.keys()) values = "".join([str(params[k]) for k in sorted((params or {}).keys())
if params[k] is not None and not k.startswith('_')]) if params.keys() else "" if k not in ("_key", "_secret") and not isinstance(params[k], (dict, list))])
_secret = "".join([path, SECRET, values]).encode("utf-8") _secret = "".join([path, SECRET, values]).encode("utf-8")
params["_secret"] = hashlib.sha1(_secret).hexdigest() params["_secret"] = hashlib.sha1(_secret).hexdigest()
params["_key"] = KEY params["_key"] = KEY