mirror of
https://github.com/veops/cmdb.git
synced 2025-09-14 07:26:54 +08:00
Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
5266cb5b88 | ||
|
c7d4bec988 | ||
|
099ddd6ca9 | ||
|
bd813174b1 | ||
|
0a43680d6e | ||
|
976c6cfe91 | ||
|
cf594f04ba | ||
|
4232094aed | ||
|
d08827d086 | ||
|
d25ae532cd | ||
|
9fbb6ee64d | ||
|
b62f0e96fd | ||
|
c1bcd0ce45 | ||
|
c8b55c34eb | ||
|
4b5906770f | ||
|
4188ac7252 | ||
|
2efbc6474a | ||
|
03eac0c4d2 | ||
|
2a861250eb | ||
|
8fc19d8b7c | ||
|
430d2ff6d0 | ||
|
2517009d70 | ||
|
67da360d80 | ||
|
24c56fb259 | ||
|
37d5da65de | ||
|
2224ebd533 | ||
|
bf6331d215 | ||
|
b18b90ab4e | ||
|
702e17a7a4 | ||
|
a7586aa140 | ||
|
ad3f96431c | ||
|
1515820713 | ||
|
7728b57878 | ||
|
a419eefd72 | ||
|
a44e5f6cf1 | ||
|
7d46e92c2d |
@@ -74,17 +74,17 @@
|
||||
|
||||
### Docker 一键快速构建
|
||||
> 方法一
|
||||
- 第一步: 先安装 docker 环境, 以及docker-compose
|
||||
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
|
||||
- 第二步: 拷贝项目
|
||||
```shell
|
||||
git clone https://github.com/veops/cmdb.git
|
||||
```
|
||||
- 第三步:进入主目录,执行:
|
||||
```
|
||||
docker-compose up -d
|
||||
docker compose up -d
|
||||
```
|
||||
> 方法二, 该方法适用于linux系统
|
||||
- 第一步: 先安装 docker 环境, 以及docker-compose
|
||||
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
|
||||
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
|
||||
```shell
|
||||
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/master/install.sh
|
||||
|
@@ -66,6 +66,7 @@ hvac = "==2.0.0"
|
||||
colorama = ">=0.4.6"
|
||||
pycryptodomex = ">=3.19.0"
|
||||
lz4 = ">=4.3.2"
|
||||
python-magic = "==0.4.27"
|
||||
|
||||
[dev-packages]
|
||||
# Testing
|
||||
|
@@ -224,7 +224,7 @@ def common_check_new_columns():
|
||||
column_type = new_column.type.compile(engine.dialect)
|
||||
default_value = new_column.default.arg if new_column.default else None
|
||||
|
||||
sql = "ALTER TABLE " + target_table_name + " ADD COLUMN " + new_column.name + " " + column_type
|
||||
sql = "ALTER TABLE " + target_table_name + " ADD COLUMN " + f"`{new_column.name}`" + " " + column_type
|
||||
if new_column.comment:
|
||||
sql += f" comment '{new_column.comment}'"
|
||||
|
||||
|
@@ -331,14 +331,14 @@ class AutoDiscoveryCICRUD(DBMixin):
|
||||
@staticmethod
|
||||
def get_attributes_by_type_id(type_id):
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
attributes = [i[1] for i in CITypeAttributeManager.get_all_attributes(type_id) or []]
|
||||
attributes = [i for i in CITypeAttributeManager.get_attributes_by_type_id(type_id) or []]
|
||||
|
||||
attr_names = set()
|
||||
adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id)
|
||||
for adt in adts:
|
||||
attr_names |= set((adt.attributes or {}).values())
|
||||
|
||||
return [attr.to_dict() for attr in attributes if attr.name in attr_names]
|
||||
return [attr for attr in attributes if attr['name'] in attr_names]
|
||||
|
||||
@classmethod
|
||||
def search(cls, page, page_size, fl=None, **kwargs):
|
||||
|
@@ -295,6 +295,7 @@ class CIManager(object):
|
||||
_no_attribute_policy=ExistPolicy.IGNORE,
|
||||
is_auto_discovery=False,
|
||||
_is_admin=False,
|
||||
ticket_id=None,
|
||||
**ci_dict):
|
||||
"""
|
||||
add ci
|
||||
@@ -303,6 +304,7 @@ class CIManager(object):
|
||||
:param _no_attribute_policy: ignore or reject
|
||||
:param is_auto_discovery: default is False
|
||||
:param _is_admin: default is False
|
||||
:param ticket_id:
|
||||
:param ci_dict:
|
||||
:return:
|
||||
"""
|
||||
@@ -417,7 +419,7 @@ class CIManager(object):
|
||||
operate_type = OperateType.UPDATE if ci is not None else OperateType.ADD
|
||||
try:
|
||||
ci = ci or CI.create(type_id=ci_type.id, is_auto_discovery=is_auto_discovery)
|
||||
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr)
|
||||
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr, ticket_id=ticket_id)
|
||||
except BadRequest as e:
|
||||
if existed is None:
|
||||
cls.delete(ci.id)
|
||||
@@ -435,7 +437,7 @@ class CIManager(object):
|
||||
|
||||
return ci.id
|
||||
|
||||
def update(self, ci_id, _is_admin=False, **ci_dict):
|
||||
def update(self, ci_id, _is_admin=False, ticket_id=None, **ci_dict):
|
||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
ci = self.confirm_ci_existed(ci_id)
|
||||
|
||||
@@ -487,7 +489,7 @@ class CIManager(object):
|
||||
ci_dict.pop(k)
|
||||
|
||||
try:
|
||||
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr)
|
||||
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr, ticket_id=ticket_id)
|
||||
except BadRequest as e:
|
||||
raise e
|
||||
|
||||
@@ -958,7 +960,7 @@ class CIRelationManager(object):
|
||||
return abort(400, ErrFormat.relation_constraint.format("1-N"))
|
||||
|
||||
@classmethod
|
||||
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, ancestor_ids=None):
|
||||
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, ancestor_ids=None, valid=True):
|
||||
|
||||
first_ci = CIManager.confirm_ci_existed(first_ci_id)
|
||||
second_ci = CIManager.confirm_ci_existed(second_ci_id)
|
||||
@@ -983,7 +985,7 @@ class CIRelationManager(object):
|
||||
relation_type_id or abort(404, ErrFormat.relation_not_found.format("{} -> {}".format(
|
||||
first_ci.ci_type.name, second_ci.ci_type.name)))
|
||||
|
||||
if current_app.config.get('USE_ACL'):
|
||||
if current_app.config.get('USE_ACL') and valid:
|
||||
resource_name = CITypeRelationManager.acl_resource_name(first_ci.ci_type.name,
|
||||
second_ci.ci_type.name)
|
||||
if not ACLManager().has_permission(
|
||||
@@ -1086,6 +1088,57 @@ class CIRelationManager(object):
|
||||
for ci_id in ci_ids:
|
||||
cls.delete_2(parent_id, ci_id, ancestor_ids=ancestor_ids)
|
||||
|
||||
@classmethod
|
||||
def build_by_attribute(cls, ci_dict):
|
||||
type_id = ci_dict['_type']
|
||||
child_items = CITypeRelation.get_by(parent_id=type_id, only_query=True).filter(
|
||||
CITypeRelation.parent_attr_id.isnot(None))
|
||||
for item in child_items:
|
||||
parent_attr = AttributeCache.get(item.parent_attr_id)
|
||||
child_attr = AttributeCache.get(item.child_attr_id)
|
||||
attr_value = ci_dict.get(parent_attr.name)
|
||||
value_table = TableMap(attr=child_attr).table
|
||||
for child in value_table.get_by(attr_id=child_attr.id, value=attr_value, only_query=True).join(
|
||||
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.child_id):
|
||||
CIRelationManager.add(ci_dict['_id'], child.ci_id, valid=False)
|
||||
|
||||
parent_items = CITypeRelation.get_by(child_id=type_id, only_query=True).filter(
|
||||
CITypeRelation.child_attr_id.isnot(None))
|
||||
for item in parent_items:
|
||||
parent_attr = AttributeCache.get(item.parent_attr_id)
|
||||
child_attr = AttributeCache.get(item.child_attr_id)
|
||||
attr_value = ci_dict.get(child_attr.name)
|
||||
value_table = TableMap(attr=parent_attr).table
|
||||
for parent in value_table.get_by(attr_id=parent_attr.id, value=attr_value, only_query=True).join(
|
||||
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.parent_id):
|
||||
CIRelationManager.add(parent.ci_id, ci_dict['_id'], valid=False)
|
||||
|
||||
@classmethod
|
||||
def rebuild_all_by_attribute(cls, ci_type_relation):
|
||||
parent_attr = AttributeCache.get(ci_type_relation['parent_attr_id'])
|
||||
child_attr = AttributeCache.get(ci_type_relation['child_attr_id'])
|
||||
if not parent_attr or not child_attr:
|
||||
return
|
||||
|
||||
parent_value_table = TableMap(attr=parent_attr).table
|
||||
child_value_table = TableMap(attr=child_attr).table
|
||||
|
||||
parent_values = parent_value_table.get_by(attr_id=parent_attr.id, only_query=True).join(
|
||||
CI, CI.id == parent_value_table.ci_id).filter(CI.type_id == ci_type_relation['parent_id'])
|
||||
child_values = child_value_table.get_by(attr_id=child_attr.id, only_query=True).join(
|
||||
CI, CI.id == child_value_table.ci_id).filter(CI.type_id == ci_type_relation['child_id'])
|
||||
|
||||
child_value2ci_ids = {}
|
||||
for child in child_values:
|
||||
child_value2ci_ids.setdefault(child.value, []).append(child.ci_id)
|
||||
|
||||
for parent in parent_values:
|
||||
for child_ci_id in child_value2ci_ids.get(parent.value, []):
|
||||
try:
|
||||
cls.add(parent.ci_id, child_ci_id, valid=False)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class CITriggerManager(object):
|
||||
@staticmethod
|
||||
|
@@ -672,6 +672,10 @@ class CITypeAttributeManager(object):
|
||||
item = CITypeTrigger.get_by(type_id=_type_id, attr_id=attr_id, to_dict=False, first=True)
|
||||
item and item.soft_delete(commit=False)
|
||||
|
||||
for item in (CITypeRelation.get_by(parent_id=type_id, parent_attr_id=attr_id, to_dict=False) +
|
||||
CITypeRelation.get_by(child_id=type_id, child_attr_id=attr_id, to_dict=False)):
|
||||
item.soft_delete(commit=False)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
CITypeAttributeCache.clean(type_id, attr_id)
|
||||
@@ -699,6 +703,10 @@ class CITypeAttributeManager(object):
|
||||
to_group = CITypeAttributeGroup.get_by(type_id=type_id, name=to_group_name, first=True, to_dict=False)
|
||||
to_group_id = to_group and to_group.id
|
||||
|
||||
if not to_group_id and CITypeInheritance.get_by(child_id=type_id, to_dict=False):
|
||||
to_group = CITypeAttributeGroup.create(type_id=type_id, name=to_group_name)
|
||||
to_group_id = to_group and to_group.id
|
||||
|
||||
if from_group_id != to_group_id:
|
||||
if from_group_id is not None:
|
||||
CITypeAttributeGroupManager.delete_item(from_group_id, attr_id)
|
||||
@@ -726,14 +734,19 @@ class CITypeRelationManager(object):
|
||||
@staticmethod
|
||||
def get():
|
||||
res = CITypeRelation.get_by(to_dict=False)
|
||||
type2attributes = dict()
|
||||
for idx, item in enumerate(res):
|
||||
_item = item.to_dict()
|
||||
res[idx] = _item
|
||||
res[idx]['parent'] = item.parent.to_dict()
|
||||
if item.parent_id not in type2attributes:
|
||||
type2attributes[item.parent_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.parent_id)]
|
||||
res[idx]['child'] = item.child.to_dict()
|
||||
if item.child_id not in type2attributes:
|
||||
type2attributes[item.child_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.child_id)]
|
||||
res[idx]['relation_type'] = item.relation_type.to_dict()
|
||||
|
||||
return res
|
||||
return res, type2attributes
|
||||
|
||||
@staticmethod
|
||||
def get_child_type_ids(type_id, level):
|
||||
@@ -765,6 +778,8 @@ class CITypeRelationManager(object):
|
||||
|
||||
ci_type_dict["relation_type"] = relation_inst.relation_type.name
|
||||
ci_type_dict["constraint"] = relation_inst.constraint
|
||||
ci_type_dict["parent_attr_id"] = relation_inst.parent_attr_id
|
||||
ci_type_dict["child_attr_id"] = relation_inst.child_attr_id
|
||||
|
||||
return ci_type_dict
|
||||
|
||||
@@ -809,7 +824,8 @@ class CITypeRelationManager(object):
|
||||
return "{} -> {}".format(first_name, second_name)
|
||||
|
||||
@classmethod
|
||||
def add(cls, parent, child, relation_type_id, constraint=ConstraintEnum.One2Many):
|
||||
def add(cls, parent, child, relation_type_id, constraint=ConstraintEnum.One2Many,
|
||||
parent_attr_id=None, child_attr_id=None):
|
||||
p = CITypeManager.check_is_existed(parent)
|
||||
c = CITypeManager.check_is_existed(child)
|
||||
|
||||
@@ -824,24 +840,21 @@ class CITypeRelationManager(object):
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.circular_dependency_error)
|
||||
|
||||
# if constraint == ConstraintEnum.Many2Many:
|
||||
# other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
|
||||
# to_dict=False, first=True)
|
||||
# other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
|
||||
# to_dict=False, first=True)
|
||||
# if other_c and other_c.child_id != c.id:
|
||||
# return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
|
||||
# if other_p and other_p.parent_id != p.id:
|
||||
# return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
|
||||
|
||||
old_parent_attr_id = None
|
||||
existed = cls._get(p.id, c.id)
|
||||
if existed is not None:
|
||||
existed.update(relation_type_id=relation_type_id,
|
||||
constraint=constraint)
|
||||
old_parent_attr_id = existed.parent_attr_id
|
||||
existed = existed.update(relation_type_id=relation_type_id,
|
||||
constraint=constraint,
|
||||
parent_attr_id=parent_attr_id,
|
||||
child_attr_id=child_attr_id,
|
||||
filter_none=False)
|
||||
else:
|
||||
existed = CITypeRelation.create(parent_id=p.id,
|
||||
child_id=c.id,
|
||||
relation_type_id=relation_type_id,
|
||||
parent_attr_id=parent_attr_id,
|
||||
child_attr_id=child_attr_id,
|
||||
constraint=constraint)
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
@@ -855,6 +868,11 @@ class CITypeRelationManager(object):
|
||||
current_user.username,
|
||||
ResourceTypeEnum.CI_TYPE_RELATION)
|
||||
|
||||
if parent_attr_id and parent_attr_id != old_parent_attr_id:
|
||||
if parent_attr_id and parent_attr_id != existed.parent_attr_id:
|
||||
from api.tasks.cmdb import rebuild_relation_for_attribute_changed
|
||||
rebuild_relation_for_attribute_changed.apply_async(args=(existed.to_dict()))
|
||||
|
||||
CITypeHistoryManager.add(CITypeOperateType.ADD_RELATION, p.id,
|
||||
change=dict(parent=p.to_dict(), child=c.to_dict(), relation_type_id=relation_type_id))
|
||||
|
||||
@@ -1147,6 +1165,8 @@ class CITypeTemplateManager(object):
|
||||
id2obj_dicts[added_id].get('child_id'),
|
||||
id2obj_dicts[added_id].get('relation_type_id'),
|
||||
id2obj_dicts[added_id].get('constraint'),
|
||||
id2obj_dicts[added_id].get('parent_attr_id'),
|
||||
id2obj_dicts[added_id].get('child_attr_id'),
|
||||
)
|
||||
else:
|
||||
obj = cls.create(flush=True, **id2obj_dicts[added_id])
|
||||
@@ -1188,6 +1208,8 @@ class CITypeTemplateManager(object):
|
||||
i.pop('choice_web_hook', None)
|
||||
i.pop('choice_other', None)
|
||||
i.pop('order', None)
|
||||
i.pop('inherited', None)
|
||||
i.pop('inherited_from', None)
|
||||
choice_value = i.pop('choice_value', None)
|
||||
if not choice_value:
|
||||
i['is_choice'] = False
|
||||
@@ -1299,6 +1321,8 @@ class CITypeTemplateManager(object):
|
||||
_group = copy.deepcopy(group)
|
||||
_group.pop('attributes', None)
|
||||
_group.pop('id', None)
|
||||
_group.pop('inherited', None)
|
||||
_group.pop('inherited_from', None)
|
||||
existed = CITypeAttributeGroup.get_by(name=_group['name'],
|
||||
type_id=type_id_map.get(_group['type_id'], _group['type_id']),
|
||||
first=True, to_dict=False)
|
||||
@@ -1354,9 +1378,13 @@ class CITypeTemplateManager(object):
|
||||
rule.pop("id", None)
|
||||
rule.pop("created_at", None)
|
||||
rule.pop("updated_at", None)
|
||||
rule.pop("relation", None)
|
||||
|
||||
rule['uid'] = current_user.uid
|
||||
|
||||
if not rule.get('attributes'):
|
||||
continue
|
||||
|
||||
existed = False
|
||||
for i in AutoDiscoveryCIType.get_by(type_id=ci_type.id, adr_id=rule['adr_id'], to_dict=False):
|
||||
if ((i.extra_option or {}).get('alias') or None) == (
|
||||
@@ -1429,7 +1457,7 @@ class CITypeTemplateManager(object):
|
||||
ci_types=CITypeManager.get_ci_types(),
|
||||
ci_type_groups=CITypeGroupManager.get(),
|
||||
relation_types=[i.to_dict() for i in RelationTypeManager.get_all()],
|
||||
ci_type_relations=CITypeRelationManager.get(),
|
||||
ci_type_relations=CITypeRelationManager.get()[0],
|
||||
ci_type_auto_discovery_rules=list(),
|
||||
type2attributes=dict(),
|
||||
type2attribute_group=dict(),
|
||||
|
@@ -167,6 +167,7 @@ class AttributeHistoryManger(object):
|
||||
new=hist.new,
|
||||
created_at=record.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
record_id=record.id,
|
||||
ticket_id=record.ticket_id,
|
||||
hid=hist.id
|
||||
)
|
||||
result.append(item)
|
||||
@@ -200,9 +201,9 @@ class AttributeHistoryManger(object):
|
||||
return username, timestamp, attr_dict, rel_dict
|
||||
|
||||
@staticmethod
|
||||
def add(record_id, ci_id, history_list, type_id=None, flush=False, commit=True):
|
||||
def add(record_id, ci_id, history_list, type_id=None, ticket_id=None, flush=False, commit=True):
|
||||
if record_id is None:
|
||||
record = OperationRecord.create(uid=current_user.uid, type_id=type_id)
|
||||
record = OperationRecord.create(uid=current_user.uid, type_id=type_id, ticket_id=ticket_id)
|
||||
record_id = record.id
|
||||
|
||||
for attr_id, operate_type, old, new in history_list or []:
|
||||
|
@@ -258,7 +258,7 @@ class CIFilterPermsCRUD(DBMixin):
|
||||
|
||||
request_id_filter = {}
|
||||
for _id, v in (kwargs.get('id_filter') or {}).items():
|
||||
key = ",".join([v['parent_path']] if v.get('parent_path') else [] + [str(_id)])
|
||||
key = ",".join(([v['parent_path']] if v.get('parent_path') else []) + [str(_id)])
|
||||
request_id_filter[key] = v['name']
|
||||
|
||||
resource = None
|
||||
|
@@ -238,11 +238,13 @@ class PreferenceManager(object):
|
||||
views = _views
|
||||
|
||||
view2cr_ids = dict()
|
||||
name2view = dict()
|
||||
result = dict()
|
||||
name2id = list()
|
||||
for view in views:
|
||||
view2cr_ids.setdefault(view['name'], []).extend(view['cr_ids'])
|
||||
name2id.append([view['name'], view['id']])
|
||||
name2view[view['name']] = view
|
||||
|
||||
id2type = dict()
|
||||
for view_name in view2cr_ids:
|
||||
@@ -286,6 +288,8 @@ class PreferenceManager(object):
|
||||
topo_flatten=topo_flatten,
|
||||
level2constraint=level2constraint,
|
||||
leaf=leaf,
|
||||
option=name2view[view_name]['option'],
|
||||
is_public=name2view[view_name]['is_public'],
|
||||
leaf2show_types=leaf2show_types,
|
||||
node2show_types=node2show_types,
|
||||
show_types=[CITypeCache.get(j).to_dict()
|
||||
@@ -297,14 +301,18 @@ class PreferenceManager(object):
|
||||
return result, id2type, sorted(name2id, key=lambda x: x[1])
|
||||
|
||||
@classmethod
|
||||
def create_or_update_relation_view(cls, name, cr_ids, is_public=False):
|
||||
def create_or_update_relation_view(cls, name=None, cr_ids=None, _id=None, is_public=False, option=None):
|
||||
if not cr_ids:
|
||||
return abort(400, ErrFormat.preference_relation_view_node_required)
|
||||
|
||||
existed = PreferenceRelationView.get_by(name=name, to_dict=False, first=True)
|
||||
if _id is None:
|
||||
existed = PreferenceRelationView.get_by(name=name, to_dict=False, first=True)
|
||||
else:
|
||||
existed = PreferenceRelationView.get_by_id(_id)
|
||||
current_app.logger.debug(existed)
|
||||
if existed is None:
|
||||
PreferenceRelationView.create(name=name, cr_ids=cr_ids, uid=current_user.uid, is_public=is_public)
|
||||
PreferenceRelationView.create(name=name, cr_ids=cr_ids, uid=current_user.uid,
|
||||
is_public=is_public, option=option)
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
ACLManager().add_resource(name, ResourceTypeEnum.RELATION_VIEW)
|
||||
@@ -312,6 +320,11 @@ class PreferenceManager(object):
|
||||
RoleEnum.CMDB_READ_ALL,
|
||||
ResourceTypeEnum.RELATION_VIEW,
|
||||
permissions=[PermEnum.READ])
|
||||
else:
|
||||
if existed.name != name and current_app.config.get("USE_ACL"):
|
||||
ACLManager().update_resource(existed.name, name, ResourceTypeEnum.RELATION_VIEW)
|
||||
|
||||
existed.update(name=name, cr_ids=cr_ids, is_public=is_public, option=option)
|
||||
|
||||
return cls.get_relation_view()
|
||||
|
||||
|
@@ -96,7 +96,7 @@ class ErrFormat(CommonErrFormat):
|
||||
# 属性 {} 的值必须是唯一的, 当前值 {} 已存在
|
||||
attribute_value_unique_required = _l("The value of attribute {} must be unique, {} already exists")
|
||||
attribute_value_required = _l("Attribute {} value must exist") # 属性 {} 值必须存在
|
||||
|
||||
attribute_value_out_of_range = _l("Out of range value, the maximum value is 2147483647")
|
||||
# 新增或者修改属性值未知错误: {}
|
||||
attribute_value_unknown_error = _l("Unknown error when adding or modifying attribute value: {}")
|
||||
|
||||
|
@@ -17,7 +17,7 @@ def search(query=None,
|
||||
count=1,
|
||||
sort=None,
|
||||
excludes=None,
|
||||
use_id_filter=True):
|
||||
use_id_filter=False):
|
||||
if current_app.config.get("USE_ES"):
|
||||
s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
|
||||
else:
|
||||
|
@@ -66,7 +66,7 @@ class Search(object):
|
||||
self.has_m2m = True
|
||||
|
||||
self.type2filter_perms = None
|
||||
|
||||
|
||||
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||
|
||||
def _get_ids(self, ids):
|
||||
@@ -320,7 +320,8 @@ class Search(object):
|
||||
else:
|
||||
__tmp = []
|
||||
|
||||
_tmp[idx] = [j for i in __tmp for j in i]
|
||||
if __tmp:
|
||||
_tmp[idx] = [j for i in __tmp for j in i]
|
||||
else:
|
||||
_tmp[idx] = []
|
||||
level2ids[lv].append([])
|
||||
|
@@ -11,12 +11,21 @@ import six
|
||||
import api.models.cmdb as model
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
|
||||
TIME_RE = re.compile(r'(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d')
|
||||
|
||||
|
||||
class ValueDeserializeError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def string2int(x):
|
||||
return int(float(x))
|
||||
v = int(float(x))
|
||||
if v > 2147483647:
|
||||
raise ValueDeserializeError(ErrFormat.attribute_value_out_of_range)
|
||||
|
||||
return v
|
||||
|
||||
|
||||
def str2datetime(x):
|
||||
|
@@ -5,14 +5,16 @@ from __future__ import unicode_literals
|
||||
|
||||
import copy
|
||||
import imp
|
||||
import jinja2
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
|
||||
import jinja2
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from jinja2schema import infer
|
||||
from jinja2schema import to_json_schema
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.attribute import AttributeManager
|
||||
@@ -23,6 +25,7 @@ from api.lib.cmdb.const import ValueTypeEnum
|
||||
from api.lib.cmdb.history import AttributeHistoryManger
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.utils import TableMap
|
||||
from api.lib.cmdb.utils import ValueDeserializeError
|
||||
from api.lib.cmdb.utils import ValueTypeMap
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.models.cmdb import CI
|
||||
@@ -80,7 +83,7 @@ class AttributeValueManager(object):
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def _deserialize_value(value_type, value):
|
||||
def _deserialize_value(alias, value_type, value):
|
||||
if not value:
|
||||
return value
|
||||
|
||||
@@ -88,6 +91,8 @@ class AttributeValueManager(object):
|
||||
try:
|
||||
v = deserialize(value)
|
||||
return v
|
||||
except ValueDeserializeError as e:
|
||||
return abort(400, ErrFormat.attribute_value_invalid2.format(alias, e))
|
||||
except ValueError:
|
||||
return abort(400, ErrFormat.attribute_value_invalid.format(value))
|
||||
|
||||
@@ -124,7 +129,7 @@ class AttributeValueManager(object):
|
||||
|
||||
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
|
||||
ci = ci or {}
|
||||
v = self._deserialize_value(attr.value_type, value)
|
||||
v = self._deserialize_value(attr.alias, attr.value_type, value)
|
||||
|
||||
attr.is_choice and value and self._check_is_choice(attr, attr.value_type, v)
|
||||
attr.is_unique and self._check_is_unique(
|
||||
@@ -145,9 +150,10 @@ class AttributeValueManager(object):
|
||||
return AttributeHistoryManger.add(record_id, ci_id, [(attr_id, operate_type, old, new)], type_id)
|
||||
|
||||
@staticmethod
|
||||
def write_change2(changed, record_id=None):
|
||||
def write_change2(changed, record_id=None, ticket_id=None):
|
||||
for ci_id, attr_id, operate_type, old, new, type_id in changed:
|
||||
record_id = AttributeHistoryManger.add(record_id, ci_id, [(attr_id, operate_type, old, new)], type_id,
|
||||
ticket_id=ticket_id,
|
||||
commit=False, flush=False)
|
||||
try:
|
||||
db.session.commit()
|
||||
@@ -240,6 +246,8 @@ class AttributeValueManager(object):
|
||||
value = self._validate(attr, value, value_table, ci=None, type_id=type_id, ci_id=ci_id,
|
||||
type_attr=ci_attr2type_attr.get(attr.id))
|
||||
ci_dict[key] = value
|
||||
except BadRequest as e:
|
||||
raise
|
||||
except Exception as e:
|
||||
current_app.logger.warning(str(e))
|
||||
|
||||
@@ -248,12 +256,13 @@ class AttributeValueManager(object):
|
||||
|
||||
return key2attr
|
||||
|
||||
def create_or_update_attr_value(self, ci, ci_dict, key2attr):
|
||||
def create_or_update_attr_value(self, ci, ci_dict, key2attr, ticket_id=None):
|
||||
"""
|
||||
add or update attribute value, then write history
|
||||
:param ci: instance object
|
||||
:param ci_dict: attribute dict
|
||||
:param key2attr: attr key to attr
|
||||
:param ticket_id:
|
||||
:return:
|
||||
"""
|
||||
changed = []
|
||||
@@ -299,7 +308,7 @@ class AttributeValueManager(object):
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.attribute_value_unknown_error.format(e.args[0]))
|
||||
|
||||
return self.write_change2(changed)
|
||||
return self.write_change2(changed, ticket_id=ticket_id)
|
||||
|
||||
@staticmethod
|
||||
def delete_attr_value(attr_id, ci_id, commit=True):
|
||||
|
@@ -35,3 +35,32 @@ AuthCommonConfigAutoRedirect = 'auto_redirect'
|
||||
class TestType(BaseEnum):
|
||||
Connect = 'connect'
|
||||
Login = 'login'
|
||||
|
||||
|
||||
MIMEExtMap = {
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx',
|
||||
'application/msword': '.doc',
|
||||
'application/vnd.ms-word.document.macroEnabled.12': '.docm',
|
||||
'application/vnd.ms-excel': '.xls',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
|
||||
'application/vnd.ms-excel.sheet.macroEnabled.12': '.xlsm',
|
||||
'application/vnd.ms-powerpoint': '.ppt',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation': '.pptx',
|
||||
'application/vnd.ms-powerpoint.presentation.macroEnabled.12': '.pptm',
|
||||
'application/zip': '.zip',
|
||||
'application/x-7z-compressed': '.7z',
|
||||
'application/json': '.json',
|
||||
'application/pdf': '.pdf',
|
||||
'image/png': '.png',
|
||||
'image/bmp': '.bmp',
|
||||
'image/prs.btif': '.btif',
|
||||
'image/gif': '.gif',
|
||||
'image/jpeg': '.jpg',
|
||||
'image/tiff': '.tif',
|
||||
'image/vnd.microsoft.icon': '.ico',
|
||||
'image/webp': '.webp',
|
||||
'image/svg+xml': '.svg',
|
||||
'image/vnd.adobe.photoshop': '.psd',
|
||||
'text/plain': '.txt',
|
||||
'text/csv': '.csv',
|
||||
}
|
||||
|
@@ -470,8 +470,58 @@ class EditDepartmentInACL(object):
|
||||
|
||||
return f"edit_department_name_in_acl, rid: {d_rid}, success"
|
||||
|
||||
@classmethod
|
||||
def remove_from_old_department_role(cls, e_list, acl):
|
||||
result = []
|
||||
for employee in e_list:
|
||||
employee_acl_rid = employee.get('e_acl_rid')
|
||||
if employee_acl_rid == 0:
|
||||
result.append(f"employee_acl_rid == 0")
|
||||
continue
|
||||
cls.remove_single_employee_from_old_department(acl, employee, result)
|
||||
|
||||
@staticmethod
|
||||
def edit_employee_department_in_acl(e_list: list, new_d_id: int, op_uid: int):
|
||||
def remove_single_employee_from_old_department(acl, employee, result):
|
||||
from api.models.acl import Role
|
||||
old_department = DepartmentCRUD.get_department_by_id(employee.get('department_id'), False)
|
||||
if not old_department:
|
||||
return False
|
||||
|
||||
old_role = Role.get_by(first=True, name=old_department.department_name, app_id=None)
|
||||
old_d_rid_in_acl = old_role.get('id') if old_role else 0
|
||||
if old_d_rid_in_acl == 0:
|
||||
return False
|
||||
|
||||
d_acl_rid = old_department.acl_rid if old_d_rid_in_acl == old_department.acl_rid else old_d_rid_in_acl
|
||||
payload = {
|
||||
'app_id': 'acl',
|
||||
'parent_id': d_acl_rid,
|
||||
}
|
||||
try:
|
||||
acl.remove_user_from_role(employee.get('e_acl_rid'), payload)
|
||||
current_app.logger.info(f"remove {employee.get('e_acl_rid')} from {d_acl_rid}")
|
||||
except Exception as e:
|
||||
result.append(
|
||||
f"remove_user_from_role employee_acl_rid: {employee.get('e_acl_rid')}, parent_id: {d_acl_rid}, err: {e}")
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def add_employee_to_new_department(acl, employee_acl_rid, new_department_acl_rid, result):
|
||||
payload = {
|
||||
'app_id': 'acl',
|
||||
'child_ids': [employee_acl_rid],
|
||||
}
|
||||
try:
|
||||
acl.add_user_to_role(new_department_acl_rid, payload)
|
||||
current_app.logger.info(f"add {employee_acl_rid} to {new_department_acl_rid}")
|
||||
except Exception as e:
|
||||
result.append(
|
||||
f"add_user_to_role employee_acl_rid: {employee_acl_rid}, parent_id: {new_department_acl_rid}, \
|
||||
err: {e}")
|
||||
|
||||
@classmethod
|
||||
def edit_employee_department_in_acl(cls, e_list: list, new_d_id: int, op_uid: int):
|
||||
result = []
|
||||
new_department = DepartmentCRUD.get_department_by_id(new_d_id, False)
|
||||
if not new_department:
|
||||
@@ -481,7 +531,11 @@ class EditDepartmentInACL(object):
|
||||
from api.models.acl import Role
|
||||
new_role = Role.get_by(first=True, name=new_department.department_name, app_id=None)
|
||||
new_d_rid_in_acl = new_role.get('id') if new_role else 0
|
||||
acl = ACLManager('acl', str(op_uid))
|
||||
|
||||
if new_d_rid_in_acl == 0:
|
||||
# only remove from old department role
|
||||
cls.remove_from_old_department_role(e_list, acl)
|
||||
return
|
||||
|
||||
if new_d_rid_in_acl != new_department.acl_rid:
|
||||
@@ -491,43 +545,15 @@ class EditDepartmentInACL(object):
|
||||
new_department_acl_rid = new_department.acl_rid if new_d_rid_in_acl == new_department.acl_rid else \
|
||||
new_d_rid_in_acl
|
||||
|
||||
acl = ACLManager('acl', str(op_uid))
|
||||
for employee in e_list:
|
||||
old_department = DepartmentCRUD.get_department_by_id(employee.get('department_id'), False)
|
||||
if not old_department:
|
||||
continue
|
||||
employee_acl_rid = employee.get('e_acl_rid')
|
||||
if employee_acl_rid == 0:
|
||||
result.append(f"employee_acl_rid == 0")
|
||||
continue
|
||||
|
||||
old_role = Role.get_by(first=True, name=old_department.department_name, app_id=None)
|
||||
old_d_rid_in_acl = old_role.get('id') if old_role else 0
|
||||
if old_d_rid_in_acl == 0:
|
||||
return
|
||||
if old_d_rid_in_acl != old_department.acl_rid:
|
||||
old_department.update(
|
||||
acl_rid=old_d_rid_in_acl
|
||||
)
|
||||
d_acl_rid = old_department.acl_rid if old_d_rid_in_acl == old_department.acl_rid else old_d_rid_in_acl
|
||||
payload = {
|
||||
'app_id': 'acl',
|
||||
'parent_id': d_acl_rid,
|
||||
}
|
||||
try:
|
||||
acl.remove_user_from_role(employee_acl_rid, payload)
|
||||
except Exception as e:
|
||||
result.append(
|
||||
f"remove_user_from_role employee_acl_rid: {employee_acl_rid}, parent_id: {d_acl_rid}, err: {e}")
|
||||
cls.remove_single_employee_from_old_department(acl, employee, result)
|
||||
|
||||
payload = {
|
||||
'app_id': 'acl',
|
||||
'child_ids': [employee_acl_rid],
|
||||
}
|
||||
try:
|
||||
acl.add_user_to_role(new_department_acl_rid, payload)
|
||||
except Exception as e:
|
||||
result.append(
|
||||
f"add_user_to_role employee_acl_rid: {employee_acl_rid}, parent_id: {d_acl_rid}, err: {e}")
|
||||
# 在新部门中添加员工
|
||||
cls.add_employee_to_new_department(acl, employee_acl_rid, new_department_acl_rid, result)
|
||||
|
||||
return result
|
||||
|
@@ -148,10 +148,10 @@ class ACLManager(object):
|
||||
if group:
|
||||
PermissionCRUD.revoke(rid, permissions, group_id=group.id, rebuild=rebuild)
|
||||
|
||||
def del_resource(self, name, resource_type_name=None):
|
||||
def del_resource(self, name, resource_type_name=None, rebuild=True):
|
||||
resource = self._get_resource(name, resource_type_name)
|
||||
if resource:
|
||||
return ResourceCRUD.delete(resource.id)
|
||||
return ResourceCRUD.delete(resource.id, rebuild=rebuild)
|
||||
|
||||
def has_permission(self, resource_name, resource_type, perm, resource_id=None):
|
||||
if is_app_admin(self.app_id):
|
||||
|
@@ -309,7 +309,7 @@ class ResourceCRUD(object):
|
||||
return resource
|
||||
|
||||
@staticmethod
|
||||
def delete(_id):
|
||||
def delete(_id, rebuild=True):
|
||||
resource = Resource.get_by_id(_id) or abort(404, ErrFormat.resource_not_found.format("id={}".format(_id)))
|
||||
|
||||
origin = resource.to_dict()
|
||||
@@ -322,8 +322,9 @@ class ResourceCRUD(object):
|
||||
i.soft_delete()
|
||||
rebuilds.append((i.rid, i.app_id))
|
||||
|
||||
for rid, app_id in set(rebuilds):
|
||||
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE)
|
||||
if rebuild:
|
||||
for rid, app_id in set(rebuilds):
|
||||
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE)
|
||||
|
||||
AuditCRUD.add_resource_log(resource.app_id, AuditOperateType.delete,
|
||||
AuditScope.resource, resource.id, origin, {}, {})
|
||||
|
@@ -75,6 +75,9 @@ class CITypeRelation(Model):
|
||||
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
|
||||
constraint = db.Column(db.Enum(*ConstraintEnum.all()), default=ConstraintEnum.One2Many)
|
||||
|
||||
parent_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
|
||||
child_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
|
||||
|
||||
parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.parent_id")
|
||||
child = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.child_id")
|
||||
relation_type = db.relationship("RelationType", backref="c_ci_type_relations.relation_type_id")
|
||||
@@ -460,6 +463,7 @@ class PreferenceRelationView(Model):
|
||||
name = db.Column(db.String(64), index=True, nullable=False)
|
||||
cr_ids = db.Column(db.JSON) # [{parent_id: x, child_id: y}]
|
||||
is_public = db.Column(db.Boolean, default=False)
|
||||
option = db.Column(db.JSON)
|
||||
|
||||
|
||||
class PreferenceSearchOption(Model):
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
|
||||
import json
|
||||
import time
|
||||
|
||||
import redis_lock
|
||||
from flask import current_app
|
||||
@@ -33,8 +32,7 @@ from api.models.cmdb import CITypeAttribute
|
||||
@reconnect_db
|
||||
def ci_cache(ci_id, operate_type, record_id):
|
||||
from api.lib.cmdb.ci import CITriggerManager
|
||||
|
||||
time.sleep(0.01)
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
|
||||
m = api.lib.cmdb.ci.CIManager()
|
||||
ci_dict = m.get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
|
||||
@@ -52,13 +50,21 @@ def ci_cache(ci_id, operate_type, record_id):
|
||||
|
||||
CITriggerManager.fire(operate_type, ci_dict, record_id)
|
||||
|
||||
ci_dict and CIRelationManager.build_by_attribute(ci_dict)
|
||||
|
||||
|
||||
@celery.task(name="cmdb.rebuild_relation_for_attribute_changed", queue=CMDB_QUEUE)
|
||||
@reconnect_db
|
||||
def rebuild_relation_for_attribute_changed(ci_type_relation):
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
|
||||
CIRelationManager.rebuild_all_by_attribute(ci_type_relation)
|
||||
|
||||
|
||||
@celery.task(name="cmdb.batch_ci_cache", queue=CMDB_QUEUE)
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def batch_ci_cache(ci_ids, ): # only for attribute change index
|
||||
time.sleep(1)
|
||||
|
||||
for ci_id in ci_ids:
|
||||
m = api.lib.cmdb.ci.CIManager()
|
||||
ci_dict = m.get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
|
||||
@@ -87,7 +93,6 @@ def ci_delete(ci_id):
|
||||
@celery.task(name="cmdb.delete_id_filter", queue=CMDB_QUEUE)
|
||||
@reconnect_db
|
||||
def delete_id_filter(ci_id):
|
||||
|
||||
CIFilterPermsCRUD().delete_id_filter_by_ci_id(ci_id)
|
||||
|
||||
|
||||
|
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2024-03-01 13:49+0800\n"
|
||||
"POT-Creation-Date: 2024-03-29 10:42+0800\n"
|
||||
"PO-Revision-Date: 2023-12-25 20:21+0800\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: zh\n"
|
||||
@@ -322,7 +322,7 @@ msgstr "无效的属性值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:94
|
||||
msgid "{} Invalid value: {}"
|
||||
msgstr "无效的值: {}"
|
||||
msgstr "{} 无效的值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:95
|
||||
msgid "{} is not in the predefined values"
|
||||
@@ -336,6 +336,10 @@ msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
|
||||
msgid "Attribute {} value must exist"
|
||||
msgstr "属性 {} 值必须存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:99
|
||||
msgid "Out of range value, the maximum value is 2147483647"
|
||||
msgstr "超过最大值限制, 最大值是2147483647"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:101
|
||||
msgid "Unknown error when adding or modifying attribute value: {}"
|
||||
msgstr "新增或者修改属性值未知错误: {}"
|
||||
|
@@ -38,8 +38,9 @@ class LoginView(APIView):
|
||||
username = request.values.get("username") or request.values.get("email")
|
||||
password = request.values.get("password")
|
||||
_role = None
|
||||
auth_with_ldap = request.values.get('auth_with_ldap', True)
|
||||
config = AuthenticateDataCRUD(AuthenticateType.LDAP).get()
|
||||
if config.get('enabled') or config.get('enable'):
|
||||
if (config.get('enabled') or config.get('enable')) and auth_with_ldap:
|
||||
from api.lib.perm.authentication.ldap import authenticate_with_ldap
|
||||
user, authenticated = authenticate_with_ldap(username, password)
|
||||
else:
|
||||
|
@@ -76,6 +76,7 @@ class CIView(APIView):
|
||||
@has_perm_for_ci("ci_type", ResourceTypeEnum.CI, PermEnum.ADD, lambda x: CITypeCache.get(x))
|
||||
def post(self):
|
||||
ci_type = request.values.get("ci_type")
|
||||
ticket_id = request.values.pop("ticket_id", None)
|
||||
_no_attribute_policy = request.values.get("no_attribute_policy", ExistPolicy.IGNORE)
|
||||
|
||||
exist_policy = request.values.pop('exist_policy', None)
|
||||
@@ -87,6 +88,7 @@ class CIView(APIView):
|
||||
exist_policy=exist_policy or ExistPolicy.REJECT,
|
||||
_no_attribute_policy=_no_attribute_policy,
|
||||
_is_admin=request.values.pop('__is_admin', None) or False,
|
||||
ticket_id=ticket_id,
|
||||
**ci_dict)
|
||||
|
||||
return self.jsonify(ci_id=ci_id)
|
||||
@@ -95,6 +97,7 @@ class CIView(APIView):
|
||||
def put(self, ci_id=None):
|
||||
args = request.values
|
||||
ci_type = args.get("ci_type")
|
||||
ticket_id = request.values.pop("ticket_id", None)
|
||||
_no_attribute_policy = args.get("no_attribute_policy", ExistPolicy.IGNORE)
|
||||
|
||||
ci_dict = self._wrap_ci_dict()
|
||||
@@ -102,6 +105,7 @@ class CIView(APIView):
|
||||
if ci_id is not None:
|
||||
manager.update(ci_id,
|
||||
_is_admin=request.values.pop('__is_admin', None) or False,
|
||||
ticket_id=ticket_id,
|
||||
**ci_dict)
|
||||
else:
|
||||
request.values.pop('exist_policy', None)
|
||||
@@ -109,6 +113,7 @@ class CIView(APIView):
|
||||
exist_policy=ExistPolicy.REPLACE,
|
||||
_no_attribute_policy=_no_attribute_policy,
|
||||
_is_admin=request.values.pop('__is_admin', None) or False,
|
||||
ticket_id=ticket_id,
|
||||
**ci_dict)
|
||||
|
||||
return self.jsonify(ci_id=ci_id)
|
||||
@@ -221,7 +226,6 @@ class CIHeartbeatView(APIView):
|
||||
class CIFlushView(APIView):
|
||||
url_prefix = ("/ci/flush", "/ci/<int:ci_id>/flush")
|
||||
|
||||
# @auth_abandoned
|
||||
def get(self, ci_id=None):
|
||||
from api.tasks.cmdb import ci_cache
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
|
@@ -43,16 +43,19 @@ class CITypeRelationView(APIView):
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def get(self):
|
||||
res = CITypeRelationManager.get()
|
||||
res, type2attributes = CITypeRelationManager.get()
|
||||
|
||||
return self.jsonify(res)
|
||||
return self.jsonify(relations=res, type2attributes=type2attributes)
|
||||
|
||||
@has_perm_from_args("parent_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||
@args_required("relation_type_id")
|
||||
def post(self, parent_id, child_id):
|
||||
relation_type_id = request.values.get("relation_type_id")
|
||||
constraint = request.values.get("constraint")
|
||||
ctr_id = CITypeRelationManager.add(parent_id, child_id, relation_type_id, constraint)
|
||||
parent_attr_id = request.values.get("parent_attr_id")
|
||||
child_attr_id = request.values.get("child_attr_id")
|
||||
ctr_id = CITypeRelationManager.add(parent_id, child_id, relation_type_id, constraint,
|
||||
parent_attr_id, child_attr_id)
|
||||
|
||||
return self.jsonify(ctr_id=ctr_id)
|
||||
|
||||
|
@@ -97,7 +97,7 @@ class PreferenceTreeApiView(APIView):
|
||||
|
||||
|
||||
class PreferenceRelationApiView(APIView):
|
||||
url_prefix = "/preference/relation/view"
|
||||
url_prefix = ("/preference/relation/view", "/preference/relation/view/<int:_id>")
|
||||
|
||||
def get(self):
|
||||
views, id2type, name2id = PreferenceManager.get_relation_view()
|
||||
@@ -110,14 +110,20 @@ class PreferenceRelationApiView(APIView):
|
||||
@args_validate(PreferenceManager.pref_rel_cls)
|
||||
def post(self):
|
||||
name = request.values.get("name")
|
||||
is_public = request.values.get("is_public") in current_app.config.get('BOOL_TRUE')
|
||||
cr_ids = request.values.get("cr_ids")
|
||||
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(name, cr_ids)
|
||||
option = request.values.get("option") or None
|
||||
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(name, cr_ids, is_public=is_public,
|
||||
option=option)
|
||||
|
||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def put(self):
|
||||
return self.post()
|
||||
@args_required("name")
|
||||
def put(self, _id):
|
||||
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(_id=_id, **request.values)
|
||||
|
||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@args_required("name")
|
||||
|
@@ -1,10 +1,10 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
import os
|
||||
|
||||
from flask import request, abort, current_app, send_from_directory
|
||||
from flask import request, abort, current_app
|
||||
from werkzeug.utils import secure_filename
|
||||
import lz4.frame
|
||||
import magic
|
||||
|
||||
from api.lib.common_setting.const import MIMEExtMap
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.lib.common_setting.upload_file import allowed_file, generate_new_file_name, CommonFileCRUD
|
||||
from api.resource import APIView
|
||||
@@ -45,32 +45,35 @@ class PostFileView(APIView):
|
||||
|
||||
if not file:
|
||||
abort(400, ErrFormat.file_is_required)
|
||||
extension = file.mimetype.split('/')[-1]
|
||||
if '+' in extension:
|
||||
extension = file.filename.split('.')[-1]
|
||||
if file.filename == '':
|
||||
filename = f'.{extension}'
|
||||
else:
|
||||
if extension not in file.filename:
|
||||
filename = file.filename + f".{extension}"
|
||||
else:
|
||||
filename = file.filename
|
||||
|
||||
if allowed_file(filename, current_app.config.get('ALLOWED_EXTENSIONS', ALLOWED_EXTENSIONS)):
|
||||
new_filename = generate_new_file_name(filename)
|
||||
new_filename = secure_filename(new_filename)
|
||||
file_content = file.read()
|
||||
compressed_data = lz4.frame.compress(file_content)
|
||||
try:
|
||||
CommonFileCRUD.add_file(
|
||||
origin_name=filename,
|
||||
file_name=new_filename,
|
||||
binary=compressed_data,
|
||||
)
|
||||
m_type = magic.from_buffer(file.read(2048), mime=True)
|
||||
file.seek(0)
|
||||
|
||||
return self.jsonify(file_name=new_filename)
|
||||
except Exception as e:
|
||||
current_app.logger.error(e)
|
||||
abort(400, ErrFormat.upload_failed.format(e))
|
||||
if m_type == 'application/octet-stream':
|
||||
m_type = file.mimetype
|
||||
elif m_type == 'text/plain':
|
||||
# https://github.com/ahupp/python-magic/issues/193
|
||||
m_type = m_type if file.mimetype == m_type else file.mimetype
|
||||
|
||||
abort(400, ErrFormat.file_type_not_allowed.format(filename))
|
||||
extension = MIMEExtMap.get(m_type, None)
|
||||
|
||||
if extension is None:
|
||||
abort(400, f"不支持的文件类型: {m_type}")
|
||||
|
||||
filename = file.filename if file.filename and file.filename.endswith(extension) else file.filename + extension
|
||||
|
||||
new_filename = generate_new_file_name(filename)
|
||||
new_filename = secure_filename(new_filename)
|
||||
file_content = file.read()
|
||||
compressed_data = lz4.frame.compress(file_content)
|
||||
try:
|
||||
CommonFileCRUD.add_file(
|
||||
origin_name=filename,
|
||||
file_name=new_filename,
|
||||
binary=compressed_data,
|
||||
)
|
||||
|
||||
return self.jsonify(file_name=new_filename)
|
||||
except Exception as e:
|
||||
current_app.logger.error(e)
|
||||
abort(400, ErrFormat.upload_failed.format(e))
|
||||
|
@@ -52,4 +52,5 @@ WTForms==3.0.0
|
||||
shamir~=17.12.0
|
||||
pycryptodomex>=3.19.0
|
||||
colorama>=0.4.6
|
||||
lz4>=4.3.2
|
||||
lz4>=4.3.2
|
||||
python-magic==0.4.27
|
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3857903 */
|
||||
src: url('iconfont.woff2?t=1702544951995') format('woff2'),
|
||||
url('iconfont.woff?t=1702544951995') format('woff'),
|
||||
url('iconfont.ttf?t=1702544951995') format('truetype');
|
||||
src: url('iconfont.woff2?t=1711963254221') format('woff2'),
|
||||
url('iconfont.woff?t=1711963254221') format('woff'),
|
||||
url('iconfont.ttf?t=1711963254221') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,234 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.caise-VPC:before {
|
||||
content: "\e910";
|
||||
}
|
||||
|
||||
.caise-CDN:before {
|
||||
content: "\e911";
|
||||
}
|
||||
|
||||
.caise-OOS:before {
|
||||
content: "\e90f";
|
||||
}
|
||||
|
||||
.Google_Cloud_Platform:before {
|
||||
content: "\e90b";
|
||||
}
|
||||
|
||||
.Ctyun:before {
|
||||
content: "\e90c";
|
||||
}
|
||||
|
||||
.Alibaba_Cloud:before {
|
||||
content: "\e90d";
|
||||
}
|
||||
|
||||
.Azure:before {
|
||||
content: "\e90e";
|
||||
}
|
||||
|
||||
.ZStack:before {
|
||||
content: "\e904";
|
||||
}
|
||||
|
||||
.Tencent_Cloud:before {
|
||||
content: "\e905";
|
||||
}
|
||||
|
||||
.Nutanix:before {
|
||||
content: "\e906";
|
||||
}
|
||||
|
||||
.OpenStack:before {
|
||||
content: "\e907";
|
||||
}
|
||||
|
||||
.Huawei_Cloud:before {
|
||||
content: "\e908";
|
||||
}
|
||||
|
||||
.Bytecloud:before {
|
||||
content: "\e909";
|
||||
}
|
||||
|
||||
.UCloud:before {
|
||||
content: "\e90a";
|
||||
}
|
||||
|
||||
.AWS:before {
|
||||
content: "\e901";
|
||||
}
|
||||
|
||||
.ECloud:before {
|
||||
content: "\e902";
|
||||
}
|
||||
|
||||
.JDCloud:before {
|
||||
content: "\e903";
|
||||
}
|
||||
|
||||
.veops-more:before {
|
||||
content: "\e900";
|
||||
}
|
||||
|
||||
.duose-date:before {
|
||||
content: "\e8ff";
|
||||
}
|
||||
|
||||
.duose-shishu:before {
|
||||
content: "\e8fd";
|
||||
}
|
||||
|
||||
.duose-wenben:before {
|
||||
content: "\e8fe";
|
||||
}
|
||||
|
||||
.duose-json:before {
|
||||
content: "\e8f7";
|
||||
}
|
||||
|
||||
.duose-fudianshu:before {
|
||||
content: "\e8f8";
|
||||
}
|
||||
|
||||
.duose-time:before {
|
||||
content: "\e8f9";
|
||||
}
|
||||
|
||||
.duose-password:before {
|
||||
content: "\e8fa";
|
||||
}
|
||||
|
||||
.duose-link:before {
|
||||
content: "\e8fb";
|
||||
}
|
||||
|
||||
.duose-datetime:before {
|
||||
content: "\e8fc";
|
||||
}
|
||||
|
||||
.veops-setting2:before {
|
||||
content: "\e8f6";
|
||||
}
|
||||
|
||||
.veops-search:before {
|
||||
content: "\e8f5";
|
||||
}
|
||||
|
||||
.veops-delete:before {
|
||||
content: "\e8f4";
|
||||
}
|
||||
|
||||
.veops-refresh:before {
|
||||
content: "\e8f3";
|
||||
}
|
||||
|
||||
.veops-filter:before {
|
||||
content: "\e8f2";
|
||||
}
|
||||
|
||||
.veops-reduce:before {
|
||||
content: "\e8ed";
|
||||
}
|
||||
|
||||
.veops-increase:before {
|
||||
content: "\e8ee";
|
||||
}
|
||||
|
||||
.veops-configuration_table:before {
|
||||
content: "\e8ef";
|
||||
}
|
||||
|
||||
.veops-copy:before {
|
||||
content: "\e8f0";
|
||||
}
|
||||
|
||||
.veops-save:before {
|
||||
content: "\e8f1";
|
||||
}
|
||||
|
||||
.veops-setting:before {
|
||||
content: "\e8ec";
|
||||
}
|
||||
|
||||
.veops-default_avatar:before {
|
||||
content: "\e8ea";
|
||||
}
|
||||
|
||||
.veops-notice:before {
|
||||
content: "\e8eb";
|
||||
}
|
||||
|
||||
.itsm-quickStart:before {
|
||||
content: "\e8e9";
|
||||
}
|
||||
|
||||
.itsm-associatedWith:before {
|
||||
content: "\e8e8";
|
||||
}
|
||||
|
||||
.itsm-folder:before {
|
||||
content: "\e8e7";
|
||||
}
|
||||
|
||||
.report:before {
|
||||
content: "\e8e5";
|
||||
}
|
||||
|
||||
.folder:before {
|
||||
content: "\e8e6";
|
||||
}
|
||||
|
||||
.itsm-refresh:before {
|
||||
content: "\e8e4";
|
||||
}
|
||||
|
||||
.itsm-add_table:before {
|
||||
content: "\e8e2";
|
||||
}
|
||||
|
||||
.itsm-delete_page:before {
|
||||
content: "\e8e3";
|
||||
}
|
||||
|
||||
.oneterm-secret_key:before {
|
||||
content: "\e8e0";
|
||||
}
|
||||
|
||||
.oneterm-password:before {
|
||||
content: "\e8e1";
|
||||
}
|
||||
|
||||
.itsm-sla_timeout_not_handled:before {
|
||||
content: "\e8dd";
|
||||
}
|
||||
|
||||
.itsm-sla_not_timeout:before {
|
||||
content: "\e8de";
|
||||
}
|
||||
|
||||
.itsm-SLA:before {
|
||||
content: "\e8df";
|
||||
}
|
||||
|
||||
.itsm-sla_timeout_handled:before {
|
||||
content: "\e8dc";
|
||||
}
|
||||
|
||||
.itsm-sla_all:before {
|
||||
content: "\e8da";
|
||||
}
|
||||
|
||||
.itsm-generate_by_node_id:before {
|
||||
content: "\e8db";
|
||||
}
|
||||
|
||||
.cmdb-MySQL:before {
|
||||
content: "\e8d9";
|
||||
}
|
||||
|
||||
.OAUTH2:before {
|
||||
content: "\e8d8";
|
||||
}
|
||||
@@ -33,7 +261,7 @@
|
||||
content: "\e8d4";
|
||||
}
|
||||
|
||||
.a-itsm-knowledge2:before {
|
||||
.itsm-knowledge2:before {
|
||||
content: "\e8d2";
|
||||
}
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@@ -5,6 +5,405 @@
|
||||
"css_prefix_text": "",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "39782649",
|
||||
"name": "VPC",
|
||||
"font_class": "caise-VPC",
|
||||
"unicode": "e910",
|
||||
"unicode_decimal": 59664
|
||||
},
|
||||
{
|
||||
"icon_id": "39782643",
|
||||
"name": "CDN",
|
||||
"font_class": "caise-CDN",
|
||||
"unicode": "e911",
|
||||
"unicode_decimal": 59665
|
||||
},
|
||||
{
|
||||
"icon_id": "39782632",
|
||||
"name": "OOS",
|
||||
"font_class": "caise-OOS",
|
||||
"unicode": "e90f",
|
||||
"unicode_decimal": 59663
|
||||
},
|
||||
{
|
||||
"icon_id": "39729980",
|
||||
"name": "Google Cloud Platform",
|
||||
"font_class": "Google_Cloud_Platform",
|
||||
"unicode": "e90b",
|
||||
"unicode_decimal": 59659
|
||||
},
|
||||
{
|
||||
"icon_id": "39729978",
|
||||
"name": "Ctyun",
|
||||
"font_class": "Ctyun",
|
||||
"unicode": "e90c",
|
||||
"unicode_decimal": 59660
|
||||
},
|
||||
{
|
||||
"icon_id": "39729977",
|
||||
"name": "Alibaba Cloud",
|
||||
"font_class": "Alibaba_Cloud",
|
||||
"unicode": "e90d",
|
||||
"unicode_decimal": 59661
|
||||
},
|
||||
{
|
||||
"icon_id": "39729976",
|
||||
"name": "Azure",
|
||||
"font_class": "Azure",
|
||||
"unicode": "e90e",
|
||||
"unicode_decimal": 59662
|
||||
},
|
||||
{
|
||||
"icon_id": "39729985",
|
||||
"name": "ZStack",
|
||||
"font_class": "ZStack",
|
||||
"unicode": "e904",
|
||||
"unicode_decimal": 59652
|
||||
},
|
||||
{
|
||||
"icon_id": "39729986",
|
||||
"name": "Tencent Cloud",
|
||||
"font_class": "Tencent_Cloud",
|
||||
"unicode": "e905",
|
||||
"unicode_decimal": 59653
|
||||
},
|
||||
{
|
||||
"icon_id": "39729981",
|
||||
"name": "Nutanix",
|
||||
"font_class": "Nutanix",
|
||||
"unicode": "e906",
|
||||
"unicode_decimal": 59654
|
||||
},
|
||||
{
|
||||
"icon_id": "39729983",
|
||||
"name": "OpenStack",
|
||||
"font_class": "OpenStack",
|
||||
"unicode": "e907",
|
||||
"unicode_decimal": 59655
|
||||
},
|
||||
{
|
||||
"icon_id": "39729982",
|
||||
"name": "Huawei Cloud",
|
||||
"font_class": "Huawei_Cloud",
|
||||
"unicode": "e908",
|
||||
"unicode_decimal": 59656
|
||||
},
|
||||
{
|
||||
"icon_id": "39729979",
|
||||
"name": "Bytecloud",
|
||||
"font_class": "Bytecloud",
|
||||
"unicode": "e909",
|
||||
"unicode_decimal": 59657
|
||||
},
|
||||
{
|
||||
"icon_id": "39729984",
|
||||
"name": "UCloud",
|
||||
"font_class": "UCloud",
|
||||
"unicode": "e90a",
|
||||
"unicode_decimal": 59658
|
||||
},
|
||||
{
|
||||
"icon_id": "39729988",
|
||||
"name": "AWS",
|
||||
"font_class": "AWS",
|
||||
"unicode": "e901",
|
||||
"unicode_decimal": 59649
|
||||
},
|
||||
{
|
||||
"icon_id": "39729989",
|
||||
"name": "ECloud",
|
||||
"font_class": "ECloud",
|
||||
"unicode": "e902",
|
||||
"unicode_decimal": 59650
|
||||
},
|
||||
{
|
||||
"icon_id": "39729987",
|
||||
"name": "JDCloud",
|
||||
"font_class": "JDCloud",
|
||||
"unicode": "e903",
|
||||
"unicode_decimal": 59651
|
||||
},
|
||||
{
|
||||
"icon_id": "39713390",
|
||||
"name": "veops-more",
|
||||
"font_class": "veops-more",
|
||||
"unicode": "e900",
|
||||
"unicode_decimal": 59648
|
||||
},
|
||||
{
|
||||
"icon_id": "39712276",
|
||||
"name": "duose-date",
|
||||
"font_class": "duose-date",
|
||||
"unicode": "e8ff",
|
||||
"unicode_decimal": 59647
|
||||
},
|
||||
{
|
||||
"icon_id": "39712289",
|
||||
"name": "duose-shishu",
|
||||
"font_class": "duose-shishu",
|
||||
"unicode": "e8fd",
|
||||
"unicode_decimal": 59645
|
||||
},
|
||||
{
|
||||
"icon_id": "39712286",
|
||||
"name": "duose-wenben",
|
||||
"font_class": "duose-wenben",
|
||||
"unicode": "e8fe",
|
||||
"unicode_decimal": 59646
|
||||
},
|
||||
{
|
||||
"icon_id": "39712314",
|
||||
"name": "duose-json",
|
||||
"font_class": "duose-json",
|
||||
"unicode": "e8f7",
|
||||
"unicode_decimal": 59639
|
||||
},
|
||||
{
|
||||
"icon_id": "39712315",
|
||||
"name": "duose-fudianshu",
|
||||
"font_class": "duose-fudianshu",
|
||||
"unicode": "e8f8",
|
||||
"unicode_decimal": 59640
|
||||
},
|
||||
{
|
||||
"icon_id": "39712312",
|
||||
"name": "duose-time",
|
||||
"font_class": "duose-time",
|
||||
"unicode": "e8f9",
|
||||
"unicode_decimal": 59641
|
||||
},
|
||||
{
|
||||
"icon_id": "39712313",
|
||||
"name": "duose-password",
|
||||
"font_class": "duose-password",
|
||||
"unicode": "e8fa",
|
||||
"unicode_decimal": 59642
|
||||
},
|
||||
{
|
||||
"icon_id": "39712311",
|
||||
"name": "duose-link",
|
||||
"font_class": "duose-link",
|
||||
"unicode": "e8fb",
|
||||
"unicode_decimal": 59643
|
||||
},
|
||||
{
|
||||
"icon_id": "39712310",
|
||||
"name": "duose-datetime",
|
||||
"font_class": "duose-datetime",
|
||||
"unicode": "e8fc",
|
||||
"unicode_decimal": 59644
|
||||
},
|
||||
{
|
||||
"icon_id": "39705895",
|
||||
"name": "veops-setting2",
|
||||
"font_class": "veops-setting2",
|
||||
"unicode": "e8f6",
|
||||
"unicode_decimal": 59638
|
||||
},
|
||||
{
|
||||
"icon_id": "39692404",
|
||||
"name": "veops-search",
|
||||
"font_class": "veops-search",
|
||||
"unicode": "e8f5",
|
||||
"unicode_decimal": 59637
|
||||
},
|
||||
{
|
||||
"icon_id": "39680289",
|
||||
"name": "veops-delete",
|
||||
"font_class": "veops-delete",
|
||||
"unicode": "e8f4",
|
||||
"unicode_decimal": 59636
|
||||
},
|
||||
{
|
||||
"icon_id": "39677378",
|
||||
"name": "veops-refresh",
|
||||
"font_class": "veops-refresh",
|
||||
"unicode": "e8f3",
|
||||
"unicode_decimal": 59635
|
||||
},
|
||||
{
|
||||
"icon_id": "39677152",
|
||||
"name": "veops-filter",
|
||||
"font_class": "veops-filter",
|
||||
"unicode": "e8f2",
|
||||
"unicode_decimal": 59634
|
||||
},
|
||||
{
|
||||
"icon_id": "39677216",
|
||||
"name": "veops-reduce",
|
||||
"font_class": "veops-reduce",
|
||||
"unicode": "e8ed",
|
||||
"unicode_decimal": 59629
|
||||
},
|
||||
{
|
||||
"icon_id": "39677215",
|
||||
"name": "veops-increase",
|
||||
"font_class": "veops-increase",
|
||||
"unicode": "e8ee",
|
||||
"unicode_decimal": 59630
|
||||
},
|
||||
{
|
||||
"icon_id": "39677211",
|
||||
"name": "veops-configuration_table",
|
||||
"font_class": "veops-configuration_table",
|
||||
"unicode": "e8ef",
|
||||
"unicode_decimal": 59631
|
||||
},
|
||||
{
|
||||
"icon_id": "39677210",
|
||||
"name": "veops-copy",
|
||||
"font_class": "veops-copy",
|
||||
"unicode": "e8f0",
|
||||
"unicode_decimal": 59632
|
||||
},
|
||||
{
|
||||
"icon_id": "39677207",
|
||||
"name": "veops-save",
|
||||
"font_class": "veops-save",
|
||||
"unicode": "e8f1",
|
||||
"unicode_decimal": 59633
|
||||
},
|
||||
{
|
||||
"icon_id": "39664281",
|
||||
"name": "veops-setting",
|
||||
"font_class": "veops-setting",
|
||||
"unicode": "e8ec",
|
||||
"unicode_decimal": 59628
|
||||
},
|
||||
{
|
||||
"icon_id": "39664295",
|
||||
"name": "veops-default_avatar",
|
||||
"font_class": "veops-default_avatar",
|
||||
"unicode": "e8ea",
|
||||
"unicode_decimal": 59626
|
||||
},
|
||||
{
|
||||
"icon_id": "39664288",
|
||||
"name": "veops-notice",
|
||||
"font_class": "veops-notice",
|
||||
"unicode": "e8eb",
|
||||
"unicode_decimal": 59627
|
||||
},
|
||||
{
|
||||
"icon_id": "39603135",
|
||||
"name": "quickly_initiate",
|
||||
"font_class": "itsm-quickStart",
|
||||
"unicode": "e8e9",
|
||||
"unicode_decimal": 59625
|
||||
},
|
||||
{
|
||||
"icon_id": "39588554",
|
||||
"name": "itsm-associated",
|
||||
"font_class": "itsm-associatedWith",
|
||||
"unicode": "e8e8",
|
||||
"unicode_decimal": 59624
|
||||
},
|
||||
{
|
||||
"icon_id": "39517726",
|
||||
"name": "itsm-folder",
|
||||
"font_class": "itsm-folder",
|
||||
"unicode": "e8e7",
|
||||
"unicode_decimal": 59623
|
||||
},
|
||||
{
|
||||
"icon_id": "39517542",
|
||||
"name": "report",
|
||||
"font_class": "report",
|
||||
"unicode": "e8e5",
|
||||
"unicode_decimal": 59621
|
||||
},
|
||||
{
|
||||
"icon_id": "39517539",
|
||||
"name": "folder",
|
||||
"font_class": "folder",
|
||||
"unicode": "e8e6",
|
||||
"unicode_decimal": 59622
|
||||
},
|
||||
{
|
||||
"icon_id": "39123642",
|
||||
"name": "itsm-refresh (1)",
|
||||
"font_class": "itsm-refresh",
|
||||
"unicode": "e8e4",
|
||||
"unicode_decimal": 59620
|
||||
},
|
||||
{
|
||||
"icon_id": "39123405",
|
||||
"name": "itsm-add_table (1)",
|
||||
"font_class": "itsm-add_table",
|
||||
"unicode": "e8e2",
|
||||
"unicode_decimal": 59618
|
||||
},
|
||||
{
|
||||
"icon_id": "39123409",
|
||||
"name": "itsm-delete_page",
|
||||
"font_class": "itsm-delete_page",
|
||||
"unicode": "e8e3",
|
||||
"unicode_decimal": 59619
|
||||
},
|
||||
{
|
||||
"icon_id": "39117681",
|
||||
"name": "oneterm-secret_key",
|
||||
"font_class": "oneterm-secret_key",
|
||||
"unicode": "e8e0",
|
||||
"unicode_decimal": 59616
|
||||
},
|
||||
{
|
||||
"icon_id": "39117679",
|
||||
"name": "oneterm-password",
|
||||
"font_class": "oneterm-password",
|
||||
"unicode": "e8e1",
|
||||
"unicode_decimal": 59617
|
||||
},
|
||||
{
|
||||
"icon_id": "39079529",
|
||||
"name": "itsm-unprocessed",
|
||||
"font_class": "itsm-sla_timeout_not_handled",
|
||||
"unicode": "e8dd",
|
||||
"unicode_decimal": 59613
|
||||
},
|
||||
{
|
||||
"icon_id": "39079522",
|
||||
"name": "itsm-not_timeout",
|
||||
"font_class": "itsm-sla_not_timeout",
|
||||
"unicode": "e8de",
|
||||
"unicode_decimal": 59614
|
||||
},
|
||||
{
|
||||
"icon_id": "39079520",
|
||||
"name": "itsm-SLA",
|
||||
"font_class": "itsm-SLA",
|
||||
"unicode": "e8df",
|
||||
"unicode_decimal": 59615
|
||||
},
|
||||
{
|
||||
"icon_id": "39079538",
|
||||
"name": "itsm-processed",
|
||||
"font_class": "itsm-sla_timeout_handled",
|
||||
"unicode": "e8dc",
|
||||
"unicode_decimal": 59612
|
||||
},
|
||||
{
|
||||
"icon_id": "39079519",
|
||||
"name": "itsm-all_SLA",
|
||||
"font_class": "itsm-sla_all",
|
||||
"unicode": "e8da",
|
||||
"unicode_decimal": 59610
|
||||
},
|
||||
{
|
||||
"icon_id": "38970103",
|
||||
"name": "itsm-generate_by_node_id",
|
||||
"font_class": "itsm-generate_by_node_id",
|
||||
"unicode": "e8db",
|
||||
"unicode_decimal": 59611
|
||||
},
|
||||
{
|
||||
"icon_id": "38806676",
|
||||
"name": "cmdb-MySQL",
|
||||
"font_class": "cmdb-MySQL",
|
||||
"unicode": "e8d9",
|
||||
"unicode_decimal": 59609
|
||||
},
|
||||
{
|
||||
"icon_id": "38566548",
|
||||
"name": "OAuth2.0",
|
||||
@@ -43,7 +442,7 @@
|
||||
{
|
||||
"icon_id": "38533133",
|
||||
"name": "itsm-knowledge (2)",
|
||||
"font_class": "a-itsm-knowledge2",
|
||||
"font_class": "itsm-knowledge2",
|
||||
"unicode": "e8d2",
|
||||
"unicode_decimal": 59602
|
||||
},
|
||||
@@ -1841,7 +2240,7 @@
|
||||
},
|
||||
{
|
||||
"icon_id": "35341667",
|
||||
"name": "caise-chajian ",
|
||||
"name": "caise-chajian",
|
||||
"font_class": "caise-chajian",
|
||||
"unicode": "e7d2",
|
||||
"unicode_decimal": 59346
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -30,9 +30,9 @@ export function getAuthDataEnable() {
|
||||
})
|
||||
}
|
||||
|
||||
export function testLDAP(test_type, data) {
|
||||
export function testLDAP(data) {
|
||||
return axios({
|
||||
url: `/common-setting/v1/auth_config/LDAP/test?test_type=${test_type}`,
|
||||
url: `/common-setting/v1/auth_config/LDAP/test`,
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 6.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 35 KiB |
@@ -178,7 +178,7 @@
|
||||
<div v-else :style="{ width: '175px' }"></div>
|
||||
<template v-if="!disabled">
|
||||
<a-tooltip :title="$t('copy')">
|
||||
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a>
|
||||
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="veops-copy"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="$t('delete')">
|
||||
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a>
|
||||
|
@@ -17,25 +17,29 @@ export default {
|
||||
getPropertyIcon(attr) {
|
||||
switch (attr.value_type) {
|
||||
case '0':
|
||||
return 'icon-xianxing-shishu'
|
||||
return 'duose-shishu'
|
||||
case '1':
|
||||
return 'icon-xianxing-fudianshu'
|
||||
return 'duose-fudianshu'
|
||||
case '2':
|
||||
if (attr.is_password) {
|
||||
return 'icon-xianxing-password'
|
||||
return 'duose-password'
|
||||
}
|
||||
if (attr.is_link) {
|
||||
return 'icon-xianxing-link'
|
||||
return 'duose-link'
|
||||
}
|
||||
return 'icon-xianxing-wenben'
|
||||
return 'duose-wenben'
|
||||
case '3':
|
||||
return 'icon-xianxing-datetime'
|
||||
return 'duose-datetime'
|
||||
case '4':
|
||||
return 'icon-xianxing-date'
|
||||
return 'duose-date'
|
||||
case '5':
|
||||
return 'icon-xianxing-time'
|
||||
return 'duose-time'
|
||||
case '6':
|
||||
return 'icon-xianxing-json'
|
||||
return 'duose-json'
|
||||
case '7':
|
||||
return 'duose-password'
|
||||
case '8':
|
||||
return 'duose-link'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@@ -759,6 +759,52 @@ export const multicolorIconList = [
|
||||
value: 'caise-redis',
|
||||
label: 'redis'
|
||||
}]
|
||||
}, {
|
||||
value: 'cloud',
|
||||
label: '云',
|
||||
list: [{
|
||||
value: 'AWS',
|
||||
label: 'AWS'
|
||||
}, {
|
||||
value: 'Azure',
|
||||
label: 'Azure'
|
||||
}, {
|
||||
value: 'Google_Cloud_Platform',
|
||||
label: 'Google Cloud Platform'
|
||||
}, {
|
||||
value: 'Alibaba_Cloud',
|
||||
label: '阿里云'
|
||||
}, {
|
||||
value: 'Huawei_Cloud',
|
||||
label: '华为云'
|
||||
}, {
|
||||
value: 'Tencent_Cloud',
|
||||
label: '腾讯云'
|
||||
}, {
|
||||
value: 'UCloud',
|
||||
label: 'UCloud'
|
||||
}, {
|
||||
value: 'Ctyun',
|
||||
label: '天翼云'
|
||||
}, {
|
||||
value: 'ECloud',
|
||||
label: '移动云'
|
||||
}, {
|
||||
value: 'JDCloud',
|
||||
label: '京东云'
|
||||
}, {
|
||||
value: 'Bytecloud',
|
||||
label: '字节云'
|
||||
}, {
|
||||
value: 'OpenStack',
|
||||
label: 'OpenStack'
|
||||
}, {
|
||||
value: 'ZStack',
|
||||
label: 'ZStack'
|
||||
}, {
|
||||
value: 'Nutanix',
|
||||
label: 'Nutanix'
|
||||
}]
|
||||
}, {
|
||||
value: 'system',
|
||||
label: '操作系统',
|
||||
@@ -976,17 +1022,14 @@ export const multicolorIconList = [
|
||||
value: 'caise-tomcat',
|
||||
label: 'Tomcat'
|
||||
}, {
|
||||
value: 'caise-aliyun',
|
||||
label: '阿里云'
|
||||
value: 'caise-VPC',
|
||||
label: 'VPC'
|
||||
}, {
|
||||
value: 'caise-tengxunyun',
|
||||
label: '腾讯云'
|
||||
value: 'caise-CDN',
|
||||
label: 'CDN'
|
||||
}, {
|
||||
value: 'caise-huaweiyun',
|
||||
label: '华为云'
|
||||
}, {
|
||||
value: 'caise-aws',
|
||||
label: 'AWS'
|
||||
value: 'caise-OOS',
|
||||
label: '对象存储'
|
||||
}]
|
||||
}, {
|
||||
value: 'data',
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<a-layout-sider
|
||||
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null]"
|
||||
width="200px"
|
||||
width="220px"
|
||||
:collapsible="collapsible"
|
||||
v-model="collapsed"
|
||||
:trigger="null"
|
||||
@@ -15,6 +15,7 @@
|
||||
@select="onSelect"
|
||||
style="padding: 16px 0px;"
|
||||
></s-menu>
|
||||
<!-- <OpsDocs :collapsed="collapsed" /> -->
|
||||
</a-layout-sider>
|
||||
</template>
|
||||
|
||||
@@ -22,10 +23,13 @@
|
||||
import Logo from '@/components/tools/Logo'
|
||||
import SMenu from './index'
|
||||
import { mixin, mixinDevice } from '@/utils/mixin'
|
||||
// import OpsDocs from '@/modules/docs/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'SideMenu',
|
||||
components: { Logo, SMenu },
|
||||
components: { Logo, SMenu,
|
||||
// OpsDocs
|
||||
},
|
||||
mixins: [mixin, mixinDevice],
|
||||
props: {
|
||||
mode: {
|
||||
|
@@ -1,121 +1,121 @@
|
||||
<template>
|
||||
<vxe-table v-bind="$attrs" v-on="new$listeners" ref="xTable">
|
||||
<slot></slot>
|
||||
<template #empty>
|
||||
<slot name="empty">
|
||||
<div :style="{ paddingTop: '10px' }">
|
||||
<img :style="{ width: '100px', height: '90px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<div>{{ $t('noData') }}</div>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
<template #loading>
|
||||
<slot name="loading"></slot>
|
||||
</template>
|
||||
</vxe-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
// 该组件使用方法与vxe-table一致,但调用它的方法时,需先调用getVxetableRef()获取到vxe-table实体
|
||||
export default {
|
||||
name: 'OpsTable',
|
||||
data() {
|
||||
return {
|
||||
// isShifting: false,
|
||||
// lastIndex: -1,
|
||||
lastSelected: [],
|
||||
currentSelected: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
new$listeners() {
|
||||
if (!Object.keys(this.$listeners).length) {
|
||||
return this.$listeners
|
||||
}
|
||||
return Object.assign(this.$listeners, {
|
||||
// 在这里覆盖原有的change事件
|
||||
// 'checkbox-change': this.selectChangeEvent,
|
||||
'checkbox-range-change': this.checkboxRangeChange,
|
||||
'checkbox-range-start': this.checkboxRangeStart,
|
||||
'checkbox-range-end': this.checkboxRangeEnd,
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// window.onkeydown = (e) => {
|
||||
// if (e.key === 'Shift') {
|
||||
// this.isShifting = true
|
||||
// }
|
||||
// }
|
||||
// window.onkeyup = (e) => {
|
||||
// if (e.key === 'Shift') {
|
||||
// this.isShifting = false
|
||||
// this.lastIndex = -1
|
||||
// }
|
||||
// }
|
||||
},
|
||||
beforeDestroy() {
|
||||
// window.onkeydown = ''
|
||||
// window.onkeyup = ''
|
||||
},
|
||||
methods: {
|
||||
getVxetableRef() {
|
||||
return this.$refs.xTable
|
||||
},
|
||||
// selectChangeEvent(e) {
|
||||
// const xTable = this.$refs.xTable
|
||||
// const { lastIndex } = this
|
||||
// const currentIndex = e.rowIndex
|
||||
// const { tableData } = xTable.getTableData()
|
||||
// if (lastIndex > -1 && this.isShifting) {
|
||||
// let start = lastIndex
|
||||
// let end = currentIndex
|
||||
// if (lastIndex > currentIndex) {
|
||||
// start = currentIndex
|
||||
// end = lastIndex
|
||||
// }
|
||||
// const rangeData = tableData.slice(start, end + 1)
|
||||
// xTable.setCheckboxRow(rangeData, true)
|
||||
// }
|
||||
// this.lastIndex = currentIndex
|
||||
// this.$emit('checkbox-change', { ...e, records: xTable.getCheckboxRecords() })
|
||||
// },
|
||||
checkboxRangeStart(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
const lastSelected = xTable.getCheckboxRecords()
|
||||
const selectedReserve = xTable.getCheckboxReserveRecords()
|
||||
this.lastSelected = [...lastSelected, ...selectedReserve]
|
||||
this.$emit('checkbox-range-start', e)
|
||||
},
|
||||
checkboxRangeChange(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
xTable.setCheckboxRow(this.lastSelected, true)
|
||||
this.currentSelected = e.records
|
||||
// this.lastSelected = [...new Set([...this.lastSelected, ...e.records])]
|
||||
this.$emit('checkbox-range-change', {
|
||||
...e,
|
||||
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
|
||||
})
|
||||
},
|
||||
checkboxRangeEnd(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
const isAllSelected = this.currentSelected.every((item) => {
|
||||
const _idx = this.lastSelected.findIndex((ele) => _.isEqual(ele, item))
|
||||
return _idx > -1
|
||||
})
|
||||
if (isAllSelected) {
|
||||
xTable.setCheckboxRow(this.currentSelected, false)
|
||||
}
|
||||
this.currentSelected = []
|
||||
this.lastSelected = []
|
||||
this.$emit('checkbox-range-end', {
|
||||
...e,
|
||||
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
<template>
|
||||
<vxe-table v-bind="$attrs" v-on="new$listeners" ref="xTable">
|
||||
<slot></slot>
|
||||
<template #empty>
|
||||
<slot name="empty">
|
||||
<div :style="{ paddingTop: '10px' }">
|
||||
<img :style="{ width: '140px', height: '90px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<div>{{ $t('noData') }}</div>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
<template #loading>
|
||||
<slot name="loading"></slot>
|
||||
</template>
|
||||
</vxe-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
// 该组件使用方法与vxe-table一致,但调用它的方法时,需先调用getVxetableRef()获取到vxe-table实体
|
||||
export default {
|
||||
name: 'OpsTable',
|
||||
data() {
|
||||
return {
|
||||
// isShifting: false,
|
||||
// lastIndex: -1,
|
||||
lastSelected: [],
|
||||
currentSelected: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
new$listeners() {
|
||||
if (!Object.keys(this.$listeners).length) {
|
||||
return this.$listeners
|
||||
}
|
||||
return Object.assign(this.$listeners, {
|
||||
// 在这里覆盖原有的change事件
|
||||
// 'checkbox-change': this.selectChangeEvent,
|
||||
'checkbox-range-change': this.checkboxRangeChange,
|
||||
'checkbox-range-start': this.checkboxRangeStart,
|
||||
'checkbox-range-end': this.checkboxRangeEnd,
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// window.onkeydown = (e) => {
|
||||
// if (e.key === 'Shift') {
|
||||
// this.isShifting = true
|
||||
// }
|
||||
// }
|
||||
// window.onkeyup = (e) => {
|
||||
// if (e.key === 'Shift') {
|
||||
// this.isShifting = false
|
||||
// this.lastIndex = -1
|
||||
// }
|
||||
// }
|
||||
},
|
||||
beforeDestroy() {
|
||||
// window.onkeydown = ''
|
||||
// window.onkeyup = ''
|
||||
},
|
||||
methods: {
|
||||
getVxetableRef() {
|
||||
return this.$refs.xTable
|
||||
},
|
||||
// selectChangeEvent(e) {
|
||||
// const xTable = this.$refs.xTable
|
||||
// const { lastIndex } = this
|
||||
// const currentIndex = e.rowIndex
|
||||
// const { tableData } = xTable.getTableData()
|
||||
// if (lastIndex > -1 && this.isShifting) {
|
||||
// let start = lastIndex
|
||||
// let end = currentIndex
|
||||
// if (lastIndex > currentIndex) {
|
||||
// start = currentIndex
|
||||
// end = lastIndex
|
||||
// }
|
||||
// const rangeData = tableData.slice(start, end + 1)
|
||||
// xTable.setCheckboxRow(rangeData, true)
|
||||
// }
|
||||
// this.lastIndex = currentIndex
|
||||
// this.$emit('checkbox-change', { ...e, records: xTable.getCheckboxRecords() })
|
||||
// },
|
||||
checkboxRangeStart(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
const lastSelected = xTable.getCheckboxRecords()
|
||||
const selectedReserve = xTable.getCheckboxReserveRecords()
|
||||
this.lastSelected = [...lastSelected, ...selectedReserve]
|
||||
this.$emit('checkbox-range-start', e)
|
||||
},
|
||||
checkboxRangeChange(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
xTable.setCheckboxRow(this.lastSelected, true)
|
||||
this.currentSelected = e.records
|
||||
// this.lastSelected = [...new Set([...this.lastSelected, ...e.records])]
|
||||
this.$emit('checkbox-range-change', {
|
||||
...e,
|
||||
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
|
||||
})
|
||||
},
|
||||
checkboxRangeEnd(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
const isAllSelected = this.currentSelected.every((item) => {
|
||||
const _idx = this.lastSelected.findIndex((ele) => _.isEqual(ele, item))
|
||||
return _idx > -1
|
||||
})
|
||||
if (isAllSelected) {
|
||||
xTable.setCheckboxRow(this.currentSelected, false)
|
||||
}
|
||||
this.currentSelected = []
|
||||
this.lastSelected = []
|
||||
this.$emit('checkbox-range-end', {
|
||||
...e,
|
||||
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
|
@@ -64,7 +64,7 @@ export default {
|
||||
},
|
||||
triggerColor: {
|
||||
type: String,
|
||||
default: '#f0f2f5',
|
||||
default: '#f7f8fa',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
|
@@ -35,7 +35,7 @@ export default {
|
||||
},
|
||||
triggerColor: {
|
||||
type: String,
|
||||
default: '#F0F5FF',
|
||||
default: '#f7f8fa',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
@@ -52,22 +52,21 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.two-column-layout {
|
||||
margin-bottom: -24px;
|
||||
width: 100%;
|
||||
.two-column-layout-sidebar {
|
||||
height: 100%;
|
||||
padding: 15px 7px;
|
||||
border-radius: 15px;
|
||||
overflow-y: auto;
|
||||
background-color: #fff;
|
||||
}
|
||||
.two-column-layout-main {
|
||||
height: 100%;
|
||||
padding: 12px;
|
||||
background-color: #fff;
|
||||
overflow-y: auto;
|
||||
border-radius: 15px;
|
||||
border-radius: @border-radius-box;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,15 +1,11 @@
|
||||
<template>
|
||||
<div class="top-menu" v-if="routes.length > 2">
|
||||
<!-- <a-menu v-model="current" mode="horizontal">
|
||||
<a-menu-item :key="route.name" v-for="route in routes.slice(0, routes.length - 1)">
|
||||
<router-link :to="{ name: route.name }">{{ route.meta.title }}</router-link>
|
||||
</a-menu-item>
|
||||
</a-menu>-->
|
||||
<span
|
||||
:class="current === route.name ? 'top-menu-selected' : ''"
|
||||
v-for="route in defaultShowRoutes"
|
||||
:key="route.name"
|
||||
@click="() => handleClick(route)"
|
||||
:title="$t(route.meta.title)"
|
||||
>
|
||||
{{ route.meta.title }}
|
||||
</span>
|
||||
@@ -43,6 +39,7 @@
|
||||
<script>
|
||||
import store from '@/store'
|
||||
import { gridSvg, top_agent, top_acl } from '@/core/icons'
|
||||
import { getPreference } from '@/modules/cmdb/api/preference'
|
||||
export default {
|
||||
name: 'TopMenu',
|
||||
components: { gridSvg, top_agent, top_acl },
|
||||
@@ -78,10 +75,20 @@ export default {
|
||||
this.current = this.$route.matched[0].name
|
||||
},
|
||||
methods: {
|
||||
handleClick(route) {
|
||||
async handleClick(route) {
|
||||
this.visible = false
|
||||
if (route.name !== this.current) {
|
||||
this.$router.push(route.redirect)
|
||||
if (route.name === 'cmdb') {
|
||||
const preference = await getPreference()
|
||||
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
|
||||
if (lastTypeId && preference.some((item) => item.id === Number(lastTypeId))) {
|
||||
this.$router.push(`/cmdb/instances/types/${lastTypeId}`)
|
||||
} else {
|
||||
this.$router.push('/cmdb/dashboard')
|
||||
}
|
||||
} else {
|
||||
this.$router.push(route.redirect)
|
||||
}
|
||||
// this.current = route.name
|
||||
}
|
||||
},
|
||||
@@ -91,15 +98,6 @@ export default {
|
||||
|
||||
<style lang="less">
|
||||
@import '../../style/static.less';
|
||||
// .top-menu {
|
||||
// display: inline-block;
|
||||
// }
|
||||
// .ant-menu-horizontal {
|
||||
// border-bottom: 0 !important;
|
||||
// }
|
||||
// .ant-menu-horizontal > .ant-menu-item {
|
||||
// border-bottom: 0;
|
||||
// }
|
||||
|
||||
.top-menu {
|
||||
display: inline-flex;
|
||||
@@ -110,33 +108,29 @@ export default {
|
||||
line-height: @layout-header-icon-height;
|
||||
border-radius: 4px !important;
|
||||
display: inline-flex;
|
||||
align-items: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
> span {
|
||||
cursor: pointer;
|
||||
padding: 4px 10px;
|
||||
margin: 0 5px;
|
||||
border-radius: 4px;
|
||||
color: @layout-header-font-color;
|
||||
height: @layout-header-height;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
&:hover {
|
||||
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
|
||||
color: @layout-header-font-selected-color;
|
||||
border-radius: 3px 3px 0px 0px;
|
||||
}
|
||||
line-height: @layout-header-line-height;
|
||||
display: inline-block;
|
||||
}
|
||||
> span:hover,
|
||||
.top-menu-selected {
|
||||
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
|
||||
font-weight: bold;
|
||||
color: @layout-header-font-selected-color;
|
||||
border-radius: 3px 3px 0px 0px;
|
||||
border-bottom: 3px solid @layout-header-font-selected-color;
|
||||
&:hover {
|
||||
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
|
||||
color: @layout-header-font-selected-color;
|
||||
border-radius: 3px 3px 0px 0px;
|
||||
}
|
||||
}
|
||||
> span::before {
|
||||
display: block;
|
||||
content: attr(title);
|
||||
font-weight: bold;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -87,21 +87,21 @@ export default {
|
||||
computed: {
|
||||
...mapState({
|
||||
// 动态主路由
|
||||
mainMenu: state => state.routes.appRoutes,
|
||||
mainMenu: (state) => state.routes.appRoutes,
|
||||
}),
|
||||
contentPaddingLeft() {
|
||||
if (!this.fixSidebar || this.isMobile()) {
|
||||
return '0'
|
||||
}
|
||||
if (this.sidebarOpened) {
|
||||
return '200px'
|
||||
return '220px'
|
||||
}
|
||||
return '80px'
|
||||
},
|
||||
sideBarMenu() {
|
||||
const sideMenus = this.mainMenu.filter(item => this.$route.path.startsWith(item.path))
|
||||
const sideMenus = this.mainMenu.filter((item) => this.$route.path.startsWith(item.path))
|
||||
if (sideMenus.length > 1) {
|
||||
return sideMenus.find(item => item.path !== '/').children
|
||||
return sideMenus.find((item) => item.path !== '/').children
|
||||
} else {
|
||||
return sideMenus[0].children
|
||||
}
|
||||
@@ -110,6 +110,9 @@ export default {
|
||||
provide() {
|
||||
return {
|
||||
reloadBoard: this.reload,
|
||||
collapsed: () => {
|
||||
return this.collapsed
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -146,15 +149,6 @@ export default {
|
||||
this.alive = true
|
||||
})
|
||||
},
|
||||
paddingCalc() {
|
||||
let left = ''
|
||||
if (this.sidebarOpened) {
|
||||
left = this.isDesktop() ? '200px' : '80px'
|
||||
} else {
|
||||
left = (this.isMobile() && '0') || (this.fixSidebar && '80px') || '0'
|
||||
}
|
||||
return left
|
||||
},
|
||||
menuSelect() {
|
||||
if (!this.isDesktop()) {
|
||||
this.collapsed = false
|
||||
|
@@ -233,8 +233,10 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-history {
|
||||
border-radius: 15px;
|
||||
border-radius: @border-radius-box;
|
||||
height: calc(100vh - 64px);
|
||||
margin-bottom: -24px;
|
||||
padding: 24px;
|
||||
|
@@ -290,7 +290,6 @@ export default {
|
||||
const str = ` 【 ${key} : -> ${newVal} 】 `
|
||||
item.description += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : ${oldVal} -> ${newVal} 】 `
|
||||
item.description += ` 【 ${key} : ${oldVal} -> ${newVal} 】 `
|
||||
}
|
||||
}
|
||||
|
@@ -241,7 +241,6 @@ export default {
|
||||
const str = ` 【 ${key} : -> ${newVal} 】 `
|
||||
item.changeDescription += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : ${oldVal} -> ${newVal} 】 `
|
||||
item.changeDescription += ` 【 ${key} : ${oldVal} -> ${newVal} 】 `
|
||||
}
|
||||
}
|
||||
|
@@ -32,8 +32,10 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-operation-history {
|
||||
border-radius: 15px;
|
||||
border-radius: @border-radius-box;
|
||||
height: calc(100vh - 64px);
|
||||
margin-bottom: -24px;
|
||||
padding: 24px;
|
||||
|
@@ -189,8 +189,10 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-resource-types {
|
||||
border-radius: 15px;
|
||||
border-radius: @border-radius-box;
|
||||
background-color: #fff;
|
||||
height: calc(100vh - 64px);
|
||||
margin-bottom: -24px;
|
||||
|
@@ -352,8 +352,10 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-resources {
|
||||
border-radius: 15px;
|
||||
border-radius: @border-radius-box;
|
||||
background-color: #fff;
|
||||
height: calc(100vh - 64px);
|
||||
margin-bottom: -24px;
|
||||
|
@@ -285,8 +285,10 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-roles {
|
||||
border-radius: 15px;
|
||||
border-radius: @border-radius-box;
|
||||
background-color: #fff;
|
||||
height: calc(100vh - 64px);
|
||||
margin-bottom: -24px;
|
||||
|
@@ -88,10 +88,12 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-secret-key {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 15px;
|
||||
border-radius: @border-radius-box;
|
||||
height: calc(100% + 24px);
|
||||
.ant-input[disabled] {
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
|
@@ -320,8 +320,10 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-trigger {
|
||||
border-radius: 15px;
|
||||
border-radius: @border-radius-box;
|
||||
background-color: #fff;
|
||||
height: calc(100vh - 64px);
|
||||
margin-bottom: -24px;
|
||||
|
@@ -188,8 +188,10 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-users {
|
||||
border-radius: 15px;
|
||||
border-radius: @border-radius-box;
|
||||
background-color: #fff;
|
||||
height: calc(100vh - 64px);
|
||||
margin-bottom: -24px;
|
||||
|
@@ -30,11 +30,11 @@ export function getRelationTypes(CITypeID, parameter) {
|
||||
})
|
||||
}
|
||||
|
||||
export function createRelation(parentId, childrenId, relationTypeId, constraint) {
|
||||
export function createRelation(parentId, childrenId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_type_relations/${parentId}/${childrenId}`,
|
||||
method: 'post',
|
||||
data: { relation_type_id: relationTypeId, constraint }
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ export function deleteRelation(parentId, childrenId) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_type_relations/${parentId}/${childrenId}`,
|
||||
method: 'delete'
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -71,6 +71,14 @@ export function subscribeRelationView(payload) {
|
||||
})
|
||||
}
|
||||
|
||||
export function putRelationView(id, data) {
|
||||
return axios({
|
||||
url: `/v0.1/preference/relation/view/${id}`,
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 用户保存条件过滤选项
|
||||
export function getPreferenceSearch(payload) {
|
||||
// 参数有prv_id: 关系视图的id, ptv_id: 层级视图的id, type_id: 模型id
|
||||
|
BIN
cmdb-ui/src/modules/cmdb/assets/unique_card.png
Normal file
BIN
cmdb-ui/src/modules/cmdb/assets/unique_card.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 104}px` }">
|
||||
<div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 130}px` }">
|
||||
<template v-if="cmdbGrantType.includes('ci_type')">
|
||||
<div class="cmdb-grant-title">{{ $t('cmdb.components.ciTypeGrant') }}</div>
|
||||
<CiTypeGrant
|
||||
@@ -314,10 +314,10 @@ export default {
|
||||
@import '~@/style/static.less';
|
||||
.cmdb-grant {
|
||||
position: relative;
|
||||
padding: 24px 24px 0 24px;
|
||||
padding: 0 20px;
|
||||
overflow: auto;
|
||||
.cmdb-grant-title {
|
||||
border-left: 4px solid #custom_colors[color_1];
|
||||
border-left: 4px solid @primary-color;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
@@ -325,18 +325,19 @@ export default {
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-grant {
|
||||
.grant-button {
|
||||
padding: 6px 8px;
|
||||
color: #custom_colors[color_1];
|
||||
background-color: #custom_colors[color_2];
|
||||
color: @primary-color;
|
||||
background-color: @primary-color_5;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
margin: 15px 0;
|
||||
display: inline-block;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
box-shadow: 2px 3px 4px #custom_colors[color_2];
|
||||
box-shadow: 2px 3px 4px @primary-color_5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<a-modal width="800px" :visible="visible" @ok="handleOk" @cancel="handleCancel" :bodyStyle="{ padding: 0 }">
|
||||
<a-modal
|
||||
width="800px"
|
||||
:visible="visible"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
:bodyStyle="{ padding: 0, paddingTop: '20px' }"
|
||||
>
|
||||
<GrantComp
|
||||
:resourceType="resourceType"
|
||||
:app_id="app_id"
|
||||
|
@@ -11,7 +11,7 @@
|
||||
@blur="handleInputConfirm"
|
||||
@keyup.enter="handleInputConfirm"
|
||||
/>
|
||||
<a-button v-else type="primary" size="small" ghost @click="showInput">{{ $t('cmdb.components.saveQuery') }}</a-button>
|
||||
<a v-else @click="showInput"> {{ $t('cmdb.components.saveQuery') }}</a>
|
||||
</span>
|
||||
<template v-for="(item, index) in preferenceSearchList.slice(0, 3)">
|
||||
<span
|
||||
@@ -178,10 +178,10 @@ export default {
|
||||
<style lang="less" scoped>
|
||||
.preference-search-tag {
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #d9d9d9;
|
||||
display: inline-block;
|
||||
padding: 0 7px;
|
||||
padding: 2px 7px;
|
||||
margin-right: 8px;
|
||||
> span {
|
||||
margin-right: 4px;
|
||||
|
@@ -5,8 +5,15 @@
|
||||
<a-space>
|
||||
<treeselect
|
||||
v-if="type === 'resourceSearch'"
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '250px', marginRight: '10px', '--custom-height': '32px' }"
|
||||
class="custom-treeselect custom-treeselect-bgcAndBorder"
|
||||
:style="{
|
||||
width: '200px',
|
||||
marginRight: '10px',
|
||||
'--custom-height': '32px',
|
||||
'--custom-bg-color': '#fff',
|
||||
'--custom-border': '1px solid #d9d9d9',
|
||||
'--custom-multiple-lineHeight': '16px',
|
||||
}"
|
||||
v-model="currenCiType"
|
||||
:multiple="true"
|
||||
:clearable="true"
|
||||
@@ -41,15 +48,14 @@
|
||||
</treeselect>
|
||||
<a-input
|
||||
v-model="fuzzySearch"
|
||||
:style="{ display: 'inline-block', width: '244px' }"
|
||||
:style="{ display: 'inline-block', width: '200px' }"
|
||||
:placeholder="$t('cmdb.components.pleaseSearch')"
|
||||
@pressEnter="emitRefresh"
|
||||
class="ops-input ops-input-radius"
|
||||
>
|
||||
<a-icon
|
||||
type="search"
|
||||
slot="suffix"
|
||||
:style="{ color: fuzzySearch ? '#2f54eb' : '', cursor: 'pointer' }"
|
||||
:style="{ color: fuzzySearch ? '#2f54eb' : '#d9d9d9', cursor: 'pointer' }"
|
||||
@click="emitRefresh"
|
||||
/>
|
||||
<a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px', whiteSpace: 'pre-line' }">
|
||||
@@ -59,6 +65,9 @@
|
||||
<a><a-icon type="question-circle"/></a>
|
||||
</a-tooltip>
|
||||
</a-input>
|
||||
<a-tooltip :title="$t('reset')">
|
||||
<a-button @click="reset">{{ $t('reset') }}</a-button>
|
||||
</a-tooltip>
|
||||
<FilterComp
|
||||
ref="filterComp"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList"
|
||||
@@ -69,7 +78,7 @@
|
||||
<div slot="popover_item" class="search-form-bar-filter">
|
||||
<a-icon class="search-form-bar-filter-icon" type="filter" />
|
||||
{{ $t('cmdb.components.conditionFilter') }}
|
||||
<a-icon class="search-form-bar-filter-icon" type="down" />
|
||||
<a-icon class="search-form-bar-filter-icon" type="down" :style="{ color: '#d9d9d9' }" />
|
||||
</div>
|
||||
</FilterComp>
|
||||
<a-input
|
||||
@@ -91,14 +100,13 @@
|
||||
:placeholder="placeholder"
|
||||
@keyup.enter="emitRefresh"
|
||||
>
|
||||
<a-icon slot="suffix" type="copy" @click="handleCopyExpression" />
|
||||
<ops-icon slot="suffix" type="veops-copy" @click="handleCopyExpression" />
|
||||
</a-input>
|
||||
<slot></slot>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-space>
|
||||
<slot name="extraContent"></slot>
|
||||
<a-button @click="reset" size="small">{{ $t('reset') }}</a-button>
|
||||
<a-tooltip :title="$t('cmdb.components.attributeDesc')" v-if="type === 'relationView'">
|
||||
<a
|
||||
@click="
|
||||
@@ -237,7 +245,6 @@ export default {
|
||||
}
|
||||
},
|
||||
inputCiTypeGroup(value) {
|
||||
console.log(value)
|
||||
if (!value || !value.length) {
|
||||
this.$emit('updateAllAttributesList', value)
|
||||
}
|
||||
@@ -257,6 +264,7 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
@import '../../views/index.less';
|
||||
.ci-searchform-expression {
|
||||
> input {
|
||||
@@ -266,14 +274,14 @@ export default {
|
||||
border-right: none;
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-bottom: 2px solid #2f54eb;
|
||||
border-bottom: 2px solid @primary-color;
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0 2px 2px -2px #1f78d133;
|
||||
}
|
||||
}
|
||||
.ant-input-suffix {
|
||||
color: #2f54eb;
|
||||
color: #d9d9d9;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
@@ -290,14 +298,15 @@ export default {
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.search-form-bar {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
.search-form-bar-filter {
|
||||
.ops_display_wrapper();
|
||||
.ops_display_wrapper(transparent);
|
||||
.search-form-bar-filter-icon {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
const cmdb_en = {
|
||||
relation: 'Relation',
|
||||
attribute: 'Attributes',
|
||||
configTable: 'Config Table',
|
||||
menu: {
|
||||
views: 'Views',
|
||||
config: 'Configuration',
|
||||
@@ -182,7 +183,18 @@ const cmdb_en = {
|
||||
inheritType: 'Inherit Type',
|
||||
inheritTypePlaceholder: 'Please select inherit types',
|
||||
inheritFrom: 'inherit from {name}',
|
||||
groupInheritFrom: 'Please go to the {name} for modification'
|
||||
groupInheritFrom: 'Please go to the {name} for modification',
|
||||
downloadType: 'Download CIType',
|
||||
deleteCIType: 'Delete CIType',
|
||||
otherGroupTips: 'Non sortable within the other group',
|
||||
filterTips: 'click to show {name}',
|
||||
attributeAssociation: 'Attribute Association',
|
||||
attributeAssociationTip1: 'Automatically establish relationships through the attributes except password, json and multiple of two models',
|
||||
attributeAssociationTip2: 'Double click to edit',
|
||||
attributeAssociationTip3: 'Two Attributes must be selected',
|
||||
attributeAssociationTip4: 'Please select a attribute from Source CIType',
|
||||
attributeAssociationTip5: 'Please select a attribute from Target CIType',
|
||||
|
||||
},
|
||||
components: {
|
||||
unselectAttributes: 'Unselected',
|
||||
@@ -245,8 +257,10 @@ const cmdb_en = {
|
||||
unselectCIType: 'No CIType selected yet',
|
||||
pleaseUploadFile: 'Please upload files',
|
||||
batchUploadCanceled: 'Batch upload canceled',
|
||||
selectCITypeTips: 'Please select CIType',
|
||||
selectCIType: 'Select CIType',
|
||||
selectCITypeTips: 'Please select a CIType and then download',
|
||||
downloadTemplate: 'Download Template',
|
||||
clickDownload: 'Click to Download',
|
||||
drawTips: 'Click or drag files here to upload!',
|
||||
supportFileTypes: 'Supported file types: xls, xlsx',
|
||||
uploadResult: 'Upload results',
|
||||
@@ -257,6 +271,16 @@ const cmdb_en = {
|
||||
errorTips: 'Error message',
|
||||
requestFailedTips: 'An error occurred with the request, please try again later',
|
||||
requestSuccessTips: 'Upload completed',
|
||||
uploadFile: 'Upload File',
|
||||
drawTips1: 'Please <span class="cmdb-batch-upload-tips">select a CIType</span>, and then <span class="cmdb-batch-upload-tips">download</span> ,',
|
||||
drawTips2: '<span class="cmdb-batch-upload-tips">click or drag file</span> to upload',
|
||||
dataPreview: 'Preview data and upload',
|
||||
tips1: 'Kind Reminder :',
|
||||
tips2: '1. Click to download the template, and users can customize the header of the template file, including model properties and model associations',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
tips3: '2. The red color in the template file represents the model relationship, such as the $Product. Product Name (${Model Name}. {Attribute Name}) column, which establishes the relationship with the product.',
|
||||
tips4: '3. In the download template Excel file, the predefined values of attributes will be set as dropdown options. Please note that due to the limitations of Excel itself, a single dropdown box is limited to a maximum of 255 characters. If it exceeds 255 characters, we will not set the dropdown options for this attribute',
|
||||
tips5: '4. When using Excel templates, please ensure that a single file does not exceed 5000 lines.',
|
||||
},
|
||||
preference: {
|
||||
mySub: 'My Subscription',
|
||||
@@ -274,6 +298,7 @@ const cmdb_en = {
|
||||
monthsAgo: 'month ago',
|
||||
yearsAgo: 'years ago',
|
||||
just: 'just now',
|
||||
searchPlaceholder: 'Please search CIType',
|
||||
},
|
||||
custom_dashboard: {
|
||||
charts: 'Chart',
|
||||
@@ -313,14 +338,23 @@ const cmdb_en = {
|
||||
noCustomDashboard: 'The administrator has not customized the dashboard yet',
|
||||
},
|
||||
preference_relation: {
|
||||
newServiceTree: 'Add ServiceTree',
|
||||
newServiceTree: 'Add Service Tree',
|
||||
editServiceTree: 'Edit Service Tree',
|
||||
serviceTreeName: 'Name',
|
||||
serviceTreeNamePlaceholder: 'Please enter the service tree name',
|
||||
public: 'Public',
|
||||
saveLayout: 'Save Layout',
|
||||
childNodesNotFound: 'There are no child nodes and no business relationship can be formed. Please select again!',
|
||||
tips1: 'Cannot form a view with the currently selected node, please select again!',
|
||||
tips2: 'Please enter the new serviceTree name!',
|
||||
tips3: 'Please select at least two nodes!',
|
||||
tips4: 'Leaf node must be selected',
|
||||
tips5: 'Select the tree directory node and display the service tree sub nodes as a Table',
|
||||
showLeafNode: 'Show Leaf Node',
|
||||
showTreeNode: 'Show Tree Node',
|
||||
sort: 'Sort',
|
||||
sort1: 'Leaf node information comes first',
|
||||
sort2: 'Tree node information comes first'
|
||||
},
|
||||
history: {
|
||||
ciChange: 'CI',
|
||||
@@ -392,51 +426,51 @@ const cmdb_en = {
|
||||
updateFields: 'Update Field',
|
||||
pluginScript: `# -*- coding:utf-8 -*-
|
||||
|
||||
import json
|
||||
import json
|
||||
|
||||
|
||||
class AutoDiscovery(object):
|
||||
class AutoDiscovery(object):
|
||||
|
||||
@property
|
||||
def unique_key(self):
|
||||
"""
|
||||
@property
|
||||
def unique_key(self):
|
||||
"""
|
||||
|
||||
:return: Returns the name of a unique attribute
|
||||
"""
|
||||
return
|
||||
:return: Returns the name of a unique attribute
|
||||
"""
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def attributes():
|
||||
"""
|
||||
Define attribute fields
|
||||
:return: Returns a list of attribute fields. The list items are (name, type, description). The name must be in English.
|
||||
type: String Integer Float Date DateTime Time JSON
|
||||
For example:
|
||||
return [
|
||||
("ci_type", "String", "CIType name"),
|
||||
("private_ip", "String", "Internal IP, multiple values separated by commas")
|
||||
]
|
||||
"""
|
||||
return []
|
||||
@staticmethod
|
||||
def attributes():
|
||||
"""
|
||||
Define attribute fields
|
||||
:return: Returns a list of attribute fields. The list items are (name, type, description). The name must be in English.
|
||||
type: String Integer Float Date DateTime Time JSON
|
||||
For example:
|
||||
return [
|
||||
("ci_type", "String", "CIType name"),
|
||||
("private_ip", "String", "Internal IP, multiple values separated by commas")
|
||||
]
|
||||
"""
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def run():
|
||||
"""
|
||||
Execution entry, returns collected attribute values
|
||||
:return:
|
||||
Returns a list, the list item is a dictionary, the dictionary key is the attribute name, and the value is the attribute value
|
||||
For example:
|
||||
return [dict(ci_type="server", private_ip="192.168.1.1")]
|
||||
"""
|
||||
return []
|
||||
@staticmethod
|
||||
def run():
|
||||
"""
|
||||
Execution entry, returns collected attribute values
|
||||
:return:
|
||||
Returns a list, the list item is a dictionary, the dictionary key is the attribute name, and the value is the attribute value
|
||||
For example:
|
||||
return [dict(ci_type="server", private_ip="192.168.1.1")]
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = AutoDiscovery().run()
|
||||
if isinstance(result, list):
|
||||
print("AutoDiscovery::Result::{}".format(json.dumps(result)))
|
||||
else:
|
||||
print("ERROR: The collection return must be a list")
|
||||
if __name__ == "__main__":
|
||||
result = AutoDiscovery().run()
|
||||
if isinstance(result, list):
|
||||
print("AutoDiscovery::Result::{}".format(json.dumps(result)))
|
||||
else:
|
||||
print("ERROR: The collection return must be a list")
|
||||
`,
|
||||
server: 'Server',
|
||||
vserver: 'VServer',
|
||||
@@ -478,13 +512,16 @@ const cmdb_en = {
|
||||
noPermission: 'No Permission'
|
||||
},
|
||||
serviceTree: {
|
||||
deleteNode: 'Delete Node',
|
||||
remove: 'Remove',
|
||||
deleteNode: 'Delete {name}',
|
||||
tips1: 'For example: q=os_version:centos&sort=os_version',
|
||||
tips2: 'Expression search',
|
||||
alert1: 'The administrator has not configured the ServiceTree(relation view), or you do not have permission to access it!',
|
||||
copyFailed: 'Copy failed',
|
||||
deleteRelationConfirm: 'Confirm to remove selected {name} from current relationship?',
|
||||
batch: 'Batch',
|
||||
editNode: 'Edit Node',
|
||||
editNodeName: 'Edit Node Name',
|
||||
grantTitle: 'Grant(read)',
|
||||
userPlaceholder: 'Please select users',
|
||||
rolePlaceholder: 'Please select roles',
|
||||
|
@@ -1,6 +1,7 @@
|
||||
const cmdb_zh = {
|
||||
relation: '关系',
|
||||
attribute: '属性',
|
||||
configTable: '配置表格',
|
||||
menu: {
|
||||
views: '视图',
|
||||
config: '配置',
|
||||
@@ -35,7 +36,7 @@ const cmdb_zh = {
|
||||
attributeLibray: '属性库',
|
||||
addCITypeInGroup: '在该组中新增CI模型',
|
||||
addCIType: '新增CI模型',
|
||||
editGroupName: '编辑组名称',
|
||||
editGroupName: '重命名分组',
|
||||
deleteGroup: '删除该组',
|
||||
CITypeName: '模型名(英文)',
|
||||
English: '英文',
|
||||
@@ -128,7 +129,7 @@ const cmdb_zh = {
|
||||
addRelation: '新增关系',
|
||||
sourceCIType: '源模型',
|
||||
sourceCITypeTips: '请选择源模型',
|
||||
dstCIType: '目标模型名',
|
||||
dstCIType: '目标模型',
|
||||
dstCITypeTips: '请选择目标模型',
|
||||
relationType: '关联类型',
|
||||
relationTypeTips: '请选择关联类型',
|
||||
@@ -182,7 +183,17 @@ const cmdb_zh = {
|
||||
inheritType: '继承模型',
|
||||
inheritTypePlaceholder: '请选择继承模型(多选)',
|
||||
inheritFrom: '属性继承自{name}',
|
||||
groupInheritFrom: '请至{name}进行修改'
|
||||
groupInheritFrom: '请至{name}进行修改',
|
||||
downloadType: '下载模型',
|
||||
deleteCIType: '删除模型',
|
||||
otherGroupTips: '其他分组属性不可排序',
|
||||
filterTips: '点击可仅查看{name}属性',
|
||||
attributeAssociation: '属性关联',
|
||||
attributeAssociationTip1: '通过2个模型的属性值(除密码、json、多值)来自动建立关系',
|
||||
attributeAssociationTip2: '双击可编辑',
|
||||
attributeAssociationTip3: '属性关联必须选择两个属性',
|
||||
attributeAssociationTip4: '请选择原模型属性',
|
||||
attributeAssociationTip5: '请选择目标模型属性',
|
||||
},
|
||||
components: {
|
||||
unselectAttributes: '未选属性',
|
||||
@@ -220,7 +231,7 @@ const cmdb_zh = {
|
||||
beforeChange: '变更前',
|
||||
afterChange: '变更后',
|
||||
noticeContentTips: '请输入通知内容',
|
||||
saveQuery: '保存筛选条件',
|
||||
saveQuery: '保存条件',
|
||||
pleaseSearch: '请查找',
|
||||
conditionFilter: '条件过滤',
|
||||
attributeDesc: '属性说明',
|
||||
@@ -245,9 +256,10 @@ const cmdb_zh = {
|
||||
unselectCIType: '尚未选择模板类型',
|
||||
pleaseUploadFile: '请上传文件',
|
||||
batchUploadCanceled: '批量上传已取消',
|
||||
selectCITypeTips: '请选择模板类型',
|
||||
selectCIType: '选择模型',
|
||||
selectCITypeTips: '请选择模型后下载模板',
|
||||
downloadTemplate: '下载模板',
|
||||
drawTips: '点击或拖拽文件至此上传!',
|
||||
clickDownload: '点击下载',
|
||||
supportFileTypes: '支持文件类型:xls,xlsx',
|
||||
uploadResult: '上传结果',
|
||||
total: '共',
|
||||
@@ -257,6 +269,16 @@ const cmdb_zh = {
|
||||
errorTips: '错误信息',
|
||||
requestFailedTips: '请求出现错误,请稍后再试',
|
||||
requestSuccessTips: '批量上传已完成',
|
||||
uploadFile: '文件上传',
|
||||
drawTips1: '请先<span class="cmdb-batch-upload-tips">选择模型</span>,<span class="cmdb-batch-upload-tips">下载模板</span>后',
|
||||
drawTips2: '<span class="cmdb-batch-upload-tips">点击或拖拽文件</span>至此上传',
|
||||
dataPreview: '数据预览并导入',
|
||||
tips1: '温馨提示:',
|
||||
tips2: '1. 点击下载模板,用户可以自定义模板文件的表头,包括模型属性、模型关联',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
tips3: '2. 模板文件中红色为模型关系,如$产品.产品名(${模型名}.{属性名})这一列就可建立和产品之间的关系',
|
||||
tips4: '3. 下载模板excel文件中会将属性的预定义值置为下拉选项,请注意,受excel本身的限制,单个下拉框限制了最多255个字符,如果超过255个字符,我们不会设置该属性的下拉选项',
|
||||
tips5: '4. 在使用excel模板时,请确保单个文件不超过5000行',
|
||||
},
|
||||
preference: {
|
||||
mySub: '我的订阅',
|
||||
@@ -274,6 +296,7 @@ const cmdb_zh = {
|
||||
monthsAgo: '月前',
|
||||
yearsAgo: '年前',
|
||||
just: '刚刚',
|
||||
searchPlaceholder: '请搜索模型',
|
||||
},
|
||||
custom_dashboard: {
|
||||
charts: '图表',
|
||||
@@ -314,13 +337,23 @@ const cmdb_zh = {
|
||||
},
|
||||
preference_relation: {
|
||||
newServiceTree: '新增服务树',
|
||||
editServiceTree: '编辑服务树',
|
||||
serviceTreeName: '服务树名',
|
||||
serviceTreeNamePlaceholder: '请输入服务树名',
|
||||
public: '公开',
|
||||
saveLayout: '保存布局',
|
||||
childNodesNotFound: '不存在子节点,不能形成业务关系,请重新选择!',
|
||||
tips1: '不能与当前选中节点形成视图,请重新选择!',
|
||||
tips2: '请输入新增服务树名!',
|
||||
tips3: '请选择至少两个节点!',
|
||||
tips4: '树子节点为必选',
|
||||
tips5: '选中树目录节点,服务树子节点展示成Table',
|
||||
showLeafNode: '树的子节点展示成Table',
|
||||
showTreeNode: '展示树节点信息',
|
||||
sort: '顺序',
|
||||
sort1: '树子节点信息在前',
|
||||
sort2: '树节点信息在前'
|
||||
|
||||
},
|
||||
history: {
|
||||
ciChange: 'CI变更',
|
||||
@@ -392,50 +425,51 @@ const cmdb_zh = {
|
||||
updateFields: '更新字段',
|
||||
pluginScript: `# -*- coding:utf-8 -*-
|
||||
|
||||
import json
|
||||
import json
|
||||
|
||||
|
||||
class AutoDiscovery(object):
|
||||
class AutoDiscovery(object):
|
||||
|
||||
@property
|
||||
def unique_key(self):
|
||||
"""
|
||||
@property
|
||||
def unique_key(self):
|
||||
"""
|
||||
|
||||
:return: 返回唯一属性的名字
|
||||
"""
|
||||
return
|
||||
:return: Returns the name of a unique attribute
|
||||
"""
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def attributes():
|
||||
"""
|
||||
定义属性字段
|
||||
:return: 返回属性字段列表, 列表项是(名称, 类型, 描述), 名称必须是英文
|
||||
类型: String Integer Float Date DateTime Time JSON
|
||||
例如:
|
||||
return [
|
||||
("ci_type", "String", "模型名称"),
|
||||
("private_ip", "String", "内网IP, 多值逗号分隔")
|
||||
]
|
||||
"""
|
||||
return []
|
||||
@staticmethod
|
||||
def attributes():
|
||||
"""
|
||||
Define attribute fields
|
||||
:return: Returns a list of attribute fields. The list items are (name, type, description). The name must be in English.
|
||||
type: String Integer Float Date DateTime Time JSON
|
||||
For example:
|
||||
return [
|
||||
("ci_type", "String", "CIType name"),
|
||||
("private_ip", "String", "Internal IP, multiple values separated by commas")
|
||||
]
|
||||
"""
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def run():
|
||||
"""
|
||||
执行入口, 返回采集的属性值
|
||||
:return: 返回一个列表, 列表项是字典, 字典key是属性名称, value是属性值
|
||||
例如:
|
||||
return [dict(ci_type="server", private_ip="192.168.1.1")]
|
||||
"""
|
||||
return []
|
||||
@staticmethod
|
||||
def run():
|
||||
"""
|
||||
Execution entry, returns collected attribute values
|
||||
:return:
|
||||
Returns a list, the list item is a dictionary, the dictionary key is the attribute name, and the value is the attribute value
|
||||
For example:
|
||||
return [dict(ci_type="server", private_ip="192.168.1.1")]
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = AutoDiscovery().run()
|
||||
if isinstance(result, list):
|
||||
print("AutoDiscovery::Result::{}".format(json.dumps(result)))
|
||||
else:
|
||||
print("ERROR: 采集返回必须是列表")
|
||||
if __name__ == "__main__":
|
||||
result = AutoDiscovery().run()
|
||||
if isinstance(result, list):
|
||||
print("AutoDiscovery::Result::{}".format(json.dumps(result)))
|
||||
else:
|
||||
print("ERROR: The collection return must be a list")
|
||||
`,
|
||||
server: '物理机',
|
||||
vserver: '虚拟机',
|
||||
@@ -443,7 +477,7 @@ const cmdb_zh = {
|
||||
disk: '硬盘',
|
||||
},
|
||||
ci: {
|
||||
attributeDesc: '属性说明',
|
||||
attributeDesc: '查看属性配置',
|
||||
selectRows: '选取:{rows} 项',
|
||||
addRelation: '添加关系',
|
||||
all: '全部',
|
||||
@@ -477,13 +511,16 @@ const cmdb_zh = {
|
||||
noPermission: '暂无权限'
|
||||
},
|
||||
serviceTree: {
|
||||
deleteNode: '删除节点',
|
||||
remove: '移除',
|
||||
deleteNode: '移除 {name}',
|
||||
tips1: '例:q=os_version:centos&sort=os_version',
|
||||
tips2: '表达式搜索',
|
||||
alert1: '管理员 还未配置业务关系, 或者你无权限访问!',
|
||||
copyFailed: '复制失败',
|
||||
deleteRelationConfirm: '确认将选中的 {name} 从当前关系中删除?',
|
||||
batch: '批量操作',
|
||||
editNode: '编辑节点',
|
||||
editNodeName: '修改节点名',
|
||||
grantTitle: '授权(查看权限)',
|
||||
userPlaceholder: '请选择用户',
|
||||
rolePlaceholder: '请选择角色',
|
||||
|
@@ -156,8 +156,8 @@ const genCmdbRoutes = async () => {
|
||||
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
|
||||
if (lastTypeId && preference.some(item => item.id === Number(lastTypeId))) {
|
||||
routes.redirect = `/cmdb/instances/types/${lastTypeId}`
|
||||
} else if (routes.children[2].children.length > 0) {
|
||||
routes.redirect = routes.children[2].children.find(item => !item.hidden).path
|
||||
} else if (routes.children[2]?.children?.length > 0) {
|
||||
routes.redirect = routes.children[2].children.find(item => !item.hidden)?.path
|
||||
} else {
|
||||
routes.redirect = '/cmdb/dashboard'
|
||||
}
|
||||
|
@@ -49,7 +49,6 @@ export function getCITableColumns(data, attrList, width = 1600, height) {
|
||||
const _attrList = _.orderBy(attrList, ['is_fixed'], ['desc'])
|
||||
const columns = []
|
||||
for (let attr of _attrList) {
|
||||
|
||||
const editRender = { name: 'input' }
|
||||
switch (attr.value_type) {
|
||||
case '0':
|
||||
@@ -85,7 +84,7 @@ export function getCITableColumns(data, attrList, width = 1600, height) {
|
||||
|
||||
}
|
||||
columns.push({
|
||||
attr_id:attr.id,
|
||||
attr_id: attr.id,
|
||||
editRender,
|
||||
title: attr.alias || attr.name,
|
||||
field: attr.name,
|
||||
@@ -135,6 +134,35 @@ export const getPropertyStyle = (attr) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const getPropertyIcon = (attr) => {
|
||||
switch (attr.value_type) {
|
||||
case '0':
|
||||
return 'duose-shishu'
|
||||
case '1':
|
||||
return 'duose-fudianshu'
|
||||
case '2':
|
||||
if (attr.is_password) {
|
||||
return 'duose-password'
|
||||
}
|
||||
if (attr.is_link) {
|
||||
return 'duose-link'
|
||||
}
|
||||
return 'duose-wenben'
|
||||
case '3':
|
||||
return 'duose-datetime'
|
||||
case '4':
|
||||
return 'duose-date'
|
||||
case '5':
|
||||
return 'duose-time'
|
||||
case '6':
|
||||
return 'duose-json'
|
||||
case '7':
|
||||
return 'duose-password'
|
||||
case '8':
|
||||
return 'duose-link'
|
||||
}
|
||||
}
|
||||
|
||||
export const getLastLayout = (data, x1 = 0, y1 = 0, w1 = 0) => {
|
||||
const _tempData = _.orderBy(data, ['y', 'x'], ['asc', 'asc'])
|
||||
if (!_tempData.length) {
|
||||
@@ -185,7 +213,7 @@ export const getAllParentNodesLabel = (node, label) => {
|
||||
return getAllParentNodesLabel(node.parentNode, `${node.parentNode.label}-${label}`)
|
||||
}
|
||||
return label
|
||||
}
|
||||
export const getTreeSelectLabel = (node) => {
|
||||
}
|
||||
export const getTreeSelectLabel = (node) => {
|
||||
return `${getAllParentNodesLabel(node, node.label)}`
|
||||
}
|
||||
}
|
@@ -1,39 +1,41 @@
|
||||
<template>
|
||||
<div class="cmdb-batch-upload" :style="{ height: `${windowHeight - 64}px` }">
|
||||
<div id="title">
|
||||
<ci-type-choice ref="ciTypeChoice" @getCiTypeAttr="showCiType" />
|
||||
<div class="cmdb-views-header">
|
||||
<span>
|
||||
<span class="cmdb-views-header-title">{{ $t('cmdb.menu.batchUpload') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<upload-file-form
|
||||
:isUploading="isUploading"
|
||||
:ciType="ciType"
|
||||
ref="uploadFileForm"
|
||||
@uploadDone="uploadDone"
|
||||
></upload-file-form>
|
||||
</a-col>
|
||||
<a-col :span="24" v-if="ciType && uploadData.length">
|
||||
<CiUploadTable :ciTypeAttrs="ciTypeAttrs" ref="ciUploadTable" :uploadData="uploadData"></CiUploadTable>
|
||||
<div class="cmdb-batch-upload-action">
|
||||
<a-space size="large">
|
||||
<a-button type="primary" ghost @click="handleCancel">{{ $t('cancel') }}</a-button>
|
||||
<a-button @click="handleUpload" type="primary">{{ $t('upload') }}</a-button>
|
||||
<a-button v-if="hasError && !isUploading" @click="downloadError" type="primary">{{ $t('cmdb.batch.downloadFailed') }}</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="24" v-if="ciType">
|
||||
<upload-result
|
||||
ref="uploadResult"
|
||||
:upLoadData="uploadData"
|
||||
:ciType="ciType"
|
||||
:unique-field="uniqueField"
|
||||
:isUploading="isUploading"
|
||||
@uploadResultDone="uploadResultDone"
|
||||
@uploadResultError="uploadResultError"
|
||||
></upload-result>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<CiTypeChoice ref="ciTypeChoice" @getCiTypeAttr="showCiType" />
|
||||
<p class="cmdb-batch-upload-label"><span>*</span>3. {{ $t('cmdb.batch.uploadFile') }}</p>
|
||||
<UploadFileForm
|
||||
:isUploading="isUploading"
|
||||
:ciType="ciType"
|
||||
ref="uploadFileForm"
|
||||
@uploadDone="uploadDone"
|
||||
></UploadFileForm>
|
||||
<p class="cmdb-batch-upload-label">4. {{ $t('cmdb.batch.dataPreview') }}</p>
|
||||
<CiUploadTable :ciTypeAttrs="ciTypeAttrs" ref="ciUploadTable" :uploadData="uploadData"></CiUploadTable>
|
||||
<div class="cmdb-batch-upload-action">
|
||||
<a-space size="large">
|
||||
<a-button :disabled="!(ciType && uploadData.length)" @click="handleUpload" type="primary">{{
|
||||
$t('upload')
|
||||
}}</a-button>
|
||||
<a-button @click="handleCancel">{{ $t('cancel') }}</a-button>
|
||||
<a-button v-if="hasError && !isUploading" @click="downloadError" type="primary">{{
|
||||
$t('cmdb.batch.downloadFailed')
|
||||
}}</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
<UploadResult
|
||||
v-if="ciType"
|
||||
ref="uploadResult"
|
||||
:upLoadData="uploadData"
|
||||
:ciType="ciType"
|
||||
:unique-field="uniqueField"
|
||||
:isUploading="isUploading"
|
||||
@uploadResultDone="uploadResultDone"
|
||||
@uploadResultError="uploadResultError"
|
||||
></UploadResult>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -124,7 +126,7 @@ export default {
|
||||
handleCancel() {
|
||||
if (!this.isUploading) {
|
||||
this.showCiType(null)
|
||||
this.$refs.ciTypeChoice.selectNum = null
|
||||
this.$refs.ciTypeChoice.selectNum = undefined
|
||||
this.hasError = false
|
||||
} else {
|
||||
this.$message.warning(this.$t('cmdb.batch.batchUploadCanceled'))
|
||||
@@ -144,16 +146,29 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
@import '../index.less';
|
||||
.cmdb-batch-upload-label {
|
||||
color: @text-color_1;
|
||||
font-weight: bold;
|
||||
white-space: pre;
|
||||
> span {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-batch-upload {
|
||||
margin-bottom: -24px;
|
||||
padding: 24px;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 20px;
|
||||
border-radius: @border-radius-box;
|
||||
overflow: auto;
|
||||
.cmdb-batch-upload-action {
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
margin: 12px 0;
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<a-space>
|
||||
<span>{{ $t('cmdb.ciType.ciType') }}: </span>
|
||||
<div>
|
||||
<p class="cmdb-batch-upload-label"><span>*</span>1. {{ $t('cmdb.batch.selectCIType') }}</p>
|
||||
<a-select
|
||||
showSearch
|
||||
:placeholder="$t('cmdb.batch.selectCITypeTips')"
|
||||
@change="selectCiType"
|
||||
:style="{ width: '300px' }"
|
||||
:style="{ width: '50%', marginBottom: '1em' }"
|
||||
class="ops-select"
|
||||
:filter-option="filterOption"
|
||||
v-model="selectNum"
|
||||
@@ -14,13 +14,16 @@
|
||||
ciType.alias
|
||||
}}</a-select-option>
|
||||
</a-select>
|
||||
<p class="cmdb-batch-upload-label"> 2. {{ $t('cmdb.batch.downloadTemplate') }}</p>
|
||||
<a-button
|
||||
:style="{ marginBottom: '1em' }"
|
||||
@click="openModal"
|
||||
:disabled="!selectNum"
|
||||
type="primary"
|
||||
class="ops-button-primary"
|
||||
ghost
|
||||
class="ops-button-ghost"
|
||||
icon="download"
|
||||
>{{ $t('cmdb.batch.downloadTemplate') }}</a-button
|
||||
>{{ $t('cmdb.batch.clickDownload') }}</a-button
|
||||
>
|
||||
<a-modal
|
||||
:bodyStyle="{ paddingTop: 0 }"
|
||||
@@ -88,7 +91,7 @@
|
||||
</a-row>
|
||||
</template>
|
||||
</a-modal>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -107,7 +110,7 @@ export default {
|
||||
return {
|
||||
ciTypeList: [],
|
||||
ciTypeName: '',
|
||||
selectNum: null,
|
||||
selectNum: undefined,
|
||||
selectCiTypeAttrList: [],
|
||||
visible: false,
|
||||
checkedAttrs: [],
|
||||
@@ -238,6 +241,7 @@ export default {
|
||||
for (let row = 2; row < 5000; row++) {
|
||||
Object.keys(choice_value_obj).forEach((key) => {
|
||||
const formulae = `"${choice_value_obj[key].choice_value.map((value) => value[0]).join(',')}"`
|
||||
console.log(formulae)
|
||||
if (formulae.length <= 255) {
|
||||
ws.getCell(row, choice_value_obj[key].columnIdx).dataValidation = {
|
||||
type: 'list',
|
||||
|
@@ -1,13 +1,14 @@
|
||||
<template>
|
||||
<div class="cmdb-batch-upload-table">
|
||||
<vxe-table
|
||||
v-if="uploadData && uploadData.length"
|
||||
ref="xTable"
|
||||
stripe
|
||||
show-header-overflow
|
||||
show-overflow=""
|
||||
size="small"
|
||||
class="ops-stripe-table"
|
||||
:max-height="200"
|
||||
height="auto"
|
||||
:data="dataSource"
|
||||
resizable
|
||||
:row-style="rowStyle"
|
||||
@@ -21,6 +22,19 @@
|
||||
:min-width="100"
|
||||
></vxe-column>
|
||||
</vxe-table>
|
||||
<a-empty
|
||||
v-else
|
||||
:image-style="{
|
||||
height: '80px',
|
||||
marginTop: '10px',
|
||||
}"
|
||||
>
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<template slot="description">
|
||||
<p>{{ $t('noData') }}</p>
|
||||
<p>{{ $t('cmdb.batch.pleaseUploadFile') }}</p>
|
||||
</template>
|
||||
</a-empty>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -99,7 +113,21 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-batch-upload-table {
|
||||
overflow: auto;
|
||||
height: 200px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
|
||||
linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
|
||||
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y,
|
||||
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y;
|
||||
background-size: 15px 1px, 15px 1px, 1px 15px, 1px 15px;
|
||||
background-position: 0 0, 0 100%, 0 0, 100% 0;
|
||||
.ant-empty-description {
|
||||
p:last-child {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -9,13 +9,21 @@
|
||||
:fileList="fileList"
|
||||
:disabled="!ciType || isUploading"
|
||||
>
|
||||
<img :style="{ width: '80px', height: '80px' }" src="@/assets/file_upload.png" />
|
||||
<p class="ant-upload-text">{{ $t('cmdb.batch.drawTips') }}</p>
|
||||
<ops-icon type="itsm-folder" />
|
||||
<p class="ant-upload-hint">{{ $t('cmdb.batch.supportFileTypes') }}</p>
|
||||
<p v-html="$t('cmdb.batch.drawTips1')"></p>
|
||||
<p v-html="$t('cmdb.batch.drawTips2')"></p>
|
||||
<div v-for="item in fileList" :key="item.name" class="cmdb-batch-upload-dragger-file">
|
||||
<span><a-icon type="file" :style="{ color: '#2F54EB', marginRight: '5px' }" />{{ item.name }}</span>
|
||||
<a-progress :status="progressStatus" :percent="percent" />
|
||||
</div>
|
||||
</a-upload-dragger>
|
||||
<div v-for="item in fileList" :key="item.name" class="cmdb-batch-upload-dragger-file">
|
||||
<span><a-icon type="file" :style="{ color: '#2F54EB', marginRight: '5px' }" />{{ item.name }}</span>
|
||||
<a-progress :status="progressStatus" :percent="percent" />
|
||||
<div class="cmdb-batch-upload-tips">
|
||||
<p>{{ $t('cmdb.batch.tips1') }}</p>
|
||||
<div>{{ $t('cmdb.batch.tips2') }}</div>
|
||||
<div>{{ $t('cmdb.batch.tips3') }}</div>
|
||||
<div>{{ $t('cmdb.batch.tips4') }}</div>
|
||||
<div>{{ $t('cmdb.batch.tips5') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -46,15 +54,13 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
ciType: {
|
||||
handler(newValue) {
|
||||
if (!newValue) {
|
||||
this.ciItemNum = 0
|
||||
this.fileList = []
|
||||
this.dataList = []
|
||||
this.progressStatus = 'active'
|
||||
this.percent = 0
|
||||
this.$emit('uploadDone', this.dataList)
|
||||
}
|
||||
handler() {
|
||||
this.ciItemNum = 0
|
||||
this.fileList = []
|
||||
this.dataList = []
|
||||
this.progressStatus = 'active'
|
||||
this.percent = 0
|
||||
this.$emit('uploadDone', this.dataList)
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -77,12 +83,28 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-batch-upload-dragger {
|
||||
height: 220px;
|
||||
height: auto;
|
||||
margin: 16px 0;
|
||||
.ant-upload p {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.ant-upload.ant-upload-drag {
|
||||
background: rgba(240, 245, 255, 0.35);
|
||||
border: none;
|
||||
background: linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
|
||||
linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
|
||||
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y,
|
||||
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y;
|
||||
background-size: 15px 1px, 15px 1px, 1px 15px, 1px 15px;
|
||||
background-position: 0 0, 0 100%, 0 0, 100% 0;
|
||||
.ant-upload-drag-container > i {
|
||||
font-size: 60px;
|
||||
}
|
||||
.cmdb-batch-upload-tips {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
.ant-upload.ant-upload-drag .ant-upload-drag-container {
|
||||
vertical-align: baseline;
|
||||
@@ -90,23 +112,37 @@ export default {
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-batch-upload-dragger {
|
||||
position: relative;
|
||||
display: flex;
|
||||
> span {
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
}
|
||||
.cmdb-batch-upload-dragger-file {
|
||||
background-color: #fff;
|
||||
box-shadow: 0px 2px 5px rgba(78, 94, 160, 0.2);
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
background-color: @primary-color_7;
|
||||
border-radius: 2px;
|
||||
width: 80%;
|
||||
left: 50%;
|
||||
bottom: 24px;
|
||||
padding: 2px 8px;
|
||||
transform: translate(-50%);
|
||||
display: inline-flex;
|
||||
> span {
|
||||
white-space: nowrap;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
.cmdb-batch-upload-tips {
|
||||
width: 50%;
|
||||
padding-left: 20px;
|
||||
color: @text-color_3;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
p:first-child {
|
||||
color: @text-color_1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<div class="cmdb-batch-upload-result" v-if="visible">
|
||||
<h3 class="cmdb-batch-upload-result-title">{{ $t('cmdb.batch.uploadResult') }}</h3>
|
||||
<p class="cmdb-batch-upload-label">5. {{ $t('cmdb.batch.uploadResult') }}</p>
|
||||
<div class="cmdb-batch-upload-result-content">
|
||||
<h4>
|
||||
{{ $t('cmdb.batch.total') }} <span style="color: blue">{{ total }}</span> {{ $t('cmdb.batch.successItems') }}
|
||||
<span style="color: lightgreen">{{ success }}</span> {{ $t('cmdb.batch.failedItems') }} <span style="color: red">{{ errorNum }} </span>{{ $t('cmdb.batch.items') }}
|
||||
{{ $t('cmdb.batch.total') }} <span style="color: blue">{{ total }}</span>
|
||||
{{ $t('cmdb.batch.successItems') }} <span style="color: lightgreen">{{ success }}</span>
|
||||
{{ $t('cmdb.batch.failedItems') }} <span style="color: red">{{ errorNum }} </span>{{ $t('cmdb.batch.items') }}
|
||||
</h4>
|
||||
<div>
|
||||
<span>{{ $t('cmdb.batch.errorTips') }}: </span>
|
||||
@@ -39,7 +40,7 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data: function() {
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
complete: 0,
|
||||
@@ -54,6 +55,13 @@ export default {
|
||||
return this.upLoadData.length || 0
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
ciType: {
|
||||
handler() {
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async upload2Server() {
|
||||
this.visible = true
|
||||
@@ -96,10 +104,6 @@ export default {
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-batch-upload-result {
|
||||
.cmdb-batch-upload-result-title {
|
||||
border-left: 4px solid #custom_colors[color_1];
|
||||
padding-left: 10px;
|
||||
}
|
||||
.cmdb-batch-upload-result-content {
|
||||
background-color: rgba(240, 245, 255, 0.35);
|
||||
border-radius: 5px;
|
||||
|
@@ -15,21 +15,35 @@
|
||||
</span>
|
||||
</span>
|
||||
<a-space>
|
||||
<a-button size="small" icon="plus" type="primary" @click="$refs.create.handleOpen(true, 'create')">
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ops-button-ghost"
|
||||
ghost
|
||||
@click="$refs.create.handleOpen(true, 'create')"
|
||||
><ops-icon type="veops-increase" />
|
||||
{{ $t('create') }}
|
||||
</a-button>
|
||||
<a-button size="small" icon="user-add" type="primary" ghost @click="handlePerm">{{ $t('grant') }}</a-button>
|
||||
<a-popconfirm
|
||||
:title="
|
||||
$t('cmdb.preference.confirmcancelSub2', { name: `${this.$route.meta.title || this.$route.meta.name}` })
|
||||
"
|
||||
:ok-text="$t('confirm')"
|
||||
:cancel-text="$t('cancel')"
|
||||
@confirm="unsubscribe"
|
||||
placement="bottomRight"
|
||||
>
|
||||
<a-button size="small" icon="star" type="primary" ghost>{{ $t('cmdb.preference.cancelSub') }}</a-button>
|
||||
</a-popconfirm>
|
||||
<EditAttrsPopover :typeId="typeId" class="operation-icon" @refresh="refreshAfterEditAttrs">
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
class="ops-button-ghost"
|
||||
><ops-icon type="veops-configuration_table" />{{ $t('cmdb.configTable') }}</a-button
|
||||
>
|
||||
</EditAttrsPopover>
|
||||
<a-dropdown v-model="visible">
|
||||
<a-button type="primary" ghost class="ops-button-ghost">···</a-button>
|
||||
<a-menu slot="overlay" @click="handleMenuClick">
|
||||
<a-menu-item @click="handlePerm" key="grant">
|
||||
<a-icon type="user-add" />
|
||||
{{ $t('grant') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="cancelSub" @click="unsubscribe">
|
||||
<a-icon type="star" />
|
||||
{{ $t('cmdb.preference.cancelSub') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</div>
|
||||
<div class="cmdb-ci-main">
|
||||
@@ -86,7 +100,6 @@
|
||||
:scroll-y="{ enabled: true, gt: 20 }"
|
||||
:scroll-x="{ enabled: true, gt: 0 }"
|
||||
class="ops-unstripe-table"
|
||||
:style="{ margin: '0 -12px' }"
|
||||
:custom-config="{ storage: true }"
|
||||
>
|
||||
<vxe-column align="center" type="checkbox" width="60" :fixed="isCheckboxFixed ? 'left' : ''"></vxe-column>
|
||||
@@ -219,11 +232,9 @@
|
||||
</template>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-column align="left" field="operate" fixed="right" width="120">
|
||||
<vxe-column align="left" field="operate" fixed="right" width="80">
|
||||
<template #header>
|
||||
<span>{{ $t('operation') }}</span>
|
||||
<EditAttrsPopover :typeId="typeId" class="operation-icon" @refresh="refreshAfterEditAttrs" />
|
||||
<!-- <a-icon class="operation-icon" type="control" /> -->
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<a-space>
|
||||
@@ -342,7 +353,7 @@ export default {
|
||||
// if (this.selectedRowKeys && this.selectedRowKeys.length) {
|
||||
// return this.windowHeight - 246
|
||||
// }
|
||||
return this.windowHeight - 210
|
||||
return this.windowHeight - 240
|
||||
},
|
||||
},
|
||||
data() {
|
||||
@@ -377,6 +388,7 @@ export default {
|
||||
passwordValue: {},
|
||||
lastEditCiId: null,
|
||||
isContinueCloseEdit: true,
|
||||
visible: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -916,15 +928,24 @@ export default {
|
||||
})
|
||||
},
|
||||
unsubscribe(ciType, type = 'all') {
|
||||
const promises = [subscribeCIType(this.typeId, ''), subscribeTreeView(this.typeId, '')]
|
||||
Promise.all(promises).then(() => {
|
||||
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
|
||||
if (Number(ciType) === Number(lastTypeId)) {
|
||||
localStorage.setItem('ops_ci_typeid', '')
|
||||
}
|
||||
this.$message.success(this.$t('cmdb.preference.cancelSubSuccess'))
|
||||
this.resetRoute()
|
||||
this.$router.push('/cmdb/preference')
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: that.$t('warning'),
|
||||
content: that.$t('cmdb.preference.confirmcancelSub2', {
|
||||
name: `${that.$route.meta.title || that.$route.meta.name}`,
|
||||
}),
|
||||
onOk() {
|
||||
const promises = [subscribeCIType(that.typeId, ''), subscribeTreeView(that.typeId, '')]
|
||||
Promise.all(promises).then(() => {
|
||||
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
|
||||
if (Number(ciType) === Number(lastTypeId)) {
|
||||
localStorage.setItem('ops_ci_typeid', '')
|
||||
}
|
||||
that.$message.success(that.$t('cmdb.preference.cancelSubSuccess'))
|
||||
that.resetRoute()
|
||||
that.$router.push('/cmdb/preference')
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
resetRoute() {
|
||||
@@ -957,6 +978,11 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
handleMenuClick(e) {
|
||||
if (e.key === 'grant') {
|
||||
this.visible = false
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -968,11 +994,11 @@ export default {
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.cmdb-ci {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: @border-radius-box;
|
||||
height: calc(100vh - 64px);
|
||||
overflow: auto;
|
||||
margin-bottom: -24px;
|
||||
.cmdb-ci-main {
|
||||
background-color: #fff;
|
||||
border-radius: 15px;
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -106,9 +106,9 @@
|
||||
<a-date-picker
|
||||
v-decorator="[list.name, { rules: [{ required: false }] }]"
|
||||
style="width: 100%"
|
||||
:format="getFieldType(list.name) == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
|
||||
v-if="getFieldType(list.name) === 'date' || getFieldType(list.name) === 'datetime'"
|
||||
:showTime="getFieldType(list.name) === 'date' ? false : { format: 'HH:mm:ss' }"
|
||||
:format="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
|
||||
v-if="getFieldType(list.name) === '4' || getFieldType(list.name) === '3'"
|
||||
:showTime="getFieldType(list.name) === '4' ? false : { format: 'HH:mm:ss' }"
|
||||
/>
|
||||
<a-input
|
||||
v-if="getFieldType(list.name) === 'input'"
|
||||
@@ -373,7 +373,7 @@ export default {
|
||||
} else if (_find.value_type === '0' || _find.value_type === '1') {
|
||||
return 'input_number'
|
||||
} else if (_find.value_type === '4' || _find.value_type === '3') {
|
||||
return this.valueTypeMap[_find.value_type]
|
||||
return _find.value_type
|
||||
} else {
|
||||
return 'input'
|
||||
}
|
||||
|
@@ -11,9 +11,11 @@
|
||||
@setFixedList="setFixedList"
|
||||
/>
|
||||
</template>
|
||||
<div :style="{ height: '100%', width: '30px', float: 'right', borderLeft: '1px solid #e8eaec' }">
|
||||
<a-icon :style="{ margin: '13px 0 0 10px ' }" type="control" />
|
||||
</div>
|
||||
<slot>
|
||||
<div :style="{ height: '100%', width: '30px', float: 'right', borderLeft: '1px solid #e8eaec' }">
|
||||
<a-icon :style="{ margin: '13px 0 0 10px ' }" type="control" />
|
||||
</div>
|
||||
</slot>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="attr-ad" :style="{ height: `${windowHeight - 104}px` }">
|
||||
<div class="attr-ad" :style="{ height: `${windowHeight - 130}px` }">
|
||||
<div v-if="adCITypeList && adCITypeList.length">
|
||||
<a-tabs size="small" v-model="currentTab">
|
||||
<a-tab-pane v-for="item in adCITypeList" :key="item.id">
|
||||
@@ -207,7 +207,7 @@ export default {
|
||||
@import '~@/style/static.less';
|
||||
.attr-ad {
|
||||
position: relative;
|
||||
padding: 0 12px;
|
||||
padding: 0 20px;
|
||||
.attr-ad-header {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :style="{ height: `${windowHeight - 156}px`, overflow: 'auto', position: 'relative' }">
|
||||
<div :style="{ height: `${windowHeight - 187}px`, overflow: 'auto', position: 'relative' }">
|
||||
<a
|
||||
v-if="!adrIsInner"
|
||||
:style="{ position: 'absolute', right: 0, top: 0 }"
|
||||
|
@@ -1,71 +1,83 @@
|
||||
<template>
|
||||
<div :class="{ 'attribute-card': true, 'attribute-card-inherited': inherited }">
|
||||
<a-tooltip :title="inherited ? $t('cmdb.ciType.inheritFrom', { name: property.inherited_from }) : ''">
|
||||
<div class="attribute-card-content">
|
||||
<div
|
||||
:class="{ 'attribute-card-value-type-icon': true, handle: !inherited }"
|
||||
:style="{ ...getPropertyStyle(property) }"
|
||||
>
|
||||
<ValueTypeIcon :attr="property" />
|
||||
</div>
|
||||
<div :class="{ 'attribute-card-content-inner': true, 'attribute-card-name-required': property.is_required }">
|
||||
<div :class="{ 'attribute-card-name': true, 'attribute-card-name-default-show': property.default_show }">
|
||||
{{ property.alias || property.name }}
|
||||
<div
|
||||
@click="
|
||||
() => {
|
||||
if (isAdd) {
|
||||
$emit('add')
|
||||
}
|
||||
}
|
||||
"
|
||||
:class="{ 'attribute-card': true, 'attribute-card-add': isAdd, 'attribute-card-inherited': inherited }"
|
||||
>
|
||||
<div class="attribute-card-uniqueKey" v-if="isUnique">{{ $t('cmdb.ciType.uniqueKey') }}</div>
|
||||
<template v-if="!isAdd">
|
||||
<a-tooltip :title="inherited ? $t('cmdb.ciType.inheritFrom', { name: property.inherited_from }) : ''">
|
||||
<div class="attribute-card-content">
|
||||
<div :class="{ 'attribute-card-value-type-icon': true, handle: !inherited }">
|
||||
<ValueTypeIcon :attr="property" />
|
||||
</div>
|
||||
<div :class="{ 'attribute-card-content-inner': true, 'attribute-card-name-required': property.is_required }">
|
||||
<div :class="{ 'attribute-card-name': true, 'attribute-card-name-default-show': property.default_show }">
|
||||
{{ property.alias || property.name }}
|
||||
</div>
|
||||
<div v-if="property.is_password" class="attribute-card_value-type">{{ $t('cmdb.ciType.password') }}</div>
|
||||
<div v-else-if="property.is_link" class="attribute-card_value-type">{{ $t('cmdb.ciType.link') }}</div>
|
||||
<div v-else class="attribute-card_value-type">{{ valueTypeMap[property.value_type] }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="attribute-card-trigger"
|
||||
v-if="(property.value_type === '3' || property.value_type === '4') && !isStore"
|
||||
>
|
||||
<a @click="openTrigger"><ops-icon type="ops-trigger"/></a>
|
||||
</div>
|
||||
<div v-if="property.is_password" class="attribute-card_value-type">{{ $t('cmdb.ciType.password') }}</div>
|
||||
<div v-else-if="property.is_link" class="attribute-card_value-type">{{ $t('cmdb.ciType.link') }}</div>
|
||||
<div v-else class="attribute-card_value-type">{{ valueTypeMap[property.value_type] }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="attribute-card-trigger"
|
||||
v-if="(property.value_type === '3' || property.value_type === '4') && !isStore"
|
||||
</a-tooltip>
|
||||
|
||||
<div class="attribute-card-footer">
|
||||
<a-popover
|
||||
trigger="click"
|
||||
:arrowPointAtCenter="true"
|
||||
placement="bottom"
|
||||
overlayClassName="attribute-card-footer-popover"
|
||||
>
|
||||
<a @click="openTrigger"><ops-icon type="ops-trigger"/></a>
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<div slot="content">
|
||||
<h3 :style="{ textAlign: 'center', paddingTop: '0.5em' }">
|
||||
<span>{{ property.alias }}({{ property.name }})</span>
|
||||
</h3>
|
||||
<a-descriptions layout="horizontal" bordered size="small" :column="2">
|
||||
<a-descriptions-item v-for="item in propertyList" :key="item.property" :label="item.label">
|
||||
<ops-icon
|
||||
:style="{ color: property[item.property] ? '#7f97fa' : '', fontSize: '10px' }"
|
||||
:type="`ops-${item.property}-disabled`"
|
||||
/>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label></a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
<a-space :style="{ cursor: 'pointer' }">
|
||||
<ops-icon
|
||||
v-for="item in propertyList.filter((p) => property[p.property])"
|
||||
:key="item.property"
|
||||
:style="{ color: '#7f97fa', fontSize: '10px' }"
|
||||
:type="`ops-${item.property}-disabled`"
|
||||
/>
|
||||
</a-space>
|
||||
</a-popover>
|
||||
|
||||
<div class="attribute-card-footer">
|
||||
<a-popover
|
||||
trigger="click"
|
||||
:arrowPointAtCenter="true"
|
||||
placement="bottom"
|
||||
overlayClassName="attribute-card-footer-popover"
|
||||
>
|
||||
<div slot="content">
|
||||
<h3 :style="{ textAlign: 'center', paddingTop: '0.5em' }">
|
||||
<span>{{ property.alias }}({{ property.name }})</span>
|
||||
</h3>
|
||||
<a-descriptions layout="horizontal" bordered size="small" :column="2">
|
||||
<a-descriptions-item v-for="item in propertyList" :key="item.property" :label="item.label">
|
||||
<components
|
||||
:is="`ops_${item.property}`"
|
||||
v-if="property[item.property]"
|
||||
:style="{ width: '1em', height: '1em' }"
|
||||
/>
|
||||
<ops-icon v-else :type="`ops-${item.property}-disabled`" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label></a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
<a-space :style="{ cursor: 'pointer' }">
|
||||
<components
|
||||
v-for="item in propertyList.filter((p) => property[p.property])"
|
||||
:key="item.property"
|
||||
:is="`ops_${item.property}`"
|
||||
/>
|
||||
<a-space class="attribute-card-operation" v-if="!inherited">
|
||||
<a v-if="!isStore"><a-icon type="edit" @click="handleEdit"/></a>
|
||||
<a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')">
|
||||
<a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a>
|
||||
</a-tooltip>
|
||||
<a v-if="!isUnique" style="color:red;"><a-icon type="delete" @click="handleDelete"/></a>
|
||||
</a-space>
|
||||
</a-popover>
|
||||
|
||||
<a-space class="attribute-card-operation" v-if="!inherited">
|
||||
<a v-if="!isStore"><a-icon type="edit" @click="handleEdit"/></a>
|
||||
<a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')">
|
||||
<a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a>
|
||||
</a-tooltip>
|
||||
<a style="color:red;"><a-icon type="delete" @click="handleDelete"/></a>
|
||||
</a-space>
|
||||
</div>
|
||||
<TriggerForm ref="triggerForm" :CITypeId="CITypeId" />
|
||||
</div>
|
||||
<TriggerForm ref="triggerForm" :CITypeId="CITypeId" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<a><a-icon type="plus"/></a>
|
||||
<div>{{ $t('cmdb.ciType.addAttribute') }}</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -82,10 +94,15 @@ import {
|
||||
ops_is_unique,
|
||||
} from '@/core/icons'
|
||||
import { valueTypeMap } from '../../utils/const'
|
||||
import { getPropertyStyle } from '../../utils/helper'
|
||||
import TriggerForm from './triggerForm.vue'
|
||||
export default {
|
||||
name: 'AttributeCard',
|
||||
inject: {
|
||||
unique: {
|
||||
from: 'unique',
|
||||
default: () => undefined,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
ValueTypeIcon,
|
||||
TriggerForm,
|
||||
@@ -114,11 +131,21 @@ export default {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
isAdd: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
isUnique() {
|
||||
if (this.unique) {
|
||||
return this.property?.name === this.unique()
|
||||
}
|
||||
return false
|
||||
},
|
||||
valueTypeMap() {
|
||||
return valueTypeMap()
|
||||
},
|
||||
@@ -147,11 +174,10 @@ export default {
|
||||
]
|
||||
},
|
||||
inherited() {
|
||||
return this.property.inherited || false
|
||||
return this.property?.inherited || false
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getPropertyStyle,
|
||||
handleEdit() {
|
||||
this.$emit('edit')
|
||||
},
|
||||
@@ -196,11 +222,12 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.attribute-card {
|
||||
width: 182px;
|
||||
height: 80px;
|
||||
background: #f8faff;
|
||||
border-radius: 5px;
|
||||
background: @primary-color_6;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
transition: all 0.3s;
|
||||
@@ -219,7 +246,7 @@ export default {
|
||||
.attribute-card-value-type-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 12px;
|
||||
font-size: 16px;
|
||||
background: #ffffff !important;
|
||||
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
|
||||
border-radius: 2px;
|
||||
@@ -243,7 +270,7 @@ export default {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.attribute-card-name-default-show {
|
||||
color: #2f54eb;
|
||||
color: @primary-color;
|
||||
}
|
||||
.attribute-card_value-type {
|
||||
font-size: 10px;
|
||||
@@ -270,20 +297,67 @@ export default {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(180deg, #96abd6 0%, #ecf2ff 0.01%, #ffffff 143.33%);
|
||||
border-radius: 0px 0px 5px 5px;
|
||||
background: @primary-color_5;
|
||||
border-radius: 0px 0px 2px 2px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-top: 1px solid @primary-color_3;
|
||||
.attribute-card-operation {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
.attribute-card-uniqueKey {
|
||||
position: absolute;
|
||||
right: -12px;
|
||||
top: 0;
|
||||
color: @func-color_2;
|
||||
background: url('../../assets/unique_card.png');
|
||||
font-size: 10px;
|
||||
z-index: 1;
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
min-width: 55px;
|
||||
padding: 2px 0 2px 5px;
|
||||
}
|
||||
}
|
||||
.attribute-card-inherited {
|
||||
background: #f3f4f7;
|
||||
background: @primary-color_7;
|
||||
.attribute-card-footer {
|
||||
background: #eaedf3;
|
||||
background: @text-color_7;
|
||||
}
|
||||
}
|
||||
|
||||
.attribute-card-add {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
background-color: inherit;
|
||||
&:hover {
|
||||
box-shadow: none;
|
||||
background-color: @primary-color_6;
|
||||
}
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
background: linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
|
||||
linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
|
||||
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y,
|
||||
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y;
|
||||
background-size: 15px 1px, 15px 1px, 1px 15px, 1px 15px;
|
||||
background-position: 0 0, 0 100%, 0 0, 100% 0;
|
||||
}
|
||||
div {
|
||||
color: @text-color_4;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -172,8 +172,6 @@ export default {
|
||||
margin-right: 60px;
|
||||
.ant-input-group.ant-input-group-compact > *:first-child,
|
||||
.ant-input-group.ant-input-group-compact > .ant-select:first-child > .ant-select-selection {
|
||||
border-top-left-radius: 20px !important;
|
||||
border-bottom-left-radius: 20px !important;
|
||||
background-color: #custom_colors[color_1];
|
||||
color: #fff;
|
||||
border: none;
|
||||
@@ -202,7 +200,6 @@ export default {
|
||||
.ant-input {
|
||||
background-color: #f0f5ff;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
@@ -12,14 +12,28 @@
|
||||
</a-form-item>
|
||||
</span>
|
||||
</a-modal>
|
||||
<div class="ci-types-attributes" :style="{ maxHeight: `${windowHeight - 104}px` }">
|
||||
<div class="ci-types-attributes" :style="{ height: `${windowHeight - 130}px` }">
|
||||
<a-space style="margin-bottom: 10px">
|
||||
<a-button type="primary" @click="handleAddGroup" size="small" class="ops-button-primary" icon="plus">{{
|
||||
$t('cmdb.ciType.group')
|
||||
}}</a-button>
|
||||
<a-button type="primary" @click="handleOpenUniqueConstraint" size="small" class="ops-button-primary">{{
|
||||
$t('cmdb.ciType.uniqueConstraint')
|
||||
}}</a-button>
|
||||
<a-button @click="handleAddGroup" size="small" icon="plus">{{ $t('cmdb.ciType.group') }}</a-button>
|
||||
<a-button @click="handleOpenUniqueConstraint" size="small">{{ $t('cmdb.ciType.uniqueConstraint') }}</a-button>
|
||||
<div>
|
||||
<a-tooltip
|
||||
v-for="type in Object.keys(valueTypeMap)"
|
||||
:key="type"
|
||||
:title="$t('cmdb.ciType.filterTips', { name: valueTypeMap[type] })"
|
||||
>
|
||||
<span
|
||||
@click="handleFilterType(type)"
|
||||
:class="{
|
||||
'ci-types-attributes-filter': true,
|
||||
'ci-types-attributes-filter-selected': attrTypeFilter.includes(type),
|
||||
}"
|
||||
>
|
||||
<ops-icon :type="getPropertyIcon({ value_type: type })" />
|
||||
{{ valueTypeMap[type] }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-space>
|
||||
<div :key="CITypeGroup.id" v-for="(CITypeGroup, index) in CITypeGroups">
|
||||
<div>
|
||||
@@ -29,13 +43,6 @@
|
||||
>
|
||||
<span style="font-weight:700">{{ CITypeGroup.name }}</span>
|
||||
<span style="color: #c3cdd7;margin:0 5px;">({{ CITypeGroup.attributes.length }})</span>
|
||||
|
||||
<a v-if="!CITypeGroup.inherited" @click="handleEditGroupName(index, CITypeGroup)">
|
||||
<a-icon type="edit" />
|
||||
</a>
|
||||
<a v-else :style="{ cursor: 'not-allowed', color: 'gray' }">
|
||||
<a-icon type="edit" />
|
||||
</a>
|
||||
</div>
|
||||
<template v-else>
|
||||
<span>
|
||||
@@ -64,21 +71,24 @@
|
||||
@click="handleMoveGroup(index, index + 1)"
|
||||
/></a>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ $t('cmdb.ciType.selectAttribute') }}</template>
|
||||
<a><a-icon type="plus" @click="handleAddGroupAttr(index)"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ $t('cmdb.ciType.deleteGroup') }}</template>
|
||||
<a
|
||||
:style="{ color: CITypeGroup.inherited ? 'gray' : 'red' }"
|
||||
:disabled="CITypeGroup.inherited"
|
||||
><a-icon
|
||||
type="delete"
|
||||
@click="handleDeleteGroup(CITypeGroup)"
|
||||
/></a>
|
||||
</a-tooltip>
|
||||
<a-dropdown>
|
||||
<a><ops-icon type="veops-more"/></a>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item @click="handleAddGroupAttr(index)">
|
||||
<template slot="title"></template>
|
||||
<a-icon type="plus" />
|
||||
{{ $t('cmdb.ciType.addAttribute') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="handleEditGroupName(index, CITypeGroup)" :disabled="CITypeGroup.inherited">
|
||||
<a-icon type="edit" />
|
||||
{{ $t('cmdb.ciType.editGroupName') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="handleDeleteGroup(CITypeGroup)" :disabled="CITypeGroup.inherited">
|
||||
<a-icon type="delete" />
|
||||
{{ $t('cmdb.ciType.deleteGroup') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</div>
|
||||
<div class="ci-types-attributes-wrapper">
|
||||
@@ -98,7 +108,7 @@
|
||||
handle=".handle"
|
||||
>
|
||||
<AttributeCard
|
||||
v-for="item in CITypeGroup.attributes"
|
||||
v-for="item in filterValueType(CITypeGroup.attributes)"
|
||||
:key="item.id"
|
||||
@edit="handleEditProperty(item)"
|
||||
:property="item"
|
||||
@@ -106,6 +116,7 @@
|
||||
:CITypeId="CITypeId"
|
||||
:attributes="attributes"
|
||||
/>
|
||||
<AttributeCard isAdd @add="handleAddGroupAttr(index)" />
|
||||
<i></i> <i></i> <i></i> <i></i> <i></i>
|
||||
</draggable>
|
||||
</div>
|
||||
@@ -114,10 +125,11 @@
|
||||
<div :style="{ height: '32px', lineHeight: '32px', display: 'inline-block', fontSize: '14px' }">
|
||||
<span style="font-weight:700">{{ $t('other') }}</span>
|
||||
<span style="color: #c3cdd7;margin-left:5px;">({{ otherGroupAttributes.length }})</span>
|
||||
<span style="color: #c3cdd7;margin-left:5px;font-size:10px;">{{ $t('cmdb.ciType.otherGroupTips') }}</span>
|
||||
</div>
|
||||
<div style="float: right">
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ $t('cmdb.ciType.selectAttribute') }}</template>
|
||||
<template slot="title">{{ $t('cmdb.ciType.addAttribute') }}</template>
|
||||
<a @click="handleAddGroupAttr(undefined)"><a-icon type="plus"/></a>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
@@ -138,7 +150,7 @@
|
||||
handle=".handle"
|
||||
>
|
||||
<AttributeCard
|
||||
v-for="item in otherGroupAttributes"
|
||||
v-for="item in filterValueType(otherGroupAttributes)"
|
||||
:key="item.id"
|
||||
@edit="handleEditProperty(item)"
|
||||
:property="item"
|
||||
@@ -146,6 +158,7 @@
|
||||
:CITypeId="CITypeId"
|
||||
:attributes="attributes"
|
||||
/>
|
||||
<AttributeCard isAdd @add="handleAddGroupAttr(undefined)" />
|
||||
<i></i> <i></i> <i></i> <i></i> <i></i>
|
||||
</draggable>
|
||||
</div>
|
||||
@@ -185,6 +198,8 @@ import AttributeCard from './attributeCard.vue'
|
||||
import AttributeEditForm from './attributeEditForm.vue'
|
||||
import NewCiTypeAttrModal from './newCiTypeAttrModal.vue'
|
||||
import UniqueConstraint from './uniqueConstraint.vue'
|
||||
import { valueTypeMap } from '../../utils/const'
|
||||
import { getPropertyIcon } from '../../utils/helper'
|
||||
|
||||
export default {
|
||||
name: 'AttributesTable',
|
||||
@@ -214,6 +229,8 @@ export default {
|
||||
otherGroupAttributes: [],
|
||||
addGroupModal: false,
|
||||
newGroupName: '',
|
||||
attrTypeFilter: [],
|
||||
unique: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -223,9 +240,17 @@ export default {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
valueTypeMap() {
|
||||
return valueTypeMap()
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return { refresh: this.getCITypeGroupData }
|
||||
return {
|
||||
refresh: this.getCITypeGroupData,
|
||||
unique: () => {
|
||||
return this.unique
|
||||
},
|
||||
}
|
||||
},
|
||||
beforeCreate() {},
|
||||
created() {},
|
||||
@@ -233,6 +258,7 @@ export default {
|
||||
this.getCITypeGroupData()
|
||||
},
|
||||
methods: {
|
||||
getPropertyIcon,
|
||||
handleEditProperty(property) {
|
||||
this.$refs.attributeEditForm.handleEdit(property, this.attributes)
|
||||
},
|
||||
@@ -260,6 +286,7 @@ export default {
|
||||
group.editable = false
|
||||
group.originOrder = group.order
|
||||
group.originName = group.name
|
||||
// group.attributes = group.attributes.sort((a, b) => a.order - b.order)
|
||||
})
|
||||
|
||||
this.otherGroupAttributes = this.attributes
|
||||
@@ -282,6 +309,7 @@ export default {
|
||||
Promise.all(promises).then((values) => {
|
||||
console.log(values)
|
||||
this.attributes = values[0].attributes
|
||||
this.unique = values[0].unique
|
||||
const temp = {}
|
||||
this.attributes.forEach((attr) => {
|
||||
temp[attr.id] = attr
|
||||
@@ -387,6 +415,7 @@ export default {
|
||||
group.attributes = group.attributes.filter((x) => !values.checkedAttributes.includes(x.id))
|
||||
}
|
||||
})
|
||||
// this.CITypeGroups = this.CITypeGroups
|
||||
|
||||
this.otherGroupAttributes.forEach((attributes) => {
|
||||
if (values.groupId === null) {
|
||||
@@ -523,20 +552,66 @@ export default {
|
||||
handleOpenUniqueConstraint() {
|
||||
this.$refs.uniqueConstraint.open(this.attributes)
|
||||
},
|
||||
handleFilterType(type) {
|
||||
const _idx = this.attrTypeFilter.findIndex((item) => item === type)
|
||||
if (_idx > -1) {
|
||||
this.attrTypeFilter.splice(_idx, 1)
|
||||
} else {
|
||||
this.attrTypeFilter.push(type)
|
||||
}
|
||||
},
|
||||
filterValueType(array) {
|
||||
const { attrTypeFilter } = this
|
||||
return array.filter((attr) => {
|
||||
if (!attrTypeFilter.length) {
|
||||
return true
|
||||
} else {
|
||||
if (attrTypeFilter.includes('7') && attr.is_password) {
|
||||
return true
|
||||
}
|
||||
if (attrTypeFilter.includes('8') && attr.is_link) {
|
||||
return true
|
||||
}
|
||||
if (
|
||||
attrTypeFilter.includes(attr.value_type) &&
|
||||
attr.value_type === '2' &&
|
||||
(attr.is_password || attr.is_link)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (attrTypeFilter.includes(attr.value_type)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
watch: {},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.fold {
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ci-types-attributes {
|
||||
padding: 16px 24px 24px;
|
||||
padding: 0 20px;
|
||||
overflow-y: auto;
|
||||
.ci-types-attributes-filter {
|
||||
color: @text-color_4;
|
||||
cursor: pointer;
|
||||
padding: 3px 8px;
|
||||
white-space: nowrap;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.ci-types-attributes-filter:hover,
|
||||
.ci-types-attributes-filter-selected {
|
||||
background-color: @primary-color_5;
|
||||
}
|
||||
.property-item-empty {
|
||||
color: #40a9ff;
|
||||
width: calc(100% - 20px);
|
||||
|
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<a-card :bordered="false" :bodyStyle="{ padding: '0' }">
|
||||
<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="$t('cmdb.ciType.attributes')">
|
||||
<AttributesTable ref="attributesTable" :CITypeId="CITypeId" :CITypeName="CITypeName"></AttributesTable>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane forceRender key="2" :tab="$t('cmdb.ciType.relation')">
|
||||
<a-tab-pane key="2" :tab="$t('cmdb.ciType.relation')">
|
||||
<RelationTable :CITypeId="CITypeId" :CITypeName="CITypeName"></RelationTable>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" :tab="$t('cmdb.ciType.trigger')">
|
||||
@@ -18,6 +18,8 @@
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="6" :tab="$t('cmdb.ciType.grant')">
|
||||
<GrantComp :CITypeId="CITypeId" resourceType="CIType" :resourceTypeName="CITypeName"></GrantComp>
|
||||
<div class="citype-detail-title">{{ $t('cmdb.components.relationGrant') }}</div>
|
||||
<RelationTable isInGrantComp :CITypeId="CITypeId" :CITypeName="CITypeName"></RelationTable>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
@@ -74,4 +76,13 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.citype-detail-title {
|
||||
border-left: 4px solid @primary-color;
|
||||
padding-left: 10px;
|
||||
margin-left: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
|
@@ -8,11 +8,10 @@
|
||||
</div>
|
||||
<SplitPane
|
||||
v-else
|
||||
:min="280"
|
||||
:min="220"
|
||||
:max="500"
|
||||
:paneLengthPixel.sync="paneLengthPixel"
|
||||
appName="cmdb-ci-types"
|
||||
triggerColor="#F0F5FF"
|
||||
:triggerLength="18"
|
||||
>
|
||||
<template #one>
|
||||
@@ -22,22 +21,23 @@
|
||||
:disabled="!permissions.includes('admin') && !permissions.includes('cmdb_admin')"
|
||||
type="primary"
|
||||
size="small"
|
||||
icon="plus"
|
||||
ghost
|
||||
@click="handleClickAddGroup"
|
||||
class="ops-button-primary"
|
||||
>{{ $t('cmdb.ciType.group') }}</a-button
|
||||
class="ops-button-ghost"
|
||||
><ops-icon type="veops-increase" />{{ $t('cmdb.ciType.group') }}</a-button
|
||||
>
|
||||
<a-space>
|
||||
<a
|
||||
<span
|
||||
:style="{ cursor: 'pointer' }"
|
||||
@click="
|
||||
() => {
|
||||
$refs.attributeStore.open()
|
||||
}
|
||||
"
|
||||
>{{ $t('cmdb.ciType.attributeLibray') }}</a
|
||||
>
|
||||
>{{ $t('cmdb.ciType.attributeLibray') }}
|
||||
</span>
|
||||
<a-dropdown v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')">
|
||||
<a><ops-icon type="ops-menu"/></a>
|
||||
<ops-icon type="ops-menu" :style="{ cursor: 'pointer' }" />
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="0">
|
||||
<a-upload
|
||||
@@ -83,7 +83,7 @@
|
||||
<a-space>
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ $t('cmdb.ciType.addCITypeInGroup') }}</template>
|
||||
<a><a-icon type="plus" @click="handleCreate(g)"/></a>
|
||||
<a><ops-icon type="veops-increase" @click="handleCreate(g)"/></a>
|
||||
</a-tooltip>
|
||||
<template v-if="g.id !== -1">
|
||||
<a-tooltip>
|
||||
@@ -113,7 +113,7 @@
|
||||
>
|
||||
<div>
|
||||
<OpsMoveIcon
|
||||
style="width: 17px; height: 17px; display: none; position: absolute; left: 15px; top: 5px"
|
||||
style="width: 17px; height: 17px; display: none; position: absolute; left: -1px; top: 8px"
|
||||
/>
|
||||
<span class="ci-types-left-detail-icon">
|
||||
<template v-if="ci.icon">
|
||||
@@ -134,18 +134,33 @@
|
||||
</span>
|
||||
</div>
|
||||
<span class="ci-types-left-detail-title">{{ ci.alias || ci.name }}</span>
|
||||
<a-space class="ci-types-left-detail-action">
|
||||
<a><a-icon type="user-add" @click="(e) => handlePerm(e, ci)"/></a>
|
||||
<a><a-icon type="edit" @click="(e) => handleEdit(e, ci)"/></a>
|
||||
<a
|
||||
v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"
|
||||
:disabled="ci.inherited"
|
||||
@click="(e) => handleDownloadCiType(e, ci)"
|
||||
>
|
||||
<a-icon type="download" />
|
||||
<a-dropdown :getPopupContainer="(trigger) => trigger">
|
||||
<a class="ci-types-left-detail-action">
|
||||
<ops-icon type="veops-more" />
|
||||
</a>
|
||||
<a style="color: red" @click="(e) => handleDelete(e, ci)"><a-icon type="delete"/></a>
|
||||
</a-space>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item @click="(e) => handlePerm(e, ci)">
|
||||
<a-icon type="user-add" />
|
||||
{{ $t('grant') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="(e) => handleEdit(e, ci)">
|
||||
<a-icon type="edit" />
|
||||
{{ $t('cmdb.ciType.editCIType') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item
|
||||
v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"
|
||||
:disabled="ci.inherited"
|
||||
@click="(e) => handleDownloadCiType(e, ci)"
|
||||
>
|
||||
<a-icon type="download" />
|
||||
{{ $t('cmdb.ciType.downloadType') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="(e) => handleDelete(e, ci)">
|
||||
<a-icon type="delete" />
|
||||
{{ $t('cmdb.ciType.deleteCIType') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
@@ -270,17 +285,7 @@
|
||||
</div>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip :title="$t('cmdb.ciType.uniqueKeyTips')">
|
||||
<a-icon
|
||||
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
theme="filled"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<span>{{ $t('cmdb.ciType.uniqueKey') }}</span>
|
||||
</template>
|
||||
<a-form-item :help="$t('cmdb.ciType.uniqueKeyTips')" :label="$t('cmdb.ciType.uniqueKey')">
|
||||
<el-select
|
||||
size="small"
|
||||
filterable
|
||||
@@ -456,7 +461,6 @@ export default {
|
||||
},
|
||||
currentCName() {
|
||||
if (this.currentId) {
|
||||
console.log(this.currentId)
|
||||
if (this.currentId.split('%')[2] !== 'null') {
|
||||
return this.currentId.split('%')[2]
|
||||
}
|
||||
@@ -817,8 +821,8 @@ export default {
|
||||
})
|
||||
},
|
||||
handleDelete(e, record) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
e.domEvent.preventDefault()
|
||||
e.domEvent.stopPropagation()
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: that.$t('warning'),
|
||||
@@ -833,8 +837,8 @@ export default {
|
||||
})
|
||||
},
|
||||
handleDownloadCiType(e, ci) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
e.domEvent.preventDefault()
|
||||
e.domEvent.stopPropagation()
|
||||
const x = new XMLHttpRequest()
|
||||
x.open('GET', `/api/v0.1/ci_types/${ci.id}/template/export`, true)
|
||||
x.responseType = 'blob'
|
||||
@@ -855,8 +859,8 @@ export default {
|
||||
})
|
||||
},
|
||||
async handleEdit(e, record) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
e.domEvent.preventDefault()
|
||||
e.domEvent.stopPropagation()
|
||||
this.drawerTitle = this.$t('cmdb.ciType.editCIType')
|
||||
this.drawerVisible = true
|
||||
await getCITypeAttributesById(record.id).then((res) => {
|
||||
@@ -909,8 +913,8 @@ export default {
|
||||
}
|
||||
},
|
||||
handlePerm(e, ci) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
e.domEvent.preventDefault()
|
||||
e.domEvent.stopPropagation()
|
||||
roleHasPermissionToGrant({
|
||||
app_id: 'cmdb',
|
||||
resource_type_name: 'CIType',
|
||||
@@ -942,6 +946,8 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.ci-types-wrap {
|
||||
margin: 0 0 -24px 0;
|
||||
.ci-types-empty {
|
||||
@@ -955,21 +961,24 @@ export default {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
float: left;
|
||||
border-top-left-radius: 20px;
|
||||
border-top-right-radius: 20px;
|
||||
|
||||
.ci-types-left-content {
|
||||
max-height: calc(100% - 45px);
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
&:hover {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
.ci-types-left-title {
|
||||
padding: 10px 15px;
|
||||
padding: 10px 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
color: @text-color_3;
|
||||
}
|
||||
.ci-types-left-group {
|
||||
position: relative;
|
||||
padding: 8px 15px;
|
||||
padding: 8px 0 8px 14px;
|
||||
color: rgb(99, 99, 99);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
@@ -982,7 +991,7 @@ export default {
|
||||
display: none;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #e1efff;
|
||||
background-color: @primary-color_3;
|
||||
> div:nth-child(2) {
|
||||
display: inline-flex;
|
||||
}
|
||||
@@ -992,17 +1001,25 @@ export default {
|
||||
}
|
||||
}
|
||||
.ci-types-left-detail {
|
||||
padding: 3px 14px 3px 36px;
|
||||
padding: 3px 14px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
.ci-types-left-detail-action {
|
||||
display: none;
|
||||
margin-left: auto;
|
||||
}
|
||||
.ci-types-left-detail-title {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.ci-types-left-detail-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1019,7 +1036,7 @@ export default {
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
background-color: #e1efff;
|
||||
background-color: @primary-color_3;
|
||||
svg {
|
||||
display: inline !important;
|
||||
}
|
||||
@@ -1029,7 +1046,7 @@ export default {
|
||||
}
|
||||
}
|
||||
.selected {
|
||||
background-color: #e1efff;
|
||||
background-color: @primary-color_3;
|
||||
.ci-types-left-detail-title {
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -1038,6 +1055,7 @@ export default {
|
||||
.ci-types-right {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
.ci-types-right-empty {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
@@ -1049,7 +1067,6 @@ export default {
|
||||
.ci-types-left,
|
||||
.ci-types-right {
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="relation-ad" :style="{ height: `${windowHeight - 104}px` }">
|
||||
<div class="relation-ad" :style="{ height: `${windowHeight - 130}px` }">
|
||||
<div class="relation-ad-item" v-for="item in relationList" :key="item.id">
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
@@ -253,7 +253,7 @@ export default {
|
||||
<style lang="less" scoped>
|
||||
.relation-ad {
|
||||
overflow: auto;
|
||||
padding: 24px;
|
||||
padding: 0 20px;
|
||||
.relation-ad-item {
|
||||
display: inline-flex;
|
||||
justify-content: flex-start;
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div :style="{ padding: '16px 24px 24px' }">
|
||||
<div :style="{ padding: '0 20px 20px' }">
|
||||
<a-button
|
||||
v-if="!isInGrantComp"
|
||||
style="margin-bottom: 10px"
|
||||
@click="handleCreate"
|
||||
type="primary"
|
||||
@@ -14,11 +15,16 @@
|
||||
:data="tableData"
|
||||
size="small"
|
||||
show-overflow
|
||||
show-header-overflow
|
||||
highlight-hover-row
|
||||
keep-source
|
||||
:max-height="windowHeight - 180"
|
||||
:height="windowHeight - 190"
|
||||
class="ops-stripe-table"
|
||||
:row-class-name="rowClass"
|
||||
:edit-config="{ trigger: 'dblclick', mode: 'cell', showIcon: false }"
|
||||
resizable
|
||||
@edit-closed="handleEditClose"
|
||||
@edit-actived="handleEditActived"
|
||||
>
|
||||
<vxe-column field="source_ci_type_name" :title="$t('cmdb.ciType.sourceCIType')"></vxe-column>
|
||||
<vxe-column field="relation_type" :title="$t('cmdb.ciType.relationType')">
|
||||
@@ -39,11 +45,64 @@
|
||||
<span v-else>{{ constraintMap[row.constraint] }}</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column :width="250" field="attributeAssociation" :edit-render="{}">
|
||||
<template #header>
|
||||
<span>
|
||||
<a-tooltip :title="$t('cmdb.ciType.attributeAssociationTip1')">
|
||||
<a><a-icon type="question-circle"/></a>
|
||||
</a-tooltip>
|
||||
{{ $t('cmdb.ciType.attributeAssociation') }}
|
||||
<span :style="{ fontSize: '10px', fontWeight: 'normal' }" class="text-color-4">{{
|
||||
$t('cmdb.ciType.attributeAssociationTip2')
|
||||
}}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template #default="{row}">
|
||||
<span
|
||||
v-if="row.parent_attr_id && row.child_attr_id"
|
||||
>{{ getAttrNameById(row.isParent ? row.attributes : attributes, row.parent_attr_id) }}=>
|
||||
{{ getAttrNameById(row.isParent ? attributes : row.attributes, row.child_attr_id) }}</span
|
||||
>
|
||||
</template>
|
||||
<template #edit="{ row }">
|
||||
<div style="display:inline-flex;align-items:center;">
|
||||
<a-select
|
||||
allowClear
|
||||
size="small"
|
||||
v-model="parent_attr_id"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
:style="{ width: '100px' }"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="attr in filterAttributes(row.isParent ? row.attributes : attributes)"
|
||||
:key="attr.id"
|
||||
>
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
=>
|
||||
<a-select
|
||||
allowClear
|
||||
size="small"
|
||||
v-model="child_attr_id"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
:style="{ width: '100px' }"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="attr in filterAttributes(row.isParent ? attributes : row.attributes)"
|
||||
:key="attr.id"
|
||||
>
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="operation" :title="$t('operation')" width="100">
|
||||
<template #default="{row}">
|
||||
<a-space v-if="!row.isParent && row.source_ci_type_id">
|
||||
<a @click="handleOpenGrant(row)"><a-icon type="user-add"/></a>
|
||||
<a-popconfirm :title="$t('cmdb.ciType.confirmDelete2')" @confirm="handleDelete(row)">
|
||||
<a-popconfirm v-if="!isInGrantComp" :title="$t('cmdb.ciType.confirmDelete2')" @confirm="handleDelete(row)">
|
||||
<a style="color: red;"><a-icon type="delete"/></a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
@@ -62,12 +121,13 @@
|
||||
:visible="visible"
|
||||
@cancel="onClose"
|
||||
@ok="handleSubmit"
|
||||
width="500px"
|
||||
width="700px"
|
||||
>
|
||||
<a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 14 }">
|
||||
<a-form-item :label="$t('cmdb.ciType.sourceCIType')">
|
||||
<a-select
|
||||
name="source_ci_type_id"
|
||||
:placeholder="$t('cmdb.ciType.sourceCITypeTips')"
|
||||
v-decorator="[
|
||||
'source_ci_type_id',
|
||||
{ rules: [{ required: true, message: $t('cmdb.ciType.sourceCITypeTips') }] },
|
||||
@@ -82,8 +142,10 @@
|
||||
<a-select
|
||||
showSearch
|
||||
name="ci_type_id"
|
||||
:placeholder="$t('cmdb.ciType.dstCITypeTips')"
|
||||
v-decorator="['ci_type_id', { rules: [{ required: true, message: $t('cmdb.ciType.dstCITypeTips') }] }]"
|
||||
:filterOption="filterOption"
|
||||
@change="changeChild"
|
||||
>
|
||||
<a-select-option :value="CIType.id" :key="CIType.id" v-for="CIType in CITypes">
|
||||
{{ CIType.alias || CIType.name }}
|
||||
@@ -94,6 +156,7 @@
|
||||
<a-form-item :label="$t('cmdb.ciType.relationType')">
|
||||
<a-select
|
||||
name="relation_type_id"
|
||||
:placeholder="$t('cmdb.ciType.relationTypeTips')"
|
||||
v-decorator="[
|
||||
'relation_type_id',
|
||||
{ rules: [{ required: true, message: $t('cmdb.ciType.relationTypeTips') }] },
|
||||
@@ -104,9 +167,9 @@
|
||||
}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="$t('cmdb.ciType.relationConstraint')">
|
||||
<a-select
|
||||
:placeholder="$t('cmdb.ciType.relationConstraintTips')"
|
||||
v-decorator="[
|
||||
'constraint',
|
||||
{ rules: [{ required: true, message: $t('cmdb.ciType.relationConstraintTips') }] },
|
||||
@@ -117,6 +180,39 @@
|
||||
<a-select-option value="2">{{ $t('cmdb.ciType.many2Many') }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('cmdb.ciType.attributeAssociation')">
|
||||
<a-row>
|
||||
<a-col :span="11">
|
||||
<a-form-item>
|
||||
<a-select
|
||||
:placeholder="$t('cmdb.ciType.attributeAssociationTip4')"
|
||||
allowClear
|
||||
v-decorator="['parent_attr_id', { rules: [{ required: false }] }]"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(attributes)" :key="attr.id">
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="2" :style="{ textAlign: 'center' }">
|
||||
=>
|
||||
</a-col>
|
||||
<a-col :span="11">
|
||||
<a-form-item>
|
||||
<a-select
|
||||
:placeholder="$t('cmdb.ciType.attributeAssociationTip5')"
|
||||
allowClear
|
||||
v-decorator="['child_attr_id', { rules: [{ required: false }] }]"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(modalChildAttributes)" :key="attr.id">
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<CMDBGrant ref="cmdbGrant" resourceType="CITypeRelation" app_id="cmdb" />
|
||||
@@ -132,6 +228,8 @@ import {
|
||||
getRelationTypes,
|
||||
} from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
|
||||
import CMDBGrant from '../../components/cmdbGrant'
|
||||
|
||||
export default {
|
||||
@@ -148,6 +246,10 @@ export default {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isInGrantComp: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -158,6 +260,10 @@ export default {
|
||||
relationTypes: [],
|
||||
tableData: [],
|
||||
parentTableData: [],
|
||||
attributes: [],
|
||||
parent_attr_id: undefined,
|
||||
child_attr_id: undefined,
|
||||
modalChildAttributes: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -176,12 +282,20 @@ export default {
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
getCITypeAttributesById(this.CITypeId).then((res) => {
|
||||
this.attributes = res?.attributes ?? []
|
||||
})
|
||||
this.getCITypes()
|
||||
this.getRelationTypes()
|
||||
await this.getCITypeParent()
|
||||
this.getData()
|
||||
},
|
||||
methods: {
|
||||
async getData() {
|
||||
if (!this.isInGrantComp) {
|
||||
await this.getCITypeParent()
|
||||
}
|
||||
this.getCITypeChildren()
|
||||
},
|
||||
async getCITypeParent() {
|
||||
await getCITypeParent(this.CITypeId).then((res) => {
|
||||
this.parentTableData = res.parents.map((item) => {
|
||||
@@ -194,7 +308,7 @@ export default {
|
||||
})
|
||||
})
|
||||
},
|
||||
getData() {
|
||||
getCITypeChildren() {
|
||||
getCITypeChildren(this.CITypeId).then((res) => {
|
||||
const data = res.children.map((obj) => {
|
||||
return {
|
||||
@@ -223,12 +337,9 @@ export default {
|
||||
handleDelete(record) {
|
||||
deleteRelation(record.source_ci_type_id, record.id).then((res) => {
|
||||
this.$message.success(this.$t('deleteSuccess'))
|
||||
this.handleOk()
|
||||
this.getData()
|
||||
})
|
||||
},
|
||||
handleOk() {
|
||||
this.getData()
|
||||
},
|
||||
|
||||
handleCreate() {
|
||||
this.drawerTitle = this.$t('cmdb.ciType.addRelation')
|
||||
@@ -251,14 +362,29 @@ export default {
|
||||
if (!err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Received values of form: ', values)
|
||||
const {
|
||||
source_ci_type_id,
|
||||
ci_type_id,
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id = undefined,
|
||||
child_attr_id = undefined,
|
||||
} = values
|
||||
|
||||
createRelation(values.source_ci_type_id, values.ci_type_id, values.relation_type_id, values.constraint).then(
|
||||
(res) => {
|
||||
this.$message.success(this.$t('addSuccess'))
|
||||
this.onClose()
|
||||
this.handleOk()
|
||||
}
|
||||
)
|
||||
if ((!parent_attr_id && child_attr_id) || (parent_attr_id && !child_attr_id)) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
return
|
||||
}
|
||||
createRelation(source_ci_type_id, ci_type_id, {
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id,
|
||||
child_attr_id,
|
||||
}).then((res) => {
|
||||
this.$message.success(this.$t('addSuccess'))
|
||||
this.onClose()
|
||||
this.getData()
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -276,6 +402,42 @@ export default {
|
||||
if (row.isDivider) return 'relation-table-divider'
|
||||
if (row.isParent) return 'relation-table-parent'
|
||||
},
|
||||
handleEditActived({ row }) {
|
||||
this.parent_attr_id = row?.parent_attr_id ?? undefined
|
||||
this.child_attr_id = row?.child_attr_id ?? undefined
|
||||
},
|
||||
async handleEditClose({ row }) {
|
||||
const { source_ci_type_id: parentId, id: childrenId, constraint, relation_type } = row
|
||||
const { parent_attr_id, child_attr_id } = this
|
||||
const _find = this.relationTypes.find((item) => item.name === relation_type)
|
||||
const relation_type_id = _find?.id
|
||||
if ((!parent_attr_id && child_attr_id) || (parent_attr_id && !child_attr_id)) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
return
|
||||
}
|
||||
await createRelation(row.isParent ? childrenId : parentId, row.isParent ? parentId : childrenId, {
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id,
|
||||
child_attr_id,
|
||||
}).finally(() => {
|
||||
this.getData()
|
||||
})
|
||||
},
|
||||
getAttrNameById(attributes, id) {
|
||||
const _find = attributes.find((attr) => attr.id === id)
|
||||
return _find?.alias ?? _find?.name ?? id
|
||||
},
|
||||
changeChild(value) {
|
||||
this.form.setFieldsValue({ child_attr_id: undefined })
|
||||
getCITypeAttributesById(value).then((res) => {
|
||||
this.modalChildAttributes = res?.attributes ?? []
|
||||
})
|
||||
},
|
||||
filterAttributes(attributes) {
|
||||
// filter password/json/is_list
|
||||
return attributes.filter((attr) => !attr.is_password && !attr.is_list && attr.value_type !== '6')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@@ -6,6 +6,7 @@
|
||||
:visible="visible"
|
||||
@close="handleCancel"
|
||||
@ok="handleOk"
|
||||
destroyOnClose
|
||||
>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button type="primary" ghost @click="handleCancel">{{ $t('cancel') }}</a-button>
|
||||
|
@@ -134,6 +134,6 @@ export default {
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-types-triggers {
|
||||
padding: 16px 24px 24px;
|
||||
padding: 0 20px 20px;
|
||||
}
|
||||
</style>
|
||||
|
@@ -165,7 +165,7 @@
|
||||
: '#fafafa',
|
||||
}"
|
||||
>
|
||||
<div :style="{ color: fontColor }">{{ form.name }}</div>
|
||||
<div v-if="chartType === 'count'" :style="{ color: fontColor }">{{ form.name }}</div>
|
||||
<Chart
|
||||
:ref="`chart_${item.id}`"
|
||||
:chartId="item.id"
|
||||
@@ -613,6 +613,7 @@ export default {
|
||||
} else {
|
||||
this.form.category = 1
|
||||
}
|
||||
console.log(this.chartType)
|
||||
},
|
||||
showPreview() {
|
||||
this.$refs.chartForm.validate(async (valid) => {
|
||||
|
@@ -15,7 +15,8 @@
|
||||
@click="openChartForm('add', { options: { w: 3 } })"
|
||||
type="primary"
|
||||
icon="plus-circle"
|
||||
class="ops-button-primary"
|
||||
ghost
|
||||
class="ops-button-ghost"
|
||||
>{{ $t('cmdb.custom_dashboard.newChart') }}</a-button
|
||||
>
|
||||
</div>
|
||||
@@ -44,7 +45,7 @@
|
||||
? Array.isArray(item.options.bgColor)
|
||||
? `linear-gradient(to bottom, ${item.options.bgColor[0]} 0%, ${item.options.bgColor[1]} 100%)`
|
||||
: item.options.bgColor
|
||||
: '#fafafa',
|
||||
: '#fff',
|
||||
}"
|
||||
>
|
||||
<div class="cmdb-dashboard-grid-item-title">
|
||||
@@ -78,7 +79,10 @@
|
||||
></a>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item>
|
||||
<a @click="() => openChartForm('edit', item)"><a-icon style="margin-right:5px" type="edit" />{{ $t('edit') }}</a>
|
||||
<a
|
||||
@click="() => openChartForm('edit', item)"
|
||||
><a-icon style="margin-right:5px" type="edit" />{{ $t('edit') }}</a
|
||||
>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
<a @click="deleteChart(item)"><a-icon style="margin-right:5px" type="delete" />{{ $t('delete') }}</a>
|
||||
@@ -261,7 +265,7 @@ export default {
|
||||
text-align: center;
|
||||
}
|
||||
.cmdb-dashboard-grid-item {
|
||||
border-radius: 8px;
|
||||
border-radius: 2px;
|
||||
padding: 6px 12px;
|
||||
.cmdb-dashboard-grid-item-title {
|
||||
overflow: hidden;
|
||||
@@ -299,6 +303,7 @@ export default {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin-right: 5px;
|
||||
|
@@ -144,6 +144,9 @@ export default {
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.setting-discovery {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: @border-radius-box;
|
||||
.type-header {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<TwoColumnLayout appName="cmdb-adc">
|
||||
<template #one>
|
||||
<div v-for="group in ci_types_list" :key="group.id">
|
||||
<div>
|
||||
<div class="cmdb-adc-group" v-for="group in ci_types_list" :key="group.id">
|
||||
<p>
|
||||
<strong>{{ group.name || $t('other') }}</strong
|
||||
><span :style="{ color: 'rgb(195, 205, 215)' }">({{ group.ci_types.length }})</span>
|
||||
</div>
|
||||
</p>
|
||||
<div
|
||||
:class="{ 'cmdb-adc-side-item': true, 'cmdb-adc-side-item-selected': currentType === type.id }"
|
||||
v-for="type in group.ci_types"
|
||||
@@ -34,7 +34,6 @@
|
||||
<div id="discovery-ci">
|
||||
<a-input-search
|
||||
:placeholder="$t('cmdb.components.pleaseSearch')"
|
||||
class="ops-input ops-input-radius"
|
||||
:style="{ width: '200px', marginRight: '20px', marginBottom: '10px' }"
|
||||
@search="handleSearch"
|
||||
allowClear
|
||||
@@ -102,7 +101,11 @@
|
||||
sortable
|
||||
v-bind="columns.length ? { width: '130px' } : { minWidth: '130px' }"
|
||||
></vxe-column>
|
||||
<vxe-column :title="$t('operation')" v-bind="columns.length ? { width: '60px' } : { minWidth: '60px' }" align="center">
|
||||
<vxe-column
|
||||
:title="$t('operation')"
|
||||
v-bind="columns.length ? { width: '60px' } : { minWidth: '60px' }"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{row}">
|
||||
<a-space>
|
||||
<a-tooltip :title="$t('cmdb.ad.accept')">
|
||||
@@ -312,6 +315,9 @@ export default {
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.cmdb-adc {
|
||||
.cmdb-adc-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.cmdb-adc-side-item {
|
||||
.ops_popover_item();
|
||||
height: 32px;
|
||||
@@ -343,7 +349,7 @@ export default {
|
||||
}
|
||||
.cmdb-adc-side-item-selected {
|
||||
.ops_popover_item_selected();
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_3;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -77,7 +77,7 @@
|
||||
}
|
||||
|
||||
.cmdb-views-header {
|
||||
border-left: 4px solid #custom_colors[color_1];
|
||||
border-left: 4px solid @primary-color;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -85,17 +85,17 @@
|
||||
margin-bottom: 18px;
|
||||
.cmdb-views-header-title {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
font-weight: bold;
|
||||
color: @text-color_1;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.cmdb-views-header-metadata {
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
color: @text-color_3;
|
||||
margin-left: 20px;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@
|
||||
:visible="visible"
|
||||
@cancel="onClose"
|
||||
@ok="handleSubmit"
|
||||
width="500px"
|
||||
width="700px"
|
||||
>
|
||||
<a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 14 }">
|
||||
<a-form-item :label="$t('cmdb.ciType.sourceCIType')">
|
||||
@@ -69,6 +69,39 @@
|
||||
<a-select-option value="2">{{ $t('cmdb.ciType.many2Many') }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('cmdb.ciType.attributeAssociation')">
|
||||
<a-row>
|
||||
<a-col :span="11">
|
||||
<a-form-item>
|
||||
<a-select
|
||||
:placeholder="$t('cmdb.ciType.attributeAssociationTip4')"
|
||||
allowClear
|
||||
v-decorator="['parent_attr_id', { rules: [{ required: false }] }]"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(modalParentAttributes)" :key="attr.id">
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="2" :style="{ textAlign: 'center' }">
|
||||
=>
|
||||
</a-col>
|
||||
<a-col :span="11">
|
||||
<a-form-item>
|
||||
<a-select
|
||||
:placeholder="$t('cmdb.ciType.attributeAssociationTip5')"
|
||||
allowClear
|
||||
v-decorator="['child_attr_id', { rules: [{ required: false }] }]"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(modalChildAttributes)" :key="attr.id">
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
@@ -80,6 +113,8 @@ import { searchResourceType } from '@/modules/acl/api/resource'
|
||||
import { getCITypeGroupsConfig } from '@/modules/cmdb/api/ciTypeGroup'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { createRelation, deleteRelation, getCITypeChildren, getRelationTypes } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
|
||||
export default {
|
||||
name: 'Index',
|
||||
components: {
|
||||
@@ -101,6 +136,9 @@ export default {
|
||||
|
||||
sourceCITypeId: undefined,
|
||||
targetCITypeId: undefined,
|
||||
|
||||
modalParentAttributes: [],
|
||||
modalChildAttributes: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -206,13 +244,29 @@ export default {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
createRelation(values.source_ci_type_id, values.ci_type_id, values.relation_type_id, values.constraint).then(
|
||||
(res) => {
|
||||
this.$message.success(this.$t('addSuccess'))
|
||||
this.onClose()
|
||||
this.handleOk()
|
||||
}
|
||||
)
|
||||
const {
|
||||
source_ci_type_id,
|
||||
ci_type_id,
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id = undefined,
|
||||
child_attr_id = undefined,
|
||||
} = values
|
||||
|
||||
if ((!parent_attr_id && child_attr_id) || (parent_attr_id && !child_attr_id)) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
return
|
||||
}
|
||||
createRelation(source_ci_type_id, ci_type_id, {
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id,
|
||||
child_attr_id,
|
||||
}).then((res) => {
|
||||
this.$message.success(this.$t('addSuccess'))
|
||||
this.onClose()
|
||||
this.handleOk()
|
||||
})
|
||||
}
|
||||
})
|
||||
this.sourceCITypeId = undefined
|
||||
@@ -230,21 +284,35 @@ export default {
|
||||
},
|
||||
handleSourceTypeChange(value) {
|
||||
this.sourceCITypeId = value
|
||||
this.form.setFieldsValue({ parent_attr_id: undefined })
|
||||
getCITypeAttributesById(value).then((res) => {
|
||||
this.modalParentAttributes = res?.attributes ?? []
|
||||
})
|
||||
},
|
||||
handleTargetTypeChange(value) {
|
||||
this.targetCITypeId = value
|
||||
this.form.setFieldsValue({ child_attr_id: undefined })
|
||||
getCITypeAttributesById(value).then((res) => {
|
||||
this.modalChildAttributes = res?.attributes ?? []
|
||||
})
|
||||
},
|
||||
filterOption(input, option) {
|
||||
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
},
|
||||
filterAttributes(attributes) {
|
||||
// filter password/json/is_list
|
||||
return attributes.filter((attr) => !attr.is_password && !attr.is_list && attr.value_type !== '6')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.model-relation {
|
||||
background-color: #fff;
|
||||
border-radius: 15px;
|
||||
border-radius: @border-radius-box;
|
||||
padding: 24px;
|
||||
height: calc(100vh - 64px);
|
||||
margin-bottom: -24px;
|
||||
|
@@ -10,6 +10,9 @@
|
||||
:height="`${windowHeight - 160}px`"
|
||||
:data="tableData"
|
||||
:sort-config="{ defaultSort: { field: 'created_at', order: 'desc' } }"
|
||||
:edit-config="{ trigger: 'dblclick', mode: 'cell', showIcon: false }"
|
||||
@edit-closed="handleEditClose"
|
||||
@edit-actived="handleEditActived"
|
||||
>
|
||||
<vxe-column field="created_at" :title="$t('created_at')" sortable width="159px"></vxe-column>
|
||||
<vxe-column field="parent.alias" :title="$t('cmdb.ciType.sourceCIType')"></vxe-column>
|
||||
@@ -26,8 +29,59 @@
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="child.alias" :title="$t('cmdb.ciType.dstCIType')"></vxe-column>
|
||||
<vxe-column field="constraint" :title="$t('cmdb.ciType.relationConstraint')"></vxe-column>
|
||||
<vxe-column field="authorization" :title="$t('operation')" width="89px">
|
||||
<vxe-column field="constraint" :title="$t('cmdb.ciType.relationConstraint')">
|
||||
<template #default="{row}">
|
||||
{{ handleConstraint(row.constraint) }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column :width="250" field="attributeAssociation" :edit-render="{}">
|
||||
<template #header>
|
||||
<span>
|
||||
<a-tooltip :title="$t('cmdb.ciType.attributeAssociationTip1')">
|
||||
<a><a-icon type="question-circle"/></a>
|
||||
</a-tooltip>
|
||||
{{ $t('cmdb.ciType.attributeAssociation') }}
|
||||
<span :style="{ fontSize: '10px', fontWeight: 'normal' }" class="text-color-4">{{
|
||||
$t('cmdb.ciType.attributeAssociationTip2')
|
||||
}}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template #default="{row}">
|
||||
<span
|
||||
v-if="row.parent_attr_id && row.child_attr_id"
|
||||
>{{ getAttrNameById(type2attributes[row.parent_id], row.parent_attr_id) }}=>
|
||||
{{ getAttrNameById(type2attributes[row.child_id], row.child_attr_id) }}</span
|
||||
>
|
||||
</template>
|
||||
<template #edit="{ row }">
|
||||
<div style="display:inline-flex;align-items:center;">
|
||||
<a-select
|
||||
allowClear
|
||||
size="small"
|
||||
v-model="parent_attr_id"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
:style="{ width: '100px' }"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(type2attributes[row.parent_id])" :key="attr.id">
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
=>
|
||||
<a-select
|
||||
allowClear
|
||||
size="small"
|
||||
v-model="child_attr_id"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
:style="{ width: '100px' }"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(type2attributes[row.child_id])" :key="attr.id">
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="operation" :title="$t('operation')" width="89px">
|
||||
<template #default="{ row }">
|
||||
<a-space>
|
||||
<a @click="handleOpenGrant(row)"><a-icon type="user-add"/></a>
|
||||
@@ -43,7 +97,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCITypeRelations, deleteRelation } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getCITypeRelations, deleteRelation, createRelation } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getRelationTypes } from '@/modules/cmdb/api/relationType'
|
||||
import CMDBGrant from '../../../components/cmdbGrant'
|
||||
|
||||
@@ -53,6 +107,9 @@ export default {
|
||||
drawerVisible: false,
|
||||
tableData: [],
|
||||
relationTypeList: null,
|
||||
type2attributes: {},
|
||||
parent_attr_id: undefined,
|
||||
child_attr_id: undefined,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
@@ -79,11 +136,9 @@ export default {
|
||||
await this.getMainData()
|
||||
},
|
||||
async getMainData() {
|
||||
const res = await getCITypeRelations()
|
||||
res.forEach((item) => {
|
||||
item.constraint = this.handleConstraint(item.constraint)
|
||||
})
|
||||
this.tableData = res
|
||||
const { relations, type2attributes } = await getCITypeRelations()
|
||||
this.tableData = relations
|
||||
this.type2attributes = type2attributes
|
||||
},
|
||||
// 获取关系
|
||||
async getRelationTypes() {
|
||||
@@ -115,6 +170,34 @@ export default {
|
||||
this.refresh()
|
||||
})
|
||||
},
|
||||
handleEditActived({ row }) {
|
||||
this.parent_attr_id = row?.parent_attr_id ?? undefined
|
||||
this.child_attr_id = row?.child_attr_id ?? undefined
|
||||
},
|
||||
async handleEditClose({ row }) {
|
||||
const { parent_id, child_id, constraint, relation_type_id } = row
|
||||
const { parent_attr_id = undefined, child_attr_id = undefined } = this
|
||||
if ((!parent_attr_id && child_attr_id) || (parent_attr_id && !child_attr_id)) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
return
|
||||
}
|
||||
await createRelation(parent_id, child_id, {
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id,
|
||||
child_attr_id,
|
||||
}).finally(() => {
|
||||
this.getMainData()
|
||||
})
|
||||
},
|
||||
getAttrNameById(attributes, id) {
|
||||
const _find = attributes.find((attr) => attr.id === id)
|
||||
return _find?.alias ?? _find?.name ?? id
|
||||
},
|
||||
filterAttributes(attributes) {
|
||||
// filter password/json/is_list
|
||||
return attributes.filter((attr) => !attr.is_password && !attr.is_list && attr.value_type !== '6')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@@ -51,7 +51,6 @@
|
||||
:class="{
|
||||
'cmdb-preference-avatar': true,
|
||||
'cmdb-preference-avatar-noicon': !ciType.icon,
|
||||
'cmdb-preference-avatar-noicon-is_subscribed': !ciType.icon && ciType.is_subscribed,
|
||||
}"
|
||||
:style="{ width: '30px', height: '30px', marginRight: '10px' }"
|
||||
>
|
||||
@@ -94,7 +93,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="cmdb-preference-right">
|
||||
<div v-for="group in citypeData" :key="group.id">
|
||||
<a-input-search
|
||||
v-model="searchValue"
|
||||
:style="{ width: '300px', marginBottom: '20px' }"
|
||||
:placeholder="$t('cmdb.preference.searchPlaceholder')"
|
||||
/>
|
||||
<div v-for="group in filterCiTypeData" :key="group.id">
|
||||
<p @click="changeGroupExpand(group)" :style="{ display: 'inline-block', cursor: 'pointer' }">
|
||||
<a-icon :type="expandKeys.includes(group.id) ? 'caret-down' : 'caret-right'" />{{ group.name }}({{
|
||||
group.ci_types ? group.ci_types.length : 0
|
||||
@@ -108,7 +112,6 @@
|
||||
:class="{
|
||||
'cmdb-preference-avatar': true,
|
||||
'cmdb-preference-avatar-noicon': !item.icon,
|
||||
'cmdb-preference-avatar-noicon-is_subscribed': !item.icon && item.is_subscribed,
|
||||
}"
|
||||
>
|
||||
<template v-if="item.icon">
|
||||
@@ -188,6 +191,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import router, { resetRouter } from '@/router'
|
||||
import store from '@/store'
|
||||
import { mapState } from 'vuex'
|
||||
@@ -219,12 +223,29 @@ export default {
|
||||
},
|
||||
type_id2users: {},
|
||||
myPreferences: [],
|
||||
searchValue: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
}),
|
||||
filterCiTypeData() {
|
||||
if (this.searchValue) {
|
||||
const _citypeData = _.cloneDeep(this.citypeData)
|
||||
_citypeData.forEach((group) => {
|
||||
if (group.ci_types) {
|
||||
group.ci_types = group.ci_types.filter(
|
||||
(item) =>
|
||||
item.name.toLowerCase().includes(this.searchValue.toLowerCase()) ||
|
||||
item.alias.toLowerCase().includes(this.searchValue.toLowerCase())
|
||||
)
|
||||
}
|
||||
})
|
||||
return _citypeData
|
||||
}
|
||||
return this.citypeData
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getCITypes(true)
|
||||
@@ -376,7 +397,6 @@ export default {
|
||||
.cmdb-preference {
|
||||
margin: -24px;
|
||||
overflow: auto;
|
||||
background: url('../../assets/preference_background.png');
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -392,7 +412,7 @@ export default {
|
||||
.cmdb-preference-left {
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
padding: 12px 18px;
|
||||
padding: 24px 18px;
|
||||
.cmdb-preference-left-card {
|
||||
background: url('../../assets/preference_card.png');
|
||||
background-repeat: no-repeat;
|
||||
@@ -426,17 +446,19 @@ export default {
|
||||
.cmdb-preference-group-title {
|
||||
text-align: center;
|
||||
margin-bottom: 5px;
|
||||
i {
|
||||
color: @primary-color;
|
||||
}
|
||||
> span {
|
||||
display: inline-block;
|
||||
color: #fff;
|
||||
background: linear-gradient(90deg, #305bec, #78cfff);
|
||||
color: @text-color_2;
|
||||
border-radius: 16px;
|
||||
font-weight: 600;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
}
|
||||
.cmdb-preference-group-content {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
color: @text-color_1;
|
||||
font-weight: 400;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -447,7 +469,11 @@ export default {
|
||||
&:hover {
|
||||
background: #ffffff;
|
||||
box-shadow: 0px 2px 8px rgba(149, 160, 208, 0.25);
|
||||
border-radius: 8px;
|
||||
border-radius: @border-radius-box;
|
||||
.cmdb-preference-avatar {
|
||||
box-shadow: none;
|
||||
background-color: @primary-color_5;
|
||||
}
|
||||
.cmdb-preference-group-content-action {
|
||||
display: inline;
|
||||
white-space: nowrap;
|
||||
@@ -479,7 +505,7 @@ export default {
|
||||
.cmdb-preference-right {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
padding-top: 18px;
|
||||
padding-top: 24px;
|
||||
.cmdb-preference-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -494,11 +520,11 @@ export default {
|
||||
display: inline-block;
|
||||
width: 195px;
|
||||
height: 155px;
|
||||
border-radius: 8px;
|
||||
border-radius: @border-radius-box;
|
||||
background-color: #fff;
|
||||
box-shadow: 0px 2px 8px rgba(149, 160, 208, 0.25);
|
||||
margin: 0 20px 20px 0;
|
||||
padding: 8px;
|
||||
padding: 12px;
|
||||
&:hover {
|
||||
box-shadow: 4px 25px 30px rgba(50, 89, 134, 0.25);
|
||||
transform: scale(1.1);
|
||||
@@ -550,7 +576,7 @@ export default {
|
||||
.cmdb-preference-progress-gray {
|
||||
height: 5px;
|
||||
border-radius: 5px;
|
||||
background-color: #d9d9d9;
|
||||
background-color: @text-color_6;
|
||||
margin-top: 5px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
@@ -560,7 +586,7 @@ export default {
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-radius: 5px;
|
||||
background: linear-gradient(90deg, #305bec, #78cfff);
|
||||
background: @primary-color_8;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -592,20 +618,17 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
box-shadow: 0px 4px 4px rgba(129, 140, 186, 0.25);
|
||||
border-radius: 5px;
|
||||
border-radius: 1px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.cmdb-preference-avatar-noicon {
|
||||
background-color: #7f97fa;
|
||||
> span {
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
color: @text-color_4;
|
||||
}
|
||||
}
|
||||
.cmdb-preference-avatar-noicon-is_subscribed {
|
||||
background-color: #47a964;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -15,9 +15,7 @@
|
||||
>{{ $t('cmdb.preference_relation.newServiceTree') }}</a-button
|
||||
>
|
||||
<template v-else>
|
||||
<a-input v-model="newRelationViewName" :placeholder="$t('cmdb.preference_relation.serviceTreeName')"></a-input>
|
||||
<a-checkbox v-model="is_public">{{ $t('cmdb.preference_relation.public') }}</a-checkbox>
|
||||
<a-button type="primary" size="small" @click="handleSaveRelationViews">{{ $t('save') }}</a-button>
|
||||
<a-button type="primary" size="small" @click="openServiceTreeModal({}, 'add')">{{ $t('save') }}</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@@ -26,13 +24,13 @@
|
||||
() => {
|
||||
isEdit = false
|
||||
checkedNodes = []
|
||||
newRelationViewName = ''
|
||||
}
|
||||
"
|
||||
>{{ $t('cancel') }}</a-button
|
||||
>
|
||||
</template>
|
||||
<a-button type="primary" size="small" @click="handleSave">{{ $t('cmdb.preference_relation.saveLayout') }}</a-button>
|
||||
<a-button size="small" @click="handleSave">{{ $t('cmdb.preference_relation.saveLayout') }}</a-button>
|
||||
<span>{{ $t('cmdb.preference_relation.tips5') }}</span>
|
||||
</a-space>
|
||||
</div>
|
||||
<SeeksRelationGraph v-if="isPullConfig" ref="ciTypeRelationGraph" :options="graphOptions">
|
||||
@@ -46,7 +44,7 @@
|
||||
</div>
|
||||
</SeeksRelationGraph>
|
||||
</div>
|
||||
<template v-if="relationViews.views">
|
||||
<template v-if="relationViews.views && !loading">
|
||||
<a-row :gutter="4">
|
||||
<a-col
|
||||
:xl="12"
|
||||
@@ -54,11 +52,17 @@
|
||||
:md="12"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
:key="`${view}${idx}`"
|
||||
v-for="(view, idx) in Object.keys(relationViews.views)"
|
||||
:key="`${view}`"
|
||||
v-for="view in Object.keys(relationViews.views)"
|
||||
>
|
||||
<div class="relation-views">
|
||||
<h3 :style="{ padding: '10px 0 0 20px' }">{{ view }}</h3>
|
||||
<a
|
||||
class="relation-views-edit"
|
||||
@click="openServiceTreeModal({ name: view }, 'edit')"
|
||||
><ops-icon
|
||||
type="icon-xianxing-edit"
|
||||
/></a>
|
||||
<a-popconfirm :title="$t('cmdb.ciType.confirmDelete', { name: `${view}` })" @confirm="confirmDelete(view)">
|
||||
<a class="relation-views-close"><a-icon type="close"/></a>
|
||||
</a-popconfirm>
|
||||
@@ -69,6 +73,7 @@
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<ServiceTreeModal ref="serviceTreeModal" @submitServiceTree="submitServiceTree" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -78,11 +83,17 @@ import router, { resetRouter } from '@/router'
|
||||
import store from '@/store'
|
||||
import SeeksRelationGraph from '@/modules/cmdb/3rd/relation-graph'
|
||||
import { getCITypeRelations } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getRelationView, deleteRelationView, subscribeRelationView } from '@/modules/cmdb/api/preference'
|
||||
import {
|
||||
getRelationView,
|
||||
deleteRelationView,
|
||||
subscribeRelationView,
|
||||
putRelationView,
|
||||
} from '@/modules/cmdb/api/preference'
|
||||
import { getSystemConfig, saveSystemConfig } from '../../api/system_config'
|
||||
import ServiceTreeModal from './serviceTreeModal.vue'
|
||||
export default {
|
||||
name: 'PreferenceRelation',
|
||||
components: { SeeksRelationGraph },
|
||||
components: { SeeksRelationGraph, ServiceTreeModal },
|
||||
data() {
|
||||
const defaultOptions = {
|
||||
allowShowMiniToolBar: false,
|
||||
@@ -103,6 +114,7 @@ export default {
|
||||
}
|
||||
const relationViewOptions = {
|
||||
...defaultOptions,
|
||||
disableZoom: true,
|
||||
layouts: [
|
||||
{
|
||||
layoutName: 'tree',
|
||||
@@ -116,11 +128,10 @@ export default {
|
||||
relationViewOptions,
|
||||
isEdit: false,
|
||||
relationViews: {},
|
||||
newRelationViewName: '',
|
||||
graphJsonData: {},
|
||||
checkedNodes: [],
|
||||
is_public: true,
|
||||
isPullConfig: false,
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
@@ -143,7 +154,7 @@ export default {
|
||||
this.getViewsData()
|
||||
},
|
||||
async getMainData() {
|
||||
const ciTypeRelations = await getCITypeRelations()
|
||||
const { relations: ciTypeRelations } = await getCITypeRelations()
|
||||
const nodes = []
|
||||
const links = []
|
||||
ciTypeRelations.forEach((item) => {
|
||||
@@ -210,6 +221,7 @@ export default {
|
||||
return maxEle
|
||||
},
|
||||
async getViewsData() {
|
||||
this.loading = true
|
||||
const data = await getRelationView()
|
||||
this.relationViews = data
|
||||
const { views } = data
|
||||
@@ -240,6 +252,7 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.relationViewsGraph[index].setJsonData(_graphJsonData)
|
||||
})
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
checked(e, node) {
|
||||
@@ -288,32 +301,59 @@ export default {
|
||||
this.checkedNodes.push(node.id)
|
||||
}
|
||||
},
|
||||
async handleSaveRelationViews() {
|
||||
if (!this.newRelationViewName) {
|
||||
this.$message.warning(this.$t('cmdb.preference_relation.tips2'))
|
||||
return
|
||||
}
|
||||
if (this.checkedNodes.length < 2) {
|
||||
openServiceTreeModal(treeData, type) {
|
||||
if (type === 'add' && this.checkedNodes.length < 2) {
|
||||
this.$message.warning(this.$t('cmdb.preference_relation.tips3'))
|
||||
return
|
||||
}
|
||||
// eslint-disable-next-line camelcase
|
||||
const cr_ids = []
|
||||
this.checkedNodes.forEach((item, idx) => {
|
||||
if (idx !== this.checkedNodes.length - 1) {
|
||||
cr_ids.push({ parent_id: Number(item), child_id: Number(this.checkedNodes[idx + 1]) })
|
||||
let _treeData = { ...treeData }
|
||||
if (type === 'edit') {
|
||||
const { name } = _treeData
|
||||
_treeData = {
|
||||
...treeData,
|
||||
...(this.relationViews?.views[name]?.option ?? {}),
|
||||
is_public: this.relationViews?.views[name]?.is_public ?? true,
|
||||
}
|
||||
})
|
||||
await subscribeRelationView({
|
||||
cr_ids,
|
||||
name: this.newRelationViewName,
|
||||
is_public: this.is_public,
|
||||
})
|
||||
}
|
||||
this.$refs.serviceTreeModal.open(_treeData, type)
|
||||
},
|
||||
async submitServiceTree(treeData, type, originName) {
|
||||
const { name, is_public, is_show_leaf_node, is_show_tree_node, sort } = treeData
|
||||
if (type === 'add') {
|
||||
const cr_ids = []
|
||||
this.checkedNodes.forEach((item, idx) => {
|
||||
if (idx !== this.checkedNodes.length - 1) {
|
||||
cr_ids.push({ parent_id: Number(item), child_id: Number(this.checkedNodes[idx + 1]) })
|
||||
}
|
||||
})
|
||||
await subscribeRelationView({
|
||||
cr_ids,
|
||||
name,
|
||||
is_public,
|
||||
option: { is_show_leaf_node, is_show_tree_node, sort, is_public },
|
||||
})
|
||||
} else {
|
||||
const _name = name === originName ? name : originName
|
||||
const topo_flatten = this.relationViews?.views[_name]?.topo_flatten ?? []
|
||||
const name2id = this.relationViews?.name2id.find((item) => item[0] === _name)
|
||||
const cr_ids = []
|
||||
topo_flatten.forEach((item, idx) => {
|
||||
if (idx !== topo_flatten.length - 1) {
|
||||
cr_ids.push({ parent_id: Number(item), child_id: Number(topo_flatten[idx + 1]) })
|
||||
}
|
||||
})
|
||||
console.log(originName, name, cr_ids, name2id)
|
||||
await putRelationView(name2id[1], {
|
||||
cr_ids,
|
||||
name,
|
||||
is_public,
|
||||
option: { is_show_leaf_node, is_show_tree_node, sort, is_public },
|
||||
})
|
||||
}
|
||||
this.resetRoute()
|
||||
this.getViewsData()
|
||||
this.isEdit = false
|
||||
this.checkedNodes = []
|
||||
this.newRelationViewName = ''
|
||||
},
|
||||
async confirmDelete(viewName) {
|
||||
await deleteRelationView(viewName)
|
||||
@@ -359,7 +399,7 @@ export default {
|
||||
width: 100%;
|
||||
.ci-type-relation-header {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
z-index: 10;
|
||||
}
|
||||
@@ -368,12 +408,19 @@ export default {
|
||||
background-color: #fff;
|
||||
margin-top: 5px;
|
||||
position: relative;
|
||||
.relation-views-edit,
|
||||
.relation-views-close {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
right: 20px;
|
||||
right: 60px;
|
||||
top: 10px;
|
||||
}
|
||||
.relation-views-edit {
|
||||
right: 46px;
|
||||
}
|
||||
.relation-views-close {
|
||||
right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<a-modal width="700px" :title="title" :visible="visible" @cancel="handleCancel" @ok="handleOK">
|
||||
<a-form-model ref="form" :model="form" :rules="rules" :label-col="{ span: 8 }" :wrapper-col="{ span: 14 }">
|
||||
<a-form-model-item :label="$t('cmdb.preference_relation.serviceTreeName')" prop="name">
|
||||
<a-input v-model="form.name" :placeholder="$t('cmdb.preference_relation.serviceTreeNamePlaceholder')" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item :label="$t('cmdb.preference_relation.public')" prop="is_public">
|
||||
<a-checkbox v-model="form.is_public"> </a-checkbox>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item :label="$t('cmdb.preference_relation.showLeafNode')" prop="is_show_leaf_node">
|
||||
<a-checkbox :checked="form.is_show_leaf_node" @change="changeLeaf"> </a-checkbox>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item :label="$t('cmdb.preference_relation.showTreeNode')" prop="is_show_tree_node">
|
||||
<a-checkbox v-model="form.is_show_tree_node"> </a-checkbox>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item
|
||||
v-if="form.is_show_leaf_node && form.is_show_tree_node"
|
||||
:label="$t('cmdb.preference_relation.sort')"
|
||||
prop="sort"
|
||||
>
|
||||
<a-radio-group v-model="form.sort">
|
||||
<a-radio :value="1">
|
||||
{{ $t('cmdb.preference_relation.sort1') }}
|
||||
</a-radio>
|
||||
<a-radio :value="2">
|
||||
{{ $t('cmdb.preference_relation.sort2') }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ServiceTreeModal',
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
type: 'add',
|
||||
originTreeData: {},
|
||||
form: {
|
||||
name: '',
|
||||
is_public: true,
|
||||
is_show_leaf_node: true,
|
||||
is_show_tree_node: false,
|
||||
sort: 1,
|
||||
},
|
||||
rules: {
|
||||
name: [{ required: true, message: this.$t('cmdb.preference_relation.serviceTreeNamePlaceholder') }],
|
||||
is_public: [{ required: false }],
|
||||
is_show_leaf_node: [{ required: true }],
|
||||
is_show_tree_node: [{ required: false }],
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
if (this.type === 'edit') {
|
||||
return this.$t('cmdb.preference_relation.editServiceTree')
|
||||
}
|
||||
return this.$t('cmdb.preference_relation.newServiceTree')
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
open(treeData = {}, type) {
|
||||
this.visible = true
|
||||
this.type = type
|
||||
this.originTreeData = { ...treeData }
|
||||
this.form = { name: '', is_public: true, is_show_leaf_node: true, is_show_tree_node: false, sort: 1, ...treeData }
|
||||
},
|
||||
handleCancel() {
|
||||
this.$refs.form.resetFields()
|
||||
this.visible = false
|
||||
},
|
||||
handleOK() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (valid) {
|
||||
this.$emit('submitServiceTree', this.form, this.type, this.originTreeData?.name ?? undefined)
|
||||
this.handleCancel()
|
||||
}
|
||||
})
|
||||
},
|
||||
changeLeaf(e) {
|
||||
const checked = e.target.checked
|
||||
if (!checked) {
|
||||
this.$message.warning(this.$t('cmdb.preference_relation.tips4'))
|
||||
return
|
||||
}
|
||||
this.form = {
|
||||
...this.form,
|
||||
is_show_leaf_node: checked,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@@ -1,61 +1,51 @@
|
||||
<template>
|
||||
<div :style="{ marginBottom: '-24px', overflow: 'hidden' }">
|
||||
<div v-if="relationViews.name2id && relationViews.name2id.length" class="relation-views-wrapper">
|
||||
<div class="cmdb-views-header">
|
||||
<span
|
||||
class="cmdb-views-header-title"
|
||||
>{{ $route.meta.name }}
|
||||
<div
|
||||
class="ops-list-batch-action"
|
||||
:style="{ backgroundColor: '#c0ceeb' }"
|
||||
v-if="showBatchLevel !== null && batchTreeKey && batchTreeKey.length"
|
||||
>
|
||||
<span
|
||||
@click="
|
||||
() => {
|
||||
$refs.grantModal.open('depart')
|
||||
}
|
||||
"
|
||||
>{{ $t('grant') }}</span
|
||||
>
|
||||
<a-divider type="vertical" />
|
||||
<span
|
||||
@click="
|
||||
() => {
|
||||
$refs.revokeModal.open()
|
||||
}
|
||||
"
|
||||
>{{ $t('revoke') }}</span
|
||||
>
|
||||
<template v-if="showBatchLevel > 0">
|
||||
<a-divider type="vertical" />
|
||||
<span @click="batchDeleteCIRelationFromTree">{{ $t('delete') }}</span>
|
||||
</template>
|
||||
<a-divider type="vertical" />
|
||||
<span
|
||||
@click="
|
||||
() => {
|
||||
showBatchLevel = null
|
||||
batchTreeKey = []
|
||||
}
|
||||
"
|
||||
>{{ $t('cancel') }}</span
|
||||
>
|
||||
<span>{{ $t('selectRows', { rows: batchTreeKey.length }) }}</span>
|
||||
</div>
|
||||
</span>
|
||||
<a-button size="small" icon="user-add" type="primary" ghost @click="handlePerm">{{ $t('grant') }}</a-button>
|
||||
</div>
|
||||
<SplitPane
|
||||
:min="200"
|
||||
:max="500"
|
||||
:paneLengthPixel.sync="paneLengthPixel"
|
||||
:appName="`cmdb-relation-views-${viewId}`"
|
||||
triggerColor="#F0F5FF"
|
||||
:triggerLength="18"
|
||||
>
|
||||
<template #one>
|
||||
<div class="relation-views-left" :style="{ height: `${windowHeight - 115}px` }">
|
||||
<div class="relation-views-left-header" :title="$route.meta.name">{{ $route.meta.name }}</div>
|
||||
<div
|
||||
class="ops-list-batch-action"
|
||||
:style="{ marginBottom: '10px' }"
|
||||
v-if="showBatchLevel !== null && batchTreeKey && batchTreeKey.length"
|
||||
>
|
||||
<span
|
||||
@click="
|
||||
() => {
|
||||
$refs.grantModal.open('depart')
|
||||
}
|
||||
"
|
||||
>{{ $t('grant') }}</span
|
||||
>
|
||||
<span
|
||||
@click="
|
||||
() => {
|
||||
$refs.revokeModal.open()
|
||||
}
|
||||
"
|
||||
>{{ $t('revoke') }}</span
|
||||
>
|
||||
<template v-if="showBatchLevel > 0">
|
||||
<span @click="batchDeleteCIRelationFromTree">{{ $t('cmdb.serviceTree.remove') }}</span>
|
||||
</template>
|
||||
<span
|
||||
@click="
|
||||
() => {
|
||||
showBatchLevel = null
|
||||
batchTreeKey = []
|
||||
}
|
||||
"
|
||||
>{{ $t('cancel') }}</span
|
||||
>
|
||||
<span>{{ $t('selectRows', { rows: batchTreeKey.length }) }}</span>
|
||||
</div>
|
||||
<a-tree
|
||||
:selectedKeys="selectedKeys"
|
||||
:loadData="onLoadData"
|
||||
@@ -65,9 +55,10 @@
|
||||
@drop="onDrop"
|
||||
:expandedKeys="expandedKeys"
|
||||
>
|
||||
<template #title="{ key: treeKey, title, isLeaf }">
|
||||
<template #title="{ key: treeKey, title,number, isLeaf }">
|
||||
<ContextMenu
|
||||
:title="title"
|
||||
:number="number"
|
||||
:treeKey="treeKey"
|
||||
:levels="levels"
|
||||
:isLeaf="isLeaf"
|
||||
@@ -79,51 +70,69 @@
|
||||
:showBatchLevel="showBatchLevel"
|
||||
:batchTreeKey="batchTreeKey"
|
||||
@clickCheckbox="clickCheckbox"
|
||||
@updateTreeData="updateTreeData"
|
||||
/>
|
||||
</template>
|
||||
</a-tree>
|
||||
</div>
|
||||
</template>
|
||||
<template #two>
|
||||
<div id="relation-views-right" class="relation-views-right" :style="{ height: `${windowHeight - 115}px` }">
|
||||
<a-tabs :activeKey="String(currentTypeId[0])" type="card" @change="changeCIType" class="ops-tab">
|
||||
<a-tab-pane v-for="item in showTypes" :key="`${item.id}`" :tab="item.alias || item.name"> </a-tab-pane>
|
||||
<div id="relation-views-right" class="relation-views-right" :style="{ height: `${windowHeight - 64}px` }">
|
||||
<a-tabs :activeKey="currentTypeId[0]" class="ops-tab" @change="changeCIType" size="small">
|
||||
<a-tab-pane v-for="item in showTypes" :key="item.id" :tab="item.alias || item.name"> </a-tab-pane>
|
||||
<a-space slot="tabBarExtraContent">
|
||||
<a-button
|
||||
v-if="isLeaf"
|
||||
type="primary"
|
||||
class="ops-button-ghost"
|
||||
ghost
|
||||
@click="$refs.create.handleOpen(true, 'create')"
|
||||
><ops-icon type="veops-increase" />{{ $t('create') }}</a-button
|
||||
>
|
||||
<a-button icon="user-add" type="primary" ghost @click="handlePerm" class="ops-button-ghost">{{
|
||||
$t('grant')
|
||||
}}</a-button>
|
||||
<EditAttrsPopover
|
||||
:typeId="Number(currentTypeId[0])"
|
||||
class="operation-icon"
|
||||
@refresh="refreshAfterEditAttrs"
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
class="ops-button-ghost"
|
||||
><ops-icon type="veops-configuration_table" />{{ $t('cmdb.configTable') }}</a-button
|
||||
>
|
||||
</EditAttrsPopover>
|
||||
</a-space>
|
||||
</a-tabs>
|
||||
<SearchForm
|
||||
ref="search"
|
||||
@refresh="refreshTable"
|
||||
:preferenceAttrList="preferenceAttrList"
|
||||
:isShowExpression="true"
|
||||
:isShowExpression="!(isLeaf && isShowBatchIcon)"
|
||||
:typeId="Number(currentTypeId[0])"
|
||||
@copyExpression="copyExpression"
|
||||
type="relationView"
|
||||
:style="{ padding: '0 12px', marginTop: '16px' }"
|
||||
/>
|
||||
<div class="relation-views-right-bar">
|
||||
<a-space>
|
||||
<a-button v-if="isLeaf" type="primary" size="small" @click="$refs.create.handleOpen(true, 'create')">{{
|
||||
$t('create')
|
||||
}}</a-button>
|
||||
|
||||
>
|
||||
<PreferenceSearch
|
||||
v-if="!(isLeaf && isShowBatchIcon)"
|
||||
ref="preferenceSearch"
|
||||
@getQAndSort="getQAndSort"
|
||||
@setParamsFromPreferenceSearch="setParamsFromPreferenceSearch"
|
||||
/>
|
||||
<a-space slot="extraContent">
|
||||
<div class="ops-list-batch-action" v-if="isLeaf && isShowBatchIcon">
|
||||
<template v-if="selectedRowKeys.length">
|
||||
<span @click="$refs.create.handleOpen(true, 'update')">{{ $t('update') }}</span>
|
||||
<a-divider type="vertical" />
|
||||
<span @click="openBatchDownload">{{ $t('download') }}</span>
|
||||
<a-divider type="vertical" />
|
||||
<span @click="batchDelete">{{ $t('cmdb.ciType.deleteInstance') }}</span>
|
||||
<a-divider type="vertical" />
|
||||
<span @click="batchDeleteCIRelation">{{ $t('cmdb.history.deleteRelation') }}</span>
|
||||
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<PreferenceSearch
|
||||
ref="preferenceSearch"
|
||||
@getQAndSort="getQAndSort"
|
||||
@setParamsFromPreferenceSearch="setParamsFromPreferenceSearch"
|
||||
/>
|
||||
</a-space>
|
||||
</div>
|
||||
</SearchForm>
|
||||
<vxe-table
|
||||
:id="`cmdb-relation-${viewId}-${currentTypeId}`"
|
||||
border
|
||||
@@ -283,14 +292,9 @@
|
||||
</template>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-column align="left" field="operate" fixed="right" width="120">
|
||||
<vxe-column align="left" field="operate" fixed="right" width="80">
|
||||
<template #header>
|
||||
<span>{{ $t('operation') }}</span>
|
||||
<EditAttrsPopover
|
||||
:typeId="Number(currentTypeId[0])"
|
||||
class="operation-icon"
|
||||
@refresh="refreshAfterEditAttrs"
|
||||
/>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<a-space>
|
||||
@@ -482,6 +486,9 @@ export default {
|
||||
contextMenuKey: null,
|
||||
showBatchLevel: null,
|
||||
batchTreeKey: [],
|
||||
|
||||
statisticsObj: {},
|
||||
viewOption: {},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -490,7 +497,7 @@ export default {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableHeight() {
|
||||
return this.windowHeight - 295
|
||||
return this.windowHeight - 244
|
||||
},
|
||||
selectedKeys() {
|
||||
if (this.treeKeys.length <= 1) {
|
||||
@@ -519,6 +526,15 @@ export default {
|
||||
.map((item) => item.split('%')[0])
|
||||
.join(',')
|
||||
},
|
||||
is_show_leaf_node() {
|
||||
return this.viewOption?.is_show_leaf_node ?? true
|
||||
},
|
||||
is_show_tree_node() {
|
||||
return this.viewOption?.is_show_tree_node ?? false
|
||||
},
|
||||
leaf_tree_sort() {
|
||||
return this.viewOption?.sort ?? 1
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
@@ -638,6 +654,7 @@ export default {
|
||||
if (q && q[0] === ',') {
|
||||
q = q.slice(1)
|
||||
}
|
||||
|
||||
if (this.treeKeys.length === 0) {
|
||||
// await this.judgeCITypes(q)
|
||||
if (!refreshType) {
|
||||
@@ -684,8 +701,10 @@ export default {
|
||||
.map((item) => item.split('%')[0])
|
||||
.join(',')}`
|
||||
}
|
||||
const typeId = parseInt(this.treeKeys[this.treeKeys.length - 1].split('%')[1])
|
||||
|
||||
await this.judgeCITypes()
|
||||
|
||||
const typeId = parseInt(this.treeKeys[this.treeKeys.length - 1].split('%')[1])
|
||||
let level = []
|
||||
if (!this.leaf.includes(typeId)) {
|
||||
let startIdx = 0
|
||||
@@ -705,8 +724,8 @@ export default {
|
||||
} else {
|
||||
level = [1]
|
||||
}
|
||||
q += `&level=${level.join(',')}`
|
||||
await this.judgeCITypes(q)
|
||||
|
||||
q += `&level=${this.topo_flatten.includes(this.currentTypeId[0]) ? 1 : level.join(',')}`
|
||||
if (!refreshType) {
|
||||
this.loadNoRoot(this.treeKeys[this.treeKeys.length - 1], level)
|
||||
}
|
||||
@@ -793,69 +812,106 @@ export default {
|
||||
this.selectedRowKeys = []
|
||||
this.currentTypeId = [typeId]
|
||||
this.loadColumns()
|
||||
this.$nextTick(() => {
|
||||
this.refreshTable()
|
||||
})
|
||||
// this.$nextTick(() => {
|
||||
// this.refreshTable()
|
||||
// })
|
||||
},
|
||||
|
||||
async judgeCITypes(q) {
|
||||
const showTypeIds = []
|
||||
let _showTypes = []
|
||||
async judgeCITypes() {
|
||||
let _showTypeIds = []
|
||||
|
||||
let _showTypes = []
|
||||
if (this.treeKeys.length) {
|
||||
const typeId = parseInt(this.treeKeys[this.treeKeys.length - 1].split('%')[1])
|
||||
|
||||
_showTypes = this.node2ShowTypes[typeId + '']
|
||||
_showTypes.forEach((item) => {
|
||||
_showTypeIds.push(item.id)
|
||||
})
|
||||
} else {
|
||||
_showTypeIds = JSON.parse(JSON.stringify(this.origShowTypeIds))
|
||||
_showTypes = JSON.parse(JSON.stringify(this.origShowTypes))
|
||||
}
|
||||
const promises = _showTypeIds.map((typeId) => {
|
||||
let _q = (`q=_type:${typeId},` + q).replace(/count=\d*/, 'count=1')
|
||||
if (Object.values(this.level2constraint).includes('2')) {
|
||||
_q = _q + `&has_m2m=1`
|
||||
if (this.is_show_leaf_node) {
|
||||
const typeId = parseInt(this.treeKeys[this.treeKeys.length - 1].split('%')[1])
|
||||
_showTypeIds = _.cloneDeep(this.origShowTypeIds)
|
||||
_showTypes = _.cloneDeep(this.node2ShowTypes[typeId])
|
||||
}
|
||||
if (this.root_parent_path) {
|
||||
_q = _q + `&root_parent_path=${this.root_parent_path}`
|
||||
}
|
||||
// if (this.treeKeys.length === 0) {
|
||||
// return searchCI2(_q).then((res) => {
|
||||
// if (res.numfound !== 0) {
|
||||
// showTypeIds.push(typeId)
|
||||
// }
|
||||
// })
|
||||
// } else {
|
||||
_q = _q + `&descendant_ids=${this.descendant_ids}`
|
||||
return searchCIRelation(_q).then((res) => {
|
||||
if (res.numfound !== 0) {
|
||||
showTypeIds.push(typeId)
|
||||
}
|
||||
})
|
||||
// }
|
||||
})
|
||||
await Promise.all(promises).then(async () => {
|
||||
if (showTypeIds.length && showTypeIds.sort().join(',') !== this.showTypeIds.sort().join(',')) {
|
||||
const showTypes = []
|
||||
_showTypes.forEach((item) => {
|
||||
if (showTypeIds.includes(item.id)) {
|
||||
showTypes.push(item)
|
||||
if (this.is_show_tree_node) {
|
||||
const treeKeyTypeId = Number(this.treeKeys.slice(-1)[0].split('%')[1])
|
||||
const _idx = this.topo_flatten.findIndex((item) => item === treeKeyTypeId)
|
||||
if (_idx > -1 && _idx < this.topo_flatten.length - 1) {
|
||||
const _showTreeTypeId = this.topo_flatten[_idx + 1]
|
||||
const _showTreeTypes = this.relationViews.id2type[_showTreeTypeId]
|
||||
if (this.leaf_tree_sort === 1) {
|
||||
_showTypeIds.push(_showTreeTypeId)
|
||||
_showTypes.push(_showTreeTypes)
|
||||
} else {
|
||||
_showTypeIds.unshift(_showTreeTypeId)
|
||||
_showTypes.unshift(_showTreeTypes)
|
||||
}
|
||||
})
|
||||
this.showTypes = showTypes
|
||||
this.showTypeIds = showTypeIds
|
||||
if (
|
||||
!this.currentTypeId.length ||
|
||||
(this.currentTypeId.length && !this.showTypeIds.includes(this.currentTypeId[0]))
|
||||
) {
|
||||
this.currentTypeId = [this.showTypeIds[0]]
|
||||
await this.loadColumns()
|
||||
}
|
||||
}
|
||||
})
|
||||
this.showTypeIds = _showTypeIds
|
||||
this.showTypes = _showTypes
|
||||
} else {
|
||||
this.showTypeIds = _.cloneDeep(this.origShowTypeIds)
|
||||
this.showTypes = JSON.parse(JSON.stringify(this.origShowTypes))
|
||||
}
|
||||
if (
|
||||
!this.currentTypeId.length ||
|
||||
(this.currentTypeId.length && !this.showTypeIds.includes(this.currentTypeId[0]))
|
||||
) {
|
||||
this.currentTypeId = [this.showTypeIds[0]]
|
||||
await this.loadColumns()
|
||||
}
|
||||
// const showTypeIds = []
|
||||
// let _showTypes = []
|
||||
// let _showTypeIds = []
|
||||
|
||||
// if (this.treeKeys.length) {
|
||||
// const typeId = parseInt(this.treeKeys[this.treeKeys.length - 1].split('%')[1])
|
||||
|
||||
// _showTypes = this.node2ShowTypes[typeId + '']
|
||||
// _showTypes.forEach((item) => {
|
||||
// _showTypeIds.push(item.id)
|
||||
// })
|
||||
// } else {
|
||||
// _showTypeIds = JSON.parse(JSON.stringify(this.origShowTypeIds))
|
||||
// _showTypes = JSON.parse(JSON.stringify(this.origShowTypes))
|
||||
// }
|
||||
// const promises = _showTypeIds.map((typeId) => {
|
||||
// let _q = (`q=_type:${typeId},` + q).replace(/count=\d*/, 'count=1')
|
||||
// if (Object.values(this.level2constraint).includes('2')) {
|
||||
// _q = _q + `&has_m2m=1`
|
||||
// }
|
||||
// if (this.root_parent_path) {
|
||||
// _q = _q + `&root_parent_path=${this.root_parent_path}`
|
||||
// }
|
||||
// // if (this.treeKeys.length === 0) {
|
||||
// // return searchCI2(_q).then((res) => {
|
||||
// // if (res.numfound !== 0) {
|
||||
// // showTypeIds.push(typeId)
|
||||
// // }
|
||||
// // })
|
||||
// // } else {
|
||||
// _q = _q + `&descendant_ids=${this.descendant_ids}`
|
||||
// return searchCIRelation(_q).then((res) => {
|
||||
// if (res.numfound !== 0) {
|
||||
// showTypeIds.push(typeId)
|
||||
// }
|
||||
// })
|
||||
// // }
|
||||
// })
|
||||
// await Promise.all(promises).then(async () => {
|
||||
// if (showTypeIds.length && showTypeIds.sort().join(',') !== this.showTypeIds.sort().join(',')) {
|
||||
// const showTypes = []
|
||||
// _showTypes.forEach((item) => {
|
||||
// if (showTypeIds.includes(item.id)) {
|
||||
// showTypes.push(item)
|
||||
// }
|
||||
// })
|
||||
// console.log(showTypes)
|
||||
// this.showTypes = showTypes
|
||||
// this.showTypeIds = showTypeIds
|
||||
// if (
|
||||
// !this.currentTypeId.length ||
|
||||
// (this.currentTypeId.length && !this.showTypeIds.includes(this.currentTypeId[0]))
|
||||
// ) {
|
||||
// this.currentTypeId = [this.showTypeIds[0]]
|
||||
// await this.loadColumns()
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
},
|
||||
|
||||
async loadRoot() {
|
||||
@@ -980,7 +1036,8 @@ export default {
|
||||
const treeData = []
|
||||
facet.forEach((item) => {
|
||||
treeData.push({
|
||||
title: `${item[0]} (${item[1]})`,
|
||||
title: item[0],
|
||||
number: item[1],
|
||||
key: this.treeKeys.join('@^@') + '@^@' + item[2] + '%' + item[3] + '%' + `{"${item[4]}":"${item[0]}"}`,
|
||||
isLeaf: this.leaf.includes(item[3]),
|
||||
id: item[2],
|
||||
@@ -1041,16 +1098,19 @@ export default {
|
||||
this.leaf = this.relationViews.views[this.viewName].leaf
|
||||
this.currentView = `${this.viewId}`
|
||||
this.typeId = this.levels[0][0]
|
||||
this.viewOption = this.relationViews.views[this.viewName].option ?? {}
|
||||
this.refreshTable()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async loadColumns() {
|
||||
this.getAttributeList()
|
||||
const res = await getSubscribeAttributes(this.currentTypeId[0])
|
||||
this.preferenceAttrList = res.attributes
|
||||
this.calcColumns()
|
||||
if (this.currentTypeId[0]) {
|
||||
this.getAttributeList()
|
||||
const res = await getSubscribeAttributes(this.currentTypeId[0])
|
||||
this.preferenceAttrList = res.attributes
|
||||
this.calcColumns()
|
||||
}
|
||||
},
|
||||
|
||||
calcColumns() {
|
||||
@@ -1183,7 +1243,6 @@ export default {
|
||||
const dragId = _splitDragKey[_splitDragKey.length - 1].split('%')[0]
|
||||
// const targetObj = JSON.parse(_splitTargetKey[_splitTargetKey.length - 1].split('%')[2])
|
||||
const targetId = _splitTargetKey[_splitTargetKey.length - 1].split('%')[0]
|
||||
console.log(_splitDragKey)
|
||||
// TODO 拖拽这里不造咋弄 等等再说吧
|
||||
batchUpdateCIRelationChildren([dragId], [targetId]).then((res) => {
|
||||
this.reload()
|
||||
@@ -1354,6 +1413,9 @@ export default {
|
||||
...data,
|
||||
}
|
||||
this.initialInstanceList = _initialInstanceList
|
||||
this.$nextTick(() => {
|
||||
this.refreshTable()
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
@@ -1579,7 +1641,6 @@ export default {
|
||||
.split('@^@')
|
||||
.filter((item) => !!item)
|
||||
.reverse()
|
||||
console.log(needGrantNodes)
|
||||
|
||||
const needGrantRids = [...department, ...user]
|
||||
const floor = Math.ceil(needGrantRids.length / 6)
|
||||
@@ -1687,6 +1748,29 @@ export default {
|
||||
this.showBatchLevel = null
|
||||
this.batchTreeKey = []
|
||||
},
|
||||
findNode(node, target) {
|
||||
for (let i = 0; i < node.length; i++) {
|
||||
if (node[i].id === target) {
|
||||
return node[i]
|
||||
}
|
||||
if (node[i].children && node[i].children.length) {
|
||||
for (let i = 0; i < node[i].children.length; i++) {
|
||||
const found = this.findNode(node[i].children, target)
|
||||
if (found) {
|
||||
return found
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
},
|
||||
updateTreeData(ciId, value) {
|
||||
const _find = this.findNode(this.treeData, ciId)
|
||||
if (_find) {
|
||||
this.$set(_find, 'title', value)
|
||||
}
|
||||
this.refreshTable()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1701,15 +1785,24 @@ export default {
|
||||
width: 100%;
|
||||
float: left;
|
||||
position: relative;
|
||||
// transition: all 0.3s;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
padding: 12px;
|
||||
border-top-left-radius: 15px;
|
||||
border-top-right-radius: 15px;
|
||||
padding: 0;
|
||||
&:hover {
|
||||
overflow: auto;
|
||||
}
|
||||
.relation-views-left-header {
|
||||
border-left: 4px solid @primary-color;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
padding-left: 12px;
|
||||
margin-bottom: 12px;
|
||||
color: @text-color_1;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: default;
|
||||
}
|
||||
.ant-tree li {
|
||||
padding: 2px 0;
|
||||
}
|
||||
@@ -1732,15 +1825,8 @@ export default {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
background-color: #fff;
|
||||
.relation-views-right-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
padding: 20px;
|
||||
border-radius: @border-radius-box;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -6,7 +6,10 @@
|
||||
}"
|
||||
@click="clickNode"
|
||||
>
|
||||
<span>
|
||||
<span class="relation-views-node-switch">
|
||||
<a-icon v-if="!isLeaf" :type="switchIcon"></a-icon>
|
||||
</span>
|
||||
<span class="relation-views-node-content">
|
||||
<a-checkbox @click.stop="clickCheckbox" class="relation-views-node-checkbox" v-if="showCheckbox" />
|
||||
<template v-if="icon">
|
||||
<img
|
||||
@@ -24,61 +27,82 @@
|
||||
/>
|
||||
<span class="relation-views-node-icon" v-else>{{ icon ? icon[0].toUpperCase() : 'i' }}</span>
|
||||
</template>
|
||||
<span class="relation-views-node-title">{{ this.title }}</span>
|
||||
</span>
|
||||
<a-dropdown>
|
||||
<a-menu slot="overlay" @click="({ key: menuKey }) => this.onContextMenuClick(this.treeKey, menuKey)">
|
||||
<template v-if="showBatchLevel === null">
|
||||
<a-menu-item
|
||||
v-for="item in menuList"
|
||||
:key="item.id"
|
||||
><a-icon type="plus-circle" />{{ $t('new') }} {{ item.alias }}</a-menu-item
|
||||
>
|
||||
<a-menu-item
|
||||
v-if="showDelete"
|
||||
key="delete"
|
||||
><ops-icon type="icon-xianxing-delete" />{{ $t('cmdb.serviceTree.deleteNode') }}</a-menu-item
|
||||
>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="grant"><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item>
|
||||
<a-menu-item key="revoke"><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item>
|
||||
<a-menu-item key="view"><a-icon type="eye" />{{ $t('cmdb.serviceTree.view') }}</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item
|
||||
key="batch"
|
||||
><ops-icon type="icon-xianxing-copy" />{{ $t('cmdb.serviceTree.batch') }}</a-menu-item
|
||||
>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-menu-item
|
||||
:disabled="!batchTreeKey || !batchTreeKey.length"
|
||||
key="batchGrant"
|
||||
><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item
|
||||
>
|
||||
<a-menu-item
|
||||
:disabled="!batchTreeKey || !batchTreeKey.length"
|
||||
key="batchRevoke"
|
||||
><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item
|
||||
>
|
||||
<a-menu-divider />
|
||||
<template v-if="showBatchLevel > 0">
|
||||
<span class="relation-views-node-title" v-if="!isEditNodeName" :title="title">{{ title }}</span>
|
||||
<a-input
|
||||
ref="input"
|
||||
@blur="changeNodeName"
|
||||
@pressEnter="
|
||||
() => {
|
||||
$refs.input.blur()
|
||||
}
|
||||
"
|
||||
size="small"
|
||||
v-else
|
||||
v-model="editNodeName"
|
||||
:style="{ marginLeft: '5px' }"
|
||||
/>
|
||||
<span class="relation-views-node-number">{{ number }}</span>
|
||||
<a-dropdown overlayClassName="relation-views-node-dropdown" :overlayStyle="{ width: '200px' }">
|
||||
<a-menu slot="overlay" @click="({ key: menuKey }) => onContextMenuClick(this.treeKey, menuKey)">
|
||||
<template v-if="showBatchLevel === null">
|
||||
<a-divider orientation="left">{{ $t('cmdb.relation') }}</a-divider>
|
||||
<a-menu-item
|
||||
v-for="item in menuList"
|
||||
:key="item.id"
|
||||
><a-icon type="plus-circle" />{{ $t('add') }} {{ item.alias }}</a-menu-item
|
||||
>
|
||||
<a-menu-item
|
||||
v-if="showDelete"
|
||||
key="delete"
|
||||
><ops-icon type="icon-xianxing-delete" />{{
|
||||
$t('cmdb.serviceTree.deleteNode', { name: title })
|
||||
}}</a-menu-item
|
||||
>
|
||||
<a-divider orientation="left">{{ $t('cmdb.components.perm') }}</a-divider>
|
||||
<a-menu-item key="grant"><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item>
|
||||
<a-menu-item key="revoke"><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item>
|
||||
<a-menu-item key="view"><a-icon type="eye" />{{ $t('cmdb.serviceTree.view') }}</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item
|
||||
key="editNodeName"
|
||||
><ops-icon type="icon-xianxing-edit" />{{ $t('cmdb.serviceTree.editNodeName') }}</a-menu-item
|
||||
>
|
||||
<a-menu-item
|
||||
key="batch"
|
||||
><ops-icon type="veops-copy" />{{ $t('cmdb.serviceTree.batch') }}</a-menu-item
|
||||
>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-menu-item
|
||||
:disabled="!batchTreeKey || !batchTreeKey.length"
|
||||
key="batchDelete"
|
||||
><ops-icon type="icon-xianxing-delete" />{{ $t('delete') }}</a-menu-item
|
||||
key="batchGrant"
|
||||
><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item
|
||||
>
|
||||
<a-menu-item
|
||||
:disabled="!batchTreeKey || !batchTreeKey.length"
|
||||
key="batchRevoke"
|
||||
><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item
|
||||
>
|
||||
<a-menu-divider />
|
||||
<template v-if="showBatchLevel > 0">
|
||||
<a-menu-item
|
||||
:disabled="!batchTreeKey || !batchTreeKey.length"
|
||||
key="batchDelete"
|
||||
><ops-icon type="icon-xianxing-delete" />{{ $t('cmdb.serviceTree.remove') }}</a-menu-item
|
||||
>
|
||||
<a-menu-divider />
|
||||
</template>
|
||||
<a-menu-item key="batchCancel"><a-icon type="close-circle" />{{ $t('cancel') }}</a-menu-item>
|
||||
</template>
|
||||
<a-menu-item key="batchCancel"><a-icon type="close-circle" />{{ $t('cancel') }}</a-menu-item>
|
||||
</template>
|
||||
</a-menu>
|
||||
<a-icon class="relation-views-node-operation" type="ellipsis" />
|
||||
</a-dropdown>
|
||||
<a-icon :style="{ fontSize: '10px' }" v-if="childLength && !isLeaf" :type="switchIcon"></a-icon>
|
||||
</a-menu>
|
||||
<a-icon class="relation-views-node-operation" type="ellipsis" />
|
||||
</a-dropdown>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { updateCI } from '../../../api/ci.js'
|
||||
export default {
|
||||
name: 'ContextMenu',
|
||||
props: {
|
||||
@@ -86,6 +110,10 @@ export default {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
number: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
treeKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
@@ -121,13 +149,14 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
switchIcon: 'down',
|
||||
switchIcon: 'caret-right',
|
||||
isEditNodeName: false,
|
||||
editNodeName: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
childLength() {
|
||||
const reg = /(?<=\()\S+(?=\))/g
|
||||
return Number(this.title.match(reg)[0])
|
||||
return this.number
|
||||
},
|
||||
splitTreeKey() {
|
||||
return this.treeKey.split('@^@')
|
||||
@@ -175,26 +204,63 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
onContextMenuClick(treeKey, menuKey) {
|
||||
if (menuKey === 'editNodeName') {
|
||||
this.isEditNodeName = true
|
||||
this.editNodeName = this.title
|
||||
this.$nextTick(() => {
|
||||
this.$refs.input.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
this.$emit('onContextMenuClick', treeKey, menuKey)
|
||||
},
|
||||
clickNode() {
|
||||
this.$emit('onNodeClick', this.treeKey)
|
||||
this.switchIcon = this.switchIcon === 'down' ? 'up' : 'down'
|
||||
this.switchIcon = this.switchIcon === 'caret-right' ? 'caret-down' : 'caret-right'
|
||||
},
|
||||
clickCheckbox() {
|
||||
this.$emit('clickCheckbox', this.treeKey)
|
||||
},
|
||||
changeNodeName(e) {
|
||||
const value = e.target.value
|
||||
if (value !== this.title) {
|
||||
const ci = this.treeKey
|
||||
.split('@^@')
|
||||
.slice(-1)[0]
|
||||
.split('%')
|
||||
const unique = Object.keys(JSON.parse(ci[2]))[0]
|
||||
const ciId = Number(ci[0])
|
||||
|
||||
updateCI(ciId, { [unique]: value }).then((res) => {
|
||||
this.$message.success(this.$t('updateSuccess'))
|
||||
this.$emit('updateTreeData', ciId, value)
|
||||
})
|
||||
}
|
||||
this.isEditNodeName = false
|
||||
this.editNodeName = ''
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.relation-views-node {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
> span {
|
||||
.relation-views-node-switch {
|
||||
display: inline-block;
|
||||
width: 15px;
|
||||
color: @text-color_5;
|
||||
i {
|
||||
opacity: 0;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
.relation-views-node-content {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
@@ -215,16 +281,21 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
width: calc(100% - 16px);
|
||||
flex: 1;
|
||||
color: @text-color_1;
|
||||
}
|
||||
.relation-views-node-number {
|
||||
color: @text-color_4;
|
||||
font-size: 12px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
.relation-views-node-operation {
|
||||
opacity: 0;
|
||||
width: 15px;
|
||||
}
|
||||
}
|
||||
.relation-views-node-operation {
|
||||
display: none;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.relation-views-node-checkbox,
|
||||
.relation-views-node-moveright {
|
||||
.relation-views-node-checkbox {
|
||||
> span {
|
||||
.relation-views-node-checkbox {
|
||||
margin-right: 10px;
|
||||
@@ -234,14 +305,16 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.relation-views-left .ant-tree:hover {
|
||||
.relation-views-node .relation-views-node-switch i {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.relation-views-left .ant-tree-node-content-wrapper:hover {
|
||||
.relation-views-node-operation {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
@import '~@/style/static.less';
|
||||
.relation-views-left {
|
||||
ul:has(.relation-views-node-checkbox) > li > ul {
|
||||
margin-left: 26px;
|
||||
@@ -249,5 +322,28 @@ export default {
|
||||
ul:has(.relation-views-node-checkbox) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
.ant-tree-node-content-wrapper:hover {
|
||||
.relation-views-node-operation {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.ant-tree li .ant-tree-node-content-wrapper.ant-tree-node-selected,
|
||||
.ant-tree li .ant-tree-node-content-wrapper:hover {
|
||||
background-color: @primary-color_3;
|
||||
}
|
||||
}
|
||||
|
||||
.relation-views-node-dropdown {
|
||||
.ant-divider {
|
||||
margin: 0;
|
||||
.ant-divider-inner-text {
|
||||
font-size: 12px;
|
||||
color: @text-color_3;
|
||||
}
|
||||
}
|
||||
.ant-dropdown-menu-item {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,196 +1,194 @@
|
||||
<template>
|
||||
<div id="resource_search" :style="{ height: fromCronJob ? `${windowHeight - 48}px` : `${windowHeight - 90}px` }">
|
||||
<div
|
||||
class="resource-search"
|
||||
id="resource_search"
|
||||
:style="{ height: fromCronJob ? `${windowHeight - 48}px` : `${windowHeight - 64}px` }"
|
||||
>
|
||||
<div class="cmdb-views-header">
|
||||
<span>
|
||||
<span class="cmdb-views-header-title">{{ $t('cmdb.menu.ciSearch') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div :style="{ backgroundColor: '#fff', padding: '12px', borderRadius: '15px' }">
|
||||
<SearchForm
|
||||
ref="search"
|
||||
type="resourceSearch"
|
||||
@refresh="handleSearch"
|
||||
:preferenceAttrList="allAttributesList"
|
||||
@updateAllAttributesList="updateAllAttributesList"
|
||||
@copyExpression="copyExpression"
|
||||
/>
|
||||
<div
|
||||
<a-button
|
||||
v-if="!fromCronJob"
|
||||
:style="{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
height: '32px',
|
||||
marginBottom: '5px',
|
||||
alignItems: 'center',
|
||||
}"
|
||||
>
|
||||
<a-button icon="download" type="primary" ghost size="small" @click="handleExport">{{
|
||||
$t('download')
|
||||
}}</a-button>
|
||||
<PreferenceSearch
|
||||
ref="preferenceSearch"
|
||||
@getQAndSort="getQAndSort"
|
||||
@setParamsFromPreferenceSearch="setParamsFromPreferenceSearch"
|
||||
/>
|
||||
</div>
|
||||
<vxe-table
|
||||
:id="`cmdb-resource`"
|
||||
border
|
||||
keep-source
|
||||
show-overflow
|
||||
resizable
|
||||
ref="xTable"
|
||||
size="small"
|
||||
row-id="_id"
|
||||
:loading="loading"
|
||||
:height="fromCronJob ? windowHeight - 180 : windowHeight - 250"
|
||||
show-header-overflow
|
||||
highlight-hover-row
|
||||
:data="instanceList"
|
||||
:sort-config="{ remote: true, trigger: 'cell' }"
|
||||
@sort-change="handleSortCol"
|
||||
:row-key="true"
|
||||
:column-key="true"
|
||||
:cell-style="getCellStyle"
|
||||
:scroll-y="{ enabled: true, gt: 20 }"
|
||||
:scroll-x="{ enabled: true, gt: 0 }"
|
||||
:export-config="{
|
||||
isColgroup: true,
|
||||
type: 'xlsx',
|
||||
types: ['xlsx', 'csv', 'html', 'xml', 'txt'],
|
||||
mode: 'current',
|
||||
modes: ['current'],
|
||||
isFooter: false,
|
||||
isHeader: true,
|
||||
isColgroup: true,
|
||||
}"
|
||||
class="ops-unstripe-table"
|
||||
:style="{ margin: '0 -12px' }"
|
||||
:custom-config="{ storage: true }"
|
||||
icon="download"
|
||||
type="primary"
|
||||
class="ops-button-ghost"
|
||||
ghost
|
||||
@click="handleExport"
|
||||
>{{ $t('download') }}</a-button
|
||||
>
|
||||
</div>
|
||||
<SearchForm
|
||||
ref="search"
|
||||
type="resourceSearch"
|
||||
@refresh="handleSearch"
|
||||
:preferenceAttrList="allAttributesList"
|
||||
@updateAllAttributesList="updateAllAttributesList"
|
||||
@copyExpression="copyExpression"
|
||||
>
|
||||
<PreferenceSearch
|
||||
v-if="!fromCronJob"
|
||||
ref="preferenceSearch"
|
||||
@getQAndSort="getQAndSort"
|
||||
@setParamsFromPreferenceSearch="setParamsFromPreferenceSearch"
|
||||
/>
|
||||
</SearchForm>
|
||||
<vxe-table
|
||||
:id="`cmdb-resource`"
|
||||
border
|
||||
keep-source
|
||||
show-overflow
|
||||
resizable
|
||||
ref="xTable"
|
||||
size="small"
|
||||
row-id="_id"
|
||||
:loading="loading"
|
||||
:height="fromCronJob ? windowHeight - 180 : windowHeight - 240"
|
||||
show-header-overflow
|
||||
highlight-hover-row
|
||||
:data="instanceList"
|
||||
:sort-config="{ remote: true, trigger: 'cell' }"
|
||||
@sort-change="handleSortCol"
|
||||
:row-key="true"
|
||||
:column-key="true"
|
||||
:cell-style="getCellStyle"
|
||||
:scroll-y="{ enabled: true, gt: 20 }"
|
||||
:scroll-x="{ enabled: true, gt: 0 }"
|
||||
:export-config="{
|
||||
isColgroup: true,
|
||||
type: 'xlsx',
|
||||
types: ['xlsx', 'csv', 'html', 'xml', 'txt'],
|
||||
mode: 'current',
|
||||
modes: ['current'],
|
||||
isFooter: false,
|
||||
isHeader: true,
|
||||
isColgroup: true,
|
||||
}"
|
||||
class="ops-unstripe-table"
|
||||
:custom-config="{ storage: true }"
|
||||
>
|
||||
<vxe-column
|
||||
v-if="instanceList.length"
|
||||
:title="$t('cmdb.ciType.ciType')"
|
||||
field="ci_type_alias"
|
||||
:width="100"
|
||||
fixed="left"
|
||||
></vxe-column>
|
||||
<vxe-colgroup v-for="colGroup in columnsGroup" :key="colGroup.value" :title="colGroup.label">
|
||||
<template #header>
|
||||
<span :style="{ display: 'inline-flex', alignItems: 'center' }">
|
||||
{{ colGroup.label }}
|
||||
<EditAttrsPopover
|
||||
:style="{ borderLeft: 'none', width: '30px', height: '38px', cursor: 'pointer' }"
|
||||
v-if="colGroup.isCiType"
|
||||
:typeId="Number(colGroup.id.split('-')[1])"
|
||||
@refresh="loadInstance"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
<vxe-column
|
||||
v-if="instanceList.length"
|
||||
:title="$t('cmdb.ciType.ciType')"
|
||||
field="ci_type_alias"
|
||||
:width="100"
|
||||
fixed="left"
|
||||
></vxe-column>
|
||||
<vxe-colgroup v-for="colGroup in columnsGroup" :key="colGroup.value" :title="colGroup.label">
|
||||
<template #header>
|
||||
<span :style="{ display: 'inline-flex', alignItems: 'center' }">
|
||||
{{ colGroup.label }}
|
||||
<EditAttrsPopover
|
||||
:style="{ borderLeft: 'none', width: '30px', height: '38px', cursor: 'pointer' }"
|
||||
v-if="colGroup.isCiType"
|
||||
:typeId="Number(colGroup.id.split('-')[1])"
|
||||
@refresh="loadInstance"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
<vxe-column
|
||||
v-for="(col, index) in colGroup.children"
|
||||
:key="`${col.field}_${index}`"
|
||||
:title="col.title"
|
||||
:field="col.field"
|
||||
:width="col.width"
|
||||
:minWidth="100"
|
||||
:cell-type="col.value_type === '2' ? 'string' : 'auto'"
|
||||
>
|
||||
<template v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice" #default="{row}">
|
||||
<span v-if="col.value_type === '6' && row[col.field]">{{ JSON.stringify(row[col.field]) }}</span>
|
||||
<a
|
||||
v-else-if="col.is_link && row[col.field]"
|
||||
:href="
|
||||
row[col.field].startsWith('http') || row[col.field].startsWith('https')
|
||||
? `${row[col.field]}`
|
||||
: `http://${row[col.field]}`
|
||||
"
|
||||
target="_blank"
|
||||
>{{ row[col.field] }}</a
|
||||
>
|
||||
<PasswordField
|
||||
v-else-if="col.is_password && row[col.field]"
|
||||
:ci_id="row._id"
|
||||
:attr_id="col.attr_id"
|
||||
></PasswordField>
|
||||
<template v-else-if="col.is_choice">
|
||||
<template v-if="col.is_list">
|
||||
<span
|
||||
v-for="value in row[col.field]"
|
||||
:key="value"
|
||||
:style="{
|
||||
borderRadius: '4px',
|
||||
padding: '1px 5px',
|
||||
margin: '2px',
|
||||
...getChoiceValueStyle(col, value),
|
||||
}"
|
||||
><ops-icon
|
||||
:style="{ color: getChoiceValueIcon(col, value).color }"
|
||||
:type="getChoiceValueIcon(col, value).name"
|
||||
/>{{ value }}</span
|
||||
>
|
||||
</template>
|
||||
v-for="(col, index) in colGroup.children"
|
||||
:key="`${col.field}_${index}`"
|
||||
:title="col.title"
|
||||
:field="col.field"
|
||||
:width="col.width"
|
||||
:minWidth="100"
|
||||
:cell-type="col.value_type === '2' ? 'string' : 'auto'"
|
||||
>
|
||||
<template v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice" #default="{row}">
|
||||
<span v-if="col.value_type === '6' && row[col.field]">{{ JSON.stringify(row[col.field]) }}</span>
|
||||
<a
|
||||
v-else-if="col.is_link && row[col.field]"
|
||||
:href="
|
||||
row[col.field].startsWith('http') || row[col.field].startsWith('https')
|
||||
? `${row[col.field]}`
|
||||
: `http://${row[col.field]}`
|
||||
"
|
||||
target="_blank"
|
||||
>{{ row[col.field] }}</a
|
||||
>
|
||||
<PasswordField
|
||||
v-else-if="col.is_password && row[col.field]"
|
||||
:ci_id="row._id"
|
||||
:attr_id="col.attr_id"
|
||||
></PasswordField>
|
||||
<template v-else-if="col.is_choice">
|
||||
<template v-if="col.is_list">
|
||||
<span
|
||||
v-else
|
||||
v-for="value in row[col.field]"
|
||||
:key="value"
|
||||
:style="{
|
||||
borderRadius: '4px',
|
||||
padding: '1px 5px',
|
||||
margin: '2px 0',
|
||||
...getChoiceValueStyle(col, row[col.field]),
|
||||
margin: '2px',
|
||||
...getChoiceValueStyle(col, value),
|
||||
}"
|
||||
>
|
||||
<ops-icon
|
||||
:style="{ color: getChoiceValueIcon(col, row[col.field]).color }"
|
||||
:type="getChoiceValueIcon(col, row[col.field]).name"
|
||||
/>
|
||||
{{ row[col.field] }}</span
|
||||
><ops-icon
|
||||
:style="{ color: getChoiceValueIcon(col, value).color }"
|
||||
:type="getChoiceValueIcon(col, value).name"
|
||||
/>{{ value }}</span
|
||||
>
|
||||
</template>
|
||||
<span
|
||||
v-else
|
||||
:style="{
|
||||
borderRadius: '4px',
|
||||
padding: '1px 5px',
|
||||
margin: '2px 0',
|
||||
...getChoiceValueStyle(col, row[col.field]),
|
||||
}"
|
||||
>
|
||||
<ops-icon
|
||||
:style="{ color: getChoiceValueIcon(col, row[col.field]).color }"
|
||||
:type="getChoiceValueIcon(col, row[col.field]).name"
|
||||
/>
|
||||
{{ row[col.field] }}</span
|
||||
>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-colgroup>
|
||||
|
||||
<template #empty>
|
||||
<div>
|
||||
<img :style="{ width: '200px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<div>{{ $t('noData') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #loading>
|
||||
<div style="height: 200px; line-height: 200px">{{ $t('loading') }}</div>
|
||||
</template>
|
||||
</vxe-table>
|
||||
<div :style="{ textAlign: 'right', marginTop: '4px' }">
|
||||
<a-pagination
|
||||
:showSizeChanger="true"
|
||||
:current="currentPage"
|
||||
size="small"
|
||||
:total="totalNumber"
|
||||
show-quick-jumper
|
||||
:page-size="pageSize"
|
||||
:page-size-options="pageSizeOptions"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
:show-total="
|
||||
(total, range) =>
|
||||
$t('pagination.total', {
|
||||
range0: range[0],
|
||||
range1: range[1],
|
||||
total,
|
||||
})
|
||||
"
|
||||
@change="
|
||||
(page) => {
|
||||
currentPage = page
|
||||
loadInstance(sortByTable)
|
||||
}
|
||||
"
|
||||
>
|
||||
<template slot="buildOptionText" slot-scope="props">
|
||||
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('itemsPerPage') }}</span>
|
||||
<span v-if="props.value === '100000'">{{ $t('all') }}</span>
|
||||
</template>
|
||||
</a-pagination>
|
||||
</div>
|
||||
</vxe-column>
|
||||
</vxe-colgroup>
|
||||
|
||||
<template #empty>
|
||||
<div>
|
||||
<img :style="{ width: '140px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<div>{{ $t('noData') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #loading>
|
||||
<div style="height: 200px; line-height: 200px">{{ $t('loading') }}</div>
|
||||
</template>
|
||||
</vxe-table>
|
||||
<div :style="{ textAlign: 'right', marginTop: '4px' }">
|
||||
<a-pagination
|
||||
:showSizeChanger="true"
|
||||
:current="currentPage"
|
||||
size="small"
|
||||
:total="totalNumber"
|
||||
show-quick-jumper
|
||||
:page-size="pageSize"
|
||||
:page-size-options="pageSizeOptions"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
:show-total="
|
||||
(total, range) =>
|
||||
$t('pagination.total', {
|
||||
range0: range[0],
|
||||
range1: range[1],
|
||||
total,
|
||||
})
|
||||
"
|
||||
@change="
|
||||
(page) => {
|
||||
currentPage = page
|
||||
loadInstance(sortByTable)
|
||||
}
|
||||
"
|
||||
>
|
||||
<template slot="buildOptionText" slot-scope="props">
|
||||
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('itemsPerPage') }}</span>
|
||||
<span v-if="props.value === '100000'">{{ $t('all') }}</span>
|
||||
</template>
|
||||
</a-pagination>
|
||||
</div>
|
||||
|
||||
<BatchDownload
|
||||
@@ -227,6 +225,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
ciTypes: [],
|
||||
originAllAttributesList: [],
|
||||
allAttributesList: [], // 当前选择的模型的全部attributes 默认全部
|
||||
currentPage: 1,
|
||||
pageSizeOptions: ['50', '100', '200', '100000'],
|
||||
@@ -260,18 +259,19 @@ export default {
|
||||
this.ciTypes = res.ci_types
|
||||
})
|
||||
},
|
||||
getAllAttr() {
|
||||
searchAttributes({ page_size: 9999 }).then((res) => {
|
||||
async getAllAttr() {
|
||||
await searchAttributes({ page_size: 9999 }).then((res) => {
|
||||
this.allAttributesList = res.attributes
|
||||
this.originAllAttributesList = res.attributes
|
||||
})
|
||||
},
|
||||
updateAllAttributesList(value) {
|
||||
async updateAllAttributesList(value) {
|
||||
if (value && value.length) {
|
||||
getCITypeAttributesByTypeIds({ type_ids: value.join(',') }).then((res) => {
|
||||
await getCITypeAttributesByTypeIds({ type_ids: value.join(',') }).then((res) => {
|
||||
this.allAttributesList = res.attributes
|
||||
})
|
||||
} else {
|
||||
this.getAllAttr()
|
||||
this.allAttributesList = this.originAllAttributesList
|
||||
}
|
||||
},
|
||||
async loadInstance(sortByTable = undefined) {
|
||||
@@ -373,7 +373,6 @@ export default {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const _commonColumnsGroup = Object.keys(commonObject).map((key) => {
|
||||
return {
|
||||
id: `parent-${key}`,
|
||||
@@ -411,7 +410,7 @@ export default {
|
||||
return { ...item, id: item.field, label: item.title }
|
||||
})
|
||||
},
|
||||
handleSearch() {
|
||||
async handleSearch() {
|
||||
this.currentPage = 1
|
||||
this.loadInstance()
|
||||
},
|
||||
@@ -534,3 +533,14 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.resource-search {
|
||||
margin-bottom: -24px;
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: @border-radius-box;
|
||||
}
|
||||
</style>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user