Compare commits

..

18 Commits

Author SHA1 Message Date
simontigers
693ae4ff05 fix: deploy init common (#407) 2024-03-01 17:21:32 +08:00
pycook
a1a9d99eb4 release: v2.3.12 2024-03-01 17:04:38 +08:00
dagongren
e045e0fb43 fix(cmdb-ui):to lowercase (#406) 2024-03-01 13:52:30 +08:00
pycook
09376dbd2b feat(api): CIType inheritance (#405) 2024-03-01 13:51:13 +08:00
dagongren
7fda5a1e7b feat(cmdb-ui):ci type inherit (#404) 2024-03-01 13:39:20 +08:00
dagongren
113b84763f feat:ci detail share (#403) 2024-02-27 16:13:28 +08:00
dagongren
190170acad fix(cmdb-ui):triggers webhook headers (#402) 2024-02-26 13:46:40 +08:00
pycook
513d2af4b8 feat(api): Remove many-to-many restrictions (#401) 2024-02-26 10:17:53 +08:00
pycook
4588bd8996 fix(api): db-setup commands (#399)
fix(api): db-setup commands
2024-02-23 11:05:11 +08:00
dagongren
082da5fade fix(cmdb-ui):resource search common attrs (#397) 2024-02-22 16:19:12 +08:00
pycook
013b116eb5 feat(acl): login channel add ssh options (#396) 2024-02-21 18:10:44 +08:00
simontigers
208d29165b fix: grant common perm after create new employee (#394) 2024-02-04 13:48:02 +08:00
dagongren
d510330cde fix(cmdb-ui):fix multiple default value (#395) 2024-02-04 11:49:44 +08:00
wang-liang0615
ea4f0fc2a5 fix(ui):login email-》username (#393) 2024-01-31 15:52:27 +08:00
pycook
9bcdaacdc4 docs: update init sql
docs: update init sql
2024-01-26 13:57:36 +08:00
pycook
5045581ddf feat(api): Auto-increment id can be used as primary key (#391) 2024-01-26 13:12:17 +08:00
simontigers
232913172c fix: change common_setting task queue (#390) 2024-01-25 17:39:52 +08:00
pycook
157e1809ed release: 2.3.11 2024-01-13 15:06:51 +08:00
43 changed files with 1270 additions and 400 deletions

View File

@@ -1,13 +1,14 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import click
import copy import copy
import datetime import datetime
import json import json
import requests
import time import time
import uuid import uuid
import click
import requests
from flask import current_app from flask import current_app
from flask.cli import with_appcontext from flask.cli import with_appcontext
from flask_login import login_user from flask_login import login_user
@@ -196,6 +197,8 @@ def cmdb_counter():
CMDBCounterCache.flush_adc_counter() CMDBCounterCache.flush_adc_counter()
i = 0 i = 0
CMDBCounterCache.flush_sub_counter()
i += 1 i += 1
except: except:
import traceback import traceback

View File

@@ -4,7 +4,7 @@ from flask.cli import with_appcontext
from werkzeug.datastructures import MultiDict from werkzeug.datastructures import MultiDict
from api.lib.common_setting.acl import ACLManager from api.lib.common_setting.acl import ACLManager
from api.lib.common_setting.employee import EmployeeAddForm from api.lib.common_setting.employee import EmployeeAddForm, GrantEmployeeACLPerm
from api.lib.common_setting.resp_format import ErrFormat from api.lib.common_setting.resp_format import ErrFormat
from api.models.common_setting import Employee, Department from api.models.common_setting import Employee, Department
@@ -158,50 +158,11 @@ class InitDepartment(object):
def init_backend_resource(self): def init_backend_resource(self):
acl = self.check_app('backend') acl = self.check_app('backend')
resources_types = acl.get_all_resources_types()
perms = ['read', 'grant', 'delete', 'update']
acl_rid = self.get_admin_user_rid() acl_rid = self.get_admin_user_rid()
results = list(filter(lambda t: t['name'] == '操作权限', resources_types['groups'])) if acl_rid == 0:
if len(results) == 0: return
payload = dict( GrantEmployeeACLPerm(acl).grant_by_rid(acl_rid, True)
app_id=acl.app_name,
name='操作权限',
description='',
perms=perms
)
resource_type = acl.create_resources_type(payload)
else:
resource_type = results[0]
resource_type_id = resource_type['id']
existed_perms = resources_types.get('id2perms', {}).get(resource_type_id, [])
existed_perms = [p['name'] for p in existed_perms]
new_perms = []
for perm in perms:
if perm not in existed_perms:
new_perms.append(perm)
if len(new_perms) > 0:
resource_type['perms'] = existed_perms + new_perms
acl.update_resources_type(resource_type_id, resource_type)
resource_list = acl.get_resource_by_type(None, None, resource_type['id'])
for name in ['公司信息', '公司架构', '通知设置']:
target = list(filter(lambda r: r['name'] == name, resource_list))
if len(target) == 0:
payload = dict(
type_id=resource_type['id'],
app_id=acl.app_name,
name=name,
)
resource = acl.create_resource(payload)
else:
resource = target[0]
if acl_rid > 0:
acl.grant_resource(acl_rid, resource['id'], perms)
@staticmethod @staticmethod
def check_app(app_name): def check_app(app_name):

View File

@@ -89,9 +89,12 @@ def db_setup():
""" """
db.create_all() db.create_all()
db.session.execute("set global sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE," try:
"ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'") db.session.execute("set global sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,"
db.session.commit() "ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'")
db.session.commit()
except:
pass
try: try:
db.session.execute("set global tidb_enable_noop_functions='ON'") db.session.execute("set global tidb_enable_noop_functions='ON'")

View File

@@ -330,8 +330,8 @@ class AutoDiscoveryCICRUD(DBMixin):
@staticmethod @staticmethod
def get_attributes_by_type_id(type_id): def get_attributes_by_type_id(type_id):
from api.lib.cmdb.cache import CITypeAttributesCache from api.lib.cmdb.ci_type import CITypeAttributeManager
attributes = [i[1] for i in CITypeAttributesCache.get2(type_id) or []] attributes = [i[1] for i in CITypeAttributeManager.get_all_attributes(type_id) or []]
attr_names = set() attr_names = set()
adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id) adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id)

View File

@@ -5,6 +5,7 @@ import copy
import datetime import datetime
import json import json
import threading import threading
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask_login import current_user from flask_login import current_user
@@ -13,7 +14,6 @@ from werkzeug.exceptions import BadRequest
from api.extensions import db from api.extensions import db
from api.extensions import rd from api.extensions import rd
from api.lib.cmdb.cache import AttributeCache from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeAttributesCache
from api.lib.cmdb.cache import CITypeCache from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.cache import CMDBCounterCache from api.lib.cmdb.cache import CMDBCounterCache
from api.lib.cmdb.ci_type import CITypeAttributeManager from api.lib.cmdb.ci_type import CITypeAttributeManager
@@ -52,7 +52,6 @@ from api.models.cmdb import AttributeHistory
from api.models.cmdb import AutoDiscoveryCI from api.models.cmdb import AutoDiscoveryCI
from api.models.cmdb import CI from api.models.cmdb import CI
from api.models.cmdb import CIRelation from api.models.cmdb import CIRelation
from api.models.cmdb import CITypeAttribute
from api.models.cmdb import CITypeRelation from api.models.cmdb import CITypeRelation
from api.models.cmdb import CITypeTrigger from api.models.cmdb import CITypeTrigger
from api.tasks.cmdb import ci_cache from api.tasks.cmdb import ci_cache
@@ -309,14 +308,18 @@ class CIManager(object):
""" """
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
ci_type = CITypeManager.check_is_existed(ci_type_name) ci_type = CITypeManager.check_is_existed(ci_type_name)
raw_dict = copy.deepcopy(ci_dict)
unique_key = AttributeCache.get(ci_type.unique_id) or abort( unique_key = AttributeCache.get(ci_type.unique_id) or abort(
400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id))) 400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id)))
if (unique_key.default and unique_key.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and
not ci_dict.get(unique_key.name)):
ci_dict[unique_key.name] = cls._auto_inc_id(unique_key)
unique_value = ci_dict.get(unique_key.name) or ci_dict.get(unique_key.alias) or ci_dict.get(unique_key.id) unique_value = ci_dict.get(unique_key.name) or ci_dict.get(unique_key.alias) or ci_dict.get(unique_key.id)
unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name)) unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name))
attrs = CITypeAttributesCache.get2(ci_type_name) attrs = CITypeAttributeManager.get_all_attributes(ci_type.id)
ci_type_attrs_name = {attr.name: attr for _, attr in attrs} ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
ci_type_attrs_alias = {attr.alias: attr for _, attr in attrs} ci_type_attrs_alias = {attr.alias: attr for _, attr in attrs}
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs} ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
@@ -346,7 +349,8 @@ class CIManager(object):
if attr.default.get('default') and attr.default.get('default') in ( if attr.default.get('default') and attr.default.get('default') in (
AttributeDefaultValueEnum.CREATED_AT, AttributeDefaultValueEnum.UPDATED_AT): AttributeDefaultValueEnum.CREATED_AT, AttributeDefaultValueEnum.UPDATED_AT):
ci_dict[attr.name] = now ci_dict[attr.name] = now
elif attr.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID: elif (attr.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and
not ci_dict.get(attr.name)):
ci_dict[attr.name] = cls._auto_inc_id(attr) ci_dict[attr.name] = cls._auto_inc_id(attr)
elif ((attr.name not in ci_dict and attr.alias not in ci_dict) or ( elif ((attr.name not in ci_dict and attr.alias not in ci_dict) or (
ci_dict.get(attr.name) is None and ci_dict.get(attr.alias) is None)): ci_dict.get(attr.name) is None and ci_dict.get(attr.alias) is None)):
@@ -381,7 +385,7 @@ class CIManager(object):
cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id) cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
ref_ci_dict = dict() ref_ci_dict = dict()
for k in ci_dict: for k in copy.deepcopy(ci_dict):
if k.startswith("$") and "." in k: if k.startswith("$") and "." in k:
ref_ci_dict[k] = ci_dict[k] ref_ci_dict[k] = ci_dict[k]
continue continue
@@ -393,7 +397,10 @@ class CIManager(object):
_attr_name = ((ci_type_attrs_name.get(k) and ci_type_attrs_name[k].name) or _attr_name = ((ci_type_attrs_name.get(k) and ci_type_attrs_name[k].name) or
(ci_type_attrs_alias.get(k) and ci_type_attrs_alias[k].name)) (ci_type_attrs_alias.get(k) and ci_type_attrs_alias[k].name))
if limit_attrs and _attr_name not in limit_attrs: if limit_attrs and _attr_name not in limit_attrs:
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k)) if k in raw_dict:
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
else:
ci_dict.pop(k)
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name or k in ci_type_attrs_alias} ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name or k in ci_type_attrs_alias}
@@ -425,7 +432,9 @@ class CIManager(object):
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
ci = self.confirm_ci_existed(ci_id) ci = self.confirm_ci_existed(ci_id)
attrs = CITypeAttributesCache.get2(ci.type_id) raw_dict = copy.deepcopy(ci_dict)
attrs = CITypeAttributeManager.get_all_attributes(ci.type_id)
ci_type_attrs_name = {attr.name: attr for _, attr in attrs} ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs} ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
for _, attr in attrs: for _, attr in attrs:
@@ -462,9 +471,12 @@ class CIManager(object):
key2attr = value_manager.valid_attr_value(ci_dict, ci.type_id, ci.id, ci_type_attrs_name, key2attr = value_manager.valid_attr_value(ci_dict, ci.type_id, ci.id, ci_type_attrs_name,
ci_attr2type_attr=ci_attr2type_attr) ci_attr2type_attr=ci_attr2type_attr)
if limit_attrs: if limit_attrs:
for k in ci_dict: for k in copy.deepcopy(ci_dict):
if k not in limit_attrs: if k not in limit_attrs:
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k)) if k in raw_dict:
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
else:
ci_dict.pop(k)
try: 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)
@@ -512,8 +524,7 @@ class CIManager(object):
ci_delete_trigger.apply_async(args=(trigger, OperateType.DELETE, ci_dict), queue=CMDB_QUEUE) ci_delete_trigger.apply_async(args=(trigger, OperateType.DELETE, ci_dict), queue=CMDB_QUEUE)
attrs = CITypeAttribute.get_by(type_id=ci.type_id, to_dict=False) attrs = [i for _, i in CITypeAttributeManager.get_all_attributes(type_id=ci.type_id)]
attrs = [AttributeCache.get(attr.attr_id) for attr in attrs]
for attr in attrs: for attr in attrs:
value_table = TableMap(attr=attr).table value_table = TableMap(attr=attr).table
for item in value_table.get_by(ci_id=ci_id, to_dict=False): for item in value_table.get_by(ci_id=ci_id, to_dict=False):

View File

@@ -1,6 +1,7 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import copy import copy
import toposort import toposort
from flask import abort from flask import abort
from flask import current_app from flask import current_app
@@ -41,6 +42,7 @@ from api.models.cmdb import CITypeAttributeGroup
from api.models.cmdb import CITypeAttributeGroupItem from api.models.cmdb import CITypeAttributeGroupItem
from api.models.cmdb import CITypeGroup from api.models.cmdb import CITypeGroup
from api.models.cmdb import CITypeGroupItem from api.models.cmdb import CITypeGroupItem
from api.models.cmdb import CITypeInheritance
from api.models.cmdb import CITypeRelation from api.models.cmdb import CITypeRelation
from api.models.cmdb import CITypeTrigger from api.models.cmdb import CITypeTrigger
from api.models.cmdb import CITypeUniqueConstraint from api.models.cmdb import CITypeUniqueConstraint
@@ -86,6 +88,7 @@ class CITypeManager(object):
for type_dict in ci_types: for type_dict in ci_types:
attr = AttributeCache.get(type_dict["unique_id"]) attr = AttributeCache.get(type_dict["unique_id"])
type_dict["unique_key"] = attr and attr.name type_dict["unique_key"] = attr and attr.name
type_dict['parent_ids'] = CITypeInheritanceManager.get_parents(type_dict['id'])
if resources is None or type_dict['name'] in resources: if resources is None or type_dict['name'] in resources:
res.append(type_dict) res.append(type_dict)
@@ -132,8 +135,13 @@ class CITypeManager(object):
kwargs["unique_id"] = unique_key.id kwargs["unique_id"] = unique_key.id
kwargs['uid'] = current_user.uid kwargs['uid'] = current_user.uid
parent_ids = kwargs.pop('parent_ids', None)
ci_type = CIType.create(**kwargs) ci_type = CIType.create(**kwargs)
CITypeInheritanceManager.add(parent_ids, ci_type.id)
CITypeAttributeManager.add(ci_type.id, [unique_key.id], is_required=True) CITypeAttributeManager.add(ci_type.id, [unique_key.id], is_required=True)
CITypeCache.clean(ci_type.name) CITypeCache.clean(ci_type.name)
@@ -230,6 +238,12 @@ class CITypeManager(object):
for item in AutoDiscoveryCI.get_by(type_id=type_id, to_dict=False): for item in AutoDiscoveryCI.get_by(type_id=type_id, to_dict=False):
item.delete(commit=False) item.delete(commit=False)
for item in CITypeInheritance.get_by(parent_id=type_id, to_dict=False):
item.delete(commit=False)
for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False):
item.delete(commit=False)
db.session.commit() db.session.commit()
ci_type.soft_delete() ci_type.soft_delete()
@@ -242,6 +256,100 @@ class CITypeManager(object):
ACLManager().del_resource(ci_type.name, ResourceTypeEnum.CI) ACLManager().del_resource(ci_type.name, ResourceTypeEnum.CI)
class CITypeInheritanceManager(object):
cls = CITypeInheritance
@classmethod
def get_parents(cls, type_id):
return [i.parent_id for i in cls.cls.get_by(child_id=type_id, to_dict=False)]
@classmethod
def recursive_children(cls, type_id):
result = []
def _get_child(_id):
children = [i.child_id for i in cls.cls.get_by(parent_id=_id, to_dict=False)]
result.extend(children)
for child_id in children:
_get_child(child_id)
_get_child(type_id)
return result
@classmethod
def base(cls, type_id):
result = []
q = []
def _get_parents(_type_id):
parents = [i.parent_id for i in cls.cls.get_by(child_id=_type_id, to_dict=False)]
for i in parents[::-1]:
q.append(i)
try:
out = q.pop(0)
except IndexError:
return
result.append(out)
_get_parents(out)
_get_parents(type_id)
return result[::-1]
@classmethod
def add(cls, parent_ids, child_id):
rels = {}
for i in cls.cls.get_by(to_dict=False):
rels.setdefault(i.child_id, set()).add(i.parent_id)
try:
toposort_flatten(rels)
except toposort.CircularDependencyError as e:
current_app.logger.warning(str(e))
return abort(400, ErrFormat.circular_dependency_error)
for parent_id in parent_ids or []:
if parent_id == child_id:
return abort(400, ErrFormat.circular_dependency_error)
existed = cls.cls.get_by(parent_id=parent_id, child_id=child_id, first=True, to_dict=False)
if existed is None:
rels.setdefault(child_id, set()).add(parent_id)
try:
toposort_flatten(rels)
except toposort.CircularDependencyError as e:
current_app.logger.warning(str(e))
return abort(400, ErrFormat.circular_dependency_error)
cls.cls.create(parent_id=parent_id, child_id=child_id, commit=False)
db.session.commit()
@classmethod
def delete(cls, parent_id, child_id):
existed = cls.cls.get_by(parent_id=parent_id, child_id=child_id, first=True, to_dict=False)
if existed is not None:
children = cls.recursive_children(child_id) + [child_id]
for _id in children:
if CI.get_by(type_id=_id, to_dict=False, first=True) is not None:
return abort(400, ErrFormat.ci_exists_and_cannot_delete_inheritance)
attr_ids = set([i.id for _, i in CITypeAttributeManager.get_all_attributes(parent_id)])
for _id in children:
for attr_id in attr_ids:
for i in PreferenceShowAttributes.get_by(type_id=_id, attr_id=attr_id, to_dict=False):
i.soft_delete(commit=False)
db.session.commit()
existed.soft_delete()
class CITypeGroupManager(object): class CITypeGroupManager(object):
cls = CITypeGroup cls = CITypeGroup
@@ -262,6 +370,7 @@ class CITypeGroupManager(object):
ci_type = CITypeCache.get(t['type_id']).to_dict() ci_type = CITypeCache.get(t['type_id']).to_dict()
if resources is None or (ci_type and ci_type['name'] in resources): if resources is None or (ci_type and ci_type['name'] in resources):
ci_type['permissions'] = resources[ci_type['name']] if resources is not None else None ci_type['permissions'] = resources[ci_type['name']] if resources is not None else None
ci_type['inherited'] = True if CITypeInheritanceManager.get_parents(ci_type['id']) else False
group.setdefault("ci_types", []).append(ci_type) group.setdefault("ci_types", []).append(ci_type)
group_types.add(t["type_id"]) group_types.add(t["type_id"])
@@ -271,6 +380,7 @@ class CITypeGroupManager(object):
for ci_type in ci_types: for ci_type in ci_types:
if ci_type["id"] not in group_types and (resources is None or ci_type['name'] in resources): if ci_type["id"] not in group_types and (resources is None or ci_type['name'] in resources):
ci_type['permissions'] = resources.get(ci_type['name']) if resources is not None else None ci_type['permissions'] = resources.get(ci_type['name']) if resources is not None else None
ci_type['inherited'] = True if CITypeInheritanceManager.get_parents(ci_type['id']) else False
other_types['ci_types'].append(ci_type) other_types['ci_types'].append(ci_type)
groups.append(other_types) groups.append(other_types)
@@ -362,40 +472,62 @@ class CITypeAttributeManager(object):
return attr.name return attr.name
@staticmethod @staticmethod
def get_attr_names_by_type_id(type_id): def get_all_attributes(type_id):
return [AttributeCache.get(attr.attr_id).name for attr in CITypeAttributesCache.get(type_id)] parent_ids = CITypeInheritanceManager.base(type_id)
result = []
for _type_id in parent_ids + [type_id]:
result.extend(CITypeAttributesCache.get2(_type_id))
return result
@classmethod
def get_attr_names_by_type_id(cls, type_id):
return [attr.name for _, attr in cls.get_all_attributes(type_id)]
@staticmethod @staticmethod
def get_attributes_by_type_id(type_id, choice_web_hook_parse=True, choice_other_parse=True): def get_attributes_by_type_id(type_id, choice_web_hook_parse=True, choice_other_parse=True):
has_config_perm = ACLManager('cmdb').has_permission( has_config_perm = ACLManager('cmdb').has_permission(
CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG) CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG)
attrs = CITypeAttributesCache.get(type_id) parent_ids = CITypeInheritanceManager.base(type_id)
result = list()
for attr in sorted(attrs, key=lambda x: (x.order, x.id)):
attr_dict = AttributeManager().get_attribute(attr.attr_id, choice_web_hook_parse, choice_other_parse)
attr_dict["is_required"] = attr.is_required
attr_dict["order"] = attr.order
attr_dict["default_show"] = attr.default_show
if not has_config_perm:
attr_dict.pop('choice_web_hook', None)
attr_dict.pop('choice_other', None)
result.append(attr_dict) result = list()
id2pos = dict()
type2name = {i: CITypeCache.get(i) for i in parent_ids}
for _type_id in parent_ids + [type_id]:
attrs = CITypeAttributesCache.get(_type_id)
for attr in sorted(attrs, key=lambda x: (x.order, x.id)):
attr_dict = AttributeManager().get_attribute(attr.attr_id, choice_web_hook_parse, choice_other_parse)
attr_dict["is_required"] = attr.is_required
attr_dict["order"] = attr.order
attr_dict["default_show"] = attr.default_show
attr_dict["inherited"] = False if _type_id == type_id else True
attr_dict["inherited_from"] = type2name.get(_type_id) and type2name[_type_id].alias
if not has_config_perm:
attr_dict.pop('choice_web_hook', None)
attr_dict.pop('choice_other', None)
if attr_dict['id'] not in id2pos:
id2pos[attr_dict['id']] = len(result)
result.append(attr_dict)
else:
result[id2pos[attr_dict['id']]] = attr_dict
return result return result
@staticmethod @classmethod
def get_common_attributes(type_ids): def get_common_attributes(cls, type_ids):
has_config_perm = False has_config_perm = False
for type_id in type_ids: for type_id in type_ids:
has_config_perm |= ACLManager('cmdb').has_permission( has_config_perm |= ACLManager('cmdb').has_permission(
CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG) CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG)
result = CITypeAttribute.get_by(__func_in___key_type_id=list(map(int, type_ids)), to_dict=False) result = {type_id: [i for _, i in cls.get_all_attributes(type_id)] for type_id in type_ids}
attr2types = {} attr2types = {}
for i in result: for type_id in result:
attr2types.setdefault(i.attr_id, []).append(i.type_id) for i in result[type_id]:
attr2types.setdefault(i.id, []).append(type_id)
attrs = [] attrs = []
for attr_id in attr2types: for attr_id in attr2types:
@@ -529,8 +661,18 @@ class CITypeAttributeManager(object):
attr_id = _from.get('attr_id') attr_id = _from.get('attr_id')
from_group_id = _from.get('group_id') from_group_id = _from.get('group_id')
to_group_id = _to.get('group_id') to_group_id = _to.get('group_id')
from_group_name = _from.get('group_name')
to_group_name = _to.get('group_name')
order = _to.get('order') order = _to.get('order')
if from_group_name:
from_group = CITypeAttributeGroup.get_by(type_id=type_id, name=from_group_name, first=True, to_dict=False)
from_group_id = from_group and from_group.id
if to_group_name:
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 from_group_id != to_group_id: if from_group_id != to_group_id:
if from_group_id is not None: if from_group_id is not None:
CITypeAttributeGroupManager.delete_item(from_group_id, attr_id) CITypeAttributeGroupManager.delete_item(from_group_id, attr_id)
@@ -656,15 +798,15 @@ class CITypeRelationManager(object):
current_app.logger.warning(str(e)) current_app.logger.warning(str(e))
return abort(400, ErrFormat.circular_dependency_error) return abort(400, ErrFormat.circular_dependency_error)
if constraint == ConstraintEnum.Many2Many: # if constraint == ConstraintEnum.Many2Many:
other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many, # other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
to_dict=False, first=True) # to_dict=False, first=True)
other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many, # other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
to_dict=False, first=True) # to_dict=False, first=True)
if other_c and other_c.child_id != c.id: # if other_c and other_c.child_id != c.id:
return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name)) # return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
if other_p and other_p.parent_id != p.id: # if other_p and other_p.parent_id != p.id:
return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name)) # return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
existed = cls._get(p.id, c.id) existed = cls._get(p.id, c.id)
if existed is not None: if existed is not None:
@@ -739,25 +881,66 @@ class CITypeAttributeGroupManager(object):
@staticmethod @staticmethod
def get_by_type_id(type_id, need_other=False): def get_by_type_id(type_id, need_other=False):
groups = CITypeAttributeGroup.get_by(type_id=type_id) parent_ids = CITypeInheritanceManager.base(type_id)
groups = sorted(groups, key=lambda x: x["order"] or 0)
grouped = list() groups = []
id2type = {i: CITypeCache.get(i).alias for i in parent_ids}
for _type_id in parent_ids + [type_id]:
_groups = CITypeAttributeGroup.get_by(type_id=_type_id)
_groups = sorted(_groups, key=lambda x: x["order"] or 0)
for i in _groups:
if type_id != _type_id:
i['inherited'] = True
i['inherited_from'] = id2type[_type_id]
else:
i['inherited'] = False
groups.extend(_groups)
grouped = set()
attributes = CITypeAttributeManager.get_attributes_by_type_id(type_id) attributes = CITypeAttributeManager.get_attributes_by_type_id(type_id)
id2attr = {i.get('id'): i for i in attributes} id2attr = {i.get('id'): i for i in attributes}
group2pos = dict()
attr2pos = dict()
result = []
for group in groups: for group in groups:
items = CITypeAttributeGroupItem.get_by(group_id=group["id"], to_dict=False) items = CITypeAttributeGroupItem.get_by(group_id=group["id"], to_dict=False)
items = sorted(items, key=lambda x: x.order or 0) items = sorted(items, key=lambda x: x.order or 0)
group["attributes"] = [id2attr.get(i.attr_id) for i in items if i.attr_id in id2attr]
grouped.extend([i.attr_id for i in items]) if group['name'] not in group2pos:
group_pos = len(result)
group['attributes'] = []
result.append(group)
group2pos[group['name']] = group_pos
else:
group_pos = group2pos[group['name']]
attr = None
for i in items:
if i.attr_id in id2attr:
attr = id2attr[i.attr_id]
attr['inherited'] = group['inherited']
attr['inherited_from'] = group.get('inherited_from')
result[group_pos]['attributes'].append(attr)
if i.attr_id in attr2pos:
result[attr2pos[i.attr_id][0]]['attributes'].remove(attr2pos[i.attr_id][1])
attr2pos[i.attr_id] = [group_pos, attr]
group.pop('inherited_from', None)
grouped |= set([i.attr_id for i in items])
if need_other: if need_other:
grouped = set(grouped) grouped = set(grouped)
other_attributes = [attr for attr in attributes if attr["id"] not in grouped] other_attributes = [attr for attr in attributes if attr["id"] not in grouped]
groups.append(dict(attributes=other_attributes)) result.append(dict(attributes=other_attributes))
return groups return result
@staticmethod @staticmethod
def create_or_update(type_id, name, attr_order, group_order=0, is_update=False): def create_or_update(type_id, name, attr_order, group_order=0, is_update=False):
@@ -891,10 +1074,16 @@ class CITypeAttributeGroupManager(object):
@classmethod @classmethod
def transfer(cls, type_id, _from, _to): def transfer(cls, type_id, _from, _to):
current_app.logger.info("CIType[{0}] {1} -> {2}".format(type_id, _from, _to)) current_app.logger.info("CIType[{0}] {1} -> {2}".format(type_id, _from, _to))
from_group = CITypeAttributeGroup.get_by_id(_from) if isinstance(_from, int):
from_group = CITypeAttributeGroup.get_by_id(_from)
else:
from_group = CITypeAttributeGroup.get_by(name=_from, first=True, to_dict=False)
from_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_from))) from_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_from)))
to_group = CITypeAttributeGroup.get_by_id(_to) if isinstance(_to, int):
to_group = CITypeAttributeGroup.get_by_id(_to)
else:
to_group = CITypeAttributeGroup.get_by(name=_to, first=True, to_dict=False)
to_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_to))) to_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_to)))
from_order, to_order = from_group.order, to_group.order from_order, to_order = from_group.order, to_group.order

View File

@@ -2,6 +2,7 @@
import copy import copy
import six import six
import toposort import toposort
from flask import abort from flask import abort
@@ -14,6 +15,7 @@ from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeAttributesCache from api.lib.cmdb.cache import CITypeAttributesCache
from api.lib.cmdb.cache import CITypeCache from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.cache import CMDBCounterCache from api.lib.cmdb.cache import CMDBCounterCache
from api.lib.cmdb.ci_type import CITypeAttributeManager
from api.lib.cmdb.const import ConstraintEnum from api.lib.cmdb.const import ConstraintEnum
from api.lib.cmdb.const import PermEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum from api.lib.cmdb.const import ResourceTypeEnum
@@ -112,8 +114,8 @@ class PreferenceManager(object):
CITypeAttribute, CITypeAttribute.attr_id == PreferenceShowAttributes.attr_id).filter( CITypeAttribute, CITypeAttribute.attr_id == PreferenceShowAttributes.attr_id).filter(
PreferenceShowAttributes.uid == current_user.uid).filter( PreferenceShowAttributes.uid == current_user.uid).filter(
PreferenceShowAttributes.type_id == type_id).filter( PreferenceShowAttributes.type_id == type_id).filter(
PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).filter( PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).group_by(
CITypeAttribute.type_id == type_id).all() CITypeAttribute.attr_id).all()
result = [] result = []
for i in sorted(attrs, key=lambda x: x.PreferenceShowAttributes.order): for i in sorted(attrs, key=lambda x: x.PreferenceShowAttributes.order):
@@ -123,17 +125,16 @@ class PreferenceManager(object):
is_subscribed = True is_subscribed = True
if not attrs: if not attrs:
attrs = db.session.query(CITypeAttribute).filter( result = CITypeAttributeManager.get_attributes_by_type_id(type_id,
CITypeAttribute.type_id == type_id).filter( choice_web_hook_parse=False,
CITypeAttribute.deleted.is_(False)).filter( choice_other_parse=False)
CITypeAttribute.default_show.is_(True)).order_by(CITypeAttribute.order) result = [i for i in result if i['default_show']]
result = [i.attr.to_dict() for i in attrs]
is_subscribed = False is_subscribed = False
for i in result: for i in result:
if i["is_choice"]: if i["is_choice"]:
i.update(dict(choice_value=AttributeManager.get_choice_values( i.update(dict(choice_value=AttributeManager.get_choice_values(
i["id"], i["value_type"], i["choice_web_hook"], i.get("choice_other")))) i["id"], i["value_type"], i.get("choice_web_hook"), i.get("choice_other"))))
return is_subscribed, result return is_subscribed, result

View File

@@ -60,6 +60,8 @@ class ErrFormat(CommonErrFormat):
only_owner_can_delete = _l("Only the creator can delete it!") # 只有创建人才能删除它! only_owner_can_delete = _l("Only the creator can delete it!") # 只有创建人才能删除它!
ci_exists_and_cannot_delete_type = _l( ci_exists_and_cannot_delete_type = _l(
"The model cannot be deleted because the CI already exists") # 因为CI已经存在不能删除模型 "The model cannot be deleted because the CI already exists") # 因为CI已经存在不能删除模型
ci_exists_and_cannot_delete_inheritance = _l(
"The inheritance cannot be deleted because the CI already exists") # 因为CI已经存在不能删除继承关系
# 因为关系视图 {} 引用了该模型,不能删除模型 # 因为关系视图 {} 引用了该模型,不能删除模型
ci_relation_view_exists_and_cannot_delete_type = _l( ci_relation_view_exists_and_cannot_delete_type = _l(

View File

@@ -16,7 +16,7 @@ from wtforms import validators
from api.extensions import db from api.extensions import db
from api.lib.common_setting.acl import ACLManager from api.lib.common_setting.acl import ACLManager
from api.lib.common_setting.const import OperatorType from api.lib.common_setting.const import OperatorType
from api.lib.cmdb.const import CMDB_QUEUE from api.lib.perm.acl.const import ACL_QUEUE
from api.lib.common_setting.resp_format import ErrFormat from api.lib.common_setting.resp_format import ErrFormat
from api.models.common_setting import Employee, Department from api.models.common_setting import Employee, Department
@@ -141,7 +141,7 @@ class EmployeeCRUD(object):
def add(**kwargs): def add(**kwargs):
try: try:
res = CreateEmployee().create_single(**kwargs) res = CreateEmployee().create_single(**kwargs)
refresh_employee_acl_info.apply_async(args=(), queue=CMDB_QUEUE) refresh_employee_acl_info.apply_async(args=(res.employee_id,), queue=ACL_QUEUE)
return res return res
except Exception as e: except Exception as e:
abort(400, str(e)) abort(400, str(e))
@@ -171,7 +171,7 @@ class EmployeeCRUD(object):
if len(e_list) > 0: if len(e_list) > 0:
edit_employee_department_in_acl.apply_async( edit_employee_department_in_acl.apply_async(
args=(e_list, new_department_id, current_user.uid), args=(e_list, new_department_id, current_user.uid),
queue=CMDB_QUEUE queue=ACL_QUEUE
) )
return existed return existed
@@ -577,7 +577,6 @@ class EmployeeCRUD(object):
@staticmethod @staticmethod
def import_employee(employee_list): def import_employee(employee_list):
res = CreateEmployee().batch_create(employee_list) res = CreateEmployee().batch_create(employee_list)
refresh_employee_acl_info.apply_async(args=(), queue=CMDB_QUEUE)
return res return res
@staticmethod @staticmethod
@@ -788,9 +787,11 @@ class CreateEmployee(object):
if existed: if existed:
return existed return existed
return Employee.create( res = Employee.create(
**kwargs **kwargs
) )
refresh_employee_acl_info.apply_async(args=(res.employee_id,), queue=ACL_QUEUE)
return res
@staticmethod @staticmethod
def get_department_by_name(d_name): def get_department_by_name(d_name):
@@ -897,3 +898,75 @@ class EmployeeUpdateByUidForm(Form):
avatar = StringField(validators=[]) avatar = StringField(validators=[])
sex = StringField(validators=[]) sex = StringField(validators=[])
mobile = StringField(validators=[]) mobile = StringField(validators=[])
class GrantEmployeeACLPerm(object):
"""
Grant ACL Permission After Create New Employee
"""
def __init__(self, acl=None):
self.perms_by_create_resources_type = ['read', 'grant', 'delete', 'update']
self.perms_by_common_grant = ['read']
self.resource_name_list = ['公司信息', '公司架构', '通知设置']
self.acl = acl if acl else self.check_app('backend')
self.resources_types = self.acl.get_all_resources_types()
self.resources_type = self.get_resources_type()
self.resource_list = self.acl.get_resource_by_type(None, None, self.resources_type['id'])
@staticmethod
def check_app(app_name):
acl = ACLManager(app_name)
payload = dict(
name=app_name,
description=app_name
)
app = acl.validate_app()
if not app:
acl.create_app(payload)
return acl
def get_resources_type(self):
results = list(filter(lambda t: t['name'] == '操作权限', self.resources_types['groups']))
if len(results) == 0:
payload = dict(
app_id=self.acl.app_name,
name='操作权限',
description='',
perms=self.perms_by_create_resources_type
)
resource_type = self.acl.create_resources_type(payload)
else:
resource_type = results[0]
resource_type_id = resource_type['id']
existed_perms = self.resources_types.get('id2perms', {}).get(resource_type_id, [])
existed_perms = [p['name'] for p in existed_perms]
new_perms = []
for perm in self.perms_by_create_resources_type:
if perm not in existed_perms:
new_perms.append(perm)
if len(new_perms) > 0:
resource_type['perms'] = existed_perms + new_perms
self.acl.update_resources_type(resource_type_id, resource_type)
return resource_type
def grant(self, rid_list):
[self.grant_by_rid(rid) for rid in rid_list if rid > 0]
def grant_by_rid(self, rid, is_admin=False):
for name in self.resource_name_list:
resource = list(filter(lambda r: r['name'] == name, self.resource_list))
if len(resource) == 0:
payload = dict(
type_id=self.resources_type['id'],
app_id=self.acl.app_name,
name=name,
)
resource = self.acl.create_resource(payload)
else:
resource = resource[0]
perms = self.perms_by_create_resources_type if is_admin else self.perms_by_common_grant
self.acl.grant_resource(rid, resource['id'], perms)

View File

@@ -389,6 +389,7 @@ class AuditCRUD(object):
logout_at=logout_at, logout_at=logout_at,
ip=request.headers.get('X-Real-IP') or request.remote_addr, ip=request.headers.get('X-Real-IP') or request.remote_addr,
browser=request.headers.get('User-Agent'), browser=request.headers.get('User-Agent'),
channel=request.values.get('channel', 'web'),
) )
if logout_at is None: if logout_at is None:

View File

@@ -356,7 +356,7 @@ class AuditLoginLog(Model2):
__tablename__ = "acl_audit_login_logs" __tablename__ = "acl_audit_login_logs"
username = db.Column(db.String(64), index=True) username = db.Column(db.String(64), index=True)
channel = db.Column(db.Enum('web', 'api'), default="web") channel = db.Column(db.Enum('web', 'api', 'ssh'), default="web")
ip = db.Column(db.String(15)) ip = db.Column(db.String(15))
browser = db.Column(db.String(256)) browser = db.Column(db.String(256))
description = db.Column(db.String(128)) description = db.Column(db.String(128))

View File

@@ -2,6 +2,7 @@
import datetime import datetime
from sqlalchemy.dialects.mysql import DOUBLE from sqlalchemy.dialects.mysql import DOUBLE
from api.extensions import db from api.extensions import db
@@ -56,6 +57,16 @@ class CIType(Model):
uid = db.Column(db.Integer, index=True) uid = db.Column(db.Integer, index=True)
class CITypeInheritance(Model):
__tablename__ = "c_ci_type_inheritance"
parent_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
child_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeInheritance.parent_id")
child = db.relationship("CIType", primaryjoin="CIType.id==CITypeInheritance.child_id")
class CITypeRelation(Model): class CITypeRelation(Model):
__tablename__ = "c_ci_type_relations" __tablename__ = "c_ci_type_relations"

View File

@@ -3,14 +3,14 @@ from flask import current_app
from api.extensions import celery from api.extensions import celery
from api.lib.common_setting.acl import ACLManager from api.lib.common_setting.acl import ACLManager
from api.lib.cmdb.const import CMDB_QUEUE from api.lib.perm.acl.const import ACL_QUEUE
from api.lib.common_setting.resp_format import ErrFormat from api.lib.common_setting.resp_format import ErrFormat
from api.models.common_setting import Department, Employee from api.models.common_setting import Department, Employee
from api.lib.decorator import flush_db from api.lib.decorator import flush_db
from api.lib.decorator import reconnect_db from api.lib.decorator import reconnect_db
@celery.task(name="common_setting.edit_employee_department_in_acl", queue=CMDB_QUEUE) @celery.task(name="common_setting.edit_employee_department_in_acl", queue=ACL_QUEUE)
@flush_db @flush_db
@reconnect_db @reconnect_db
def edit_employee_department_in_acl(e_list, new_d_id, op_uid): def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
@@ -77,10 +77,10 @@ def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
return result return result
@celery.task(name="common_setting.refresh_employee_acl_info", queue=CMDB_QUEUE) @celery.task(name="common_setting.refresh_employee_acl_info", queue=ACL_QUEUE)
@flush_db @flush_db
@reconnect_db @reconnect_db
def refresh_employee_acl_info(): def refresh_employee_acl_info(current_employee_id=None):
acl = ACLManager('acl') acl = ACLManager('acl')
role_map = {role['name']: role for role in acl.get_all_roles()} role_map = {role['name']: role for role in acl.get_all_roles()}
@@ -90,8 +90,12 @@ def refresh_employee_acl_info():
query = Employee.query.filter(*criterion).order_by( query = Employee.query.filter(*criterion).order_by(
Employee.created_at.desc() Employee.created_at.desc()
) )
current_employee_rid = 0
for em in query.all(): for em in query.all():
if current_employee_id and em.employee_id == current_employee_id:
current_employee_rid = em.acl_rid if em.acl_rid else 0
if em.acl_uid and em.acl_rid: if em.acl_uid and em.acl_rid:
continue continue
role = role_map.get(em.username, None) role = role_map.get(em.username, None)
@@ -105,6 +109,9 @@ def refresh_employee_acl_info():
if not em.acl_rid: if not em.acl_rid:
params['acl_rid'] = role.get('id', 0) params['acl_rid'] = role.get('id', 0)
if current_employee_id and em.employee_id == current_employee_id:
current_employee_rid = params['acl_rid'] if params.get('acl_rid', 0) else 0
try: try:
em.update(**params) em.update(**params)
current_app.logger.info( current_app.logger.info(
@@ -113,3 +120,12 @@ def refresh_employee_acl_info():
except Exception as e: except Exception as e:
current_app.logger.error(str(e)) current_app.logger.error(str(e))
continue continue
if current_employee_rid and current_employee_rid > 0:
try:
from api.lib.common_setting.employee import GrantEmployeeACLPerm
GrantEmployeeACLPerm().grant_by_rid(current_employee_rid, False)
current_app.logger.info(f"GrantEmployeeACLPerm success, current_employee_rid: {current_employee_rid}")
except Exception as e:
current_app.logger.error(str(e))

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-01-03 11:39+0800\n" "POT-Creation-Date: 2024-03-01 13:49+0800\n"
"PO-Revision-Date: 2023-12-25 20:21+0800\n" "PO-Revision-Date: 2023-12-25 20:21+0800\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: zh\n" "Language: zh\n"
@@ -234,205 +234,209 @@ msgstr "只有创建人才能删除它!"
msgid "The model cannot be deleted because the CI already exists" msgid "The model cannot be deleted because the CI already exists"
msgstr "因为CI已经存在不能删除模型" msgstr "因为CI已经存在不能删除模型"
#: api/lib/cmdb/resp_format.py:65 #: api/lib/cmdb/resp_format.py:63
msgid "The inheritance cannot be deleted because the CI already exists"
msgstr "因为CI已经存在不能删除继承关系"
#: api/lib/cmdb/resp_format.py:67
msgid "" msgid ""
"The model cannot be deleted because the model is referenced by the " "The model cannot be deleted because the model is referenced by the "
"relational view {}" "relational view {}"
msgstr "因为关系视图 {} 引用了该模型,不能删除模型" msgstr "因为关系视图 {} 引用了该模型,不能删除模型"
#: api/lib/cmdb/resp_format.py:67 #: api/lib/cmdb/resp_format.py:69
msgid "Model group {} does not exist" msgid "Model group {} does not exist"
msgstr "模型分组 {} 不存在" msgstr "模型分组 {} 不存在"
#: api/lib/cmdb/resp_format.py:68 #: api/lib/cmdb/resp_format.py:70
msgid "Model group {} already exists" msgid "Model group {} already exists"
msgstr "模型分组 {} 已经存在" msgstr "模型分组 {} 已经存在"
#: api/lib/cmdb/resp_format.py:69 #: api/lib/cmdb/resp_format.py:71
msgid "Model relationship {} does not exist" msgid "Model relationship {} does not exist"
msgstr "模型关系 {} 不存在" msgstr "模型关系 {} 不存在"
#: api/lib/cmdb/resp_format.py:70 #: api/lib/cmdb/resp_format.py:72
msgid "Attribute group {} already exists" msgid "Attribute group {} already exists"
msgstr "属性分组 {} 已存在" msgstr "属性分组 {} 已存在"
#: api/lib/cmdb/resp_format.py:71 #: api/lib/cmdb/resp_format.py:73
msgid "Attribute group {} does not exist" msgid "Attribute group {} does not exist"
msgstr "属性分组 {} 不存在" msgstr "属性分组 {} 不存在"
#: api/lib/cmdb/resp_format.py:73 #: api/lib/cmdb/resp_format.py:75
msgid "Attribute group <{0}> - attribute <{1}> does not exist" msgid "Attribute group <{0}> - attribute <{1}> does not exist"
msgstr "属性组<{0}> - 属性<{1}> 不存在" msgstr "属性组<{0}> - 属性<{1}> 不存在"
#: api/lib/cmdb/resp_format.py:74 #: api/lib/cmdb/resp_format.py:76
msgid "The unique constraint already exists!" msgid "The unique constraint already exists!"
msgstr "唯一约束已经存在!" msgstr "唯一约束已经存在!"
#: api/lib/cmdb/resp_format.py:76 #: api/lib/cmdb/resp_format.py:78
msgid "Uniquely constrained attributes cannot be JSON and multi-valued" msgid "Uniquely constrained attributes cannot be JSON and multi-valued"
msgstr "唯一约束的属性不能是 JSON 和 多值" msgstr "唯一约束的属性不能是 JSON 和 多值"
#: api/lib/cmdb/resp_format.py:77 #: api/lib/cmdb/resp_format.py:79
msgid "Duplicated trigger" msgid "Duplicated trigger"
msgstr "重复的触发器" msgstr "重复的触发器"
#: api/lib/cmdb/resp_format.py:78 #: api/lib/cmdb/resp_format.py:80
msgid "Trigger {} does not exist" msgid "Trigger {} does not exist"
msgstr "触发器 {} 不存在" msgstr "触发器 {} 不存在"
#: api/lib/cmdb/resp_format.py:80 #: api/lib/cmdb/resp_format.py:82
msgid "Operation record {} does not exist" msgid "Operation record {} does not exist"
msgstr "操作记录 {} 不存在" msgstr "操作记录 {} 不存在"
#: api/lib/cmdb/resp_format.py:81 #: api/lib/cmdb/resp_format.py:83
msgid "Unique identifier cannot be deleted" msgid "Unique identifier cannot be deleted"
msgstr "不能删除唯一标识" msgstr "不能删除唯一标识"
#: api/lib/cmdb/resp_format.py:82 #: api/lib/cmdb/resp_format.py:84
msgid "Cannot delete default sorted attributes" msgid "Cannot delete default sorted attributes"
msgstr "不能删除默认排序的属性" msgstr "不能删除默认排序的属性"
#: api/lib/cmdb/resp_format.py:84 #: api/lib/cmdb/resp_format.py:86
msgid "No node selected" msgid "No node selected"
msgstr "没有选择节点" msgstr "没有选择节点"
#: api/lib/cmdb/resp_format.py:85 #: api/lib/cmdb/resp_format.py:87
msgid "This search option does not exist!" msgid "This search option does not exist!"
msgstr "该搜索选项不存在!" msgstr "该搜索选项不存在!"
#: api/lib/cmdb/resp_format.py:86 #: api/lib/cmdb/resp_format.py:88
msgid "This search option has a duplicate name!" msgid "This search option has a duplicate name!"
msgstr "该搜索选项命名重复!" msgstr "该搜索选项命名重复!"
#: api/lib/cmdb/resp_format.py:88 #: api/lib/cmdb/resp_format.py:90
msgid "Relationship type {} already exists" msgid "Relationship type {} already exists"
msgstr "关系类型 {} 已经存在" msgstr "关系类型 {} 已经存在"
#: api/lib/cmdb/resp_format.py:89 #: api/lib/cmdb/resp_format.py:91
msgid "Relationship type {} does not exist" msgid "Relationship type {} does not exist"
msgstr "关系类型 {} 不存在" msgstr "关系类型 {} 不存在"
#: api/lib/cmdb/resp_format.py:91 #: api/lib/cmdb/resp_format.py:93
msgid "Invalid attribute value: {}" msgid "Invalid attribute value: {}"
msgstr "无效的属性值: {}" msgstr "无效的属性值: {}"
#: api/lib/cmdb/resp_format.py:92 #: api/lib/cmdb/resp_format.py:94
msgid "{} Invalid value: {}" msgid "{} Invalid value: {}"
msgstr "无效的值: {}" msgstr "无效的值: {}"
#: api/lib/cmdb/resp_format.py:93 #: api/lib/cmdb/resp_format.py:95
msgid "{} is not in the predefined values" msgid "{} is not in the predefined values"
msgstr "{} 不在预定义值里" msgstr "{} 不在预定义值里"
#: api/lib/cmdb/resp_format.py:95 #: api/lib/cmdb/resp_format.py:97
msgid "The value of attribute {} must be unique, {} already exists" msgid "The value of attribute {} must be unique, {} already exists"
msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在" msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
#: api/lib/cmdb/resp_format.py:96 #: api/lib/cmdb/resp_format.py:98
msgid "Attribute {} value must exist" msgid "Attribute {} value must exist"
msgstr "属性 {} 值必须存在" msgstr "属性 {} 值必须存在"
#: api/lib/cmdb/resp_format.py:99 #: api/lib/cmdb/resp_format.py:101
msgid "Unknown error when adding or modifying attribute value: {}" msgid "Unknown error when adding or modifying attribute value: {}"
msgstr "新增或者修改属性值未知错误: {}" msgstr "新增或者修改属性值未知错误: {}"
#: api/lib/cmdb/resp_format.py:101 #: api/lib/cmdb/resp_format.py:103
msgid "Duplicate custom name" msgid "Duplicate custom name"
msgstr "订制名重复" msgstr "订制名重复"
#: api/lib/cmdb/resp_format.py:103 #: api/lib/cmdb/resp_format.py:105
msgid "Number of models exceeds limit: {}" msgid "Number of models exceeds limit: {}"
msgstr "模型数超过限制: {}" msgstr "模型数超过限制: {}"
#: api/lib/cmdb/resp_format.py:104 #: api/lib/cmdb/resp_format.py:106
msgid "The number of CIs exceeds the limit: {}" msgid "The number of CIs exceeds the limit: {}"
msgstr "CI数超过限制: {}" msgstr "CI数超过限制: {}"
#: api/lib/cmdb/resp_format.py:106 #: api/lib/cmdb/resp_format.py:108
msgid "Auto-discovery rule: {} already exists!" msgid "Auto-discovery rule: {} already exists!"
msgstr "自动发现规则: {} 已经存在!" msgstr "自动发现规则: {} 已经存在!"
#: api/lib/cmdb/resp_format.py:107 #: api/lib/cmdb/resp_format.py:109
msgid "Auto-discovery rule: {} does not exist!" msgid "Auto-discovery rule: {} does not exist!"
msgstr "自动发现规则: {} 不存在!" msgstr "自动发现规则: {} 不存在!"
#: api/lib/cmdb/resp_format.py:109 #: api/lib/cmdb/resp_format.py:111
msgid "This auto-discovery rule is referenced by the model and cannot be deleted!" msgid "This auto-discovery rule is referenced by the model and cannot be deleted!"
msgstr "该自动发现规则被模型引用, 不能删除!" msgstr "该自动发现规则被模型引用, 不能删除!"
#: api/lib/cmdb/resp_format.py:111 #: api/lib/cmdb/resp_format.py:113
msgid "The application of auto-discovery rules cannot be defined repeatedly!" msgid "The application of auto-discovery rules cannot be defined repeatedly!"
msgstr "自动发现规则的应用不能重复定义!" msgstr "自动发现规则的应用不能重复定义!"
#: api/lib/cmdb/resp_format.py:112 #: api/lib/cmdb/resp_format.py:114
msgid "The auto-discovery you want to modify: {} does not exist!" msgid "The auto-discovery you want to modify: {} does not exist!"
msgstr "您要修改的自动发现: {} 不存在!" msgstr "您要修改的自动发现: {} 不存在!"
#: api/lib/cmdb/resp_format.py:113 #: api/lib/cmdb/resp_format.py:115
msgid "Attribute does not include unique identifier: {}" msgid "Attribute does not include unique identifier: {}"
msgstr "属性字段没有包括唯一标识: {}" msgstr "属性字段没有包括唯一标识: {}"
#: api/lib/cmdb/resp_format.py:114 #: api/lib/cmdb/resp_format.py:116
msgid "The auto-discovery instance does not exist!" msgid "The auto-discovery instance does not exist!"
msgstr "自动发现的实例不存在!" msgstr "自动发现的实例不存在!"
#: api/lib/cmdb/resp_format.py:115 #: api/lib/cmdb/resp_format.py:117
msgid "The model is not associated with this auto-discovery!" msgid "The model is not associated with this auto-discovery!"
msgstr "模型并未关联该自动发现!" msgstr "模型并未关联该自动发现!"
#: api/lib/cmdb/resp_format.py:116 #: api/lib/cmdb/resp_format.py:118
msgid "Only the creator can modify the Secret!" msgid "Only the creator can modify the Secret!"
msgstr "只有创建人才能修改Secret!" msgstr "只有创建人才能修改Secret!"
#: api/lib/cmdb/resp_format.py:118 #: api/lib/cmdb/resp_format.py:120
msgid "This rule already has auto-discovery instances and cannot be deleted!" msgid "This rule already has auto-discovery instances and cannot be deleted!"
msgstr "该规则已经有自动发现的实例, 不能被删除!" msgstr "该规则已经有自动发现的实例, 不能被删除!"
#: api/lib/cmdb/resp_format.py:120 #: api/lib/cmdb/resp_format.py:122
msgid "The default auto-discovery rule is already referenced by model {}!" msgid "The default auto-discovery rule is already referenced by model {}!"
msgstr "该默认的自动发现规则 已经被模型 {} 引用!" msgstr "该默认的自动发现规则 已经被模型 {} 引用!"
#: api/lib/cmdb/resp_format.py:122 #: api/lib/cmdb/resp_format.py:124
msgid "The unique_key method must return a non-empty string!" msgid "The unique_key method must return a non-empty string!"
msgstr "unique_key方法必须返回非空字符串!" msgstr "unique_key方法必须返回非空字符串!"
#: api/lib/cmdb/resp_format.py:123 #: api/lib/cmdb/resp_format.py:125
msgid "The attributes method must return a list" msgid "The attributes method must return a list"
msgstr "attributes方法必须返回的是list" msgstr "attributes方法必须返回的是list"
#: api/lib/cmdb/resp_format.py:125 #: api/lib/cmdb/resp_format.py:127
msgid "The list returned by the attributes method cannot be empty!" msgid "The list returned by the attributes method cannot be empty!"
msgstr "attributes方法返回的list不能为空!" msgstr "attributes方法返回的list不能为空!"
#: api/lib/cmdb/resp_format.py:127 #: api/lib/cmdb/resp_format.py:129
msgid "Only administrators can define execution targets as: all nodes!" msgid "Only administrators can define execution targets as: all nodes!"
msgstr "只有管理员才可以定义执行机器为: 所有节点!" msgstr "只有管理员才可以定义执行机器为: 所有节点!"
#: api/lib/cmdb/resp_format.py:128 #: api/lib/cmdb/resp_format.py:130
msgid "Execute targets permission check failed: {}" msgid "Execute targets permission check failed: {}"
msgstr "执行机器权限检查不通过: {}" msgstr "执行机器权限检查不通过: {}"
#: api/lib/cmdb/resp_format.py:130 #: api/lib/cmdb/resp_format.py:132
msgid "CI filter authorization must be named!" msgid "CI filter authorization must be named!"
msgstr "CI过滤授权 必须命名!" msgstr "CI过滤授权 必须命名!"
#: api/lib/cmdb/resp_format.py:131 #: api/lib/cmdb/resp_format.py:133
msgid "CI filter authorization is currently not supported or query" msgid "CI filter authorization is currently not supported or query"
msgstr "CI过滤授权 暂时不支持 或 查询" msgstr "CI过滤授权 暂时不支持 或 查询"
#: api/lib/cmdb/resp_format.py:134 #: api/lib/cmdb/resp_format.py:136
msgid "You do not have permission to operate attribute {}!" msgid "You do not have permission to operate attribute {}!"
msgstr "您没有属性 {} 的操作权限!" msgstr "您没有属性 {} 的操作权限!"
#: api/lib/cmdb/resp_format.py:135 #: api/lib/cmdb/resp_format.py:137
msgid "You do not have permission to operate this CI!" msgid "You do not have permission to operate this CI!"
msgstr "您没有该CI的操作权限!" msgstr "您没有该CI的操作权限!"
#: api/lib/cmdb/resp_format.py:137 #: api/lib/cmdb/resp_format.py:139
msgid "Failed to save password: {}" msgid "Failed to save password: {}"
msgstr "保存密码失败: {}" msgstr "保存密码失败: {}"
#: api/lib/cmdb/resp_format.py:138 #: api/lib/cmdb/resp_format.py:140
msgid "Failed to get password: {}" msgid "Failed to get password: {}"
msgstr "获取密码失败: {}" msgstr "获取密码失败: {}"

View File

@@ -14,6 +14,7 @@ from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.ci_type import CITypeAttributeGroupManager from api.lib.cmdb.ci_type import CITypeAttributeGroupManager
from api.lib.cmdb.ci_type import CITypeAttributeManager from api.lib.cmdb.ci_type import CITypeAttributeManager
from api.lib.cmdb.ci_type import CITypeGroupManager from api.lib.cmdb.ci_type import CITypeGroupManager
from api.lib.cmdb.ci_type import CITypeInheritanceManager
from api.lib.cmdb.ci_type import CITypeManager from api.lib.cmdb.ci_type import CITypeManager
from api.lib.cmdb.ci_type import CITypeTemplateManager from api.lib.cmdb.ci_type import CITypeTemplateManager
from api.lib.cmdb.ci_type import CITypeTriggerManager from api.lib.cmdb.ci_type import CITypeTriggerManager
@@ -43,9 +44,13 @@ class CITypeView(APIView):
q = request.args.get("type_name") q = request.args.get("type_name")
if type_id is not None: if type_id is not None:
ci_types = [CITypeCache.get(type_id).to_dict()] ci_type = CITypeCache.get(type_id).to_dict()
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(type_id)
ci_types = [ci_type]
elif type_name is not None: elif type_name is not None:
ci_types = [CITypeCache.get(type_name).to_dict()] ci_type = CITypeCache.get(type_name).to_dict()
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(ci_type['id'])
ci_types = [ci_type]
else: else:
ci_types = CITypeManager().get_ci_types(q) ci_types = CITypeManager().get_ci_types(q)
count = len(ci_types) count = len(ci_types)
@@ -53,7 +58,7 @@ class CITypeView(APIView):
return self.jsonify(numfound=count, ci_types=ci_types) return self.jsonify(numfound=count, ci_types=ci_types)
@args_required("name") @args_required("name")
@args_validate(CITypeManager.cls) @args_validate(CITypeManager.cls, exclude_args=['parent_ids'])
def post(self): def post(self):
params = request.values params = request.values
@@ -84,6 +89,26 @@ class CITypeView(APIView):
return self.jsonify(type_id=type_id) return self.jsonify(type_id=type_id)
class CITypeInheritanceView(APIView):
url_prefix = ("/ci_types/inheritance",)
@args_required("parent_ids")
@args_required("child_id")
@has_perm_from_args("child_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
def post(self):
CITypeInheritanceManager.add(request.values['parent_ids'], request.values['child_id'])
return self.jsonify(**request.values)
@args_required("parent_id")
@args_required("child_id")
@has_perm_from_args("child_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
def delete(self):
CITypeInheritanceManager.delete(request.values['parent_id'], request.values['child_id'])
return self.jsonify(**request.values)
class CITypeGroupView(APIView): class CITypeGroupView(APIView):
url_prefix = ("/ci_types/groups", url_prefix = ("/ci_types/groups",
"/ci_types/groups/config", "/ci_types/groups/config",
@@ -248,8 +273,8 @@ class CITypeAttributeTransferView(APIView):
@args_required('from') @args_required('from')
@args_required('to') @args_required('to')
def post(self, type_id): def post(self, type_id):
_from = request.values.get('from') # {'attr_id': xx, 'group_id': xx} _from = request.values.get('from') # {'attr_id': xx, 'group_id': xx, 'group_name': xx}
_to = request.values.get('to') # {'group_id': xx, 'order': xxx} _to = request.values.get('to') # {'group_id': xx, 'group_name': xx, 'order': xxx}
CITypeAttributeManager.transfer(type_id, _from, _to) CITypeAttributeManager.transfer(type_id, _from, _to)
@@ -262,8 +287,8 @@ class CITypeAttributeGroupTransferView(APIView):
@args_required('from') @args_required('from')
@args_required('to') @args_required('to')
def post(self, type_id): def post(self, type_id):
_from = request.values.get('from') # group_id _from = request.values.get('from') # group_id or group_name
_to = request.values.get('to') # group_id _to = request.values.get('to') # group_id or group_name
CITypeAttributeGroupManager.transfer(type_id, _from, _to) CITypeAttributeGroupManager.transfer(type_id, _from, _to)
@@ -296,7 +321,7 @@ class CITypeAttributeGroupView(APIView):
attr_order = list(zip(attrs, orders)) attr_order = list(zip(attrs, orders))
group = CITypeAttributeGroupManager.create_or_update(type_id, name, attr_order, order) group = CITypeAttributeGroupManager.create_or_update(type_id, name, attr_order, order)
current_app.logger.warning(group.id)
return self.jsonify(group_id=group.id) return self.jsonify(group_id=group.id)
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id) @has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
@@ -310,11 +335,13 @@ class CITypeAttributeGroupView(APIView):
attr_order = list(zip(attrs, orders)) attr_order = list(zip(attrs, orders))
CITypeAttributeGroupManager.update(group_id, name, attr_order, order) CITypeAttributeGroupManager.update(group_id, name, attr_order, order)
return self.jsonify(group_id=group_id) return self.jsonify(group_id=group_id)
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id) @has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
def delete(self, group_id): def delete(self, group_id):
CITypeAttributeGroupManager.delete(group_id) CITypeAttributeGroupManager.delete(group_id)
return self.jsonify(group_id=group_id) return self.jsonify(group_id=group_id)

View File

@@ -205,3 +205,21 @@ export function ciTypeFilterPermissions(type_id) {
method: 'get', method: 'get',
}) })
} }
// parent_ids, child_id
export function postCiTypeInheritance(data) {
return axios({
url: `/v0.1/ci_types/inheritance`,
method: 'post',
data
})
}
// parent_id, child_id
export function deleteCiTypeInheritance(data) {
return axios({
url: `/v0.1/ci_types/inheritance`,
method: 'delete',
data
})
}

View File

@@ -0,0 +1,148 @@
<template>
<treeselect
:disabled="disabled"
ref="cmdb_type_select"
:disable-branch-nodes="true"
class="custom-treeselect custom-treeselect-bgcAndBorder"
:style="{
'--custom-height': '30px',
lineHeight: '30px',
'--custom-bg-color': '#fff',
'--custom-border': '1px solid #d9d9d9',
}"
v-model="currenCiType"
:multiple="multiple"
:clearable="true"
searchable
:options="ciTypeGroup"
value-consists-of="LEAF_PRIORITY"
:placeholder="placeholder || `${$t(`placeholder2`)}`"
:load-options="loadOptions"
@select="
(node, instanceId) => {
$emit('select', node, instanceId)
}
"
@deselect="
(node, instanceId) => {
$emit('deselect', node, instanceId)
}
"
:normalizer="
(node) => {
return {
id: node.id || -1,
label: node.alias || node.name || '其他',
title: node.alias || node.name || '其他',
}
}
"
>
<div
:title="node.label"
slot="option-label"
slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
>
{{ node.label }}
</div>
<div slot="value-label" slot-scope="{ node }">{{ getTreeSelectLabel(node) }}</div>
</treeselect>
</template>
<script>
import _ from 'lodash'
import { getCITypeGroupsConfig } from '@/modules/cmdb/api/ciTypeGroup'
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
import { getTreeSelectLabel } from '../../utils/helper'
export default {
name: 'CMDBTypeSelect',
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: [String, Number, Array],
default: null,
},
selectType: {
type: String,
default: 'attributes',
},
attrIdkey: {
type: String,
default: 'id',
},
disabled: {
type: Boolean,
default: false,
},
multiple: {
type: Boolean,
default: false,
},
placeholder: {
type: String,
default: '',
},
},
data() {
return {
ciTypeGroup: [],
childrenOptions: [],
}
},
computed: {
currenCiType: {
get() {
return this.value
},
set(val) {
this.$emit('change', val)
return val
},
},
},
async mounted() {
if (this.value) {
const typeId = this.value.split('-')[0]
await getCITypeAttributesById(this.value.split('-')[0]).then((res) => {
this.childrenOptions = res.attributes.map((item) => ({ ...item, id: `${typeId}-${item[this.attrIdkey]}` }))
})
}
this.getCITypeGroups()
},
methods: {
getTreeSelectLabel,
getCITypeGroups() {
getCITypeGroupsConfig({ need_other: true }).then((res) => {
this.ciTypeGroup = res
.filter((item) => item.ci_types && item.ci_types.length)
.map((item) => {
item.id = `type_${item.id || -1}`
item.children = item.ci_types.map((type) => {
const obj = { ...type }
if (this.selectType === 'attributes') {
obj.children = this.value && type.id === Number(this.value.split('-')[0]) ? this.childrenOptions : null
}
return obj
})
return { ..._.cloneDeep(item) }
})
})
},
loadOptions({ action, parentNode, callback }) {
getCITypeAttributesById(parentNode.id).then((res) => {
parentNode.children = res.attributes.map((item) => ({
...item,
id: `${parentNode.id}-${item[this.attrIdkey]}`,
}))
callback()
})
},
},
}
</script>
<style></style>

View File

@@ -0,0 +1,2 @@
import CMDBTypeSelect from './cmdbTypeSelect.vue'
export default CMDBTypeSelect

View File

@@ -145,7 +145,7 @@ export default {
selectedRowKeys: { selectedRowKeys: {
type: Array, type: Array,
default: () => [], default: () => [],
} },
}, },
data() { data() {
return { return {
@@ -179,7 +179,12 @@ export default {
this.fuzzySearch = '' this.fuzzySearch = ''
}, },
}, },
inject: ['setPreferenceSearchCurrent'], inject: {
setPreferenceSearchCurrent: {
from: 'setPreferenceSearchCurrent',
default: null,
},
},
mounted() { mounted() {
if (this.type === 'resourceSearch') { if (this.type === 'resourceSearch') {
this.getCITypeGroups() this.getCITypeGroups()
@@ -234,7 +239,9 @@ export default {
} }
}, },
emitRefresh() { emitRefresh() {
this.setPreferenceSearchCurrent(null) if (this.setPreferenceSearchCurrent) {
this.setPreferenceSearchCurrent(null)
}
this.$nextTick(() => { this.$nextTick(() => {
this.$emit('refresh', true) this.$emit('refresh', true)
}) })

View File

@@ -88,7 +88,9 @@ export default {
} catch {} } catch {}
const headers = {} const headers = {}
this.$refs.Header.headers.forEach((item) => { this.$refs.Header.headers.forEach((item) => {
headers[item.key] = item.value if (item.key) {
headers[item.key] = item.value
}
}) })
let authorization = {} let authorization = {}
const type = this.$refs.Authorization.authorizationType const type = this.$refs.Authorization.authorizationType

View File

@@ -19,6 +19,7 @@ const cmdb_en = {
operationHistory: 'Operation Audit', operationHistory: 'Operation Audit',
relationType: 'Relation Type', relationType: 'Relation Type',
ad: 'AutoDiscovery', ad: 'AutoDiscovery',
cidetail: 'CI Detail'
}, },
ciType: { ciType: {
ciType: 'CIType', ciType: 'CIType',
@@ -175,7 +176,12 @@ const cmdb_en = {
time: 'Time', time: 'Time',
json: 'JSON', json: 'JSON',
event: 'Event', event: 'Event',
reg: 'Regex' reg: 'Regex',
isInherit: 'Inherit',
inheritType: 'Inherit Type',
inheritTypePlaceholder: 'Please select inherit types',
inheritFrom: 'inherit from {name}',
groupInheritFrom: 'Please go to the {name} for modification'
}, },
components: { components: {
unselectAttributes: 'Unselected', unselectAttributes: 'Unselected',
@@ -467,6 +473,8 @@ const cmdb_en = {
tips10: 'Other attributes of the CIType are computed using expressions\n\nA code snippet computes the returned value.', tips10: 'Other attributes of the CIType are computed using expressions\n\nA code snippet computes the returned value.',
newUpdateField: 'Add a Attribute', newUpdateField: 'Add a Attribute',
attributeSettings: 'Attribute Settings', attributeSettings: 'Attribute Settings',
share: 'Share',
noPermission: 'No Permission'
}, },
serviceTree: { serviceTree: {
deleteNode: 'Delete Node', deleteNode: 'Delete Node',

View File

@@ -19,6 +19,7 @@ const cmdb_zh = {
operationHistory: '操作审计', operationHistory: '操作审计',
relationType: '关系类型', relationType: '关系类型',
ad: '自动发现', ad: '自动发现',
cidetail: 'CI 详情'
}, },
ciType: { ciType: {
ciType: '模型', ciType: '模型',
@@ -175,7 +176,12 @@ const cmdb_zh = {
time: '时间', time: '时间',
json: 'JSON', json: 'JSON',
event: '事件', event: '事件',
reg: '正则校验' reg: '正则校验',
isInherit: '是否继承',
inheritType: '继承模型',
inheritTypePlaceholder: '请选择继承模型(多选)',
inheritFrom: '属性继承自{name}',
groupInheritFrom: '请至{name}进行修改'
}, },
components: { components: {
unselectAttributes: '未选属性', unselectAttributes: '未选属性',
@@ -466,6 +472,8 @@ const cmdb_zh = {
tips10: '模型的其他属性通过表达式的方式计算出来\n\n一个代码片段计算返回的值', tips10: '模型的其他属性通过表达式的方式计算出来\n\n一个代码片段计算返回的值',
newUpdateField: '新增修改字段', newUpdateField: '新增修改字段',
attributeSettings: '字段设置', attributeSettings: '字段设置',
share: '分享',
noPermission: '暂无权限'
}, },
serviceTree: { serviceTree: {
deleteNode: '删除节点', deleteNode: '删除节点',

View File

@@ -56,6 +56,13 @@ const genCmdbRoutes = async () => {
meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc-selected', keepAlive: false }, meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc-selected', keepAlive: false },
component: () => import('../views/discoveryCI/index.vue') component: () => import('../views/discoveryCI/index.vue')
}, },
{
path: `/cmdb/cidetail/:typeId/:ciId`,
name: 'cmdb_ci_detail',
hidden: true,
meta: { title: 'cmdb.menu.cidetail', keepAlive: false },
component: () => import('../views/ci/ciDetailPage.vue')
},
{ {
path: '/cmdb/disabled2', path: '/cmdb/disabled2',
name: 'cmdb_disabled2', name: 'cmdb_disabled2',

View File

@@ -178,4 +178,14 @@ export const downloadExcel = (data, fileName = `${moment().format('YYYY-MM-DD HH
// ws['!rows'] = [{ 'hpt': 80 }] // ws['!rows'] = [{ 'hpt': 80 }]
// STEP 4: Write Excel file to browser #导出 // STEP 4: Write Excel file to browser #导出
XLSXS.writeFile(wb, fileName + '.xlsx') XLSXS.writeFile(wb, fileName + '.xlsx')
} }
export const getAllParentNodesLabel = (node, label) => {
if (node.parentNode) {
return getAllParentNodesLabel(node.parentNode, `${node.parentNode.label}-${label}`)
}
return label
}
export const getTreeSelectLabel = (node) => {
return `${getAllParentNodesLabel(node, node.label)}`
}

View File

@@ -0,0 +1,71 @@
<template>
<div>
<div class="ci-detail-header">{{ this.type.alias }}</div>
<div class="ci-detail-page">
<CiDetailTab ref="ciDetailTab" :typeId="typeId" />
</div>
</div>
</template>
<script>
import CiDetailTab from './modules/ciDetailTab.vue'
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
import { getCIType } from '@/modules/cmdb/api/CIType'
export default {
name: 'CiDetailPage',
components: { CiDetailTab },
data() {
return {
typeId: Number(this.$route.params.typeId),
type: {},
attrList: [],
attributes: {},
}
},
provide() {
return {
attrList: () => {
return this.attrList
},
attributes: () => {
return this.attributes
},
}
},
mounted() {
const { ciId = undefined } = this.$route.params
if (ciId) {
this.$refs.ciDetailTab.create(Number(ciId))
}
getCIType(this.typeId).then((res) => {
this.type = res.ci_types[0]
})
this.getAttributeList()
},
methods: {
async getAttributeList() {
await getCITypeAttributesById(this.typeId).then((res) => {
this.attrList = res.attributes
this.attributes = res
})
},
},
}
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.ci-detail-header {
border-left: 3px solid @primary-color;
padding-left: 10px;
font-size: 16px;
font-weight: 700;
margin-bottom: 10px;
}
.ci-detail-page {
background-color: #fff;
height: calc(100vh - 122px);
}
</style>

View File

@@ -57,7 +57,7 @@
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span> <span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span>
</div> </div>
</SearchForm> </SearchForm>
<CiDetail ref="detail" :typeId="typeId" /> <CiDetailDrawer ref="detail" :typeId="typeId" />
<ops-table <ops-table
:id="`cmdb-ci-${typeId}`" :id="`cmdb-ci-${typeId}`"
border border
@@ -297,7 +297,7 @@ import router, { resetRouter } from '@/router'
import SearchForm from '../../components/searchForm/SearchForm.vue' import SearchForm from '../../components/searchForm/SearchForm.vue'
import CreateInstanceForm from './modules/CreateInstanceForm' import CreateInstanceForm from './modules/CreateInstanceForm'
import CiDetail from './modules/CiDetail' import CiDetailDrawer from './modules/ciDetailDrawer.vue'
import EditAttrsPopover from './modules/editAttrsPopover' import EditAttrsPopover from './modules/editAttrsPopover'
import JsonEditor from '../../components/JsonEditor/jsonEditor.vue' import JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
import { searchCI, updateCI, deleteCI } from '@/modules/cmdb/api/ci' import { searchCI, updateCI, deleteCI } from '@/modules/cmdb/api/ci'
@@ -320,7 +320,7 @@ export default {
components: { components: {
SearchForm, SearchForm,
CreateInstanceForm, CreateInstanceForm,
CiDetail, CiDetailDrawer,
JsonEditor, JsonEditor,
PasswordField, PasswordField,
EditAttrsPopover, EditAttrsPopover,

View File

@@ -0,0 +1,50 @@
<template>
<CustomDrawer
width="80%"
placement="left"
@close="
() => {
visible = false
}
"
:visible="visible"
:hasTitle="false"
:hasFooter="false"
:bodyStyle="{ padding: 0, height: '100vh' }"
destroyOnClose
>
<CiDetailTab ref="ciDetailTab" :typeId="typeId" :treeViewsLevels="treeViewsLevels" />
</CustomDrawer>
</template>
<script>
import CiDetailTab from './ciDetailTab.vue'
export default {
name: 'CiDetailDrawer',
components: { CiDetailTab },
props: {
typeId: {
type: Number,
required: false,
default: null,
},
treeViewsLevels: {
type: Array,
default: () => [],
},
},
data() {
return {
visible: false,
}
},
methods: {
create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
this.visible = true
this.$nextTick(() => {
this.$refs.ciDetailTab.create(ciId, activeTabKey, ciDetailRelationKey)
})
},
},
}
</script>

View File

@@ -39,7 +39,11 @@
class="ops-stripe-table" class="ops-stripe-table"
> >
<template #operation_default="{ row }"> <template #operation_default="{ row }">
<a-popconfirm arrowPointAtCenter :title="$t('cmdb.ci.confirmDeleteRelation')" @confirm="deleteRelation(row._id, ciId)"> <a-popconfirm
arrowPointAtCenter
:title="$t('cmdb.ci.confirmDeleteRelation')"
@confirm="deleteRelation(row._id, ciId)"
>
<a <a
:disabled="!canEdit[parent.id]" :disabled="!canEdit[parent.id]"
:style="{ :style="{
@@ -82,7 +86,11 @@
class="ops-stripe-table" class="ops-stripe-table"
> >
<template #operation_default="{ row }"> <template #operation_default="{ row }">
<a-popconfirm arrowPointAtCenter :title="$t('cmdb.ci.confirmDeleteRelation')" @confirm="deleteRelation(ciId, row._id)"> <a-popconfirm
arrowPointAtCenter
:title="$t('cmdb.ci.confirmDeleteRelation')"
@confirm="deleteRelation(ciId, row._id)"
>
<a <a
:disabled="!canEdit[child.id]" :disabled="!canEdit[child.id]"
:style="{ :style="{
@@ -416,6 +424,7 @@ export default {
<style lang="less" scoped> <style lang="less" scoped>
.ci-detail-relation { .ci-detail-relation {
height: 100%;
.ci-detail-relation-table-title { .ci-detail-relation-table-title {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;

View File

@@ -2,7 +2,7 @@
<div <div
id="ci-detail-relation-topo" id="ci-detail-relation-topo"
class="ci-detail-relation-topo" class="ci-detail-relation-topo"
:style="{ width: '100%', marginTop: '20px', height: 'calc(100vh - 136px)' }" :style="{ width: '100%', marginTop: '20px', height: 'calc(100% - 44px)' }"
></div> ></div>
</template> </template>

View File

@@ -1,23 +1,13 @@
<template> <template>
<CustomDrawer <div :style="{ height: '100%' }">
width="80%" <a-tabs v-if="hasPermission" class="ci-detail-tab" v-model="activeTabKey" @change="changeTab">
placement="left" <a @click="shareCi" slot="tabBarExtraContent" :style="{ marginRight: '24px' }">
@close=" <a-icon type="share-alt" />
() => { {{ $t('cmdb.ci.share') }}
visible = false </a>
}
"
:visible="visible"
:hasTitle="false"
:hasFooter="false"
:bodyStyle="{ padding: 0, height: '100vh' }"
wrapClassName="ci-detail"
destroyOnClose
>
<a-tabs v-model="activeTabKey" @change="changeTab">
<a-tab-pane key="tab_1"> <a-tab-pane key="tab_1">
<span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span> <span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span>
<div :style="{ maxHeight: `${windowHeight - 44}px`, overflow: 'auto', padding: '24px' }" class="ci-detail-attr"> <div class="ci-detail-attr">
<el-descriptions <el-descriptions
:title="group.name || $t('other')" :title="group.name || $t('other')"
:key="group.name" :key="group.name"
@@ -37,18 +27,18 @@
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="tab_2"> <a-tab-pane key="tab_2">
<span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span> <span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
<div :style="{ padding: '24px' }"> <div :style="{ height: '100%', padding: '24px' }">
<CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" /> <CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
</div> </div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="tab_3"> <a-tab-pane key="tab_3">
<span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span> <span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span>
<div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }"> <div :style="{ padding: '24px', height: '100%' }">
<vxe-table <vxe-table
ref="xTable" ref="xTable"
:data="ciHistory" :data="ciHistory"
size="small" size="small"
:max-height="`${windowHeight - 94}px`" height="auto"
:span-method="mergeRowMethod" :span-method="mergeRowMethod"
border border
:scroll-y="{ enabled: false }" :scroll-y="{ enabled: false }"
@@ -88,12 +78,22 @@
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="tab_4"> <a-tab-pane key="tab_4">
<span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span> <span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span>
<div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }"> <div :style="{ padding: '24px', height: '100%' }">
<TriggerTable :ci_id="ci._id" /> <TriggerTable :ci_id="ci._id" />
</div> </div>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</CustomDrawer> <a-empty
v-else
:image-style="{
height: '100px',
}"
:style="{ paddingTop: '20%' }"
>
<img slot="image" :src="require('@/assets/data_empty.png')" />
<span slot="description"> {{ $t('cmdb.ci.noPermission') }} </span>
</a-empty>
</div>
</template> </template>
<script> <script>
@@ -105,8 +105,8 @@ import { getCIById } from '@/modules/cmdb/api/ci'
import CiDetailAttrContent from './ciDetailAttrContent.vue' import CiDetailAttrContent from './ciDetailAttrContent.vue'
import CiDetailRelation from './ciDetailRelation.vue' import CiDetailRelation from './ciDetailRelation.vue'
import TriggerTable from '../../operation_history/modules/triggerTable.vue' import TriggerTable from '../../operation_history/modules/triggerTable.vue'
export default { export default {
name: 'CiDetailTab',
components: { components: {
ElDescriptions: Descriptions, ElDescriptions: Descriptions,
ElDescriptionsItem: DescriptionsItem, ElDescriptionsItem: DescriptionsItem,
@@ -126,7 +126,6 @@ export default {
}, },
data() { data() {
return { return {
visible: false,
ci: {}, ci: {},
attributeGroups: [], attributeGroups: [],
activeTabKey: 'tab_1', activeTabKey: 'tab_1',
@@ -134,13 +133,13 @@ export default {
ciHistory: [], ciHistory: [],
ciId: null, ciId: null,
ci_types: [], ci_types: [],
hasPermission: true,
} }
}, },
computed: { computed: {
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
operateTypeMap() { operateTypeMap() {
return { return {
0: this.$t('new'), 0: this.$t('new'),
@@ -156,10 +155,22 @@ export default {
}, },
} }
}, },
inject: ['reload', 'handleSearch', 'attrList'], inject: {
reload: {
from: 'reload',
default: null,
},
handleSearch: {
from: 'handleSearch',
default: null,
},
attrList: {
from: 'attrList',
default: () => [],
},
},
methods: { methods: {
create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') { async create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
this.visible = true
this.activeTabKey = activeTabKey this.activeTabKey = activeTabKey
if (activeTabKey === 'tab_2') { if (activeTabKey === 'tab_2') {
this.$nextTick(() => { this.$nextTick(() => {
@@ -167,12 +178,14 @@ export default {
}) })
} }
this.ciId = ciId this.ciId = ciId
this.getAttributes() await this.getCI()
this.getCI() if (this.hasPermission) {
this.getCIHistory() this.getAttributes()
getCITypes().then((res) => { this.getCIHistory()
this.ci_types = res.ci_types getCITypes().then((res) => {
}) this.ci_types = res.ci_types
})
}
}, },
getAttributes() { getAttributes() {
getCITypeGroupById(this.typeId, { need_other: 1 }) getCITypeGroupById(this.typeId, { need_other: 1 })
@@ -181,11 +194,14 @@ export default {
}) })
.catch((e) => {}) .catch((e) => {})
}, },
getCI() { async getCI() {
getCIById(this.ciId) await getCIById(this.ciId)
.then((res) => { .then((res) => {
// this.ci = res.ci if (res.result.length) {
this.ci = res.result[0] this.ci = res.result[0]
} else {
this.hasPermission = false
}
}) })
.catch((e) => {}) .catch((e) => {})
}, },
@@ -270,9 +286,13 @@ export default {
// 修改的字段为树形视图订阅的字段 则全部reload // 修改的字段为树形视图订阅的字段 则全部reload
setTimeout(() => { setTimeout(() => {
if (_find) { if (_find) {
this.reload() if (this.reload) {
this.reload()
}
} else { } else {
this.handleSearch() if (this.handleSearch) {
this.handleSearch()
}
} }
}, 500) }, 500)
}, },
@@ -303,23 +323,49 @@ export default {
// 修改的字段为树形视图订阅的字段 则全部reload // 修改的字段为树形视图订阅的字段 则全部reload
setTimeout(() => { setTimeout(() => {
if (_find) { if (_find) {
this.reload() if (this.reload) {
this.reload()
}
} else { } else {
this.handleSearch() if (this.handleSearch) {
this.handleSearch()
}
} }
}, 500) }, 500)
}, },
shareCi() {
const text = `${document.location.host}/cmdb/cidetail/${this.typeId}/${this.ciId}`
this.$copyText(text)
.then(() => {
this.$message.success(this.$t('copySuccess'))
})
.catch(() => {
this.$message.error(this.$t('cmdb.ci.copyFailed'))
})
},
}, },
} }
</script> </script>
<style lang="less" scoped></style>
<style lang="less"> <style lang="less">
.ci-detail { .ci-detail-tab {
height: 100%;
.ant-tabs-content {
height: calc(100% - 45px);
.ant-tabs-tabpane {
height: 100%;
}
}
.ant-tabs-bar { .ant-tabs-bar {
margin: 0; margin: 0;
} }
.ant-tabs-extra-content {
line-height: 44px;
}
.ci-detail-attr { .ci-detail-attr {
height: 100%;
overflow: auto;
padding: 24px;
.el-descriptions-item__content { .el-descriptions-item__content {
cursor: default; cursor: default;
&:hover a { &:hover a {

View File

@@ -25,7 +25,9 @@
initialValue: initialValue:
attr.default && attr.default.default attr.default && attr.default.default
? attr.is_list ? attr.is_list
? attr.default.default.split(',') ? Array.isArray(attr.default.default)
? attr.default.default
: attr.default.default.split(',')
: attr.default.default : attr.default.default
: null, : null,
}, },

View File

@@ -1,24 +1,30 @@
<template> <template>
<div class="attribute-card"> <div :class="{ 'attribute-card': true, 'attribute-card-inherited': inherited }">
<div class="attribute-card-content"> <a-tooltip :title="inherited ? $t('cmdb.ciType.inheritFrom', { name: property.inherited_from }) : ''">
<div class="attribute-card-value-type-icon handle" :style="{ ...getPropertyStyle(property) }"> <div class="attribute-card-content">
<ValueTypeIcon :attr="property" /> <div
</div> :class="{ 'attribute-card-value-type-icon': true, handle: !inherited }"
<div :class="{ 'attribute-card-content-inner': true, 'attribute-card-name-required': property.is_required }"> :style="{ ...getPropertyStyle(property) }"
<div :class="{ 'attribute-card-name': true, 'attribute-card-name-default-show': property.default_show }"> >
{{ property.alias || property.name }} <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>
<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>
<div </a-tooltip>
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>
<div class="attribute-card-footer"> <div class="attribute-card-footer">
<a-popover <a-popover
trigger="click" trigger="click"
@@ -51,7 +57,7 @@
</a-space> </a-space>
</a-popover> </a-popover>
<a-space class="attribute-card-operation"> <a-space class="attribute-card-operation" v-if="!inherited">
<a v-if="!isStore"><a-icon type="edit" @click="handleEdit"/></a> <a v-if="!isStore"><a-icon type="edit" @click="handleEdit"/></a>
<a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')"> <a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')">
<a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a> <a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a>
@@ -140,6 +146,9 @@ export default {
}, },
] ]
}, },
inherited() {
return this.property.inherited || false
},
}, },
methods: { methods: {
getPropertyStyle, getPropertyStyle,
@@ -211,13 +220,15 @@ export default {
width: 32px; width: 32px;
height: 32px; height: 32px;
font-size: 12px; font-size: 12px;
cursor: move;
background: #ffffff !important; background: #ffffff !important;
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2); box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
border-radius: 2px; border-radius: 2px;
text-align: center; text-align: center;
line-height: 32px; line-height: 32px;
} }
.handle {
cursor: move;
}
.attribute-card-content-inner { .attribute-card-content-inner {
padding-left: 12px; padding-left: 12px;
font-weight: 400; font-weight: 400;
@@ -269,6 +280,12 @@ export default {
} }
} }
} }
.attribute-card-inherited {
background: #f3f4f7;
.attribute-card-footer {
background: #eaedf3;
}
}
</style> </style>
<style lang="less"> <style lang="less">
.attribute-card-footer-popover { .attribute-card-footer-popover {

View File

@@ -1,6 +1,11 @@
<template> <template>
<div> <div>
<a-modal v-model="addGroupModal" :title="$t('cmdb.ciType.addGroup')" @cancel="handleCancelCreateGroup" @ok="handleCreateGroup"> <a-modal
v-model="addGroupModal"
:title="$t('cmdb.ciType.addGroup')"
@cancel="handleCancelCreateGroup"
@ok="handleCreateGroup"
>
<span> <span>
<a-form-item :label="$t('name')" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }"> <a-form-item :label="$t('name')" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
<a-input type="text" v-model.trim="newGroupName" /> <a-input type="text" v-model.trim="newGroupName" />
@@ -9,21 +14,12 @@
</a-modal> </a-modal>
<div class="ci-types-attributes" :style="{ maxHeight: `${windowHeight - 104}px` }"> <div class="ci-types-attributes" :style="{ maxHeight: `${windowHeight - 104}px` }">
<a-space style="margin-bottom: 10px"> <a-space style="margin-bottom: 10px">
<a-button <a-button type="primary" @click="handleAddGroup" size="small" class="ops-button-primary" icon="plus">{{
type="primary" $t('cmdb.ciType.group')
@click="handleAddGroup" }}</a-button>
size="small" <a-button type="primary" @click="handleOpenUniqueConstraint" size="small" class="ops-button-primary">{{
class="ops-button-primary" $t('cmdb.ciType.uniqueConstraint')
icon="plus" }}</a-button>
>{{ $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-space> </a-space>
<div :key="CITypeGroup.id" v-for="(CITypeGroup, index) in CITypeGroups"> <div :key="CITypeGroup.id" v-for="(CITypeGroup, index) in CITypeGroups">
<div> <div>
@@ -33,7 +29,11 @@
> >
<span style="font-weight:700">{{ CITypeGroup.name }}</span> <span style="font-weight:700">{{ CITypeGroup.name }}</span>
<span style="color: #c3cdd7;margin:0 5px;">({{ CITypeGroup.attributes.length }})</span> <span style="color: #c3cdd7;margin:0 5px;">({{ CITypeGroup.attributes.length }})</span>
<a @click="handleEditGroupName(index, CITypeGroup)">
<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-icon type="edit" />
</a> </a>
</div> </div>
@@ -71,7 +71,13 @@
</a-tooltip> </a-tooltip>
<a-tooltip> <a-tooltip>
<template slot="title">{{ $t('cmdb.ciType.deleteGroup') }}</template> <template slot="title">{{ $t('cmdb.ciType.deleteGroup') }}</template>
<a style="color:red;"><a-icon type="delete" @click="handleDeleteGroup(CITypeGroup)"/></a> <a
:style="{ color: CITypeGroup.inherited ? 'gray' : 'red' }"
:disabled="CITypeGroup.inherited"
><a-icon
type="delete"
@click="handleDeleteGroup(CITypeGroup)"
/></a>
</a-tooltip> </a-tooltip>
</a-space> </a-space>
</div> </div>
@@ -82,7 +88,7 @@
@start="drag = true" @start="drag = true"
@change=" @change="
(e) => { (e) => {
handleChange(e, CITypeGroup.id) handleChange(e, CITypeGroup.name)
} }
" "
:filter="'.filter-empty'" :filter="'.filter-empty'"
@@ -124,7 +130,7 @@
@start="drag = true" @start="drag = true"
@change=" @change="
(e) => { (e) => {
handleChange(e, -1) handleChange(e, null)
} }
" "
:animation="300" :animation="300"
@@ -144,18 +150,18 @@
</draggable> </draggable>
</div> </div>
</div> </div>
<attribute-edit-form <AttributeEditForm
ref="attributeEditForm" ref="attributeEditForm"
:CITypeId="CITypeId" :CITypeId="CITypeId"
:CITypeName="CITypeName" :CITypeName="CITypeName"
@ok="handleOk" @ok="handleOk"
></attribute-edit-form> ></AttributeEditForm>
<new-ci-type-attr-modal <NewCiTypeAttrModal
ref="newCiTypeAttrModal" ref="newCiTypeAttrModal"
:CITypeId="CITypeId" :CITypeId="CITypeId"
:linked-ids="linkedIds" :linked-ids="linkedIds"
@ok="handleOk" @ok="handleOk"
></new-ci-type-attr-modal> ></NewCiTypeAttrModal>
<UniqueConstraint ref="uniqueConstraint" :CITypeId="CITypeId" /> <UniqueConstraint ref="uniqueConstraint" :CITypeId="CITypeId" />
</div> </div>
</template> </template>
@@ -347,8 +353,8 @@ export default {
}, },
handleMoveGroup(beforeIndex, afterIndex) { handleMoveGroup(beforeIndex, afterIndex) {
const fromGroupId = this.CITypeGroups[beforeIndex].id const fromGroupId = this.CITypeGroups[beforeIndex].name
const toGroupId = this.CITypeGroups[afterIndex].id const toGroupId = this.CITypeGroups[afterIndex].name
transferCITypeGroupIndex(this.CITypeId, { from: fromGroupId, to: toGroupId }).then((res) => { transferCITypeGroupIndex(this.CITypeId, { from: fromGroupId, to: toGroupId }).then((res) => {
this.$message.success(this.$t('operateSuccess')) this.$message.success(this.$t('operateSuccess'))
const beforeGroup = this.CITypeGroups[beforeIndex] const beforeGroup = this.CITypeGroups[beforeIndex]
@@ -414,14 +420,14 @@ export default {
}) })
}, },
handleChange(e, group) { handleChange(e, group) {
console.log('changess') console.log('changess', group)
if (e.hasOwnProperty('moved') && e.moved.oldIndex !== e.moved.newIndex) { if (e.hasOwnProperty('moved') && e.moved.oldIndex !== e.moved.newIndex) {
if (group === -1) { if (group === -1) {
this.$message.error(this.$t('cmdb.ciType.attributeSortedTips')) this.$message.error(this.$t('cmdb.ciType.attributeSortedTips'))
} else { } else {
transferCITypeAttrIndex(this.CITypeId, { transferCITypeAttrIndex(this.CITypeId, {
from: { attr_id: e.moved.element.id, group_id: group > -1 ? group : null }, from: { attr_id: e.moved.element.id, group_name: group },
to: { order: e.moved.newIndex, group_id: group > -1 ? group : null }, to: { order: e.moved.newIndex, group_name: group },
}) })
.then((res) => this.$message.success(this.$t('updateSuccess'))) .then((res) => this.$message.success(this.$t('updateSuccess')))
.catch(() => { .catch(() => {
@@ -431,14 +437,14 @@ export default {
} }
if (e.hasOwnProperty('added')) { if (e.hasOwnProperty('added')) {
this.addRemoveGroupFlag = { to: { group_id: group > -1 ? group : null, order: e.added.newIndex }, inited: true } this.addRemoveGroupFlag = { to: { group_name: group, order: e.added.newIndex }, inited: true }
} }
if (e.hasOwnProperty('removed')) { if (e.hasOwnProperty('removed')) {
this.$nextTick(() => { this.$nextTick(() => {
transferCITypeAttrIndex(this.CITypeId, { transferCITypeAttrIndex(this.CITypeId, {
from: { attr_id: e.removed.element.id, group_id: group > -1 ? group : null }, from: { attr_id: e.removed.element.id, group_name: group },
to: { group_id: this.addRemoveGroupFlag.to.group_id, order: this.addRemoveGroupFlag.to.order }, to: { group_name: this.addRemoveGroupFlag.to.group_name, order: this.addRemoveGroupFlag.to.order },
}) })
.then((res) => this.$message.success(this.$t('saveSuccess'))) .then((res) => this.$message.success(this.$t('saveSuccess')))
.catch(() => { .catch(() => {

View File

@@ -139,6 +139,7 @@
<a><a-icon type="edit" @click="(e) => handleEdit(e, ci)"/></a> <a><a-icon type="edit" @click="(e) => handleEdit(e, ci)"/></a>
<a <a
v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')" v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"
:disabled="ci.inherited"
@click="(e) => handleDownloadCiType(e, ci)" @click="(e) => handleDownloadCiType(e, ci)"
> >
<a-icon type="download" /> <a-icon type="download" />
@@ -176,6 +177,7 @@
placement="right" placement="right"
width="900px" width="900px"
:destroyOnClose="true" :destroyOnClose="true"
:bodyStyle="{ height: 'calc(100vh - 108px)' }"
> >
<a-form <a-form
:form="form" :form="form"
@@ -204,6 +206,35 @@
<a-form-item :label="$t('alias')"> <a-form-item :label="$t('alias')">
<a-input name="alias" v-decorator="['alias', { rules: [] }]" /> <a-input name="alias" v-decorator="['alias', { rules: [] }]" />
</a-form-item> </a-form-item>
<a-form-item :label="$t('cmdb.ciType.isInherit')">
<a-radio-group v-model="isInherit">
<a-radio :value="true">
</a-radio>
<a-radio :value="false">
</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item v-if="isInherit" :label="$t('cmdb.ciType.inheritType')">
<CMDBTypeSelect
multiple
:placeholder="$t('cmdb.ciType.inheritTypePlaceholder')"
v-decorator="[
'parent_ids',
{ rules: [{ required: true, message: $t('cmdb.ciType.inheritTypePlaceholder') }] },
]"
selectType="ci_type"
:class="{
'custom-treeselect': true,
}"
:style="{
'--custom-height': '32px',
lineHeight: '32px',
'--custom-multiple-lineHeight': '14px',
}"
/>
</a-form-item>
<a-form-item :label="$t('icon')"> <a-form-item :label="$t('icon')">
<IconArea class="ci_types-icon-area" ref="iconArea" /> <IconArea class="ci_types-icon-area" ref="iconArea" />
</a-form-item> </a-form-item>
@@ -296,7 +327,14 @@ import router, { resetRouter } from '@/router'
import store from '@/store' import store from '@/store'
import draggable from 'vuedraggable' import draggable from 'vuedraggable'
import { Select, Option } from 'element-ui' import { Select, Option } from 'element-ui'
import { createCIType, updateCIType, deleteCIType } from '@/modules/cmdb/api/CIType' import {
createCIType,
updateCIType,
deleteCIType,
getCIType,
postCiTypeInheritance,
deleteCiTypeInheritance,
} from '@/modules/cmdb/api/CIType'
import { import {
getCITypeGroupsConfig, getCITypeGroupsConfig,
postCITypeGroup, postCITypeGroup,
@@ -316,6 +354,7 @@ import CMDBGrant from '../../components/cmdbGrant'
import { ops_move_icon as OpsMoveIcon } from '@/core/icons' import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
import AttributeStore from './attributeStore.vue' import AttributeStore from './attributeStore.vue'
import { getAllDepAndEmployee } from '@/api/company' import { getAllDepAndEmployee } from '@/api/company'
import CMDBTypeSelect from '../../components/cmdbTypeSelect'
export default { export default {
name: 'CITypes', name: 'CITypes',
@@ -330,6 +369,7 @@ export default {
SplitPane, SplitPane,
OpsMoveIcon, OpsMoveIcon,
AttributeStore, AttributeStore,
CMDBTypeSelect,
}, },
inject: ['reload'], inject: ['reload'],
data() { data() {
@@ -368,6 +408,9 @@ export default {
default_order_asc: '1', default_order_asc: '1',
allTreeDepAndEmp: [], allTreeDepAndEmp: [],
editCiType: null,
isInherit: false,
} }
}, },
computed: { computed: {
@@ -563,6 +606,7 @@ export default {
this.filterInput = '' this.filterInput = ''
this.form.resetFields() this.form.resetFields()
this.drawerVisible = false this.drawerVisible = false
this.isInherit = false
}, },
handleCreateNewAttrDone() { handleCreateNewAttrDone() {
this.getAttributes() this.getAttributes()
@@ -583,6 +627,22 @@ export default {
const icon = const icon =
_icon && _icon.name ? `${_icon.name}$$${_icon.color || ''}$$${_icon.id || ''}$$${_icon.url || ''}` : '' _icon && _icon.name ? `${_icon.name}$$${_icon.color || ''}$$${_icon.id || ''}$$${_icon.url || ''}` : ''
if (values.id) { if (values.id) {
const { parent_ids: oldP = [] } = this.editCiType
const { parent_ids: newP = [] } = values
const { remove, add } = this.compareArrays(newP, oldP)
if (add && add.length) {
await postCiTypeInheritance({ parent_ids: add, child_id: values.id }).catch(() => {
this.loading = false
})
}
if (remove && remove.length) {
for (let i = 0; i < remove.length; i++) {
await deleteCiTypeInheritance({ parent_id: remove[i], child_id: values.id }).catch(() => {
this.loading = false
})
}
}
delete values.parent_ids
await this.updateCIType(values.id, { await this.updateCIType(values.id, {
...values, ...values,
icon, icon,
@@ -593,6 +653,23 @@ export default {
} }
}) })
}, },
compareArrays(newArr, oldArr) {
const remove = []
const add = []
for (let i = 0; i < oldArr.length; i++) {
const item = oldArr[i]
if (newArr.indexOf(item) === -1) {
remove.push(item)
}
}
for (let i = 0; i < newArr.length; i++) {
const item = newArr[i]
if (oldArr.indexOf(item) === -1) {
add.push(item)
}
}
return { remove, add }
},
start(g) { start(g) {
console.log('start', g) console.log('start', g)
this.startId = g.id this.startId = g.id
@@ -767,37 +844,50 @@ export default {
router.addRoutes(store.getters.appRoutes) router.addRoutes(store.getters.appRoutes)
}) })
}, },
handleEdit(e, record) { async handleEdit(e, record) {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
this.drawerTitle = this.$t('cmdb.ciType.editCIType') this.drawerTitle = this.$t('cmdb.ciType.editCIType')
this.drawerVisible = true this.drawerVisible = true
getCITypeAttributesById(record.id).then((res) => { await getCITypeAttributesById(record.id).then((res) => {
this.orderSelectionOptions = res.attributes.filter((item) => item.is_required) this.orderSelectionOptions = res.attributes.filter((item) => item.is_required)
this.$nextTick(() => { })
this.default_order_asc = record.default_order_attr && record.default_order_attr.startsWith('-') ? '2' : '1' await getCIType(record.id).then((res) => {
const ci_type = res.ci_types[0]
this.form.setFieldsValue({ this.editCiType = ci_type ?? null
id: record.id, if (ci_type.parent_ids && ci_type.parent_ids.length) {
alias: record.alias, this.isInherit = true
name: record.name, this.$nextTick(() => {
unique_key: record.unique_id, this.form.setFieldsValue({
default_order_attr: parent_ids: ci_type.parent_ids,
record.default_order_attr && record.default_order_attr.startsWith('-') })
? record.default_order_attr.slice(1)
: record.default_order_attr,
}) })
this.$refs.iconArea.setIcon( }
record.icon })
? { this.$nextTick(() => {
name: record.icon.split('$$')[0] || '', this.default_order_asc = record.default_order_attr && record.default_order_attr.startsWith('-') ? '2' : '1'
color: record.icon.split('$$')[1] || '',
id: record.icon.split('$$')[2] ? Number(record.icon.split('$$')[2]) : null, this.form.setFieldsValue({
url: record.icon.split('$$')[3] || '', id: record.id,
} alias: record.alias,
: {} name: record.name,
) unique_key: record.unique_id,
default_order_attr:
record.default_order_attr && record.default_order_attr.startsWith('-')
? record.default_order_attr.slice(1)
: record.default_order_attr,
}) })
this.$refs.iconArea.setIcon(
record.icon
? {
name: record.icon.split('$$')[0] || '',
color: record.icon.split('$$')[1] || '',
id: record.icon.split('$$')[2] ? Number(record.icon.split('$$')[2]) : null,
url: record.icon.split('$$')[3] || '',
}
: {}
)
}) })
}, },
handleCreatNewAttr() { handleCreatNewAttr() {

View File

@@ -20,7 +20,9 @@
" "
>{{ $t('cancel') }}</a-button >{{ $t('cancel') }}</a-button
> >
<a-button :loading="confirmLoading" @click="handleSubmit(false)" type="primary">{{ $t('cmdb.ciType.continueAdd') }}</a-button> <a-button :loading="confirmLoading" @click="handleSubmit(false)" type="primary">{{
$t('cmdb.ciType.continueAdd')
}}</a-button>
<a-button :loading="confirmLoading" type="primary" @click="handleSubmit">{{ $t('confirm') }}</a-button> <a-button :loading="confirmLoading" type="primary" @click="handleSubmit">{{ $t('confirm') }}</a-button>
</template> </template>
<a-tabs v-model="activeKey"> <a-tabs v-model="activeKey">
@@ -47,7 +49,7 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import { searchAttributes, createCITypeAttributes, updateCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr' import { searchAttributes, createCITypeAttributes, updateCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
import { updateCITypeGroupById, getCITypeGroupById } from '@/modules/cmdb/api/CIType' import { createCITypeGroupById, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
import CreateNewAttribute from './ceateNewAttribute.vue' import CreateNewAttribute from './ceateNewAttribute.vue'
import { valueTypeMap } from '../../utils/const' import { valueTypeMap } from '../../utils/const'
import AttributesTransfer from '../../components/attributesTransfer' import AttributesTransfer from '../../components/attributesTransfer'
@@ -102,11 +104,11 @@ export default {
if (this.currentGroup) { if (this.currentGroup) {
await this.updateCurrentGroup() await this.updateCurrentGroup()
const { id, name, order, attributes } = this.currentGroup const { id, name, order, attributes } = this.currentGroup
const attrIds = attributes.map((i) => i.id) const attrIds = attributes.filter((i) => !i.inherited).map((i) => i.id)
this.targetKeys.forEach((key) => { this.targetKeys.forEach((key) => {
attrIds.push(Number(key)) attrIds.push(Number(key))
}) })
await updateCITypeGroupById(id, { name, order, attributes: [...new Set(attrIds)] }) await createCITypeGroupById(this.CITypeId, { name, order, attributes: [...new Set(attrIds)] })
} }
this.confirmLoading = false this.confirmLoading = false
this.handleClose(isCloseModal) this.handleClose(isCloseModal)
@@ -140,9 +142,9 @@ export default {
if (this.currentGroup) { if (this.currentGroup) {
await this.updateCurrentGroup() await this.updateCurrentGroup()
const { id, name, order, attributes } = this.currentGroup const { id, name, order, attributes } = this.currentGroup
const attrIds = attributes.map((i) => i.id) const attrIds = attributes.filter((i) => !i.inherited).map((i) => i.id)
attrIds.push(newAttrId) attrIds.push(newAttrId)
await updateCITypeGroupById(id, { name, order, attributes: attrIds }) await createCITypeGroupById(this.CITypeId, { name, order, attributes: attrIds })
} }
this.confirmLoading = false this.confirmLoading = false
this.loadTotalAttrs() this.loadTotalAttrs()

View File

@@ -1,5 +1,5 @@
<template> <template>
<div> <div :style="{ height: '100%' }">
<vxe-table <vxe-table
show-overflow show-overflow
show-header-overflow show-header-overflow
@@ -7,7 +7,7 @@
size="small" size="small"
class="ops-stripe-table" class="ops-stripe-table"
:data="tableData" :data="tableData"
v-bind="ci_id ? { maxHeight: `${windowHeight - 94}px` } : { height: `${windowHeight - 225}px` }" v-bind="ci_id ? { height: 'auto' } : { height: `${windowHeight - 225}px` }"
> >
<vxe-column field="trigger_name" :title="$t('cmdb.history.triggerName')"> </vxe-column> <vxe-column field="trigger_name" :title="$t('cmdb.history.triggerName')"> </vxe-column>
<vxe-column field="type" :title="$t('type')"> <vxe-column field="type" :title="$t('type')">

View File

@@ -316,7 +316,7 @@
<!-- <GrantDrawer ref="grantDrawer" resourceTypeName="RelationView" app_id="cmdb" /> --> <!-- <GrantDrawer ref="grantDrawer" resourceTypeName="RelationView" app_id="cmdb" /> -->
<CMDBGrant ref="cmdbGrant" resourceType="RelationView" app_id="cmdb" /> <CMDBGrant ref="cmdbGrant" resourceType="RelationView" app_id="cmdb" />
<ci-detail ref="detail" :typeId="Number(currentTypeId[0])" /> <CiDetailDrawer ref="detail" :typeId="Number(currentTypeId[0])" />
<create-instance-form <create-instance-form
ref="create" ref="create"
:typeIdFromRelation="Number(currentTypeId[0])" :typeIdFromRelation="Number(currentTypeId[0])"
@@ -354,7 +354,7 @@ import { roleHasPermissionToGrant } from '@/modules/acl/api/permission'
import { searchResourceType } from '@/modules/acl/api/resource' import { searchResourceType } from '@/modules/acl/api/resource'
import SplitPane from '@/components/SplitPane' import SplitPane from '@/components/SplitPane'
import EditAttrsPopover from '../ci/modules/editAttrsPopover.vue' import EditAttrsPopover from '../ci/modules/editAttrsPopover.vue'
import CiDetail from '../ci/modules/CiDetail' import CiDetailDrawer from '../ci/modules/ciDetailDrawer.vue'
import CreateInstanceForm from '../ci/modules/CreateInstanceForm' import CreateInstanceForm from '../ci/modules/CreateInstanceForm'
import JsonEditor from '../../components/JsonEditor/jsonEditor.vue' import JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
import BatchDownload from '../../components/batchDownload/batchDownload.vue' import BatchDownload from '../../components/batchDownload/batchDownload.vue'
@@ -375,7 +375,7 @@ export default {
SplitPane, SplitPane,
ElTree: Tree, ElTree: Tree,
EditAttrsPopover, EditAttrsPopover,
CiDetail, CiDetailDrawer,
CreateInstanceForm, CreateInstanceForm,
JsonEditor, JsonEditor,
BatchDownload, BatchDownload,

View File

@@ -206,7 +206,7 @@
import _ from 'lodash' import _ from 'lodash'
import SearchForm from '../../components/searchForm/SearchForm.vue' import SearchForm from '../../components/searchForm/SearchForm.vue'
import { searchCI } from '../../api/ci' import { searchCI } from '../../api/ci'
import { searchAttributes, getCITypeAttributesByTypeIds } from '../../api/CITypeAttr' import { searchAttributes, getCITypeAttributesByTypeIds, getCITypeAttributesById } from '../../api/CITypeAttr'
import { getCITypes } from '../../api/CIType' import { getCITypes } from '../../api/CIType'
import { getSubscribeAttributes } from '../../api/preference' import { getSubscribeAttributes } from '../../api/preference'
import { getCITableColumns } from '../../utils/helper' import { getCITableColumns } from '../../utils/helper'
@@ -315,61 +315,59 @@ export default {
this.instanceList = [] this.instanceList = []
this.totalNumber = res['numfound'] this.totalNumber = res['numfound']
const oldData = res.result const { attributes: resAllAttributes } = await getCITypeAttributesByTypeIds({
type_ids: Object.keys(res.counter).join(','),
function allKeys(data) { })
const keys = {} const _columnsGroup = Object.keys(res.counter).map((key) => {
const ignoreAttr = ['_id', '_type', 'ci_type', 'ci_type_alias', 'unique', 'unique_alias'] const _find = this.ciTypes.find((item) => item.name === key)
data.forEach((item) => { return {
Object.keys(item).forEach((key) => { id: `parent-${_find.id}`,
if (!ignoreAttr.includes(key)) { value: key,
keys[key] = '' label: _find?.alias || _find?.name,
} isCiType: true,
}) }
})
const ciTypeAttribute = {}
const promises = _columnsGroup.map((item) => {
return getCITypeAttributesById(item.id.split('-')[1]).then((res) => {
ciTypeAttribute[item.label] = res.attributes
}) })
return keys })
} await Promise.all(promises)
function tidy(data) { const outputKeys = {}
const outputKeys = allKeys(data) resAllAttributes.forEach((attr) => {
const common = {} outputKeys[attr.name] = ''
data.forEach((item) => { })
const tmp = {}
Object.keys(outputKeys).forEach((j) => { const common = {}
if (j in item) { Object.keys(outputKeys).forEach((key) => {
tmp[j] = item[j] Object.entries(ciTypeAttribute).forEach(([type, attrs]) => {
// 提取common if (attrs.find((a) => a.name === key)) {
{ if (key in common) {
const key = item['ci_type_alias'] common[key][type] = ''
if (j in common) {
common[j][[key]] = ''
} else {
common[j] = { [key]: '' }
}
}
} else { } else {
tmp[j] = null common[key] = { [type]: '' }
}
})
})
const commonObject = {}
const commonKeys = []
// 整理common
Object.keys(common).forEach((key) => {
if (Object.keys(common[key]).length > 1) {
commonKeys.push(key)
const reverseKey = Object.keys(common[key]).join('&')
if (!commonObject[reverseKey]) {
commonObject[reverseKey] = [key]
} else {
commonObject[reverseKey].push(key)
} }
} }
}) })
return { commonObject, commonKeys } })
}
const commonObject = {}
const commonKeys = []
// 整理common
Object.keys(common).forEach((key) => {
if (Object.keys(common[key]).length > 1) {
commonKeys.push(key)
const reverseKey = Object.keys(common[key]).join('&')
if (!commonObject[reverseKey]) {
commonObject[reverseKey] = [key]
} else {
commonObject[reverseKey].push(key)
}
}
})
const { commonObject, commonKeys } = tidy(oldData)
const _commonColumnsGroup = Object.keys(commonObject).map((key) => { const _commonColumnsGroup = Object.keys(commonObject).map((key) => {
return { return {
id: `parent-${key}`, id: `parent-${key}`,
@@ -385,24 +383,14 @@ export default {
} }
}) })
const _columnsGroup = Object.keys(res.counter).map((key) => { const promises1 = _columnsGroup.map((item) => {
const _find = this.ciTypes.find((item) => item.name === key)
return {
id: `parent-${_find.id}`,
value: key,
label: _find?.alias || _find?.name,
isCiType: true,
}
})
const promises = _columnsGroup.map((item) => {
return getSubscribeAttributes(item.id.split('-')[1]).then((res1) => { return getSubscribeAttributes(item.id.split('-')[1]).then((res1) => {
item.children = this.getColumns(res.result, res1.attributes).filter( item.children = this.getColumns(res.result, res1.attributes).filter(
(col) => !commonKeys.includes(col.field) (col) => !commonKeys.includes(col.field)
) )
}) })
}) })
await Promise.all(promises).then(() => { await Promise.all(promises1).then(() => {
this.columnsGroup = [..._commonColumnsGroup, ..._columnsGroup] this.columnsGroup = [..._commonColumnsGroup, ..._columnsGroup]
this.instanceList = res['result'] this.instanceList = res['result']
}) })

View File

@@ -371,7 +371,7 @@
} }
" "
/> />
<ci-detail ref="detail" :typeId="Number(typeId)" :treeViewsLevels="treeViewsLevels" /> <CiDetailDrawer ref="detail" :typeId="Number(typeId)" :treeViewsLevels="treeViewsLevels" />
<create-instance-form <create-instance-form
ref="create" ref="create"
:typeIdFromRelation="Number(typeId)" :typeIdFromRelation="Number(typeId)"
@@ -404,7 +404,7 @@ import PasswordField from '../../components/passwordField/index.vue'
import SplitPane from '@/components/SplitPane' import SplitPane from '@/components/SplitPane'
import TreeViewsNode from './modules/treeViewsNode.vue' import TreeViewsNode from './modules/treeViewsNode.vue'
import EditAttrsPopover from '../ci/modules/editAttrsPopover.vue' import EditAttrsPopover from '../ci/modules/editAttrsPopover.vue'
import CiDetail from '../ci/modules/CiDetail' import CiDetailDrawer from '../ci/modules/ciDetailDrawer.vue'
import CreateInstanceForm from '../ci/modules/CreateInstanceForm' import CreateInstanceForm from '../ci/modules/CreateInstanceForm'
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr' import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
import JsonEditor from '../../components/JsonEditor/jsonEditor.vue' import JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
@@ -424,7 +424,7 @@ export default {
SplitPane, SplitPane,
TreeViewsNode, TreeViewsNode,
EditAttrsPopover, EditAttrsPopover,
CiDetail, CiDetailDrawer,
CreateInstanceForm, CreateInstanceForm,
JsonEditor, JsonEditor,
BatchDownload, BatchDownload,

View File

@@ -139,7 +139,7 @@ export default {
console.log('login form', values) console.log('login form', values)
const loginParams = { ...values } const loginParams = { ...values }
delete loginParams.username delete loginParams.username
loginParams[!state.loginType ? 'email' : 'username'] = values.username loginParams.username = values.username
loginParams.password = appConfig.useEncryption ? md5(values.password) : values.password loginParams.password = appConfig.useEncryption ? md5(values.password) : values.password
localStorage.setItem('ops_auth_type', '') localStorage.setItem('ops_auth_type', '')
Login({ userInfo: loginParams }) Login({ userInfo: loginParams })

View File

@@ -33,7 +33,7 @@ services:
- redis - redis
cmdb-api: cmdb-api:
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.3.10 image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.3.12
# build: # build:
# context: . # context: .
# target: cmdb-api # target: cmdb-api
@@ -57,6 +57,8 @@ services:
nohup flask cmdb-trigger > trigger.log 2>&1 & nohup flask cmdb-trigger > trigger.log 2>&1 &
flask cmdb-init-cache flask cmdb-init-cache
flask cmdb-init-acl flask cmdb-init-acl
flask init-import-user-from-acl
flask init-department
flask cmdb-counter > counter.log 2>&1 flask cmdb-counter > counter.log 2>&1
depends_on: depends_on:
@@ -68,7 +70,7 @@ services:
- cmdb-api - cmdb-api
cmdb-ui: cmdb-ui:
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.3.10 image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.3.12
# build: # build:
# context: . # context: .
# target: cmdb-ui # target: cmdb-ui

File diff suppressed because one or more lines are too long