mirror of
https://github.com/veops/cmdb.git
synced 2025-09-07 05:47:00 +08:00
Compare commits
32 Commits
dependabot
...
2.3.12
Author | SHA1 | Date | |
---|---|---|---|
|
693ae4ff05 | ||
|
a1a9d99eb4 | ||
|
e045e0fb43 | ||
|
09376dbd2b | ||
|
7fda5a1e7b | ||
|
113b84763f | ||
|
190170acad | ||
|
513d2af4b8 | ||
|
4588bd8996 | ||
|
082da5fade | ||
|
013b116eb5 | ||
|
208d29165b | ||
|
d510330cde | ||
|
ea4f0fc2a5 | ||
|
9bcdaacdc4 | ||
|
5045581ddf | ||
|
232913172c | ||
|
157e1809ed | ||
|
ab8ccf7d1b | ||
|
ae1f0f6b4f | ||
|
ff47e0ade6 | ||
|
0dd272fb04 | ||
|
6bc5c1516d | ||
|
4a4b9e6ef0 | ||
|
6e3f9478b3 | ||
|
691051c254 | ||
|
8f066e95a6 | ||
|
75bca39bf6 | ||
|
3360e4d0fe | ||
|
521fcd0ba2 | ||
|
fc113425cb | ||
|
81a76a9632 |
@@ -5,8 +5,8 @@ name = "pypi"
|
||||
|
||||
[packages]
|
||||
# Flask
|
||||
Flask = "==2.3.2"
|
||||
Werkzeug = ">=2.3.6"
|
||||
Flask = "==2.2.5"
|
||||
Werkzeug = "==2.2.3"
|
||||
click = ">=5.0"
|
||||
# Api
|
||||
Flask-RESTful = "==0.3.10"
|
||||
|
@@ -50,7 +50,7 @@ def add_user():
|
||||
|
||||
if is_admin:
|
||||
app = AppCache.get('acl') or App.create(name='acl')
|
||||
acl_admin = RoleCache.get('acl_admin') or RoleCRUD.add_role('acl_admin', app.id, True)
|
||||
acl_admin = RoleCache.get_by_name(app.id, 'acl_admin') or RoleCRUD.add_role('acl_admin', app.id, True)
|
||||
rid = RoleCache.get_by_name(None, username).id
|
||||
|
||||
RoleRelationCRUD.add(acl_admin, acl_admin.id, [rid], app.id)
|
||||
|
@@ -115,6 +115,8 @@ def cmdb_init_acl():
|
||||
_app = AppCache.get('cmdb') or App.create(name='cmdb')
|
||||
app_id = _app.id
|
||||
|
||||
current_app.test_request_context().push()
|
||||
|
||||
# 1. add resource type
|
||||
for resource_type in ResourceTypeEnum.all():
|
||||
try:
|
||||
@@ -183,11 +185,21 @@ def cmdb_counter():
|
||||
UserCRUD.add(username='worker', password=uuid.uuid4().hex, email='worker@xxx.com')
|
||||
|
||||
login_user(UserCache.get('worker'))
|
||||
|
||||
i = 0
|
||||
while True:
|
||||
try:
|
||||
db.session.remove()
|
||||
|
||||
CMDBCounterCache.reset()
|
||||
|
||||
if i % 5 == 0:
|
||||
CMDBCounterCache.flush_adc_counter()
|
||||
i = 0
|
||||
|
||||
CMDBCounterCache.flush_sub_counter()
|
||||
|
||||
i += 1
|
||||
except:
|
||||
import traceback
|
||||
print(traceback.format_exc())
|
||||
|
@@ -4,7 +4,7 @@ from flask.cli import with_appcontext
|
||||
from werkzeug.datastructures import MultiDict
|
||||
|
||||
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.models.common_setting import Employee, Department
|
||||
|
||||
@@ -158,50 +158,11 @@ class InitDepartment(object):
|
||||
|
||||
def init_backend_resource(self):
|
||||
acl = self.check_app('backend')
|
||||
resources_types = acl.get_all_resources_types()
|
||||
|
||||
perms = ['read', 'grant', 'delete', 'update']
|
||||
|
||||
acl_rid = self.get_admin_user_rid()
|
||||
|
||||
results = list(filter(lambda t: t['name'] == '操作权限', resources_types['groups']))
|
||||
if len(results) == 0:
|
||||
payload = dict(
|
||||
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)
|
||||
if acl_rid == 0:
|
||||
return
|
||||
GrantEmployeeACLPerm(acl).grant_by_rid(acl_rid, True)
|
||||
|
||||
@staticmethod
|
||||
def check_app(app_name):
|
||||
|
@@ -89,6 +89,19 @@ def db_setup():
|
||||
"""
|
||||
db.create_all()
|
||||
|
||||
try:
|
||||
db.session.execute("set global sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,"
|
||||
"ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'")
|
||||
db.session.commit()
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
db.session.execute("set global tidb_enable_noop_functions='ON'")
|
||||
db.session.commit()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@click.group()
|
||||
def translate():
|
||||
|
@@ -330,8 +330,8 @@ class AutoDiscoveryCICRUD(DBMixin):
|
||||
|
||||
@staticmethod
|
||||
def get_attributes_by_type_id(type_id):
|
||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
attributes = [i[1] for i in CITypeAttributesCache.get2(type_id) or []]
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
attributes = [i[1] for i in CITypeAttributeManager.get_all_attributes(type_id) or []]
|
||||
|
||||
attr_names = set()
|
||||
adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id)
|
||||
|
@@ -5,10 +5,14 @@ from __future__ import unicode_literals
|
||||
from flask import current_app
|
||||
|
||||
from api.extensions import cache
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.custom_dashboard import CustomDashboardManager
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIType
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
from api.models.cmdb import PreferenceTreeView
|
||||
from api.models.cmdb import RelationType
|
||||
|
||||
|
||||
@@ -226,7 +230,9 @@ class CITypeAttributeCache(object):
|
||||
|
||||
|
||||
class CMDBCounterCache(object):
|
||||
KEY = 'CMDB::Counter'
|
||||
KEY = 'CMDB::Counter::dashboard'
|
||||
KEY2 = 'CMDB::Counter::adc'
|
||||
KEY3 = 'CMDB::Counter::sub'
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
@@ -429,3 +435,47 @@ class CMDBCounterCache(object):
|
||||
return
|
||||
|
||||
return numfound
|
||||
|
||||
@classmethod
|
||||
def flush_adc_counter(cls):
|
||||
res = db.session.query(CI.type_id, CI.is_auto_discovery)
|
||||
result = dict()
|
||||
for i in res:
|
||||
result.setdefault(i.type_id, dict(total=0, auto_discovery=0))
|
||||
result[i.type_id]['total'] += 1
|
||||
if i.is_auto_discovery:
|
||||
result[i.type_id]['auto_discovery'] += 1
|
||||
|
||||
cache.set(cls.KEY2, result, timeout=0)
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_adc_counter(cls):
|
||||
return cache.get(cls.KEY2) or cls.flush_adc_counter()
|
||||
|
||||
@classmethod
|
||||
def flush_sub_counter(cls):
|
||||
result = dict(type_id2users=dict())
|
||||
|
||||
types = db.session.query(PreferenceShowAttributes.type_id,
|
||||
PreferenceShowAttributes.uid, PreferenceShowAttributes.created_at).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
||||
PreferenceShowAttributes.uid, PreferenceShowAttributes.type_id)
|
||||
for i in types:
|
||||
result['type_id2users'].setdefault(i.type_id, []).append(i.uid)
|
||||
|
||||
types = PreferenceTreeView.get_by(to_dict=False)
|
||||
for i in types:
|
||||
|
||||
result['type_id2users'].setdefault(i.type_id, [])
|
||||
if i.uid not in result['type_id2users'][i.type_id]:
|
||||
result['type_id2users'][i.type_id].append(i.uid)
|
||||
|
||||
cache.set(cls.KEY3, result, timeout=0)
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_sub_counter(cls):
|
||||
return cache.get(cls.KEY3) or cls.flush_sub_counter()
|
||||
|
@@ -14,8 +14,8 @@ from werkzeug.exceptions import BadRequest
|
||||
from api.extensions import db
|
||||
from api.extensions import rd
|
||||
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 CMDBCounterCache
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
@@ -52,7 +52,6 @@ from api.models.cmdb import AttributeHistory
|
||||
from api.models.cmdb import AutoDiscoveryCI
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.models.cmdb import CITypeTrigger
|
||||
from api.tasks.cmdb import ci_cache
|
||||
@@ -218,15 +217,7 @@ class CIManager(object):
|
||||
|
||||
@classmethod
|
||||
def get_ad_statistics(cls):
|
||||
res = CI.get_by(to_dict=False)
|
||||
result = dict()
|
||||
for i in res:
|
||||
result.setdefault(i.type_id, dict(total=0, auto_discovery=0))
|
||||
result[i.type_id]['total'] += 1
|
||||
if i.is_auto_discovery:
|
||||
result[i.type_id]['auto_discovery'] += 1
|
||||
|
||||
return result
|
||||
return CMDBCounterCache.get_adc_counter()
|
||||
|
||||
@staticmethod
|
||||
def ci_is_exist(unique_key, unique_value, type_id):
|
||||
@@ -317,14 +308,18 @@ class CIManager(object):
|
||||
"""
|
||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
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(
|
||||
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 = 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_alias = {attr.alias: attr for _, attr in attrs}
|
||||
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
||||
@@ -354,7 +349,8 @@ class CIManager(object):
|
||||
if attr.default.get('default') and attr.default.get('default') in (
|
||||
AttributeDefaultValueEnum.CREATED_AT, AttributeDefaultValueEnum.UPDATED_AT):
|
||||
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)
|
||||
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)):
|
||||
@@ -368,6 +364,8 @@ class CIManager(object):
|
||||
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
ci_dict[attr.name] = now
|
||||
|
||||
value_manager = AttributeValueManager()
|
||||
|
||||
computed_attrs = []
|
||||
for _, attr in attrs:
|
||||
if attr.is_computed:
|
||||
@@ -378,7 +376,8 @@ class CIManager(object):
|
||||
elif attr.alias in ci_dict:
|
||||
password_dict[attr.id] = ci_dict.pop(attr.alias)
|
||||
|
||||
value_manager = AttributeValueManager()
|
||||
if attr.re_check and password_dict.get(attr.id):
|
||||
value_manager.check_re(attr.re_check, password_dict[attr.id])
|
||||
|
||||
if computed_attrs:
|
||||
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
|
||||
@@ -386,7 +385,7 @@ class CIManager(object):
|
||||
cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
|
||||
|
||||
ref_ci_dict = dict()
|
||||
for k in ci_dict:
|
||||
for k in copy.deepcopy(ci_dict):
|
||||
if k.startswith("$") and "." in k:
|
||||
ref_ci_dict[k] = ci_dict[k]
|
||||
continue
|
||||
@@ -398,7 +397,10 @@ class CIManager(object):
|
||||
_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))
|
||||
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}
|
||||
|
||||
@@ -430,13 +432,17 @@ class CIManager(object):
|
||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
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_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
||||
for _, attr in attrs:
|
||||
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
ci_dict[attr.name] = now
|
||||
|
||||
value_manager = AttributeValueManager()
|
||||
|
||||
password_dict = dict()
|
||||
computed_attrs = list()
|
||||
for _, attr in attrs:
|
||||
@@ -448,7 +454,8 @@ class CIManager(object):
|
||||
elif attr.alias in ci_dict:
|
||||
password_dict[attr.id] = ci_dict.pop(attr.alias)
|
||||
|
||||
value_manager = AttributeValueManager()
|
||||
if attr.re_check and password_dict.get(attr.id):
|
||||
value_manager.check_re(attr.re_check, password_dict[attr.id])
|
||||
|
||||
if computed_attrs:
|
||||
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
|
||||
@@ -464,9 +471,12 @@ class CIManager(object):
|
||||
key2attr = value_manager.valid_attr_value(ci_dict, ci.type_id, ci.id, ci_type_attrs_name,
|
||||
ci_attr2type_attr=ci_attr2type_attr)
|
||||
if limit_attrs:
|
||||
for k in ci_dict:
|
||||
for k in copy.deepcopy(ci_dict):
|
||||
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:
|
||||
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr)
|
||||
@@ -514,8 +524,7 @@ class CIManager(object):
|
||||
|
||||
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 = [AttributeCache.get(attr.attr_id) for attr in attrs]
|
||||
attrs = [i for _, i in CITypeAttributeManager.get_all_attributes(type_id=ci.type_id)]
|
||||
for attr in attrs:
|
||||
value_table = TableMap(attr=attr).table
|
||||
for item in value_table.get_by(ci_id=ci_id, to_dict=False):
|
||||
|
@@ -42,10 +42,12 @@ from api.models.cmdb import CITypeAttributeGroup
|
||||
from api.models.cmdb import CITypeAttributeGroupItem
|
||||
from api.models.cmdb import CITypeGroup
|
||||
from api.models.cmdb import CITypeGroupItem
|
||||
from api.models.cmdb import CITypeInheritance
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.models.cmdb import CITypeTrigger
|
||||
from api.models.cmdb import CITypeUniqueConstraint
|
||||
from api.models.cmdb import CustomDashboard
|
||||
from api.models.cmdb import PreferenceCITypeOrder
|
||||
from api.models.cmdb import PreferenceRelationView
|
||||
from api.models.cmdb import PreferenceSearchOption
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
@@ -75,16 +77,18 @@ class CITypeManager(object):
|
||||
return CIType.get_by_id(ci_type.id)
|
||||
|
||||
@staticmethod
|
||||
def get_ci_types(type_name=None):
|
||||
def get_ci_types(type_name=None, like=True):
|
||||
resources = None
|
||||
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
|
||||
resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.CI_TYPE)])
|
||||
|
||||
ci_types = CIType.get_by() if type_name is None else CIType.get_by_like(name=type_name)
|
||||
ci_types = CIType.get_by() if type_name is None else (
|
||||
CIType.get_by_like(name=type_name) if like else CIType.get_by(name=type_name))
|
||||
res = list()
|
||||
for type_dict in ci_types:
|
||||
attr = AttributeCache.get(type_dict["unique_id"])
|
||||
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:
|
||||
res.append(type_dict)
|
||||
|
||||
@@ -131,8 +135,13 @@ class CITypeManager(object):
|
||||
|
||||
kwargs["unique_id"] = unique_key.id
|
||||
kwargs['uid'] = current_user.uid
|
||||
|
||||
parent_ids = kwargs.pop('parent_ids', None)
|
||||
|
||||
ci_type = CIType.create(**kwargs)
|
||||
|
||||
CITypeInheritanceManager.add(parent_ids, ci_type.id)
|
||||
|
||||
CITypeAttributeManager.add(ci_type.id, [unique_key.id], is_required=True)
|
||||
|
||||
CITypeCache.clean(ci_type.name)
|
||||
@@ -222,13 +231,19 @@ class CITypeManager(object):
|
||||
|
||||
for table in [PreferenceTreeView, PreferenceShowAttributes, PreferenceSearchOption, CustomDashboard,
|
||||
CITypeGroupItem, CITypeAttributeGroup, CITypeAttribute, CITypeUniqueConstraint, CITypeTrigger,
|
||||
AutoDiscoveryCIType, CIFilterPerms]:
|
||||
AutoDiscoveryCIType, CIFilterPerms, PreferenceCITypeOrder]:
|
||||
for item in table.get_by(type_id=type_id, to_dict=False):
|
||||
item.soft_delete(commit=False)
|
||||
|
||||
for item in AutoDiscoveryCI.get_by(type_id=type_id, to_dict=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()
|
||||
|
||||
ci_type.soft_delete()
|
||||
@@ -241,6 +256,100 @@ class CITypeManager(object):
|
||||
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):
|
||||
cls = CITypeGroup
|
||||
|
||||
@@ -261,6 +370,7 @@ class CITypeGroupManager(object):
|
||||
ci_type = CITypeCache.get(t['type_id']).to_dict()
|
||||
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['inherited'] = True if CITypeInheritanceManager.get_parents(ci_type['id']) else False
|
||||
group.setdefault("ci_types", []).append(ci_type)
|
||||
group_types.add(t["type_id"])
|
||||
|
||||
@@ -270,6 +380,7 @@ class CITypeGroupManager(object):
|
||||
for ci_type in ci_types:
|
||||
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['inherited'] = True if CITypeInheritanceManager.get_parents(ci_type['id']) else False
|
||||
other_types['ci_types'].append(ci_type)
|
||||
|
||||
groups.append(other_types)
|
||||
@@ -361,40 +472,62 @@ class CITypeAttributeManager(object):
|
||||
return attr.name
|
||||
|
||||
@staticmethod
|
||||
def get_attr_names_by_type_id(type_id):
|
||||
return [AttributeCache.get(attr.attr_id).name for attr in CITypeAttributesCache.get(type_id)]
|
||||
def get_all_attributes(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
|
||||
def get_attributes_by_type_id(type_id, choice_web_hook_parse=True, choice_other_parse=True):
|
||||
has_config_perm = ACLManager('cmdb').has_permission(
|
||||
CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG)
|
||||
|
||||
attrs = CITypeAttributesCache.get(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)
|
||||
parent_ids = CITypeInheritanceManager.base(type_id)
|
||||
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
def get_common_attributes(type_ids):
|
||||
@classmethod
|
||||
def get_common_attributes(cls, type_ids):
|
||||
has_config_perm = False
|
||||
for type_id in type_ids:
|
||||
has_config_perm |= ACLManager('cmdb').has_permission(
|
||||
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 = {}
|
||||
for i in result:
|
||||
attr2types.setdefault(i.attr_id, []).append(i.type_id)
|
||||
for type_id in result:
|
||||
for i in result[type_id]:
|
||||
attr2types.setdefault(i.id, []).append(type_id)
|
||||
|
||||
attrs = []
|
||||
for attr_id in attr2types:
|
||||
@@ -528,8 +661,18 @@ class CITypeAttributeManager(object):
|
||||
attr_id = _from.get('attr_id')
|
||||
from_group_id = _from.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')
|
||||
|
||||
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 is not None:
|
||||
CITypeAttributeGroupManager.delete_item(from_group_id, attr_id)
|
||||
@@ -655,15 +798,15 @@ class CITypeRelationManager(object):
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.circular_dependency_error)
|
||||
|
||||
if constraint == ConstraintEnum.Many2Many:
|
||||
other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
|
||||
to_dict=False, first=True)
|
||||
other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
|
||||
to_dict=False, first=True)
|
||||
if other_c and other_c.child_id != c.id:
|
||||
return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
|
||||
if other_p and other_p.parent_id != p.id:
|
||||
return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
|
||||
# if constraint == ConstraintEnum.Many2Many:
|
||||
# other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
|
||||
# to_dict=False, first=True)
|
||||
# other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
|
||||
# to_dict=False, first=True)
|
||||
# if other_c and other_c.child_id != c.id:
|
||||
# return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
|
||||
# if other_p and other_p.parent_id != p.id:
|
||||
# return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
|
||||
|
||||
existed = cls._get(p.id, c.id)
|
||||
if existed is not None:
|
||||
@@ -738,25 +881,66 @@ class CITypeAttributeGroupManager(object):
|
||||
|
||||
@staticmethod
|
||||
def get_by_type_id(type_id, need_other=False):
|
||||
groups = CITypeAttributeGroup.get_by(type_id=type_id)
|
||||
groups = sorted(groups, key=lambda x: x["order"] or 0)
|
||||
grouped = list()
|
||||
parent_ids = CITypeInheritanceManager.base(type_id)
|
||||
|
||||
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)
|
||||
id2attr = {i.get('id'): i for i in attributes}
|
||||
|
||||
group2pos = dict()
|
||||
attr2pos = dict()
|
||||
result = []
|
||||
for group in groups:
|
||||
items = CITypeAttributeGroupItem.get_by(group_id=group["id"], to_dict=False)
|
||||
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:
|
||||
grouped = set(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
|
||||
def create_or_update(type_id, name, attr_order, group_order=0, is_update=False):
|
||||
@@ -890,10 +1074,16 @@ class CITypeAttributeGroupManager(object):
|
||||
@classmethod
|
||||
def transfer(cls, 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)))
|
||||
|
||||
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)))
|
||||
|
||||
from_order, to_order = from_group.order, to_group.order
|
||||
@@ -1274,7 +1464,7 @@ class CITypeTemplateManager(object):
|
||||
from api.lib.common_setting.upload_file import CommonFileCRUD
|
||||
|
||||
tpt = dict(
|
||||
ci_types=CITypeManager.get_ci_types(type_name=ci_type.name),
|
||||
ci_types=CITypeManager.get_ci_types(type_name=ci_type.name, like=False),
|
||||
ci_type_auto_discovery_rules=list(),
|
||||
type2attributes=dict(),
|
||||
type2attribute_group=dict(),
|
||||
|
@@ -114,6 +114,8 @@ class CIFilterPermsCRUD(DBMixin):
|
||||
|
||||
obj.soft_delete()
|
||||
|
||||
return obj
|
||||
|
||||
else:
|
||||
if not kwargs.get('ci_filter') and not kwargs.get('attr_filter'):
|
||||
return
|
||||
|
@@ -14,6 +14,8 @@ from api.lib.cmdb.attribute import AttributeManager
|
||||
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 CMDBCounterCache
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
from api.lib.cmdb.const import ConstraintEnum
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
@@ -24,6 +26,7 @@ from api.lib.exception import AbortException
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.models.cmdb import PreferenceCITypeOrder
|
||||
from api.models.cmdb import PreferenceRelationView
|
||||
from api.models.cmdb import PreferenceSearchOption
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
@@ -38,13 +41,22 @@ class PreferenceManager(object):
|
||||
|
||||
@staticmethod
|
||||
def get_types(instance=False, tree=False):
|
||||
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, to_dict=False), key=lambda x: x.order)
|
||||
|
||||
types = db.session.query(PreferenceShowAttributes.type_id).filter(
|
||||
PreferenceShowAttributes.uid == current_user.uid).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
||||
PreferenceShowAttributes.type_id).all() if instance else []
|
||||
types = sorted(types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
|
||||
ci_type_order) if not i.is_tree}.get(x.type_id, 1))
|
||||
|
||||
tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else []
|
||||
type_ids = set([i.type_id for i in types + tree_types])
|
||||
tree_types = sorted(tree_types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
|
||||
ci_type_order) if i.is_tree}.get(x.type_id, 1))
|
||||
|
||||
type_ids = [i.type_id for i in types + tree_types]
|
||||
if types and tree_types:
|
||||
type_ids = set(type_ids)
|
||||
|
||||
return [CITypeCache.get(type_id).to_dict() for type_id in type_ids]
|
||||
|
||||
@@ -59,32 +71,36 @@ class PreferenceManager(object):
|
||||
:param tree:
|
||||
:return:
|
||||
"""
|
||||
result = dict(self=dict(instance=[], tree=[], type_id2subs_time=dict()),
|
||||
type_id2users=dict())
|
||||
result = dict(self=dict(instance=[], tree=[], type_id2subs_time=dict()))
|
||||
|
||||
result.update(CMDBCounterCache.get_sub_counter())
|
||||
|
||||
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, to_dict=False), key=lambda x: x.order)
|
||||
if instance:
|
||||
types = db.session.query(PreferenceShowAttributes.type_id,
|
||||
PreferenceShowAttributes.uid, PreferenceShowAttributes.created_at).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
||||
PreferenceShowAttributes.deleted.is_(False)).filter(
|
||||
PreferenceShowAttributes.uid == current_user.uid).group_by(
|
||||
PreferenceShowAttributes.uid, PreferenceShowAttributes.type_id)
|
||||
for i in types:
|
||||
if i.uid == current_user.uid:
|
||||
result['self']['instance'].append(i.type_id)
|
||||
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
|
||||
result['self']['type_id2subs_time'][i.type_id] = i.created_at
|
||||
result['self']['instance'].append(i.type_id)
|
||||
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
|
||||
result['self']['type_id2subs_time'][i.type_id] = i.created_at
|
||||
|
||||
result['type_id2users'].setdefault(i.type_id, []).append(i.uid)
|
||||
instance_order = [i.type_id for i in ci_type_order if not i.is_tree]
|
||||
if len(instance_order) == len(result['self']['instance']):
|
||||
result['self']['instance'] = instance_order
|
||||
|
||||
if tree:
|
||||
types = PreferenceTreeView.get_by(to_dict=False)
|
||||
types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False)
|
||||
for i in types:
|
||||
if i.uid == current_user.uid:
|
||||
result['self']['tree'].append(i.type_id)
|
||||
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
|
||||
result['self']['type_id2subs_time'][i.type_id] = i.created_at
|
||||
result['self']['tree'].append(i.type_id)
|
||||
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
|
||||
result['self']['type_id2subs_time'][i.type_id] = i.created_at
|
||||
|
||||
result['type_id2users'].setdefault(i.type_id, [])
|
||||
if i.uid not in result['type_id2users'][i.type_id]:
|
||||
result['type_id2users'][i.type_id].append(i.uid)
|
||||
tree_order = [i.type_id for i in ci_type_order if i.is_tree]
|
||||
if len(tree_order) == len(result['self']['tree']):
|
||||
result['self']['tree'] = tree_order
|
||||
|
||||
return result
|
||||
|
||||
@@ -98,8 +114,8 @@ class PreferenceManager(object):
|
||||
CITypeAttribute, CITypeAttribute.attr_id == PreferenceShowAttributes.attr_id).filter(
|
||||
PreferenceShowAttributes.uid == current_user.uid).filter(
|
||||
PreferenceShowAttributes.type_id == type_id).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).filter(
|
||||
CITypeAttribute.type_id == type_id).all()
|
||||
PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).group_by(
|
||||
CITypeAttribute.attr_id).all()
|
||||
|
||||
result = []
|
||||
for i in sorted(attrs, key=lambda x: x.PreferenceShowAttributes.order):
|
||||
@@ -109,17 +125,16 @@ class PreferenceManager(object):
|
||||
|
||||
is_subscribed = True
|
||||
if not attrs:
|
||||
attrs = db.session.query(CITypeAttribute).filter(
|
||||
CITypeAttribute.type_id == type_id).filter(
|
||||
CITypeAttribute.deleted.is_(False)).filter(
|
||||
CITypeAttribute.default_show.is_(True)).order_by(CITypeAttribute.order)
|
||||
result = [i.attr.to_dict() for i in attrs]
|
||||
result = CITypeAttributeManager.get_attributes_by_type_id(type_id,
|
||||
choice_web_hook_parse=False,
|
||||
choice_other_parse=False)
|
||||
result = [i for i in result if i['default_show']]
|
||||
is_subscribed = False
|
||||
|
||||
for i in result:
|
||||
if i["is_choice"]:
|
||||
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
|
||||
|
||||
@@ -151,9 +166,22 @@ class PreferenceManager(object):
|
||||
if i.attr_id not in attr_dict:
|
||||
i.soft_delete()
|
||||
|
||||
if not existed_all and attr_order:
|
||||
cls.add_ci_type_order_item(type_id, is_tree=False)
|
||||
|
||||
elif not PreferenceShowAttributes.get_by(type_id=type_id, uid=current_user.uid, to_dict=False):
|
||||
cls.delete_ci_type_order_item(type_id, is_tree=False)
|
||||
|
||||
@staticmethod
|
||||
def get_tree_view():
|
||||
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, is_tree=True, to_dict=False),
|
||||
key=lambda x: x.order)
|
||||
|
||||
res = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=True)
|
||||
if ci_type_order:
|
||||
res = sorted(res, key=lambda x: {ii.type_id: idx for idx, ii in enumerate(
|
||||
ci_type_order)}.get(x['type_id'], 1))
|
||||
|
||||
for item in res:
|
||||
if item["levels"]:
|
||||
ci_type = CITypeCache.get(item['type_id']).to_dict()
|
||||
@@ -172,8 +200,8 @@ class PreferenceManager(object):
|
||||
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def create_or_update_tree_view(type_id, levels):
|
||||
@classmethod
|
||||
def create_or_update_tree_view(cls, type_id, levels):
|
||||
attrs = CITypeAttributesCache.get(type_id)
|
||||
for idx, i in enumerate(levels):
|
||||
for attr in attrs:
|
||||
@@ -185,9 +213,12 @@ class PreferenceManager(object):
|
||||
if existed is not None:
|
||||
if not levels:
|
||||
existed.soft_delete()
|
||||
cls.delete_ci_type_order_item(type_id, is_tree=True)
|
||||
return existed
|
||||
return existed.update(levels=levels)
|
||||
elif levels:
|
||||
cls.add_ci_type_order_item(type_id, is_tree=True)
|
||||
|
||||
return PreferenceTreeView.create(levels=levels, type_id=type_id, uid=current_user.uid)
|
||||
|
||||
@staticmethod
|
||||
@@ -356,6 +387,9 @@ class PreferenceManager(object):
|
||||
for i in PreferenceTreeView.get_by(type_id=type_id, uid=uid, to_dict=False):
|
||||
i.soft_delete()
|
||||
|
||||
for i in PreferenceCITypeOrder.get_by(type_id=type_id, uid=uid, to_dict=False):
|
||||
i.soft_delete()
|
||||
|
||||
@staticmethod
|
||||
def can_edit_relation(parent_id, child_id):
|
||||
views = PreferenceRelationView.get_by(to_dict=False)
|
||||
@@ -381,3 +415,36 @@ class PreferenceManager(object):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def add_ci_type_order_item(type_id, is_tree=False):
|
||||
max_order = PreferenceCITypeOrder.get_by(
|
||||
uid=current_user.uid, is_tree=is_tree, only_query=True).order_by(PreferenceCITypeOrder.order.desc()).first()
|
||||
order = (max_order and max_order.order + 1) or 1
|
||||
|
||||
PreferenceCITypeOrder.create(type_id=type_id, is_tree=is_tree, uid=current_user.uid, order=order)
|
||||
|
||||
@staticmethod
|
||||
def delete_ci_type_order_item(type_id, is_tree=False):
|
||||
existed = PreferenceCITypeOrder.get_by(uid=current_user.uid, type_id=type_id, is_tree=is_tree,
|
||||
first=True, to_dict=False)
|
||||
|
||||
existed and existed.soft_delete()
|
||||
|
||||
@staticmethod
|
||||
def upsert_ci_type_order(type_ids, is_tree=False):
|
||||
for idx, type_id in enumerate(type_ids):
|
||||
order = idx + 1
|
||||
existed = PreferenceCITypeOrder.get_by(uid=current_user.uid, type_id=type_id, is_tree=is_tree,
|
||||
to_dict=False, first=True)
|
||||
if existed is not None:
|
||||
existed.update(order=order, flush=True)
|
||||
else:
|
||||
PreferenceCITypeOrder.create(uid=current_user.uid, type_id=type_id, is_tree=is_tree, order=order,
|
||||
flush=True)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("upsert citype order failed: {}".format(e))
|
||||
return abort(400, ErrFormat.unknown_error)
|
||||
|
@@ -60,6 +60,8 @@ class ErrFormat(CommonErrFormat):
|
||||
only_owner_can_delete = _l("Only the creator can delete it!") # 只有创建人才能删除它!
|
||||
ci_exists_and_cannot_delete_type = _l(
|
||||
"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(
|
||||
|
@@ -5,10 +5,10 @@ from __future__ import unicode_literals
|
||||
|
||||
import copy
|
||||
import imp
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import jinja2
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from jinja2schema import infer
|
||||
@@ -117,6 +117,11 @@ class AttributeValueManager(object):
|
||||
if type_attr and type_attr.is_required and not value and value != 0:
|
||||
return abort(400, ErrFormat.attribute_value_required.format(attr.alias))
|
||||
|
||||
@staticmethod
|
||||
def check_re(expr, value):
|
||||
if not re.compile(expr).match(str(value)):
|
||||
return abort(400, ErrFormat.attribute_value_invalid.format(value))
|
||||
|
||||
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
|
||||
ci = ci or {}
|
||||
v = self._deserialize_value(attr.value_type, value)
|
||||
@@ -130,6 +135,9 @@ class AttributeValueManager(object):
|
||||
if v == "" and attr.value_type not in (ValueTypeEnum.TEXT,):
|
||||
v = None
|
||||
|
||||
if attr.re_check and value:
|
||||
self.check_re(attr.re_check, value)
|
||||
|
||||
return v
|
||||
|
||||
@staticmethod
|
||||
|
@@ -16,7 +16,7 @@ from wtforms import validators
|
||||
from api.extensions import db
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
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.models.common_setting import Employee, Department
|
||||
|
||||
@@ -141,7 +141,7 @@ class EmployeeCRUD(object):
|
||||
def add(**kwargs):
|
||||
try:
|
||||
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
|
||||
except Exception as e:
|
||||
abort(400, str(e))
|
||||
@@ -171,7 +171,7 @@ class EmployeeCRUD(object):
|
||||
if len(e_list) > 0:
|
||||
edit_employee_department_in_acl.apply_async(
|
||||
args=(e_list, new_department_id, current_user.uid),
|
||||
queue=CMDB_QUEUE
|
||||
queue=ACL_QUEUE
|
||||
)
|
||||
|
||||
return existed
|
||||
@@ -577,7 +577,6 @@ class EmployeeCRUD(object):
|
||||
@staticmethod
|
||||
def import_employee(employee_list):
|
||||
res = CreateEmployee().batch_create(employee_list)
|
||||
refresh_employee_acl_info.apply_async(args=(), queue=CMDB_QUEUE)
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
@@ -788,9 +787,11 @@ class CreateEmployee(object):
|
||||
if existed:
|
||||
return existed
|
||||
|
||||
return Employee.create(
|
||||
res = Employee.create(
|
||||
**kwargs
|
||||
)
|
||||
refresh_employee_acl_info.apply_async(args=(res.employee_id,), queue=ACL_QUEUE)
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def get_department_by_name(d_name):
|
||||
@@ -897,3 +898,75 @@ class EmployeeUpdateByUidForm(Form):
|
||||
avatar = StringField(validators=[])
|
||||
sex = 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)
|
||||
|
@@ -389,6 +389,7 @@ class AuditCRUD(object):
|
||||
logout_at=logout_at,
|
||||
ip=request.headers.get('X-Real-IP') or request.remote_addr,
|
||||
browser=request.headers.get('User-Agent'),
|
||||
channel=request.values.get('channel', 'web'),
|
||||
)
|
||||
|
||||
if logout_at is None:
|
||||
|
@@ -356,7 +356,7 @@ class AuditLoginLog(Model2):
|
||||
__tablename__ = "acl_audit_login_logs"
|
||||
|
||||
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))
|
||||
browser = db.Column(db.String(256))
|
||||
description = db.Column(db.String(128))
|
||||
|
@@ -57,6 +57,16 @@ class CIType(Model):
|
||||
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):
|
||||
__tablename__ = "c_ci_type_relations"
|
||||
|
||||
@@ -94,6 +104,8 @@ class Attribute(Model):
|
||||
_choice_web_hook = db.Column('choice_web_hook', db.JSON)
|
||||
choice_other = db.Column(db.JSON)
|
||||
|
||||
re_check = db.Column(db.Text)
|
||||
|
||||
uid = db.Column(db.Integer, index=True)
|
||||
|
||||
option = db.Column(db.JSON)
|
||||
@@ -464,6 +476,15 @@ class PreferenceSearchOption(Model):
|
||||
option = db.Column(db.JSON)
|
||||
|
||||
|
||||
class PreferenceCITypeOrder(Model):
|
||||
__tablename__ = "c_pcto"
|
||||
|
||||
uid = db.Column(db.Integer, index=True, nullable=False)
|
||||
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'))
|
||||
order = db.Column(db.SmallInteger, default=0)
|
||||
is_tree = db.Column(db.Boolean, default=False) # True is tree view, False is resource view
|
||||
|
||||
|
||||
# custom
|
||||
class CustomDashboard(Model):
|
||||
__tablename__ = "c_c_d"
|
||||
|
@@ -3,14 +3,14 @@ from flask import current_app
|
||||
|
||||
from api.extensions import celery
|
||||
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.models.common_setting import Department, Employee
|
||||
from api.lib.decorator import flush_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
|
||||
@reconnect_db
|
||||
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
|
||||
|
||||
|
||||
@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
|
||||
@reconnect_db
|
||||
def refresh_employee_acl_info():
|
||||
def refresh_employee_acl_info(current_employee_id=None):
|
||||
acl = ACLManager('acl')
|
||||
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(
|
||||
Employee.created_at.desc()
|
||||
)
|
||||
current_employee_rid = 0
|
||||
|
||||
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:
|
||||
continue
|
||||
role = role_map.get(em.username, None)
|
||||
@@ -105,6 +109,9 @@ def refresh_employee_acl_info():
|
||||
if not em.acl_rid:
|
||||
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:
|
||||
em.update(**params)
|
||||
current_app.logger.info(
|
||||
@@ -113,3 +120,12 @@ def refresh_employee_acl_info():
|
||||
except Exception as e:
|
||||
current_app.logger.error(str(e))
|
||||
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))
|
||||
|
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2024-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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: zh\n"
|
||||
@@ -234,205 +234,209 @@ msgstr "只有创建人才能删除它!"
|
||||
msgid "The model cannot be deleted because the CI already exists"
|
||||
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 ""
|
||||
"The model cannot be deleted because the model is referenced by the "
|
||||
"relational view {}"
|
||||
msgstr "因为关系视图 {} 引用了该模型,不能删除模型"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:67
|
||||
#: api/lib/cmdb/resp_format.py:69
|
||||
msgid "Model group {} does not exist"
|
||||
msgstr "模型分组 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:68
|
||||
#: api/lib/cmdb/resp_format.py:70
|
||||
msgid "Model group {} already exists"
|
||||
msgstr "模型分组 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:69
|
||||
#: api/lib/cmdb/resp_format.py:71
|
||||
msgid "Model relationship {} does not exist"
|
||||
msgstr "模型关系 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:70
|
||||
#: api/lib/cmdb/resp_format.py:72
|
||||
msgid "Attribute group {} already exists"
|
||||
msgstr "属性分组 {} 已存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:71
|
||||
#: api/lib/cmdb/resp_format.py:73
|
||||
msgid "Attribute group {} does not exist"
|
||||
msgstr "属性分组 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:73
|
||||
#: api/lib/cmdb/resp_format.py:75
|
||||
msgid "Attribute group <{0}> - attribute <{1}> does not exist"
|
||||
msgstr "属性组<{0}> - 属性<{1}> 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:74
|
||||
#: api/lib/cmdb/resp_format.py:76
|
||||
msgid "The unique constraint already exists!"
|
||||
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"
|
||||
msgstr "唯一约束的属性不能是 JSON 和 多值"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:77
|
||||
#: api/lib/cmdb/resp_format.py:79
|
||||
msgid "Duplicated trigger"
|
||||
msgstr "重复的触发器"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:78
|
||||
#: api/lib/cmdb/resp_format.py:80
|
||||
msgid "Trigger {} does not exist"
|
||||
msgstr "触发器 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:80
|
||||
#: api/lib/cmdb/resp_format.py:82
|
||||
msgid "Operation record {} does not exist"
|
||||
msgstr "操作记录 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:81
|
||||
#: api/lib/cmdb/resp_format.py:83
|
||||
msgid "Unique identifier cannot be deleted"
|
||||
msgstr "不能删除唯一标识"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:82
|
||||
#: api/lib/cmdb/resp_format.py:84
|
||||
msgid "Cannot delete default sorted attributes"
|
||||
msgstr "不能删除默认排序的属性"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:84
|
||||
#: api/lib/cmdb/resp_format.py:86
|
||||
msgid "No node selected"
|
||||
msgstr "没有选择节点"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:85
|
||||
#: api/lib/cmdb/resp_format.py:87
|
||||
msgid "This search option does not exist!"
|
||||
msgstr "该搜索选项不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:86
|
||||
#: api/lib/cmdb/resp_format.py:88
|
||||
msgid "This search option has a duplicate name!"
|
||||
msgstr "该搜索选项命名重复!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:88
|
||||
#: api/lib/cmdb/resp_format.py:90
|
||||
msgid "Relationship type {} already exists"
|
||||
msgstr "关系类型 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:89
|
||||
#: api/lib/cmdb/resp_format.py:91
|
||||
msgid "Relationship type {} does not exist"
|
||||
msgstr "关系类型 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:91
|
||||
#: api/lib/cmdb/resp_format.py:93
|
||||
msgid "Invalid attribute value: {}"
|
||||
msgstr "无效的属性值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:92
|
||||
#: api/lib/cmdb/resp_format.py:94
|
||||
msgid "{} Invalid value: {}"
|
||||
msgstr "无效的值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:93
|
||||
#: api/lib/cmdb/resp_format.py:95
|
||||
msgid "{} is not in the predefined values"
|
||||
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"
|
||||
msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:96
|
||||
#: api/lib/cmdb/resp_format.py:98
|
||||
msgid "Attribute {} value must exist"
|
||||
msgstr "属性 {} 值必须存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:99
|
||||
#: api/lib/cmdb/resp_format.py:101
|
||||
msgid "Unknown error when adding or modifying attribute value: {}"
|
||||
msgstr "新增或者修改属性值未知错误: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:101
|
||||
#: api/lib/cmdb/resp_format.py:103
|
||||
msgid "Duplicate custom name"
|
||||
msgstr "订制名重复"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:103
|
||||
#: api/lib/cmdb/resp_format.py:105
|
||||
msgid "Number of models exceeds limit: {}"
|
||||
msgstr "模型数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:104
|
||||
#: api/lib/cmdb/resp_format.py:106
|
||||
msgid "The number of CIs exceeds the limit: {}"
|
||||
msgstr "CI数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:106
|
||||
#: api/lib/cmdb/resp_format.py:108
|
||||
msgid "Auto-discovery rule: {} already exists!"
|
||||
msgstr "自动发现规则: {} 已经存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:107
|
||||
#: api/lib/cmdb/resp_format.py:109
|
||||
msgid "Auto-discovery rule: {} does not exist!"
|
||||
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!"
|
||||
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!"
|
||||
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!"
|
||||
msgstr "您要修改的自动发现: {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:113
|
||||
#: api/lib/cmdb/resp_format.py:115
|
||||
msgid "Attribute does not include unique identifier: {}"
|
||||
msgstr "属性字段没有包括唯一标识: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:114
|
||||
#: api/lib/cmdb/resp_format.py:116
|
||||
msgid "The auto-discovery instance does not exist!"
|
||||
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!"
|
||||
msgstr "模型并未关联该自动发现!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:116
|
||||
#: api/lib/cmdb/resp_format.py:118
|
||||
msgid "Only the creator can modify the 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!"
|
||||
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 {}!"
|
||||
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!"
|
||||
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"
|
||||
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!"
|
||||
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!"
|
||||
msgstr "只有管理员才可以定义执行机器为: 所有节点!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:128
|
||||
#: api/lib/cmdb/resp_format.py:130
|
||||
msgid "Execute targets permission check failed: {}"
|
||||
msgstr "执行机器权限检查不通过: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:130
|
||||
#: api/lib/cmdb/resp_format.py:132
|
||||
msgid "CI filter authorization must be named!"
|
||||
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"
|
||||
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 {}!"
|
||||
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!"
|
||||
msgstr "您没有该CI的操作权限!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:137
|
||||
#: api/lib/cmdb/resp_format.py:139
|
||||
msgid "Failed to save password: {}"
|
||||
msgstr "保存密码失败: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:138
|
||||
#: api/lib/cmdb/resp_format.py:140
|
||||
msgid "Failed to get password: {}"
|
||||
msgstr "获取密码失败: {}"
|
||||
|
||||
|
@@ -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 CITypeAttributeManager
|
||||
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 CITypeTemplateManager
|
||||
from api.lib.cmdb.ci_type import CITypeTriggerManager
|
||||
@@ -43,9 +44,13 @@ class CITypeView(APIView):
|
||||
q = request.args.get("type_name")
|
||||
|
||||
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:
|
||||
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:
|
||||
ci_types = CITypeManager().get_ci_types(q)
|
||||
count = len(ci_types)
|
||||
@@ -53,7 +58,7 @@ class CITypeView(APIView):
|
||||
return self.jsonify(numfound=count, ci_types=ci_types)
|
||||
|
||||
@args_required("name")
|
||||
@args_validate(CITypeManager.cls)
|
||||
@args_validate(CITypeManager.cls, exclude_args=['parent_ids'])
|
||||
def post(self):
|
||||
params = request.values
|
||||
|
||||
@@ -84,6 +89,26 @@ class CITypeView(APIView):
|
||||
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):
|
||||
url_prefix = ("/ci_types/groups",
|
||||
"/ci_types/groups/config",
|
||||
@@ -248,8 +273,8 @@ class CITypeAttributeTransferView(APIView):
|
||||
@args_required('from')
|
||||
@args_required('to')
|
||||
def post(self, type_id):
|
||||
_from = request.values.get('from') # {'attr_id': xx, 'group_id': xx}
|
||||
_to = request.values.get('to') # {'group_id': xx, 'order': xxx}
|
||||
_from = request.values.get('from') # {'attr_id': xx, 'group_id': xx, 'group_name': xx}
|
||||
_to = request.values.get('to') # {'group_id': xx, 'group_name': xx, 'order': xxx}
|
||||
|
||||
CITypeAttributeManager.transfer(type_id, _from, _to)
|
||||
|
||||
@@ -262,8 +287,8 @@ class CITypeAttributeGroupTransferView(APIView):
|
||||
@args_required('from')
|
||||
@args_required('to')
|
||||
def post(self, type_id):
|
||||
_from = request.values.get('from') # group_id
|
||||
_to = request.values.get('to') # group_id
|
||||
_from = request.values.get('from') # group_id or group_name
|
||||
_to = request.values.get('to') # group_id or group_name
|
||||
|
||||
CITypeAttributeGroupManager.transfer(type_id, _from, _to)
|
||||
|
||||
@@ -296,7 +321,7 @@ class CITypeAttributeGroupView(APIView):
|
||||
|
||||
attr_order = list(zip(attrs, orders))
|
||||
group = CITypeAttributeGroupManager.create_or_update(type_id, name, attr_order, order)
|
||||
current_app.logger.warning(group.id)
|
||||
|
||||
return self.jsonify(group_id=group.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))
|
||||
CITypeAttributeGroupManager.update(group_id, name, attr_order, order)
|
||||
|
||||
return self.jsonify(group_id=group_id)
|
||||
|
||||
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||
def delete(self, group_id):
|
||||
CITypeAttributeGroupManager.delete(group_id)
|
||||
|
||||
return self.jsonify(group_id=group_id)
|
||||
|
||||
|
||||
@@ -465,16 +492,16 @@ class CITypeGrantView(APIView):
|
||||
|
||||
acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
|
||||
|
||||
if request.values.get('ci_filter') or request.values.get('attr_filter'):
|
||||
CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values)
|
||||
else:
|
||||
resource = None
|
||||
if 'ci_filter' in request.values or 'attr_filter' in request.values:
|
||||
resource = CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values)
|
||||
|
||||
if not resource:
|
||||
from api.tasks.acl import role_rebuild
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
|
||||
app_id = AppCache.get('cmdb').id
|
||||
current_app.logger.info((rid, app_id))
|
||||
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE)
|
||||
current_app.logger.info('done')
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
@@ -187,3 +188,15 @@ class PreferenceRelationRevokeView(APIView):
|
||||
acl.revoke_resource_from_role_by_rid(name, rid, ResourceTypeEnum.RELATION_VIEW, perms)
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
|
||||
class PreferenceCITypeOrderView(APIView):
|
||||
url_prefix = ("/preference/ci_types/order",)
|
||||
|
||||
def post(self):
|
||||
type_ids = request.values.get("type_ids")
|
||||
is_tree = request.values.get("is_tree") in current_app.config.get('BOOL_TRUE')
|
||||
|
||||
PreferenceManager.upsert_ci_type_order(type_ids, is_tree)
|
||||
|
||||
return self.jsonify(type_ids=type_ids, is_tree=is_tree)
|
||||
|
@@ -8,7 +8,7 @@ elasticsearch==7.17.9
|
||||
email-validator==1.3.1
|
||||
environs==4.2.0
|
||||
flasgger==0.9.5
|
||||
Flask==2.3.2
|
||||
Flask==2.2.5
|
||||
Flask-Bcrypt==1.0.1
|
||||
flask-babel==4.0.0
|
||||
Flask-Caching==2.0.2
|
||||
@@ -46,7 +46,7 @@ supervisor==4.0.3
|
||||
timeout-decorator==0.5.0
|
||||
toposort==1.10
|
||||
treelib==1.6.1
|
||||
Werkzeug>=2.3.6
|
||||
Werkzeug==2.2.3
|
||||
WTForms==3.0.0
|
||||
shamir~=17.12.0
|
||||
pycryptodomex>=3.19.0
|
||||
|
@@ -13,7 +13,7 @@ const getAntdSerials = (color) => {
|
||||
|
||||
const themePluginOption = {
|
||||
fileName: 'css/theme-colors-[contenthash:8].css',
|
||||
matchColors: getAntdSerials('#1890ff'), // 主色系列
|
||||
matchColors: getAntdSerials('#2f54eb'), // 主色系列
|
||||
// 改变样式选择器,解决样式覆盖问题
|
||||
changeSelector (selector) {
|
||||
switch (selector) {
|
||||
|
@@ -1,45 +1,45 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-row class="row" type="flex" justify="end">
|
||||
<a-col>
|
||||
<a-space align="end">
|
||||
<a-button
|
||||
class="left-button"
|
||||
size="small"
|
||||
:disabled="prevIsDisabled"
|
||||
@click="prevPage"
|
||||
><a-icon
|
||||
type="left"
|
||||
/></a-button>
|
||||
<a-button class="page-button" size="small">{{ currentPage }}</a-button>
|
||||
<a-button
|
||||
class="right-button"
|
||||
size="small"
|
||||
:disabled="nextIsDisabled"
|
||||
@click="nextPage"
|
||||
><a-icon
|
||||
type="right"
|
||||
/></a-button>
|
||||
<a-dropdown
|
||||
class="dropdown"
|
||||
size="small"
|
||||
placement="topCenter"
|
||||
:trigger="['click']"
|
||||
:disabled="dropdownIsDisabled"
|
||||
>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item v-for="(size, index) in pageSizes" :key="index" @click="handleItemClick(size)">
|
||||
{{ `${size}${$t('itemsPerPage')}` }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
<a-button size="small">{{ `${pageSize}${$t('itemsPerPage')}` }}<a-icon type="down" /> </a-button>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
<a-row class="row" type="flex" justify="end">
|
||||
<a-col>
|
||||
<a-space align="end">
|
||||
<a-button
|
||||
class="left-button"
|
||||
size="small"
|
||||
:disabled="prevIsDisabled"
|
||||
@click="prevPage"
|
||||
><a-icon
|
||||
type="left"
|
||||
/></a-button>
|
||||
<a-button class="page-button" size="small">{{ currentPage }}</a-button>
|
||||
<a-button
|
||||
class="right-button"
|
||||
size="small"
|
||||
:disabled="nextIsDisabled"
|
||||
@click="nextPage"
|
||||
><a-icon
|
||||
type="right"
|
||||
/></a-button>
|
||||
<a-dropdown
|
||||
class="dropdown"
|
||||
size="small"
|
||||
placement="topCenter"
|
||||
:trigger="['click']"
|
||||
:disabled="dropdownIsDisabled"
|
||||
>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item v-for="(size, index) in pageSizes" :key="index" @click="handleItemClick(size)">
|
||||
{{ `${size}${$t('itemsPerPage')}` }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
<a-button size="small">{{ `${pageSize}${$t('itemsPerPage')}` }}<a-icon type="down" /> </a-button>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Pager',
|
||||
@@ -117,7 +117,7 @@
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row {
|
||||
margin-top: 5px;
|
||||
@@ -135,4 +135,3 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
19
cmdb-ui/src/components/RegexSelect/constants.js
Normal file
19
cmdb-ui/src/components/RegexSelect/constants.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/* eslint-disable no-useless-escape */
|
||||
|
||||
import i18n from '@/lang'
|
||||
export const regList = () => {
|
||||
return [
|
||||
{ id: 'letter', label: i18n.t('regexSelect.letter'), value: '^[A-Za-z]+$', message: '请输入字母' },
|
||||
{ id: 'number', label: i18n.t('regexSelect.number'), value: '^-?(?!0\\d+)\\d+(\\.\\d+)?$', message: '请输入数字' },
|
||||
{ id: 'letterAndNumber', label: i18n.t('regexSelect.letterAndNumber'), value: '^[A-Za-z0-9.]+$', message: '请输入字母和数字' },
|
||||
{ id: 'phone', label: i18n.t('regexSelect.phone'), value: '^1[3-9]\\d{9}$', message: '请输入正确手机号码' },
|
||||
{ id: 'landline', label: i18n.t('regexSelect.landline'), value: '^(?:(?:\\d{3}-)?\\d{8}|^(?:\\d{4}-)?\\d{7,8})(?:-\\d+)?$', message: '请输入正确座机' },
|
||||
{ id: 'zipCode', label: i18n.t('regexSelect.zipCode'), value: '^(0[1-7]|1[0-356]|2[0-7]|3[0-6]|4[0-7]|5[1-7]|6[1-7]|7[0-5]|8[013-6])\\d{4}$', message: '请输入正确邮政编码' },
|
||||
{ id: 'IDCard', label: i18n.t('regexSelect.IDCard'), value: '(^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}$)', message: '请输入正确身份证号' },
|
||||
{ id: 'ip', label: i18n.t('regexSelect.ip'), value: '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$', message: '请输入正确IP地址' },
|
||||
{ id: 'email', label: i18n.t('regexSelect.email'), value: '^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\.\\w+([-.]\\w+)*$', message: '请输入正确邮箱' },
|
||||
{ id: 'link', label: i18n.t('regexSelect.link'), value: '^(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z0-9]{2,}(\.[a-zA-Z0-9]{2,})(\.[a-zA-Z0-9]{2,})?$', message: '请输入链接' },
|
||||
{ id: 'monetaryAmount', label: i18n.t('regexSelect.monetaryAmount'), value: '^-?\\d+(,\\d{3})*(\.\\d{1,2})?$', message: '请输入货币金额' },
|
||||
{ id: 'custom', label: i18n.t('regexSelect.custom'), value: '', message: '' }
|
||||
]
|
||||
}
|
2
cmdb-ui/src/components/RegexSelect/index.js
Normal file
2
cmdb-ui/src/components/RegexSelect/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import RegexSelect from './regexSelect.vue'
|
||||
export default RegexSelect
|
208
cmdb-ui/src/components/RegexSelect/regexSelect.vue
Normal file
208
cmdb-ui/src/components/RegexSelect/regexSelect.vue
Normal file
@@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<a-popover
|
||||
trigger="click"
|
||||
placement="bottom"
|
||||
ref="regexSelect"
|
||||
overlayClassName="regex-select-wrapper"
|
||||
:overlayStyle="{ '--overlay-width': `${width}px` }"
|
||||
@visibleChange="visibleChange"
|
||||
>
|
||||
<div class="regex-select" slot="content">
|
||||
<div class="regex-select-left">
|
||||
<div class="regex-select-left-header">{{ $t('regexSelect.limitedFormat') }}</div>
|
||||
<div
|
||||
@click="
|
||||
() => {
|
||||
current = reg
|
||||
testInput = ''
|
||||
showMessage = false
|
||||
}
|
||||
"
|
||||
:class="{
|
||||
'regex-select-left-reg': true,
|
||||
'regex-select-left-reg-selected': current && current.label === reg.label,
|
||||
}"
|
||||
v-for="(reg, index) in regList"
|
||||
:key="reg.label"
|
||||
>
|
||||
<a-divider :style="{ margin: '2px -12px', width: 'calc(100% + 24px)' }" v-if="index === regList.length - 1" />
|
||||
{{ reg.label }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="regex-select-right">
|
||||
<template v-if="current">
|
||||
<div class="regex-select-right-header">{{ $t('regexSelect.regExp') }}</div>
|
||||
<div
|
||||
v-if="current.label !== $t('regexSelect.custom')"
|
||||
:style="{ color: '#000', fontSize: '12px', margin: '12px 0' }"
|
||||
>
|
||||
{{ current.value }}
|
||||
</div>
|
||||
<a-input
|
||||
:style="{ margin: '12px 0' }"
|
||||
size="small"
|
||||
v-else
|
||||
v-model="current.value"
|
||||
@change="
|
||||
() => {
|
||||
this.$emit('change', current)
|
||||
}
|
||||
"
|
||||
/>
|
||||
<template v-if="isShowErrorMsg">
|
||||
<div class="regex-select-right-header">{{ $t('regexSelect.errMsg') }}</div>
|
||||
<a-input :style="{ margin: '12px 0' }" size="small" v-model="current.message" />
|
||||
</template>
|
||||
<div class="regex-select-right-header">{{ $t('regexSelect.test') }}</div>
|
||||
<a-input v-model="testInput" :style="{ margin: '12px 0 4px' }" size="small" @change="validate" />
|
||||
<span :style="{ color: 'red', fontSize: '12px' }" v-if="showMessage">{{
|
||||
locale === 'zh' ? current.message || '错误' : $t('regexSelect.error')
|
||||
}}</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<a-input ref="regInput" :placeholder="$t('regexSelect.placeholder')" :value="current.label" @change="changeLabel">
|
||||
</a-input>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { regList } from './constants'
|
||||
export default {
|
||||
name: 'RegexSelect',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
isShowErrorMsg: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
limitedFormat: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showMessage: false,
|
||||
width: 370,
|
||||
testInput: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['locale']),
|
||||
regList() {
|
||||
if (this.limitedFormat.length) {
|
||||
return regList().filter((item) => this.limitedFormat.includes(item.id))
|
||||
}
|
||||
return regList()
|
||||
},
|
||||
current: {
|
||||
get() {
|
||||
if (this.value?.value && !this.value?.label) {
|
||||
const _find = this.regList.find((reg) => reg.value === this.value?.value)
|
||||
return { ...this.value, label: _find?.label ?? this.$t('regexSelect.custom') }
|
||||
}
|
||||
return this.value ?? {}
|
||||
},
|
||||
set(val) {
|
||||
this.showMessage = false
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
const regInput = this.$refs.regInput.$refs.input
|
||||
this.width = regInput.offsetWidth || 370
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
validate(e) {
|
||||
const reg = RegExp(this.current.value, 'g')
|
||||
this.showMessage = !reg.test(e.target.value)
|
||||
},
|
||||
changeLabel(e) {
|
||||
this.current = {}
|
||||
},
|
||||
visibleChange(visible) {
|
||||
if (visible) {
|
||||
this.$nextTick(() => {
|
||||
this.testInput = ''
|
||||
this.showMessage = false
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.regex-select {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
display: flex;
|
||||
.regex-select-left {
|
||||
width: 40%;
|
||||
height: 100%;
|
||||
border: 1px solid #cacdd9;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
&-reg {
|
||||
padding-left: 2px;
|
||||
cursor: pointer;
|
||||
&-selected,
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
}
|
||||
}
|
||||
}
|
||||
&-right {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
border: 1px solid #cacdd9;
|
||||
border-radius: 4px;
|
||||
margin-left: 8px;
|
||||
padding: 12px;
|
||||
}
|
||||
&-left,
|
||||
&-right {
|
||||
&-header {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #000000;
|
||||
border-left: 2px solid #custom_colors[color_1];
|
||||
padding-left: 6px;
|
||||
margin-left: -6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.regex-select-wrapper {
|
||||
.ant-popover-arrow {
|
||||
display: none;
|
||||
}
|
||||
.ant-popover-inner-content {
|
||||
padding: 0;
|
||||
min-width: 370px;
|
||||
width: var(--overlay-width);
|
||||
}
|
||||
}
|
||||
.regex-select-wrapper.ant-popover-placement-bottom .ant-popover-content {
|
||||
margin-top: -8px;
|
||||
}
|
||||
.regex-select-wrapper.ant-popover-placement-top .ant-popover-content {
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
</style>
|
@@ -119,7 +119,8 @@ export default {
|
||||
border-radius: 4px;
|
||||
color: @layout-header-font-color;
|
||||
height: @layout-header-height;
|
||||
line-height: @layout-header-height;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
&:hover {
|
||||
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
|
||||
color: @layout-header-font-selected-color;
|
||||
|
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
primaryColor: '#1890ff', // primary color of ant design
|
||||
primaryColor: '#2f54eb', // primary color of ant design
|
||||
navTheme: 'dark', // theme for nav menu
|
||||
layout: 'sidemenu', // nav menu position: sidemenu or topmenu
|
||||
contentWidth: 'Fixed', // layout of content: Fluid or Fixed, only works when layout is topmenu
|
||||
|
@@ -145,6 +145,26 @@ export default {
|
||||
sizeLimit: 'The image size cannot exceed 2MB!',
|
||||
nodata: 'There are currently no custom icons available. Click here to upload'
|
||||
},
|
||||
regexSelect: {
|
||||
limitedFormat: 'Limited Format',
|
||||
regExp: 'RegExp',
|
||||
errMsg: 'Error Message',
|
||||
test: 'Test',
|
||||
placeholder: 'Please Select RegExp',
|
||||
error: 'Error',
|
||||
letter: 'letter',
|
||||
number: 'number',
|
||||
letterAndNumber: 'letter&number',
|
||||
phone: 'phone',
|
||||
landline: 'landline',
|
||||
zipCode: 'zip code',
|
||||
IDCard: 'ID card',
|
||||
ip: 'IP',
|
||||
email: 'email',
|
||||
link: 'link',
|
||||
monetaryAmount: 'monetary amount',
|
||||
custom: 'custom',
|
||||
},
|
||||
cmdb: cmdb_en,
|
||||
cs: cs_en,
|
||||
acl: acl_en,
|
||||
|
@@ -145,6 +145,26 @@ export default {
|
||||
sizeLimit: '图片大小不可超过2MB!',
|
||||
nodata: '暂无自定义图标,点击此处上传'
|
||||
},
|
||||
regexSelect: {
|
||||
limitedFormat: '限定格式',
|
||||
regExp: '正则表达式',
|
||||
errMsg: '错误时提示',
|
||||
test: '测试',
|
||||
placeholder: '请选择正则表达式',
|
||||
error: '错误',
|
||||
letter: '字母',
|
||||
number: '数字',
|
||||
letterAndNumber: '字母和数字',
|
||||
phone: '手机号码',
|
||||
landline: '座机',
|
||||
zipCode: '邮政编码',
|
||||
IDCard: '身份证号',
|
||||
ip: 'IP地址',
|
||||
email: '邮箱',
|
||||
link: '链接',
|
||||
monetaryAmount: '货币金额',
|
||||
custom: '自定义',
|
||||
},
|
||||
cmdb: cmdb_zh,
|
||||
cs: cs_zh,
|
||||
acl: acl_zh,
|
||||
|
@@ -57,7 +57,7 @@
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="operate" :title="$t('batchOperate')">
|
||||
<vxe-column field="operate" :title="$t('acl.batchOperate')">
|
||||
<template #default="{row}">
|
||||
<a-button size="small" type="danger" @click="handleClearAll(row)">
|
||||
{{ $t('clear') }}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<template>
|
||||
<template>
|
||||
<CustomDrawer
|
||||
width="800px"
|
||||
placement="left"
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<template>
|
||||
<template>
|
||||
<CustomDrawer
|
||||
@close="handleClose"
|
||||
width="500"
|
||||
|
@@ -57,17 +57,20 @@
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</ops-table>
|
||||
<vxe-pager
|
||||
<a-pagination
|
||||
size="small"
|
||||
:layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']"
|
||||
:current-page.sync="tablePage.currentPage"
|
||||
:page-size.sync="tablePage.pageSize"
|
||||
show-size-changer
|
||||
show-quick-jumper
|
||||
:current="tablePage.currentPage"
|
||||
:total="tablePage.total"
|
||||
:page-sizes="pageSizeOptions"
|
||||
@page-change="handlePageChange"
|
||||
:style="{ marginTop: '10px' }"
|
||||
>
|
||||
</vxe-pager>
|
||||
:show-total="(total, range) => `当前展示 ${range[0]}-${range[1]} 条数据, 共 ${total} 条`"
|
||||
:page-size="tablePage.pageSize"
|
||||
:default-current="1"
|
||||
:page-size-options="pageSizeOptions"
|
||||
@change="pageOrSizeChange"
|
||||
@showSizeChange="pageOrSizeChange"
|
||||
:style="{ marginTop: '10px', textAlign: 'right' }"
|
||||
/>
|
||||
</a-spin>
|
||||
|
||||
<resourceTypeForm ref="resourceTypeForm" :handleOk="handleOk"> </resourceTypeForm>
|
||||
@@ -89,7 +92,7 @@ export default {
|
||||
loading: false,
|
||||
groups: [],
|
||||
id2perms: {},
|
||||
pageSizeOptions: [10, 25, 50, 100],
|
||||
pageSizeOptions: ['20', '50', '100', '200'],
|
||||
tablePage: {
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
@@ -176,7 +179,7 @@ export default {
|
||||
this.handleOk()
|
||||
})
|
||||
},
|
||||
handlePageChange({ currentPage, pageSize }) {
|
||||
pageOrSizeChange(currentPage, pageSize) {
|
||||
this.tablePage.currentPage = currentPage
|
||||
this.tablePage.pageSize = pageSize
|
||||
this.searchData()
|
||||
|
@@ -77,7 +77,7 @@
|
||||
<!-- 2 -->
|
||||
|
||||
<vxe-table-column field="name" :title="$t('acl.resourceName')" :min-widh="150" fixed="left" show-overflow>
|
||||
<template #title="{row}">
|
||||
<template #title="{ row }">
|
||||
{{ row.isGroup ? $t('acl.groupName') : $t('acl.resourceName') }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
@@ -86,10 +86,12 @@
|
||||
<vxe-table-column field="user" :title="$t('acl.creator')" :min-widh="100"> </vxe-table-column>
|
||||
|
||||
<!-- 4 -->
|
||||
<vxe-table-column field="created_at" :title="$t('created_at')" :min-widh="220" align="center"> </vxe-table-column>
|
||||
<vxe-table-column field="created_at" :title="$t('created_at')" :min-widh="220" align="center">
|
||||
</vxe-table-column>
|
||||
|
||||
<!-- 5 -->
|
||||
<vxe-table-column field="updated_at" :title="$t('updated_at')" :min-widh="220" fixed="center"> </vxe-table-column>
|
||||
<vxe-table-column field="updated_at" :title="$t('updated_at')" :min-widh="220" fixed="center">
|
||||
</vxe-table-column>
|
||||
|
||||
<!-- 6 -->
|
||||
|
||||
@@ -99,8 +101,9 @@
|
||||
:min-widh="200"
|
||||
fixed="right"
|
||||
align="center"
|
||||
show-overflow>
|
||||
<template #default="{row}">
|
||||
show-overflow
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span v-show="isGroup">
|
||||
<a @click="handleDisplayMember(row)">{{ $t('acl.member') }}</a>
|
||||
<a-divider type="vertical" />
|
||||
@@ -117,27 +120,36 @@
|
||||
</a>
|
||||
</a-tooltip>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm :title="$t('confirmDelete')" @confirm="handleDelete(row)" @cancel="cancel" :okText="$t('yes')" :cancelText="$t('no')">
|
||||
<a-popconfirm
|
||||
:title="$t('confirmDelete')"
|
||||
@confirm="handleDelete(row)"
|
||||
@cancel="cancel"
|
||||
:okText="$t('yes')"
|
||||
:cancelText="$t('no')"
|
||||
>
|
||||
<a style="color: red"><a-icon type="delete"/></a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</ops-table>
|
||||
<vxe-pager
|
||||
<a-pagination
|
||||
size="small"
|
||||
:layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']"
|
||||
:current-page.sync="tablePage.currentPage"
|
||||
:page-size.sync="tablePage.pageSize"
|
||||
show-size-changer
|
||||
show-quick-jumper
|
||||
:current="tablePage.currentPage"
|
||||
:total="tablePage.total"
|
||||
:page-sizes="pageSizeOptions"
|
||||
@page-change="handlePageChange"
|
||||
:style="{ marginTop: '10px' }"
|
||||
>
|
||||
</vxe-pager>
|
||||
:show-total="(total, range) => `当前展示 ${range[0]}-${range[1]} 条数据, 共 ${total} 条`"
|
||||
:page-size="tablePage.pageSize"
|
||||
:default-current="1"
|
||||
:page-size-options="pageSizeOptions"
|
||||
@change="pageOrSizeChange"
|
||||
@showSizeChange="pageOrSizeChange"
|
||||
:style="{ marginTop: '10px', textAlign: 'right' }"
|
||||
/>
|
||||
</a-spin>
|
||||
</div>
|
||||
<div v-else style="text-align: center;margin-top:20%">
|
||||
<a-icon style="font-size:50px; margin-bottom: 20px; color: orange" type="info-circle" />
|
||||
<div v-else style="text-align: center; margin-top: 20%">
|
||||
<a-icon style="font-size: 50px; margin-bottom: 20px; color: orange" type="info-circle" />
|
||||
<h3>{{ $t('acl.addTypeTips') }}</h3>
|
||||
</div>
|
||||
<resourceForm ref="resourceForm" @fresh="handleOk"> </resourceForm>
|
||||
@@ -191,7 +203,7 @@ export default {
|
||||
isGroup: false,
|
||||
allResourceTypes: [],
|
||||
currentType: { id: 0 },
|
||||
pageSizeOptions: [10, 25, 50, 100],
|
||||
pageSizeOptions: ['20', '50', '100', '200'],
|
||||
searchName: '',
|
||||
selectedRows: [],
|
||||
}
|
||||
@@ -291,7 +303,7 @@ export default {
|
||||
}
|
||||
},
|
||||
cancel() {},
|
||||
handlePageChange({ currentPage, pageSize }) {
|
||||
pageOrSizeChange(currentPage, pageSize) {
|
||||
this.tablePage.currentPage = currentPage
|
||||
this.tablePage.pageSize = pageSize
|
||||
this.searchData()
|
||||
@@ -302,7 +314,6 @@ export default {
|
||||
.getVxetableRef()
|
||||
.getCheckboxRecords()
|
||||
.concat(this.$refs.xTable.getVxetableRef().getCheckboxReserveRecords())
|
||||
console.log(this.selectedRows)
|
||||
},
|
||||
onSelectRangeEnd({ records }) {
|
||||
this.selectedRows = records
|
||||
|
@@ -42,13 +42,13 @@
|
||||
|
||||
<!-- 2 -->
|
||||
<vxe-table-column field="is_app_admin" :title="$t('admin')" :min-width="100" align="center">
|
||||
<template #default="{row}">
|
||||
<template #default="{ row }">
|
||||
<a-icon type="check" v-if="row.is_app_admin" />
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
|
||||
<vxe-table-column field="id" :title="$t('acl.inheritedFrom')" :min-width="150">
|
||||
<template #default="{row}">
|
||||
<template #default="{ row }">
|
||||
<a-tag color="cyan" v-for="role in id2parents[row.id]" :key="role.id">{{ role.name }}</a-tag>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
@@ -69,13 +69,13 @@
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #default="{row}">
|
||||
<template #default="{ row }">
|
||||
{{ row.uid ? $t('no') : $t('yes') }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
|
||||
<vxe-table-column field="action" :title="$t('operation')" :width="120" fixed="right">
|
||||
<template #default="{row}">
|
||||
<template #default="{ row }">
|
||||
<a-space>
|
||||
<a-tooltip :title="$t('acl.resourceList')">
|
||||
<a
|
||||
@@ -104,17 +104,20 @@
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</ops-table>
|
||||
<vxe-pager
|
||||
<a-pagination
|
||||
size="small"
|
||||
:layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']"
|
||||
:current-page.sync="tablePage.currentPage"
|
||||
:page-size.sync="tablePage.pageSize"
|
||||
show-size-changer
|
||||
show-quick-jumper
|
||||
:current="tablePage.currentPage"
|
||||
:total="tablePage.total"
|
||||
:page-sizes="pageSizeOptions"
|
||||
@page-change="handlePageChange"
|
||||
:style="{ marginTop: '10px' }"
|
||||
>
|
||||
</vxe-pager>
|
||||
:show-total="(total, range) => `当前展示 ${range[0]}-${range[1]} 条数据, 共 ${total} 条`"
|
||||
:page-size="tablePage.pageSize"
|
||||
:default-current="1"
|
||||
:page-size-options="pageSizeOptions"
|
||||
@change="pageOrSizeChange"
|
||||
@showSizeChange="pageOrSizeChange"
|
||||
:style="{ marginTop: '10px', textAlign: 'right' }"
|
||||
/>
|
||||
</a-spin>
|
||||
|
||||
<roleForm ref="roleForm" :allRoles="allRoles" :id2parents="id2parents" :handleOk="handleOk"></roleForm>
|
||||
@@ -149,7 +152,7 @@ export default {
|
||||
tableData: [],
|
||||
allRoles: [],
|
||||
id2parents: {},
|
||||
pageSizeOptions: [10, 25, 50, 100],
|
||||
pageSizeOptions: ['20', '50', '100', '200'],
|
||||
searchName: '',
|
||||
filterTableValue: { user_role: 1, user_only: 0 },
|
||||
}
|
||||
@@ -254,7 +257,7 @@ export default {
|
||||
cancel(e) {
|
||||
return false
|
||||
},
|
||||
handlePageChange({ currentPage, pageSize }) {
|
||||
pageOrSizeChange(currentPage, pageSize) {
|
||||
this.tablePage.currentPage = currentPage
|
||||
this.tablePage.pageSize = pageSize
|
||||
this.loadData()
|
||||
|
@@ -205,3 +205,21 @@ export function ciTypeFilterPermissions(type_id) {
|
||||
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
|
||||
})
|
||||
}
|
||||
|
@@ -114,3 +114,12 @@ export function revokeRelationView(rid, data) {
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// preference citype order
|
||||
export function preferenceCitypeOrder(data) {
|
||||
return axios({
|
||||
url: `/v0.1/preference/ci_types/order`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
@@ -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>
|
@@ -0,0 +1,2 @@
|
||||
import CMDBTypeSelect from './cmdbTypeSelect.vue'
|
||||
export default CMDBTypeSelect
|
@@ -145,7 +145,7 @@ export default {
|
||||
selectedRowKeys: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -179,7 +179,12 @@ export default {
|
||||
this.fuzzySearch = ''
|
||||
},
|
||||
},
|
||||
inject: ['setPreferenceSearchCurrent'],
|
||||
inject: {
|
||||
setPreferenceSearchCurrent: {
|
||||
from: 'setPreferenceSearchCurrent',
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.type === 'resourceSearch') {
|
||||
this.getCITypeGroups()
|
||||
@@ -234,7 +239,9 @@ export default {
|
||||
}
|
||||
},
|
||||
emitRefresh() {
|
||||
this.setPreferenceSearchCurrent(null)
|
||||
if (this.setPreferenceSearchCurrent) {
|
||||
this.setPreferenceSearchCurrent(null)
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$emit('refresh', true)
|
||||
})
|
||||
|
@@ -88,7 +88,9 @@ export default {
|
||||
} catch {}
|
||||
const headers = {}
|
||||
this.$refs.Header.headers.forEach((item) => {
|
||||
headers[item.key] = item.value
|
||||
if (item.key) {
|
||||
headers[item.key] = item.value
|
||||
}
|
||||
})
|
||||
let authorization = {}
|
||||
const type = this.$refs.Authorization.authorizationType
|
||||
|
@@ -19,6 +19,7 @@ const cmdb_en = {
|
||||
operationHistory: 'Operation Audit',
|
||||
relationType: 'Relation Type',
|
||||
ad: 'AutoDiscovery',
|
||||
cidetail: 'CI Detail'
|
||||
},
|
||||
ciType: {
|
||||
ciType: 'CIType',
|
||||
@@ -174,7 +175,13 @@ const cmdb_en = {
|
||||
date: 'Date',
|
||||
time: 'Time',
|
||||
json: 'JSON',
|
||||
event: 'Event'
|
||||
event: 'Event',
|
||||
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: {
|
||||
unselectAttributes: 'Unselected',
|
||||
@@ -366,12 +373,12 @@ const cmdb_en = {
|
||||
ad: {
|
||||
upload: 'Import',
|
||||
download: 'Export',
|
||||
accpet: 'Accept',
|
||||
accpetBy: 'Accept By',
|
||||
accept: 'Accept',
|
||||
acceptBy: 'Accept By',
|
||||
acceptTime: 'Accept Time',
|
||||
confirmAccept: 'Confirm Accept?',
|
||||
accpetSuccess: 'Accept successfully',
|
||||
isAccpet: 'Is accept',
|
||||
acceptSuccess: 'Accept successfully',
|
||||
isAccept: 'Is accept',
|
||||
deleteADC: 'Confirm to delete this data?',
|
||||
batchDelete: 'Confirm to delete this data?',
|
||||
agent: 'Built-in & Plug-ins',
|
||||
@@ -466,6 +473,8 @@ const cmdb_en = {
|
||||
tips10: 'Other attributes of the CIType are computed using expressions\n\nA code snippet computes the returned value.',
|
||||
newUpdateField: 'Add a Attribute',
|
||||
attributeSettings: 'Attribute Settings',
|
||||
share: 'Share',
|
||||
noPermission: 'No Permission'
|
||||
},
|
||||
serviceTree: {
|
||||
deleteNode: 'Delete Node',
|
||||
|
@@ -19,6 +19,7 @@ const cmdb_zh = {
|
||||
operationHistory: '操作审计',
|
||||
relationType: '关系类型',
|
||||
ad: '自动发现',
|
||||
cidetail: 'CI 详情'
|
||||
},
|
||||
ciType: {
|
||||
ciType: '模型',
|
||||
@@ -174,7 +175,13 @@ const cmdb_zh = {
|
||||
date: '日期',
|
||||
time: '时间',
|
||||
json: 'JSON',
|
||||
event: '事件'
|
||||
event: '事件',
|
||||
reg: '正则校验',
|
||||
isInherit: '是否继承',
|
||||
inheritType: '继承模型',
|
||||
inheritTypePlaceholder: '请选择继承模型(多选)',
|
||||
inheritFrom: '属性继承自{name}',
|
||||
groupInheritFrom: '请至{name}进行修改'
|
||||
},
|
||||
components: {
|
||||
unselectAttributes: '未选属性',
|
||||
@@ -366,12 +373,12 @@ const cmdb_zh = {
|
||||
ad: {
|
||||
upload: '规则导入',
|
||||
download: '规则导出',
|
||||
accpet: '入库',
|
||||
accpetBy: '入库人',
|
||||
accept: '入库',
|
||||
acceptBy: '入库人',
|
||||
acceptTime: '入库时间',
|
||||
confirmAccept: '确认入库?',
|
||||
accpetSuccess: '入库成功',
|
||||
isAccpet: '是否入库',
|
||||
acceptSuccess: '入库成功',
|
||||
isAccept: '是否入库',
|
||||
deleteADC: '确认删除该条数据?',
|
||||
batchDelete: '确认删除这些数据?',
|
||||
agent: '内置 & 插件',
|
||||
@@ -465,6 +472,8 @@ const cmdb_zh = {
|
||||
tips10: '模型的其他属性通过表达式的方式计算出来\n\n一个代码片段计算返回的值',
|
||||
newUpdateField: '新增修改字段',
|
||||
attributeSettings: '字段设置',
|
||||
share: '分享',
|
||||
noPermission: '暂无权限'
|
||||
},
|
||||
serviceTree: {
|
||||
deleteNode: '删除节点',
|
||||
|
@@ -56,6 +56,13 @@ const genCmdbRoutes = async () => {
|
||||
meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc-selected', keepAlive: false },
|
||||
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',
|
||||
name: 'cmdb_disabled2',
|
||||
@@ -138,7 +145,7 @@ const genCmdbRoutes = async () => {
|
||||
const [preference, relation] = await Promise.all([getPreference(), getRelationView()])
|
||||
|
||||
preference.forEach(item => {
|
||||
routes.children[2].children.unshift({
|
||||
routes.children[2].children.push({
|
||||
path: `/cmdb/instances/types/${item.id}`,
|
||||
component: () => import(`../views/ci/index`),
|
||||
name: `cmdb_${item.id}`,
|
||||
|
@@ -178,4 +178,14 @@ export const downloadExcel = (data, fileName = `${moment().format('YYYY-MM-DD HH
|
||||
// ws['!rows'] = [{ 'hpt': 80 }]
|
||||
// STEP 4: Write Excel file to browser #导出
|
||||
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)}`
|
||||
}
|
71
cmdb-ui/src/modules/cmdb/views/ci/ciDetailPage.vue
Normal file
71
cmdb-ui/src/modules/cmdb/views/ci/ciDetailPage.vue
Normal 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>
|
@@ -57,7 +57,7 @@
|
||||
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span>
|
||||
</div>
|
||||
</SearchForm>
|
||||
<CiDetail ref="detail" :typeId="typeId" />
|
||||
<CiDetailDrawer ref="detail" :typeId="typeId" />
|
||||
<ops-table
|
||||
:id="`cmdb-ci-${typeId}`"
|
||||
border
|
||||
@@ -101,7 +101,6 @@
|
||||
:cell-type="col.value_type === '2' ? 'string' : 'auto'"
|
||||
:fixed="col.is_fixed ? 'left' : ''"
|
||||
>
|
||||
<!-- <template #edit="{row}"><a-input v-model="row[col.field]"></a-input></template> -->
|
||||
<template #header>
|
||||
<span class="vxe-handle">
|
||||
<OpsMoveIcon
|
||||
@@ -110,7 +109,7 @@
|
||||
<span>{{ col.title }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="col.is_choice || col.is_password || col.is_list" #edit="{ row }">
|
||||
<template v-if="col.is_choice || col.is_password" #edit="{ row }">
|
||||
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
|
||||
<a-select
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
@@ -147,18 +146,6 @@
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
:style="{ width: '100%', height: '32px' }"
|
||||
v-model="row[col.field]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
v-else-if="col.is_list"
|
||||
:showArrow="false"
|
||||
mode="tags"
|
||||
class="ci-table-edit-select"
|
||||
allowClear
|
||||
>
|
||||
</a-select>
|
||||
</template>
|
||||
<template
|
||||
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
|
||||
@@ -293,8 +280,6 @@
|
||||
</a-pagination>
|
||||
</div>
|
||||
<create-instance-form ref="create" @reload="reloadData" @submit="batchUpdate" />
|
||||
<!-- <EditAttrsDrawer ref="editAttrsDrawer" @refresh="refreshAfterEditAttrs" /> -->
|
||||
<!-- <batch-update-relation :typeId="typeId" ref="batchUpdateRelation" @submit="batchUpdateRelation" /> -->
|
||||
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
||||
<BatchDownload ref="batchDownload" @batchDownload="batchDownload" />
|
||||
<MetadataDrawer ref="metadataDrawer" />
|
||||
@@ -312,7 +297,7 @@ import router, { resetRouter } from '@/router'
|
||||
|
||||
import SearchForm from '../../components/searchForm/SearchForm.vue'
|
||||
import CreateInstanceForm from './modules/CreateInstanceForm'
|
||||
import CiDetail from './modules/CiDetail'
|
||||
import CiDetailDrawer from './modules/ciDetailDrawer.vue'
|
||||
import EditAttrsPopover from './modules/editAttrsPopover'
|
||||
import JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
|
||||
import { searchCI, updateCI, deleteCI } from '@/modules/cmdb/api/ci'
|
||||
@@ -335,7 +320,7 @@ export default {
|
||||
components: {
|
||||
SearchForm,
|
||||
CreateInstanceForm,
|
||||
CiDetail,
|
||||
CiDetailDrawer,
|
||||
JsonEditor,
|
||||
PasswordField,
|
||||
EditAttrsPopover,
|
||||
@@ -471,11 +456,6 @@ export default {
|
||||
const regSort = /(?<=sort=).+/g
|
||||
|
||||
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
|
||||
// if (exp) {
|
||||
// exp = exp.replace(/(\:)/g, '$1*')
|
||||
// exp = exp.replace(/(\,)/g, '*$1')
|
||||
// }
|
||||
// If the sorting is done by clicking on the table, the table will prevail.
|
||||
let sort
|
||||
if (sortByTable) {
|
||||
sort = sortByTable
|
||||
@@ -484,7 +464,6 @@ export default {
|
||||
}
|
||||
const res = await searchCI({
|
||||
q: `_type:${this.typeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`,
|
||||
// q: `${this.mergeQ(queryParams)}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`,
|
||||
count: this.pageSize,
|
||||
page: this.currentPage,
|
||||
sort,
|
||||
@@ -533,55 +512,17 @@ export default {
|
||||
this.$refs['xTable'].getVxetableRef().setCheckboxRow(rows, true)
|
||||
}
|
||||
},
|
||||
// mergeQ(params) {
|
||||
// let q = `_type:${this.typeId}`
|
||||
// Object.keys(params).forEach((key) => {
|
||||
// if (!['pageNo', 'pageSize', 'sortField', 'sortOrder'].includes(key) && params[key] + '' !== '') {
|
||||
// if (typeof params[key] === 'object' && params[key] && params[key].length > 1) {
|
||||
// q += `,${key}:(${params[key].join(';')})`
|
||||
// } else if (params[key]) {
|
||||
// q += `,${key}:*${params[key]}*`
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// return q
|
||||
// },
|
||||
|
||||
async loadPreferenceAttrList() {
|
||||
const subscribed = await getSubscribeAttributes(this.typeId)
|
||||
this.preferenceAttrList = subscribed.attributes // All columns that have been subscribed
|
||||
},
|
||||
|
||||
onSelectChange() {
|
||||
// const current = records.map((i) => i.ci_id || i._id)
|
||||
|
||||
// const cached = new Set(this.selectedRowKeys)
|
||||
// if (checked) {
|
||||
// current.forEach((i) => {
|
||||
// cached.add(i)
|
||||
// })
|
||||
// } else {
|
||||
// if (row) {
|
||||
// cached.delete(row.ci_id || row._id)
|
||||
// } else {
|
||||
// this.instanceList.map((row) => {
|
||||
// cached.delete(row.ci_id || row._id)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
const xTable = this.$refs.xTable.getVxetableRef()
|
||||
const records = [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()]
|
||||
this.selectedRowKeys = records.map((i) => i.ci_id || i._id)
|
||||
},
|
||||
onSelectRangeEnd({ records }) {
|
||||
// const current = records.map((i) => i.ci_id || i._id)
|
||||
// const cached = new Set(this.selectedRowKeys)
|
||||
// current.forEach((i) => {
|
||||
// cached.add(i)
|
||||
// })
|
||||
this.selectedRowKeys = records.map((i) => i.ci_id || i._id)
|
||||
|
||||
// this.setSelectRows()
|
||||
},
|
||||
reloadData() {
|
||||
this.loadTableData()
|
||||
@@ -623,7 +564,7 @@ export default {
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
$table.revertData(row)
|
||||
this.loadTableData()
|
||||
})
|
||||
}
|
||||
this.columns.forEach((col) => {
|
||||
@@ -694,12 +635,22 @@ export default {
|
||||
}
|
||||
})
|
||||
this.$refs.create.visible = false
|
||||
const key = 'updatable'
|
||||
let errorMsg = ''
|
||||
for (let i = 0; i < this.selectedRowKeys.length; i++) {
|
||||
await updateCI(this.selectedRowKeys[i], payload, false)
|
||||
.then(() => {
|
||||
successNum += 1
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((error) => {
|
||||
errorMsg = errorMsg + '\n' + `${this.selectedRowKeys[i]}:${error.response?.data?.message ?? ''}`
|
||||
this.$notification.warning({
|
||||
key,
|
||||
message: this.$t('warning'),
|
||||
description: errorMsg,
|
||||
duration: 0,
|
||||
style: { whiteSpace: 'break-spaces' },
|
||||
})
|
||||
errorNum += 1
|
||||
})
|
||||
.finally(() => {
|
||||
|
@@ -24,7 +24,9 @@
|
||||
/>
|
||||
</template>
|
||||
<template v-if="parentsType && parentsType.length">
|
||||
<a-divider style="font-size:14px;margin:14px 0;font-weight:700;">{{ $t('cmdb.menu.citypeRelation') }}</a-divider>
|
||||
<a-divider style="font-size:14px;margin:14px 0;font-weight:700;">{{
|
||||
$t('cmdb.menu.citypeRelation')
|
||||
}}</a-divider>
|
||||
<a-form>
|
||||
<a-row :gutter="24" align="top" type="flex">
|
||||
<a-col :span="12" v-for="item in parentsType" :key="item.id">
|
||||
@@ -40,7 +42,11 @@
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-input :placeholder="$t('cmdb.ci.tips1')" v-model="parentsForm[item.name].value" style="width: 50%" />
|
||||
<a-input
|
||||
:placeholder="$t('cmdb.ci.tips1')"
|
||||
v-model="parentsForm[item.name].value"
|
||||
style="width: 50%"
|
||||
/>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
@@ -60,7 +60,7 @@
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="attr.is_list">
|
||||
<span> {{ ci[attr.name].join(',') }}</span>
|
||||
<span> {{ ci[attr.name] && Array.isArray(ci[attr.name]) ? ci[attr.name].join(',') : ci[attr.name] }}</span>
|
||||
</template>
|
||||
<template v-else>{{ getName(ci[attr.name]) }}</template>
|
||||
</span>
|
||||
@@ -105,23 +105,6 @@
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
:style="{ width: '100%' }"
|
||||
v-decorator="[
|
||||
attr.name,
|
||||
{
|
||||
rules: [{ required: attr.is_required }],
|
||||
},
|
||||
]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
v-else-if="attr.is_list"
|
||||
mode="tags"
|
||||
showSearch
|
||||
allowClear
|
||||
size="small"
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
>
|
||||
</a-select>
|
||||
<a-input-number
|
||||
size="small"
|
||||
v-decorator="[
|
||||
@@ -131,7 +114,7 @@
|
||||
},
|
||||
]"
|
||||
style="width: 100%"
|
||||
v-else-if="attr.value_type === '0' || attr.value_type === '1'"
|
||||
v-else-if="(attr.value_type === '0' || attr.value_type === '1') && !attr.is_list"
|
||||
/>
|
||||
<a-date-picker
|
||||
size="small"
|
||||
@@ -144,22 +127,9 @@
|
||||
style="width: 100%"
|
||||
:format="attr.value_type === '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
|
||||
:valueFormat="attr.value_type === '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
|
||||
v-else-if="attr.value_type === '4' || attr.value_type === '3'"
|
||||
v-else-if="(attr.value_type === '4' || attr.value_type === '3') && !attr.is_list"
|
||||
:showTime="attr.value_type === '4' ? false : { format: 'HH:mm:ss' }"
|
||||
/>
|
||||
<!-- <a-input
|
||||
size="small"
|
||||
@focus="(e) => handleFocusInput(e, attr)"
|
||||
v-decorator="[
|
||||
attr.name,
|
||||
{
|
||||
validateTrigger: ['submit'],
|
||||
rules: [{ required: attr.is_required }],
|
||||
},
|
||||
]"
|
||||
style="width: 100%"
|
||||
v-else-if="attr.value_type === '6'"
|
||||
/> -->
|
||||
<a-input
|
||||
size="small"
|
||||
v-decorator="[
|
||||
@@ -241,7 +211,9 @@ export default {
|
||||
this.$nextTick(async () => {
|
||||
if (this.attr.is_list && !this.attr.is_choice) {
|
||||
this.form.setFieldsValue({
|
||||
[`${this.attr.name}`]: this.ci[this.attr.name] || null,
|
||||
[`${this.attr.name}`]: Array.isArray(this.ci[this.attr.name])
|
||||
? this.ci[this.attr.name].join(',')
|
||||
: this.ci[this.attr.name],
|
||||
})
|
||||
return
|
||||
}
|
||||
|
50
cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailDrawer.vue
Normal file
50
cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailDrawer.vue
Normal 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>
|
@@ -39,7 +39,11 @@
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<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
|
||||
:disabled="!canEdit[parent.id]"
|
||||
:style="{
|
||||
@@ -82,7 +86,11 @@
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<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
|
||||
:disabled="!canEdit[child.id]"
|
||||
:style="{
|
||||
@@ -416,6 +424,7 @@ export default {
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-detail-relation {
|
||||
height: 100%;
|
||||
.ci-detail-relation-table-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div
|
||||
id="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>
|
||||
</template>
|
||||
|
||||
|
@@ -1,23 +1,13 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
width="80%"
|
||||
placement="left"
|
||||
@close="
|
||||
() => {
|
||||
visible = false
|
||||
}
|
||||
"
|
||||
:visible="visible"
|
||||
:hasTitle="false"
|
||||
:hasFooter="false"
|
||||
:bodyStyle="{ padding: 0, height: '100vh' }"
|
||||
wrapClassName="ci-detail"
|
||||
destroyOnClose
|
||||
>
|
||||
<a-tabs v-model="activeTabKey" @change="changeTab">
|
||||
<div :style="{ height: '100%' }">
|
||||
<a-tabs v-if="hasPermission" class="ci-detail-tab" v-model="activeTabKey" @change="changeTab">
|
||||
<a @click="shareCi" slot="tabBarExtraContent" :style="{ marginRight: '24px' }">
|
||||
<a-icon type="share-alt" />
|
||||
{{ $t('cmdb.ci.share') }}
|
||||
</a>
|
||||
<a-tab-pane key="tab_1">
|
||||
<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
|
||||
:title="group.name || $t('other')"
|
||||
:key="group.name"
|
||||
@@ -37,18 +27,18 @@
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_2">
|
||||
<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" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_3">
|
||||
<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
|
||||
ref="xTable"
|
||||
:data="ciHistory"
|
||||
size="small"
|
||||
:max-height="`${windowHeight - 94}px`"
|
||||
height="auto"
|
||||
:span-method="mergeRowMethod"
|
||||
border
|
||||
:scroll-y="{ enabled: false }"
|
||||
@@ -88,12 +78,22 @@
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_4">
|
||||
<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" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
@@ -105,8 +105,8 @@ import { getCIById } from '@/modules/cmdb/api/ci'
|
||||
import CiDetailAttrContent from './ciDetailAttrContent.vue'
|
||||
import CiDetailRelation from './ciDetailRelation.vue'
|
||||
import TriggerTable from '../../operation_history/modules/triggerTable.vue'
|
||||
|
||||
export default {
|
||||
name: 'CiDetailTab',
|
||||
components: {
|
||||
ElDescriptions: Descriptions,
|
||||
ElDescriptionsItem: DescriptionsItem,
|
||||
@@ -126,7 +126,6 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
ci: {},
|
||||
attributeGroups: [],
|
||||
activeTabKey: 'tab_1',
|
||||
@@ -134,13 +133,13 @@ export default {
|
||||
ciHistory: [],
|
||||
ciId: null,
|
||||
ci_types: [],
|
||||
hasPermission: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
|
||||
operateTypeMap() {
|
||||
return {
|
||||
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: {
|
||||
create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
|
||||
this.visible = true
|
||||
async create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
|
||||
this.activeTabKey = activeTabKey
|
||||
if (activeTabKey === 'tab_2') {
|
||||
this.$nextTick(() => {
|
||||
@@ -167,12 +178,14 @@ export default {
|
||||
})
|
||||
}
|
||||
this.ciId = ciId
|
||||
this.getAttributes()
|
||||
this.getCI()
|
||||
this.getCIHistory()
|
||||
getCITypes().then((res) => {
|
||||
this.ci_types = res.ci_types
|
||||
})
|
||||
await this.getCI()
|
||||
if (this.hasPermission) {
|
||||
this.getAttributes()
|
||||
this.getCIHistory()
|
||||
getCITypes().then((res) => {
|
||||
this.ci_types = res.ci_types
|
||||
})
|
||||
}
|
||||
},
|
||||
getAttributes() {
|
||||
getCITypeGroupById(this.typeId, { need_other: 1 })
|
||||
@@ -181,11 +194,14 @@ export default {
|
||||
})
|
||||
.catch((e) => {})
|
||||
},
|
||||
getCI() {
|
||||
getCIById(this.ciId)
|
||||
async getCI() {
|
||||
await getCIById(this.ciId)
|
||||
.then((res) => {
|
||||
// this.ci = res.ci
|
||||
this.ci = res.result[0]
|
||||
if (res.result.length) {
|
||||
this.ci = res.result[0]
|
||||
} else {
|
||||
this.hasPermission = false
|
||||
}
|
||||
})
|
||||
.catch((e) => {})
|
||||
},
|
||||
@@ -270,9 +286,13 @@ export default {
|
||||
// 修改的字段为树形视图订阅的字段 则全部reload
|
||||
setTimeout(() => {
|
||||
if (_find) {
|
||||
this.reload()
|
||||
if (this.reload) {
|
||||
this.reload()
|
||||
}
|
||||
} else {
|
||||
this.handleSearch()
|
||||
if (this.handleSearch) {
|
||||
this.handleSearch()
|
||||
}
|
||||
}
|
||||
}, 500)
|
||||
},
|
||||
@@ -303,23 +323,49 @@ export default {
|
||||
// 修改的字段为树形视图订阅的字段 则全部reload
|
||||
setTimeout(() => {
|
||||
if (_find) {
|
||||
this.reload()
|
||||
if (this.reload) {
|
||||
this.reload()
|
||||
}
|
||||
} else {
|
||||
this.handleSearch()
|
||||
if (this.handleSearch) {
|
||||
this.handleSearch()
|
||||
}
|
||||
}
|
||||
}, 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>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<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 {
|
||||
margin: 0;
|
||||
}
|
||||
.ant-tabs-extra-content {
|
||||
line-height: 44px;
|
||||
}
|
||||
.ci-detail-attr {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding: 24px;
|
||||
.el-descriptions-item__content {
|
||||
cursor: default;
|
||||
&:hover a {
|
@@ -22,7 +22,14 @@
|
||||
attr.name,
|
||||
{
|
||||
rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
|
||||
initialValue: attr.default && attr.default.default ? attr.default.default : attr.is_list ? [] : null,
|
||||
initialValue:
|
||||
attr.default && attr.default.default
|
||||
? attr.is_list
|
||||
? Array.isArray(attr.default.default)
|
||||
? attr.default.default
|
||||
: attr.default.default.split(',')
|
||||
: attr.default.default
|
||||
: null,
|
||||
},
|
||||
]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
@@ -53,19 +60,18 @@
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
<a-input
|
||||
v-else-if="attr.is_list"
|
||||
mode="tags"
|
||||
:style="{ width: '100%' }"
|
||||
v-decorator="[
|
||||
attr.name,
|
||||
{
|
||||
rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
|
||||
initialValue: attr.default && attr.default.default ? attr.default.default : attr.is_list ? [] : null,
|
||||
initialValue: attr.default && attr.default.default ? attr.default.default : '',
|
||||
},
|
||||
]"
|
||||
>
|
||||
</a-select>
|
||||
</a-input>
|
||||
<a-input-number
|
||||
v-decorator="[
|
||||
attr.name,
|
||||
|
@@ -1,103 +0,0 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:visible="visible"
|
||||
width="600"
|
||||
@close="
|
||||
() => {
|
||||
visible = false
|
||||
}
|
||||
"
|
||||
:title="$t('cmdb.ci.attributeSettings')"
|
||||
>
|
||||
<CustomTransfer
|
||||
ref="customTransfer"
|
||||
:dataSource="attrList"
|
||||
:showSearch="true"
|
||||
:listStyle="{
|
||||
width: '230px',
|
||||
height: '500px',
|
||||
}"
|
||||
:titles="[$t('cmdb.components.unselectAttributes'), $t('cmdb.components.selectAttributes')]"
|
||||
:render="item => item.title"
|
||||
:targetKeys="selectedAttrList"
|
||||
@change="handleChange"
|
||||
@selectChange="selectChange"
|
||||
>
|
||||
<span slot="notFoundContent">{{ $t('noData') }}</span>
|
||||
</CustomTransfer>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="handleSubmit" type="primary">{{ $t('confirm') }}</a-button>
|
||||
</div>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { subscribeCIType, getSubscribeAttributes } from '@/modules/cmdb/api/preference'
|
||||
import { getCITypeAttributesByName } from '@/modules/cmdb/api/CITypeAttr'
|
||||
export default {
|
||||
name: 'EditAttrsDrawer',
|
||||
data() {
|
||||
return {
|
||||
attrList: [],
|
||||
typeId: null,
|
||||
visible: false,
|
||||
selectedAttrList: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open(typeId) {
|
||||
this.typeId = typeId
|
||||
this.getAttrs()
|
||||
},
|
||||
getAttrs() {
|
||||
getCITypeAttributesByName(this.typeId).then(res => {
|
||||
const attributes = res.attributes
|
||||
getSubscribeAttributes(this.typeId).then(_res => {
|
||||
const attrList = []
|
||||
const selectedAttrList = []
|
||||
const subAttributes = _res.attributes
|
||||
this.instanceSubscribed = _res.is_subscribed
|
||||
subAttributes.forEach(item => {
|
||||
selectedAttrList.push(item.id.toString())
|
||||
})
|
||||
|
||||
attributes.forEach(item => {
|
||||
const data = {
|
||||
key: item.id.toString(),
|
||||
title: item.alias || item.name,
|
||||
}
|
||||
attrList.push(data)
|
||||
})
|
||||
|
||||
this.attrList = attrList
|
||||
this.selectedAttrList = selectedAttrList
|
||||
this.visible = true
|
||||
})
|
||||
})
|
||||
},
|
||||
handleChange(targetKeys, direction, moveKeys) {
|
||||
this.selectedAttrList = targetKeys
|
||||
},
|
||||
handleSubmit() {
|
||||
const that = this
|
||||
if (this.selectedAttrList.length) {
|
||||
subscribeCIType(this.typeId, this.selectedAttrList).then(res => {
|
||||
this.$message.success(this.$t('cmdb.components.subSuccess'))
|
||||
this.visible = false
|
||||
this.$emit('refresh')
|
||||
})
|
||||
} else {
|
||||
this.$confirm({
|
||||
title: that.$t('warning'),
|
||||
content: that.$t('cmdb.ci.tips4'),
|
||||
})
|
||||
}
|
||||
},
|
||||
selectChange(sourceSelectedKeys, targetSelectedKeys) {
|
||||
this.$refs.customTransfer.dbClick(sourceSelectedKeys, targetSelectedKeys, 'title', 'key')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@@ -1 +0,0 @@
|
||||
editAttrsDrawer 这个文件似乎也没用了
|
@@ -1,24 +1,30 @@
|
||||
<template>
|
||||
<div class="attribute-card">
|
||||
<div class="attribute-card-content">
|
||||
<div class="attribute-card-value-type-icon handle" :style="{ ...getPropertyStyle(property) }">
|
||||
<ValueTypeIcon :attr="property" />
|
||||
</div>
|
||||
<div :class="{ 'attribute-card-content-inner': true, 'attribute-card-name-required': property.is_required }">
|
||||
<div :class="{ 'attribute-card-name': true, 'attribute-card-name-default-show': property.default_show }">
|
||||
{{ property.alias || property.name }}
|
||||
<div :class="{ 'attribute-card': true, 'attribute-card-inherited': inherited }">
|
||||
<a-tooltip :title="inherited ? $t('cmdb.ciType.inheritFrom', { name: property.inherited_from }) : ''">
|
||||
<div class="attribute-card-content">
|
||||
<div
|
||||
:class="{ 'attribute-card-value-type-icon': true, handle: !inherited }"
|
||||
:style="{ ...getPropertyStyle(property) }"
|
||||
>
|
||||
<ValueTypeIcon :attr="property" />
|
||||
</div>
|
||||
<div :class="{ 'attribute-card-content-inner': true, 'attribute-card-name-required': property.is_required }">
|
||||
<div :class="{ 'attribute-card-name': true, 'attribute-card-name-default-show': property.default_show }">
|
||||
{{ property.alias || property.name }}
|
||||
</div>
|
||||
<div v-if="property.is_password" class="attribute-card_value-type">{{ $t('cmdb.ciType.password') }}</div>
|
||||
<div v-else-if="property.is_link" class="attribute-card_value-type">{{ $t('cmdb.ciType.link') }}</div>
|
||||
<div v-else class="attribute-card_value-type">{{ valueTypeMap[property.value_type] }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="attribute-card-trigger"
|
||||
v-if="(property.value_type === '3' || property.value_type === '4') && !isStore"
|
||||
>
|
||||
<a @click="openTrigger"><ops-icon type="ops-trigger"/></a>
|
||||
</div>
|
||||
<div v-if="property.is_password" class="attribute-card_value-type">{{ $t('cmdb.ciType.password') }}</div>
|
||||
<div v-else-if="property.is_link" class="attribute-card_value-type">{{ $t('cmdb.ciType.link') }}</div>
|
||||
<div v-else class="attribute-card_value-type">{{ valueTypeMap[property.value_type] }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="attribute-card-trigger"
|
||||
v-if="(property.value_type === '3' || property.value_type === '4') && !isStore"
|
||||
>
|
||||
<a @click="openTrigger"><ops-icon type="ops-trigger"/></a>
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
|
||||
<div class="attribute-card-footer">
|
||||
<a-popover
|
||||
trigger="click"
|
||||
@@ -51,7 +57,7 @@
|
||||
</a-space>
|
||||
</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-tooltip :title="$t('cmdb.ciType.computeForAllCITips')">
|
||||
<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: {
|
||||
getPropertyStyle,
|
||||
@@ -211,13 +220,15 @@ export default {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 12px;
|
||||
cursor: move;
|
||||
background: #ffffff !important;
|
||||
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
|
||||
border-radius: 2px;
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
}
|
||||
.handle {
|
||||
cursor: move;
|
||||
}
|
||||
.attribute-card-content-inner {
|
||||
padding-left: 12px;
|
||||
font-weight: 400;
|
||||
@@ -269,6 +280,12 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
.attribute-card-inherited {
|
||||
background: #f3f4f7;
|
||||
.attribute-card-footer {
|
||||
background: #eaedf3;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.attribute-card-footer-popover {
|
||||
|
@@ -13,7 +13,11 @@
|
||||
<a-form :form="form" :layout="formLayout">
|
||||
<a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.basicConfig') }}</a-divider>
|
||||
<a-col :span="12">
|
||||
<a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" :label="$t('cmdb.ciType.AttributeName')">
|
||||
<a-form-item
|
||||
:label-col="formItemLayout.labelCol"
|
||||
:wrapper-col="formItemLayout.wrapperCol"
|
||||
:label="$t('cmdb.ciType.AttributeName')"
|
||||
>
|
||||
<a-input
|
||||
:disabled="true"
|
||||
name="name"
|
||||
@@ -35,12 +39,20 @@
|
||||
</a-col>
|
||||
<a-col
|
||||
:span="12"
|
||||
><a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" :label="$t('alias')">
|
||||
><a-form-item
|
||||
:label-col="formItemLayout.labelCol"
|
||||
:wrapper-col="formItemLayout.wrapperCol"
|
||||
:label="$t('alias')"
|
||||
>
|
||||
<a-input name="alias" v-decorator="['alias', { rules: [] }]" /> </a-form-item
|
||||
></a-col>
|
||||
<a-col
|
||||
:span="12"
|
||||
><a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" :label="$t('cmdb.ciType.DataType')">
|
||||
><a-form-item
|
||||
:label-col="formItemLayout.labelCol"
|
||||
:wrapper-col="formItemLayout.wrapperCol"
|
||||
:label="$t('cmdb.ciType.DataType')"
|
||||
>
|
||||
<a-select
|
||||
:disabled="true"
|
||||
name="value_type"
|
||||
@@ -59,13 +71,12 @@
|
||||
:label="$t('cmdb.ciType.defaultValue')"
|
||||
>
|
||||
<template>
|
||||
<a-select
|
||||
<a-input
|
||||
v-if="form.getFieldValue('is_list')"
|
||||
mode="tags"
|
||||
:style="{ width: '100%' }"
|
||||
v-decorator="['default_value', { rules: [{ required: false }] }]"
|
||||
>
|
||||
</a-select>
|
||||
</a-input>
|
||||
<a-select
|
||||
v-decorator="['default_value', { rules: [{ required: false }] }]"
|
||||
mode="tags"
|
||||
@@ -160,7 +171,11 @@
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
|
||||
<a-form-item :label-col="{ span: 8 }" :wrapper-col="horizontalFormItemLayout.wrapperCol" :label="$t('cmdb.ciType.unique')">
|
||||
<a-form-item
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="horizontalFormItemLayout.wrapperCol"
|
||||
:label="$t('cmdb.ciType.unique')"
|
||||
>
|
||||
<a-switch
|
||||
:disabled="isShowComputedArea"
|
||||
@change="onChange"
|
||||
@@ -282,6 +297,11 @@
|
||||
</a-col>
|
||||
<a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider>
|
||||
<a-row>
|
||||
<a-col :span="24" v-if="!['6'].includes(currentValueType)">
|
||||
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 12 }" :label="$t('cmdb.ciType.reg')">
|
||||
<RegSelect :isShowErrorMsg="false" v-model="re_check" :limitedFormat="getLimitedFormat()" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')">
|
||||
<FontArea ref="fontArea" />
|
||||
@@ -303,11 +323,7 @@
|
||||
<span
|
||||
style="position:relative;white-space:pre;"
|
||||
>{{ $t('cmdb.ciType.computedAttribute') }}
|
||||
<a-tooltip
|
||||
:title="
|
||||
$t('cmdb.ciType.computedAttributeTips')
|
||||
"
|
||||
>
|
||||
<a-tooltip :title="$t('cmdb.ciType.computedAttributeTips')">
|
||||
<a-icon
|
||||
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
@@ -355,6 +371,8 @@ import _ from 'lodash'
|
||||
import moment from 'moment'
|
||||
import vueJsonEditor from 'vue-json-editor'
|
||||
import {
|
||||
// createAttribute,
|
||||
// createCITypeAttributes,
|
||||
updateAttributeById,
|
||||
updateCITypeAttributesById,
|
||||
canDefineComputed,
|
||||
@@ -364,10 +382,11 @@ import { valueTypeMap } from '../../utils/const'
|
||||
import ComputedArea from './computedArea.vue'
|
||||
import PreValueArea from './preValueArea.vue'
|
||||
import FontArea from './fontArea.vue'
|
||||
import RegSelect from '@/components/RegexSelect'
|
||||
|
||||
export default {
|
||||
name: 'AttributeEditForm',
|
||||
components: { ComputedArea, PreValueArea, vueJsonEditor, FontArea },
|
||||
components: { ComputedArea, PreValueArea, vueJsonEditor, FontArea, RegSelect },
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
@@ -395,6 +414,7 @@ export default {
|
||||
isShowComputedArea: false,
|
||||
|
||||
defaultForDatetime: '',
|
||||
re_check: {},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -517,15 +537,30 @@ export default {
|
||||
})
|
||||
}
|
||||
console.log(_record)
|
||||
if (!['6'].includes(_record.value_type) && _record.re_check) {
|
||||
this.re_check = {
|
||||
value: _record.re_check,
|
||||
}
|
||||
} else {
|
||||
this.re_check = {}
|
||||
}
|
||||
if (_record.default) {
|
||||
this.$nextTick(() => {
|
||||
if (_record.value_type === '0') {
|
||||
this.form.setFieldsValue({
|
||||
default_value: _record.default.default ? [_record.default.default] : [],
|
||||
})
|
||||
if (_record.is_list) {
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
default_value: _record.default.default ? _record.default.default : '',
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.form.setFieldsValue({
|
||||
default_value: _record.default.default ? [_record.default.default] : [],
|
||||
})
|
||||
}
|
||||
} else if (_record.value_type === '6') {
|
||||
this.default_value_json = _record?.default?.default || null
|
||||
} else if (_record.value_type === '3' || _record.value_type === '4') {
|
||||
} else if ((_record.value_type === '3' || _record.value_type === '4') && !_record.is_list) {
|
||||
if (_record?.default?.default === '$created_at' || _record?.default?.default === '$updated_at') {
|
||||
this.defaultForDatetime = _record.default.default
|
||||
this.form.setFieldsValue({
|
||||
@@ -584,6 +619,9 @@ export default {
|
||||
await this.form.validateFields(async (err, values) => {
|
||||
if (!err) {
|
||||
console.log('Received values of form: ', values)
|
||||
// if (values.choice_value) {
|
||||
// values.choice_value = values.choice_value.split('\n')
|
||||
// }
|
||||
|
||||
if (this.record.is_required !== values.is_required || this.record.default_show !== values.default_show) {
|
||||
console.log('changed is_required')
|
||||
@@ -598,7 +636,11 @@ export default {
|
||||
delete values['is_required']
|
||||
const { default_value } = values
|
||||
if (values.value_type === '0' && default_value) {
|
||||
values.default = { default: default_value[0] || null }
|
||||
if (values.is_list) {
|
||||
values.default = { default: default_value || null }
|
||||
} else {
|
||||
values.default = { default: default_value[0] || null }
|
||||
}
|
||||
} else if (values.value_type === '6') {
|
||||
if (this.default_value_json_right) {
|
||||
values.default = { default: this.default_value_json }
|
||||
@@ -606,13 +648,13 @@ export default {
|
||||
values.default = { default: null }
|
||||
}
|
||||
} else if (default_value || default_value === 0) {
|
||||
if (values.value_type === '3') {
|
||||
if (values.value_type === '3' && !values.is_list) {
|
||||
if (default_value === '$created_at' || default_value === '$updated_at') {
|
||||
values.default = { default: default_value }
|
||||
} else {
|
||||
values.default = { default: moment(default_value).format('YYYY-MM-DD HH:mm:ss') }
|
||||
}
|
||||
} else if (values.value_type === '4') {
|
||||
} else if (values.value_type === '4' && !values.is_list) {
|
||||
values.default = { default: moment(default_value).format('YYYY-MM-DD') }
|
||||
} else {
|
||||
values.default = { default: default_value }
|
||||
@@ -641,6 +683,9 @@ export default {
|
||||
values.value_type = '2'
|
||||
values.is_link = true
|
||||
}
|
||||
if (values.value_type !== '6') {
|
||||
values.re_check = this.re_check?.value ?? null
|
||||
}
|
||||
if (values.id) {
|
||||
await this.updateAttribute(values.id, { ...values, option: { fontOptions } }, isCalcComputed)
|
||||
} else {
|
||||
@@ -698,6 +743,21 @@ export default {
|
||||
async handleCalcComputed() {
|
||||
await this.handleSubmit(true)
|
||||
},
|
||||
getLimitedFormat() {
|
||||
if (['0'].includes(this.currentValueType)) {
|
||||
return ['number', 'phone', 'landline', 'zipCode', 'IDCard', 'monetaryAmount', 'custom']
|
||||
}
|
||||
if (['1'].includes(this.currentValueType)) {
|
||||
return ['number', 'monetaryAmount', 'custom']
|
||||
}
|
||||
if (['3', '4', '5'].includes(this.currentValueType)) {
|
||||
return ['custom']
|
||||
}
|
||||
if (this.currentValueType === '8') {
|
||||
return ['link', 'custom']
|
||||
}
|
||||
return []
|
||||
},
|
||||
},
|
||||
watch: {},
|
||||
}
|
||||
|
@@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<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>
|
||||
<a-form-item :label="$t('name')" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-input type="text" v-model.trim="newGroupName" />
|
||||
@@ -9,21 +14,12 @@
|
||||
</a-modal>
|
||||
<div class="ci-types-attributes" :style="{ maxHeight: `${windowHeight - 104}px` }">
|
||||
<a-space style="margin-bottom: 10px">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleAddGroup"
|
||||
size="small"
|
||||
class="ops-button-primary"
|
||||
icon="plus"
|
||||
>{{ $t('cmdb.ciType.group') }}</a-button
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleOpenUniqueConstraint"
|
||||
size="small"
|
||||
class="ops-button-primary"
|
||||
>{{ $t('cmdb.ciType.uniqueConstraint') }}</a-button
|
||||
>
|
||||
<a-button type="primary" @click="handleAddGroup" size="small" class="ops-button-primary" icon="plus">{{
|
||||
$t('cmdb.ciType.group')
|
||||
}}</a-button>
|
||||
<a-button type="primary" @click="handleOpenUniqueConstraint" size="small" class="ops-button-primary">{{
|
||||
$t('cmdb.ciType.uniqueConstraint')
|
||||
}}</a-button>
|
||||
</a-space>
|
||||
<div :key="CITypeGroup.id" v-for="(CITypeGroup, index) in CITypeGroups">
|
||||
<div>
|
||||
@@ -33,7 +29,11 @@
|
||||
>
|
||||
<span style="font-weight:700">{{ CITypeGroup.name }}</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>
|
||||
</div>
|
||||
@@ -71,7 +71,13 @@
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<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-space>
|
||||
</div>
|
||||
@@ -82,7 +88,7 @@
|
||||
@start="drag = true"
|
||||
@change="
|
||||
(e) => {
|
||||
handleChange(e, CITypeGroup.id)
|
||||
handleChange(e, CITypeGroup.name)
|
||||
}
|
||||
"
|
||||
:filter="'.filter-empty'"
|
||||
@@ -124,7 +130,7 @@
|
||||
@start="drag = true"
|
||||
@change="
|
||||
(e) => {
|
||||
handleChange(e, -1)
|
||||
handleChange(e, null)
|
||||
}
|
||||
"
|
||||
:animation="300"
|
||||
@@ -144,18 +150,18 @@
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
<attribute-edit-form
|
||||
<AttributeEditForm
|
||||
ref="attributeEditForm"
|
||||
:CITypeId="CITypeId"
|
||||
:CITypeName="CITypeName"
|
||||
@ok="handleOk"
|
||||
></attribute-edit-form>
|
||||
<new-ci-type-attr-modal
|
||||
></AttributeEditForm>
|
||||
<NewCiTypeAttrModal
|
||||
ref="newCiTypeAttrModal"
|
||||
:CITypeId="CITypeId"
|
||||
:linked-ids="linkedIds"
|
||||
@ok="handleOk"
|
||||
></new-ci-type-attr-modal>
|
||||
></NewCiTypeAttrModal>
|
||||
<UniqueConstraint ref="uniqueConstraint" :CITypeId="CITypeId" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -347,8 +353,8 @@ export default {
|
||||
},
|
||||
|
||||
handleMoveGroup(beforeIndex, afterIndex) {
|
||||
const fromGroupId = this.CITypeGroups[beforeIndex].id
|
||||
const toGroupId = this.CITypeGroups[afterIndex].id
|
||||
const fromGroupId = this.CITypeGroups[beforeIndex].name
|
||||
const toGroupId = this.CITypeGroups[afterIndex].name
|
||||
transferCITypeGroupIndex(this.CITypeId, { from: fromGroupId, to: toGroupId }).then((res) => {
|
||||
this.$message.success(this.$t('operateSuccess'))
|
||||
const beforeGroup = this.CITypeGroups[beforeIndex]
|
||||
@@ -414,14 +420,14 @@ export default {
|
||||
})
|
||||
},
|
||||
handleChange(e, group) {
|
||||
console.log('changess')
|
||||
console.log('changess', group)
|
||||
if (e.hasOwnProperty('moved') && e.moved.oldIndex !== e.moved.newIndex) {
|
||||
if (group === -1) {
|
||||
this.$message.error(this.$t('cmdb.ciType.attributeSortedTips'))
|
||||
} else {
|
||||
transferCITypeAttrIndex(this.CITypeId, {
|
||||
from: { attr_id: e.moved.element.id, group_id: group > -1 ? group : null },
|
||||
to: { order: e.moved.newIndex, group_id: group > -1 ? group : null },
|
||||
from: { attr_id: e.moved.element.id, group_name: group },
|
||||
to: { order: e.moved.newIndex, group_name: group },
|
||||
})
|
||||
.then((res) => this.$message.success(this.$t('updateSuccess')))
|
||||
.catch(() => {
|
||||
@@ -431,14 +437,14 @@ export default {
|
||||
}
|
||||
|
||||
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')) {
|
||||
this.$nextTick(() => {
|
||||
transferCITypeAttrIndex(this.CITypeId, {
|
||||
from: { attr_id: e.removed.element.id, group_id: group > -1 ? group : null },
|
||||
to: { group_id: this.addRemoveGroupFlag.to.group_id, order: this.addRemoveGroupFlag.to.order },
|
||||
from: { attr_id: e.removed.element.id, group_name: group },
|
||||
to: { group_name: this.addRemoveGroupFlag.to.group_name, order: this.addRemoveGroupFlag.to.order },
|
||||
})
|
||||
.then((res) => this.$message.success(this.$t('saveSuccess')))
|
||||
.catch(() => {
|
||||
|
@@ -21,7 +21,10 @@
|
||||
message: $t('cmdb.ciType.attributeNameTips'),
|
||||
pattern: RegExp('^(?!\\d)[a-zA-Z_0-9]+$'),
|
||||
},
|
||||
{ message: $t('cmdb.ciType.buildinAttribute'), pattern: RegExp('^(?!(id|_id|ci_id|type|_type|ci_type)$).*$') },
|
||||
{
|
||||
message: $t('cmdb.ciType.buildinAttribute'),
|
||||
pattern: RegExp('^(?!(id|_id|ci_id|type|_type|ci_type)$).*$'),
|
||||
},
|
||||
],
|
||||
},
|
||||
]"
|
||||
@@ -59,13 +62,12 @@
|
||||
:label="$t('cmdb.ciType.defaultValue')"
|
||||
>
|
||||
<template>
|
||||
<a-select
|
||||
<a-input
|
||||
v-if="form.getFieldValue('is_list')"
|
||||
mode="tags"
|
||||
:style="{ width: '100%' }"
|
||||
v-decorator="['default_value', { rules: [{ required: false }] }]"
|
||||
>
|
||||
</a-select>
|
||||
</a-input>
|
||||
<a-input-number
|
||||
style="width: 100%"
|
||||
v-else-if="currentValueType === '1'"
|
||||
@@ -162,7 +164,11 @@
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
|
||||
<a-form-item :label-col="{ span: 8 }" :wrapper-col="horizontalFormItemLayout.wrapperCol" :label="$t('cmdb.ciType.unique')">
|
||||
<a-form-item
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="horizontalFormItemLayout.wrapperCol"
|
||||
:label="$t('cmdb.ciType.unique')"
|
||||
>
|
||||
<a-switch
|
||||
:disabled="isShowComputedArea"
|
||||
@change="onChange"
|
||||
@@ -280,6 +286,11 @@
|
||||
</a-col>
|
||||
<a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider>
|
||||
<a-row>
|
||||
<a-col :span="24" v-if="!['6'].includes(currentValueType)">
|
||||
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 12 }" :label="$t('cmdb.ciType.reg')">
|
||||
<RegSelect :isShowErrorMsg="false" v-model="re_check" :limitedFormat="getLimitedFormat()" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')">
|
||||
<FontArea ref="fontArea" />
|
||||
@@ -296,11 +307,7 @@
|
||||
<span
|
||||
style="position:relative;white-space:pre;"
|
||||
>{{ $t('cmdb.ciType.computedAttribute') }}
|
||||
<a-tooltip
|
||||
:title="
|
||||
$t('cmdb.ciType.computedAttributeTips')
|
||||
"
|
||||
>
|
||||
<a-tooltip :title="$t('cmdb.ciType.computedAttributeTips')">
|
||||
<a-icon
|
||||
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
@@ -340,6 +347,7 @@ import { valueTypeMap } from '../../utils/const'
|
||||
import ComputedArea from './computedArea.vue'
|
||||
import PreValueArea from './preValueArea.vue'
|
||||
import FontArea from './fontArea.vue'
|
||||
import RegSelect from '@/components/RegexSelect'
|
||||
|
||||
export default {
|
||||
name: 'CreateNewAttribute',
|
||||
@@ -348,6 +356,7 @@ export default {
|
||||
PreValueArea,
|
||||
vueJsonEditor,
|
||||
FontArea,
|
||||
RegSelect,
|
||||
},
|
||||
props: {
|
||||
hasFooter: {
|
||||
@@ -374,6 +383,8 @@ export default {
|
||||
isShowComputedArea: false,
|
||||
|
||||
defaultForDatetime: '',
|
||||
|
||||
re_check: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -397,8 +408,12 @@ export default {
|
||||
const data = { is_required, default_show }
|
||||
delete values.is_required
|
||||
delete values.default_show
|
||||
if (values.value_type === '0' && default_value && default_value.length) {
|
||||
values.default = { default: default_value[0] }
|
||||
if (values.value_type === '0' && default_value) {
|
||||
if (values.is_list) {
|
||||
values.default = { default: default_value || null }
|
||||
} else {
|
||||
values.default = { default: default_value[0] || null }
|
||||
}
|
||||
} else if (values.value_type === '6') {
|
||||
if (this.default_value_json_right) {
|
||||
values.default = { default: this.default_value_json }
|
||||
@@ -406,13 +421,13 @@ export default {
|
||||
values.default = { default: null }
|
||||
}
|
||||
} else if (default_value || default_value === 0) {
|
||||
if (values.value_type === '3') {
|
||||
if (values.value_type === '3' && !values.is_list) {
|
||||
if (default_value === '$created_at' || default_value === '$updated_at') {
|
||||
values.default = { default: default_value }
|
||||
} else {
|
||||
values.default = { default: moment(default_value).format('YYYY-MM-DD HH:mm:ss') }
|
||||
}
|
||||
} else if (values.value_type === '4') {
|
||||
} else if (values.value_type === '4' && !values.is_list) {
|
||||
values.default = { default: moment(default_value).format('YYYY-MM-DD') }
|
||||
} else {
|
||||
values.default = { default: default_value }
|
||||
@@ -449,6 +464,9 @@ export default {
|
||||
values.value_type = '2'
|
||||
values.is_link = true
|
||||
}
|
||||
if (values.value_type !== '6') {
|
||||
values.re_check = this.re_check?.value ?? null
|
||||
}
|
||||
const { attr_id } = await createAttribute({ ...values, option: { fontOptions } })
|
||||
|
||||
this.form.resetFields()
|
||||
@@ -539,6 +557,21 @@ export default {
|
||||
default_value: key,
|
||||
})
|
||||
},
|
||||
getLimitedFormat() {
|
||||
if (['0'].includes(this.currentValueType)) {
|
||||
return ['number', 'phone', 'landline', 'zipCode', 'IDCard', 'monetaryAmount', 'custom']
|
||||
}
|
||||
if (['1'].includes(this.currentValueType)) {
|
||||
return ['number', 'monetaryAmount', 'custom']
|
||||
}
|
||||
if (['3', '4', '5'].includes(this.currentValueType)) {
|
||||
return ['custom']
|
||||
}
|
||||
if (this.currentValueType === '8') {
|
||||
return ['link', 'custom']
|
||||
}
|
||||
return []
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@@ -37,7 +37,7 @@
|
||||
>{{ $t('cmdb.ciType.attributeLibray') }}</a
|
||||
>
|
||||
<a-dropdown v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')">
|
||||
<a><ops-icon type="ops-menu" /></a>
|
||||
<a><ops-icon type="ops-menu"/></a>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="0">
|
||||
<a-upload
|
||||
@@ -48,12 +48,15 @@
|
||||
action="/api/v0.1/ci_types/template/import/file"
|
||||
@change="changeUploadFile"
|
||||
>
|
||||
<a><a-icon type="upload" /></a><a> {{ $t('upload') }}</a>
|
||||
<a><a-icon type="upload"/></a><a> {{ $t('upload') }}</a>
|
||||
</a-upload>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="1">
|
||||
<a-space>
|
||||
<a href="/api/v0.1/ci_types/template/export/file"><a-icon type="download" /> {{ $t('download') }}</a>
|
||||
<a
|
||||
href="/api/v0.1/ci_types/template/export/file"
|
||||
><a-icon type="download" /> {{ $t('download') }}</a
|
||||
>
|
||||
</a-space>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
@@ -63,9 +66,11 @@
|
||||
<draggable class="ci-types-left-content" :list="CITypeGroups" @end="handleChangeGroups" filter=".undraggable">
|
||||
<div v-for="g in CITypeGroups" :key="g.id || g.name">
|
||||
<div
|
||||
:class="`${currentGId === g.id && !currentCId ? 'selected' : ''} ci-types-left-group ${
|
||||
g.id === -1 ? 'undraggable' : ''
|
||||
}`"
|
||||
:class="
|
||||
`${currentGId === g.id && !currentCId ? 'selected' : ''} ci-types-left-group ${
|
||||
g.id === -1 ? 'undraggable' : ''
|
||||
}`
|
||||
"
|
||||
@click="handleClickGroup(g.id)"
|
||||
>
|
||||
<div>
|
||||
@@ -78,16 +83,16 @@
|
||||
<a-space>
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ $t('cmdb.ciType.addCITypeInGroup') }}</template>
|
||||
<a><a-icon type="plus" @click="handleCreate(g)" /></a>
|
||||
<a><a-icon type="plus" @click="handleCreate(g)"/></a>
|
||||
</a-tooltip>
|
||||
<template v-if="g.id !== -1">
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ $t('cmdb.ciType.editGroup') }}</template>
|
||||
<a><a-icon type="edit" @click="handleEditGroup(g)" /></a>
|
||||
<a><a-icon type="edit" @click="handleEditGroup(g)"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ $t('cmdb.ciType.deleteGroup') }}</template>
|
||||
<a style="color: red"><a-icon type="delete" @click="handleDeleteGroup(g)" /></a>
|
||||
<a style="color: red"><a-icon type="delete" @click="handleDeleteGroup(g)"/></a>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-space>
|
||||
@@ -130,14 +135,16 @@
|
||||
</div>
|
||||
<span class="ci-types-left-detail-title">{{ ci.alias || ci.name }}</span>
|
||||
<a-space class="ci-types-left-detail-action">
|
||||
<a><a-icon type="user-add" @click="(e) => handlePerm(e, ci)" /></a>
|
||||
<a><a-icon type="edit" @click="(e) => handleEdit(e, ci)" /></a>
|
||||
<a><a-icon type="user-add" @click="(e) => handlePerm(e, ci)"/></a>
|
||||
<a><a-icon type="edit" @click="(e) => handleEdit(e, ci)"/></a>
|
||||
<a
|
||||
v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"
|
||||
@click="(e) => handleDownloadCiType(e, ci)">
|
||||
<a-icon type="download"/>
|
||||
:disabled="ci.inherited"
|
||||
@click="(e) => handleDownloadCiType(e, ci)"
|
||||
>
|
||||
<a-icon type="download" />
|
||||
</a>
|
||||
<a style="color: red" @click="(e) => handleDelete(e, ci)"><a-icon type="delete" /></a>
|
||||
<a style="color: red" @click="(e) => handleDelete(e, ci)"><a-icon type="delete"/></a>
|
||||
</a-space>
|
||||
</div>
|
||||
</draggable>
|
||||
@@ -170,6 +177,7 @@
|
||||
placement="right"
|
||||
width="900px"
|
||||
:destroyOnClose="true"
|
||||
:bodyStyle="{ height: 'calc(100vh - 108px)' }"
|
||||
>
|
||||
<a-form
|
||||
:form="form"
|
||||
@@ -198,6 +206,35 @@
|
||||
<a-form-item :label="$t('alias')">
|
||||
<a-input name="alias" v-decorator="['alias', { rules: [] }]" />
|
||||
</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')">
|
||||
<IconArea class="ci_types-icon-area" ref="iconArea" />
|
||||
</a-form-item>
|
||||
@@ -242,6 +279,11 @@
|
||||
:filter-method="filterOption"
|
||||
v-decorator="['unique_key', { rules: [{ required: true, message: $t('cmdb.ciType.uniqueKeySelect') }] }]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
@visible-change="
|
||||
() => {
|
||||
filterInput = ''
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
:key="item.id"
|
||||
@@ -285,7 +327,14 @@ import router, { resetRouter } from '@/router'
|
||||
import store from '@/store'
|
||||
import draggable from 'vuedraggable'
|
||||
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 {
|
||||
getCITypeGroupsConfig,
|
||||
postCITypeGroup,
|
||||
@@ -305,6 +354,7 @@ import CMDBGrant from '../../components/cmdbGrant'
|
||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||
import AttributeStore from './attributeStore.vue'
|
||||
import { getAllDepAndEmployee } from '@/api/company'
|
||||
import CMDBTypeSelect from '../../components/cmdbTypeSelect'
|
||||
|
||||
export default {
|
||||
name: 'CITypes',
|
||||
@@ -319,6 +369,7 @@ export default {
|
||||
SplitPane,
|
||||
OpsMoveIcon,
|
||||
AttributeStore,
|
||||
CMDBTypeSelect,
|
||||
},
|
||||
inject: ['reload'],
|
||||
data() {
|
||||
@@ -357,6 +408,9 @@ export default {
|
||||
default_order_asc: '1',
|
||||
|
||||
allTreeDepAndEmp: [],
|
||||
|
||||
editCiType: null,
|
||||
isInherit: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -549,8 +603,10 @@ export default {
|
||||
})
|
||||
},
|
||||
onClose() {
|
||||
this.filterInput = ''
|
||||
this.form.resetFields()
|
||||
this.drawerVisible = false
|
||||
this.isInherit = false
|
||||
},
|
||||
handleCreateNewAttrDone() {
|
||||
this.getAttributes()
|
||||
@@ -571,6 +627,22 @@ export default {
|
||||
const icon =
|
||||
_icon && _icon.name ? `${_icon.name}$$${_icon.color || ''}$$${_icon.id || ''}$$${_icon.url || ''}` : ''
|
||||
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, {
|
||||
...values,
|
||||
icon,
|
||||
@@ -581,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) {
|
||||
console.log('start', g)
|
||||
this.startId = g.id
|
||||
@@ -739,7 +828,7 @@ export default {
|
||||
const x = new XMLHttpRequest()
|
||||
x.open('GET', `/api/v0.1/ci_types/${ci.id}/template/export`, true)
|
||||
x.responseType = 'blob'
|
||||
x.onload = function (e) {
|
||||
x.onload = function(e) {
|
||||
const url = window.URL.createObjectURL(x.response)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
@@ -755,37 +844,50 @@ export default {
|
||||
router.addRoutes(store.getters.appRoutes)
|
||||
})
|
||||
},
|
||||
handleEdit(e, record) {
|
||||
async handleEdit(e, record) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
this.drawerTitle = this.$t('cmdb.ciType.editCIType')
|
||||
this.drawerVisible = true
|
||||
getCITypeAttributesById(record.id).then((res) => {
|
||||
await getCITypeAttributesById(record.id).then((res) => {
|
||||
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'
|
||||
|
||||
this.form.setFieldsValue({
|
||||
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,
|
||||
})
|
||||
await getCIType(record.id).then((res) => {
|
||||
const ci_type = res.ci_types[0]
|
||||
this.editCiType = ci_type ?? null
|
||||
if (ci_type.parent_ids && ci_type.parent_ids.length) {
|
||||
this.isInherit = true
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
parent_ids: ci_type.parent_ids,
|
||||
})
|
||||
})
|
||||
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] || '',
|
||||
}
|
||||
: {}
|
||||
)
|
||||
}
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
this.default_order_asc = record.default_order_attr && record.default_order_attr.startsWith('-') ? '2' : '1'
|
||||
|
||||
this.form.setFieldsValue({
|
||||
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() {
|
||||
|
@@ -20,7 +20,9 @@
|
||||
"
|
||||
>{{ $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>
|
||||
</template>
|
||||
<a-tabs v-model="activeKey">
|
||||
@@ -47,7 +49,7 @@
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
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 { valueTypeMap } from '../../utils/const'
|
||||
import AttributesTransfer from '../../components/attributesTransfer'
|
||||
@@ -102,11 +104,11 @@ export default {
|
||||
if (this.currentGroup) {
|
||||
await this.updateCurrentGroup()
|
||||
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) => {
|
||||
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.handleClose(isCloseModal)
|
||||
@@ -140,9 +142,9 @@ export default {
|
||||
if (this.currentGroup) {
|
||||
await this.updateCurrentGroup()
|
||||
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)
|
||||
await updateCITypeGroupById(id, { name, order, attributes: attrIds })
|
||||
await createCITypeGroupById(this.CITypeId, { name, order, attributes: attrIds })
|
||||
}
|
||||
this.confirmLoading = false
|
||||
this.loadTotalAttrs()
|
||||
|
@@ -79,7 +79,7 @@
|
||||
<vxe-column
|
||||
align="center"
|
||||
field="is_accept"
|
||||
:title="$t('cmdb.ad.isAccpet')"
|
||||
:title="$t('cmdb.ad.isAccept')"
|
||||
v-bind="columns.length ? { width: '100px' } : { minWidth: '100px' }"
|
||||
:filters="[
|
||||
{ label: $t('yes'), value: true },
|
||||
@@ -92,7 +92,7 @@
|
||||
</vxe-column>
|
||||
<vxe-column
|
||||
field="accept_by"
|
||||
:title="$t('cmdb.ad.accpetBy')"
|
||||
:title="$t('cmdb.ad.acceptBy')"
|
||||
v-bind="columns.length ? { width: '80px' } : { minWidth: '80px' }"
|
||||
:filters="[]"
|
||||
></vxe-column>
|
||||
@@ -186,8 +186,8 @@ export default {
|
||||
this.clickSidebar(Number(_currentType))
|
||||
return
|
||||
}
|
||||
if (res && res.length) {
|
||||
this.clickSidebar(res[0].id)
|
||||
if (res && res.length && res[0].ci_types && res[0].ci_types.length) {
|
||||
this.clickSidebar(res[0].ci_types[0].id)
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -246,7 +246,7 @@ export default {
|
||||
content: that.$t('cmdb.ad.confirmAccept'),
|
||||
onOk() {
|
||||
updateADCAccept(row.id).then(() => {
|
||||
that.$message.success(that.$t('cmdb.ad.accpetSuccess'))
|
||||
that.$message.success(that.$t('cmdb.ad.acceptSuccess'))
|
||||
that.getAdc(false)
|
||||
})
|
||||
},
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div :style="{ height: '100%' }">
|
||||
<vxe-table
|
||||
show-overflow
|
||||
show-header-overflow
|
||||
@@ -7,7 +7,7 @@
|
||||
size="small"
|
||||
class="ops-stripe-table"
|
||||
: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="type" :title="$t('type')">
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<span class="cmdb-preference-left-card-title">{{ $t('cmdb.preference.mySub') }}</span>
|
||||
<span
|
||||
class="cmdb-preference-left-card-content"
|
||||
><ops-icon type="cmdb-ci" :style="{ marginRight: '5px' }" />{{ $t('cmdb.menu.ciTable') }}:
|
||||
><ops-icon type="cmdb-ci" :style="{ marginRight: '5px' }"/>{{ $t('cmdb.menu.ciTable') }}:
|
||||
<a-badge
|
||||
showZero
|
||||
:count="self.instance.length"
|
||||
@@ -16,11 +16,10 @@
|
||||
height: '23px',
|
||||
fontSize: '14px',
|
||||
}"
|
||||
/></span
|
||||
>
|
||||
/></span>
|
||||
<span
|
||||
class="cmdb-preference-left-card-content"
|
||||
><ops-icon type="cmdb-tree" :style="{ marginRight: '5px' }" />{{ $t('cmdb.menu.ciTree') }}:
|
||||
><ops-icon type="cmdb-tree" :style="{ marginRight: '5px' }"/>{{ $t('cmdb.menu.ciTree') }}:
|
||||
<a-badge
|
||||
showZero
|
||||
:count="self.tree.length"
|
||||
@@ -31,57 +30,67 @@
|
||||
height: '23px',
|
||||
fontSize: '14px',
|
||||
}"
|
||||
/></span
|
||||
>
|
||||
/></span>
|
||||
</div>
|
||||
<div class="cmdb-preference-group" v-for="(group, index) in myPreferences" :key="group.name">
|
||||
<div class="cmdb-preference-group-title">
|
||||
<span> <ops-icon :style="{ marginRight: '10px' }" :type="group.icon" />{{ group.name }} </span>
|
||||
</div>
|
||||
<div class="cmdb-preference-group-content" v-for="ciType in group.ci_types" :key="ciType.id">
|
||||
<div
|
||||
:class="{
|
||||
'cmdb-preference-avatar': true,
|
||||
'cmdb-preference-avatar-noicon': !ciType.icon,
|
||||
'cmdb-preference-avatar-noicon-is_subscribed': !ciType.icon && ciType.is_subscribed,
|
||||
}"
|
||||
:style="{ width: '30px', height: '30px', marginRight: '10px' }"
|
||||
>
|
||||
<template v-if="ciType.icon">
|
||||
<img
|
||||
v-if="ciType.icon.split('$$')[2]"
|
||||
:src="`/api/common-setting/v1/file/${ciType.icon.split('$$')[3]}`"
|
||||
:style="{ maxHeight: '30px', maxWidth: '30px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{
|
||||
color: ciType.icon.split('$$')[1],
|
||||
fontSize: '14px',
|
||||
}"
|
||||
:type="ciType.icon.split('$$')[0]"
|
||||
/>
|
||||
</template>
|
||||
<span v-else :style="{ fontSize: '20px' }">{{ ciType.name[0].toUpperCase() }}</span>
|
||||
<draggable
|
||||
v-model="group.ci_types"
|
||||
:animation="300"
|
||||
@change="
|
||||
(e) => {
|
||||
orderChange(e, group)
|
||||
}
|
||||
"
|
||||
>
|
||||
<div class="cmdb-preference-group-content" v-for="ciType in group.ci_types" :key="ciType.id">
|
||||
<OpsMoveIcon class="cmdb-preference-move-icon" />
|
||||
<div
|
||||
:class="{
|
||||
'cmdb-preference-avatar': true,
|
||||
'cmdb-preference-avatar-noicon': !ciType.icon,
|
||||
'cmdb-preference-avatar-noicon-is_subscribed': !ciType.icon && ciType.is_subscribed,
|
||||
}"
|
||||
:style="{ width: '30px', height: '30px', marginRight: '10px' }"
|
||||
>
|
||||
<template v-if="ciType.icon">
|
||||
<img
|
||||
v-if="ciType.icon.split('$$')[2]"
|
||||
:src="`/api/common-setting/v1/file/${ciType.icon.split('$$')[3]}`"
|
||||
:style="{ maxHeight: '30px', maxWidth: '30px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{
|
||||
color: ciType.icon.split('$$')[1],
|
||||
fontSize: '14px',
|
||||
}"
|
||||
:type="ciType.icon.split('$$')[0]"
|
||||
/>
|
||||
</template>
|
||||
<span v-else :style="{ fontSize: '20px' }">{{ ciType.name[0].toUpperCase() }}</span>
|
||||
</div>
|
||||
<span class="cmdb-preference-group-content-title">{{ ciType.alias || ciType.name }}</span>
|
||||
<span class="cmdb-preference-group-content-action">
|
||||
<a-tooltip :title="$t('cmdb.preference.cancelSub')">
|
||||
<span
|
||||
@click="unsubscribe(ciType, group.type)"
|
||||
><ops-icon type="cmdb-preference-cancel-subscribe" />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-divider type="vertical" :style="{ margin: '0 3px' }" />
|
||||
<a-tooltip :title="$t('cmdb.preference.editSub')">
|
||||
<span
|
||||
@click="openSubscribeSetting(ciType, `${index + 1}`)"
|
||||
><ops-icon
|
||||
type="cmdb-preference-subscribe"
|
||||
/></span>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
<span class="cmdb-preference-group-content-title">{{ ciType.alias || ciType.name }}</span>
|
||||
<span class="cmdb-preference-group-content-action">
|
||||
<a-tooltip :title="$t('cmdb.preference.cancelSub')">
|
||||
<span
|
||||
@click="unsubscribe(ciType, group.type)"
|
||||
><ops-icon type="cmdb-preference-cancel-subscribe" />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-divider type="vertical" :style="{ margin: '0 3px' }" />
|
||||
<a-tooltip :title="$t('cmdb.preference.editSub')">
|
||||
<span
|
||||
@click="openSubscribeSetting(ciType, `${index + 1}`)"
|
||||
><ops-icon
|
||||
type="cmdb-preference-subscribe"
|
||||
/></span>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cmdb-preference-right">
|
||||
@@ -124,19 +133,12 @@
|
||||
>
|
||||
</div>
|
||||
<div class="cmdb-preference-colleague">
|
||||
<template v-if="type_id2users[item.id] && type_id2users[item.id].length">
|
||||
<span
|
||||
>{{ type_id2users[item.id].length > 99 ? '99+' : type_id2users[item.id].length
|
||||
}}{{ $t('cmdb.preference.peopleSub') }}</span
|
||||
>
|
||||
<span class="cmdb-preference-colleague-name">
|
||||
<span v-for="uid in type_id2users[item.id].slice(0, 4)" :key="uid">
|
||||
{{ getNameByUid(uid) }}
|
||||
</span>
|
||||
<span class="cmdb-preference-colleague-ellipsis" v-if="type_id2users[item.id].length > 4">...</span>
|
||||
</span>
|
||||
</template>
|
||||
<span v-else :style="{ marginLeft: 'auto' }">{{ $t('cmdb.preference.noSub') }}</span>
|
||||
<span
|
||||
v-if="type_id2users[item.id] && type_id2users[item.id].length"
|
||||
>{{ type_id2users[item.id].length > 99 ? '99+' : type_id2users[item.id].length
|
||||
}}{{ $t('cmdb.preference.peopleSub') }}</span
|
||||
>
|
||||
<span v-else>{{ $t('cmdb.preference.noSub') }}</span>
|
||||
</div>
|
||||
<div class="cmdb-preference-progress">
|
||||
<div class="cmdb-preference-progress-info">
|
||||
@@ -190,15 +192,23 @@ import router, { resetRouter } from '@/router'
|
||||
import store from '@/store'
|
||||
import { mapState } from 'vuex'
|
||||
import moment from 'moment'
|
||||
import draggable from 'vuedraggable'
|
||||
import { getCITypeGroups } from '../../api/ciTypeGroup'
|
||||
import { getPreference, getPreference2, subscribeCIType, subscribeTreeView } from '@/modules/cmdb/api/preference'
|
||||
import {
|
||||
getPreference,
|
||||
getPreference2,
|
||||
subscribeCIType,
|
||||
subscribeTreeView,
|
||||
preferenceCitypeOrder,
|
||||
} from '@/modules/cmdb/api/preference'
|
||||
import CollapseTransition from '@/components/CollapseTransition'
|
||||
import SubscribeSetting from '../../components/subscribeSetting/subscribeSetting'
|
||||
import { getCIAdcStatistics } from '../../api/ci'
|
||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||
|
||||
export default {
|
||||
name: 'Preference',
|
||||
components: { CollapseTransition, SubscribeSetting },
|
||||
components: { CollapseTransition, SubscribeSetting, draggable, OpsMoveIcon },
|
||||
data() {
|
||||
return {
|
||||
citypeData: [],
|
||||
@@ -214,7 +224,6 @@ export default {
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
allUsers: (state) => state.user.allUsers,
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
@@ -277,10 +286,6 @@ export default {
|
||||
}, 300)
|
||||
}
|
||||
},
|
||||
getNameByUid(uid) {
|
||||
const _find = this.allUsers.find((item) => item.uid === uid)
|
||||
return _find?.username[0].toUpperCase() || 'A'
|
||||
},
|
||||
getsubscribedDays(item) {
|
||||
const subscribedTime = this.self.type_id2subs_time[item.id]
|
||||
moment.duration(moment().diff(moment(subscribedTime)))
|
||||
@@ -351,6 +356,17 @@ export default {
|
||||
this.expandKeys.push(group.id)
|
||||
}
|
||||
},
|
||||
orderChange(e, group) {
|
||||
preferenceCitypeOrder({ type_ids: group.ci_types.map((type) => type.id), is_tree: group.type !== 'ci' })
|
||||
.then(() => {
|
||||
if (group.type === 'ci') {
|
||||
this.resetRoute()
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.getCITypes(false)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -426,7 +442,7 @@ export default {
|
||||
align-items: center;
|
||||
height: 45px;
|
||||
padding: 0 8px;
|
||||
cursor: default;
|
||||
cursor: move;
|
||||
justify-content: flex-start;
|
||||
&:hover {
|
||||
background: #ffffff;
|
||||
@@ -437,6 +453,15 @@ export default {
|
||||
white-space: nowrap;
|
||||
margin-left: auto;
|
||||
}
|
||||
.cmdb-preference-move-icon {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.cmdb-preference-move-icon {
|
||||
width: 14px;
|
||||
height: 20px;
|
||||
cursor: move;
|
||||
visibility: hidden;
|
||||
}
|
||||
.cmdb-preference-group-content-title {
|
||||
flex: 1;
|
||||
|
@@ -58,13 +58,9 @@
|
||||
/>
|
||||
<div class="relation-views-right-bar">
|
||||
<a-space>
|
||||
<a-button
|
||||
v-if="isLeaf"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="$refs.create.handleOpen(true, 'create')"
|
||||
>{{ $t('create') }}</a-button
|
||||
>
|
||||
<a-button v-if="isLeaf" type="primary" size="small" @click="$refs.create.handleOpen(true, 'create')">{{
|
||||
$t('create')
|
||||
}}</a-button>
|
||||
|
||||
<div class="ops-list-batch-action" v-if="isLeaf && isShowBatchIcon">
|
||||
<template v-if="selectedRowKeys.length">
|
||||
@@ -135,7 +131,7 @@
|
||||
{{ col.title }}</span
|
||||
>
|
||||
</template>
|
||||
<template v-if="col.is_choice || col.is_password || col.is_list" #edit="{ row }">
|
||||
<template v-if="col.is_choice || col.is_password" #edit="{ row }">
|
||||
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
|
||||
<a-select
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
@@ -172,18 +168,6 @@
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
:style="{ width: '100%', height: '32px' }"
|
||||
v-model="row[col.field]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
v-else-if="col.is_list"
|
||||
:showArrow="false"
|
||||
mode="tags"
|
||||
class="ci-table-edit-select"
|
||||
allowClear
|
||||
>
|
||||
</a-select>
|
||||
</template>
|
||||
<template
|
||||
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
|
||||
@@ -332,7 +316,7 @@
|
||||
<!-- <GrantDrawer ref="grantDrawer" resourceTypeName="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
|
||||
ref="create"
|
||||
:typeIdFromRelation="Number(currentTypeId[0])"
|
||||
@@ -364,13 +348,13 @@ import {
|
||||
} from '@/modules/cmdb/api/CIRelation'
|
||||
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { searchCI2, updateCI, deleteCI, searchCI } from '@/modules/cmdb/api/ci'
|
||||
import { searchCI2, updateCI, deleteCI } from '@/modules/cmdb/api/ci'
|
||||
import { getCITypes } from '../../api/CIType'
|
||||
import { roleHasPermissionToGrant } from '@/modules/acl/api/permission'
|
||||
import { searchResourceType } from '@/modules/acl/api/resource'
|
||||
import SplitPane from '@/components/SplitPane'
|
||||
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 JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
|
||||
import BatchDownload from '../../components/batchDownload/batchDownload.vue'
|
||||
@@ -391,7 +375,7 @@ export default {
|
||||
SplitPane,
|
||||
ElTree: Tree,
|
||||
EditAttrsPopover,
|
||||
CiDetail,
|
||||
CiDetailDrawer,
|
||||
CreateInstanceForm,
|
||||
JsonEditor,
|
||||
BatchDownload,
|
||||
@@ -1018,11 +1002,7 @@ export default {
|
||||
|
||||
this.$confirm({
|
||||
title: that.$t('warning'),
|
||||
content: (h) => (
|
||||
<div>
|
||||
{that.$t('confirmDelete2', { name: Object.values(firstCIObj)[0] })}
|
||||
</div>
|
||||
),
|
||||
content: (h) => <div>{that.$t('confirmDelete2', { name: Object.values(firstCIObj)[0] })}</div>,
|
||||
onOk() {
|
||||
deleteCIRelationView(_tempTreeParent[0], _tempTree[0], { ancestor_ids }).then((res) => {
|
||||
that.$message.success(that.$t('deleteSuccess'))
|
||||
@@ -1048,7 +1028,7 @@ export default {
|
||||
title: that.$t('warning'),
|
||||
content: (h) => (
|
||||
<div>
|
||||
{that.$t('cmdb.serviceTreedeleteRelationConfirm', { name: currentShowType.alias || currentShowType.name })}
|
||||
{that.$t('cmdb.serviceTree.deleteRelationConfirm', { name: currentShowType.alias || currentShowType.name })}
|
||||
</div>
|
||||
),
|
||||
onOk() {
|
||||
|
@@ -206,7 +206,7 @@
|
||||
import _ from 'lodash'
|
||||
import SearchForm from '../../components/searchForm/SearchForm.vue'
|
||||
import { searchCI } from '../../api/ci'
|
||||
import { searchAttributes, getCITypeAttributesByTypeIds } from '../../api/CITypeAttr'
|
||||
import { searchAttributes, getCITypeAttributesByTypeIds, getCITypeAttributesById } from '../../api/CITypeAttr'
|
||||
import { getCITypes } from '../../api/CIType'
|
||||
import { getSubscribeAttributes } from '../../api/preference'
|
||||
import { getCITableColumns } from '../../utils/helper'
|
||||
@@ -315,61 +315,59 @@ export default {
|
||||
this.instanceList = []
|
||||
this.totalNumber = res['numfound']
|
||||
|
||||
const oldData = res.result
|
||||
|
||||
function allKeys(data) {
|
||||
const keys = {}
|
||||
const ignoreAttr = ['_id', '_type', 'ci_type', 'ci_type_alias', 'unique', 'unique_alias']
|
||||
data.forEach((item) => {
|
||||
Object.keys(item).forEach((key) => {
|
||||
if (!ignoreAttr.includes(key)) {
|
||||
keys[key] = ''
|
||||
}
|
||||
})
|
||||
const { attributes: resAllAttributes } = await getCITypeAttributesByTypeIds({
|
||||
type_ids: Object.keys(res.counter).join(','),
|
||||
})
|
||||
const _columnsGroup = Object.keys(res.counter).map((key) => {
|
||||
const _find = this.ciTypes.find((item) => item.name === key)
|
||||
return {
|
||||
id: `parent-${_find.id}`,
|
||||
value: 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 = allKeys(data)
|
||||
const common = {}
|
||||
data.forEach((item) => {
|
||||
const tmp = {}
|
||||
Object.keys(outputKeys).forEach((j) => {
|
||||
if (j in item) {
|
||||
tmp[j] = item[j]
|
||||
// 提取common
|
||||
{
|
||||
const key = item['ci_type_alias']
|
||||
if (j in common) {
|
||||
common[j][[key]] = ''
|
||||
} else {
|
||||
common[j] = { [key]: '' }
|
||||
}
|
||||
}
|
||||
const outputKeys = {}
|
||||
resAllAttributes.forEach((attr) => {
|
||||
outputKeys[attr.name] = ''
|
||||
})
|
||||
|
||||
const common = {}
|
||||
Object.keys(outputKeys).forEach((key) => {
|
||||
Object.entries(ciTypeAttribute).forEach(([type, attrs]) => {
|
||||
if (attrs.find((a) => a.name === key)) {
|
||||
if (key in common) {
|
||||
common[key][type] = ''
|
||||
} else {
|
||||
tmp[j] = null
|
||||
}
|
||||
})
|
||||
})
|
||||
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)
|
||||
common[key] = { [type]: '' }
|
||||
}
|
||||
}
|
||||
})
|
||||
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) => {
|
||||
return {
|
||||
id: `parent-${key}`,
|
||||
@@ -385,24 +383,14 @@ export default {
|
||||
}
|
||||
})
|
||||
|
||||
const _columnsGroup = Object.keys(res.counter).map((key) => {
|
||||
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) => {
|
||||
const promises1 = _columnsGroup.map((item) => {
|
||||
return getSubscribeAttributes(item.id.split('-')[1]).then((res1) => {
|
||||
item.children = this.getColumns(res.result, res1.attributes).filter(
|
||||
(col) => !commonKeys.includes(col.field)
|
||||
)
|
||||
})
|
||||
})
|
||||
await Promise.all(promises).then(() => {
|
||||
await Promise.all(promises1).then(() => {
|
||||
this.columnsGroup = [..._commonColumnsGroup, ..._columnsGroup]
|
||||
this.instanceList = res['result']
|
||||
})
|
||||
|
@@ -32,32 +32,24 @@
|
||||
>
|
||||
<template #one>
|
||||
<div class="tree-views-left" :style="{ height: `${windowHeight - 115}px` }">
|
||||
<a-collapse
|
||||
:activeKey="current"
|
||||
accordion
|
||||
@change="handleChangeCi"
|
||||
:bordered="false"
|
||||
:destroyInactivePanel="true"
|
||||
<draggable
|
||||
v-model="subscribeTreeViewCiTypes"
|
||||
:animation="300"
|
||||
@change="
|
||||
(e) => {
|
||||
orderChange(e, subscribeTreeViewCiTypes)
|
||||
}
|
||||
"
|
||||
>
|
||||
<a-collapse-panel
|
||||
v-for="ciType in subscribeTreeViewCiTypes"
|
||||
:key="String(ciType.type_id)"
|
||||
:showArrow="false"
|
||||
:style="{
|
||||
borderRadius: '4px',
|
||||
marginBottom: '5px',
|
||||
border: 0,
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
}"
|
||||
>
|
||||
<div v-for="ciType in subscribeTreeViewCiTypes" :key="ciType.type_id">
|
||||
<div
|
||||
slot="header"
|
||||
@click="handleChangeCi(ciType.type_id)"
|
||||
:class="{
|
||||
'custom-header': true,
|
||||
'custom-header-selected': Number(ciType.type_id) === Number(typeId),
|
||||
'custom-header-selected': Number(ciType.type_id) === Number(typeId) && !treeKeys.length,
|
||||
}"
|
||||
>
|
||||
<OpsMoveIcon class="move-icon" />
|
||||
<span class="tree-views-left-header-icon">
|
||||
<template v-if="ciType.icon">
|
||||
<img
|
||||
@@ -95,6 +87,7 @@
|
||||
:tree-data="treeData"
|
||||
:load-data="onLoadData"
|
||||
:expandedKeys="expandedKeys"
|
||||
v-if="Number(ciType.type_id) === Number(typeId)"
|
||||
>
|
||||
<a-icon slot="switcherIcon" type="down" />
|
||||
<template #title="{ key: treeKey, title, isLeaf }">
|
||||
@@ -107,8 +100,8 @@
|
||||
/>
|
||||
</template>
|
||||
</a-tree>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
</template>
|
||||
<template #two>
|
||||
@@ -189,7 +182,7 @@
|
||||
{{ col.title }}</span
|
||||
>
|
||||
</template>
|
||||
<template v-if="col.is_choice || col.is_password || col.is_list" #edit="{ row }">
|
||||
<template v-if="col.is_choice || col.is_password" #edit="{ row }">
|
||||
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
|
||||
<a-select
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
@@ -226,18 +219,6 @@
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
:style="{ width: '100%', height: '32px' }"
|
||||
v-model="row[col.field]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
v-else-if="col.is_list"
|
||||
:showArrow="false"
|
||||
mode="tags"
|
||||
class="ci-table-edit-select"
|
||||
allowClear
|
||||
>
|
||||
</a-select>
|
||||
</template>
|
||||
<template
|
||||
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
|
||||
@@ -390,7 +371,7 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
<ci-detail ref="detail" :typeId="Number(typeId)" :treeViewsLevels="treeViewsLevels" />
|
||||
<CiDetailDrawer ref="detail" :typeId="Number(typeId)" :treeViewsLevels="treeViewsLevels" />
|
||||
<create-instance-form
|
||||
ref="create"
|
||||
:typeIdFromRelation="Number(typeId)"
|
||||
@@ -407,7 +388,13 @@
|
||||
/* eslint-disable no-useless-escape */
|
||||
import _ from 'lodash'
|
||||
import Sortable from 'sortablejs'
|
||||
import { getSubscribeTreeView, getSubscribeAttributes, subscribeTreeView } from '@/modules/cmdb/api/preference'
|
||||
import draggable from 'vuedraggable'
|
||||
import {
|
||||
getSubscribeTreeView,
|
||||
getSubscribeAttributes,
|
||||
subscribeTreeView,
|
||||
preferenceCitypeOrder,
|
||||
} from '@/modules/cmdb/api/preference'
|
||||
import { searchCI, updateCI, deleteCI } from '@/modules/cmdb/api/ci'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getCITableColumns } from '../../utils/helper'
|
||||
@@ -417,7 +404,7 @@ import PasswordField from '../../components/passwordField/index.vue'
|
||||
import SplitPane from '@/components/SplitPane'
|
||||
import TreeViewsNode from './modules/treeViewsNode.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 { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
|
||||
@@ -437,13 +424,14 @@ export default {
|
||||
SplitPane,
|
||||
TreeViewsNode,
|
||||
EditAttrsPopover,
|
||||
CiDetail,
|
||||
CiDetailDrawer,
|
||||
CreateInstanceForm,
|
||||
JsonEditor,
|
||||
BatchDownload,
|
||||
PreferenceSearch,
|
||||
MetadataDrawer,
|
||||
OpsMoveIcon,
|
||||
draggable,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -463,7 +451,6 @@ export default {
|
||||
pageSize: 50,
|
||||
currentPage: 1,
|
||||
totalNumber: 0,
|
||||
current: '', // 当前页面的type_id
|
||||
currentAttrList: [],
|
||||
trigger: false,
|
||||
newLoad: true,
|
||||
@@ -571,7 +558,6 @@ export default {
|
||||
this.subscribeTreeViewCiTypes = res
|
||||
if (this.subscribeTreeViewCiTypes.length) {
|
||||
this.typeId = this.$route.params.typeId || this.subscribeTreeViewCiTypes[0].type_id
|
||||
this.current = `${this.typeId}`
|
||||
this.selectedRowKeys = []
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
@@ -600,7 +586,6 @@ export default {
|
||||
async loadCurrentView() {
|
||||
if (this.subscribeTreeViewCiTypes.length) {
|
||||
this.typeId = this.$route.params.typeId || this.subscribeTreeViewCiTypes[0].type_id
|
||||
this.current = String(this.typeId)
|
||||
this.selectedRowKeys = []
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
@@ -746,9 +731,14 @@ export default {
|
||||
name: 'cmdb_tree_views_item',
|
||||
params: { typeId: Number(value) },
|
||||
})
|
||||
this.typeId = Number(value)
|
||||
} else {
|
||||
this.newLoad = true
|
||||
this.initPage()
|
||||
this.typeId = null
|
||||
this.$nextTick(() => {
|
||||
this.typeId = Number(value)
|
||||
this.newLoad = true
|
||||
this.initPage()
|
||||
})
|
||||
}
|
||||
this.isSetDataNodes = []
|
||||
},
|
||||
@@ -1136,12 +1126,22 @@ export default {
|
||||
}
|
||||
})
|
||||
this.$refs.create.visible = false
|
||||
const key = 'updatable'
|
||||
let errorMsg = ''
|
||||
for (let i = 0; i < this.selectedRowKeys.length; i++) {
|
||||
await updateCI(this.selectedRowKeys[i], payload, false)
|
||||
.then(() => {
|
||||
successNum += 1
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((error) => {
|
||||
errorMsg = errorMsg + '\n' + `${this.selectedRowKeys[i]}:${error.response?.data?.message ?? ''}`
|
||||
this.$notification.warning({
|
||||
key,
|
||||
message: this.$t('warning'),
|
||||
description: errorMsg,
|
||||
duration: 0,
|
||||
style: { whiteSpace: 'break-spaces' },
|
||||
})
|
||||
errorNum += 1
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -1209,6 +1209,13 @@ export default {
|
||||
this.$message.error(this.$t('cmdb.ci.copyFailed'))
|
||||
})
|
||||
},
|
||||
orderChange(e, subscribeTreeViewCiTypes) {
|
||||
preferenceCitypeOrder({ type_ids: subscribeTreeViewCiTypes.map((type) => type.type_id), is_tree: true }).catch(
|
||||
() => {
|
||||
this.getTreeViews()
|
||||
}
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1230,65 +1237,68 @@ export default {
|
||||
&:hover {
|
||||
overflow: auto;
|
||||
}
|
||||
.ant-collapse-borderless {
|
||||
background-color: #fff;
|
||||
}
|
||||
.ant-collapse-item:has(.custom-header-selected):not(:has(.ant-tree-treenode-selected)) > .ant-collapse-header,
|
||||
.ant-collapse-item-active:not(:has(.ant-tree-treenode-selected)) > .ant-collapse-header {
|
||||
background-color: #d6e4ff;
|
||||
}
|
||||
.ant-collapse-header {
|
||||
padding: 8px 12px 4px;
|
||||
.custom-header {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 8px 0 8px 12px;
|
||||
cursor: move;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
&:hover {
|
||||
background-color: #f0f5ff;
|
||||
> .actions,
|
||||
> .move-icon {
|
||||
display: inherit;
|
||||
}
|
||||
}
|
||||
&:hover > .custom-header > .actions {
|
||||
display: inherit;
|
||||
.move-icon {
|
||||
width: 14px;
|
||||
height: 20px;
|
||||
cursor: move;
|
||||
position: absolute;
|
||||
display: none;
|
||||
left: 0;
|
||||
}
|
||||
.custom-header {
|
||||
width: 100%;
|
||||
.tree-views-left-header-icon {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
.tree-views-left-header-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
|
||||
margin-right: 6px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.tree-views-left-header-name {
|
||||
flex: 1;
|
||||
font-weight: bold;
|
||||
margin-left: 5px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.actions {
|
||||
display: none;
|
||||
margin-left: auto;
|
||||
}
|
||||
.action {
|
||||
display: inline-block;
|
||||
width: 22px;
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
&:hover {
|
||||
background-color: #cacaca;
|
||||
}
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
|
||||
margin-right: 6px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.tree-views-left-header-name {
|
||||
flex: 1;
|
||||
font-weight: bold;
|
||||
margin-left: 5px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.actions {
|
||||
display: none;
|
||||
margin-left: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
.action {
|
||||
display: inline-block;
|
||||
width: 22px;
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
&:hover {
|
||||
background-color: #cacaca;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-collapse > .ant-collapse-item > .ant-collapse-header {
|
||||
white-space: nowrap;
|
||||
.custom-header-selected {
|
||||
background-color: #d3e3fd !important;
|
||||
}
|
||||
.ant-tree li {
|
||||
padding: 2px 0;
|
||||
|
@@ -12,13 +12,13 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(47, 122, 235, 0.2);
|
||||
background-color: @scrollbar-color;
|
||||
background-clip: padding-box;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(47, 122, 235, 0.2);
|
||||
background-color: @scrollbar-color;
|
||||
}
|
||||
|
||||
@import '~ant-design-vue/dist/antd.less';
|
||||
@@ -35,7 +35,7 @@ body {
|
||||
}
|
||||
|
||||
.ant-layout {
|
||||
background-color: #f0f5ff;
|
||||
background-color: #custom_colors() [color_2];
|
||||
}
|
||||
|
||||
.layout.ant-layout {
|
||||
@@ -99,12 +99,8 @@ body {
|
||||
height: @layout-header-icon-height;
|
||||
line-height: @layout-header-icon-height;
|
||||
display: inline-flex;
|
||||
align-items: flex-end;
|
||||
align-items: center;
|
||||
margin-right: 10px;
|
||||
&:hover {
|
||||
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
|
||||
color: @layout-header-font-selected-color;
|
||||
}
|
||||
}
|
||||
|
||||
.topmenu {
|
||||
@@ -192,12 +188,6 @@ body {
|
||||
color: @layout-header-font-color;
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
|
||||
color: @layout-header-font-selected-color;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-right: 5px;
|
||||
color: @layout-header-font-color;
|
||||
@@ -349,8 +339,6 @@ body {
|
||||
}
|
||||
|
||||
&.light {
|
||||
background-color: #225686;
|
||||
|
||||
.header-index-wide {
|
||||
.header-index-left {
|
||||
.logo {
|
||||
@@ -425,7 +413,6 @@ body {
|
||||
min-height: 100vh;
|
||||
|
||||
.ant-layout-sider-children {
|
||||
background: #225686; //浅色系左边菜单栏 深色系需删除
|
||||
overflow-y: hidden;
|
||||
> .ant-menu {
|
||||
height: calc(100vh - 40px);
|
||||
@@ -519,7 +506,7 @@ body {
|
||||
.ops-side-bar.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
|
||||
// background-image: url('../assets/sidebar_selected.png') !important;
|
||||
// background-repeat: no-repeat !important;
|
||||
background: #3e4a71;
|
||||
background: @layout-sidebar-selected-color;
|
||||
// background-size: 228px 38px;
|
||||
// background-position-x: -10px;
|
||||
// background-position-y: center;
|
||||
@@ -546,13 +533,13 @@ body {
|
||||
border-right-color: transparent;
|
||||
// background: @layout-background-light-color;
|
||||
// background: url('../assets/sidebar_background.png');
|
||||
background: #1d264c;
|
||||
background: @layout-sidebar-color;
|
||||
// background-position-x: center;
|
||||
// background-position-y: center;
|
||||
background-repeat: no-repeat !important;
|
||||
background-size: cover;
|
||||
.ant-menu-inline.ant-menu-sub {
|
||||
background-color: #000c37;
|
||||
background-color: @layout-sidebar-sub-color;
|
||||
}
|
||||
.ant-menu-submenu-content .ant-menu-item,
|
||||
.ant-menu-item {
|
||||
@@ -560,7 +547,7 @@ body {
|
||||
> a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
color: @layout-sidebar-font-color;
|
||||
}
|
||||
&:hover {
|
||||
// background: #0000000a;
|
||||
@@ -578,7 +565,7 @@ body {
|
||||
white-space: nowrap;
|
||||
}
|
||||
a:hover {
|
||||
color: #fff;
|
||||
color: @layout-sidebar-font-color;
|
||||
font-weight: 600;
|
||||
}
|
||||
&:hover .custom-menu-extra-ellipsis {
|
||||
@@ -614,7 +601,7 @@ body {
|
||||
.ant-menu-item-selected {
|
||||
a,
|
||||
a:hover {
|
||||
color: #fff;
|
||||
color: @layout-sidebar-font-color;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
@@ -624,20 +611,20 @@ body {
|
||||
}
|
||||
|
||||
.ant-menu-submenu {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
color: @layout-sidebar-font-color;
|
||||
}
|
||||
.ant-menu-submenu-title:hover {
|
||||
color: #fff;
|
||||
background: #0000000a;
|
||||
color: @layout-sidebar-font-color;
|
||||
background: @layout-sidebar-selected-color;
|
||||
font-weight: 600;
|
||||
.ant-menu-submenu-arrow::before,
|
||||
.ant-menu-submenu-arrow::after {
|
||||
background: #fff;
|
||||
background-image: linear-gradient(to right, #2e2e2e, #2e2e2e);
|
||||
background: @layout-sidebar-arrow-color;
|
||||
}
|
||||
}
|
||||
.ant-menu-submenu-selected {
|
||||
> .ant-menu-submenu-title {
|
||||
color: #fff;
|
||||
color: @layout-sidebar-font-color;
|
||||
font-weight: 800;
|
||||
}
|
||||
}
|
||||
@@ -650,7 +637,7 @@ body {
|
||||
padding-left: 0 !important;
|
||||
> a {
|
||||
padding-left: 10px;
|
||||
color: rgba(255, 255, 255, 0.6) !important;
|
||||
color: @layout-sidebar-disabled-font-color !important;
|
||||
font-size: 12px;
|
||||
}
|
||||
&:hover {
|
||||
@@ -659,8 +646,11 @@ body {
|
||||
}
|
||||
.ant-menu-submenu-arrow::after,
|
||||
.ant-menu-submenu-arrow::before {
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
background-image: linear-gradient(to right, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.6)) !important;
|
||||
background: @layout-sidebar-arrow-color;
|
||||
}
|
||||
|
||||
.ant-menu-item.ant-menu-item-active:hover {
|
||||
background: @layout-sidebar-selected-color;
|
||||
}
|
||||
}
|
||||
// 侧边栏折叠时
|
||||
@@ -668,7 +658,7 @@ body {
|
||||
.ant-menu-submenu.ant-menu-submenu-placement-rightTop {
|
||||
> .ant-menu {
|
||||
// background: url('../assets/sidebar_background.png');
|
||||
background: #1d264c;
|
||||
background: @layout-sidebar-color;
|
||||
background-position-x: center;
|
||||
background-position-y: center;
|
||||
background-repeat: no-repeat !important;
|
||||
@@ -830,14 +820,21 @@ body {
|
||||
.el-input__inner {
|
||||
border-radius: 2px !important;
|
||||
}
|
||||
.el-input__inner:hover {
|
||||
border-color: #4596de !important;
|
||||
|
||||
.el-input__inner:hover,
|
||||
.el-select .el-input.is-focus .el-input__inner,
|
||||
.el-select .el-input__inner:focus,
|
||||
.el-button.is-plain:focus,
|
||||
.el-button.is-plain:hover,
|
||||
.el-input.is-active .el-input__inner,
|
||||
.el-input__inner:focus {
|
||||
border-color: #custom_colors() [color_1] !important;
|
||||
}
|
||||
.el-select .el-input.is-focus .el-input__inner {
|
||||
border-color: #4596de !important;
|
||||
}
|
||||
.el-select-dropdown__item.selected {
|
||||
color: #4596de !important;
|
||||
.el-button--text,
|
||||
.el-select-dropdown__item.selected,
|
||||
.el-button.is-plain:focus,
|
||||
.el-button.is-plain:hover {
|
||||
color: #custom_colors() [color_1] !important;
|
||||
}
|
||||
|
||||
.ant-tabs-nav .ant-tabs-tab {
|
||||
@@ -897,7 +894,7 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.custom-vue-treeselect__control(@bgColor:#f0f5ff,@border:none) {
|
||||
.custom-vue-treeselect__control(@bgColor:#custom_colors()[color_2],@border:none) {
|
||||
background-color: @bgColor;
|
||||
border: @border;
|
||||
}
|
||||
@@ -938,29 +935,29 @@ body {
|
||||
.vue-treeselect__option--highlight,
|
||||
.vue-treeselect__option--selected {
|
||||
color: #custom_colors[color_1];
|
||||
background-color: #f0f5ff !important;
|
||||
background-color: #custom_colors() [color_2] !important;
|
||||
}
|
||||
.vue-treeselect__checkbox--checked,
|
||||
.vue-treeselect__checkbox--indeterminate {
|
||||
border-color: #2f54eb !important;
|
||||
background: #2f54eb !important;
|
||||
border-color: #custom_colors() [color_1] !important;
|
||||
background: #custom_colors() [color_1] !important;
|
||||
}
|
||||
.vue-treeselect__label-container:hover {
|
||||
.vue-treeselect__checkbox--checked,
|
||||
.vue-treeselect__checkbox--indeterminate {
|
||||
border-color: #2f54eb !important;
|
||||
background: #2f54eb !important;
|
||||
border-color: #custom_colors() [color_1] !important;
|
||||
background: #custom_colors() [color_1] !important;
|
||||
}
|
||||
}
|
||||
.vue-treeselect__multi-value-item {
|
||||
background: #f0f5ff !important;
|
||||
color: #2f54eb !important;
|
||||
background: #custom_colors() [color_2] !important;
|
||||
color: #custom_colors() [color_1] !important;
|
||||
}
|
||||
.vue-treeselect__value-remove {
|
||||
color: #2f54eb !important;
|
||||
color: #custom_colors() [color_1] !important;
|
||||
}
|
||||
.vue-treeselect__label-container:hover .vue-treeselect__checkbox--unchecked {
|
||||
border-color: #2f54eb !important;
|
||||
border-color: #custom_colors() [color_1] !important;
|
||||
}
|
||||
|
||||
//表格样式
|
||||
@@ -970,7 +967,7 @@ body {
|
||||
border: none !important;
|
||||
}
|
||||
.vxe-table--header-wrapper {
|
||||
background-color: #f0f5ff !important;
|
||||
background-color: #custom_colors() [color_2] !important;
|
||||
}
|
||||
.vxe-header--row .vxe-header--column:hover {
|
||||
background: #2f54eb1f !important;
|
||||
@@ -994,7 +991,7 @@ body {
|
||||
border: none !important;
|
||||
}
|
||||
.vxe-table--header-wrapper {
|
||||
background-color: #f0f5ff !important;
|
||||
background-color: #custom_colors() [color_2] !important;
|
||||
}
|
||||
// .vxe-table--header-wrapper.body--wrapper {
|
||||
// border-radius: 8px !important;
|
||||
@@ -1088,8 +1085,34 @@ body {
|
||||
.vxe-table--render-default .vxe-cell--checkbox:not(.is--disabled):hover .vxe-checkbox--icon,
|
||||
.is--filter-active .vxe-cell--filter .vxe-filter--btn,
|
||||
.vxe-table .vxe-sort--asc-btn.sort--active,
|
||||
.vxe-table .vxe-sort--desc-btn.sort--active {
|
||||
color: #2f54eb !important;
|
||||
.vxe-table .vxe-sort--desc-btn.sort--active,
|
||||
.vxe-select-option.is--selected,
|
||||
.vxe-loading > .vxe-loading--chunk,
|
||||
.vxe-loading > .vxe-loading--warpper,
|
||||
.vxe-pager .vxe-pager--jump-next:not(.is--disabled).is--active,
|
||||
.vxe-pager .vxe-pager--jump-next:not(.is--disabled):focus,
|
||||
.vxe-pager .vxe-pager--jump-prev:not(.is--disabled).is--active,
|
||||
.vxe-pager .vxe-pager--jump-prev:not(.is--disabled):focus,
|
||||
.vxe-pager .vxe-pager--next-btn:not(.is--disabled).is--active,
|
||||
.vxe-pager .vxe-pager--next-btn:not(.is--disabled):focus,
|
||||
.vxe-pager .vxe-pager--num-btn:not(.is--disabled).is--active,
|
||||
.vxe-pager .vxe-pager--num-btn:not(.is--disabled):focus,
|
||||
.vxe-pager .vxe-pager--prev-btn:not(.is--disabled).is--active,
|
||||
.vxe-pager .vxe-pager--prev-btn:not(.is--disabled):focus,
|
||||
.vxe-button.type--text:not(.is--disabled):hover,
|
||||
.vxe-table--filter-footer > button:hover {
|
||||
color: #custom_colors() [color_1] !important;
|
||||
}
|
||||
|
||||
.vxe-cell .vxe-default-input:focus,
|
||||
.vxe-cell .vxe-default-select:focus,
|
||||
.vxe-cell .vxe-default-textarea:focus,
|
||||
.vxe-table--filter-wrapper .vxe-default-input:focus,
|
||||
.vxe-table--filter-wrapper .vxe-default-select:focus,
|
||||
.vxe-table--filter-wrapper .vxe-default-textarea:focus,
|
||||
.vxe-select.is--active:not(.is--filter) > .vxe-input .vxe-input--inner,
|
||||
.vxe-input:not(.is--disabled).is--active .vxe-input--inner {
|
||||
border-color: #custom_colors() [color_1] !important;
|
||||
}
|
||||
|
||||
//批量操作
|
||||
@@ -1107,7 +1130,7 @@ body {
|
||||
}
|
||||
}
|
||||
> span:last-child {
|
||||
color: rgba(47, 84, 235, 0.55);
|
||||
color: #custom_colors[color_1];
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
@@ -1117,7 +1140,7 @@ body {
|
||||
.ant-tabs-card-bar {
|
||||
margin: 0;
|
||||
.ant-tabs-nav-container {
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: #fff;
|
||||
.ant-tabs-tab {
|
||||
border: none;
|
||||
border-top-left-radius: 4px;
|
||||
@@ -1125,9 +1148,9 @@ body {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.ant-tabs-tab-active {
|
||||
background: #fff !important;
|
||||
.ant-tabs-tab-active {
|
||||
background: #custom_colors[color_2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1218,7 +1241,7 @@ body {
|
||||
|
||||
.el-tabs__header {
|
||||
border-bottom: none;
|
||||
background-color: #f0f5ff;
|
||||
background-color: #custom_colors[color_2];
|
||||
border-radius: 8px 8px 0px 0px;
|
||||
}
|
||||
|
||||
@@ -1303,6 +1326,15 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
// json editor
|
||||
.jsoneditor-vue {
|
||||
div.jsoneditor {
|
||||
border: none;
|
||||
}
|
||||
div.jsoneditor-menu {
|
||||
border-bottom-color: #custom_colors[color_1];
|
||||
}
|
||||
}
|
||||
// .ant-menu.ant-menu-light {
|
||||
// &.ops-menu {
|
||||
// background-color: white;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
@border-radius-base: 2px; // 组件/浮层圆角
|
||||
@primary-color: #2f54eb; // 全局主色
|
||||
@scrollbar-color: rgba(47, 122, 235, 0.2);
|
||||
|
||||
// @layout-header-background: #1a3652;
|
||||
@layout-header-background: #fff;
|
||||
@layout-header-height: 40px;
|
||||
@layout-header-icon-height: 34px;
|
||||
@@ -15,13 +15,20 @@
|
||||
@layout-background-light-color: #fafafa;
|
||||
@layout-background-light-color-light: #f0f0f0;
|
||||
|
||||
@layout-sidebar-color: #1d264c; //bg
|
||||
@layout-sidebar-sub-color: #000c37; //bg
|
||||
@layout-sidebar-selected-color: #3e4a71; //selected bg
|
||||
@layout-sidebar-arrow-color: rgba(255, 255, 255, 0.6);
|
||||
@layout-sidebar-font-color: #fff;
|
||||
@layout-sidebar-disabled-font-color: rgba(255, 255, 255, 0.6);
|
||||
|
||||
#custom_colors() {
|
||||
color_1: #2f54eb;
|
||||
color_2: #f0f5ff;
|
||||
color_3: #D2E2FF;
|
||||
color_1: #2f54eb; //primary color
|
||||
color_2: #f0f5ff; //light background color
|
||||
color_3: #d2e2ff;
|
||||
}
|
||||
|
||||
.ops_display_wrapper(@backgroundColor:#f0f5ff) {
|
||||
.ops_display_wrapper(@backgroundColor:#custom_colors()[color_2]) {
|
||||
cursor: pointer;
|
||||
padding: 5px 8px;
|
||||
background-color: @backgroundColor;
|
||||
|
@@ -76,7 +76,8 @@ const AppDeviceEnquire = {
|
||||
const mixinPermissions = {
|
||||
computed: {
|
||||
...mapState({
|
||||
detailPermissions: state => state.user.detailPermissions
|
||||
detailPermissions: state => state.user.detailPermissions,
|
||||
roles: state => state.user.roles
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
@@ -85,7 +86,7 @@ const mixinPermissions = {
|
||||
hasDetailPermission(appName, resourceName, perms = []) {
|
||||
const appNamePer = this.detailPermissions[`${appName}`]
|
||||
const _findResourcePermissions = appNamePer.find(item => item.name === resourceName)
|
||||
return _findResourcePermissions.permissions.some(item => perms.includes(item))
|
||||
return this.roles?.permissions.includes('acl_admin') || this.roles?.permissions.includes('backend_admin') || _findResourcePermissions?.permissions.some(item => perms.includes(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -60,34 +60,6 @@
|
||||
<vxe-column field="mobile" :title="$t('cs.companyStructure.mobile')" min-width="80"></vxe-column>
|
||||
<vxe-column field="position_name" :title="$t('cs.companyStructure.positionName')" min-width="80"></vxe-column>
|
||||
<vxe-column field="department_name" :title="$t('cs.companyStructure.departmentName')" min-width="80"></vxe-column>
|
||||
<vxe-column field="current_company" v-if="useDFC" :title="$t('cs.companyStructure.currentCompany')" min-width="120"></vxe-column>
|
||||
<vxe-column field="dfc_entry_date" v-if="useDFC" :title="$t('cs.companyStructure.dfcEntryDate')" min-width="120"></vxe-column>
|
||||
<vxe-column field="entry_date" :title="$t('cs.companyStructure.entryDate')" min-width="120"></vxe-column>
|
||||
<vxe-column field="is_internship" :title="$t('cs.companyStructure.isInternship')" min-width="120"></vxe-column>
|
||||
<vxe-column field="leave_date" :title="$t('cs.companyStructure.leaveDate')" min-width="120"></vxe-column>
|
||||
<vxe-column field="id_card" :title="$t('cs.companyStructure.idCard')" min-width="120"></vxe-column>
|
||||
<vxe-column field="nation" :title="$t('cs.companyStructure.nation')" min-width="80"></vxe-column>
|
||||
<vxe-column field="id_place" :title="$t('cs.companyStructure.idPlace')" min-width="80"></vxe-column>
|
||||
<vxe-column field="party" :title="$t('cs.companyStructure.party')" min-width="80"></vxe-column>
|
||||
<vxe-column field="household_registration_type" :title="$t('cs.companyStructure.householdRegistrationType')" min-width="80"></vxe-column>
|
||||
<vxe-column field="hometown" :title="$t('cs.companyStructure.homewtown')" min-width="80"></vxe-column>
|
||||
<vxe-column field="marry" :title="$t('cs.companyStructure.marry')" min-width="80"></vxe-column>
|
||||
<vxe-column field="max_degree" :title="$t('cs.companyStructure.maxDegree')" min-width="80"></vxe-column>
|
||||
<vxe-column field="emergency_person" :title="$t('cs.companyStructure.emergencyPerson')" min-width="120"></vxe-column>
|
||||
<vxe-column field="emergency_phone" :title="$t('cs.companyStructure.emergencyPhone')" min-width="120"></vxe-column>
|
||||
<vxe-column field="bank_card_number" :title="$t('cs.companyStructure.bankCardNumber')" min-width="120"></vxe-column>
|
||||
<vxe-column field="bank_card_name" :title="$t('cs.companyStructure.bankCardName')" min-width="80"></vxe-column>
|
||||
<vxe-column field="opening_bank" :title="$t('cs.companyStructure.openingBank')" min-width="80"></vxe-column>
|
||||
<vxe-column field="account_opening_location" :title="$t('cs.companyStructure.accountOpeningLocation')" min-width="120"></vxe-column>
|
||||
<vxe-column field="school" :title="$t('cs.companyStructure.school')" min-width="80"></vxe-column>
|
||||
<vxe-column field="major" :title="$t('cs.companyStructure.major')" min-width="80"></vxe-column>
|
||||
<vxe-column field="education" :title="$t('cs.companyStructure.education')" min-width="80"></vxe-column>
|
||||
<vxe-column field="graduation_year" :title="$t('cs.companyStructure.graduationYear')" min-width="120"></vxe-column>
|
||||
<vxe-column field="birth_date" :title="$t('cs.companyStructure.birthDate')" min-width="120"></vxe-column>
|
||||
<vxe-column field="birth_place" :title="$t('cs.companyStructure.birthPlace')" min-width="120"></vxe-column>
|
||||
<vxe-column field="nationality_region" :title="$t('cs.companyStructure.nationalityRegion')" min-width="120"></vxe-column>
|
||||
<vxe-column field="first_entry_date" :title="$t('cs.companyStructure.firstEntryDate')" min-width="120"></vxe-column>
|
||||
<vxe-column field="estimated_departure_date" :title="$t('cs.companyStructure.estimatedDepartureDate')" min-width="120"></vxe-column>
|
||||
<vxe-column v-if="has_error" field="err" :title="$t('cs.companyStructure.importFailedReason')" min-width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<span :style="{ color: '#D81E06' }">{{ row.err }}</span>
|
||||
@@ -303,98 +275,6 @@ export default {
|
||||
v: '部门',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '目前所属主体',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '初始入职日期',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '目前主体入职日期',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '正式/实习生',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '离职日期',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '身份证号码',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '民族',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '籍贯',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '组织关系',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '户籍类型',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '户口所在地',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '婚姻情况',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '最高学历',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '紧急联系人',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '紧急联系电话',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '卡号',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '银行',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '开户行',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '开户地',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '学校',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '专业',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '学历',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '毕业年份',
|
||||
t: 's',
|
||||
},
|
||||
],
|
||||
]
|
||||
data[1] = data[1].filter((item) => item['v'] !== '目前所属主体')
|
||||
|
@@ -372,7 +372,7 @@ export default {
|
||||
{ label: this.$t('cs.companyStructure.mobile'), value: 'mobile' },
|
||||
{ label: this.$t('cs.companyStructure.departmentName'), value: 'department_name' },
|
||||
{ label: this.$t('cs.companyStructure.positionName'), value: 'position_name' },
|
||||
{ label: this.$t('cs.companyStructure.departmentDirector'), value: 'direct_supervisor_id' },
|
||||
{ label: this.$t('cs.companyStructure.supervisor'), value: 'direct_supervisor_id' },
|
||||
]
|
||||
},
|
||||
sceneList () {
|
||||
|
@@ -139,7 +139,7 @@ export default {
|
||||
console.log('login form', values)
|
||||
const loginParams = { ...values }
|
||||
delete loginParams.username
|
||||
loginParams[!state.loginType ? 'email' : 'username'] = values.username
|
||||
loginParams.username = values.username
|
||||
loginParams.password = appConfig.useEncryption ? md5(values.password) : values.password
|
||||
localStorage.setItem('ops_auth_type', '')
|
||||
Login({ userInfo: loginParams })
|
||||
|
@@ -18,7 +18,7 @@ module.exports = {
|
||||
// TODO 需要增加根据环境不开启主题需求
|
||||
new ThemeColorReplacer({
|
||||
fileName: 'css/theme-colors-[contenthash:8].css',
|
||||
matchColors: getAntdSerials('#1890ff'), // 主色系列
|
||||
matchColors: getAntdSerials('#2f54eb'), // 主色系列
|
||||
// 改变样式选择器,解决样式覆盖问题
|
||||
changeSelector(selector) {
|
||||
switch (selector) {
|
||||
@@ -83,11 +83,9 @@ module.exports = {
|
||||
less: {
|
||||
modifyVars: {
|
||||
/* less 变量覆盖,用于自定义 ant design 主题 */
|
||||
/*
|
||||
'primary-color': '#F5222D',
|
||||
'link-color': '#F5222D',
|
||||
'border-radius-base': '4px',
|
||||
*/
|
||||
'primary-color': '#2f54eb',
|
||||
// 'link-color': '#F5222D',
|
||||
// 'border-radius-base': '4px',
|
||||
},
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
|
@@ -33,7 +33,7 @@ services:
|
||||
- redis
|
||||
|
||||
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:
|
||||
# context: .
|
||||
# target: cmdb-api
|
||||
@@ -57,6 +57,8 @@ services:
|
||||
nohup flask cmdb-trigger > trigger.log 2>&1 &
|
||||
flask cmdb-init-cache
|
||||
flask cmdb-init-acl
|
||||
flask init-import-user-from-acl
|
||||
flask init-department
|
||||
flask cmdb-counter > counter.log 2>&1
|
||||
|
||||
depends_on:
|
||||
@@ -68,7 +70,7 @@ services:
|
||||
- cmdb-api
|
||||
|
||||
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:
|
||||
# context: .
|
||||
# target: cmdb-ui
|
||||
|
101
docs/cmdb.sql
101
docs/cmdb.sql
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user