Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
e5fe2bffaa build(deps): bump pycryptodome from 3.12.0 to 3.19.1 in /cmdb-api
Bumps [pycryptodome](https://github.com/Legrandin/pycryptodome) from 3.12.0 to 3.19.1.
- [Release notes](https://github.com/Legrandin/pycryptodome/releases)
- [Changelog](https://github.com/Legrandin/pycryptodome/blob/master/Changelog.rst)
- [Commits](https://github.com/Legrandin/pycryptodome/compare/v3.12.0...v3.19.1)

---
updated-dependencies:
- dependency-name: pycryptodome
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-05 17:39:18 +00:00
112 changed files with 8750 additions and 11043 deletions

View File

@@ -5,8 +5,8 @@ name = "pypi"
[packages] [packages]
# Flask # Flask
Flask = "==2.2.5" Flask = "==2.3.2"
Werkzeug = "==2.2.3" Werkzeug = ">=2.3.6"
click = ">=5.0" click = ">=5.0"
# Api # Api
Flask-RESTful = "==0.3.10" Flask-RESTful = "==0.3.10"
@@ -15,7 +15,6 @@ Flask-SQLAlchemy = "==2.5.0"
SQLAlchemy = "==1.4.49" SQLAlchemy = "==1.4.49"
PyMySQL = "==1.1.0" PyMySQL = "==1.1.0"
redis = "==4.6.0" redis = "==4.6.0"
python-redis-lock = "==4.0.0"
# Migrations # Migrations
Flask-Migrate = "==2.5.2" Flask-Migrate = "==2.5.2"
# Deployment # Deployment
@@ -26,7 +25,7 @@ Flask-Login = ">=0.6.2"
Flask-Bcrypt = "==1.0.1" Flask-Bcrypt = "==1.0.1"
Flask-Cors = ">=3.0.8" Flask-Cors = ">=3.0.8"
ldap3 = "==2.9.1" ldap3 = "==2.9.1"
pycryptodome = "==3.12.0" pycryptodome = "==3.19.1"
cryptography = ">=41.0.2" cryptography = ">=41.0.2"
# i18n # i18n
flask-babel = "==4.0.0" flask-babel = "==4.0.0"

View File

@@ -50,7 +50,7 @@ def add_user():
if is_admin: if is_admin:
app = AppCache.get('acl') or App.create(name='acl') app = AppCache.get('acl') or App.create(name='acl')
acl_admin = RoleCache.get_by_name(app.id, 'acl_admin') or RoleCRUD.add_role('acl_admin', app.id, True) acl_admin = RoleCache.get('acl_admin') or RoleCRUD.add_role('acl_admin', app.id, True)
rid = RoleCache.get_by_name(None, username).id rid = RoleCache.get_by_name(None, username).id
RoleRelationCRUD.add(acl_admin, acl_admin.id, [rid], app.id) RoleRelationCRUD.add(acl_admin, acl_admin.id, [rid], app.id)

View File

@@ -115,8 +115,6 @@ def cmdb_init_acl():
_app = AppCache.get('cmdb') or App.create(name='cmdb') _app = AppCache.get('cmdb') or App.create(name='cmdb')
app_id = _app.id app_id = _app.id
current_app.test_request_context().push()
# 1. add resource type # 1. add resource type
for resource_type in ResourceTypeEnum.all(): for resource_type in ResourceTypeEnum.all():
try: try:
@@ -185,21 +183,11 @@ def cmdb_counter():
UserCRUD.add(username='worker', password=uuid.uuid4().hex, email='worker@xxx.com') UserCRUD.add(username='worker', password=uuid.uuid4().hex, email='worker@xxx.com')
login_user(UserCache.get('worker')) login_user(UserCache.get('worker'))
i = 0
while True: while True:
try: try:
db.session.remove() db.session.remove()
CMDBCounterCache.reset() CMDBCounterCache.reset()
if i % 5 == 0:
CMDBCounterCache.flush_adc_counter()
i = 0
CMDBCounterCache.flush_sub_counter()
i += 1
except: except:
import traceback import traceback
print(traceback.format_exc()) print(traceback.format_exc())

View File

@@ -4,7 +4,7 @@ from flask.cli import with_appcontext
from werkzeug.datastructures import MultiDict from werkzeug.datastructures import MultiDict
from api.lib.common_setting.acl import ACLManager from api.lib.common_setting.acl import ACLManager
from api.lib.common_setting.employee import EmployeeAddForm, GrantEmployeeACLPerm from api.lib.common_setting.employee import EmployeeAddForm
from api.lib.common_setting.resp_format import ErrFormat from api.lib.common_setting.resp_format import ErrFormat
from api.models.common_setting import Employee, Department from api.models.common_setting import Employee, Department
@@ -158,11 +158,50 @@ class InitDepartment(object):
def init_backend_resource(self): def init_backend_resource(self):
acl = self.check_app('backend') acl = self.check_app('backend')
resources_types = acl.get_all_resources_types()
perms = ['read', 'grant', 'delete', 'update']
acl_rid = self.get_admin_user_rid() acl_rid = self.get_admin_user_rid()
if acl_rid == 0: results = list(filter(lambda t: t['name'] == '操作权限', resources_types['groups']))
return if len(results) == 0:
GrantEmployeeACLPerm(acl).grant_by_rid(acl_rid, True) 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)
@staticmethod @staticmethod
def check_app(app_name): def check_app(app_name):

View File

@@ -89,19 +89,6 @@ def db_setup():
""" """
db.create_all() 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() @click.group()
def translate(): def translate():

View File

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

View File

@@ -5,14 +5,10 @@ from __future__ import unicode_literals
from flask import current_app from flask import current_app
from api.extensions import cache from api.extensions import cache
from api.extensions import db
from api.lib.cmdb.custom_dashboard import CustomDashboardManager from api.lib.cmdb.custom_dashboard import CustomDashboardManager
from api.models.cmdb import Attribute from api.models.cmdb import Attribute
from api.models.cmdb import CI
from api.models.cmdb import CIType from api.models.cmdb import CIType
from api.models.cmdb import CITypeAttribute from api.models.cmdb import CITypeAttribute
from api.models.cmdb import PreferenceShowAttributes
from api.models.cmdb import PreferenceTreeView
from api.models.cmdb import RelationType from api.models.cmdb import RelationType
@@ -230,9 +226,7 @@ class CITypeAttributeCache(object):
class CMDBCounterCache(object): class CMDBCounterCache(object):
KEY = 'CMDB::Counter::dashboard' KEY = 'CMDB::Counter'
KEY2 = 'CMDB::Counter::adc'
KEY3 = 'CMDB::Counter::sub'
@classmethod @classmethod
def get(cls): def get(cls):
@@ -309,7 +303,7 @@ class CMDBCounterCache(object):
s = RelSearch([i[0] for i in type_id_names], level, other_filer or '') s = RelSearch([i[0] for i in type_id_names], level, other_filer or '')
try: try:
stats = s.statistics(type_ids, need_filter=False) stats = s.statistics(type_ids)
except SearchError as e: except SearchError as e:
current_app.logger.error(e) current_app.logger.error(e)
return return
@@ -435,47 +429,3 @@ class CMDBCounterCache(object):
return return
return numfound 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()

View File

@@ -6,7 +6,6 @@ import datetime
import json import json
import threading import threading
import redis_lock
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask_login import current_user from flask_login import current_user
@@ -15,8 +14,8 @@ from werkzeug.exceptions import BadRequest
from api.extensions import db from api.extensions import db
from api.extensions import rd from api.extensions import rd
from api.lib.cmdb.cache import AttributeCache from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeAttributesCache
from api.lib.cmdb.cache import CITypeCache from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.cache import CMDBCounterCache
from api.lib.cmdb.ci_type import CITypeAttributeManager from api.lib.cmdb.ci_type import CITypeAttributeManager
from api.lib.cmdb.ci_type import CITypeManager from api.lib.cmdb.ci_type import CITypeManager
from api.lib.cmdb.ci_type import CITypeRelationManager from api.lib.cmdb.ci_type import CITypeRelationManager
@@ -46,12 +45,14 @@ from api.lib.perm.acl.acl import is_app_admin
from api.lib.perm.acl.acl import validate_permission from api.lib.perm.acl.acl import validate_permission
from api.lib.secrets.inner import InnerCrypt from api.lib.secrets.inner import InnerCrypt
from api.lib.secrets.vault import VaultClient from api.lib.secrets.vault import VaultClient
from api.lib.utils import Lock
from api.lib.utils import handle_arg_list from api.lib.utils import handle_arg_list
from api.lib.webhook import webhook_request from api.lib.webhook import webhook_request
from api.models.cmdb import AttributeHistory from api.models.cmdb import AttributeHistory
from api.models.cmdb import AutoDiscoveryCI from api.models.cmdb import AutoDiscoveryCI
from api.models.cmdb import CI from api.models.cmdb import CI
from api.models.cmdb import CIRelation from api.models.cmdb import CIRelation
from api.models.cmdb import CITypeAttribute
from api.models.cmdb import CITypeRelation from api.models.cmdb import CITypeRelation
from api.models.cmdb import CITypeTrigger from api.models.cmdb import CITypeTrigger
from api.tasks.cmdb import ci_cache from api.tasks.cmdb import ci_cache
@@ -60,8 +61,8 @@ from api.tasks.cmdb import ci_delete_trigger
from api.tasks.cmdb import ci_relation_add from api.tasks.cmdb import ci_relation_add
from api.tasks.cmdb import ci_relation_cache from api.tasks.cmdb import ci_relation_cache
from api.tasks.cmdb import ci_relation_delete from api.tasks.cmdb import ci_relation_delete
from api.tasks.cmdb import delete_id_filter
PRIVILEGED_USERS = {"worker", "cmdb_agent", "agent"}
PASSWORD_DEFAULT_SHOW = "******" PASSWORD_DEFAULT_SHOW = "******"
@@ -217,7 +218,15 @@ class CIManager(object):
@classmethod @classmethod
def get_ad_statistics(cls): def get_ad_statistics(cls):
return CMDBCounterCache.get_adc_counter() 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
@staticmethod @staticmethod
def ci_is_exist(unique_key, unique_value, type_id): def ci_is_exist(unique_key, unique_value, type_id):
@@ -278,16 +287,16 @@ class CIManager(object):
@staticmethod @staticmethod
def _auto_inc_id(attr): def _auto_inc_id(attr):
db.session.commit() db.session.remove()
value_table = TableMap(attr_name=attr.name).table value_table = TableMap(attr_name=attr.name).table
with redis_lock.Lock(rd.r, "auto_inc_id_{}".format(attr.name)): with Lock("auto_inc_id_{}".format(attr.name), need_lock=True):
max_v = value_table.get_by(attr_id=attr.id, only_query=True).order_by( max_v = value_table.get_by(attr_id=attr.id, only_query=True).order_by(
getattr(value_table, 'value').desc()).first() getattr(value_table, 'value').desc()).first()
if max_v is not None: if max_v is not None:
return int(max_v.value) + 1 return int(max_v.value) + 1
return 1 return 1
@classmethod @classmethod
def add(cls, ci_type_name, def add(cls, ci_type_name,
@@ -308,18 +317,14 @@ class CIManager(object):
""" """
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
ci_type = CITypeManager.check_is_existed(ci_type_name) ci_type = CITypeManager.check_is_existed(ci_type_name)
raw_dict = copy.deepcopy(ci_dict)
unique_key = AttributeCache.get(ci_type.unique_id) or abort( unique_key = AttributeCache.get(ci_type.unique_id) or abort(
400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id))) 400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id)))
unique_value = None unique_value = ci_dict.get(unique_key.name) or ci_dict.get(unique_key.alias) or ci_dict.get(unique_key.id)
if not (unique_key.default and unique_key.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name))
not ci_dict.get(unique_key.name)): # primary key is not auto inc id
unique_value = ci_dict.get(unique_key.name) or ci_dict.get(unique_key.alias) or ci_dict.get(unique_key.id)
unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name))
attrs = CITypeAttributeManager.get_all_attributes(ci_type.id) attrs = CITypeAttributesCache.get2(ci_type_name)
ci_type_attrs_name = {attr.name: attr for _, attr in attrs} ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
ci_type_attrs_alias = {attr.alias: attr for _, attr in attrs} ci_type_attrs_alias = {attr.alias: attr for _, attr in attrs}
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs} ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
@@ -327,15 +332,8 @@ class CIManager(object):
ci = None ci = None
record_id = None record_id = None
password_dict = {} password_dict = {}
with redis_lock.Lock(rd.r, ci_type.name): need_lock = current_user.username not in current_app.config.get('PRIVILEGED_USERS', PRIVILEGED_USERS)
db.session.commit() with Lock(ci_type_name, need_lock=need_lock):
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)
current_app.logger.info(ci_dict[unique_key.name])
unique_value = ci_dict[unique_key.name]
existed = cls.ci_is_exist(unique_key, unique_value, ci_type.id) existed = cls.ci_is_exist(unique_key, unique_value, ci_type.id)
if existed is not None: if existed is not None:
if exist_policy == ExistPolicy.REJECT: if exist_policy == ExistPolicy.REJECT:
@@ -356,8 +354,7 @@ class CIManager(object):
if attr.default.get('default') and attr.default.get('default') in ( if attr.default.get('default') and attr.default.get('default') in (
AttributeDefaultValueEnum.CREATED_AT, AttributeDefaultValueEnum.UPDATED_AT): AttributeDefaultValueEnum.CREATED_AT, AttributeDefaultValueEnum.UPDATED_AT):
ci_dict[attr.name] = now ci_dict[attr.name] = now
elif (attr.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and elif attr.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID:
not ci_dict.get(attr.name)):
ci_dict[attr.name] = cls._auto_inc_id(attr) ci_dict[attr.name] = cls._auto_inc_id(attr)
elif ((attr.name not in ci_dict and attr.alias not in ci_dict) or ( elif ((attr.name not in ci_dict and attr.alias not in ci_dict) or (
ci_dict.get(attr.name) is None and ci_dict.get(attr.alias) is None)): ci_dict.get(attr.name) is None and ci_dict.get(attr.alias) is None)):
@@ -371,8 +368,6 @@ class CIManager(object):
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT: if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
ci_dict[attr.name] = now ci_dict[attr.name] = now
value_manager = AttributeValueManager()
computed_attrs = [] computed_attrs = []
for _, attr in attrs: for _, attr in attrs:
if attr.is_computed: if attr.is_computed:
@@ -383,8 +378,7 @@ class CIManager(object):
elif attr.alias in ci_dict: elif attr.alias in ci_dict:
password_dict[attr.id] = ci_dict.pop(attr.alias) password_dict[attr.id] = ci_dict.pop(attr.alias)
if attr.re_check and password_dict.get(attr.id): value_manager = AttributeValueManager()
value_manager.check_re(attr.re_check, password_dict[attr.id])
if computed_attrs: if computed_attrs:
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci) value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
@@ -392,7 +386,7 @@ class CIManager(object):
cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id) cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
ref_ci_dict = dict() ref_ci_dict = dict()
for k in copy.deepcopy(ci_dict): for k in ci_dict:
if k.startswith("$") and "." in k: if k.startswith("$") and "." in k:
ref_ci_dict[k] = ci_dict[k] ref_ci_dict[k] = ci_dict[k]
continue continue
@@ -404,10 +398,7 @@ class CIManager(object):
_attr_name = ((ci_type_attrs_name.get(k) and ci_type_attrs_name[k].name) or _attr_name = ((ci_type_attrs_name.get(k) and ci_type_attrs_name[k].name) or
(ci_type_attrs_alias.get(k) and ci_type_attrs_alias[k].name)) (ci_type_attrs_alias.get(k) and ci_type_attrs_alias[k].name))
if limit_attrs and _attr_name not in limit_attrs: if limit_attrs and _attr_name not in limit_attrs:
if k in raw_dict: return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
else:
ci_dict.pop(k)
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name or k in ci_type_attrs_alias} ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name or k in ci_type_attrs_alias}
@@ -439,17 +430,13 @@ class CIManager(object):
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
ci = self.confirm_ci_existed(ci_id) ci = self.confirm_ci_existed(ci_id)
raw_dict = copy.deepcopy(ci_dict) attrs = CITypeAttributesCache.get2(ci.type_id)
attrs = CITypeAttributeManager.get_all_attributes(ci.type_id)
ci_type_attrs_name = {attr.name: attr for _, attr in attrs} ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs} ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
for _, attr in attrs: for _, attr in attrs:
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT: if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
ci_dict[attr.name] = now ci_dict[attr.name] = now
value_manager = AttributeValueManager()
password_dict = dict() password_dict = dict()
computed_attrs = list() computed_attrs = list()
for _, attr in attrs: for _, attr in attrs:
@@ -461,8 +448,7 @@ class CIManager(object):
elif attr.alias in ci_dict: elif attr.alias in ci_dict:
password_dict[attr.id] = ci_dict.pop(attr.alias) password_dict[attr.id] = ci_dict.pop(attr.alias)
if attr.re_check and password_dict.get(attr.id): value_manager = AttributeValueManager()
value_manager.check_re(attr.re_check, password_dict[attr.id])
if computed_attrs: if computed_attrs:
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci) value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
@@ -470,21 +456,17 @@ class CIManager(object):
limit_attrs = self._valid_ci_for_no_read(ci) if not _is_admin else {} limit_attrs = self._valid_ci_for_no_read(ci) if not _is_admin else {}
record_id = None record_id = None
with redis_lock.Lock(rd.r, ci.ci_type.name): need_lock = current_user.username not in current_app.config.get('PRIVILEGED_USERS', PRIVILEGED_USERS)
db.session.commit() with Lock(ci.ci_type.name, need_lock=need_lock):
self._valid_unique_constraint(ci.type_id, ci_dict, ci_id) self._valid_unique_constraint(ci.type_id, ci_dict, ci_id)
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name} ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name}
key2attr = value_manager.valid_attr_value(ci_dict, ci.type_id, ci.id, ci_type_attrs_name, key2attr = value_manager.valid_attr_value(ci_dict, ci.type_id, ci.id, ci_type_attrs_name,
ci_attr2type_attr=ci_attr2type_attr) ci_attr2type_attr=ci_attr2type_attr)
if limit_attrs: if limit_attrs:
for k in copy.deepcopy(ci_dict): for k in ci_dict:
if k not in limit_attrs: if k not in limit_attrs:
if k in raw_dict: return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
else:
ci_dict.pop(k)
try: try:
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr) record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr)
@@ -532,7 +514,8 @@ class CIManager(object):
ci_delete_trigger.apply_async(args=(trigger, OperateType.DELETE, ci_dict), queue=CMDB_QUEUE) ci_delete_trigger.apply_async(args=(trigger, OperateType.DELETE, ci_dict), queue=CMDB_QUEUE)
attrs = [i for _, i in CITypeAttributeManager.get_all_attributes(type_id=ci.type_id)] attrs = CITypeAttribute.get_by(type_id=ci.type_id, to_dict=False)
attrs = [AttributeCache.get(attr.attr_id) for attr in attrs]
for attr in attrs: for attr in attrs:
value_table = TableMap(attr=attr).table value_table = TableMap(attr=attr).table
for item in value_table.get_by(ci_id=ci_id, to_dict=False): for item in value_table.get_by(ci_id=ci_id, to_dict=False):
@@ -559,7 +542,6 @@ class CIManager(object):
AttributeHistoryManger.add(None, ci_id, [(None, OperateType.DELETE, ci_dict, None)], ci.type_id) AttributeHistoryManger.add(None, ci_id, [(None, OperateType.DELETE, ci_dict, None)], ci.type_id)
ci_delete.apply_async(args=(ci_id,), queue=CMDB_QUEUE) ci_delete.apply_async(args=(ci_id,), queue=CMDB_QUEUE)
delete_id_filter.apply_async(args=(ci_id,), queue=CMDB_QUEUE)
return ci_id return ci_id
@@ -866,20 +848,6 @@ class CIRelationManager(object):
return numfound, len(ci_ids), result return numfound, len(ci_ids), result
@staticmethod
def recursive_children(ci_id):
result = []
def _get_children(_id):
children = CIRelation.get_by(first_ci_id=_id, to_dict=False)
result.extend([i.second_ci_id for i in children])
for child in children:
_get_children(child.second_ci_id)
_get_children(ci_id)
return result
@staticmethod @staticmethod
def _sort_handler(sort_by, query_sql): def _sort_handler(sort_by, query_sql):
@@ -935,7 +903,7 @@ class CIRelationManager(object):
@staticmethod @staticmethod
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation): def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
db.session.commit() db.session.remove()
if type_relation.constraint == ConstraintEnum.Many2Many: if type_relation.constraint == ConstraintEnum.Many2Many:
return return
@@ -995,7 +963,7 @@ class CIRelationManager(object):
else: else:
type_relation = CITypeRelation.get_by_id(relation_type_id) type_relation = CITypeRelation.get_by_id(relation_type_id)
with redis_lock.Lock(rd.r, "ci_relation_add_{}_{}".format(first_ci.type_id, second_ci.type_id)): with Lock("ci_relation_add_{}_{}".format(first_ci.type_id, second_ci.type_id), need_lock=True):
cls._check_constraint(first_ci_id, first_ci.type_id, second_ci_id, second_ci.type_id, type_relation) cls._check_constraint(first_ci_id, first_ci.type_id, second_ci_id, second_ci.type_id, type_relation)
@@ -1031,7 +999,6 @@ class CIRelationManager(object):
his_manager.add(cr, operate_type=OperateType.DELETE) his_manager.add(cr, operate_type=OperateType.DELETE)
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE) ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
delete_id_filter.apply_async(args=(cr.second_ci_id,), queue=CMDB_QUEUE)
return cr_id return cr_id
@@ -1043,13 +1010,9 @@ class CIRelationManager(object):
to_dict=False, to_dict=False,
first=True) first=True)
if cr is not None:
cls.delete(cr.id)
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE) ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
return cr return cr and cls.delete(cr.id)
@classmethod @classmethod
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None): def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
@@ -1090,7 +1053,7 @@ class CIRelationManager(object):
class CITriggerManager(object): class CITriggerManager(object):
@staticmethod @staticmethod
def get(type_id): def get(type_id):
db.session.commit() db.session.remove()
return CITypeTrigger.get_by(type_id=type_id, to_dict=True) return CITypeTrigger.get_by(type_id=type_id, to_dict=True)
@staticmethod @staticmethod

View File

@@ -42,12 +42,10 @@ from api.models.cmdb import CITypeAttributeGroup
from api.models.cmdb import CITypeAttributeGroupItem from api.models.cmdb import CITypeAttributeGroupItem
from api.models.cmdb import CITypeGroup from api.models.cmdb import CITypeGroup
from api.models.cmdb import CITypeGroupItem from api.models.cmdb import CITypeGroupItem
from api.models.cmdb import CITypeInheritance
from api.models.cmdb import CITypeRelation from api.models.cmdb import CITypeRelation
from api.models.cmdb import CITypeTrigger from api.models.cmdb import CITypeTrigger
from api.models.cmdb import CITypeUniqueConstraint from api.models.cmdb import CITypeUniqueConstraint
from api.models.cmdb import CustomDashboard from api.models.cmdb import CustomDashboard
from api.models.cmdb import PreferenceCITypeOrder
from api.models.cmdb import PreferenceRelationView from api.models.cmdb import PreferenceRelationView
from api.models.cmdb import PreferenceSearchOption from api.models.cmdb import PreferenceSearchOption
from api.models.cmdb import PreferenceShowAttributes from api.models.cmdb import PreferenceShowAttributes
@@ -76,23 +74,17 @@ class CITypeManager(object):
return CIType.get_by_id(ci_type.id) return CIType.get_by_id(ci_type.id)
def get_icons(self):
return {i.id: i.icon or i.name for i in db.session.query(
self.cls.id, self.cls.icon, self.cls.name).filter(self.cls.deleted.is_(False))}
@staticmethod @staticmethod
def get_ci_types(type_name=None, like=True): def get_ci_types(type_name=None):
resources = None resources = None
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'): 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)]) 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 ( ci_types = CIType.get_by() if type_name is None else CIType.get_by_like(name=type_name)
CIType.get_by_like(name=type_name) if like else CIType.get_by(name=type_name))
res = list() res = list()
for type_dict in ci_types: for type_dict in ci_types:
attr = AttributeCache.get(type_dict["unique_id"]) attr = AttributeCache.get(type_dict["unique_id"])
type_dict["unique_key"] = attr and attr.name type_dict["unique_key"] = attr and attr.name
type_dict['parent_ids'] = CITypeInheritanceManager.get_parents(type_dict['id'])
if resources is None or type_dict['name'] in resources: if resources is None or type_dict['name'] in resources:
res.append(type_dict) res.append(type_dict)
@@ -139,13 +131,8 @@ class CITypeManager(object):
kwargs["unique_id"] = unique_key.id kwargs["unique_id"] = unique_key.id
kwargs['uid'] = current_user.uid kwargs['uid'] = current_user.uid
parent_ids = kwargs.pop('parent_ids', None)
ci_type = CIType.create(**kwargs) ci_type = CIType.create(**kwargs)
CITypeInheritanceManager.add(parent_ids, ci_type.id)
CITypeAttributeManager.add(ci_type.id, [unique_key.id], is_required=True) CITypeAttributeManager.add(ci_type.id, [unique_key.id], is_required=True)
CITypeCache.clean(ci_type.name) CITypeCache.clean(ci_type.name)
@@ -227,29 +214,21 @@ class CITypeManager(object):
if item.get('parent_id') == type_id or item.get('child_id') == type_id: if item.get('parent_id') == type_id or item.get('child_id') == type_id:
return abort(400, ErrFormat.ci_relation_view_exists_and_cannot_delete_type.format(rv.name)) return abort(400, ErrFormat.ci_relation_view_exists_and_cannot_delete_type.format(rv.name))
for item in (CITypeRelation.get_by(parent_id=type_id, to_dict=False) + for item in CITypeRelation.get_by(parent_id=type_id, to_dict=False):
CITypeRelation.get_by(child_id=type_id, to_dict=False)): item.soft_delete(commit=False)
if current_app.config.get('USE_ACL'):
resource_name = CITypeRelationManager.acl_resource_name(item.parent.name, item.child.name)
ACLManager().del_resource(resource_name, ResourceTypeEnum.CI_TYPE_RELATION)
for item in CITypeRelation.get_by(child_id=type_id, to_dict=False):
item.soft_delete(commit=False) item.soft_delete(commit=False)
for table in [PreferenceTreeView, PreferenceShowAttributes, PreferenceSearchOption, CustomDashboard, for table in [PreferenceTreeView, PreferenceShowAttributes, PreferenceSearchOption, CustomDashboard,
CITypeGroupItem, CITypeAttributeGroup, CITypeAttribute, CITypeUniqueConstraint, CITypeTrigger, CITypeGroupItem, CITypeAttributeGroup, CITypeAttribute, CITypeUniqueConstraint, CITypeTrigger,
AutoDiscoveryCIType, CIFilterPerms, PreferenceCITypeOrder]: AutoDiscoveryCIType, CIFilterPerms]:
for item in table.get_by(type_id=type_id, to_dict=False): for item in table.get_by(type_id=type_id, to_dict=False):
item.soft_delete(commit=False) item.soft_delete(commit=False)
for item in AutoDiscoveryCI.get_by(type_id=type_id, to_dict=False): for item in AutoDiscoveryCI.get_by(type_id=type_id, to_dict=False):
item.delete(commit=False) item.delete(commit=False)
for item in CITypeInheritance.get_by(parent_id=type_id, to_dict=False):
item.delete(commit=False)
for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False):
item.delete(commit=False)
db.session.commit() db.session.commit()
ci_type.soft_delete() ci_type.soft_delete()
@@ -262,100 +241,6 @@ class CITypeManager(object):
ACLManager().del_resource(ci_type.name, ResourceTypeEnum.CI) ACLManager().del_resource(ci_type.name, ResourceTypeEnum.CI)
class CITypeInheritanceManager(object):
cls = CITypeInheritance
@classmethod
def get_parents(cls, type_id):
return [i.parent_id for i in cls.cls.get_by(child_id=type_id, to_dict=False)]
@classmethod
def recursive_children(cls, type_id):
result = []
def _get_child(_id):
children = [i.child_id for i in cls.cls.get_by(parent_id=_id, to_dict=False)]
result.extend(children)
for child_id in children:
_get_child(child_id)
_get_child(type_id)
return result
@classmethod
def base(cls, type_id):
result = []
q = []
def _get_parents(_type_id):
parents = [i.parent_id for i in cls.cls.get_by(child_id=_type_id, to_dict=False)]
for i in parents[::-1]:
q.append(i)
try:
out = q.pop(0)
except IndexError:
return
result.append(out)
_get_parents(out)
_get_parents(type_id)
return result[::-1]
@classmethod
def add(cls, parent_ids, child_id):
rels = {}
for i in cls.cls.get_by(to_dict=False):
rels.setdefault(i.child_id, set()).add(i.parent_id)
try:
toposort_flatten(rels)
except toposort.CircularDependencyError as e:
current_app.logger.warning(str(e))
return abort(400, ErrFormat.circular_dependency_error)
for parent_id in parent_ids or []:
if parent_id == child_id:
return abort(400, ErrFormat.circular_dependency_error)
existed = cls.cls.get_by(parent_id=parent_id, child_id=child_id, first=True, to_dict=False)
if existed is None:
rels.setdefault(child_id, set()).add(parent_id)
try:
toposort_flatten(rels)
except toposort.CircularDependencyError as e:
current_app.logger.warning(str(e))
return abort(400, ErrFormat.circular_dependency_error)
cls.cls.create(parent_id=parent_id, child_id=child_id, commit=False)
db.session.commit()
@classmethod
def delete(cls, parent_id, child_id):
existed = cls.cls.get_by(parent_id=parent_id, child_id=child_id, first=True, to_dict=False)
if existed is not None:
children = cls.recursive_children(child_id) + [child_id]
for _id in children:
if CI.get_by(type_id=_id, to_dict=False, first=True) is not None:
return abort(400, ErrFormat.ci_exists_and_cannot_delete_inheritance)
attr_ids = set([i.id for _, i in CITypeAttributeManager.get_all_attributes(parent_id)])
for _id in children:
for attr_id in attr_ids:
for i in PreferenceShowAttributes.get_by(type_id=_id, attr_id=attr_id, to_dict=False):
i.soft_delete(commit=False)
db.session.commit()
existed.soft_delete()
class CITypeGroupManager(object): class CITypeGroupManager(object):
cls = CITypeGroup cls = CITypeGroup
@@ -376,7 +261,6 @@ class CITypeGroupManager(object):
ci_type = CITypeCache.get(t['type_id']).to_dict() ci_type = CITypeCache.get(t['type_id']).to_dict()
if resources is None or (ci_type and ci_type['name'] in resources): if resources is None or (ci_type and ci_type['name'] in resources):
ci_type['permissions'] = resources[ci_type['name']] if resources is not None else None ci_type['permissions'] = resources[ci_type['name']] if resources is not None else None
ci_type['inherited'] = True if CITypeInheritanceManager.get_parents(ci_type['id']) else False
group.setdefault("ci_types", []).append(ci_type) group.setdefault("ci_types", []).append(ci_type)
group_types.add(t["type_id"]) group_types.add(t["type_id"])
@@ -386,7 +270,6 @@ class CITypeGroupManager(object):
for ci_type in ci_types: for ci_type in ci_types:
if ci_type["id"] not in group_types and (resources is None or ci_type['name'] in resources): if ci_type["id"] not in group_types and (resources is None or ci_type['name'] in resources):
ci_type['permissions'] = resources.get(ci_type['name']) if resources is not None else None ci_type['permissions'] = resources.get(ci_type['name']) if resources is not None else None
ci_type['inherited'] = True if CITypeInheritanceManager.get_parents(ci_type['id']) else False
other_types['ci_types'].append(ci_type) other_types['ci_types'].append(ci_type)
groups.append(other_types) groups.append(other_types)
@@ -478,62 +361,40 @@ class CITypeAttributeManager(object):
return attr.name return attr.name
@staticmethod @staticmethod
def get_all_attributes(type_id): def get_attr_names_by_type_id(type_id):
parent_ids = CITypeInheritanceManager.base(type_id) return [AttributeCache.get(attr.attr_id).name for attr in CITypeAttributesCache.get(type_id)]
result = []
for _type_id in parent_ids + [type_id]:
result.extend(CITypeAttributesCache.get2(_type_id))
return result
@classmethod
def get_attr_names_by_type_id(cls, type_id):
return [attr.name for _, attr in cls.get_all_attributes(type_id)]
@staticmethod @staticmethod
def get_attributes_by_type_id(type_id, choice_web_hook_parse=True, choice_other_parse=True): def get_attributes_by_type_id(type_id, choice_web_hook_parse=True, choice_other_parse=True):
has_config_perm = ACLManager('cmdb').has_permission( has_config_perm = ACLManager('cmdb').has_permission(
CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG) CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG)
parent_ids = CITypeInheritanceManager.base(type_id) attrs = CITypeAttributesCache.get(type_id)
result = list() result = list()
id2pos = dict() for attr in sorted(attrs, key=lambda x: (x.order, x.id)):
type2name = {i: CITypeCache.get(i) for i in parent_ids} attr_dict = AttributeManager().get_attribute(attr.attr_id, choice_web_hook_parse, choice_other_parse)
for _type_id in parent_ids + [type_id]: attr_dict["is_required"] = attr.is_required
attrs = CITypeAttributesCache.get(_type_id) attr_dict["order"] = attr.order
for attr in sorted(attrs, key=lambda x: (x.order, x.id)): attr_dict["default_show"] = attr.default_show
attr_dict = AttributeManager().get_attribute(attr.attr_id, choice_web_hook_parse, choice_other_parse) if not has_config_perm:
attr_dict["is_required"] = attr.is_required attr_dict.pop('choice_web_hook', None)
attr_dict["order"] = attr.order attr_dict.pop('choice_other', None)
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: result.append(attr_dict)
id2pos[attr_dict['id']] = len(result)
result.append(attr_dict)
else:
result[id2pos[attr_dict['id']]] = attr_dict
return result return result
@classmethod @staticmethod
def get_common_attributes(cls, type_ids): def get_common_attributes(type_ids):
has_config_perm = False has_config_perm = False
for type_id in type_ids: for type_id in type_ids:
has_config_perm |= ACLManager('cmdb').has_permission( has_config_perm |= ACLManager('cmdb').has_permission(
CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG) CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG)
result = {type_id: [i for _, i in cls.get_all_attributes(type_id)] for type_id in type_ids} result = CITypeAttribute.get_by(__func_in___key_type_id=list(map(int, type_ids)), to_dict=False)
attr2types = {} attr2types = {}
for type_id in result: for i in result:
for i in result[type_id]: attr2types.setdefault(i.attr_id, []).append(i.type_id)
attr2types.setdefault(i.id, []).append(type_id)
attrs = [] attrs = []
for attr_id in attr2types: for attr_id in attr2types:
@@ -650,30 +511,10 @@ class CITypeAttributeManager(object):
existed.soft_delete() existed.soft_delete()
for ci in CI.get_by(type_id=type_id, to_dict=False): for ci in CI.get_by(type_id=type_id, to_dict=False):
AttributeValueManager.delete_attr_value(attr_id, ci.id, commit=False) AttributeValueManager.delete_attr_value(attr_id, ci.id)
ci_cache.apply_async(args=(ci.id, None, None), queue=CMDB_QUEUE) ci_cache.apply_async(args=(ci.id, None, None), queue=CMDB_QUEUE)
for item in PreferenceShowAttributes.get_by(type_id=type_id, attr_id=attr_id, to_dict=False):
item.soft_delete(commit=False)
child_ids = CITypeInheritanceManager.recursive_children(type_id)
for _type_id in [type_id] + child_ids:
for item in CITypeUniqueConstraint.get_by(type_id=_type_id, to_dict=False):
if attr_id in item.attr_ids:
attr_ids = copy.deepcopy(item.attr_ids)
attr_ids.remove(attr_id)
if attr_ids:
item.update(attr_ids=attr_ids, commit=False)
else:
item.soft_delete(commit=False)
item = CITypeTrigger.get_by(type_id=_type_id, attr_id=attr_id, to_dict=False, first=True)
item and item.soft_delete(commit=False)
db.session.commit()
CITypeAttributeCache.clean(type_id, attr_id) CITypeAttributeCache.clean(type_id, attr_id)
CITypeHistoryManager.add(CITypeOperateType.DELETE_ATTRIBUTE, type_id, attr_id=attr.id, CITypeHistoryManager.add(CITypeOperateType.DELETE_ATTRIBUTE, type_id, attr_id=attr.id,
@@ -687,18 +528,8 @@ class CITypeAttributeManager(object):
attr_id = _from.get('attr_id') attr_id = _from.get('attr_id')
from_group_id = _from.get('group_id') from_group_id = _from.get('group_id')
to_group_id = _to.get('group_id') to_group_id = _to.get('group_id')
from_group_name = _from.get('group_name')
to_group_name = _to.get('group_name')
order = _to.get('order') order = _to.get('order')
if from_group_name:
from_group = CITypeAttributeGroup.get_by(type_id=type_id, name=from_group_name, first=True, to_dict=False)
from_group_id = from_group and from_group.id
if to_group_name:
to_group = CITypeAttributeGroup.get_by(type_id=type_id, name=to_group_name, first=True, to_dict=False)
to_group_id = to_group and to_group.id
if from_group_id != to_group_id: if from_group_id != to_group_id:
if from_group_id is not None: if from_group_id is not None:
CITypeAttributeGroupManager.delete_item(from_group_id, attr_id) CITypeAttributeGroupManager.delete_item(from_group_id, attr_id)
@@ -824,15 +655,15 @@ class CITypeRelationManager(object):
current_app.logger.warning(str(e)) current_app.logger.warning(str(e))
return abort(400, ErrFormat.circular_dependency_error) return abort(400, ErrFormat.circular_dependency_error)
# if constraint == ConstraintEnum.Many2Many: if constraint == ConstraintEnum.Many2Many:
# other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many, other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
# to_dict=False, first=True) to_dict=False, first=True)
# other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many, other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
# to_dict=False, first=True) to_dict=False, first=True)
# if other_c and other_c.child_id != c.id: if other_c and other_c.child_id != c.id:
# return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name)) return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
# if other_p and other_p.parent_id != p.id: if other_p and other_p.parent_id != p.id:
# return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name)) return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
existed = cls._get(p.id, c.id) existed = cls._get(p.id, c.id)
if existed is not None: if existed is not None:
@@ -907,66 +738,25 @@ class CITypeAttributeGroupManager(object):
@staticmethod @staticmethod
def get_by_type_id(type_id, need_other=False): def get_by_type_id(type_id, need_other=False):
parent_ids = CITypeInheritanceManager.base(type_id) groups = CITypeAttributeGroup.get_by(type_id=type_id)
groups = sorted(groups, key=lambda x: x["order"] or 0)
groups = [] grouped = list()
id2type = {i: CITypeCache.get(i).alias for i in parent_ids}
for _type_id in parent_ids + [type_id]:
_groups = CITypeAttributeGroup.get_by(type_id=_type_id)
_groups = sorted(_groups, key=lambda x: x["order"] or 0)
for i in _groups:
if type_id != _type_id:
i['inherited'] = True
i['inherited_from'] = id2type[_type_id]
else:
i['inherited'] = False
groups.extend(_groups)
grouped = set()
attributes = CITypeAttributeManager.get_attributes_by_type_id(type_id) attributes = CITypeAttributeManager.get_attributes_by_type_id(type_id)
id2attr = {i.get('id'): i for i in attributes} id2attr = {i.get('id'): i for i in attributes}
group2pos = dict()
attr2pos = dict()
result = []
for group in groups: for group in groups:
items = CITypeAttributeGroupItem.get_by(group_id=group["id"], to_dict=False) items = CITypeAttributeGroupItem.get_by(group_id=group["id"], to_dict=False)
items = sorted(items, key=lambda x: x.order or 0) items = sorted(items, key=lambda x: x.order or 0)
group["attributes"] = [id2attr.get(i.attr_id) for i in items if i.attr_id in id2attr]
if group['name'] not in group2pos: grouped.extend([i.attr_id for i in items])
group_pos = len(result)
group['attributes'] = []
result.append(group)
group2pos[group['name']] = group_pos
else:
group_pos = group2pos[group['name']]
attr = None
for i in items:
if i.attr_id in id2attr:
attr = id2attr[i.attr_id]
attr['inherited'] = group['inherited']
attr['inherited_from'] = group.get('inherited_from')
result[group_pos]['attributes'].append(attr)
if i.attr_id in attr2pos:
result[attr2pos[i.attr_id][0]]['attributes'].remove(attr2pos[i.attr_id][1])
attr2pos[i.attr_id] = [group_pos, attr]
group.pop('inherited_from', None)
grouped |= set([i.attr_id for i in items])
if need_other: if need_other:
grouped = set(grouped) grouped = set(grouped)
other_attributes = [attr for attr in attributes if attr["id"] not in grouped] other_attributes = [attr for attr in attributes if attr["id"] not in grouped]
result.append(dict(attributes=other_attributes)) groups.append(dict(attributes=other_attributes))
return result return groups
@staticmethod @staticmethod
def create_or_update(type_id, name, attr_order, group_order=0, is_update=False): def create_or_update(type_id, name, attr_order, group_order=0, is_update=False):
@@ -1100,16 +890,10 @@ class CITypeAttributeGroupManager(object):
@classmethod @classmethod
def transfer(cls, type_id, _from, _to): def transfer(cls, type_id, _from, _to):
current_app.logger.info("CIType[{0}] {1} -> {2}".format(type_id, _from, _to)) current_app.logger.info("CIType[{0}] {1} -> {2}".format(type_id, _from, _to))
if isinstance(_from, int): from_group = CITypeAttributeGroup.get_by_id(_from)
from_group = CITypeAttributeGroup.get_by_id(_from)
else:
from_group = CITypeAttributeGroup.get_by(name=_from, first=True, to_dict=False)
from_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_from))) from_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_from)))
if isinstance(_to, int): to_group = CITypeAttributeGroup.get_by_id(_to)
to_group = CITypeAttributeGroup.get_by_id(_to)
else:
to_group = CITypeAttributeGroup.get_by(name=_to, first=True, to_dict=False)
to_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_to))) to_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_to)))
from_order, to_order = from_group.order, to_group.order from_order, to_order = from_group.order, to_group.order
@@ -1490,7 +1274,7 @@ class CITypeTemplateManager(object):
from api.lib.common_setting.upload_file import CommonFileCRUD from api.lib.common_setting.upload_file import CommonFileCRUD
tpt = dict( tpt = dict(
ci_types=CITypeManager.get_ci_types(type_name=ci_type.name, like=False), ci_types=CITypeManager.get_ci_types(type_name=ci_type.name),
ci_type_auto_discovery_rules=list(), ci_type_auto_discovery_rules=list(),
type2attributes=dict(), type2attributes=dict(),
type2attribute_group=dict(), type2attribute_group=dict(),

View File

@@ -1,15 +1,12 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import copy
import functools import functools
import redis_lock
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask import request from flask import request
from flask_login import current_user from flask_login import current_user
from api.extensions import db
from api.extensions import rd
from api.lib.cmdb.const import ResourceTypeEnum from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.mixin import DBMixin from api.lib.mixin import DBMixin
@@ -43,11 +40,6 @@ class CIFilterPermsCRUD(DBMixin):
result[i['rid']]['ci_filter'] = "" result[i['rid']]['ci_filter'] = ""
result[i['rid']]['ci_filter'] += (i['ci_filter'] or "") result[i['rid']]['ci_filter'] += (i['ci_filter'] or "")
if i['id_filter']:
if not result[i['rid']]['id_filter']:
result[i['rid']]['id_filter'] = {}
result[i['rid']]['id_filter'].update(i['id_filter'] or {})
return result return result
def get_by_ids(self, _ids, type_id=None): def get_by_ids(self, _ids, type_id=None):
@@ -78,11 +70,6 @@ class CIFilterPermsCRUD(DBMixin):
result[i['type_id']]['ci_filter'] = "" result[i['type_id']]['ci_filter'] = ""
result[i['type_id']]['ci_filter'] += (i['ci_filter'] or "") result[i['type_id']]['ci_filter'] += (i['ci_filter'] or "")
if i['id_filter']:
if not result[i['type_id']]['id_filter']:
result[i['type_id']]['id_filter'] = {}
result[i['type_id']]['id_filter'].update(i['id_filter'] or {})
return result return result
@classmethod @classmethod
@@ -95,54 +82,6 @@ class CIFilterPermsCRUD(DBMixin):
type2filter_perms = cls().get_by_ids(list(map(int, [i['name'] for i in res2])), type_id=type_id) type2filter_perms = cls().get_by_ids(list(map(int, [i['name'] for i in res2])), type_id=type_id)
return type2filter_perms.get(type_id, {}).get('attr_filter') or [] return type2filter_perms.get(type_id, {}).get('attr_filter') or []
def _revoke_children(self, rid, id_filter, rebuild=True):
items = self.cls.get_by(rid=rid, ci_filter=None, attr_filter=None, to_dict=False)
for item in items:
changed, item_id_filter = False, copy.deepcopy(item.id_filter)
for prefix in id_filter:
for k, v in copy.deepcopy((item.id_filter or {})).items():
if k.startswith(prefix) and k != prefix:
item_id_filter.pop(k)
changed = True
if not item_id_filter and current_app.config.get('USE_ACL'):
item.soft_delete(commit=False)
ACLManager().del_resource(str(item.id), ResourceTypeEnum.CI_FILTER, rebuild=rebuild)
elif changed:
item.update(id_filter=item_id_filter, commit=False)
db.session.commit()
def _revoke_parent(self, rid, parent_path, rebuild=True):
parent_path = [i for i in parent_path.split(',') if i] or []
revoke_nodes = [','.join(parent_path[:i]) for i in range(len(parent_path), 0, -1)]
for node_path in revoke_nodes:
delete_item, can_deleted = None, True
items = self.cls.get_by(rid=rid, ci_filter=None, attr_filter=None, to_dict=False)
for item in items:
if node_path in item.id_filter:
delete_item = item
if any(filter(lambda x: x.startswith(node_path) and x != node_path, item.id_filter.keys())):
can_deleted = False
break
if can_deleted and delete_item:
id_filter = copy.deepcopy(delete_item.id_filter)
id_filter.pop(node_path)
delete_item = delete_item.update(id_filter=id_filter, filter_none=False)
if current_app.config.get('USE_ACL') and not id_filter:
ACLManager().del_resource(str(delete_item.id), ResourceTypeEnum.CI_FILTER, rebuild=False)
delete_item.soft_delete()
items.remove(delete_item)
if rebuild:
from api.tasks.acl import role_rebuild
from api.lib.perm.acl.const import ACL_QUEUE
from api.lib.perm.acl.cache import AppCache
role_rebuild.apply_async(args=(rid, AppCache.get('cmdb').id), queue=ACL_QUEUE)
def _can_add(self, **kwargs): def _can_add(self, **kwargs):
ci_filter = kwargs.get('ci_filter') ci_filter = kwargs.get('ci_filter')
attr_filter = kwargs.get('attr_filter') or "" attr_filter = kwargs.get('attr_filter') or ""
@@ -163,67 +102,34 @@ class CIFilterPermsCRUD(DBMixin):
def add(self, **kwargs): def add(self, **kwargs):
kwargs = self._can_add(**kwargs) or kwargs kwargs = self._can_add(**kwargs) or kwargs
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid'])):
request_id_filter = {}
if kwargs.get('id_filter'):
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
rid=kwargs.get('rid'),
ci_filter=None,
attr_filter=None,
first=True, to_dict=False)
for _id, v in (kwargs.get('id_filter') or {}).items(): obj = self.cls.get_by(type_id=kwargs.get('type_id'),
key = ",".join(([v['parent_path']] if v.get('parent_path') else []) + [str(_id)]) rid=kwargs.get('rid'),
request_id_filter[key] = v['name'] first=True, to_dict=False)
if obj is not None:
obj = obj.update(filter_none=False, **kwargs)
if not obj.attr_filter and not obj.ci_filter:
if current_app.config.get('USE_ACL'):
ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER)
else: obj.soft_delete()
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
rid=kwargs.get('rid'),
id_filter=None,
first=True, to_dict=False)
is_recursive = kwargs.pop('is_recursive', 0)
if obj is not None:
if obj.id_filter and isinstance(kwargs.get('id_filter'), dict):
obj_id_filter = copy.deepcopy(obj.id_filter)
for k, v in request_id_filter.items():
obj_id_filter[k] = v
kwargs['id_filter'] = obj_id_filter
obj = obj.update(filter_none=False, **kwargs)
if not obj.attr_filter and not obj.ci_filter and not obj.id_filter:
if current_app.config.get('USE_ACL'):
ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER, rebuild=False)
obj.soft_delete()
if not is_recursive and request_id_filter:
self._revoke_children(obj.rid, request_id_filter, rebuild=False)
else:
if not kwargs.get('ci_filter') and not kwargs.get('attr_filter'):
return return
else: obj = self.cls.create(**kwargs)
if not kwargs.get('ci_filter') and not kwargs.get('attr_filter') and not kwargs.get('id_filter'):
return
if request_id_filter: if current_app.config.get('USE_ACL'):
kwargs['id_filter'] = request_id_filter try:
ACLManager().add_resource(obj.id, ResourceTypeEnum.CI_FILTER)
except:
pass
ACLManager().grant_resource_to_role_by_rid(obj.id,
kwargs.get('rid'),
ResourceTypeEnum.CI_FILTER)
obj = self.cls.create(**kwargs) return obj
if current_app.config.get('USE_ACL'): # new resource
try:
ACLManager().add_resource(obj.id, ResourceTypeEnum.CI_FILTER)
except:
pass
ACLManager().grant_resource_to_role_by_rid(obj.id,
kwargs.get('rid'),
ResourceTypeEnum.CI_FILTER)
return obj
def _can_update(self, **kwargs): def _can_update(self, **kwargs):
pass pass
@@ -232,84 +138,19 @@ class CIFilterPermsCRUD(DBMixin):
pass pass
def delete(self, **kwargs): def delete(self, **kwargs):
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid'])): obj = self.cls.get_by(type_id=kwargs.get('type_id'),
obj = self.cls.get_by(type_id=kwargs.get('type_id'), rid=kwargs.get('rid'),
rid=kwargs.get('rid'), first=True, to_dict=False)
id_filter=None,
first=True, to_dict=False)
if obj is not None:
resource = None
if current_app.config.get('USE_ACL'):
resource = ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER)
obj.soft_delete()
return resource
def delete2(self, **kwargs):
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid'])):
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
rid=kwargs.get('rid'),
ci_filter=None,
attr_filter=None,
first=True, to_dict=False)
request_id_filter = {}
for _id, v in (kwargs.get('id_filter') or {}).items():
key = ",".join([v['parent_path']] if v.get('parent_path') else [] + [str(_id)])
request_id_filter[key] = v['name']
if obj is not None:
resource = None resource = None
if obj is not None: if current_app.config.get('USE_ACL'):
resource = ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER)
id_filter = {} obj.soft_delete()
for k, v in copy.deepcopy(obj.id_filter or {}).items(): # important
if k not in request_id_filter:
id_filter[k] = v
if not id_filter and current_app.config.get('USE_ACL'):
resource = ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER, rebuild=False)
obj.soft_delete()
db.session.commit()
else:
obj.update(id_filter=id_filter)
self._revoke_children(kwargs.get('rid'), request_id_filter, rebuild=False)
self._revoke_parent(kwargs.get('rid'), kwargs.get('parent_path'))
return resource return resource
def delete_id_filter_by_ci_id(self, ci_id):
items = self.cls.get_by(ci_filter=None, attr_filter=None, to_dict=False)
rebuild_roles = set()
for item in items:
id_filter = copy.deepcopy(item.id_filter)
changed = False
for node_path in item.id_filter:
if str(ci_id) in node_path:
id_filter.pop(node_path)
changed = True
if changed:
rebuild_roles.add(item.rid)
if not id_filter:
item.soft_delete(commit=False)
else:
item.update(id_filter=id_filter, commit=False)
db.session.commit()
if rebuild_roles:
from api.tasks.acl import role_rebuild
from api.lib.perm.acl.const import ACL_QUEUE
from api.lib.perm.acl.cache import AppCache
for rid in rebuild_roles:
role_rebuild.apply_async(args=(rid, AppCache.get('cmdb').id), queue=ACL_QUEUE)
def has_perm_for_ci(arg_name, resource_type, perm, callback=None, app=None): def has_perm_for_ci(arg_name, resource_type, perm, callback=None, app=None):
def decorator_has_perm(func): def decorator_has_perm(func):

View File

@@ -14,8 +14,6 @@ from api.lib.cmdb.attribute import AttributeManager
from api.lib.cmdb.cache import AttributeCache from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeAttributesCache from api.lib.cmdb.cache import CITypeAttributesCache
from api.lib.cmdb.cache import CITypeCache from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.cache import CMDBCounterCache
from api.lib.cmdb.ci_type import CITypeAttributeManager
from api.lib.cmdb.const import ConstraintEnum from api.lib.cmdb.const import ConstraintEnum
from api.lib.cmdb.const import PermEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum from api.lib.cmdb.const import ResourceTypeEnum
@@ -26,7 +24,6 @@ from api.lib.exception import AbortException
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager
from api.models.cmdb import CITypeAttribute from api.models.cmdb import CITypeAttribute
from api.models.cmdb import CITypeRelation from api.models.cmdb import CITypeRelation
from api.models.cmdb import PreferenceCITypeOrder
from api.models.cmdb import PreferenceRelationView from api.models.cmdb import PreferenceRelationView
from api.models.cmdb import PreferenceSearchOption from api.models.cmdb import PreferenceSearchOption
from api.models.cmdb import PreferenceShowAttributes from api.models.cmdb import PreferenceShowAttributes
@@ -41,22 +38,13 @@ class PreferenceManager(object):
@staticmethod @staticmethod
def get_types(instance=False, tree=False): 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( types = db.session.query(PreferenceShowAttributes.type_id).filter(
PreferenceShowAttributes.uid == current_user.uid).filter( PreferenceShowAttributes.uid == current_user.uid).filter(
PreferenceShowAttributes.deleted.is_(False)).group_by( PreferenceShowAttributes.deleted.is_(False)).group_by(
PreferenceShowAttributes.type_id).all() if instance else [] 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 [] tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else []
tree_types = sorted(tree_types, key=lambda x: {i.type_id: idx for idx, i in enumerate( type_ids = set([i.type_id for i in types + tree_types])
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] return [CITypeCache.get(type_id).to_dict() for type_id in type_ids]
@@ -71,36 +59,32 @@ class PreferenceManager(object):
:param tree: :param tree:
:return: :return:
""" """
result = dict(self=dict(instance=[], tree=[], type_id2subs_time=dict())) result = dict(self=dict(instance=[], tree=[], type_id2subs_time=dict()),
type_id2users=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: if instance:
types = db.session.query(PreferenceShowAttributes.type_id, types = db.session.query(PreferenceShowAttributes.type_id,
PreferenceShowAttributes.uid, PreferenceShowAttributes.created_at).filter( PreferenceShowAttributes.uid, PreferenceShowAttributes.created_at).filter(
PreferenceShowAttributes.deleted.is_(False)).filter( PreferenceShowAttributes.deleted.is_(False)).group_by(
PreferenceShowAttributes.uid == current_user.uid).group_by(
PreferenceShowAttributes.uid, PreferenceShowAttributes.type_id) PreferenceShowAttributes.uid, PreferenceShowAttributes.type_id)
for i in types: for i in types:
result['self']['instance'].append(i.type_id) if i.uid == current_user.uid:
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")): result['self']['instance'].append(i.type_id)
result['self']['type_id2subs_time'][i.type_id] = i.created_at 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
instance_order = [i.type_id for i in ci_type_order if not i.is_tree] result['type_id2users'].setdefault(i.type_id, []).append(i.uid)
if len(instance_order) == len(result['self']['instance']):
result['self']['instance'] = instance_order
if tree: if tree:
types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) types = PreferenceTreeView.get_by(to_dict=False)
for i in types: for i in types:
result['self']['tree'].append(i.type_id) if i.uid == current_user.uid:
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")): result['self']['tree'].append(i.type_id)
result['self']['type_id2subs_time'][i.type_id] = i.created_at 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
tree_order = [i.type_id for i in ci_type_order if i.is_tree] result['type_id2users'].setdefault(i.type_id, [])
if len(tree_order) == len(result['self']['tree']): if i.uid not in result['type_id2users'][i.type_id]:
result['self']['tree'] = tree_order result['type_id2users'][i.type_id].append(i.uid)
return result return result
@@ -114,8 +98,8 @@ class PreferenceManager(object):
CITypeAttribute, CITypeAttribute.attr_id == PreferenceShowAttributes.attr_id).filter( CITypeAttribute, CITypeAttribute.attr_id == PreferenceShowAttributes.attr_id).filter(
PreferenceShowAttributes.uid == current_user.uid).filter( PreferenceShowAttributes.uid == current_user.uid).filter(
PreferenceShowAttributes.type_id == type_id).filter( PreferenceShowAttributes.type_id == type_id).filter(
PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).group_by( PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).filter(
CITypeAttribute.attr_id).all() CITypeAttribute.type_id == type_id).all()
result = [] result = []
for i in sorted(attrs, key=lambda x: x.PreferenceShowAttributes.order): for i in sorted(attrs, key=lambda x: x.PreferenceShowAttributes.order):
@@ -125,16 +109,17 @@ class PreferenceManager(object):
is_subscribed = True is_subscribed = True
if not attrs: if not attrs:
result = CITypeAttributeManager.get_attributes_by_type_id(type_id, attrs = db.session.query(CITypeAttribute).filter(
choice_web_hook_parse=False, CITypeAttribute.type_id == type_id).filter(
choice_other_parse=False) CITypeAttribute.deleted.is_(False)).filter(
result = [i for i in result if i['default_show']] CITypeAttribute.default_show.is_(True)).order_by(CITypeAttribute.order)
result = [i.attr.to_dict() for i in attrs]
is_subscribed = False is_subscribed = False
for i in result: for i in result:
if i["is_choice"]: if i["is_choice"]:
i.update(dict(choice_value=AttributeManager.get_choice_values( i.update(dict(choice_value=AttributeManager.get_choice_values(
i["id"], i["value_type"], i.get("choice_web_hook"), i.get("choice_other")))) i["id"], i["value_type"], i["choice_web_hook"], i.get("choice_other"))))
return is_subscribed, result return is_subscribed, result
@@ -166,22 +151,9 @@ class PreferenceManager(object):
if i.attr_id not in attr_dict: if i.attr_id not in attr_dict:
i.soft_delete() 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 @staticmethod
def get_tree_view(): 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) 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: for item in res:
if item["levels"]: if item["levels"]:
ci_type = CITypeCache.get(item['type_id']).to_dict() ci_type = CITypeCache.get(item['type_id']).to_dict()
@@ -200,8 +172,8 @@ class PreferenceManager(object):
return res return res
@classmethod @staticmethod
def create_or_update_tree_view(cls, type_id, levels): def create_or_update_tree_view(type_id, levels):
attrs = CITypeAttributesCache.get(type_id) attrs = CITypeAttributesCache.get(type_id)
for idx, i in enumerate(levels): for idx, i in enumerate(levels):
for attr in attrs: for attr in attrs:
@@ -213,12 +185,9 @@ class PreferenceManager(object):
if existed is not None: if existed is not None:
if not levels: if not levels:
existed.soft_delete() existed.soft_delete()
cls.delete_ci_type_order_item(type_id, is_tree=True)
return existed return existed
return existed.update(levels=levels) return existed.update(levels=levels)
elif 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) return PreferenceTreeView.create(levels=levels, type_id=type_id, uid=current_user.uid)
@staticmethod @staticmethod
@@ -387,9 +356,6 @@ class PreferenceManager(object):
for i in PreferenceTreeView.get_by(type_id=type_id, uid=uid, to_dict=False): for i in PreferenceTreeView.get_by(type_id=type_id, uid=uid, to_dict=False):
i.soft_delete() i.soft_delete()
for i in PreferenceCITypeOrder.get_by(type_id=type_id, uid=uid, to_dict=False):
i.soft_delete()
@staticmethod @staticmethod
def can_edit_relation(parent_id, child_id): def can_edit_relation(parent_id, child_id):
views = PreferenceRelationView.get_by(to_dict=False) views = PreferenceRelationView.get_by(to_dict=False)
@@ -415,36 +381,3 @@ class PreferenceManager(object):
return False return False
return True 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)

View File

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

View File

@@ -16,11 +16,10 @@ def search(query=None,
ret_key=RetKey.NAME, ret_key=RetKey.NAME,
count=1, count=1,
sort=None, sort=None,
excludes=None, excludes=None):
use_id_filter=True):
if current_app.config.get("USE_ES"): if current_app.config.get("USE_ES"):
s = SearchFromES(query, fl, facet, page, ret_key, count, sort) s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
else: else:
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes, use_id_filter=use_id_filter) s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes)
return s return s

View File

@@ -62,7 +62,7 @@ QUERY_CI_BY_ATTR_NAME = """
QUERY_CI_BY_ID = """ QUERY_CI_BY_ID = """
SELECT c_cis.id as ci_id SELECT c_cis.id as ci_id
FROM c_cis FROM c_cis
WHERE c_cis.id {} WHERE c_cis.id={}
""" """
QUERY_CI_BY_TYPE = """ QUERY_CI_BY_TYPE = """

View File

@@ -44,10 +44,7 @@ class Search(object):
count=1, count=1,
sort=None, sort=None,
ci_ids=None, ci_ids=None,
excludes=None, excludes=None):
parent_node_perm_passed=False,
use_id_filter=False,
use_ci_filter=True):
self.orig_query = query self.orig_query = query
self.fl = fl or [] self.fl = fl or []
self.excludes = excludes or [] self.excludes = excludes or []
@@ -57,17 +54,12 @@ class Search(object):
self.count = count self.count = count
self.sort = sort self.sort = sort
self.ci_ids = ci_ids or [] self.ci_ids = ci_ids or []
self.raw_ci_ids = copy.deepcopy(self.ci_ids)
self.query_sql = "" self.query_sql = ""
self.type_id_list = [] self.type_id_list = []
self.only_type_query = False self.only_type_query = False
self.parent_node_perm_passed = parent_node_perm_passed
self.use_id_filter = use_id_filter
self.use_ci_filter = use_ci_filter
self.valid_type_names = [] self.valid_type_names = []
self.type2filter_perms = dict() self.type2filter_perms = dict()
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
@staticmethod @staticmethod
def _operator_proc(key): def _operator_proc(key):
@@ -114,7 +106,7 @@ class Search(object):
self.type_id_list.append(str(ci_type.id)) self.type_id_list.append(str(ci_type.id))
if ci_type.id in self.type2filter_perms: if ci_type.id in self.type2filter_perms:
ci_filter = self.type2filter_perms[ci_type.id].get('ci_filter') ci_filter = self.type2filter_perms[ci_type.id].get('ci_filter')
if ci_filter and self.use_ci_filter and not self.use_id_filter: if ci_filter:
sub = [] sub = []
ci_filter = Template(ci_filter).render(user=current_user) ci_filter = Template(ci_filter).render(user=current_user)
for i in ci_filter.split(','): for i in ci_filter.split(','):
@@ -130,14 +122,6 @@ class Search(object):
self.fl = set(self.type2filter_perms[ci_type.id]['attr_filter']) self.fl = set(self.type2filter_perms[ci_type.id]['attr_filter'])
else: else:
self.fl = set(self.fl) & set(self.type2filter_perms[ci_type.id]['attr_filter']) self.fl = set(self.fl) & set(self.type2filter_perms[ci_type.id]['attr_filter'])
if self.type2filter_perms[ci_type.id].get('id_filter') and self.use_id_filter:
if not self.raw_ci_ids:
self.ci_ids = list(self.type2filter_perms[ci_type.id]['id_filter'].keys())
if self.use_id_filter and not self.ci_ids and not self.is_app_admin:
self.raw_ci_ids = [0]
else: else:
raise SearchError(ErrFormat.no_permission.format(ci_type.alias, PermEnum.READ)) raise SearchError(ErrFormat.no_permission.format(ci_type.alias, PermEnum.READ))
else: else:
@@ -154,10 +138,7 @@ class Search(object):
@staticmethod @staticmethod
def _id_query_handler(v): def _id_query_handler(v):
if ";" in v: return QUERY_CI_BY_ID.format(v)
return QUERY_CI_BY_ID.format("in {}".format(v.replace(';', ',')))
else:
return QUERY_CI_BY_ID.format("= {}".format(v))
@staticmethod @staticmethod
def _in_query_handler(attr, v, is_not): def _in_query_handler(attr, v, is_not):
@@ -171,7 +152,6 @@ class Search(object):
"NOT LIKE" if is_not else "LIKE", "NOT LIKE" if is_not else "LIKE",
_v.replace("*", "%")) for _v in new_v]) _v.replace("*", "%")) for _v in new_v])
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, in_query) _query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, in_query)
return _query_sql return _query_sql
@staticmethod @staticmethod
@@ -187,7 +167,6 @@ class Search(object):
"NOT BETWEEN" if is_not else "BETWEEN", "NOT BETWEEN" if is_not else "BETWEEN",
start.replace("*", "%"), end.replace("*", "%")) start.replace("*", "%"), end.replace("*", "%"))
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, range_query) _query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, range_query)
return _query_sql return _query_sql
@staticmethod @staticmethod
@@ -204,7 +183,6 @@ class Search(object):
comparison_query = "{0} '{1}'".format(v[0], v[1:].replace("*", "%")) comparison_query = "{0} '{1}'".format(v[0], v[1:].replace("*", "%"))
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, comparison_query) _query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, comparison_query)
return _query_sql return _query_sql
@staticmethod @staticmethod
@@ -216,7 +194,6 @@ class Search(object):
elif field.startswith("-"): elif field.startswith("-"):
field = field[1:] field = field[1:]
sort_type = "DESC" sort_type = "DESC"
return field, sort_type return field, sort_type
def __sort_by_id(self, sort_type, query_sql): def __sort_by_id(self, sort_type, query_sql):
@@ -345,11 +322,6 @@ class Search(object):
return numfound, res return numfound, res
def __get_type2filter_perms(self):
res2 = ACLManager('cmdb').get_resources(ResourceTypeEnum.CI_FILTER)
if res2:
self.type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
def __get_types_has_read(self): def __get_types_has_read(self):
""" """
:return: _type:(type1;type2) :return: _type:(type1;type2)
@@ -359,23 +331,14 @@ class Search(object):
self.valid_type_names = {i['name'] for i in res if PermEnum.READ in i['permissions']} self.valid_type_names = {i['name'] for i in res if PermEnum.READ in i['permissions']}
self.__get_type2filter_perms() res2 = acl.get_resources(ResourceTypeEnum.CI_FILTER)
if res2:
for type_id in self.type2filter_perms: self.type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
ci_type = CITypeCache.get(type_id)
if ci_type:
if self.type2filter_perms[type_id].get('id_filter'):
if self.use_id_filter:
self.valid_type_names.add(ci_type.name)
elif self.type2filter_perms[type_id].get('ci_filter'):
if self.use_ci_filter:
self.valid_type_names.add(ci_type.name)
else:
self.valid_type_names.add(ci_type.name)
return "_type:({})".format(";".join(self.valid_type_names)) return "_type:({})".format(";".join(self.valid_type_names))
def __confirm_type_first(self, queries): def __confirm_type_first(self, queries):
has_type = False has_type = False
result = [] result = []
@@ -408,10 +371,8 @@ class Search(object):
else: else:
result.append(q) result.append(q)
if self.parent_node_perm_passed: _is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
self.__get_type2filter_perms() if result and not has_type and not _is_app_admin:
self.valid_type_names = "ALL"
elif result and not has_type and not self.is_app_admin:
type_q = self.__get_types_has_read() type_q = self.__get_types_has_read()
if id_query: if id_query:
ci = CIManager.get_by_id(id_query) ci = CIManager.get_by_id(id_query)
@@ -420,11 +381,13 @@ class Search(object):
result.insert(0, "_type:{}".format(ci.type_id)) result.insert(0, "_type:{}".format(ci.type_id))
else: else:
result.insert(0, type_q) result.insert(0, type_q)
elif self.is_app_admin: elif _is_app_admin:
self.valid_type_names = "ALL" self.valid_type_names = "ALL"
else: else:
self.__get_types_has_read() self.__get_types_has_read()
current_app.logger.warning(result)
return result return result
def __query_by_attr(self, q, queries, alias): def __query_by_attr(self, q, queries, alias):
@@ -516,7 +479,7 @@ class Search(object):
def _filter_ids(self, query_sql): def _filter_ids(self, query_sql):
if self.ci_ids: if self.ci_ids:
return "SELECT * FROM ({0}) AS IN_QUERY WHERE IN_QUERY.ci_id IN ({1})".format( return "SELECT * FROM ({0}) AS IN_QUERY WHERE IN_QUERY.ci_id IN ({1})".format(
query_sql, ",".join(list(set(map(str, self.ci_ids))))) query_sql, ",".join(list(map(str, self.ci_ids))))
return query_sql return query_sql
@@ -548,9 +511,6 @@ class Search(object):
s = time.time() s = time.time()
if query_sql: if query_sql:
query_sql = self._filter_ids(query_sql) query_sql = self._filter_ids(query_sql)
if self.raw_ci_ids and not self.ci_ids:
return 0, []
self.query_sql = query_sql self.query_sql = query_sql
# current_app.logger.debug(query_sql) # current_app.logger.debug(query_sql)
numfound, res = self._execute_sql(query_sql) numfound, res = self._execute_sql(query_sql)
@@ -609,8 +569,3 @@ class Search(object):
total = len(response) total = len(response)
return response, counter, total, self.page, numfound, facet return response, counter, total, self.page, numfound, facet
def get_ci_ids(self):
_, ci_ids = self._query_build_raw()
return ci_ids

View File

@@ -1,11 +1,9 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import json import json
import sys
from collections import Counter from collections import Counter
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask_login import current_user
from api.extensions import rd from api.extensions import rd
from api.lib.cmdb.ci import CIRelationManager from api.lib.cmdb.ci import CIRelationManager
@@ -13,14 +11,11 @@ from api.lib.cmdb.ci_type import CITypeRelationManager
from api.lib.cmdb.const import ConstraintEnum from api.lib.cmdb.const import ConstraintEnum
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2 from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import is_app_admin
from api.models.cmdb import CI from api.models.cmdb import CI
from api.models.cmdb import CIRelation
class Search(object): class Search(object):
@@ -34,9 +29,7 @@ class Search(object):
sort=None, sort=None,
reverse=False, reverse=False,
ancestor_ids=None, ancestor_ids=None,
descendant_ids=None, has_m2m=None):
has_m2m=None,
root_parent_path=None):
self.orig_query = query self.orig_query = query
self.fl = fl self.fl = fl
self.facet_field = facet_field self.facet_field = facet_field
@@ -53,8 +46,6 @@ class Search(object):
level[0] if isinstance(level, list) and level else level) level[0] if isinstance(level, list) and level else level)
self.ancestor_ids = ancestor_ids self.ancestor_ids = ancestor_ids
self.descendant_ids = descendant_ids
self.root_parent_path = root_parent_path
self.has_m2m = has_m2m or False self.has_m2m = has_m2m or False
if not self.has_m2m: if not self.has_m2m:
if self.ancestor_ids: if self.ancestor_ids:
@@ -65,23 +56,27 @@ class Search(object):
if _l < int(level) and c == ConstraintEnum.Many2Many: if _l < int(level) and c == ConstraintEnum.Many2Many:
self.has_m2m = True self.has_m2m = True
self.type2filter_perms = None
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
def _get_ids(self, ids): def _get_ids(self, ids):
if self.level[-1] == 1 and len(ids) == 1:
if self.ancestor_ids is None:
return [i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0], to_dict=False)]
else:
seconds = {i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0],
ancestor_ids=self.ancestor_ids,
to_dict=False)}
return list(seconds)
merge_ids = [] merge_ids = []
key = [] key = []
_tmp = [] _tmp = []
for level in range(1, sorted(self.level)[-1] + 1): for level in range(1, sorted(self.level)[-1] + 1):
if len(self.descendant_ids) >= level and self.type2filter_perms.get(self.descendant_ids[level - 1]):
id_filter_limit, _ = self._get_ci_filter(self.type2filter_perms[self.descendant_ids[level - 1]])
else:
id_filter_limit = {}
if not self.has_m2m: if not self.has_m2m:
key, prefix = list(map(str, ids)), REDIS_PREFIX_CI_RELATION _tmp = map(lambda x: json.loads(x).keys(),
filter(lambda x: x is not None, rd.get(ids, REDIS_PREFIX_CI_RELATION) or []))
ids = [j for i in _tmp for j in i]
key, prefix = ids, REDIS_PREFIX_CI_RELATION
else: else:
if not self.ancestor_ids: if not self.ancestor_ids:
@@ -97,16 +92,12 @@ class Search(object):
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]])) key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
prefix = REDIS_PREFIX_CI_RELATION2 prefix = REDIS_PREFIX_CI_RELATION2
if not key or id_filter_limit is None: _tmp = list(map(lambda x: json.loads(x).keys() if x else [], rd.get(key, prefix) or []))
ids = [j for i in _tmp for j in i]
if not key:
return [] return []
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
_tmp = [[i[0] for i in x if (not id_filter_limit or (
key[idx] not in id_filter_limit or int(i[0]) in id_filter_limit[key[idx]]) or
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
ids = [j for i in _tmp for j in i]
if level in self.level: if level in self.level:
merge_ids.extend(ids) merge_ids.extend(ids)
@@ -129,28 +120,7 @@ class Search(object):
return merge_ids return merge_ids
def _has_read_perm_from_parent_nodes(self):
self.root_parent_path = list(map(str, self.root_parent_path))
if str(self.root_id).isdigit() and str(self.root_id) not in self.root_parent_path:
self.root_parent_path.append(str(self.root_id))
self.root_parent_path = set(self.root_parent_path)
if self.is_app_admin:
self.type2filter_perms = {}
return True
res = ACLManager().get_resources(ResourceTypeEnum.CI_FILTER) or {}
self.type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res]))) or {}
for _, filters in self.type2filter_perms.items():
if set((filters.get('id_filter') or {}).keys()) & self.root_parent_path:
return True
return True
def search(self): def search(self):
use_ci_filter = len(self.descendant_ids) == self.level[0] - 1
parent_node_perm_passed = self._has_read_perm_from_parent_nodes()
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids] cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids]
@@ -191,105 +161,42 @@ class Search(object):
page=self.page, page=self.page,
count=self.count, count=self.count,
sort=self.sort, sort=self.sort,
ci_ids=merge_ids, ci_ids=merge_ids).search()
parent_node_perm_passed=parent_node_perm_passed,
use_ci_filter=use_ci_filter).search()
def _get_ci_filter(self, filter_perms, ci_filters=None): def statistics(self, type_ids):
ci_filters = ci_filters or []
if ci_filters:
result = {}
for item in ci_filters:
res = SearchFromDB('_type:{},{}'.format(item['type_id'], item['ci_filter']),
count=sys.maxsize, parent_node_perm_passed=True).get_ci_ids()
if res:
result[item['type_id']] = set(res)
return {}, result if result else None
result = dict()
if filter_perms.get('id_filter'):
for k in filter_perms['id_filter']:
node_path = k.split(',')
if len(node_path) == 1:
result[int(node_path[0])] = 1
elif not self.has_m2m:
result.setdefault(node_path[-2], set()).add(int(node_path[-1]))
else:
result.setdefault(','.join(node_path[:-1]), set()).add(int(node_path[-1]))
if result:
return result, None
else:
return None, None
return {}, None
def statistics(self, type_ids, need_filter=True):
self.level = int(self.level) self.level = int(self.level)
acl = ACLManager('cmdb')
type2filter_perms = dict()
if not self.is_app_admin:
res2 = acl.get_resources(ResourceTypeEnum.CI_FILTER)
if res2:
type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
_tmp = [] _tmp = []
level2ids = {} level2ids = {}
for lv in range(1, self.level + 1): for lv in range(1, self.level + 1):
level2ids[lv] = [] level2ids[lv] = []
if need_filter:
id_filter_limit, ci_filter_limit = None, None
if len(self.descendant_ids or []) >= lv and type2filter_perms.get(self.descendant_ids[lv - 1]):
id_filter_limit, _ = self._get_ci_filter(type2filter_perms[self.descendant_ids[lv - 1]])
elif type_ids and self.level == lv:
ci_filters = [type2filter_perms[type_id] for type_id in type_ids if type_id in type2filter_perms]
if ci_filters:
id_filter_limit, ci_filter_limit = self._get_ci_filter({}, ci_filters=ci_filters)
else:
id_filter_limit = {}
else:
id_filter_limit = {}
else:
id_filter_limit, ci_filter_limit = {}, {}
if lv == 1: if lv == 1:
if not self.has_m2m: if not self.has_m2m:
key, prefix = [str(i) for i in ids], REDIS_PREFIX_CI_RELATION key, prefix = ids, REDIS_PREFIX_CI_RELATION
else: else:
key = ["{},{}".format(self.ancestor_ids, _id) for _id in ids]
if not self.ancestor_ids: if not self.ancestor_ids:
key, prefix = [str(i) for i in ids], REDIS_PREFIX_CI_RELATION key, prefix = ids, REDIS_PREFIX_CI_RELATION
else: else:
key = ["{},{}".format(self.ancestor_ids, _id) for _id in ids]
prefix = REDIS_PREFIX_CI_RELATION2 prefix = REDIS_PREFIX_CI_RELATION2
level2ids[lv] = [[i] for i in key] level2ids[lv] = [[i] for i in key]
if not key or id_filter_limit is None: if not key:
_tmp = [[]] * len(ids) _tmp = []
continue continue
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
_tmp = []
if type_ids and lv == self.level: if type_ids and lv == self.level:
_tmp = [[i for i in x if i[1] in type_ids and _tmp = list(map(lambda x: [i for i in x if i[1] in type_ids],
(not id_filter_limit or (key[idx] not in id_filter_limit or (map(lambda x: list(json.loads(x).items()),
int(i[0]) in id_filter_limit[key[idx]]) or [i or '{}' for i in rd.get(key, prefix) or []]))))
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
else: else:
_tmp = [[i for i in x if (not id_filter_limit or (key[idx] not in id_filter_limit or _tmp = list(map(lambda x: list(json.loads(x).items()),
int(i[0]) in id_filter_limit[key[idx]]) or [i or '{}' for i in rd.get(key, prefix) or []]))
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
if ci_filter_limit:
_tmp = [[j for j in i if j[1] not in ci_filter_limit or int(j[0]) in ci_filter_limit[j[1]]]
for i in _tmp]
else: else:
for idx, item in enumerate(_tmp): for idx, item in enumerate(_tmp):
if item: if item:
if not self.has_m2m: if not self.has_m2m:
@@ -301,22 +208,15 @@ class Search(object):
level2ids[lv].append(key) level2ids[lv].append(key)
if key: if key:
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
if type_ids and lv == self.level: if type_ids and lv == self.level:
__tmp = [[i for i in x if i[1] in type_ids and __tmp = map(lambda x: [(_id, type_id) for _id, type_id in json.loads(x).items()
(not id_filter_limit or ( if type_id in type_ids],
key[idx] not in id_filter_limit or filter(lambda x: x is not None,
int(i[0]) in id_filter_limit[key[idx]]) or rd.get(key, prefix) or []))
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
else: else:
__tmp = [[i for i in x if (not id_filter_limit or ( __tmp = map(lambda x: list(json.loads(x).items()),
key[idx] not in id_filter_limit or filter(lambda x: x is not None,
int(i[0]) in id_filter_limit[key[idx]]) or rd.get(key, prefix) or []))
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
if ci_filter_limit:
__tmp = [[j for j in i if j[1] not in ci_filter_limit or
int(j[0]) in ci_filter_limit[j[1]]] for i in __tmp]
else: else:
__tmp = [] __tmp = []

View File

@@ -5,10 +5,10 @@ from __future__ import unicode_literals
import copy import copy
import imp import imp
import jinja2
import os import os
import re
import tempfile import tempfile
import jinja2
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from jinja2schema import infer from jinja2schema import infer
@@ -117,11 +117,6 @@ class AttributeValueManager(object):
if type_attr and type_attr.is_required and not value and value != 0: if type_attr and type_attr.is_required and not value and value != 0:
return abort(400, ErrFormat.attribute_value_required.format(attr.alias)) 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): def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
ci = ci or {} ci = ci or {}
v = self._deserialize_value(attr.value_type, value) v = self._deserialize_value(attr.value_type, value)
@@ -135,9 +130,6 @@ class AttributeValueManager(object):
if v == "" and attr.value_type not in (ValueTypeEnum.TEXT,): if v == "" and attr.value_type not in (ValueTypeEnum.TEXT,):
v = None v = None
if attr.re_check and value:
self.check_re(attr.re_check, value)
return v return v
@staticmethod @staticmethod
@@ -302,9 +294,9 @@ class AttributeValueManager(object):
return self.write_change2(changed) return self.write_change2(changed)
@staticmethod @staticmethod
def delete_attr_value(attr_id, ci_id, commit=True): def delete_attr_value(attr_id, ci_id):
attr = AttributeCache.get(attr_id) attr = AttributeCache.get(attr_id)
if attr is not None: if attr is not None:
value_table = TableMap(attr=attr).table value_table = TableMap(attr=attr).table
for item in value_table.get_by(attr_id=attr.id, ci_id=ci_id, to_dict=False): for item in value_table.get_by(attr_id=attr.id, ci_id=ci_id, to_dict=False):
item.delete(commit=commit) item.delete()

View File

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

View File

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

View File

@@ -2,11 +2,10 @@
import msgpack import msgpack
import redis_lock
from api.extensions import cache from api.extensions import cache
from api.extensions import rd
from api.lib.decorator import flush_db from api.lib.decorator import flush_db
from api.lib.utils import Lock
from api.models.acl import App from api.models.acl import App
from api.models.acl import Permission from api.models.acl import Permission
from api.models.acl import Resource from api.models.acl import Resource
@@ -137,14 +136,14 @@ class HasResourceRoleCache(object):
@classmethod @classmethod
def add(cls, rid, app_id): def add(cls, rid, app_id):
with redis_lock.Lock(rd.r, 'HasResourceRoleCache'): with Lock('HasResourceRoleCache'):
c = cls.get(app_id) c = cls.get(app_id)
c[rid] = 1 c[rid] = 1
cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0) cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0)
@classmethod @classmethod
def remove(cls, rid, app_id): def remove(cls, rid, app_id):
with redis_lock.Lock(rd.r, 'HasResourceRoleCache'): with Lock('HasResourceRoleCache'):
c = cls.get(app_id) c = cls.get(app_id)
c.pop(rid, None) c.pop(rid, None)
cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0) cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0)

View File

@@ -194,7 +194,7 @@ def validate(ticket):
def _parse_tag(string, tag): def _parse_tag(string, tag):
""" """
Used for parsing xml. Search string for the first occurrence of Used for parsing xml. Search string for the first occurence of
<tag>.....</tag> and return text (stripped of leading and tailing <tag>.....</tag> and return text (stripped of leading and tailing
whitespace) between tags. Return "" if tag not found. whitespace) between tags. Return "" if tag not found.
""" """

View File

@@ -1,6 +1,8 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import base64 import base64
import sys
import time
from typing import Set from typing import Set
import elasticsearch import elasticsearch
@@ -211,6 +213,52 @@ class ESHandler(object):
return 0, [], {} return 0, [], {}
class Lock(object):
def __init__(self, name, timeout=10, app=None, need_lock=True):
self.lock_key = name
self.need_lock = need_lock
self.timeout = timeout
if not app:
app = current_app
self.app = app
try:
self.redis = redis.Redis(host=self.app.config.get('CACHE_REDIS_HOST'),
port=self.app.config.get('CACHE_REDIS_PORT'),
password=self.app.config.get('CACHE_REDIS_PASSWORD'))
except:
self.app.logger.error("cannot connect redis")
raise Exception("cannot connect redis")
def lock(self, timeout=None):
if not timeout:
timeout = self.timeout
retry = 0
while retry < 100:
timestamp = time.time() + timeout + 1
_lock = self.redis.setnx(self.lock_key, timestamp)
if _lock == 1 or (
time.time() > float(self.redis.get(self.lock_key) or sys.maxsize) and
time.time() > float(self.redis.getset(self.lock_key, timestamp) or sys.maxsize)):
break
else:
retry += 1
time.sleep(0.6)
if retry >= 100:
raise Exception("get lock failed...")
def release(self):
if time.time() < float(self.redis.get(self.lock_key)):
self.redis.delete(self.lock_key)
def __enter__(self):
if self.need_lock:
self.lock()
def __exit__(self, exc_type, exc_val, exc_tb):
if self.need_lock:
self.release()
class AESCrypto(object): class AESCrypto(object):
BLOCK_SIZE = 16 # Bytes BLOCK_SIZE = 16 # Bytes
pad = lambda s: s + ((AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE) * pad = lambda s: s + ((AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE) *

View File

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

View File

@@ -57,16 +57,6 @@ class CIType(Model):
uid = db.Column(db.Integer, index=True) uid = db.Column(db.Integer, index=True)
class CITypeInheritance(Model):
__tablename__ = "c_ci_type_inheritance"
parent_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
child_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeInheritance.parent_id")
child = db.relationship("CIType", primaryjoin="CIType.id==CITypeInheritance.child_id")
class CITypeRelation(Model): class CITypeRelation(Model):
__tablename__ = "c_ci_type_relations" __tablename__ = "c_ci_type_relations"
@@ -104,8 +94,6 @@ class Attribute(Model):
_choice_web_hook = db.Column('choice_web_hook', db.JSON) _choice_web_hook = db.Column('choice_web_hook', db.JSON)
choice_other = db.Column(db.JSON) choice_other = db.Column(db.JSON)
re_check = db.Column(db.Text)
uid = db.Column(db.Integer, index=True) uid = db.Column(db.Integer, index=True)
option = db.Column(db.JSON) option = db.Column(db.JSON)
@@ -476,15 +464,6 @@ class PreferenceSearchOption(Model):
option = db.Column(db.JSON) 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 # custom
class CustomDashboard(Model): class CustomDashboard(Model):
__tablename__ = "c_c_d" __tablename__ = "c_c_d"
@@ -569,7 +548,6 @@ class CIFilterPerms(Model):
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id')) type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'))
ci_filter = db.Column(db.Text) ci_filter = db.Column(db.Text)
attr_filter = db.Column(db.Text) attr_filter = db.Column(db.Text)
id_filter = db.Column(db.JSON) # {node_path: unique_value}
rid = db.Column(db.Integer, index=True) rid = db.Column(db.Integer, index=True)

View File

@@ -4,7 +4,6 @@
import json import json
import time import time
import redis_lock
from flask import current_app from flask import current_app
from flask_login import login_user from flask_login import login_user
@@ -18,10 +17,10 @@ from api.lib.cmdb.const import CMDB_QUEUE
from api.lib.cmdb.const import REDIS_PREFIX_CI from api.lib.cmdb.const import REDIS_PREFIX_CI
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2 from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.decorator import flush_db from api.lib.decorator import flush_db
from api.lib.decorator import reconnect_db from api.lib.decorator import reconnect_db
from api.lib.perm.acl.cache import UserCache from api.lib.perm.acl.cache import UserCache
from api.lib.utils import Lock
from api.lib.utils import handle_arg_list from api.lib.utils import handle_arg_list
from api.models.cmdb import CI from api.models.cmdb import CI
from api.models.cmdb import CIRelation from api.models.cmdb import CIRelation
@@ -84,13 +83,6 @@ def ci_delete(ci_id):
current_app.logger.info("{0} delete..........".format(ci_id)) current_app.logger.info("{0} delete..........".format(ci_id))
@celery.task(name="cmdb.delete_id_filter", queue=CMDB_QUEUE)
@reconnect_db
def delete_id_filter(ci_id):
CIFilterPermsCRUD().delete_id_filter_by_ci_id(ci_id)
@celery.task(name="cmdb.ci_delete_trigger", queue=CMDB_QUEUE) @celery.task(name="cmdb.ci_delete_trigger", queue=CMDB_QUEUE)
@reconnect_db @reconnect_db
def ci_delete_trigger(trigger, operate_type, ci_dict): def ci_delete_trigger(trigger, operate_type, ci_dict):
@@ -107,7 +99,7 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
@flush_db @flush_db
@reconnect_db @reconnect_db
def ci_relation_cache(parent_id, child_id, ancestor_ids): def ci_relation_cache(parent_id, child_id, ancestor_ids):
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)): with Lock("CIRelation_{}".format(parent_id)):
if ancestor_ids is None: if ancestor_ids is None:
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0] children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = json.loads(children) if children is not None else {} children = json.loads(children) if children is not None else {}
@@ -185,7 +177,7 @@ def ci_relation_add(parent_dict, child_id, uid):
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE) @celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
@reconnect_db @reconnect_db
def ci_relation_delete(parent_id, child_id, ancestor_ids): def ci_relation_delete(parent_id, child_id, ancestor_ids):
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)): with Lock("CIRelation_{}".format(parent_id)):
if ancestor_ids is None: if ancestor_ids is None:
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0] children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = json.loads(children) if children is not None else {} children = json.loads(children) if children is not None else {}

View File

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

View File

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

View File

@@ -11,7 +11,8 @@ from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.ci import CIManager from api.lib.cmdb.ci import CIManager
from api.lib.cmdb.ci import CIRelationManager from api.lib.cmdb.ci import CIRelationManager
from api.lib.cmdb.const import ExistPolicy from api.lib.cmdb.const import ExistPolicy
from api.lib.cmdb.const import ResourceTypeEnum, PermEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RetKey from api.lib.cmdb.const import RetKey
from api.lib.cmdb.perms import has_perm_for_ci from api.lib.cmdb.perms import has_perm_for_ci
from api.lib.cmdb.search import SearchError from api.lib.cmdb.search import SearchError
@@ -151,10 +152,9 @@ class CISearchView(APIView):
ret_key = RetKey.NAME ret_key = RetKey.NAME
facet = handle_arg_list(request.values.get("facet", "")) facet = handle_arg_list(request.values.get("facet", ""))
sort = request.values.get("sort") sort = request.values.get("sort")
use_id_filter = request.values.get("use_id_filter", False) in current_app.config.get('BOOL_TRUE')
start = time.time() start = time.time()
s = search(query, fl, facet, page, ret_key, count, sort, excludes, use_id_filter=use_id_filter) s = search(query, fl, facet, page, ret_key, count, sort, excludes)
try: try:
response, counter, total, page, numfound, facet = s.search() response, counter, total, page, numfound, facet = s.search()
except SearchError as e: except SearchError as e:

View File

@@ -13,6 +13,7 @@ from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.search import SearchError from api.lib.cmdb.search import SearchError
from api.lib.cmdb.search.ci_relation.search import Search from api.lib.cmdb.search.ci_relation.search import Search
from api.lib.decorator import args_required from api.lib.decorator import args_required
from api.lib.perm.auth import auth_abandoned
from api.lib.utils import get_page from api.lib.utils import get_page
from api.lib.utils import get_page_size from api.lib.utils import get_page_size
from api.lib.utils import handle_arg_list from api.lib.utils import handle_arg_list
@@ -35,8 +36,6 @@ class CIRelationSearchView(APIView):
root_id = request.values.get('root_id') root_id = request.values.get('root_id')
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
root_parent_path = handle_arg_list(request.values.get('root_parent_path') or '')
descendant_ids = list(map(int, handle_arg_list(request.values.get('descendant_ids', []))))
level = list(map(int, handle_arg_list(request.values.get('level', '1')))) level = list(map(int, handle_arg_list(request.values.get('level', '1'))))
query = request.values.get('q', "") query = request.values.get('q', "")
@@ -48,8 +47,7 @@ class CIRelationSearchView(APIView):
start = time.time() start = time.time()
s = Search(root_id, level, query, fl, facet, page, count, sort, reverse, s = Search(root_id, level, query, fl, facet, page, count, sort, reverse,
ancestor_ids=ancestor_ids, has_m2m=has_m2m, root_parent_path=root_parent_path, ancestor_ids=ancestor_ids, has_m2m=has_m2m)
descendant_ids=descendant_ids)
try: try:
response, counter, total, page, numfound, facet = s.search() response, counter, total, page, numfound, facet = s.search()
except SearchError as e: except SearchError as e:
@@ -67,16 +65,16 @@ class CIRelationSearchView(APIView):
class CIRelationStatisticsView(APIView): class CIRelationStatisticsView(APIView):
url_prefix = "/ci_relations/statistics" url_prefix = "/ci_relations/statistics"
@auth_abandoned
def get(self): def get(self):
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids')))) root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
level = request.values.get('level', 1) level = request.values.get('level', 1)
type_ids = set(map(int, handle_arg_list(request.values.get('type_ids', [])))) type_ids = set(map(int, handle_arg_list(request.values.get('type_ids', []))))
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
descendant_ids = list(map(int, handle_arg_list(request.values.get('descendant_ids', []))))
has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE') has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE')
start = time.time() start = time.time()
s = Search(root_ids, level, ancestor_ids=ancestor_ids, descendant_ids=descendant_ids, has_m2m=has_m2m) s = Search(root_ids, level, ancestor_ids=ancestor_ids, has_m2m=has_m2m)
try: try:
result = s.statistics(type_ids) result = s.statistics(type_ids)
except SearchError as e: except SearchError as e:

View File

@@ -14,7 +14,6 @@ from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.ci_type import CITypeAttributeGroupManager from api.lib.cmdb.ci_type import CITypeAttributeGroupManager
from api.lib.cmdb.ci_type import CITypeAttributeManager from api.lib.cmdb.ci_type import CITypeAttributeManager
from api.lib.cmdb.ci_type import CITypeGroupManager from api.lib.cmdb.ci_type import CITypeGroupManager
from api.lib.cmdb.ci_type import CITypeInheritanceManager
from api.lib.cmdb.ci_type import CITypeManager from api.lib.cmdb.ci_type import CITypeManager
from api.lib.cmdb.ci_type import CITypeTemplateManager from api.lib.cmdb.ci_type import CITypeTemplateManager
from api.lib.cmdb.ci_type import CITypeTriggerManager from api.lib.cmdb.ci_type import CITypeTriggerManager
@@ -38,23 +37,15 @@ from api.resource import APIView
class CITypeView(APIView): class CITypeView(APIView):
url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>", url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>")
"/ci_types/icons")
def get(self, type_id=None, type_name=None): def get(self, type_id=None, type_name=None):
if request.url.endswith("icons"):
return self.jsonify(CITypeManager().get_icons())
q = request.args.get("type_name") q = request.args.get("type_name")
if type_id is not None: if type_id is not None:
ci_type = CITypeCache.get(type_id).to_dict() ci_types = [CITypeCache.get(type_id).to_dict()]
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(type_id)
ci_types = [ci_type]
elif type_name is not None: elif type_name is not None:
ci_type = CITypeCache.get(type_name).to_dict() ci_types = [CITypeCache.get(type_name).to_dict()]
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(ci_type['id'])
ci_types = [ci_type]
else: else:
ci_types = CITypeManager().get_ci_types(q) ci_types = CITypeManager().get_ci_types(q)
count = len(ci_types) count = len(ci_types)
@@ -62,7 +53,7 @@ class CITypeView(APIView):
return self.jsonify(numfound=count, ci_types=ci_types) return self.jsonify(numfound=count, ci_types=ci_types)
@args_required("name") @args_required("name")
@args_validate(CITypeManager.cls, exclude_args=['parent_ids']) @args_validate(CITypeManager.cls)
def post(self): def post(self):
params = request.values params = request.values
@@ -93,26 +84,6 @@ class CITypeView(APIView):
return self.jsonify(type_id=type_id) return self.jsonify(type_id=type_id)
class CITypeInheritanceView(APIView):
url_prefix = ("/ci_types/inheritance",)
@args_required("parent_ids")
@args_required("child_id")
@has_perm_from_args("child_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
def post(self):
CITypeInheritanceManager.add(request.values['parent_ids'], request.values['child_id'])
return self.jsonify(**request.values)
@args_required("parent_id")
@args_required("child_id")
@has_perm_from_args("child_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
def delete(self):
CITypeInheritanceManager.delete(request.values['parent_id'], request.values['child_id'])
return self.jsonify(**request.values)
class CITypeGroupView(APIView): class CITypeGroupView(APIView):
url_prefix = ("/ci_types/groups", url_prefix = ("/ci_types/groups",
"/ci_types/groups/config", "/ci_types/groups/config",
@@ -277,8 +248,8 @@ class CITypeAttributeTransferView(APIView):
@args_required('from') @args_required('from')
@args_required('to') @args_required('to')
def post(self, type_id): def post(self, type_id):
_from = request.values.get('from') # {'attr_id': xx, 'group_id': xx, 'group_name': xx} _from = request.values.get('from') # {'attr_id': xx, 'group_id': xx}
_to = request.values.get('to') # {'group_id': xx, 'group_name': xx, 'order': xxx} _to = request.values.get('to') # {'group_id': xx, 'order': xxx}
CITypeAttributeManager.transfer(type_id, _from, _to) CITypeAttributeManager.transfer(type_id, _from, _to)
@@ -291,8 +262,8 @@ class CITypeAttributeGroupTransferView(APIView):
@args_required('from') @args_required('from')
@args_required('to') @args_required('to')
def post(self, type_id): def post(self, type_id):
_from = request.values.get('from') # group_id or group_name _from = request.values.get('from') # group_id
_to = request.values.get('to') # group_id or group_name _to = request.values.get('to') # group_id
CITypeAttributeGroupManager.transfer(type_id, _from, _to) CITypeAttributeGroupManager.transfer(type_id, _from, _to)
@@ -325,7 +296,7 @@ class CITypeAttributeGroupView(APIView):
attr_order = list(zip(attrs, orders)) attr_order = list(zip(attrs, orders))
group = CITypeAttributeGroupManager.create_or_update(type_id, name, attr_order, order) group = CITypeAttributeGroupManager.create_or_update(type_id, name, attr_order, order)
current_app.logger.warning(group.id)
return self.jsonify(group_id=group.id) return self.jsonify(group_id=group.id)
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id) @has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
@@ -339,13 +310,11 @@ class CITypeAttributeGroupView(APIView):
attr_order = list(zip(attrs, orders)) attr_order = list(zip(attrs, orders))
CITypeAttributeGroupManager.update(group_id, name, attr_order, order) CITypeAttributeGroupManager.update(group_id, name, attr_order, order)
return self.jsonify(group_id=group_id) return self.jsonify(group_id=group_id)
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id) @has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
def delete(self, group_id): def delete(self, group_id):
CITypeAttributeGroupManager.delete(group_id) CITypeAttributeGroupManager.delete(group_id)
return self.jsonify(group_id=group_id) return self.jsonify(group_id=group_id)
@@ -494,19 +463,18 @@ class CITypeGrantView(APIView):
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and not is_app_admin('cmdb'): if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and not is_app_admin('cmdb'):
return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT)) return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
if perms and not request.values.get('id_filter'): acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
new_resource = None if request.values.get('ci_filter') or request.values.get('attr_filter'):
if 'ci_filter' in request.values or 'attr_filter' in request.values or 'id_filter' in request.values: CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values)
new_resource = CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values) else:
if not new_resource:
from api.tasks.acl import role_rebuild from api.tasks.acl import role_rebuild
from api.lib.perm.acl.const import ACL_QUEUE from api.lib.perm.acl.const import ACL_QUEUE
app_id = AppCache.get('cmdb').id app_id = AppCache.get('cmdb').id
current_app.logger.info((rid, app_id))
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE) role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE)
current_app.logger.info('done')
return self.jsonify(code=200) return self.jsonify(code=200)
@@ -527,18 +495,10 @@ class CITypeRevokeView(APIView):
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and not is_app_admin('cmdb'): if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and not is_app_admin('cmdb'):
return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT)) return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
app_id = AppCache.get('cmdb').id
resource = None
if request.values.get('id_filter'):
CIFilterPermsCRUD().delete2(
type_id=type_id, rid=rid, id_filter=request.values['id_filter'],
parent_path=request.values.get('parent_path'))
return self.jsonify(type_id=type_id, rid=rid)
acl.revoke_resource_from_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False) acl.revoke_resource_from_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
app_id = AppCache.get('cmdb').id
resource = None
if PermEnum.READ in perms or not perms: if PermEnum.READ in perms or not perms:
resource = CIFilterPermsCRUD().delete(type_id=type_id, rid=rid) resource = CIFilterPermsCRUD().delete(type_id=type_id, rid=rid)

View File

@@ -2,7 +2,6 @@
from flask import abort from flask import abort
from flask import current_app
from flask import request from flask import request
from api.lib.cmdb.ci_type import CITypeManager from api.lib.cmdb.ci_type import CITypeManager
@@ -188,15 +187,3 @@ class PreferenceRelationRevokeView(APIView):
acl.revoke_resource_from_role_by_rid(name, rid, ResourceTypeEnum.RELATION_VIEW, perms) acl.revoke_resource_from_role_by_rid(name, rid, ResourceTypeEnum.RELATION_VIEW, perms)
return self.jsonify(code=200) 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)

View File

@@ -8,7 +8,7 @@ elasticsearch==7.17.9
email-validator==1.3.1 email-validator==1.3.1
environs==4.2.0 environs==4.2.0
flasgger==0.9.5 flasgger==0.9.5
Flask==2.2.5 Flask==2.3.2
Flask-Bcrypt==1.0.1 Flask-Bcrypt==1.0.1
flask-babel==4.0.0 flask-babel==4.0.0
Flask-Caching==2.0.2 Flask-Caching==2.0.2
@@ -37,7 +37,6 @@ PyMySQL==1.1.0
ldap3==2.9.1 ldap3==2.9.1
PyYAML==6.0.1 PyYAML==6.0.1
redis==4.6.0 redis==4.6.0
python-redis-lock==4.0.0
requests==2.31.0 requests==2.31.0
requests_oauthlib==1.3.1 requests_oauthlib==1.3.1
markdownify==0.11.6 markdownify==0.11.6
@@ -47,7 +46,7 @@ supervisor==4.0.3
timeout-decorator==0.5.0 timeout-decorator==0.5.0
toposort==1.10 toposort==1.10
treelib==1.6.1 treelib==1.6.1
Werkzeug==2.2.3 Werkzeug>=2.3.6
WTForms==3.0.0 WTForms==3.0.0
shamir~=17.12.0 shamir~=17.12.0
pycryptodomex>=3.19.0 pycryptodomex>=3.19.0

View File

@@ -13,7 +13,7 @@ const getAntdSerials = (color) => {
const themePluginOption = { const themePluginOption = {
fileName: 'css/theme-colors-[contenthash:8].css', fileName: 'css/theme-colors-[contenthash:8].css',
matchColors: getAntdSerials('#2f54eb'), // 主色系列 matchColors: getAntdSerials('#1890ff'), // 主色系列
// 改变样式选择器,解决样式覆盖问题 // 改变样式选择器,解决样式覆盖问题
changeSelector (selector) { changeSelector (selector) {
switch (selector) { switch (selector) {

View File

@@ -1,41 +1,41 @@
import i18n from '@/lang' import i18n from '@/lang'
export const ruleTypeList = () => { export const ruleTypeList = () => {
return [ return [
{ value: 'and', label: i18n.t('cmdbFilterComp.and') }, { value: 'and', label: i18n.t('cmdbFilterComp.and') },
{ value: 'or', label: i18n.t('cmdbFilterComp.or') }, { value: 'or', label: i18n.t('cmdbFilterComp.or') },
// { value: 'not', label: '非' }, // { value: 'not', label: '非' },
] ]
} }
export const expList = () => { export const expList = () => {
return [ return [
{ value: 'is', label: i18n.t('cmdbFilterComp.is') }, { value: 'is', label: i18n.t('cmdbFilterComp.is') },
{ value: '~is', label: i18n.t('cmdbFilterComp.~is') }, { value: '~is', label: i18n.t('cmdbFilterComp.~is') },
{ value: 'contain', label: i18n.t('cmdbFilterComp.contain') }, { value: 'contain', label: i18n.t('cmdbFilterComp.contain') },
{ value: '~contain', label: i18n.t('cmdbFilterComp.~contain') }, { value: '~contain', label: i18n.t('cmdbFilterComp.~contain') },
{ value: 'start_with', label: i18n.t('cmdbFilterComp.start_with') }, { value: 'start_with', label: i18n.t('cmdbFilterComp.start_with') },
{ value: '~start_with', label: i18n.t('cmdbFilterComp.~start_with') }, { value: '~start_with', label: i18n.t('cmdbFilterComp.~start_with') },
{ value: 'end_with', label: i18n.t('cmdbFilterComp.end_with') }, { value: 'end_with', label: i18n.t('cmdbFilterComp.end_with') },
{ value: '~end_with', label: i18n.t('cmdbFilterComp.~end_with') }, { value: '~end_with', label: i18n.t('cmdbFilterComp.~end_with') },
{ value: '~value', label: i18n.t('cmdbFilterComp.~value') }, // 为空的定义有点绕 { value: '~value', label: i18n.t('cmdbFilterComp.~value') }, // 为空的定义有点绕
{ value: 'value', label: i18n.t('cmdbFilterComp.value') }, { value: 'value', label: i18n.t('cmdbFilterComp.value') },
] ]
} }
export const advancedExpList = () => { export const advancedExpList = () => {
return [ return [
{ value: 'in', label: i18n.t('cmdbFilterComp.in') }, { value: 'in', label: i18n.t('cmdbFilterComp.in') },
{ value: '~in', label: i18n.t('cmdbFilterComp.~in') }, { value: '~in', label: i18n.t('cmdbFilterComp.~in') },
{ value: 'range', label: i18n.t('cmdbFilterComp.range') }, { value: 'range', label: i18n.t('cmdbFilterComp.range') },
{ value: '~range', label: i18n.t('cmdbFilterComp.~range') }, { value: '~range', label: i18n.t('cmdbFilterComp.~range') },
{ value: 'compare', label: i18n.t('cmdbFilterComp.compare') }, { value: 'compare', label: i18n.t('cmdbFilterComp.compare') },
] ]
} }
export const compareTypeList = [ export const compareTypeList = [
{ value: '1', label: '>' }, { value: '1', label: '>' },
{ value: '2', label: '>=' }, { value: '2', label: '>=' },
{ value: '3', label: '<' }, { value: '3', label: '<' },
{ value: '4', label: '<=' }, { value: '4', label: '<=' },
] ]

View File

@@ -1,346 +1,332 @@
<template> <template>
<div> <div>
<a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id"> <a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id">
<div :style="{ width: '70px', height: '24px', position: 'relative' }"> <div :style="{ width: '70px', height: '24px', position: 'relative' }">
<treeselect <treeselect
v-if="index" v-if="index"
class="custom-treeselect" class="custom-treeselect"
:style="{ width: '70px', '--custom-height': '24px', position: 'absolute', top: '-17px', left: 0 }" :style="{ width: '70px', '--custom-height': '24px', position: 'absolute', top: '-17px', left: 0 }"
v-model="item.type" v-model="item.type"
:multiple="false" :multiple="false"
:clearable="false" :clearable="false"
searchable searchable
:options="ruleTypeList" :options="ruleTypeList"
:normalizer=" :normalizer="
(node) => { (node) => {
return { return {
id: node.value, id: node.value,
label: node.label, label: node.label,
children: node.children, children: node.children,
} }
} }
" "
:disabled="disabled" >
> </treeselect>
</treeselect> </div>
</div> <treeselect
<treeselect class="custom-treeselect"
class="custom-treeselect" :style="{ width: '130px', '--custom-height': '24px' }"
:style="{ width: '130px', '--custom-height': '24px' }" v-model="item.property"
v-model="item.property" :multiple="false"
:multiple="false" :clearable="false"
:clearable="false" searchable
searchable :options="canSearchPreferenceAttrList"
:options="canSearchPreferenceAttrList" :normalizer="
:normalizer=" (node) => {
(node) => { return {
return { id: node.name,
id: node.name, label: node.alias || node.name,
label: node.alias || node.name, children: node.children,
children: node.children, }
} }
} "
" appendToBody
appendToBody :zIndex="1050"
:zIndex="1050" >
:disabled="disabled" <div
> :title="node.label"
<div slot="option-label"
:title="node.label" slot-scope="{ node }"
slot="option-label" :style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
slot-scope="{ node }" >
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }" <ValueTypeMapIcon :attr="node.raw" />
> {{ node.label }}
<ValueTypeMapIcon :attr="node.raw" /> </div>
{{ node.label }} <div
</div> :style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
<div slot="value-label"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }" slot-scope="{ node }"
slot="value-label" >
slot-scope="{ node }" <ValueTypeMapIcon :attr="node.raw" /> {{ node.label }}
> </div>
<ValueTypeMapIcon :attr="node.raw" /> {{ node.label }} </treeselect>
</div> <treeselect
</treeselect> class="custom-treeselect"
<treeselect :style="{ width: '100px', '--custom-height': '24px' }"
class="custom-treeselect" v-model="item.exp"
:style="{ width: '100px', '--custom-height': '24px' }" :multiple="false"
v-model="item.exp" :clearable="false"
:multiple="false" searchable
:clearable="false" :options="[...getExpListByProperty(item.property), ...advancedExpList]"
searchable :normalizer="
:options="[...getExpListByProperty(item.property), ...advancedExpList]" (node) => {
:normalizer=" return {
(node) => { id: node.value,
return { label: node.label,
id: node.value, children: node.children,
label: node.label, }
children: node.children, }
} "
} @select="(value) => handleChangeExp(value, item, index)"
" appendToBody
@select="(value) => handleChangeExp(value, item, index)" :zIndex="1050"
appendToBody >
:zIndex="1050" </treeselect>
:disabled="disabled" <treeselect
> class="custom-treeselect"
</treeselect> :style="{ width: '175px', '--custom-height': '24px' }"
<treeselect v-model="item.value"
class="custom-treeselect" :multiple="false"
:style="{ width: '175px', '--custom-height': '24px' }" :clearable="false"
v-model="item.value" searchable
:multiple="false" v-if="isChoiceByProperty(item.property) && (item.exp === 'is' || item.exp === '~is')"
:clearable="false" :options="getChoiceValueByProperty(item.property)"
searchable :placeholder="$t('placeholder2')"
v-if="isChoiceByProperty(item.property) && (item.exp === 'is' || item.exp === '~is')" :normalizer="
:options="getChoiceValueByProperty(item.property)" (node) => {
:placeholder="$t('placeholder2')" return {
:normalizer=" id: node[0],
(node) => { label: node[0],
return { children: node.children,
id: node[0], }
label: node[0], }
children: node.children, "
} appendToBody
} :zIndex="1050"
" >
appendToBody <div
:zIndex="1050" :title="node.label"
:disabled="disabled" slot="option-label"
> slot-scope="{ node }"
<div :style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
:title="node.label" >
slot="option-label" {{ node.label }}
slot-scope="{ node }" </div>
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }" </treeselect>
> <a-input-group
{{ node.label }} size="small"
</div> compact
</treeselect> v-else-if="item.exp === 'range' || item.exp === '~range'"
<a-input-group :style="{ width: '175px' }"
size="small" >
compact <a-input
v-else-if="item.exp === 'range' || item.exp === '~range'" class="ops-input"
:style="{ width: '175px' }" size="small"
> v-model="item.min"
<a-input :style="{ width: '78px' }"
class="ops-input" :placeholder="$t('min')"
size="small" />
v-model="item.min" ~
:style="{ width: '78px' }" <a-input
:placeholder="$t('min')" class="ops-input"
:disabled="disabled" size="small"
/> v-model="item.max"
~ :style="{ width: '78px' }"
<a-input :placeholder="$t('max')"
class="ops-input" />
size="small" </a-input-group>
v-model="item.max" <a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }">
:style="{ width: '78px' }" <treeselect
:placeholder="$t('max')" class="custom-treeselect"
:disabled="disabled" :style="{ width: '60px', '--custom-height': '24px' }"
/> v-model="item.compareType"
</a-input-group> :multiple="false"
<a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }"> :clearable="false"
<treeselect searchable
class="custom-treeselect" :options="compareTypeList"
:style="{ width: '60px', '--custom-height': '24px' }" :normalizer="
v-model="item.compareType" (node) => {
:multiple="false" return {
:clearable="false" id: node.value,
searchable label: node.label,
:options="compareTypeList" children: node.children,
:normalizer=" }
(node) => { }
return { "
id: node.value, appendToBody
label: node.label, :zIndex="1050"
children: node.children, >
} </treeselect>
} <a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" />
" </a-input-group>
appendToBody <a-input
:zIndex="1050" v-else-if="item.exp !== 'value' && item.exp !== '~value'"
:disabled="disabled" size="small"
> v-model="item.value"
</treeselect> :placeholder="item.exp === 'in' || item.exp === '~in' ? $t('cmdbFilterComp.split', { separator: ';' }) : ''"
<a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" /> class="ops-input"
</a-input-group> :style="{ width: '175px' }"
<a-input ></a-input>
v-else-if="item.exp !== 'value' && item.exp !== '~value'" <div v-else :style="{ width: '175px' }"></div>
size="small" <a-tooltip :title="$t('copy')">
v-model="item.value" <a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a>
:placeholder="item.exp === 'in' || item.exp === '~in' ? $t('cmdbFilterComp.split', { separator: ';' }) : ''" </a-tooltip>
class="ops-input" <a-tooltip :title="$t('delete')">
:style="{ width: '175px' }" <a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a>
:disabled="disabled" </a-tooltip>
></a-input> <a-tooltip :title="$t('cmdbFilterComp.addHere')" v-if="needAddHere">
<div v-else :style="{ width: '175px' }"></div> <a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a>
<template v-if="!disabled"> </a-tooltip>
<a-tooltip :title="$t('copy')"> </a-space>
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a> <div class="table-filter-add">
</a-tooltip> <a @click="handleAddRule">+ {{ $t('new') }}</a>
<a-tooltip :title="$t('delete')"> </div>
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a> </div>
</a-tooltip> </template>
<a-tooltip :title="$t('cmdbFilterComp.addHere')" v-if="needAddHere">
<a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a> <script>
</a-tooltip> import _ from 'lodash'
</template> import { v4 as uuidv4 } from 'uuid'
</a-space> import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants'
<div class="table-filter-add" v-if="!disabled"> import ValueTypeMapIcon from '../CMDBValueTypeMapIcon'
<a @click="handleAddRule">+ {{ $t('new') }}</a>
</div> export default {
</div> name: 'Expression',
</template> components: { ValueTypeMapIcon },
model: {
<script> prop: 'value',
import _ from 'lodash' event: 'change',
import { v4 as uuidv4 } from 'uuid' },
import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants' props: {
import ValueTypeMapIcon from '../CMDBValueTypeMapIcon' value: {
type: Array,
export default { default: () => [],
name: 'Expression', },
components: { ValueTypeMapIcon }, canSearchPreferenceAttrList: {
model: { type: Array,
prop: 'value', required: true,
event: 'change', default: () => [],
}, },
props: { needAddHere: {
value: { type: Boolean,
type: Array, default: false,
default: () => [], },
}, },
canSearchPreferenceAttrList: { data() {
type: Array, return {
required: true, compareTypeList,
default: () => [], }
}, },
needAddHere: { computed: {
type: Boolean, ruleList: {
default: false, get() {
}, return this.value
disabled: { },
type: Boolean, set(val) {
default: false, this.$emit('change', val)
}, return val
}, },
data() { },
return { ruleTypeList() {
compareTypeList, return ruleTypeList()
} },
}, expList() {
computed: { return expList()
ruleList: { },
get() { advancedExpList() {
return this.value return advancedExpList()
}, },
set(val) { },
this.$emit('change', val) methods: {
return val getExpListByProperty(property) {
}, if (property) {
}, const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
ruleTypeList() { if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) {
return ruleTypeList() return [
}, { value: 'is', label: this.$t('cmdbFilterComp.is') },
expList() { { value: '~is', label: this.$t('cmdbFilterComp.~is') },
return expList() { value: '~value', label: this.$t('cmdbFilterComp.~value') }, // 为空的定义有点绕
}, { value: 'value', label: this.$t('cmdbFilterComp.value') },
advancedExpList() { ]
return advancedExpList() }
}, return this.expList
}, }
methods: { return this.expList
getExpListByProperty(property) { },
if (property) { isChoiceByProperty(property) {
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property) const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) { if (_find) {
return [ return _find.is_choice
{ value: 'is', label: this.$t('cmdbFilterComp.is') }, }
{ value: '~is', label: this.$t('cmdbFilterComp.~is') }, return false
{ value: '~value', label: this.$t('cmdbFilterComp.~value') }, // 为空的定义有点绕 },
{ value: 'value', label: this.$t('cmdbFilterComp.value') }, handleAddRule() {
] this.ruleList.push({
} id: uuidv4(),
return this.expList type: 'and',
} property: this.canSearchPreferenceAttrList[0]?.name,
return this.expList exp: 'is',
}, value: null,
isChoiceByProperty(property) { })
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property) this.$emit('change', this.ruleList)
if (_find) { },
return _find.is_choice handleCopyRule(item) {
} this.ruleList.push({ ...item, id: uuidv4() })
return false this.$emit('change', this.ruleList)
}, },
handleAddRule() { handleDeleteRule(item) {
this.ruleList.push({ const idx = this.ruleList.findIndex((r) => r.id === item.id)
id: uuidv4(), if (idx > -1) {
type: 'and', this.ruleList.splice(idx, 1)
property: this.canSearchPreferenceAttrList[0]?.name, }
exp: 'is', this.$emit('change', this.ruleList)
value: null, },
}) handleAddRuleAt(item) {
this.$emit('change', this.ruleList) const idx = this.ruleList.findIndex((r) => r.id === item.id)
}, if (idx > -1) {
handleCopyRule(item) { this.ruleList.splice(idx, 0, {
this.ruleList.push({ ...item, id: uuidv4() }) id: uuidv4(),
this.$emit('change', this.ruleList) type: 'and',
}, property: this.canSearchPreferenceAttrList[0]?.name,
handleDeleteRule(item) { exp: 'is',
const idx = this.ruleList.findIndex((r) => r.id === item.id) value: null,
if (idx > -1) { })
this.ruleList.splice(idx, 1) }
} this.$emit('change', this.ruleList)
this.$emit('change', this.ruleList) },
}, getChoiceValueByProperty(property) {
handleAddRuleAt(item) { const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
const idx = this.ruleList.findIndex((r) => r.id === item.id) if (_find) {
if (idx > -1) { return _find.choice_value
this.ruleList.splice(idx, 0, { }
id: uuidv4(), return []
type: 'and', },
property: this.canSearchPreferenceAttrList[0]?.name, handleChangeExp({ value }, item, index) {
exp: 'is', const _ruleList = _.cloneDeep(this.ruleList)
value: null, if (value === 'range') {
}) _ruleList[index] = {
} ..._ruleList[index],
this.$emit('change', this.ruleList) min: '',
}, max: '',
getChoiceValueByProperty(property) { exp: value,
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property) }
if (_find) { } else if (value === 'compare') {
return _find.choice_value _ruleList[index] = {
} ..._ruleList[index],
return [] compareType: '1',
}, exp: value,
handleChangeExp({ value }, item, index) { }
const _ruleList = _.cloneDeep(this.ruleList) } else {
if (value === 'range') { _ruleList[index] = {
_ruleList[index] = { ..._ruleList[index],
..._ruleList[index], exp: value,
min: '', }
max: '', }
exp: value, this.ruleList = _ruleList
} this.$emit('change', this.ruleList)
} else if (value === 'compare') { },
_ruleList[index] = { },
..._ruleList[index], }
compareType: '1', </script>
exp: value,
} <style></style>
} else {
_ruleList[index] = {
..._ruleList[index],
exp: value,
}
}
this.ruleList = _ruleList
this.$emit('change', this.ruleList)
},
},
}
</script>
<style></style>

View File

@@ -1,302 +1,296 @@
<template> <template>
<div> <div>
<a-popover <a-popover
v-if="isDropdown" v-if="isDropdown"
v-model="visible" v-model="visible"
trigger="click" trigger="click"
:placement="placement" :placement="placement"
overlayClassName="table-filter" overlayClassName="table-filter"
@visibleChange="visibleChange" @visibleChange="visibleChange"
> >
<slot name="popover_item"> <slot name="popover_item">
<a-button type="primary" ghost>{{ $t('cmdbFilterComp.conditionFilter') }}<a-icon type="filter"/></a-button> <a-button type="primary" ghost>{{ $t('cmdbFilterComp.conditionFilter') }}<a-icon type="filter"/></a-button>
</slot> </slot>
<template slot="content"> <template slot="content">
<Expression <Expression
:needAddHere="needAddHere" :needAddHere="needAddHere"
v-model="ruleList" v-model="ruleList"
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)" :canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
:disabled="disabled" />
/> <a-divider :style="{ margin: '10px 0' }" />
<a-divider :style="{ margin: '10px 0' }" /> <div style="width:554px">
<div style="width:554px"> <a-space :style="{ display: 'flex', justifyContent: 'flex-end' }">
<a-space :style="{ display: 'flex', justifyContent: 'flex-end' }"> <a-button type="primary" size="small" @click="handleSubmit">{{ $t('confirm') }}</a-button>
<a-button type="primary" size="small" @click="handleSubmit">{{ $t('confirm') }}</a-button> <a-button size="small" @click="handleClear">{{ $t('clear') }}</a-button>
<a-button size="small" @click="handleClear">{{ $t('clear') }}</a-button> </a-space>
</a-space> </div>
</div> </template>
</template> </a-popover>
</a-popover> <Expression
<Expression :needAddHere="needAddHere"
:needAddHere="needAddHere" v-else
v-else v-model="ruleList"
v-model="ruleList" :canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)" />
:disabled="disabled" </div>
/> </template>
</div>
</template> <script>
import { v4 as uuidv4 } from 'uuid'
<script> import Expression from './expression.vue'
import { v4 as uuidv4 } from 'uuid' import { advancedExpList, compareTypeList } from './constants'
import Expression from './expression.vue'
import { advancedExpList, compareTypeList } from './constants' export default {
name: 'FilterComp',
export default { components: { Expression },
name: 'FilterComp', props: {
components: { Expression }, canSearchPreferenceAttrList: {
props: { type: Array,
canSearchPreferenceAttrList: { required: true,
type: Array, default: () => [],
required: true, },
default: () => [], expression: {
}, type: String,
expression: { default: '',
type: String, },
default: '', regQ: {
}, type: String,
regQ: { default: '(?<=q=).+(?=&)|(?<=q=).+$',
type: String, },
default: '(?<=q=).+(?=&)|(?<=q=).+$', placement: {
}, type: String,
placement: { default: 'bottomRight',
type: String, },
default: 'bottomRight', isDropdown: {
}, type: Boolean,
isDropdown: { default: true,
type: Boolean, },
default: true, needAddHere: {
}, type: Boolean,
needAddHere: { default: false,
type: Boolean, },
default: false, },
}, data() {
disabled: { return {
type: Boolean, advancedExpList,
default: false, compareTypeList,
}, visible: false,
}, ruleList: [],
data() { filterExp: '',
return { }
advancedExpList, },
compareTypeList,
visible: false, methods: {
ruleList: [], visibleChange(open, isInitOne = true) {
filterExp: '', // isInitOne 初始化exp为空时ruleList是否默认给一条
} // const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
}, const exp = this.expression.match(new RegExp(this.regQ, 'g'))
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
methods: { : null
visibleChange(open, isInitOne = true) { if (open && exp) {
// isInitOne 初始化exp为空时ruleList是否默认给一条 const expArray = exp.split(',').map((item) => {
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g let has_not = ''
const exp = this.expression.match(new RegExp(this.regQ, 'g')) const key = item.split(':')[0]
? this.expression.match(new RegExp(this.regQ, 'g'))[0] const val = item
: null .split(':')
if (open && exp) { .slice(1)
const expArray = exp.split(',').map((item) => { .join(':')
let has_not = '' let type, property, exp, value, min, max, compareType
const key = item.split(':')[0] if (key.includes('-')) {
const val = item type = 'or'
.split(':') if (key.includes('~')) {
.slice(1) property = key.substring(2)
.join(':') has_not = '~'
let type, property, exp, value, min, max, compareType } else {
if (key.includes('-')) { property = key.substring(1)
type = 'or' }
if (key.includes('~')) { } else {
property = key.substring(2) type = 'and'
has_not = '~' if (key.includes('~')) {
} else { property = key.substring(1)
property = key.substring(1) has_not = '~'
} } else {
} else { property = key
type = 'and' }
if (key.includes('~')) { }
property = key.substring(1)
has_not = '~' const in_reg = /(?<=\().+(?=\))/g
} else { const range_reg = /(?<=\[).+(?=\])/g
property = key const compare_reg = /(?<=>=|<=|>(?!=)|<(?!=)).+/
} if (val === '*') {
} exp = has_not + 'value'
value = ''
const in_reg = /(?<=\().+(?=\))/g } else if (in_reg.test(val)) {
const range_reg = /(?<=\[).+(?=\])/g exp = has_not + 'in'
const compare_reg = /(?<=>=|<=|>(?!=)|<(?!=)).+/ value = val.match(in_reg)[0]
if (val === '*') { } else if (range_reg.test(val)) {
exp = has_not + 'value' exp = has_not + 'range'
value = '' value = val.match(range_reg)[0]
} else if (in_reg.test(val)) { min = value.split('_TO_')[0]
exp = has_not + 'in' max = value.split('_TO_')[1]
value = val.match(in_reg)[0] } else if (compare_reg.test(val)) {
} else if (range_reg.test(val)) { exp = has_not + 'compare'
exp = has_not + 'range' value = val.match(compare_reg)[0]
value = val.match(range_reg)[0] const _compareType = val.substring(0, val.match(compare_reg)['index'])
min = value.split('_TO_')[0] const idx = compareTypeList.findIndex((item) => item.label === _compareType)
max = value.split('_TO_')[1] compareType = compareTypeList[idx].value
} else if (compare_reg.test(val)) { } else if (!val.includes('*')) {
exp = has_not + 'compare' exp = has_not + 'is'
value = val.match(compare_reg)[0] value = val
const _compareType = val.substring(0, val.match(compare_reg)['index']) } else {
const idx = compareTypeList.findIndex((item) => item.label === _compareType) const resList = [
compareType = compareTypeList[idx].value ['contain', /(?<=\*).*(?=\*)/g],
} else if (!val.includes('*')) { ['end_with', /(?<=\*).+/g],
exp = has_not + 'is' ['start_with', /.+(?=\*)/g],
value = val ]
} else { for (let i = 0; i < 3; i++) {
const resList = [ const reg = resList[i]
['contain', /(?<=\*).*(?=\*)/g], if (reg[1].test(val)) {
['end_with', /(?<=\*).+/g], exp = has_not + reg[0]
['start_with', /.+(?=\*)/g], value = val.match(reg[1])[0]
] break
for (let i = 0; i < 3; i++) { }
const reg = resList[i] }
if (reg[1].test(val)) { }
exp = has_not + reg[0] return {
value = val.match(reg[1])[0] id: uuidv4(),
break type,
} property,
} exp,
} value,
return { min,
id: uuidv4(), max,
type, compareType,
property, }
exp, })
value, this.ruleList = [...expArray]
min, } else if (open) {
max, const _canSearchPreferenceAttrList = this.canSearchPreferenceAttrList.filter((attr) => !attr.is_password)
compareType, this.ruleList = isInitOne
} ? [
}) {
this.ruleList = [...expArray] id: uuidv4(),
} else if (open) { type: 'and',
const _canSearchPreferenceAttrList = this.canSearchPreferenceAttrList.filter((attr) => !attr.is_password) property:
this.ruleList = isInitOne _canSearchPreferenceAttrList && _canSearchPreferenceAttrList.length
? [ ? _canSearchPreferenceAttrList[0].name
{ : undefined,
id: uuidv4(), exp: 'is',
type: 'and', value: null,
property: },
_canSearchPreferenceAttrList && _canSearchPreferenceAttrList.length ]
? _canSearchPreferenceAttrList[0].name : []
: undefined, }
exp: 'is', },
value: null, handleClear() {
}, this.ruleList = [
] {
: [] id: uuidv4(),
} type: 'and',
}, property: this.canSearchPreferenceAttrList[0].name,
handleClear() { exp: 'is',
this.ruleList = [ value: null,
{ },
id: uuidv4(), ]
type: 'and', this.filterExp = ''
property: this.canSearchPreferenceAttrList[0].name, this.visible = false
exp: 'is', this.$emit('setExpFromFilter', this.filterExp)
value: null, },
}, handleSubmit() {
] if (this.ruleList && this.ruleList.length) {
this.filterExp = '' this.ruleList[0].type = 'and' // 增删后以防万一第一个不是and
this.visible = false this.filterExp = ''
this.$emit('setExpFromFilter', this.filterExp) const expList = this.ruleList.map((rule) => {
}, let singleRuleExp = ''
handleSubmit() { let _exp = rule.exp
if (this.ruleList && this.ruleList.length) { if (rule.type === 'or') {
this.ruleList[0].type = 'and' // 增删后以防万一第一个不是and singleRuleExp += '-'
this.filterExp = '' }
const expList = this.ruleList.map((rule) => { if (rule.exp.includes('~')) {
let singleRuleExp = '' singleRuleExp += '~'
let _exp = rule.exp _exp = rule.exp.split('~')[1]
if (rule.type === 'or') { }
singleRuleExp += '-' singleRuleExp += `${rule.property}:`
} if (_exp === 'is') {
if (rule.exp.includes('~')) { singleRuleExp += `${rule.value ?? ''}`
singleRuleExp += '~' }
_exp = rule.exp.split('~')[1] if (_exp === 'contain') {
} singleRuleExp += `*${rule.value ?? ''}*`
singleRuleExp += `${rule.property}:` }
if (_exp === 'is') { if (_exp === 'start_with') {
singleRuleExp += `${rule.value ?? ''}` singleRuleExp += `${rule.value ?? ''}*`
} }
if (_exp === 'contain') { if (_exp === 'end_with') {
singleRuleExp += `*${rule.value ?? ''}*` singleRuleExp += `*${rule.value ?? ''}`
} }
if (_exp === 'start_with') { if (_exp === 'value') {
singleRuleExp += `${rule.value ?? ''}*` singleRuleExp += `*`
} }
if (_exp === 'end_with') { if (_exp === 'in') {
singleRuleExp += `*${rule.value ?? ''}` singleRuleExp += `(${rule.value ?? ''})`
} }
if (_exp === 'value') { if (_exp === 'range') {
singleRuleExp += `*` singleRuleExp += `[${rule.min}_TO_${rule.max}]`
} }
if (_exp === 'in') { if (_exp === 'compare') {
singleRuleExp += `(${rule.value ?? ''})` const idx = compareTypeList.findIndex((item) => item.value === rule.compareType)
} singleRuleExp += `${compareTypeList[idx].label}${rule.value ?? ''}`
if (_exp === 'range') { }
singleRuleExp += `[${rule.min}_TO_${rule.max}]` return singleRuleExp
} })
if (_exp === 'compare') { this.filterExp = expList.join(',')
const idx = compareTypeList.findIndex((item) => item.value === rule.compareType) this.$emit('setExpFromFilter', this.filterExp)
singleRuleExp += `${compareTypeList[idx].label}${rule.value ?? ''}` } else {
} this.$emit('setExpFromFilter', '')
return singleRuleExp }
}) this.visible = false
this.filterExp = expList.join(',') },
this.$emit('setExpFromFilter', this.filterExp) },
} else { }
this.$emit('setExpFromFilter', '') </script>
}
this.visible = false <style lang="less" scoped>
}, .table-filter {
}, .table-filter-add {
} margin-top: 10px;
</script> & > a {
padding: 2px 8px;
<style lang="less" scoped> &:hover {
.table-filter { background-color: #f0faff;
.table-filter-add { border-radius: 5px;
margin-top: 10px; }
& > a { }
padding: 2px 8px; }
&:hover { .table-filter-extra-icon {
background-color: #f0faff; padding: 0px 2px;
border-radius: 5px; &:hover {
} display: inline-block;
} border-radius: 5px;
} background-color: #f0faff;
.table-filter-extra-icon { }
padding: 0px 2px; }
&:hover { }
display: inline-block; </style>
border-radius: 5px;
background-color: #f0faff; <style lang="less">
} .table-filter-extra-operation {
} .ant-popover-inner-content {
} padding: 3px 4px;
</style> .operation {
cursor: pointer;
<style lang="less"> width: 90px;
.table-filter-extra-operation { height: 30px;
.ant-popover-inner-content { line-height: 30px;
padding: 3px 4px; padding: 3px 4px;
.operation { border-radius: 5px;
cursor: pointer; transition: all 0.3s;
width: 90px; &:hover {
height: 30px; background-color: #f0faff;
line-height: 30px; }
padding: 3px 4px; > .anticon {
border-radius: 5px; margin-right: 10px;
transition: all 0.3s; }
&:hover { }
background-color: #f0faff; }
} }
> .anticon { </style>
margin-right: 10px;
}
}
}
}
</style>

View File

@@ -1,45 +1,45 @@
<template> <template>
<div> <div>
<a-row class="row" type="flex" justify="end"> <a-row class="row" type="flex" justify="end">
<a-col> <a-col>
<a-space align="end"> <a-space align="end">
<a-button <a-button
class="left-button" class="left-button"
size="small" size="small"
:disabled="prevIsDisabled" :disabled="prevIsDisabled"
@click="prevPage" @click="prevPage"
><a-icon ><a-icon
type="left" type="left"
/></a-button> /></a-button>
<a-button class="page-button" size="small">{{ currentPage }}</a-button> <a-button class="page-button" size="small">{{ currentPage }}</a-button>
<a-button <a-button
class="right-button" class="right-button"
size="small" size="small"
:disabled="nextIsDisabled" :disabled="nextIsDisabled"
@click="nextPage" @click="nextPage"
><a-icon ><a-icon
type="right" type="right"
/></a-button> /></a-button>
<a-dropdown <a-dropdown
class="dropdown" class="dropdown"
size="small" size="small"
placement="topCenter" placement="topCenter"
:trigger="['click']" :trigger="['click']"
:disabled="dropdownIsDisabled" :disabled="dropdownIsDisabled"
> >
<a-menu slot="overlay"> <a-menu slot="overlay">
<a-menu-item v-for="(size, index) in pageSizes" :key="index" @click="handleItemClick(size)"> <a-menu-item v-for="(size, index) in pageSizes" :key="index" @click="handleItemClick(size)">
{{ `${size}${$t('itemsPerPage')}` }} {{ `${size}${$t('itemsPerPage')}` }}
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
<a-button size="small">{{ `${pageSize}${$t('itemsPerPage')}` }}<a-icon type="down" /> </a-button> <a-button size="small">{{ `${pageSize}${$t('itemsPerPage')}` }}<a-icon type="down" /> </a-button>
</a-dropdown> </a-dropdown>
</a-space> </a-space>
</a-col> </a-col>
</a-row> </a-row>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'Pager', name: 'Pager',
@@ -117,7 +117,7 @@
}, },
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.row { .row {
margin-top: 5px; margin-top: 5px;
@@ -135,3 +135,4 @@
} }
} }
</style> </style>

View File

@@ -1,19 +0,0 @@
/* 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: '' }
]
}

View File

@@ -1,2 +0,0 @@
import RegexSelect from './regexSelect.vue'
export default RegexSelect

View File

@@ -1,208 +0,0 @@
<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>

View File

@@ -1,183 +1,179 @@
<template> <template>
<div ref="splitPane" class="split-pane" :class="direction + ' ' + appName" :style="{ flexDirection: direction }"> <div ref="splitPane" class="split-pane" :class="direction + ' ' + appName" :style="{ flexDirection: direction }">
<div class="pane pane-one" ref="one" :style="lengthType + ':' + paneLengthValue1"> <div class="pane pane-one" ref="one" :style="lengthType + ':' + paneLengthValue1">
<slot name="one"></slot> <slot name="one"></slot>
</div> </div>
<div class="spliter-wrap"> <div class="spliter-wrap">
<a-button <a-button
v-show="collapsable" v-show="collapsable"
:icon="isExpanded ? 'left' : 'right'" :icon="isExpanded ? 'left' : 'right'"
class="collapse-btn" class="collapse-btn"
@click="handleExpand" @click="handleExpand"
></a-button> ></a-button>
<div <div
class="pane-trigger" class="pane-trigger"
@mousedown="handleMouseDown" @mousedown="handleMouseDown"
:style="{ backgroundColor: triggerColor, width: `${triggerLength}px` }" :style="{ backgroundColor: triggerColor, width: `${triggerLength}px` }"
></div> ></div>
</div> </div>
<div class="pane pane-two" ref="two" :style="lengthType + ':' + paneLengthValue2"> <div class="pane pane-two" ref="two" :style="lengthType + ':' + paneLengthValue2">
<slot name="two"></slot> <slot name="two"></slot>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'SplitPane', name: 'SplitPane',
props: { props: {
direction: { direction: {
type: String, type: String,
default: 'row', default: 'row',
}, },
min: { min: {
type: Number, type: Number,
default: 10, default: 10,
}, },
max: { max: {
type: Number, type: Number,
default: 90, default: 90,
}, },
paneLengthPixel: { paneLengthPixel: {
type: Number, type: Number,
default: 220, default: 220,
}, },
triggerLength: { triggerLength: {
type: Number, type: Number,
default: 8, default: 8,
}, },
appName: { appName: {
type: String, type: String,
default: 'viewer', default: 'viewer',
}, },
collapsable: { collapsable: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
triggerColor: { triggerColor: {
type: String, type: String,
default: '#f0f2f5', default: '#f0f2f5',
}, },
}, },
data() { data() {
return { return {
triggerLeftOffset: 0, // 鼠标距滑动器左()侧偏移量 triggerLeftOffset: 0, // 鼠标距滑动器左()侧偏移量
isExpanded: localStorage.getItem(`${this.appName}-isExpanded`) isExpanded: localStorage.getItem(`${this.appName}-isExpanded`)
? JSON.parse(localStorage.getItem(`${this.appName}-isExpanded`)) ? JSON.parse(localStorage.getItem(`${this.appName}-isExpanded`))
: false, : false,
parentContainer: null, parentContainer: null,
} }
}, },
computed: { computed: {
lengthType() { lengthType() {
return this.direction === 'row' ? 'width' : 'height' return this.direction === 'row' ? 'width' : 'height'
}, },
minLengthType() { minLengthType() {
return this.direction === 'row' ? 'minWidth' : 'minHeight' return this.direction === 'row' ? 'minWidth' : 'minHeight'
}, },
paneLengthValue1() { paneLengthValue1() {
return `calc(${this.paneLengthPercent}% - ${this.triggerLength / 2 + 'px'})` return `calc(${this.paneLengthPercent}% - ${this.triggerLength / 2 + 'px'})`
}, },
paneLengthValue2() { paneLengthValue2() {
const rest = 100 - this.paneLengthPercent const rest = 100 - this.paneLengthPercent
return `calc(${rest}% - ${this.triggerLength / 2 + 'px'})` return `calc(${rest}% - ${this.triggerLength / 2 + 'px'})`
}, },
paneLengthPercent() { paneLengthPercent() {
const clientRectWidth = this.parentContainer const clientRectWidth = this.parentContainer
? this.parentContainer.clientWidth ? this.parentContainer.clientWidth
: document.documentElement.getBoundingClientRect().width : document.documentElement.getBoundingClientRect().width
return (this.paneLengthPixel / clientRectWidth) * 100 return (this.paneLengthPixel / clientRectWidth) * 100
}, },
}, },
watch: { watch: {
isExpanded(newValue) { isExpanded(newValue) {
if (newValue) { if (newValue) {
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none' document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
} else { } else {
document.querySelector(`.${this.appName} .pane-two`).style.display = '' document.querySelector(`.${this.appName} .pane-two`).style.display = ''
} }
}, },
}, },
mounted() { mounted() {
const paneLengthPixel = localStorage.getItem(`${this.appName}-paneLengthPixel`) this.parentContainer = document.querySelector(`.${this.appName}`)
if (paneLengthPixel) { if (this.isExpanded) {
this.$emit('update:paneLengthPixel', Number(paneLengthPixel)) document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
} } else {
this.parentContainer = document.querySelector(`.${this.appName}`) document.querySelector(`.${this.appName} .pane-two`).style.display = ''
if (this.isExpanded) { }
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none' },
} else {
document.querySelector(`.${this.appName} .pane-two`).style.display = '' methods: {
} // 按下滑动器
}, handleMouseDown(e) {
document.addEventListener('mousemove', this.handleMouseMove)
methods: { document.addEventListener('mouseup', this.handleMouseUp)
// 按下滑动器 if (this.direction === 'row') {
handleMouseDown(e) { this.triggerLeftOffset = e.pageX - e.srcElement.getBoundingClientRect().left
document.addEventListener('mousemove', this.handleMouseMove) } else {
document.addEventListener('mouseup', this.handleMouseUp) this.triggerLeftOffset = e.pageY - e.srcElement.getBoundingClientRect().top
if (this.direction === 'row') { }
this.triggerLeftOffset = e.pageX - e.srcElement.getBoundingClientRect().left },
} else {
this.triggerLeftOffset = e.pageY - e.srcElement.getBoundingClientRect().top // 按下滑动器后移动鼠标
} handleMouseMove(e) {
}, this.isExpanded = false
this.$emit('expand', this.isExpanded)
// 按下滑动器后移动鼠标 const clientRect = this.$refs.splitPane.getBoundingClientRect()
handleMouseMove(e) { let paneLengthPixel = 0
this.isExpanded = false
this.$emit('expand', this.isExpanded) if (this.direction === 'row') {
const clientRect = this.$refs.splitPane.getBoundingClientRect() const offset = e.pageX - clientRect.left - this.triggerLeftOffset + this.triggerLength / 2
let paneLengthPixel = 0 paneLengthPixel = offset
} else {
if (this.direction === 'row') { const offset = e.pageY - clientRect.top - this.triggerLeftOffset + this.triggerLength / 2
const offset = e.pageX - clientRect.left - this.triggerLeftOffset + this.triggerLength / 2 paneLengthPixel = offset
paneLengthPixel = offset }
} else {
const offset = e.pageY - clientRect.top - this.triggerLeftOffset + this.triggerLength / 2 if (paneLengthPixel < this.min) {
paneLengthPixel = offset paneLengthPixel = this.min
} }
if (paneLengthPixel > this.max) {
if (paneLengthPixel < this.min) { paneLengthPixel = this.max
paneLengthPixel = this.min }
}
if (paneLengthPixel > this.max) { this.$emit('update:paneLengthPixel', paneLengthPixel)
paneLengthPixel = this.max
} localStorage.setItem(`${this.appName}-paneLengthPixel`, paneLengthPixel)
},
this.$emit('update:paneLengthPixel', paneLengthPixel)
// 松开滑动器
localStorage.setItem(`${this.appName}-paneLengthPixel`, paneLengthPixel) handleMouseUp() {
}, document.removeEventListener('mousemove', this.handleMouseMove)
},
// 松开滑动器
handleMouseUp() { handleExpand() {
document.removeEventListener('mousemove', this.handleMouseMove) this.isExpanded = !this.isExpanded
}, this.$emit('expand', this.isExpanded)
localStorage.setItem(`${this.appName}-isExpanded`, this.isExpanded)
handleExpand() { },
this.isExpanded = !this.isExpanded },
this.$emit('expand', this.isExpanded) }
localStorage.setItem(`${this.appName}-isExpanded`, this.isExpanded) </script>
},
}, <style scoped lang="less">
} @import './index.less';
</script> </style>
<style scoped lang="less">
@import './index.less';
</style>

View File

@@ -1,2 +1,2 @@
import SplitPane from './SplitPane' import SplitPane from './SplitPane'
export default SplitPane export default SplitPane

View File

@@ -1,48 +1,48 @@
.split-pane { .split-pane {
height: 100%; height: 100%;
display: flex; display: flex;
} }
.split-pane .pane-two { .split-pane .pane-two {
flex: 1; flex: 1;
} }
.split-pane .pane-trigger { .split-pane .pane-trigger {
user-select: none; user-select: none;
} }
.split-pane.row .pane-one { .split-pane.row .pane-one {
width: 20%; width: 20%;
height: 100%; height: 100%;
// overflow-y: auto; // overflow-y: auto;
} }
.split-pane.column .pane { .split-pane.column .pane {
width: 100%; width: 100%;
} }
.split-pane.row .pane-trigger { .split-pane.row .pane-trigger {
width: 8px; width: 8px;
height: 100%; height: 100%;
cursor: e-resize; cursor: e-resize;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAPCAYAAADDNm69AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAeSURBVBhXY/4PBMzMzA379u1rANFMDGhgGAswMAAAn6EH6K9ktYAAAAAASUVORK5CYII=') background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAPCAYAAADDNm69AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAeSURBVBhXY/4PBMzMzA379u1rANFMDGhgGAswMAAAn6EH6K9ktYAAAAAASUVORK5CYII=')
1px 50% no-repeat #f0f2f5; 1px 50% no-repeat #f0f2f5;
} }
.split-pane .collapse-btn { .split-pane .collapse-btn {
width: 25px; width: 25px;
height: 70px; height: 70px;
position: absolute; position: absolute;
right: 8px; right: 8px;
top: calc(50% - 35px); top: calc(50% - 35px);
background-color: #f0f2f5; background-color: #f0f2f5;
border-color: transparent; border-color: transparent;
border-radius: 8px 0px 0px 8px; border-radius: 8px 0px 0px 8px;
.anticon { .anticon {
color: #7cb0fe; color: #7cb0fe;
} }
} }
.split-pane .spliter-wrap { .split-pane .spliter-wrap {
position: relative; position: relative;
} }

View File

@@ -119,8 +119,7 @@ export default {
border-radius: 4px; border-radius: 4px;
color: @layout-header-font-color; color: @layout-header-font-color;
height: @layout-header-height; height: @layout-header-height;
display: inline-flex; line-height: @layout-header-height;
align-items: center;
&:hover { &:hover {
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%); 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; color: @layout-header-font-selected-color;

View File

@@ -14,7 +14,7 @@
*/ */
export default { export default {
primaryColor: '#2f54eb', // primary color of ant design primaryColor: '#1890ff', // primary color of ant design
navTheme: 'dark', // theme for nav menu navTheme: 'dark', // theme for nav menu
layout: 'sidemenu', // nav menu position: sidemenu or topmenu layout: 'sidemenu', // nav menu position: sidemenu or topmenu
contentWidth: 'Fixed', // layout of content: Fluid or Fixed, only works when layout is topmenu contentWidth: 'Fixed', // layout of content: Fluid or Fixed, only works when layout is topmenu

View File

@@ -25,7 +25,6 @@ export default {
deleting: 'Deleting', deleting: 'Deleting',
deletingTip: 'Deleting, total of {total}, {successNum} succeeded, {errorNum} failed', deletingTip: 'Deleting, total of {total}, {successNum} succeeded, {errorNum} failed',
grant: 'Grant', grant: 'Grant',
revoke: 'Revoke',
login_at: 'Login At', login_at: 'Login At',
logout_at: 'Logout At', logout_at: 'Logout At',
createSuccess: 'Create Success', createSuccess: 'Create Success',
@@ -146,26 +145,6 @@ export default {
sizeLimit: 'The image size cannot exceed 2MB!', sizeLimit: 'The image size cannot exceed 2MB!',
nodata: 'There are currently no custom icons available. Click here to upload' 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, cmdb: cmdb_en,
cs: cs_en, cs: cs_en,
acl: acl_en, acl: acl_en,

View File

@@ -25,7 +25,6 @@ export default {
deleting: '正在删除', deleting: '正在删除',
deletingTip: '正在删除,共{total}个,成功{successNum}个,失败{errorNum}个', deletingTip: '正在删除,共{total}个,成功{successNum}个,失败{errorNum}个',
grant: '授权', grant: '授权',
revoke: '回收',
login_at: '登录时间', login_at: '登录时间',
logout_at: '登出时间', logout_at: '登出时间',
createSuccess: '创建成功', createSuccess: '创建成功',
@@ -146,26 +145,6 @@ export default {
sizeLimit: '图片大小不可超过2MB', sizeLimit: '图片大小不可超过2MB',
nodata: '暂无自定义图标,点击此处上传' nodata: '暂无自定义图标,点击此处上传'
}, },
regexSelect: {
limitedFormat: '限定格式',
regExp: '正则表达式',
errMsg: '错误时提示',
test: '测试',
placeholder: '请选择正则表达式',
error: '错误',
letter: '字母',
number: '数字',
letterAndNumber: '字母和数字',
phone: '手机号码',
landline: '座机',
zipCode: '邮政编码',
IDCard: '身份证号',
ip: 'IP地址',
email: '邮箱',
link: '链接',
monetaryAmount: '货币金额',
custom: '自定义',
},
cmdb: cmdb_zh, cmdb: cmdb_zh,
cs: cs_zh, cs: cs_zh,
acl: acl_zh, acl: acl_zh,

View File

@@ -57,7 +57,7 @@
</a-tag> </a-tag>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="operate" :title="$t('acl.batchOperate')"> <vxe-column field="operate" :title="$t('batchOperate')">
<template #default="{row}"> <template #default="{row}">
<a-button size="small" type="danger" @click="handleClearAll(row)"> <a-button size="small" type="danger" @click="handleClearAll(row)">
{{ $t('clear') }} {{ $t('clear') }}

View File

@@ -1,4 +1,4 @@
<template> <template>
<CustomDrawer <CustomDrawer
width="800px" width="800px"
placement="left" placement="left"

View File

@@ -1,4 +1,4 @@
<template> <template>
<CustomDrawer <CustomDrawer
@close="handleClose" @close="handleClose"
width="500" width="500"

View File

@@ -57,20 +57,17 @@
</template> </template>
</vxe-table-column> </vxe-table-column>
</ops-table> </ops-table>
<a-pagination <vxe-pager
size="small" size="small"
show-size-changer :layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']"
show-quick-jumper :current-page.sync="tablePage.currentPage"
:current="tablePage.currentPage" :page-size.sync="tablePage.pageSize"
:total="tablePage.total" :total="tablePage.total"
:show-total="(total, range) => `当前展示 ${range[0]}-${range[1]} 条数据, 共 ${total} 条`" :page-sizes="pageSizeOptions"
:page-size="tablePage.pageSize" @page-change="handlePageChange"
:default-current="1" :style="{ marginTop: '10px' }"
:page-size-options="pageSizeOptions" >
@change="pageOrSizeChange" </vxe-pager>
@showSizeChange="pageOrSizeChange"
:style="{ marginTop: '10px', textAlign: 'right' }"
/>
</a-spin> </a-spin>
<resourceTypeForm ref="resourceTypeForm" :handleOk="handleOk"> </resourceTypeForm> <resourceTypeForm ref="resourceTypeForm" :handleOk="handleOk"> </resourceTypeForm>
@@ -92,7 +89,7 @@ export default {
loading: false, loading: false,
groups: [], groups: [],
id2perms: {}, id2perms: {},
pageSizeOptions: ['20', '50', '100', '200'], pageSizeOptions: [10, 25, 50, 100],
tablePage: { tablePage: {
total: 0, total: 0,
currentPage: 1, currentPage: 1,
@@ -179,7 +176,7 @@ export default {
this.handleOk() this.handleOk()
}) })
}, },
pageOrSizeChange(currentPage, pageSize) { handlePageChange({ currentPage, pageSize }) {
this.tablePage.currentPage = currentPage this.tablePage.currentPage = currentPage
this.tablePage.pageSize = pageSize this.tablePage.pageSize = pageSize
this.searchData() this.searchData()

View File

@@ -77,7 +77,7 @@
<!-- 2 --> <!-- 2 -->
<vxe-table-column field="name" :title="$t('acl.resourceName')" :min-widh="150" fixed="left" show-overflow> <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') }} {{ row.isGroup ? $t('acl.groupName') : $t('acl.resourceName') }}
</template> </template>
</vxe-table-column> </vxe-table-column>
@@ -86,12 +86,10 @@
<vxe-table-column field="user" :title="$t('acl.creator')" :min-widh="100"> </vxe-table-column> <vxe-table-column field="user" :title="$t('acl.creator')" :min-widh="100"> </vxe-table-column>
<!-- 4 --> <!-- 4 -->
<vxe-table-column field="created_at" :title="$t('created_at')" :min-widh="220" align="center"> <vxe-table-column field="created_at" :title="$t('created_at')" :min-widh="220" align="center"> </vxe-table-column>
</vxe-table-column>
<!-- 5 --> <!-- 5 -->
<vxe-table-column field="updated_at" :title="$t('updated_at')" :min-widh="220" fixed="center"> <vxe-table-column field="updated_at" :title="$t('updated_at')" :min-widh="220" fixed="center"> </vxe-table-column>
</vxe-table-column>
<!-- 6 --> <!-- 6 -->
@@ -101,9 +99,8 @@
:min-widh="200" :min-widh="200"
fixed="right" fixed="right"
align="center" align="center"
show-overflow show-overflow>
> <template #default="{row}">
<template #default="{ row }">
<span v-show="isGroup"> <span v-show="isGroup">
<a @click="handleDisplayMember(row)">{{ $t('acl.member') }}</a> <a @click="handleDisplayMember(row)">{{ $t('acl.member') }}</a>
<a-divider type="vertical" /> <a-divider type="vertical" />
@@ -120,36 +117,27 @@
</a> </a>
</a-tooltip> </a-tooltip>
<a-divider type="vertical" /> <a-divider type="vertical" />
<a-popconfirm <a-popconfirm :title="$t('confirmDelete')" @confirm="handleDelete(row)" @cancel="cancel" :okText="$t('yes')" :cancelText="$t('no')">
:title="$t('confirmDelete')"
@confirm="handleDelete(row)"
@cancel="cancel"
:okText="$t('yes')"
:cancelText="$t('no')"
>
<a style="color: red"><a-icon type="delete"/></a> <a style="color: red"><a-icon type="delete"/></a>
</a-popconfirm> </a-popconfirm>
</template> </template>
</vxe-table-column> </vxe-table-column>
</ops-table> </ops-table>
<a-pagination <vxe-pager
size="small" size="small"
show-size-changer :layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']"
show-quick-jumper :current-page.sync="tablePage.currentPage"
:current="tablePage.currentPage" :page-size.sync="tablePage.pageSize"
:total="tablePage.total" :total="tablePage.total"
:show-total="(total, range) => `当前展示 ${range[0]}-${range[1]} 条数据, 共 ${total} 条`" :page-sizes="pageSizeOptions"
:page-size="tablePage.pageSize" @page-change="handlePageChange"
:default-current="1" :style="{ marginTop: '10px' }"
:page-size-options="pageSizeOptions" >
@change="pageOrSizeChange" </vxe-pager>
@showSizeChange="pageOrSizeChange"
:style="{ marginTop: '10px', textAlign: 'right' }"
/>
</a-spin> </a-spin>
</div> </div>
<div v-else style="text-align: center; margin-top: 20%"> <div v-else style="text-align: center;margin-top:20%">
<a-icon style="font-size: 50px; margin-bottom: 20px; color: orange" type="info-circle" /> <a-icon style="font-size:50px; margin-bottom: 20px; color: orange" type="info-circle" />
<h3>{{ $t('acl.addTypeTips') }}</h3> <h3>{{ $t('acl.addTypeTips') }}</h3>
</div> </div>
<resourceForm ref="resourceForm" @fresh="handleOk"> </resourceForm> <resourceForm ref="resourceForm" @fresh="handleOk"> </resourceForm>
@@ -203,7 +191,7 @@ export default {
isGroup: false, isGroup: false,
allResourceTypes: [], allResourceTypes: [],
currentType: { id: 0 }, currentType: { id: 0 },
pageSizeOptions: ['20', '50', '100', '200'], pageSizeOptions: [10, 25, 50, 100],
searchName: '', searchName: '',
selectedRows: [], selectedRows: [],
} }
@@ -303,7 +291,7 @@ export default {
} }
}, },
cancel() {}, cancel() {},
pageOrSizeChange(currentPage, pageSize) { handlePageChange({ currentPage, pageSize }) {
this.tablePage.currentPage = currentPage this.tablePage.currentPage = currentPage
this.tablePage.pageSize = pageSize this.tablePage.pageSize = pageSize
this.searchData() this.searchData()
@@ -314,6 +302,7 @@ export default {
.getVxetableRef() .getVxetableRef()
.getCheckboxRecords() .getCheckboxRecords()
.concat(this.$refs.xTable.getVxetableRef().getCheckboxReserveRecords()) .concat(this.$refs.xTable.getVxetableRef().getCheckboxReserveRecords())
console.log(this.selectedRows)
}, },
onSelectRangeEnd({ records }) { onSelectRangeEnd({ records }) {
this.selectedRows = records this.selectedRows = records

View File

@@ -42,13 +42,13 @@
<!-- 2 --> <!-- 2 -->
<vxe-table-column field="is_app_admin" :title="$t('admin')" :min-width="100" align="center"> <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" /> <a-icon type="check" v-if="row.is_app_admin" />
</template> </template>
</vxe-table-column> </vxe-table-column>
<vxe-table-column field="id" :title="$t('acl.inheritedFrom')" :min-width="150"> <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> <a-tag color="cyan" v-for="role in id2parents[row.id]" :key="role.id">{{ role.name }}</a-tag>
</template> </template>
</vxe-table-column> </vxe-table-column>
@@ -69,13 +69,13 @@
} }
" "
> >
<template #default="{ row }"> <template #default="{row}">
{{ row.uid ? $t('no') : $t('yes') }} {{ row.uid ? $t('no') : $t('yes') }}
</template> </template>
</vxe-table-column> </vxe-table-column>
<vxe-table-column field="action" :title="$t('operation')" :width="120" fixed="right"> <vxe-table-column field="action" :title="$t('operation')" :width="120" fixed="right">
<template #default="{ row }"> <template #default="{row}">
<a-space> <a-space>
<a-tooltip :title="$t('acl.resourceList')"> <a-tooltip :title="$t('acl.resourceList')">
<a <a
@@ -104,20 +104,17 @@
</template> </template>
</vxe-table-column> </vxe-table-column>
</ops-table> </ops-table>
<a-pagination <vxe-pager
size="small" size="small"
show-size-changer :layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']"
show-quick-jumper :current-page.sync="tablePage.currentPage"
:current="tablePage.currentPage" :page-size.sync="tablePage.pageSize"
:total="tablePage.total" :total="tablePage.total"
:show-total="(total, range) => `当前展示 ${range[0]}-${range[1]} 条数据, 共 ${total} 条`" :page-sizes="pageSizeOptions"
:page-size="tablePage.pageSize" @page-change="handlePageChange"
:default-current="1" :style="{ marginTop: '10px' }"
:page-size-options="pageSizeOptions" >
@change="pageOrSizeChange" </vxe-pager>
@showSizeChange="pageOrSizeChange"
:style="{ marginTop: '10px', textAlign: 'right' }"
/>
</a-spin> </a-spin>
<roleForm ref="roleForm" :allRoles="allRoles" :id2parents="id2parents" :handleOk="handleOk"></roleForm> <roleForm ref="roleForm" :allRoles="allRoles" :id2parents="id2parents" :handleOk="handleOk"></roleForm>
@@ -152,7 +149,7 @@ export default {
tableData: [], tableData: [],
allRoles: [], allRoles: [],
id2parents: {}, id2parents: {},
pageSizeOptions: ['20', '50', '100', '200'], pageSizeOptions: [10, 25, 50, 100],
searchName: '', searchName: '',
filterTableValue: { user_role: 1, user_only: 0 }, filterTableValue: { user_role: 1, user_only: 0 },
} }
@@ -257,7 +254,7 @@ export default {
cancel(e) { cancel(e) {
return false return false
}, },
pageOrSizeChange(currentPage, pageSize) { handlePageChange({ currentPage, pageSize }) {
this.tablePage.currentPage = currentPage this.tablePage.currentPage = currentPage
this.tablePage.pageSize = pageSize this.tablePage.pageSize = pageSize
this.loadData() this.loadData()

View File

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

View File

@@ -114,12 +114,3 @@ export function revokeRelationView(rid, data) {
data: data data: data
}) })
} }
// preference citype order
export function preferenceCitypeOrder(data) {
return axios({
url: `/v0.1/preference/ci_types/order`,
method: 'POST',
data: data
})
}

View File

@@ -1,150 +1,150 @@
<template> <template>
<div class="ci-type-grant"> <div class="ci-type-grant">
<vxe-table <vxe-table
ref="xTable" ref="xTable"
size="mini" size="mini"
stripe stripe
class="ops-stripe-table" class="ops-stripe-table"
:data="filterTableData" :data="filterTableData"
:max-height="`${tableHeight}px`" :max-height="`${tableHeight}px`"
:row-style="(params) => getCurrentRowStyle(params, addedRids)" :row-style="(params) => getCurrentRowStyle(params, addedRids)"
> >
<vxe-column field="name"></vxe-column> <vxe-column field="name"></vxe-column>
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]"> <vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
<template #default="{row}"> <template #default="{row}">
<ReadCheckbox <ReadCheckbox
v-if="['read'].includes(col.split('_')[0])" v-if="['read'].includes(col.split('_')[0])"
:value="row[col.split('_')[0]]" :value="row[col.split('_')[0]]"
:valueKey="col" :valueKey="col"
:rid="row.rid" :rid="row.rid"
@openReadGrantModal="() => openReadGrantModal(col, row)" @openReadGrantModal="() => openReadGrantModal(col, row)"
/> />
<a-checkbox v-else-if="col === 'grant'" :checked="row[col]" @click="clickGrant(col, row)"></a-checkbox> <a-checkbox v-else-if="col === 'grant'" :checked="row[col]" @click="clickGrant(col, row)"></a-checkbox>
<a-checkbox @change="(e) => handleChange(e, col, row)" v-else v-model="row[col]"></a-checkbox> <a-checkbox @change="(e) => handleChange(e, col, row)" v-else v-model="row[col]"></a-checkbox>
</template> </template>
</vxe-column> </vxe-column>
<template #empty> <template #empty>
<div v-if="loading()" style="height: 200px; line-height: 200px;color:#2F54EB"> <div v-if="loading()" style="height: 200px; line-height: 200px;color:#2F54EB">
<a-icon type="loading" /> {{ $t('loading') }} <a-icon type="loading" /> {{ $t('loading') }}
</div> </div>
<div v-else> <div v-else>
<img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" /> <img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" />
<div>{{ $t('noData') }}</div> <div>{{ $t('noData') }}</div>
</div> </div>
</template> </template>
</vxe-table> </vxe-table>
<a-space> <a-space>
<span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span> <span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
<span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span> <span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
</a-space> </a-space>
</div> </div>
</template> </template>
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import { permMap } from './constants.js' import { permMap } from './constants.js'
import { grantCiType, revokeCiType } from '../../api/CIType' import { grantCiType, revokeCiType } from '../../api/CIType'
import ReadCheckbox from './readCheckbox.vue' import ReadCheckbox from './readCheckbox.vue'
import { getCurrentRowStyle } from './utils' import { getCurrentRowStyle } from './utils'
export default { export default {
name: 'CiTypeGrant', name: 'CiTypeGrant',
components: { ReadCheckbox }, components: { ReadCheckbox },
inject: ['loading', 'isModal'], inject: ['loading', 'isModal'],
props: { props: {
CITypeId: { CITypeId: {
type: Number, type: Number,
default: null, default: null,
}, },
tableData: { tableData: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
grantType: { grantType: {
type: String, type: String,
default: 'ci_type', default: 'ci_type',
}, },
addedRids: { addedRids: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
}, },
computed: { computed: {
filterTableData() { filterTableData() {
const _tableData = this.tableData.filter((data) => { const _tableData = this.tableData.filter((data) => {
const _intersection = _.intersection( const _intersection = _.intersection(
Object.keys(data), Object.keys(data),
this.columns.map((col) => col.split('_')[0]) this.columns.map((col) => col.split('_')[0])
) )
return _intersection && _intersection.length return _intersection && _intersection.length
}) })
return _.uniqBy(_tableData, (item) => item.rid) return _.uniqBy(_tableData, (item) => item.rid)
}, },
columns() { columns() {
if (this.grantType === 'ci_type') { if (this.grantType === 'ci_type') {
return ['config', 'grant'] return ['config', 'grant']
} }
return ['read_attr', 'read_ci', 'create', 'update', 'delete'] return ['read_attr', 'read_ci', 'create', 'update', 'delete']
}, },
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
tableHeight() { tableHeight() {
if (this.isModal) { if (this.isModal) {
return (this.windowHeight - 104) / 2 return (this.windowHeight - 104) / 2
} }
return (this.windowHeight - 104) / 2 - 116 return (this.windowHeight - 104) / 2 - 116
}, },
permMap() { permMap() {
return permMap() return permMap()
} }
}, },
methods: { methods: {
getCurrentRowStyle, getCurrentRowStyle,
async handleChange(e, col, row) { async handleChange(e, col, row) {
if (e.target.checked) { if (e.target.checked) {
await grantCiType(this.CITypeId, row.rid, { perms: [col] }).catch(() => { await grantCiType(this.CITypeId, row.rid, { perms: [col] }).catch(() => {
this.$emit('getTableData') this.$emit('getTableData')
}) })
} else { } else {
await revokeCiType(this.CITypeId, row.rid, { perms: [col] }).catch(() => { await revokeCiType(this.CITypeId, row.rid, { perms: [col] }).catch(() => {
this.$emit('getTableData') this.$emit('getTableData')
}) })
} }
}, },
grantDepart() { grantDepart() {
this.$emit('grantDepart', this.grantType) this.$emit('grantDepart', this.grantType)
}, },
grantRole() { grantRole() {
this.$emit('grantRole', this.grantType) this.$emit('grantRole', this.grantType)
}, },
openReadGrantModal(col, row) { openReadGrantModal(col, row) {
this.$emit('openReadGrantModal', col, row) this.$emit('openReadGrantModal', col, row)
}, },
clickGrant(col, row, rowIndex) { clickGrant(col, row, rowIndex) {
if (!row[col]) { if (!row[col]) {
this.handleChange({ target: { checked: true } }, col, row) this.handleChange({ target: { checked: true } }, col, row)
const _idx = this.tableData.findIndex((item) => item.rid === row.rid) const _idx = this.tableData.findIndex((item) => item.rid === row.rid)
this.$set(this.tableData, _idx, { ...this.tableData[_idx], grant: true }) this.$set(this.tableData, _idx, { ...this.tableData[_idx], grant: true })
} else { } else {
const that = this const that = this
this.$confirm({ this.$confirm({
title: that.$t('warning'), title: that.$t('warning'),
content: that.$t('cmdb.components.confirmRevoke', { name: `${row.name}` }), content: that.$t('cmdb.components.confirmRevoke', { name: `${row.name}` }),
onOk() { onOk() {
that.handleChange({ target: { checked: false } }, col, row) that.handleChange({ target: { checked: false } }, col, row)
const _idx = that.tableData.findIndex((item) => item.rid === row.rid) const _idx = that.tableData.findIndex((item) => item.rid === row.rid)
that.$set(that.tableData, _idx, { ...that.tableData[_idx], grant: false }) that.$set(that.tableData, _idx, { ...that.tableData[_idx], grant: false })
}, },
}) })
} }
}, },
}, },
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.ci-type-grant { .ci-type-grant {
padding: 10px 0; padding: 10px 0;
} }
</style> </style>

View File

@@ -1,15 +1,15 @@
import i18n from '@/lang' import i18n from '@/lang'
export const permMap = () => { export const permMap = () => {
return { return {
read: i18n.t('view'), read: i18n.t('view'),
add: i18n.t('new'), add: i18n.t('new'),
create: i18n.t('new'), create: i18n.t('new'),
update: i18n.t('update'), update: i18n.t('update'),
delete: i18n.t('delete'), delete: i18n.t('delete'),
config: i18n.t('cmdb.components.config'), config: i18n.t('cmdb.components.config'),
grant: i18n.t('grant'), grant: i18n.t('grant'),
'read_attr': i18n.t('cmdb.components.readAttribute'), 'read_attr': i18n.t('cmdb.components.readAttribute'),
'read_ci': i18n.t('cmdb.components.readCI') 'read_ci': i18n.t('cmdb.components.readCI')
} }
} }

View File

@@ -1,343 +1,343 @@
<template> <template>
<div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 104}px` }"> <div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 104}px` }">
<template v-if="cmdbGrantType.includes('ci_type')"> <template v-if="cmdbGrantType.includes('ci_type')">
<div class="cmdb-grant-title">{{ $t('cmdb.components.ciTypeGrant') }}</div> <div class="cmdb-grant-title">{{ $t('cmdb.components.ciTypeGrant') }}</div>
<CiTypeGrant <CiTypeGrant
:CITypeId="CITypeId" :CITypeId="CITypeId"
:tableData="tableData" :tableData="tableData"
grantType="ci_type" grantType="ci_type"
@grantDepart="grantDepart" @grantDepart="grantDepart"
@grantRole="grantRole" @grantRole="grantRole"
@getTableData="getTableData" @getTableData="getTableData"
ref="grant_ci_type" ref="grant_ci_type"
:addedRids="addedRids" :addedRids="addedRids"
/> />
</template> </template>
<template <template
v-if=" v-if="
cmdbGrantType.includes('ci_type,ci') || (cmdbGrantType.includes('ci') && !cmdbGrantType.includes('ci_type')) cmdbGrantType.includes('ci_type,ci') || (cmdbGrantType.includes('ci') && !cmdbGrantType.includes('ci_type'))
" "
> >
<div class="cmdb-grant-title">{{ $t('cmdb.components.ciGrant') }}</div> <div class="cmdb-grant-title">{{ $t('cmdb.components.ciGrant') }}</div>
<CiTypeGrant <CiTypeGrant
:CITypeId="CITypeId" :CITypeId="CITypeId"
:tableData="tableData" :tableData="tableData"
grantType="ci" grantType="ci"
@grantDepart="grantDepart" @grantDepart="grantDepart"
@grantRole="grantRole" @grantRole="grantRole"
@getTableData="getTableData" @getTableData="getTableData"
@openReadGrantModal="openReadGrantModal" @openReadGrantModal="openReadGrantModal"
ref="grant_ci" ref="grant_ci"
:addedRids="addedRids" :addedRids="addedRids"
/> />
</template> </template>
<template v-if="cmdbGrantType.includes('type_relation')"> <template v-if="cmdbGrantType.includes('type_relation')">
<div class="cmdb-grant-title">{{ $t('cmdb.components.relationGrant') }}</div> <div class="cmdb-grant-title">{{ $t('cmdb.components.relationGrant') }}</div>
<TypeRelationGrant <TypeRelationGrant
:typeRelationIds="typeRelationIds" :typeRelationIds="typeRelationIds"
:tableData="tableData" :tableData="tableData"
grantType="type_relation" grantType="type_relation"
@grantDepart="grantDepart" @grantDepart="grantDepart"
@grantRole="grantRole" @grantRole="grantRole"
@getTableData="getTableData" @getTableData="getTableData"
ref="grant_type_relation" ref="grant_type_relation"
:addedRids="addedRids" :addedRids="addedRids"
/> />
</template> </template>
<template v-if="cmdbGrantType.includes('relation_view')"> <template v-if="cmdbGrantType.includes('relation_view')">
<div class="cmdb-grant-title">{{ resourceTypeName }}{{ $t('cmdb.components.perm') }}</div> <div class="cmdb-grant-title">{{ resourceTypeName }}{{ $t('cmdb.components.perm') }}</div>
<RelationViewGrant <RelationViewGrant
:resourceTypeName="resourceTypeName" :resourceTypeName="resourceTypeName"
:tableData="tableData" :tableData="tableData"
grantType="relation_view" grantType="relation_view"
@grantDepart="grantDepart" @grantDepart="grantDepart"
@grantRole="grantRole" @grantRole="grantRole"
@getTableData="getTableData" @getTableData="getTableData"
ref="grant_relation_view" ref="grant_relation_view"
:addedRids="addedRids" :addedRids="addedRids"
/> />
</template> </template>
<GrantModal ref="grantModal" @handleOk="handleOk" /> <GrantModal ref="grantModal" @handleOk="handleOk" />
<ReadGrantModal ref="readGrantModal" :CITypeId="CITypeId" @updateTableDataRead="updateTableDataRead" /> <ReadGrantModal ref="readGrantModal" :CITypeId="CITypeId" @updateTableDataRead="updateTableDataRead" />
</div> </div>
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from 'vuex'
import CiTypeGrant from './ciTypeGrant.vue' import CiTypeGrant from './ciTypeGrant.vue'
import TypeRelationGrant from './typeRelationGrant.vue' import TypeRelationGrant from './typeRelationGrant.vue'
import { searchResource } from '@/modules/acl/api/resource' import { searchResource } from '@/modules/acl/api/resource'
import { getResourcePerms } from '@/modules/acl/api/permission' import { getResourcePerms } from '@/modules/acl/api/permission'
import GrantModal from './grantModal.vue' import GrantModal from './grantModal.vue'
import ReadGrantModal from './readGrantModal' import ReadGrantModal from './readGrantModal'
import RelationViewGrant from './relationViewGrant.vue' import RelationViewGrant from './relationViewGrant.vue'
import { getCITypeGroupById, ciTypeFilterPermissions } from '../../api/CIType' import { getCITypeGroupById, ciTypeFilterPermissions } from '../../api/CIType'
export default { export default {
name: 'GrantComp', name: 'GrantComp',
components: { CiTypeGrant, TypeRelationGrant, RelationViewGrant, GrantModal, ReadGrantModal }, components: { CiTypeGrant, TypeRelationGrant, RelationViewGrant, GrantModal, ReadGrantModal },
props: { props: {
CITypeId: { CITypeId: {
type: Number, type: Number,
default: null, default: null,
}, },
resourceTypeName: { resourceTypeName: {
type: String, type: String,
default: '', default: '',
}, },
resourceType: { resourceType: {
type: String, type: String,
default: 'CIType', default: 'CIType',
}, },
app_id: { app_id: {
type: String, type: String,
default: 'cmdb', default: 'cmdb',
}, },
cmdbGrantType: { cmdbGrantType: {
type: String, type: String,
default: 'ci_type,ci', default: 'ci_type,ci',
}, },
typeRelationIds: { typeRelationIds: {
type: Array, type: Array,
default: null, default: null,
}, },
isModal: { isModal: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
}, },
inject: ['resource_type'], inject: ['resource_type'],
data() { data() {
return { return {
tableData: [], tableData: [],
grantType: '', grantType: '',
resource_id: null, resource_id: null,
attrGroup: [], attrGroup: [],
filerPerimissions: {}, filerPerimissions: {},
loading: false, loading: false,
addedRids: [], // added rid this time addedRids: [], // added rid this time
} }
}, },
computed: { computed: {
...mapState({ ...mapState({
allEmployees: (state) => state.user.allEmployees, allEmployees: (state) => state.user.allEmployees,
allDepartments: (state) => state.user.allDepartments, allDepartments: (state) => state.user.allDepartments,
}), }),
child_resource_type() { child_resource_type() {
return this.resource_type() return this.resource_type()
}, },
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
}, },
provide() { provide() {
return { return {
attrGroup: () => { attrGroup: () => {
return this.attrGroup return this.attrGroup
}, },
filerPerimissions: () => { filerPerimissions: () => {
return this.filerPerimissions return this.filerPerimissions
}, },
loading: () => { loading: () => {
return this.loading return this.loading
}, },
isModal: this.isModal, isModal: this.isModal,
} }
}, },
watch: { watch: {
resourceTypeName: { resourceTypeName: {
immediate: true, immediate: true,
handler() { handler() {
this.init() this.init()
}, },
}, },
CITypeId: { CITypeId: {
immediate: true, immediate: true,
handler() { handler() {
if (this.CITypeId && this.cmdbGrantType.includes('ci')) { if (this.CITypeId && this.cmdbGrantType.includes('ci')) {
this.getFilterPermissions() this.getFilterPermissions()
this.getAttrGroup() this.getAttrGroup()
} }
}, },
}, },
}, },
mounted() {}, mounted() {},
methods: { methods: {
getAttrGroup() { getAttrGroup() {
getCITypeGroupById(this.CITypeId, { need_other: true }).then((res) => { getCITypeGroupById(this.CITypeId, { need_other: true }).then((res) => {
this.attrGroup = res this.attrGroup = res
}) })
}, },
getFilterPermissions() { getFilterPermissions() {
ciTypeFilterPermissions(this.CITypeId).then((res) => { ciTypeFilterPermissions(this.CITypeId).then((res) => {
this.filerPerimissions = res this.filerPerimissions = res
}) })
}, },
async init() { async init() {
const _find = this.child_resource_type.groups.find((item) => item.name === this.resourceType) const _find = this.child_resource_type.groups.find((item) => item.name === this.resourceType)
const resource_type_id = _find?.id ?? 0 const resource_type_id = _find?.id ?? 0
const res = await searchResource({ const res = await searchResource({
app_id: this.app_id, app_id: this.app_id,
resource_type_id, resource_type_id,
page_size: 9999, page_size: 9999,
}) })
const _tempFind = res.resources.find((item) => item.name === this.resourceTypeName) const _tempFind = res.resources.find((item) => item.name === this.resourceTypeName)
console.log(this.resourceTypeName) console.log(this.resourceTypeName)
this.resource_id = _tempFind?.id || 0 this.resource_id = _tempFind?.id || 0
this.getTableData() this.getTableData()
}, },
async getTableData() { async getTableData() {
this.loading = true this.loading = true
const _tableData = await getResourcePerms(this.resource_id, { need_users: 0 }) const _tableData = await getResourcePerms(this.resource_id, { need_users: 0 })
const perms = [] const perms = []
for (const key in _tableData) { for (const key in _tableData) {
const obj = {} const obj = {}
obj.name = key obj.name = key
_tableData[key].perms.forEach((perm) => { _tableData[key].perms.forEach((perm) => {
obj[`${perm.name}`] = true obj[`${perm.name}`] = true
obj.rid = perm?.rid ?? null obj.rid = perm?.rid ?? null
}) })
perms.push(obj) perms.push(obj)
} }
this.tableData = perms this.tableData = perms
this.loading = false this.loading = false
}, },
// Grant the department in common-setting and get the roleid from it // Grant the department in common-setting and get the roleid from it
grantDepart(grantType) { grantDepart(grantType) {
this.$refs.grantModal.open('depart') this.$refs.grantModal.open('depart')
this.grantType = grantType this.grantType = grantType
}, },
// Grant the oldest role permissions // Grant the oldest role permissions
grantRole(grantType) { grantRole(grantType) {
this.$refs.grantModal.open('role') this.$refs.grantModal.open('role')
this.grantType = grantType this.grantType = grantType
}, },
handleOk(params, type) { handleOk(params, type) {
const { grantType } = this const { grantType } = this
let rids let rids
if (type === 'depart') { if (type === 'depart') {
rids = [ rids = [
...params.department.map((rid) => { ...params.department.map((rid) => {
const _find = this.allDepartments.find((dep) => dep.acl_rid === rid) const _find = this.allDepartments.find((dep) => dep.acl_rid === rid)
return { rid, name: _find?.department_name ?? rid } return { rid, name: _find?.department_name ?? rid }
}), }),
...params.user.map((rid) => { ...params.user.map((rid) => {
const _find = this.allEmployees.find((dep) => dep.acl_rid === rid) const _find = this.allEmployees.find((dep) => dep.acl_rid === rid)
return { rid, name: _find?.nickname ?? rid } return { rid, name: _find?.nickname ?? rid }
}), }),
] ]
} }
if (type === 'role') { if (type === 'role') {
rids = [ rids = [
...params.map((role) => { ...params.map((role) => {
return { rid: role.id, name: role.name } return { rid: role.id, name: role.name }
}), }),
] ]
} }
if (grantType === 'ci_type') { if (grantType === 'ci_type') {
this.tableData.unshift( this.tableData.unshift(
...rids.map(({ rid, name }) => { ...rids.map(({ rid, name }) => {
const _find = this.tableData.find((item) => item.rid === rid) const _find = this.tableData.find((item) => item.rid === rid)
return { return {
rid, rid,
name, name,
conifg: false, conifg: false,
grant: false, grant: false,
..._find, ..._find,
} }
}) })
) )
} }
if (grantType === 'ci') { if (grantType === 'ci') {
this.tableData.unshift( this.tableData.unshift(
...rids.map(({ rid, name }) => { ...rids.map(({ rid, name }) => {
const _find = this.tableData.find((item) => item.rid === rid) const _find = this.tableData.find((item) => item.rid === rid)
return { return {
rid, rid,
name, name,
read_attr: false, read_attr: false,
read_ci: false, read_ci: false,
create: false, create: false,
update: false, update: false,
delete: false, delete: false,
..._find, ..._find,
} }
}) })
) )
} }
if (grantType === 'type_relation') { if (grantType === 'type_relation') {
this.tableData.unshift( this.tableData.unshift(
...rids.map(({ rid, name }) => { ...rids.map(({ rid, name }) => {
return { return {
rid, rid,
name, name,
create: false, create: false,
grant: false, grant: false,
delete: false, delete: false,
} }
}) })
) )
} }
if (grantType === 'relation_view') { if (grantType === 'relation_view') {
this.tableData.unshift( this.tableData.unshift(
...rids.map(({ rid, name }) => { ...rids.map(({ rid, name }) => {
return { return {
rid, rid,
name, name,
read: false, read: false,
grant: false, grant: false,
} }
}) })
) )
} }
this.addedRids = rids this.addedRids = rids
this.$nextTick(() => { this.$nextTick(() => {
setTimeout(() => { setTimeout(() => {
this.$refs[`grant_${grantType}`].$refs.xTable.elemStore['main-body-wrapper'].scrollTo(0, 0) this.$refs[`grant_${grantType}`].$refs.xTable.elemStore['main-body-wrapper'].scrollTo(0, 0)
}, 300) }, 300)
}) })
}, },
openReadGrantModal(col, row) { openReadGrantModal(col, row) {
this.$refs.readGrantModal.open(col, row) this.$refs.readGrantModal.open(col, row)
}, },
updateTableDataRead(row, hasRead) { updateTableDataRead(row, hasRead) {
const _idx = this.tableData.findIndex((item) => item.rid === row.rid) const _idx = this.tableData.findIndex((item) => item.rid === row.rid)
this.$set(this.tableData, _idx, { ...this.tableData[_idx], read: hasRead }) this.$set(this.tableData, _idx, { ...this.tableData[_idx], read: hasRead })
this.getFilterPermissions() this.getFilterPermissions()
}, },
}, },
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less'; @import '~@/style/static.less';
.cmdb-grant { .cmdb-grant {
position: relative; position: relative;
padding: 24px 24px 0 24px; padding: 24px 24px 0 24px;
overflow: auto; overflow: auto;
.cmdb-grant-title { .cmdb-grant-title {
border-left: 4px solid #custom_colors[color_1]; border-left: 4px solid #custom_colors[color_1];
padding-left: 10px; padding-left: 10px;
} }
} }
</style> </style>
<style lang="less"> <style lang="less">
@import '~@/style/static.less'; @import '~@/style/static.less';
.cmdb-grant { .cmdb-grant {
.grant-button { .grant-button {
padding: 6px 8px; padding: 6px 8px;
color: #custom_colors[color_1]; color: #custom_colors[color_1];
background-color: #custom_colors[color_2]; background-color: #custom_colors[color_2];
border-radius: 2px; border-radius: 2px;
cursor: pointer; cursor: pointer;
margin: 15px 0; margin: 15px 0;
display: inline-block; display: inline-block;
transition: all 0.3s; transition: all 0.3s;
&:hover { &:hover {
box-shadow: 2px 3px 4px #custom_colors[color_2]; box-shadow: 2px 3px 4px #custom_colors[color_2];
} }
} }
} }
</style> </style>

View File

@@ -1,67 +1,57 @@
<template> <template>
<a-modal :title="title" :visible="visible" @ok="handleOk" @cancel="handleCancel" destroyOnClose> <a-modal :title="title" :visible="visible" @ok="handleOk" @cancel="handleCancel" destroyOnClose>
<EmployeeTransfer <EmployeeTransfer
:isDisabledAllCompany="true" :isDisabledAllCompany="true"
v-if="type === 'depart'" v-if="type === 'depart'"
uniqueKey="acl_rid" uniqueKey="acl_rid"
ref="employeeTransfer" ref="employeeTransfer"
:height="350" :height="350"
/> />
<RoleTransfer app_id="cmdb" :height="350" ref="roleTransfer" v-if="type === 'role'" /> <RoleTransfer app_id="cmdb" :height="350" ref="roleTransfer" v-if="type === 'role'" />
</a-modal> </a-modal>
</template> </template>
<script> <script>
import EmployeeTransfer from '@/components/EmployeeTransfer' import EmployeeTransfer from '@/components/EmployeeTransfer'
import RoleTransfer from '@/components/RoleTransfer' import RoleTransfer from '@/components/RoleTransfer'
export default {
export default { name: 'GrantModal',
name: 'GrantModal', components: { EmployeeTransfer, RoleTransfer },
components: { EmployeeTransfer, RoleTransfer }, data() {
props: { return {
customTitle: { visible: false,
type: String, type: 'depart',
default: '', }
}, },
}, computed: {
data() { title() {
return { if (this.type === 'depart') {
visible: false, return this.$t('cmdb.components.grantUser')
type: 'depart', }
} return this.$t('cmdb.components.grantRole')
}, },
computed: { },
title() { methods: {
if (this.customTitle) { open(type) {
return this.customTitle this.visible = true
} this.type = type
if (this.type === 'depart') { },
return this.$t('cmdb.components.grantUser') handleOk() {
} let params
return this.$t('cmdb.components.grantRole') if (this.type === 'depart') {
}, params = this.$refs.employeeTransfer.getValues()
}, }
methods: { if (this.type === 'role') {
open(type) { params = this.$refs.roleTransfer.getValues()
this.visible = true }
this.type = type this.handleCancel()
}, this.$emit('handleOk', params, this.type)
handleOk() { },
let params handleCancel() {
if (this.type === 'depart') { this.visible = false
params = this.$refs.employeeTransfer.getValues() },
} },
if (this.type === 'role') { }
params = this.$refs.roleTransfer.getValues() </script>
}
this.handleCancel() <style></style>
this.$emit('handleOk', params, this.type)
},
handleCancel() {
this.visible = false
},
},
}
</script>
<style></style>

View File

@@ -1,57 +1,57 @@
<template> <template>
<a-modal width="800px" :visible="visible" @ok="handleOk" @cancel="handleCancel" :bodyStyle="{ padding: 0 }"> <a-modal width="800px" :visible="visible" @ok="handleOk" @cancel="handleCancel" :bodyStyle="{ padding: 0 }">
<GrantComp <GrantComp
:resourceType="resourceType" :resourceType="resourceType"
:app_id="app_id" :app_id="app_id"
:cmdbGrantType="cmdbGrantType" :cmdbGrantType="cmdbGrantType"
:resourceTypeName="resourceTypeName" :resourceTypeName="resourceTypeName"
:typeRelationIds="typeRelationIds" :typeRelationIds="typeRelationIds"
:CITypeId="CITypeId" :CITypeId="CITypeId"
:isModal="true" :isModal="true"
/> />
</a-modal> </a-modal>
</template> </template>
<script> <script>
import GrantComp from './grantComp.vue' import GrantComp from './grantComp.vue'
export default { export default {
name: 'CMDBGrant', name: 'CMDBGrant',
components: { GrantComp }, components: { GrantComp },
props: { props: {
resourceType: { resourceType: {
type: String, type: String,
default: 'CIType', default: 'CIType',
}, },
app_id: { app_id: {
type: String, type: String,
default: '', default: '',
}, },
}, },
data() { data() {
return { return {
visible: false, visible: false,
resourceTypeName: '', resourceTypeName: '',
typeRelationIds: [], typeRelationIds: [],
cmdbGrantType: '', cmdbGrantType: '',
CITypeId: null, CITypeId: null,
} }
}, },
methods: { methods: {
open({ name, typeRelationIds = [], cmdbGrantType, CITypeId }) { open({ name, typeRelationIds = [], cmdbGrantType, CITypeId }) {
this.visible = true this.visible = true
this.resourceTypeName = name this.resourceTypeName = name
this.typeRelationIds = typeRelationIds this.typeRelationIds = typeRelationIds
this.cmdbGrantType = cmdbGrantType this.cmdbGrantType = cmdbGrantType
this.CITypeId = CITypeId this.CITypeId = CITypeId
}, },
handleOk() { handleOk() {
this.handleCancel() this.handleCancel()
}, },
handleCancel() { handleCancel() {
this.visible = false this.visible = false
}, },
}, },
} }
</script> </script>
<style></style> <style></style>

View File

@@ -1,89 +1,89 @@
<template> <template>
<div :class="{ 'read-checkbox': true, 'ant-checkbox-wrapper': isHalfChecked }" @click="openReadGrantModal"> <div :class="{ 'read-checkbox': true, 'ant-checkbox-wrapper': isHalfChecked }" @click="openReadGrantModal">
<a-tooltip <a-tooltip
v-if="value && isHalfChecked" v-if="value && isHalfChecked"
:title="valueKey === 'read_ci' ? filerPerimissions[this.rid].name || '' : ''" :title="valueKey === 'read_ci' ? filerPerimissions[this.rid].name || '' : ''"
> >
<div v-if="value && isHalfChecked" :class="{ 'read-checkbox-half-checked': true, 'ant-checkbox': true }"></div> <div v-if="value && isHalfChecked" :class="{ 'read-checkbox-half-checked': true, 'ant-checkbox': true }"></div>
</a-tooltip> </a-tooltip>
<a-checkbox v-else :checked="value" /> <a-checkbox v-else :checked="value" />
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'ReadCheckbox', name: 'ReadCheckbox',
inject: { inject: {
provide_filerPerimissions: { provide_filerPerimissions: {
from: 'filerPerimissions', from: 'filerPerimissions',
}, },
}, },
props: { props: {
value: { value: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
valueKey: { valueKey: {
type: String, type: String,
default: 'read_attr', default: 'read_attr',
}, },
rid: { rid: {
type: Number, type: Number,
default: 0, default: 0,
}, },
}, },
computed: { computed: {
filerPerimissions() { filerPerimissions() {
return this.provide_filerPerimissions() return this.provide_filerPerimissions()
}, },
filterKey() { filterKey() {
if (this.valueKey === 'read_attr') { if (this.valueKey === 'read_attr') {
return 'attr_filter' return 'attr_filter'
} }
return 'ci_filter' return 'ci_filter'
}, },
isHalfChecked() { isHalfChecked() {
if (this.filerPerimissions[this.rid]) { if (this.filerPerimissions[this.rid]) {
const _tempValue = this.filerPerimissions[this.rid][this.filterKey] const _tempValue = this.filerPerimissions[this.rid][this.filterKey]
return !!(_tempValue && _tempValue.length) return !!(_tempValue && _tempValue.length)
} }
return false return false
}, },
}, },
methods: { methods: {
openReadGrantModal() { openReadGrantModal() {
this.$emit('openReadGrantModal') this.$emit('openReadGrantModal')
}, },
}, },
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less'; @import '~@/style/static.less';
.read-checkbox { .read-checkbox {
.read-checkbox-half-checked { .read-checkbox-half-checked {
width: 16px; width: 16px;
height: 16px; height: 16px;
border: 1px solid #d9d9d9; border: 1px solid #d9d9d9;
border-radius: 2px; border-radius: 2px;
cursor: pointer; cursor: pointer;
margin: 0; margin: 0;
padding: 0; padding: 0;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
&::after { &::after {
content: ''; content: '';
position: absolute; position: absolute;
width: 0; width: 0;
height: 0; height: 0;
// background-color: #custom_colors[color_1]; // background-color: #custom_colors[color_1];
border-radius: 2px; border-radius: 2px;
border: 14px solid transparent; border: 14px solid transparent;
border-left-color: #custom_colors[color_1]; border-left-color: #custom_colors[color_1];
transform: rotate(225deg); transform: rotate(225deg);
top: -16px; top: -16px;
left: -17px; left: -17px;
} }
} }
} }
</style> </style>

View File

@@ -1,212 +1,205 @@
<template> <template>
<a-modal :width="680" :title="title" :visible="visible" @ok="handleOk" @cancel="handleCancel"> <a-modal :width="680" :title="title" :visible="visible" @ok="handleOk" @cancel="handleCancel">
<CustomRadio <CustomRadio
:radioList="[ :radioList="[
{ value: 1, label: $t('cmdb.components.all') }, { value: 1, label: $t('cmdb.components.all') },
{ value: 2, label: $t('cmdb.components.customize'), layout: 'vertical' }, { value: 2, label: $t('cmdb.components.customize'), layout: 'vertical' },
{ value: 3, label: $t('cmdb.components.none') }, { value: 3, label: $t('cmdb.components.none') },
]" ]"
:value="radioValue" v-model="radioValue"
@change="changeRadioValue" >
> <template slot="extra_2" v-if="radioValue === 2">
<template slot="extra_2" v-if="radioValue === 2"> <treeselect
<treeselect v-if="colType === 'read_attr'"
v-if="colType === 'read_attr'" v-model="selectedAttr"
v-model="selectedAttr" :multiple="true"
:multiple="true" :clearable="true"
:clearable="true" searchable
searchable :options="attrGroup"
:options="attrGroup" :placeholder="$t('cmdb.ciType.selectAttributes')"
:placeholder="$t('cmdb.ciType.selectAttributes')" value-consists-of="LEAF_PRIORITY"
value-consists-of="LEAF_PRIORITY" :limit="10"
:limit="10" :limitText="(count) => `+ ${count}`"
:limitText="(count) => `+ ${count}`" :normalizer="
:normalizer=" (node) => {
(node) => { return {
return { id: node.name || -1,
id: node.name || -1, label: node.alias || node.name || $t('other'),
label: node.alias || node.name || $t('other'), title: node.alias || node.name || $t('other'),
title: node.alias || node.name || $t('other'), children: node.attributes,
children: node.attributes, }
} }
} "
" appendToBody
appendToBody zIndex="1050"
zIndex="1050" >
> </treeselect>
</treeselect> <a-form-model
<a-form-model :model="form"
:model="form" :rules="rules"
:rules="rules" v-if="colType === 'read_ci'"
v-if="colType === 'read_ci'" :labelCol="{ span: 2 }"
:labelCol="{ span: 2 }" :wrapperCol="{ span: 10 }"
:wrapperCol="{ span: 10 }" ref="form"
ref="form" >
> <a-form-model-item :label="$t('name')" prop="name">
<a-form-model-item :label="$t('name')" prop="name"> <a-input v-model="form.name" />
<a-input v-model="form.name" /> </a-form-model-item>
</a-form-model-item> <FilterComp
<FilterComp ref="filterComp"
ref="filterComp" :isDropdown="false"
:isDropdown="false" :canSearchPreferenceAttrList="canSearchPreferenceAttrList"
:canSearchPreferenceAttrList="canSearchPreferenceAttrList" @setExpFromFilter="setExpFromFilter"
@setExpFromFilter="setExpFromFilter" :expression="expression"
:expression="expression" />
/> </a-form-model>
</a-form-model> </template>
</template> </CustomRadio>
</CustomRadio> </a-modal>
</a-modal> </template>
</template>
<script>
<script> import { grantCiType, revokeCiType } from '../../api/CIType'
import { grantCiType, revokeCiType } from '../../api/CIType' import { getCITypeAttributesByTypeIds } from '../../api/CITypeAttr'
import { getCITypeAttributesByTypeIds } from '../../api/CITypeAttr' import FilterComp from '@/components/CMDBFilterComp'
import FilterComp from '@/components/CMDBFilterComp'
export default {
export default { name: 'ReadGrantModal',
name: 'ReadGrantModal', components: { FilterComp },
components: { FilterComp }, props: {
props: { CITypeId: {
CITypeId: { type: Number,
type: Number, default: null,
default: null, },
}, },
}, inject: {
inject: { provide_attrGroup: {
provide_attrGroup: { from: 'attrGroup',
from: 'attrGroup', },
}, provide_filerPerimissions: {
provide_filerPerimissions: { from: 'filerPerimissions',
from: 'filerPerimissions', },
}, },
}, data() {
data() { return {
return { visible: false,
visible: false, colType: '',
colType: '', row: {},
row: {}, radioValue: 1,
radioValue: 1, radioStyle: {
radioStyle: { display: 'block',
display: 'block', height: '30px',
height: '30px', lineHeight: '30px',
lineHeight: '30px', },
}, selectedAttr: [],
selectedAttr: [], ruleList: [],
ruleList: [], canSearchPreferenceAttrList: [],
canSearchPreferenceAttrList: [], expression: '',
expression: '', form: {
form: { name: '',
name: '', },
}, rules: {
rules: { name: [{ required: true, message: this.$t('cmdb.components.customizeFilterName') }],
name: [{ required: true, message: this.$t('cmdb.components.customizeFilterName') }], },
}, }
} },
}, computed: {
computed: { title() {
title() { if (this.colType === 'read_attr') {
if (this.colType === 'read_attr') { return this.$t('cmdb.components.attributeGrant')
return this.$t('cmdb.components.attributeGrant') }
} return this.$t('cmdb.components.ciGrant')
return this.$t('cmdb.components.ciGrant') },
}, attrGroup() {
attrGroup() { return this.provide_attrGroup()
return this.provide_attrGroup() },
}, filerPerimissions() {
filerPerimissions() { return this.provide_filerPerimissions()
return this.provide_filerPerimissions() },
}, filterKey() {
filterKey() { if (this.colType === 'read_attr') {
if (this.colType === 'read_attr') { return 'attr_filter'
return 'attr_filter' }
} return 'ci_filter'
return 'ci_filter' },
}, },
}, methods: {
methods: { async open(colType, row) {
async open(colType, row) { this.visible = true
this.visible = true this.colType = colType
this.colType = colType this.row = row
this.row = row if (this.colType === 'read_ci') {
this.form = { await getCITypeAttributesByTypeIds({ type_ids: this.CITypeId }).then((res) => {
name: '', this.canSearchPreferenceAttrList = res.attributes.filter((item) => item.value_type !== '6')
} })
if (this.colType === 'read_ci') { }
await getCITypeAttributesByTypeIds({ type_ids: this.CITypeId }).then((res) => { if (this.filerPerimissions[row.rid]) {
this.canSearchPreferenceAttrList = res.attributes.filter((item) => item.value_type !== '6') const _tempValue = this.filerPerimissions[row.rid][this.filterKey]
}) if (_tempValue && _tempValue.length) {
} this.radioValue = 2
if (this.filerPerimissions[row.rid]) { if (this.colType === 'read_attr') {
const _tempValue = this.filerPerimissions[row.rid][this.filterKey] this.selectedAttr = _tempValue
if (_tempValue && _tempValue.length) { } else {
this.radioValue = 2 this.expression = `q=${_tempValue}`
if (this.colType === 'read_attr') { this.form = {
this.selectedAttr = _tempValue name: this.filerPerimissions[row.rid].name || '',
} else { }
this.expression = `q=${_tempValue}` this.$nextTick(() => {
this.form = { this.$refs.filterComp.visibleChange(true)
name: this.filerPerimissions[row.rid].name || '', })
} }
this.$nextTick(() => { }
this.$refs.filterComp.visibleChange(true) } else {
}) this.form = {
} name: '',
} }
} }
}, },
async handleOk() { async handleOk() {
if (this.radioValue === 1) { if (this.radioValue === 1) {
await grantCiType(this.CITypeId, this.row.rid, { await grantCiType(this.CITypeId, this.row.rid, {
perms: ['read'], perms: ['read'],
attr_filter: this.colType === 'read_attr' ? [] : undefined, attr_filter: this.colType === 'read_attr' ? [] : undefined,
ci_filter: this.colType === 'read_ci' ? '' : undefined, ci_filter: this.colType === 'read_ci' ? '' : undefined,
}) })
} else if (this.radioValue === 2) { } else if (this.radioValue === 2) {
if (this.colType === 'read_ci') { if (this.colType === 'read_ci') {
this.$refs.filterComp.handleSubmit() this.$refs.filterComp.handleSubmit()
} }
await grantCiType(this.CITypeId, this.row.rid, { await grantCiType(this.CITypeId, this.row.rid, {
perms: ['read'], perms: ['read'],
attr_filter: this.colType === 'read_attr' ? this.selectedAttr : undefined, attr_filter: this.colType === 'read_attr' ? this.selectedAttr : undefined,
ci_filter: this.colType === 'read_ci' ? this.expression.slice(2) : undefined, ci_filter: this.colType === 'read_ci' ? this.expression.slice(2) : undefined,
name: this.colType === 'read_ci' ? this.form.name : undefined, name: this.colType === 'read_ci' ? this.form.name : undefined,
}) })
} else { } else {
const _tempValue = this.filerPerimissions?.[this.row.rid]?.[this.filterKey] const _tempValue = this.filerPerimissions?.[this.row.rid]?.[this.filterKey]
await revokeCiType(this.CITypeId, this.row.rid, { await revokeCiType(this.CITypeId, this.row.rid, {
perms: ['read'], perms: ['read'],
attr_filter: this.colType === 'read_attr' ? _tempValue : undefined, attr_filter: this.colType === 'read_attr' ? _tempValue : undefined,
ci_filter: this.colType === 'read_ci' ? _tempValue : undefined, ci_filter: this.colType === 'read_ci' ? _tempValue : undefined,
}) })
} }
this.$emit('updateTableDataRead', this.row, this.radioValue === 1 || this.radioValue === 2) this.$emit('updateTableDataRead', this.row, this.radioValue === 1 || this.radioValue === 2)
this.handleCancel() this.handleCancel()
}, },
handleCancel() { handleCancel() {
this.radioValue = 1 this.radioValue = 1
this.selectedAttr = [] this.selectedAttr = []
if (this.$refs.form) { if (this.$refs.form) {
this.$refs.form.resetFields() this.$refs.form.resetFields()
} }
this.visible = false this.visible = false
}, },
setExpFromFilter(filterExp) { setExpFromFilter(filterExp) {
let expression = '' let expression = ''
if (filterExp) { if (filterExp) {
expression = `q=${filterExp}` expression = `q=${filterExp}`
} }
this.expression = expression this.expression = expression
}, },
changeRadioValue(value) { },
if (this.id_filter) { }
this.$message.warning(this.$t('cmdb.serviceTree.grantedByServiceTreeTips')) </script>
} else {
this.radioValue = value <style></style>
}
},
},
}
</script>
<style></style>

View File

@@ -1,98 +1,98 @@
<template> <template>
<div class="ci-relation-grant"> <div class="ci-relation-grant">
<vxe-table <vxe-table
ref="xTable" ref="xTable"
size="mini" size="mini"
stripe stripe
class="ops-stripe-table" class="ops-stripe-table"
:data="tableData" :data="tableData"
:max-height="`${tableHeight}px`" :max-height="`${tableHeight}px`"
:row-style="(params) => getCurrentRowStyle(params, addedRids)" :row-style="(params) => getCurrentRowStyle(params, addedRids)"
> >
<vxe-column field="name"></vxe-column> <vxe-column field="name"></vxe-column>
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]"> <vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
<template #default="{row}"> <template #default="{row}">
<a-checkbox @change="(e) => handleChange(e, col, row)" v-model="row[col]"></a-checkbox> <a-checkbox @change="(e) => handleChange(e, col, row)" v-model="row[col]"></a-checkbox>
</template> </template>
</vxe-column> </vxe-column>
</vxe-table> </vxe-table>
<a-space> <a-space>
<span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span> <span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
<span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span> <span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
</a-space> </a-space>
</div> </div>
</template> </template>
<script> <script>
import { permMap } from './constants.js' import { permMap } from './constants.js'
import { grantRelationView, revokeRelationView } from '../../api/preference.js' import { grantRelationView, revokeRelationView } from '../../api/preference.js'
import { getCurrentRowStyle } from './utils' import { getCurrentRowStyle } from './utils'
export default { export default {
name: 'RelationViewGrant', name: 'RelationViewGrant',
inject: ['loading', 'isModal'], inject: ['loading', 'isModal'],
props: { props: {
resourceTypeName: { resourceTypeName: {
type: String, type: String,
default: '', default: '',
}, },
tableData: { tableData: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
grantType: { grantType: {
type: String, type: String,
default: 'relation_view', default: 'relation_view',
}, },
addedRids: { addedRids: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
}, },
data() { data() {
return { return {
columns: ['read', 'grant'], columns: ['read', 'grant'],
} }
}, },
computed: { computed: {
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
tableHeight() { tableHeight() {
if (this.isModal) { if (this.isModal) {
return (this.windowHeight - 104) / 2 return (this.windowHeight - 104) / 2
} }
return (this.windowHeight - 104) / 2 - 116 return (this.windowHeight - 104) / 2 - 116
}, },
permMap() { permMap() {
return permMap() return permMap()
} }
}, },
methods: { methods: {
getCurrentRowStyle, getCurrentRowStyle,
grantDepart() { grantDepart() {
this.$emit('grantDepart', this.grantType) this.$emit('grantDepart', this.grantType)
}, },
grantRole() { grantRole() {
this.$emit('grantRole', this.grantType) this.$emit('grantRole', this.grantType)
}, },
handleChange(e, col, row) { handleChange(e, col, row) {
if (e.target.checked) { if (e.target.checked) {
grantRelationView(row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => { grantRelationView(row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => {
this.$emit('getTableData') this.$emit('getTableData')
}) })
} else { } else {
revokeRelationView(row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => { revokeRelationView(row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => {
this.$emit('getTableData') this.$emit('getTableData')
}) })
} }
}, },
}, },
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.ci-relation-grant { .ci-relation-grant {
padding: 10px 0; padding: 10px 0;
} }
</style> </style>

View File

@@ -1,122 +0,0 @@
<template>
<a-modal :visible="visible" @cancel="handleCancel" @ok="handleOK" :title="$t('revoke')">
<a-form-model :model="form" :label-col="{ span: 4 }" :wrapper-col="{ span: 16 }">
<a-form-model-item :label="$t('user')">
<EmployeeTreeSelect
class="custom-treeselect custom-treeselect-bgcAndBorder"
:style="{
'--custom-height': '32px',
lineHeight: '32px',
'--custom-bg-color': '#fff',
'--custom-border': '1px solid #d9d9d9',
'--custom-multiple-lineHeight': '18px',
}"
:multiple="true"
v-model="form.users"
:placeholder="$t('cmdb.serviceTree.userPlaceholder')"
:idType="2"
departmentKey="acl_rid"
employeeKey="acl_rid"
/>
</a-form-model-item>
<a-form-model-item :label="$t('role')">
<treeselect
v-model="form.roles"
:multiple="true"
:options="filterAllRoles"
class="custom-treeselect custom-treeselect-bgcAndBorder"
:style="{
'--custom-height': '32px',
lineHeight: '32px',
'--custom-bg-color': '#fff',
'--custom-border': '1px solid #d9d9d9',
'--custom-multiple-lineHeight': '18px',
}"
:limit="10"
:limitText="(count) => `+ ${count}`"
:normalizer="
(node) => {
return {
id: node.id,
label: node.name,
}
}
"
appendToBody
zIndex="1050"
:placeholder="$t('cmdb.serviceTree.rolePlaceholder')"
@search-change="searchRole"
/>
</a-form-model-item>
</a-form-model>
</a-modal>
</template>
<script>
import EmployeeTreeSelect from '@/views/setting/components/employeeTreeSelect.vue'
import { getAllDepAndEmployee } from '@/api/company'
import { searchRole } from '@/modules/acl/api/role'
export default {
name: 'RevokeModal',
components: { EmployeeTreeSelect },
data() {
return {
visible: false,
form: {
users: undefined,
roles: undefined,
},
allTreeDepAndEmp: [],
allRoles: [],
filterAllRoles: [],
}
},
provide() {
return {
provide_allTreeDepAndEmp: () => {
return this.allTreeDepAndEmp
},
}
},
mounted() {
this.getAllDepAndEmployee()
this.loadRoles()
},
methods: {
async loadRoles() {
const res = await searchRole({ page_size: 9999, app_id: 'cmdb', is_all: true })
this.allRoles = res.roles
this.filterAllRoles = this.allRoles.slice(0, 100)
},
getAllDepAndEmployee() {
getAllDepAndEmployee({ block: 0 }).then((res) => {
this.allTreeDepAndEmp = res
})
},
open() {
this.visible = true
this.$nextTick(() => {
this.form = {
users: undefined,
roles: undefined,
}
})
},
handleCancel() {
this.visible = false
},
searchRole(searchQuery) {
this.filterAllRoles = this.allRoles
.filter((item) => item.name.toLowerCase().includes(searchQuery.toLowerCase()))
.slice(0, 100)
},
handleOK() {
this.$emit('handleRevoke', this.form)
this.handleCancel()
},
},
}
</script>
<style></style>

View File

@@ -1,100 +1,100 @@
<template> <template>
<div class="ci-relation-grant"> <div class="ci-relation-grant">
<vxe-table <vxe-table
ref="xTable" ref="xTable"
size="mini" size="mini"
stripe stripe
class="ops-stripe-table" class="ops-stripe-table"
:data="tableData" :data="tableData"
:max-height="`${tableHeight}px`" :max-height="`${tableHeight}px`"
:row-style="(params) => getCurrentRowStyle(params, addedRids)" :row-style="(params) => getCurrentRowStyle(params, addedRids)"
> >
<vxe-column field="name"></vxe-column> <vxe-column field="name"></vxe-column>
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]"> <vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
<template #default="{row}"> <template #default="{row}">
<a-checkbox @change="(e) => handleChange(e, col, row)" v-model="row[col]"></a-checkbox> <a-checkbox @change="(e) => handleChange(e, col, row)" v-model="row[col]"></a-checkbox>
</template> </template>
</vxe-column> </vxe-column>
</vxe-table> </vxe-table>
<a-space> <a-space>
<span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span> <span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
<span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span> <span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
</a-space> </a-space>
</div> </div>
</template> </template>
<script> <script>
import { permMap } from './constants.js' import { permMap } from './constants.js'
import { grantTypeRelation, revokeTypeRelation } from '../../api/CITypeRelation.js' import { grantTypeRelation, revokeTypeRelation } from '../../api/CITypeRelation.js'
import { getCurrentRowStyle } from './utils' import { getCurrentRowStyle } from './utils'
export default { export default {
name: 'TypeRelationGrant', name: 'TypeRelationGrant',
inject: ['loading', 'isModal'], inject: ['loading', 'isModal'],
props: { props: {
tableData: { tableData: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
grantType: { grantType: {
type: String, type: String,
default: 'type_relation', default: 'type_relation',
}, },
typeRelationIds: { typeRelationIds: {
type: Array, type: Array,
default: null, default: null,
}, },
addedRids: { addedRids: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
}, },
data() { data() {
return { return {
columns: ['create', 'grant', 'delete'], columns: ['create', 'grant', 'delete'],
} }
}, },
computed: { computed: {
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
tableHeight() { tableHeight() {
if (this.isModal) { if (this.isModal) {
return (this.windowHeight - 104) / 2 return (this.windowHeight - 104) / 2
} }
return (this.windowHeight - 104) / 2 - 116 return (this.windowHeight - 104) / 2 - 116
}, },
permMap() { permMap() {
return permMap() return permMap()
} }
}, },
methods: { methods: {
getCurrentRowStyle, getCurrentRowStyle,
grantDepart() { grantDepart() {
this.$emit('grantDepart', this.grantType) this.$emit('grantDepart', this.grantType)
}, },
grantRole() { grantRole() {
this.$emit('grantRole', this.grantType) this.$emit('grantRole', this.grantType)
}, },
handleChange(e, col, row) { handleChange(e, col, row) {
const first = this.typeRelationIds[0] const first = this.typeRelationIds[0]
const second = this.typeRelationIds[1] const second = this.typeRelationIds[1]
if (e.target.checked) { if (e.target.checked) {
grantTypeRelation(first, second, row.rid, { perms: [col] }).catch(() => { grantTypeRelation(first, second, row.rid, { perms: [col] }).catch(() => {
this.$emit('getTableData') this.$emit('getTableData')
}) })
} else { } else {
revokeTypeRelation(first, second, row.rid, { perms: [col] }).catch(() => { revokeTypeRelation(first, second, row.rid, { perms: [col] }).catch(() => {
this.$emit('getTableData') this.$emit('getTableData')
}) })
} }
}, },
}, },
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.ci-relation-grant { .ci-relation-grant {
padding: 10px 0; padding: 10px 0;
} }
</style> </style>

View File

@@ -1,4 +1,4 @@
export const getCurrentRowStyle = ({ row }, addedRids) => { export const getCurrentRowStyle = ({ row }, addedRids) => {
const idx = addedRids.findIndex(item => item.rid === row.rid) const idx = addedRids.findIndex(item => item.rid === row.rid)
return idx > -1 ? 'background-color:#E0E7FF!important' : '' return idx > -1 ? 'background-color:#E0E7FF!important' : ''
} }

View File

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

View File

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

View File

@@ -1,305 +1,294 @@
<template> <template>
<div> <div>
<div id="search-form-bar" class="search-form-bar"> <div id="search-form-bar" class="search-form-bar">
<div :style="{ display: 'inline-flex', alignItems: 'center' }"> <div :style="{ display: 'inline-flex', alignItems: 'center' }">
<a-space> <a-space>
<treeselect <treeselect
v-if="type === 'resourceSearch'" v-if="type === 'resourceSearch'"
class="custom-treeselect" class="custom-treeselect"
:style="{ width: '250px', marginRight: '10px', '--custom-height': '32px' }" :style="{ width: '250px', marginRight: '10px', '--custom-height': '32px' }"
v-model="currenCiType" v-model="currenCiType"
:multiple="true" :multiple="true"
:clearable="true" :clearable="true"
searchable searchable
:options="ciTypeGroup" :options="ciTypeGroup"
:limit="1" :limit="1"
:limitText="(count) => `+ ${count}`" :limitText="(count) => `+ ${count}`"
value-consists-of="LEAF_PRIORITY" value-consists-of="LEAF_PRIORITY"
:placeholder="$t('cmdb.ciType.ciType')" :placeholder="$t('cmdb.ciType.ciType')"
@close="closeCiTypeGroup" @close="closeCiTypeGroup"
@open="openCiTypeGroup" @open="openCiTypeGroup"
@input="inputCiTypeGroup" @input="inputCiTypeGroup"
:normalizer=" :normalizer="
(node) => { (node) => {
return { return {
id: node.id || -1, id: node.id || -1,
label: node.alias || node.name || $t('other'), label: node.alias || node.name || $t('other'),
title: node.alias || node.name || $t('other'), title: node.alias || node.name || $t('other'),
children: node.ci_types, children: node.ci_types,
} }
} }
" "
> >
<div <div
:title="node.label" :title="node.label"
slot="option-label" slot="option-label"
slot-scope="{ node }" slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }" :style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
> >
{{ node.label }} {{ node.label }}
</div> </div>
</treeselect> </treeselect>
<a-input <a-input
v-model="fuzzySearch" v-model="fuzzySearch"
:style="{ display: 'inline-block', width: '244px' }" :style="{ display: 'inline-block', width: '244px' }"
:placeholder="$t('cmdb.components.pleaseSearch')" :placeholder="$t('cmdb.components.pleaseSearch')"
@pressEnter="emitRefresh" @pressEnter="emitRefresh"
class="ops-input ops-input-radius" class="ops-input ops-input-radius"
> >
<a-icon <a-icon
type="search" type="search"
slot="suffix" slot="suffix"
:style="{ color: fuzzySearch ? '#2f54eb' : '', cursor: 'pointer' }" :style="{ color: fuzzySearch ? '#2f54eb' : '', cursor: 'pointer' }"
@click="emitRefresh" @click="emitRefresh"
/> />
<a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px', whiteSpace: 'pre-line' }"> <a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px' }">
<template slot="title"> <template slot="title">
{{ $t('cmdb.components.ciSearchTips') }} {{ $t('cmdb.components.ciSearchTips') }}
</template> </template>
<a><a-icon type="question-circle"/></a> <a><a-icon type="question-circle"/></a>
</a-tooltip> </a-tooltip>
</a-input> </a-input>
<FilterComp <FilterComp
ref="filterComp" ref="filterComp"
:canSearchPreferenceAttrList="canSearchPreferenceAttrList" :canSearchPreferenceAttrList="canSearchPreferenceAttrList"
@setExpFromFilter="setExpFromFilter" @setExpFromFilter="setExpFromFilter"
:expression="expression" :expression="expression"
placement="bottomLeft" placement="bottomLeft"
> >
<div slot="popover_item" class="search-form-bar-filter"> <div slot="popover_item" class="search-form-bar-filter">
<a-icon class="search-form-bar-filter-icon" type="filter" /> <a-icon class="search-form-bar-filter-icon" type="filter" />
{{ $t('cmdb.components.conditionFilter') }} {{ $t('cmdb.components.conditionFilter') }}
<a-icon class="search-form-bar-filter-icon" type="down" /> <a-icon class="search-form-bar-filter-icon" type="down" />
</div> </div>
</FilterComp> </FilterComp>
<a-input <a-input
v-if="isShowExpression" v-if="isShowExpression"
v-model="expression" v-model="expression"
v-show="!selectedRowKeys.length" v-show="!selectedRowKeys.length"
@focus=" @focus="
() => { () => {
isFocusExpression = true isFocusExpression = true
} }
" "
@blur=" @blur="
() => { () => {
isFocusExpression = false isFocusExpression = false
} }
" "
class="ci-searchform-expression" class="ci-searchform-expression"
:style="{ width }" :style="{ width }"
:placeholder="placeholder" :placeholder="placeholder"
@keyup.enter="emitRefresh" @keyup.enter="emitRefresh"
> >
<a-icon slot="suffix" type="copy" @click="handleCopyExpression" /> <a-icon slot="suffix" type="copy" @click="handleCopyExpression" />
</a-input> </a-input>
<slot></slot> <slot></slot>
</a-space> </a-space>
</div> </div>
<a-space> <a-space>
<slot name="extraContent"></slot> <a-button @click="reset" size="small">{{ $t('reset') }}</a-button>
<a-button @click="reset" size="small">{{ $t('reset') }}</a-button> <a-tooltip :title="$t('cmdb.components.attributeDesc')" v-if="type === 'relationView'">
<a-tooltip :title="$t('cmdb.components.attributeDesc')" v-if="type === 'relationView'"> <a
<a @click="
@click=" () => {
() => { $refs.metadataDrawer.open(typeId)
$refs.metadataDrawer.open(typeId) }
} "
" ><a-icon
><a-icon v-if="type === 'relationView'"
v-if="type === 'relationView'" type="question-circle"
type="question-circle" /></a>
/></a> </a-tooltip>
</a-tooltip> </a-space>
</a-space> </div>
</div> <MetadataDrawer ref="metadataDrawer" />
<MetadataDrawer ref="metadataDrawer" /> </div>
</div> </template>
</template>
<script>
<script> import _ from 'lodash'
import _ from 'lodash' import Treeselect from '@riophae/vue-treeselect'
import Treeselect from '@riophae/vue-treeselect' import MetadataDrawer from '../../views/ci/modules/MetadataDrawer.vue'
import MetadataDrawer from '../../views/ci/modules/MetadataDrawer.vue' import FilterComp from '@/components/CMDBFilterComp'
import FilterComp from '@/components/CMDBFilterComp' import { getCITypeGroups } from '../../api/ciTypeGroup'
import { getCITypeGroups } from '../../api/ciTypeGroup' export default {
export default { name: 'SearchForm',
name: 'SearchForm', components: { MetadataDrawer, FilterComp, Treeselect },
components: { MetadataDrawer, FilterComp, Treeselect }, props: {
props: { preferenceAttrList: {
preferenceAttrList: { type: Array,
type: Array, required: true,
required: true, },
}, isShowExpression: {
isShowExpression: { type: Boolean,
type: Boolean, default: true,
default: true, },
}, typeId: {
typeId: { type: Number,
type: Number, default: null,
default: null, },
}, type: {
type: { type: String,
type: String, default: '',
default: '', },
}, selectedRowKeys: {
selectedRowKeys: { type: Array,
type: Array, default: () => [],
default: () => [], }
}, },
}, data() {
data() { return {
return { // Advanced Search Expand/Close
// Advanced Search Expand/Close advanced: false,
advanced: false, queryParam: {},
queryParam: {}, isFocusExpression: false,
isFocusExpression: false, expression: '',
expression: '', fuzzySearch: '',
fuzzySearch: '', currenCiType: [],
currenCiType: [], ciTypeGroup: [],
ciTypeGroup: [], lastCiType: [],
lastCiType: [], }
} },
},
computed: {
computed: { placeholder() {
placeholder() { return this.isFocusExpression ? this.$t('cmdb.components.ciSearchTips2') : this.$t('cmdb.ciType.expr')
return this.isFocusExpression ? this.$t('cmdb.components.ciSearchTips2') : this.$t('cmdb.ciType.expr') },
}, width() {
width() { return '200px'
return '200px' },
}, canSearchPreferenceAttrList() {
canSearchPreferenceAttrList() { return this.preferenceAttrList.filter((item) => item.value_type !== '6')
return this.preferenceAttrList.filter((item) => item.value_type !== '6') },
}, },
}, watch: {
watch: { '$route.path': function(newValue, oldValue) {
'$route.path': function(newValue, oldValue) { this.queryParam = {}
this.queryParam = {} this.expression = ''
this.expression = '' this.fuzzySearch = ''
this.fuzzySearch = '' },
}, },
}, inject: ['setPreferenceSearchCurrent'],
inject: { mounted() {
setPreferenceSearchCurrent: { if (this.type === 'resourceSearch') {
from: 'setPreferenceSearchCurrent', this.getCITypeGroups()
default: null, }
}, },
}, methods: {
mounted() { getCITypeGroups() {
if (this.type === 'resourceSearch') { getCITypeGroups({ need_other: true }).then((res) => {
this.getCITypeGroups() this.ciTypeGroup = res
} .filter((item) => item.ci_types && item.ci_types.length)
}, .map((item) => {
methods: { item.id = `parent_${item.id || -1}`
// toggleAdvanced() { return { ..._.cloneDeep(item) }
// this.advanced = !this.advanced })
// }, })
getCITypeGroups() { },
getCITypeGroups({ need_other: true }).then((res) => { reset() {
this.ciTypeGroup = res this.queryParam = {}
.filter((item) => item.ci_types && item.ci_types.length) this.expression = ''
.map((item) => { this.fuzzySearch = ''
item.id = `parent_${item.id || -1}` this.currenCiType = []
return { ..._.cloneDeep(item) } this.emitRefresh()
}) },
}) setExpFromFilter(filterExp) {
}, const regSort = /(?<=sort=).+/g
reset() { const expSort = this.expression.match(regSort) ? this.expression.match(regSort)[0] : undefined
this.queryParam = {} let expression = ''
this.expression = '' if (filterExp) {
this.fuzzySearch = '' expression = `q=${filterExp}`
this.currenCiType = [] }
this.emitRefresh() if (expSort) {
}, expression += `&sort=${expSort}`
setExpFromFilter(filterExp) { }
const regSort = /(?<=sort=).+/g this.expression = expression
const expSort = this.expression.match(regSort) ? this.expression.match(regSort)[0] : undefined this.emitRefresh()
let expression = '' },
if (filterExp) { handleSubmit() {
expression = `q=${filterExp}` this.$refs.filterComp.handleSubmit()
} },
if (expSort) { openCiTypeGroup() {
expression += `&sort=${expSort}` this.lastCiType = _.cloneDeep(this.currenCiType)
} },
this.expression = expression closeCiTypeGroup(value) {
this.emitRefresh() if (!_.isEqual(value, this.lastCiType)) {
}, this.$emit('updateAllAttributesList', value)
handleSubmit() { }
this.$refs.filterComp.handleSubmit() },
}, inputCiTypeGroup(value) {
openCiTypeGroup() { console.log(value)
this.lastCiType = _.cloneDeep(this.currenCiType) if (!value || !value.length) {
}, this.$emit('updateAllAttributesList', value)
closeCiTypeGroup(value) { }
if (!_.isEqual(value, this.lastCiType)) { },
this.$emit('updateAllAttributesList', value) emitRefresh() {
} this.setPreferenceSearchCurrent(null)
}, this.$nextTick(() => {
inputCiTypeGroup(value) { this.$emit('refresh', true)
console.log(value) })
if (!value || !value.length) { },
this.$emit('updateAllAttributesList', value) handleCopyExpression() {
} this.$emit('copyExpression')
}, },
emitRefresh() { },
if (this.setPreferenceSearchCurrent) { }
this.setPreferenceSearchCurrent(null) </script>
} <style lang="less">
this.$nextTick(() => { @import '../../views/index.less';
this.$emit('refresh', true) .ci-searchform-expression {
}) > input {
}, border-bottom: 2px solid #d9d9d9;
handleCopyExpression() { border-top: none;
this.$emit('copyExpression') border-left: none;
}, border-right: none;
}, &:hover,
} &:focus {
</script> border-bottom: 2px solid #2f54eb;
<style lang="less"> }
@import '../../views/index.less'; &:focus {
.ci-searchform-expression { box-shadow: 0 2px 2px -2px #1f78d133;
> input { }
border-bottom: 2px solid #d9d9d9; }
border-top: none; .ant-input-suffix {
border-left: none; color: #2f54eb;
border-right: none; cursor: pointer;
&:hover, }
&:focus { }
border-bottom: 2px solid #2f54eb; .cmdb-search-form {
} .ant-form-item-label {
&:focus { overflow: hidden;
box-shadow: 0 2px 2px -2px #1f78d133; text-overflow: ellipsis;
} white-space: nowrap;
} }
.ant-input-suffix { }
color: #2f54eb; </style>
cursor: pointer;
} <style lang="less" scoped>
} @import '~@/style/static.less';
.cmdb-search-form {
.ant-form-item-label { .search-form-bar {
overflow: hidden; margin-bottom: 10px;
text-overflow: ellipsis; display: flex;
white-space: nowrap; justify-content: space-between;
} align-items: center;
} .search-form-bar-filter {
</style> .ops_display_wrapper();
.search-form-bar-filter-icon {
<style lang="less" scoped> color: #custom_colors[color_1];
@import '~@/style/static.less'; font-size: 12px;
}
.search-form-bar { }
margin-bottom: 10px; }
display: flex; </style>
justify-content: space-between;
align-items: center;
.search-form-bar-filter {
.ops_display_wrapper();
.search-form-bar-filter-icon {
color: #custom_colors[color_1];
font-size: 12px;
}
}
}
</style>

View File

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

View File

@@ -1,503 +1,483 @@
const cmdb_en = { const cmdb_en = {
relation: 'Relation', relation: 'Relation',
attribute: 'Attributes', attribute: 'Attributes',
menu: { menu: {
views: 'Views', views: 'Views',
config: 'Configuration', config: 'Configuration',
backend: 'Management', backend: 'Management',
ciTable: 'Resource Views', ciTable: 'Resource Views',
ciTree: 'Tree Views', ciTree: 'Tree Views',
ciSearch: 'Search', ciSearch: 'Search',
adCIs: 'AutoDiscovery Pool', adCIs: 'AutoDiscovery Pool',
preference: 'Preference', preference: 'Preference',
batchUpload: 'Batch Import', batchUpload: 'Batch Import',
citypeManage: 'Modeling', citypeManage: 'Modeling',
backendManage: 'Backend', backendManage: 'Backend',
customDashboard: 'Custom Dashboard', customDashboard: 'Custom Dashboard',
serviceTreeDefine: 'Service Tree', serviceTreeDefine: 'Service Tree',
citypeRelation: 'CIType Relation', citypeRelation: 'CIType Relation',
operationHistory: 'Operation Audit', operationHistory: 'Operation Audit',
relationType: 'Relation Type', relationType: 'Relation Type',
ad: 'AutoDiscovery', ad: 'AutoDiscovery',
cidetail: 'CI Detail' },
}, ciType: {
ciType: { ciType: 'CIType',
ciType: 'CIType', attributes: 'Attributes',
attributes: 'Attributes', relation: 'Relation',
relation: 'Relation', trigger: 'Triggers',
trigger: 'Triggers', attributeAD: 'Attributes AutoDiscovery',
attributeAD: 'Attributes AutoDiscovery', relationAD: 'Relation AutoDiscovery',
relationAD: 'Relation AutoDiscovery', grant: 'Grant',
grant: 'Grant', addGroup: 'New Group',
addGroup: 'New Group', editGroup: 'Edit Group',
editGroup: 'Edit Group', group: 'Group',
group: 'Group', attributeLibray: 'Attribute Library',
attributeLibray: 'Attribute Library', addCITypeInGroup: 'Add a new CIType to the group',
addCITypeInGroup: 'Add a new CIType to the group', addCIType: 'Add CIType',
addCIType: 'Add CIType', editGroupName: 'Edit group name',
editGroupName: 'Edit group name', deleteGroup: 'Delete this group',
deleteGroup: 'Delete this group', CITypeName: 'Name(English)',
CITypeName: 'Name(English)', English: 'English',
English: 'English', inputAttributeName: 'Please enter the attribute name',
inputAttributeName: 'Please enter the attribute name', attributeNameTips: 'It cannot start with a number, it can be English numbers and underscores (_)',
attributeNameTips: 'It cannot start with a number, it can be English numbers and underscores (_)', editCIType: 'Edit CIType',
editCIType: 'Edit CIType', defaultSort: 'Default sort',
defaultSort: 'Default sort', selectDefaultOrderAttr: 'Select default sorting attributes',
selectDefaultOrderAttr: 'Select default sorting attributes', asec: 'Forward order',
asec: 'Forward order', desc: 'Reverse order',
desc: 'Reverse order', uniqueKey: 'Uniquely Identifies',
uniqueKey: 'Unique Identifies', uniqueKeySelect: 'Please select a unique identifier',
uniqueKeySelect: 'Please select a unique identifier', notfound: 'Can\'t find what you want?',
uniqueKeyTips: 'json/password/computed/choice can not be unique identifies', cannotDeleteGroupTips: 'There is data under this group and cannot be deleted!',
notfound: 'Can\'t find what you want?', confirmDeleteGroup: 'Are you sure you want to delete group [{groupName}]?',
cannotDeleteGroupTips: 'There is data under this group and cannot be deleted!', confirmDeleteCIType: 'Are you sure you want to delete model [{typeName}]?',
confirmDeleteGroup: 'Are you sure you want to delete group [{groupName}]?', uploading: 'Uploading',
confirmDeleteCIType: 'Are you sure you want to delete model [{typeName}]?', uploadFailed: 'Upload failed, please try again later',
uploading: 'Uploading', addPlugin: 'New plugin',
uploadFailed: 'Upload failed, please try again later', deletePlugin: 'Delete plugin',
addPlugin: 'New plugin', confirmDeleteADT: 'Do you confirm to delete [{pluginName}]',
deletePlugin: 'Delete plugin', attributeMap: 'Attribute mapping',
confirmDeleteADT: 'Do you confirm to delete [{pluginName}]', autoDiscovery: 'AutoDiscovery',
attributeMap: 'Attribute mapping', node: 'Node',
autoDiscovery: 'AutoDiscovery', adExecConfig: 'Execute configuration',
node: 'Node', adExecTarget: 'Execute targets',
adExecConfig: 'Execute configuration', oneagentIdTips: 'Please enter the hexadecimal OneAgent ID starting with 0x',
adExecTarget: 'Execute targets', selectFromCMDBTips: 'Select from CMDB ',
oneagentIdTips: 'Please enter the hexadecimal OneAgent ID starting with 0x', adAutoInLib: 'Save as CI auto',
selectFromCMDBTips: 'Select from CMDB ', adInterval: 'Collection frequency',
adAutoInLib: 'Save as CI auto', byInterval: 'by interval',
adInterval: 'Collection frequency', allNodes: 'All nodes',
byInterval: 'by interval', specifyNodes: 'Specify Node',
allNodes: 'All nodes', specifyNodesTips: 'Please fill in the specify node!',
specifyNodes: 'Specify Node', username: 'Username',
specifyNodesTips: 'Please fill in the specify node!', password: 'Password',
username: 'Username', link: 'Link',
password: 'Password', list: 'List',
link: 'Link', listTips: 'The value of the field is one or more, and the type of the value returned by the interface is list.',
list: 'List', computeForAllCITips: 'All CI trigger computes',
listTips: 'The value of the field is one or more, and the type of the value returned by the interface is list.', confirmcomputeForAllCITips: 'Confirm triggering computes for all CIs?',
computeForAllCITips: 'All CI trigger computes', isUnique: 'Is it unique',
confirmcomputeForAllCITips: 'Confirm triggering computes for all CIs?', unique: 'Unique',
isUnique: 'Is it unique', isChoice: 'Choiced',
unique: 'Unique', defaultShow: 'Default Display',
isChoice: 'Choiced', defaultShowTips: 'The CI instance table displays this field by default',
defaultShow: 'Default Display', isSortable: 'Sortable',
defaultShowTips: 'The CI instance table displays this field by default', isIndex: 'Indexed',
isSortable: 'Sortable', index: 'Index',
isIndex: 'Indexed', indexTips: 'Fields can be used for retrieval to speed up queries',
index: 'Index', confirmDelete: 'Confirm to delete [{name}]?',
indexTips: 'Fields can be used for retrieval to speed up queries', confirmDelete2: 'Confirm to delete?',
confirmDelete: 'Confirm to delete [{name}]?', computeSuccess: 'Triggered successfully!',
confirmDelete2: 'Confirm to delete?', basicConfig: 'Basic Settings',
computeSuccess: 'Triggered successfully!', AttributeName: 'Name(English)',
basicConfig: 'Basic Settings', DataType: 'Data Type',
AttributeName: 'Name(English)', defaultValue: 'Default value',
DataType: 'Data Type', autoIncID: 'Auto-increment ID',
defaultValue: 'Default value', customTime: 'Custom time',
autoIncID: 'Auto-increment ID', advancedSettings: 'Advanced Settings',
customTime: 'Custom time', font: 'Font',
advancedSettings: 'Advanced Settings', color: 'Color',
font: 'Font', choiceValue: 'Predefined value',
color: 'Color', computedAttribute: 'Computed Attribute',
choiceValue: 'Predefined value', computedAttributeTips: 'The value of this attribute is calculated through an expression constructed from other attributes of the CIType or by executing a piece of code. The reference method of the attribute is: {{ attribute name }}',
computedAttribute: 'Computed Attribute', addAttribute: 'New attribute',
computedAttributeTips: 'The value of this attribute is calculated through an expression constructed from other attributes of the CIType or by executing a piece of code. The reference method of the attribute is: {{ attribute name }}', existedAttributes: 'Already have attributes',
addAttribute: 'New attribute', editAttribute: 'Edit attribute',
existedAttributes: 'Already have attributes', addAttributeTips1: 'If sorting is selected, it must also be selected!',
editAttribute: 'Edit attribute', uniqueConstraint: 'Unique Constraint',
addAttributeTips1: 'If sorting is selected, it must also be selected!', up: 'Move up',
uniqueConstraint: 'Unique Constraint', down: 'Move down',
up: 'Move up', selectAttribute: 'Select Attribute',
down: 'Move down', groupExisted: 'Group name already exists',
selectAttribute: 'Select Attribute', attributeSortedTips: 'Attributes in other groups cannot be sorted. If you need to sort, please drag them to a custom group first!',
groupExisted: 'Group name already exists', buildinAttribute: 'built-in attributes',
attributeSortedTips: 'Attributes in other groups cannot be sorted. If you need to sort, please drag them to a custom group first!', expr: 'Expression',
buildinAttribute: 'built-in attributes', code: 'Code',
expr: 'Expression', apply: 'apply',
code: 'Code', continueAdd: 'Keep adding',
apply: 'apply', filter: 'Filter',
continueAdd: 'Keep adding', choiceOther: 'Other CIType Attributes',
filter: 'Filter', choiceWebhookTips: 'The returned results are filtered by fields, and the hierarchical nesting is separated by ##, such as k1##k2. The web request returns {k1: [{k2: 1}, {k2: 2}]}, and the parsing result is [1, 2 ]',
choiceOther: 'Other CIType Attributes', selectCIType: 'Please select a CMDB CIType',
choiceWebhookTips: 'The returned results are filtered by fields, and the hierarchical nesting is separated by ##, such as k1##k2. The web request returns {k1: [{k2: 1}, {k2: 2}]}, and the parsing result is [1, 2 ]', selectCITypeAttributes: 'Please select CIType attributes',
selectCIType: 'Please select a CMDB CIType', selectAttributes: 'Please select attributes',
selectCITypeAttributes: 'Please select CIType attributes', choiceScriptDemo: 'class ChoiceValue(object):\n @staticmethod\n def values():\n """\n Execution entry, returns predefined value\n :return: Returns a list, the type of the value is the same as the type of the attribute\n For example:\n return ["online", "offline"]\n """\n return []',
selectAttributes: 'Please select attributes', valueExisted: 'The current value already exists!',
choiceScriptDemo: 'class ChoiceValue(object):\n @staticmethod\n def values():\n """\n Execution entry, returns predefined value\n :return: Returns a list, the type of the value is the same as the type of the attribute\n For example:\n return ["online", "offline"]\n """\n return []', addRelation: 'Add Relation',
valueExisted: 'The current value already exists!', sourceCIType: 'Source CIType',
addRelation: 'Add Relation', sourceCITypeTips: 'Please select Source CIType',
sourceCIType: 'Source CIType', dstCIType: 'Target CIType',
sourceCITypeTips: 'Please select Source CIType', dstCITypeTips: 'Please select target CIType',
dstCIType: 'Target CIType', relationType: 'Relation Type',
dstCITypeTips: 'Please select target CIType', relationTypeTips: 'Please select relation type',
relationType: 'Relation Type', isParent: 'is parent',
relationTypeTips: 'Please select relation type', relationConstraint: 'Constraints',
isParent: 'is parent', relationConstraintTips: 'please select a relationship constraint',
relationConstraint: 'Constraints', one2Many: 'One to Many',
relationConstraintTips: 'please select a relationship constraint', one2One: 'One to One',
one2Many: 'One to Many', many2Many: 'Many to Many',
one2One: 'One to One', basicInfo: 'Basic Information',
many2Many: 'Many to Many', nameInputTips: 'Please enter name',
basicInfo: 'Basic Information', triggerDataChange: 'Data changes',
nameInputTips: 'Please enter name', triggerDate: 'Date attribute',
triggerDataChange: 'Data changes', triggerEnable: 'Turn on',
triggerDate: 'Date attribute', descInput: 'Please enter remarks',
triggerEnable: 'Turn on', triggerCondition: 'Triggering conditions',
descInput: 'Please enter remarks', addInstance: 'Add new instance',
triggerCondition: 'Triggering conditions', deleteInstance: 'Delete instance',
addInstance: 'Add new instance', changeInstance: 'Instance changes',
deleteInstance: 'Delete instance', selectMutipleAttributes: 'Please select attributes (multiple selections)',
changeInstance: 'Instance changes', selectSingleAttribute: 'Please select an attribute (single choice)',
selectMutipleAttributes: 'Please select attributes (multiple selections)', beforeDays: 'ahead of time',
selectSingleAttribute: 'Please select an attribute (single choice)', days: 'Days',
beforeDays: 'ahead of time', notifyAt: 'Send time',
days: 'Days', notify: 'Notify',
notifyAt: 'Send time', triggerAction: 'Trigger action',
notify: 'Notify', receivers: 'Recipients',
triggerAction: 'Trigger action', emailTips: 'Please enter your email address, separate multiple email addresses with ;',
receivers: 'Recipients', customEmail: 'Custom recipients',
emailTips: 'Please enter your email address, separate multiple email addresses with ;', notifySubject: 'Notification title',
customEmail: 'Custom recipients', notifySubjectTips: 'Please enter notification title',
notifySubject: 'Notification title', notifyContent: 'Content',
notifySubjectTips: 'Please enter notification title', notifyMethod: 'Notify methods',
notifyContent: 'Content', botSelect: 'Please select a robot',
notifyMethod: 'Notify methods', refAttributeTips: 'The title and content can reference the attribute value of the CIType. The reference method is: {{ attr_name }}',
botSelect: 'Please select a robot', webhookRefAttributeTips: 'Request parameters can reference the attribute value of the model. The reference method is: {{ attr_name }}',
refAttributeTips: 'The title and content can reference the attribute value of the CIType. The reference method is: {{ attr_name }}', newTrigger: 'Add trigger',
webhookRefAttributeTips: 'Request parameters can reference the attribute value of the model. The reference method is: {{ attr_name }}', editTriggerTitle: 'Edit trigger {name}',
newTrigger: 'Add trigger', newTriggerTitle: 'Add trigger {name}',
editTriggerTitle: 'Edit trigger {name}', confirmDeleteTrigger: 'Are you sure to delete this trigger?',
newTriggerTitle: 'Add trigger {name}', int: 'Integer',
confirmDeleteTrigger: 'Are you sure to delete this trigger?', float: 'Float',
int: 'Integer', text: 'Text',
float: 'Float', datetime: 'DateTime',
text: 'Text', date: 'Date',
datetime: 'DateTime', time: 'Time',
date: 'Date', json: 'JSON',
time: 'Time', event: 'Event'
json: 'JSON', },
event: 'Event', components: {
reg: 'Regex', unselectAttributes: 'Unselected',
isInherit: 'Inherit', selectAttributes: 'Selected',
inheritType: 'Inherit Type', downloadCI: 'Export data',
inheritTypePlaceholder: 'Please select inherit types', filename: 'Filename',
inheritFrom: 'inherit from {name}', filenameInputTips: 'Please enter filename',
groupInheritFrom: 'Please go to the {name} for modification' saveType: 'Save type',
}, saveTypeTips: 'Please select save type',
components: { xlsx: 'Excel workbook (*.xlsx)',
unselectAttributes: 'Unselected', csv: 'CSV (comma separated) (*.csv)',
selectAttributes: 'Selected', html: 'Web page (*.html)',
downloadCI: 'Export data', xml: 'XML data (*.xml)',
filename: 'Filename', txt: 'Text file (tab delimited) (*.txt)',
filenameInputTips: 'Please enter filename', grantUser: 'Grant User/Department',
saveType: 'Save type', grantRole: 'Grant Role',
saveTypeTips: 'Please select save type', confirmRevoke: 'Confirm to delete the [Authorization] permission of [{name}]?',
xlsx: 'Excel workbook (*.xlsx)', readAttribute: 'View Attributes',
csv: 'CSV (comma separated) (*.csv)', readCI: 'View CIs',
html: 'Web page (*.html)', config: 'Configuration',
xml: 'XML data (*.xml)', ciTypeGrant: 'Grant CIType',
txt: 'Text file (tab delimited) (*.txt)', ciGrant: 'Grant CI',
grantUser: 'Grant User/Department', attributeGrant: 'Grant Attribute',
grantRole: 'Grant Role', relationGrant: 'Grant Relation',
confirmRevoke: 'Confirm to delete the [Authorization] permission of [{name}]?', perm: 'Permissions',
readAttribute: 'View Attributes', all: 'All',
readCI: 'View CIs', customize: 'Customize',
config: 'Configuration', none: 'None',
ciTypeGrant: 'Grant CIType', customizeFilterName: 'Please enter a custom filter name',
ciGrant: 'Grant CI', colorPickerError: 'Initialization color format error, use #fff or rgb format',
attributeGrant: 'Grant Attribute', example: 'Example value',
relationGrant: 'Grant Relation', aliyun: 'aliyun',
perm: 'Permissions', tencentcloud: 'Tencent Cloud',
all: 'All', huaweicloud: 'Huawei Cloud',
customize: 'Customize', beforeChange: 'Before change',
none: 'None', afterChange: 'After change',
customizeFilterName: 'Please enter a custom filter name', noticeContentTips: 'Please enter notification content',
colorPickerError: 'Initialization color format error, use #fff or rgb format', saveQuery: 'Save Filters',
example: 'Example value', pleaseSearch: 'Please search',
aliyun: 'aliyun', conditionFilter: 'Conditional filtering',
tencentcloud: 'Tencent Cloud', attributeDesc: 'Attribute Description',
huaweicloud: 'Huawei Cloud', ciSearchTips: '1. JSON attributes cannot be searched<br />2. If the search content includes commas, they need to be escaped,<br />3. Only index attributes are searched, non-index attributes use conditional filtering',
beforeChange: 'Before change', ciSearchTips2: 'For example: q=hostname:*0.0.0.0*',
afterChange: 'After change', subCIType: 'Subscription CIType',
noticeContentTips: 'Please enter notification content', already: 'already',
saveQuery: 'Save Filters', not: 'not',
pleaseSearch: 'Please search', sub: 'subscription',
conditionFilter: 'Conditional filtering', selectBelow: 'Please select below',
attributeDesc: 'Attribute Description', subSuccess: 'Subscription successful',
ciSearchTips: '1. JSON/password/link attributes cannot be searched\n2. If the search content includes commas, they need to be escaped\n3. Only index attributes are searched, non-index attributes use conditional filtering', selectMethods: 'Please select a method',
ciSearchTips2: 'For example: q=hostname:*0.0.0.0*', noAuthRequest: 'No certification requested yet',
subCIType: 'Subscription CIType', noParamRequest: 'No parameter certification yet',
already: 'already', requestParam: 'Request parameters',
not: 'not', param: 'Parameter{param}',
sub: 'subscription', value: 'Value{value}',
selectBelow: 'Please select below', clear: 'Clear',
subSuccess: 'Subscription successful', },
selectMethods: 'Please select a method', batch: {
noAuthRequest: 'No certification requested yet', downloadFailed: 'Download failed',
noParamRequest: 'No parameter certification yet', unselectCIType: 'No CIType selected yet',
requestParam: 'Request parameters', pleaseUploadFile: 'Please upload files',
param: 'Parameter{param}', batchUploadCanceled: 'Batch upload canceled',
value: 'Value{value}', selectCITypeTips: 'Please select CIType',
clear: 'Clear', downloadTemplate: 'Download Template',
}, drawTips: 'Click or drag files here to upload!',
batch: { supportFileTypes: 'Supported file types: xls, xlsx',
downloadFailed: 'Download failed', uploadResult: 'Upload results',
unselectCIType: 'No CIType selected yet', total: 'total',
pleaseUploadFile: 'Please upload files', successItems: 'items, succeeded',
batchUploadCanceled: 'Batch upload canceled', failedItems: 'items, failed',
selectCITypeTips: 'Please select CIType', items: 'items',
downloadTemplate: 'Download Template', errorTips: 'Error message',
drawTips: 'Click or drag files here to upload!', requestFailedTips: 'An error occurred with the request, please try again later',
supportFileTypes: 'Supported file types: xls, xlsx', requestSuccessTips: 'Upload completed',
uploadResult: 'Upload results', },
total: 'total', preference: {
successItems: 'items, succeeded', mySub: 'My Subscription',
failedItems: 'items, failed', sub: 'Subscribe',
items: 'items', cancelSub: 'Unsubscribe',
errorTips: 'Error message', editSub: 'Edit subscription',
requestFailedTips: 'An error occurred with the request, please try again later', peopleSub: ' people subscribed',
requestSuccessTips: 'Upload completed', noSub: 'No subscribed',
}, cancelSubSuccess: 'Unsubscribe successfully',
preference: { confirmcancelSub: 'Are you sure to cancel your subscription?',
mySub: 'My Subscription', confirmcancelSub2: 'Are you sure you want to unsubscribe {name}?',
sub: 'Subscribe', of: 'of',
cancelSub: 'Unsubscribe', hoursAgo: 'hours ago',
editSub: 'Edit subscription', daysAgo: 'days ago',
peopleSub: ' people subscribed', monthsAgo: 'month ago',
noSub: 'No subscribed', yearsAgo: 'years ago',
cancelSubSuccess: 'Unsubscribe successfully', just: 'just now',
confirmcancelSub: 'Are you sure to cancel your subscription?', },
confirmcancelSub2: 'Are you sure you want to unsubscribe {name}?', custom_dashboard: {
of: 'of', charts: 'Chart',
hoursAgo: 'hours ago', newChart: 'Add Chart',
daysAgo: 'days ago', editChart: 'Edit Chart',
monthsAgo: 'month ago', title: 'Title',
yearsAgo: 'years ago', titleTips: 'Please enter a chart title',
just: 'just now', calcIndicators: 'Counter',
}, dimensions: 'Dimensions',
custom_dashboard: { selectDimensions: 'Please select a dimension',
charts: 'Chart', quantity: 'Quantity',
newChart: 'Add Chart', childCIType: 'Relational CIType',
editChart: 'Edit Chart', level: 'Level',
title: 'Title', levelTips: 'Please enter the relationship level',
titleTips: 'Please enter a chart title', preview: 'Preview',
calcIndicators: 'Counter', showIcon: 'Display icon',
dimensions: 'Dimensions', chartType: 'Chart Type',
selectDimensions: 'Please select a dimension', dataFilter: 'Data Filtering',
quantity: 'Quantity', format: 'Formats',
childCIType: 'Relational CIType', fontColor: 'Font Color',
level: 'Level', backgroundColor: 'Background',
levelTips: 'Please enter the relationship level', chartColor: 'Chart Color',
preview: 'Preview', chartLength: 'Length',
showIcon: 'Display icon', barType: 'Bar Type',
chartType: 'Chart Type', stackedBar: 'Stacked Bar',
dataFilter: 'Data Filtering', multipleSeriesBar: 'Multiple Series Bar ',
format: 'Formats', axis: 'Axis',
fontColor: 'Font Color', direction: 'Direction',
backgroundColor: 'Background', lowerShadow: 'Lower Shadow',
chartColor: 'Chart Color', count: 'Indicator',
chartLength: 'Length', bar: 'Bar',
barType: 'Bar Type', line: 'Line',
stackedBar: 'Stacked Bar', pie: 'Pie',
multipleSeriesBar: 'Multiple Series Bar ', table: 'Table',
axis: 'Axis', default: 'default',
direction: 'Direction', relation: 'Relation',
lowerShadow: 'Lower Shadow', noCustomDashboard: 'The administrator has not customized the dashboard yet',
count: 'Indicator', },
bar: 'Bar', preference_relation: {
line: 'Line', newServiceTree: 'Add ServiceTree',
pie: 'Pie', serviceTreeName: 'Name',
table: 'Table', public: 'Public',
default: 'default', saveLayout: 'Save Layout',
relation: 'Relation', childNodesNotFound: 'There are no child nodes and no business relationship can be formed. Please select again!',
noCustomDashboard: 'The administrator has not customized the dashboard yet', tips1: 'Cannot form a view with the currently selected node, please select again!',
}, tips2: 'Please enter the new serviceTree name!',
preference_relation: { tips3: 'Please select at least two nodes!',
newServiceTree: 'Add ServiceTree', },
serviceTreeName: 'Name', history: {
public: 'Public', ciChange: 'CI',
saveLayout: 'Save Layout', relationChange: 'Relation',
childNodesNotFound: 'There are no child nodes and no business relationship can be formed. Please select again!', ciTypeChange: 'CIType',
tips1: 'Cannot form a view with the currently selected node, please select again!', triggerHistory: 'Triggers',
tips2: 'Please enter the new serviceTree name!', opreateTime: 'Operate Time',
tips3: 'Please select at least two nodes!', user: 'User',
}, userTips: 'Enter filter username',
history: { filter: 'Search',
ciChange: 'CI', filterOperate: 'fitler operation',
relationChange: 'Relation', attribute: 'Attribute',
ciTypeChange: 'CIType', old: 'Old',
triggerHistory: 'Triggers', new: 'New',
opreateTime: 'Operate Time', noUpdate: 'No update',
user: 'User', itemsPerPage: '/page',
userTips: 'Enter filter username', triggerName: 'Name',
filter: 'Search', event: 'Event',
filterOperate: 'fitler operation', action: 'Actoin',
attribute: 'Attribute', status: 'Status',
old: 'Old', done: 'Done',
new: 'New', undone: 'Undone',
noUpdate: 'No update', triggerTime: 'Trigger Time',
itemsPerPage: '/page', totalItems: '{total} records in total',
triggerName: 'Name', pleaseSelect: 'Please select',
event: 'Event', startTime: 'Start Time',
action: 'Actoin', endTime: 'End Time',
status: 'Status', deleteCIType: 'Delete CIType',
done: 'Done', addCIType: 'Add CIType',
undone: 'Undone', updateCIType: 'Update CIType',
triggerTime: 'Trigger Time', addAttribute: 'Add Attribute',
totalItems: '{total} records in total', updateAttribute: 'Update Attribute',
pleaseSelect: 'Please select', deleteAttribute: 'Delete Attribute',
startTime: 'Start Time', addTrigger: 'Add Trigger',
endTime: 'End Time', updateTrigger: 'Update Trigger',
deleteCIType: 'Delete CIType', deleteTrigger: 'Delete Trigger',
addCIType: 'Add CIType', addUniqueConstraint: 'Add Unique Constraint',
updateCIType: 'Update CIType', updateUniqueConstraint: 'Update Unique Constraint',
addAttribute: 'Add Attribute', deleteUniqueConstraint: 'Delete Unique Constraint',
updateAttribute: 'Update Attribute', addRelation: 'Add Relation',
deleteAttribute: 'Delete Attribute', deleteRelation: 'Delete Relation',
addTrigger: 'Add Trigger', noModifications: 'No Modifications',
updateTrigger: 'Update Trigger', attr: 'attribute',
deleteTrigger: 'Delete Trigger', attrId: 'attribute id',
addUniqueConstraint: 'Add Unique Constraint', changeDescription: 'attribute id: {attr_id}, {before_days} day(s) in advance, Subject: {subject}\nContent: {body}\nNotify At: {notify_at}'
updateUniqueConstraint: 'Update Unique Constraint', },
deleteUniqueConstraint: 'Delete Unique Constraint', relation_type: {
addRelation: 'Add Relation', addRelationType: 'New',
deleteRelation: 'Delete Relation', nameTips: 'Please enter a type name',
noModifications: 'No Modifications', },
attr: 'attribute', ad: {
attrId: 'attribute id', upload: 'Import',
changeDescription: 'attribute id: {attr_id}, {before_days} day(s) in advance, Subject: {subject}\nContent: {body}\nNotify At: {notify_at}' download: 'Export',
}, accpet: 'Accept',
relation_type: { accpetBy: 'Accept By',
addRelationType: 'New', acceptTime: 'Accept Time',
nameTips: 'Please enter a type name', confirmAccept: 'Confirm Accept?',
}, accpetSuccess: 'Accept successfully',
ad: { isAccpet: 'Is accept',
upload: 'Import', deleteADC: 'Confirm to delete this data?',
download: 'Export', batchDelete: 'Confirm to delete this data?',
accept: 'Accept', agent: 'Built-in & Plug-ins',
acceptBy: 'Accept By', snmp: 'Network Devices',
acceptTime: 'Accept Time', http: 'Public Clouds',
confirmAccept: 'Confirm Accept?', rule: 'AutoDiscovery Rules',
acceptSuccess: 'Accept successfully', timeout: 'Timeout error',
isAccept: 'Is accept', mode: 'Mode',
deleteADC: 'Confirm to delete this data?', collectSettings: 'Collection Settings',
batchDelete: 'Confirm to delete this data?', updateFields: 'Update Field',
agent: 'Built-in & Plug-ins', pluginScript: `# -*- coding:utf-8 -*-
snmp: 'Network Devices',
http: 'Public Clouds', import json
rule: 'AutoDiscovery Rules',
timeout: 'Timeout error',
mode: 'Mode', class AutoDiscovery(object):
collectSettings: 'Collection Settings',
updateFields: 'Update Field', @property
pluginScript: `# -*- coding:utf-8 -*- def unique_key(self):
"""
import json
:return: Returns the name of a unique attribute
"""
class AutoDiscovery(object): return
@property @staticmethod
def unique_key(self): def attributes():
""" """
Define attribute fields
:return: Returns the name of a unique attribute :return: Returns a list of attribute fields. The list items are (name, type, description). The name must be in English.
""" type: String Integer Float Date DateTime Time JSON
return For example:
return [
@staticmethod ("ci_type", "String", "CIType name"),
def attributes(): ("private_ip", "String", "Internal IP, multiple values separated by commas")
""" ]
Define attribute fields """
:return: Returns a list of attribute fields. The list items are (name, type, description). The name must be in English. return []
type: String Integer Float Date DateTime Time JSON
For example: @staticmethod
return [ def run():
("ci_type", "String", "CIType name"), """
("private_ip", "String", "Internal IP, multiple values separated by commas") Execution entry, returns collected attribute values
] :return:
""" Returns a list, the list item is a dictionary, the dictionary key is the attribute name, and the value is the attribute value
return [] For example:
return [dict(ci_type="server", private_ip="192.168.1.1")]
@staticmethod """
def run(): return []
"""
Execution entry, returns collected attribute values
:return: if __name__ == "__main__":
Returns a list, the list item is a dictionary, the dictionary key is the attribute name, and the value is the attribute value result = AutoDiscovery().run()
For example: if isinstance(result, list):
return [dict(ci_type="server", private_ip="192.168.1.1")] print("AutoDiscovery::Result::{}".format(json.dumps(result)))
""" else:
return [] print("ERROR: The collection return must be a list")
`,
server: 'Server',
if __name__ == "__main__": vserver: 'VServer',
result = AutoDiscovery().run() nic: 'NIC',
if isinstance(result, list): disk: 'harddisk',
print("AutoDiscovery::Result::{}".format(json.dumps(result))) },
else: ci: {
print("ERROR: The collection return must be a list") attributeDesc: 'Attribute Description',
`, selectRows: 'Select: {rows} items',
server: 'Server', addRelation: 'Add Relation',
vserver: 'VServer', all: 'All',
nic: 'NIC', batchUpdate: 'Batch Update',
disk: 'harddisk', batchUpdateConfirm: 'Are you sure you want to make batch updates?',
}, batchUpdateInProgress: 'Currently being updated in batches',
ci: { batchUpdateInProgress2: 'Updating in batches, {total} in total, {successNum} successful, {errorNum} failed',
attributeDesc: 'Attribute Description', batchDeleting: 'Deleting...',
selectRows: 'Select: {rows} items', batchDeleting2: 'Deleting {total} items in total, {successNum} items successful, {errorNum} items failed',
addRelation: 'Add Relation', copyFailed: 'Copy failed',
all: 'All', noLevel: 'No hierarchical relationship!',
batchUpdate: 'Batch Update', batchAddRelation: 'Batch Add Relation',
batchUpdateConfirm: 'Are you sure you want to make batch updates?', history: 'History',
batchUpdateInProgress: 'Currently being updated in batches', topo: 'Topology',
batchUpdateInProgress2: 'Updating in batches, {total} in total, {successNum} successful, {errorNum} failed', table: 'Table',
batchDeleting: 'Deleting...', m2mTips: 'The current CIType relationship is many-to-many, please go to the SerivceTree(relation view) to add or delete',
batchDeleting2: 'Deleting {total} items in total, {successNum} items successful, {errorNum} items failed', confirmDeleteRelation: 'Confirm to delete the relationship?',
copyFailed: 'Copy failed', tips1: 'Use commas to separate multiple values',
noLevel: 'No hierarchical relationship!', tips2: 'The field can be modified as needed. When the value is empty, the field will be left empty.',
batchAddRelation: 'Batch Add Relation', tips3: 'Please select the fields that need to be modified',
history: 'History', tips4: 'At least one field must be selected',
topo: 'Topology', tips5: 'Search name | alias',
table: 'Table', tips6: 'Speed up retrieval, full-text search possible, no need to use conditional filtering\n\n json currently does not support indexing \n\nText characters longer than 190 cannot be indexed',
m2mTips: 'The current CIType relationship is many-to-many, please go to the SerivceTree(relation view) to add or delete', tips7: 'The form of expression is a drop-down box, and the value must be in the predefined value',
confirmDeleteRelation: 'Confirm to delete the relationship?', tips8: 'Multiple values, such as intranet IP',
tips1: 'Use commas to separate multiple values', tips9: 'For front-end only',
tips2: 'The field can be modified as needed. When the value is empty, the field will be left empty.', tips10: 'Other attributes of the CIType are computed using expressions\n\nA code snippet computes the returned value.',
tips3: 'Please select the fields that need to be modified', newUpdateField: 'Add a Attribute',
tips4: 'At least one field must be selected', attributeSettings: 'Attribute Settings',
tips5: 'Search name | alias', },
tips6: 'Speed up retrieval, full-text search possible, no need to use conditional filtering\n\n json/link/password currently does not support indexing \n\nText characters longer than 190 cannot be indexed', serviceTree: {
tips7: 'The form of expression is a drop-down box, and the value must be in the predefined value', deleteNode: 'Delete Node',
tips8: 'Multiple values, such as intranet IP', tips1: 'For example: q=os_version:centos&sort=os_version',
tips9: 'For front-end only', tips2: 'Expression search',
tips10: 'Other attributes of the CIType are computed using expressions\n\nA code snippet computes the returned value.', alert1: 'The administrator has not configured the ServiceTree(relation view), or you do not have permission to access it!',
newUpdateField: 'Add a Attribute', copyFailed: 'Copy failed',
attributeSettings: 'Attribute Settings', deleteRelationConfirm: 'Confirm to remove selected {name} from current relationship?',
share: 'Share', },
noPermission: 'No Permission' tree: {
}, tips1: 'Please go to Preference page first to complete your subscription!',
serviceTree: { subSettings: 'Settings',
deleteNode: 'Delete Node', }
tips1: 'For example: q=os_version:centos&sort=os_version', }
tips2: 'Expression search', export default cmdb_en
alert1: 'The administrator has not configured the ServiceTree(relation view), or you do not have permission to access it!',
copyFailed: 'Copy failed',
deleteRelationConfirm: 'Confirm to remove selected {name} from current relationship?',
batch: 'Batch',
grantTitle: 'Grant(read)',
userPlaceholder: 'Please select users',
rolePlaceholder: 'Please select roles',
grantedByServiceTree: 'Granted By Service Tree:',
grantedByServiceTreeTips: 'Please delete id_filter in Servive Tree',
peopleHasRead: 'Personnel authorized to read:',
authorizationPolicy: 'CI Authorization Policy:',
idAuthorizationPolicy: 'Authorized by node:',
view: 'View permissions'
},
tree: {
tips1: 'Please go to Preference page first to complete your subscription!',
subSettings: 'Settings',
}
}
export default cmdb_en

View File

@@ -1,502 +1,482 @@
const cmdb_zh = { const cmdb_zh = {
relation: '关系', relation: '关系',
attribute: '属性', attribute: '属性',
menu: { menu: {
views: '视图', views: '视图',
config: '配置', config: '配置',
backend: '管理端', backend: '管理端',
ciTable: '资源数据', ciTable: '资源数据',
ciTree: '资源层级', ciTree: '资源层级',
ciSearch: '资源搜索', ciSearch: '资源搜索',
adCIs: '自动发现池', adCIs: '自动发现池',
preference: '我的订阅', preference: '我的订阅',
batchUpload: '批量导入', batchUpload: '批量导入',
citypeManage: '模型配置', citypeManage: '模型配置',
backendManage: '后台管理', backendManage: '后台管理',
customDashboard: '定制仪表盘', customDashboard: '定制仪表盘',
serviceTreeDefine: '服务树定义', serviceTreeDefine: '服务树定义',
citypeRelation: '模型关系', citypeRelation: '模型关系',
operationHistory: '操作审计', operationHistory: '操作审计',
relationType: '关系类型', relationType: '关系类型',
ad: '自动发现', ad: '自动发现',
cidetail: 'CI 详情' },
}, ciType: {
ciType: { ciType: '模型',
ciType: '模型', attributes: '模型属性',
attributes: '模型属性', relation: '模型关联',
relation: '模型关联', trigger: '触发器',
trigger: '触发器', attributeAD: '属性自动发现',
attributeAD: '属性自动发现', relationAD: '关系自动发现',
relationAD: '关系自动发现', grant: '权限配置',
grant: '权限配置', addGroup: '新增分组',
addGroup: '新增分组', editGroup: '修改分组',
editGroup: '修改分组', group: '分组',
group: '分组', attributeLibray: '属性库',
attributeLibray: '属性库', addCITypeInGroup: '在该组中新增CI模型',
addCITypeInGroup: '在该组中新增CI模型', addCIType: '新增CI模型',
addCIType: '新增CI模型', editGroupName: '编辑组名称',
editGroupName: '编辑组名称', deleteGroup: '删除该组',
deleteGroup: '删除该组', CITypeName: '模型名(英文)',
CITypeName: '模型名(英文)', English: '英文',
English: '英文', inputAttributeName: '请输入属性名',
inputAttributeName: '请输入属性名', attributeNameTips: '不能以数字开头,可以是英文 数字以及下划线 (_)',
attributeNameTips: '不能以数字开头,可以是英文 数字以及下划线 (_)', editCIType: '编辑模型',
editCIType: '编辑模型', defaultSort: '默认排序',
defaultSort: '默认排序', selectDefaultOrderAttr: '选择默认排序属性',
selectDefaultOrderAttr: '选择默认排序属性', asec: '正序',
asec: '序', desc: '序',
desc: '倒序', uniqueKey: '唯一标识',
uniqueKey: '唯一标识', uniqueKeySelect: '请选择唯一标识',
uniqueKeySelect: '请选择唯一标识', notfound: '找不到想要的?',
uniqueKeyTips: 'json、密码、计算属性、预定义值属性不能作为唯一标识', cannotDeleteGroupTips: '该分组下有数据, 不能删除!',
notfound: '找不到想要的?', confirmDeleteGroup: '确定要删除分组 【{groupName}】 吗?',
cannotDeleteGroupTips: '该分组下有数据, 不能删除!', confirmDeleteCIType: '确定要删除模型 【{typeName}】 吗?',
confirmDeleteGroup: '确定要删除分组 【{groupName}】 吗?', uploading: '正在导入中',
confirmDeleteCIType: '确定要删除模型 【{typeName}】 吗?', uploadFailed: '导入失败,请稍后重试',
uploading: '正在导入中', addPlugin: '新建plugin',
uploadFailed: '导入失败,请稍后重试', deletePlugin: '删除plugin',
addPlugin: '新建plugin', confirmDeleteADT: '确认删除 【{pluginName}】',
deletePlugin: '删除plugin', attributeMap: '字段映射',
confirmDeleteADT: '确认删除 【{pluginName}】', autoDiscovery: '自动发现',
attributeMap: '字段映射', node: '节点',
autoDiscovery: '自动发现', adExecConfig: '执行配置',
node: '节点', adExecTarget: '执行机器',
adExecConfig: '执行配置', oneagentIdTips: '请输入以0x开头的16进制OneAgent ID',
adExecTarget: '执行机器', selectFromCMDBTips: '从CMDB中选择 ',
oneagentIdTips: '请输入以0x开头的16进制OneAgent ID', adAutoInLib: '自动入库',
selectFromCMDBTips: '从CMDB中选择 ', adInterval: '采集频率',
adAutoInLib: '自动入库', byInterval: '按间隔',
adInterval: '采集频率', allNodes: '所有节点',
byInterval: '按间隔', specifyNodes: '指定节点',
allNodes: '所有节点', specifyNodesTips: '请填写指定节点',
specifyNodes: '指定节点', username: '用户名',
specifyNodesTips: '请填写指定节点!', password: '密码',
username: '用户名', link: '链接',
password: '密码', list: '多值',
link: '链接', listTips: '字段的值是1个或者多个接口返回的值的类型是list',
list: '多值', computeForAllCITips: '所有CI触发计算',
listTips: '字段的值是1个或者多个接口返回的值的类型是list', confirmcomputeForAllCITips: '确认触发所有CI的计算',
computeForAllCITips: '所有CI触发计算', isUnique: '是否唯一',
confirmcomputeForAllCITips: '确认触发所有CI的计算', unique: '唯一',
isUnique: '是否唯一', isChoice: '是否选择',
unique: '唯一', defaultShow: '默认显示',
isChoice: '是否选择', defaultShowTips: 'CI实例表格默认展示该字段',
defaultShow: '默认显示', isSortable: '可排序',
defaultShowTips: 'CI实例表格默认展示该字段', isIndex: '是否索引',
isSortable: '可排序', index: '索引',
isIndex: '是否索引', indexTips: '字段可被用于检索,加速查询',
index: '索引', confirmDelete: '确认删除【{name}】?',
indexTips: '字段可被用于检索,加速查询', confirmDelete2: '确认删除?',
confirmDelete: '确认删除【{name}】?', computeSuccess: '触发成功!',
confirmDelete2: '确认删除?', basicConfig: '基础设置',
computeSuccess: '触发成功!', AttributeName: '属性名(英文)',
basicConfig: '基础设置', DataType: '数据类型',
AttributeName: '属性名(英文)', defaultValue: '默认值',
DataType: '数据类型', autoIncID: '自增ID',
defaultValue: '默认值', customTime: '自定义时间',
autoIncID: '自增ID', advancedSettings: '高级设置',
customTime: '自定义时间', font: '字体',
advancedSettings: '高级设置', color: '颜色',
font: '字体', choiceValue: '预定义值',
color: '颜色', computedAttribute: '计算属性',
choiceValue: '预定义值', computedAttributeTips: '该属性的值是通过模型的其它属性构建的表达式或者执行一段代码的方式计算而来,属性的引用方法为: {{ 属性名 }}',
computedAttribute: '计算属性', addAttribute: '新增属性',
computedAttributeTips: '该属性的值是通过模型的其它属性构建的表达式或者执行一段代码的方式计算而来,属性的引用方法为: {{ 属性名 }}', existedAttributes: '已有属性',
addAttribute: '新增属性', editAttribute: '编辑属性',
existedAttributes: '已有属性', addAttributeTips1: '选中排序,则必须也要选中!',
editAttribute: '编辑属性', uniqueConstraint: '唯一校验',
addAttributeTips1: '选中排序,则必须也要选中!', up: '上移',
uniqueConstraint: '唯一校验', down: '下移',
up: '上移', selectAttribute: '添加属性',
down: '下移', groupExisted: '分组名称已存在',
selectAttribute: '添加属性', attributeSortedTips: '其他分组中的属性不能进行排序,如需排序请先拖至自定义的分组!',
groupExisted: '分组名称已存在', buildinAttribute: '内置字段',
attributeSortedTips: '其他分组中的属性不能进行排序,如需排序请先拖至自定义的分组!', expr: '表达式',
buildinAttribute: '内置字段', code: '代码',
expr: '表达式', apply: '应用',
code: '代码', continueAdd: '继续添加',
apply: '应用', filter: '过滤',
continueAdd: '继续添加', choiceOther: '其他模型属性',
filter: '过滤', choiceWebhookTips: '返回的结果按字段来过滤,层级嵌套用##分隔比如k1##k2web请求返回{k1: [{k2: 1}, {k2: 2}]}, 解析结果为[1, 2]',
choiceOther: '其他模型属性', selectCIType: '请选择CMDB模型',
choiceWebhookTips: '返回的结果按字段来过滤,层级嵌套用##分隔比如k1##k2web请求返回{k1: [{k2: 1}, {k2: 2}]}, 解析结果为[1, 2]', selectCITypeAttributes: '请选择模型属性',
selectCIType: '请选择CMDB模型', selectAttributes: '请选择属性',
selectCITypeAttributes: '请选择模型属性', choiceScriptDemo: 'class ChoiceValue(object):\n @staticmethod\n def values():\n """\n 执行入口, 返回预定义值\n :return: 返回一个列表, 值的类型同属性的类型\n 例如:\n return ["在线", "下线"]\n """\n return []',
selectAttributes: '请选择属性', valueExisted: '当前值已存在!',
choiceScriptDemo: 'class ChoiceValue(object):\n @staticmethod\n def values():\n """\n 执行入口, 返回预定义值\n :return: 返回一个列表, 值的类型同属性的类型\n 例如:\n return ["在线", "下线"]\n """\n return []', addRelation: '新增关系',
valueExisted: '当前值已存在!', sourceCIType: '源模型',
addRelation: '新增关系', sourceCITypeTips: '请选择源模型',
sourceCIType: '模型', dstCIType: '目标模型',
sourceCITypeTips: '请选择模型', dstCITypeTips: '请选择目标模型',
dstCIType: '目标模型名', relationType: '关联类型',
dstCITypeTips: '请选择目标模型', relationTypeTips: '请选择关联类型',
relationType: '关联类型', isParent: '',
relationTypeTips: '请选择关联类型', relationConstraint: '关系约束',
isParent: '被', relationConstraintTips: '请选择关系约束',
relationConstraint: '关系约束', one2Many: '一对多',
relationConstraintTips: '请选择关系约束', one2One: '一对一',
one2Many: '对多', many2Many: '对多',
one2One: '一对一', basicInfo: '基本信息',
many2Many: '多对多', nameInputTips: '请输入名称',
basicInfo: '基本信息', triggerDataChange: '数据变更',
nameInputTips: '请输入名称', triggerDate: '日期属性',
triggerDataChange: '数据变更', triggerEnable: '开启',
triggerDate: '日期属性', descInput: '请输入备注',
triggerEnable: '开启', triggerCondition: '触发条件',
descInput: '请输入备注', addInstance: '新增实例',
triggerCondition: '触发条件', deleteInstance: '删除实例',
addInstance: '新增实例', changeInstance: '实例变更',
deleteInstance: '删除实例', selectMutipleAttributes: '请选择属性(多选)',
changeInstance: '实例变更', selectSingleAttribute: '请选择属性(单选)',
selectMutipleAttributes: '请选择属性(多选)', beforeDays: '提前',
selectSingleAttribute: '请选择属性(单选)', days: '天',
beforeDays: '提前', notifyAt: '发送时间',
days: '', notify: '通知',
notifyAt: '发送时间', triggerAction: '触发动作',
notify: '通知', receivers: '收件人',
triggerAction: '触发动作', emailTips: '请输入邮箱,多个邮箱用;分隔',
receivers: '收件人', customEmail: '自定义收件人',
emailTips: '请输入邮箱,多个邮箱用;分隔', notifySubject: '通知标题',
customEmail: '自定义收件人', notifySubjectTips: '请输入通知标题',
notifySubject: '通知标题', notifyContent: '内容',
notifySubjectTips: '请输入通知标题', notifyMethod: '通知方式',
notifyContent: '内容', botSelect: '请选择机器人',
notifyMethod: '通知方式', refAttributeTips: '标题、内容可以引用该模型的属性值,引用方法为: {{ attr_name }}',
botSelect: '请选择机器人', webhookRefAttributeTips: '请求参数可以引用该模型的属性值,引用方法为: {{ attr_name }}',
refAttributeTips: '标题、内容可以引用该模型的属性值,引用方法为: {{ attr_name }}', newTrigger: '新增触发器',
webhookRefAttributeTips: '请求参数可以引用该模型的属性值,引用方法为: {{ attr_name }}', editTriggerTitle: '编辑触发器 {name}',
newTrigger: '新增触发器', newTriggerTitle: '新增触发器 {name}',
editTriggerTitle: '编辑触发器 {name}', confirmDeleteTrigger: '确认删除该触发器吗?',
newTriggerTitle: '新增触发器 {name}', int: '整数',
confirmDeleteTrigger: '确认删除该触发器吗?', float: '浮点数',
int: '整数', text: '文本',
float: '浮点数', datetime: '日期时间',
text: '文本', date: '日期',
datetime: '日期时间', time: '时间',
date: '日期', json: 'JSON',
time: '时间', event: '事件'
json: 'JSON', },
event: '事件', components: {
reg: '正则校验', unselectAttributes: '未选属性',
isInherit: '是否继承', selectAttributes: '已选属性',
inheritType: '继承模型', downloadCI: '导出数据',
inheritTypePlaceholder: '请选择继承模型(多选)', filename: '文件名',
inheritFrom: '属性继承自{name}', filenameInputTips: '请输入文件名',
groupInheritFrom: '请至{name}进行修改' saveType: '保存类型',
}, saveTypeTips: '请选择保存类型',
components: { xlsx: 'Excel工作簿(*.xlsx)',
unselectAttributes: '未选属性', csv: 'CSV(逗号分隔)(*.csv)',
selectAttributes: '已选属性', html: '网页(*.html)',
downloadCI: '导出数据', xml: 'XML数据(*.xml)',
filename: '文件名', txt: '文本文件(制表符分隔)(*.txt)',
filenameInputTips: '请输入文件名', grantUser: '授权用户/部门',
saveType: '保存类型', grantRole: '授权角色',
saveTypeTips: '请选择保存类型', confirmRevoke: '确认删除 【{name}】 的 【授权】 权限?',
xlsx: 'Excel工作簿(*.xlsx)', readAttribute: '查看字段',
csv: 'CSV(逗号分隔)(*.csv)', readCI: '查看实例',
html: '网页(*.html)', config: '配置',
xml: 'XML数据(*.xml)', ciTypeGrant: '模型权限',
txt: '文本文件(制表符分隔)(*.txt)', ciGrant: '实例权限',
grantUser: '授权用户/部门', attributeGrant: '字段权限',
grantRole: '授权角色', relationGrant: '关系权限',
confirmRevoke: '确认删除 【{name}】 的 【授权】 权限', perm: '权限',
readAttribute: '查看字段', all: '全部',
readCI: '查看实例', customize: '自定义',
config: '配置', none: '',
ciTypeGrant: '模型权限', customizeFilterName: '请输入自定义筛选条件名',
ciGrant: '实例权限', colorPickerError: '初始化颜色格式错误,使用#fff或rgb格式',
attributeGrant: '字段权限', example: '示例值',
relationGrant: '关系权限', aliyun: '阿里云',
perm: '权限', tencentcloud: '腾讯云',
all: '全部', huaweicloud: '华为云',
customize: '自定义', beforeChange: '变更前',
none: '', afterChange: '变更后',
customizeFilterName: '请输入自定义筛选条件名', noticeContentTips: '请输入通知内容',
colorPickerError: '初始化颜色格式错误,使用#fff或rgb格式', saveQuery: '保存筛选条件',
example: '示例值', pleaseSearch: '请查找',
aliyun: '阿里云', conditionFilter: '条件过滤',
tencentcloud: '腾讯云', attributeDesc: '属性说明',
huaweicloud: '华为云', ciSearchTips: '1. json属性不能搜索<br />2. 搜索内容包括逗号, 则需转义 ,<br />3. 只搜索索引属性, 非索引属性使用条件过滤',
beforeChange: '变更前', ciSearchTips2: '例: q=hostname:*0.0.0.0*',
afterChange: '变更后', subCIType: '订阅模型',
noticeContentTips: '请输入通知内容', already: '已',
saveQuery: '保存筛选条件', not: '',
pleaseSearch: '请查找', sub: '订阅',
conditionFilter: '条件过滤', selectBelow: '请在下方进行选择',
attributeDesc: '属性说明', subSuccess: '订阅成功',
ciSearchTips: '1. json、密码、链接属性不能搜索\n2. 搜索内容包括逗号, 则需转义\n3. 只搜索索引属性, 非索引属性使用条件过滤', selectMethods: '请选择方式',
ciSearchTips2: '例: q=hostname:*0.0.0.0*', noAuthRequest: '暂无请求认证',
subCIType: '订阅模型', noParamRequest: '暂无参数认证',
already: '', requestParam: '请求参数',
not: '', param: '参数{param}',
sub: '订阅', value: '值{value}',
selectBelow: '请在下方进行选择', clear: '清空',
subSuccess: '订阅成功', },
selectMethods: '请选择方式', batch: {
noAuthRequest: '暂无请求认证', downloadFailed: '失败下载',
noParamRequest: '暂无参数认证', unselectCIType: '尚未选择模板类型',
requestParam: '请求参数', pleaseUploadFile: '请上传文件',
param: '参数{param}', batchUploadCanceled: '批量上传已取消',
value: '值{value}', selectCITypeTips: '请选择模板类型',
clear: '清空', downloadTemplate: '下载模板',
}, drawTips: '点击或拖拽文件至此上传!',
batch: { supportFileTypes: '支持文件类型xlsxlsx',
downloadFailed: '失败下载', uploadResult: '上传结果',
unselectCIType: '尚未选择模板类型', total: '共',
pleaseUploadFile: '请上传文件', successItems: '条,已成功',
batchUploadCanceled: '批量上传已取消', failedItems: '条,失败',
selectCITypeTips: '请选择模板类型', items: '',
downloadTemplate: '下载模板', errorTips: '错误信息',
drawTips: '点击或拖拽文件至此上传!', requestFailedTips: '请求出现错误,请稍后再试',
supportFileTypes: '支持文件类型xlsxlsx', requestSuccessTips: '批量上传已完成',
uploadResult: '上传结果', },
total: '共', preference: {
successItems: '条,已成功', mySub: '我的订阅',
failedItems: '条,失败', sub: '订阅',
items: '', cancelSub: '取消订阅',
errorTips: '错误信息', editSub: '编辑订阅',
requestFailedTips: '请求出现错误,请稍后再试', peopleSub: '位同事已订阅',
requestSuccessTips: '批量上传已完成', noSub: '暂无同事订阅',
}, cancelSubSuccess: '取消订阅成功',
preference: { confirmcancelSub: '确认取消订阅',
mySub: '我的订阅', confirmcancelSub2: '确认取消订阅 {name} 吗?',
sub: '订阅', of: '',
cancelSub: '取消订阅', hoursAgo: '小时前',
editSub: '编辑订阅', daysAgo: '天前',
peopleSub: '位同事已订阅', monthsAgo: '月前',
noSub: '暂无同事订阅', yearsAgo: '年前',
cancelSubSuccess: '取消订阅成功', just: '刚刚',
confirmcancelSub: '确认取消订阅', },
confirmcancelSub2: '确认取消订阅 {name} 吗?', custom_dashboard: {
of: '', charts: '图表',
hoursAgo: '小时前', newChart: '新增图表',
daysAgo: '天前', editChart: '编辑图表',
monthsAgo: '月前', title: '标题',
yearsAgo: '年前', titleTips: '请输入图表标题',
just: '刚刚', calcIndicators: '计算指标',
}, dimensions: '维度',
custom_dashboard: { selectDimensions: '请选择维度',
charts: '图表', quantity: '数量',
newChart: '新增图表', childCIType: '关系模型',
editChart: '编辑图表', level: '层级',
title: '标题', levelTips: '请输入关系层级',
titleTips: '请输入图表标题', preview: '预览',
calcIndicators: '计算指标', showIcon: '是否显示icon',
dimensions: '维度', chartType: '图表类型',
selectDimensions: '请选择维度', dataFilter: '数据筛选',
quantity: '数量', format: '格式',
childCIType: '关系模型', fontColor: '字体颜色',
level: '层级', backgroundColor: '背景颜色',
levelTips: '请输入关系层级', chartColor: '图表颜色',
preview: '预览', chartLength: '图表长度',
showIcon: '是否显示icon', barType: '柱状图类型',
chartType: '图表类型', stackedBar: '堆积柱状图',
dataFilter: '数据筛选', multipleSeriesBar: '多系列柱状图',
format: '格式', axis: '',
fontColor: '字体颜色', direction: '方向',
backgroundColor: '背景颜色', lowerShadow: '下方阴影',
chartColor: '图表颜色', count: '指标',
chartLength: '图表长度', bar: '柱状图',
barType: '柱状图类型', line: '折线图',
stackedBar: '堆积柱状图', pie: '状图',
multipleSeriesBar: '多系列柱状图', table: '表格',
axis: '', default: '默认',
direction: '方向', relation: '关系',
lowerShadow: '下方阴影', noCustomDashboard: '管理员暂未定制仪表盘',
count: '指标', },
bar: '柱状图', preference_relation: {
line: '折线图', newServiceTree: '新增服务树',
pie: '饼状图', serviceTreeName: '服务树名',
table: '表格', public: '公开',
default: '默认', saveLayout: '保存布局',
relation: '关系', childNodesNotFound: '不存在子节点,不能形成业务关系,请重新选择!',
noCustomDashboard: '管理员暂未定制仪表盘', tips1: '不能与当前选中节点形成视图,请重新选择!',
}, tips2: '请输入新增服务树名!',
preference_relation: { tips3: '请选择至少两个节点!',
newServiceTree: '新增服务树', },
serviceTreeName: '服务树名', history: {
public: '公开', ciChange: 'CI变更',
saveLayout: '保存布局', relationChange: '关系变更',
childNodesNotFound: '不存在子节点,不能形成业务关系,请重新选择!', ciTypeChange: '模型变更',
tips1: '不能与当前选中节点形成视图,请重新选择!', triggerHistory: '触发历史',
tips2: '请输入新增服务树名!', opreateTime: '操作时间',
tips3: '请选择至少两个节点!', user: '用户',
}, userTips: '输入筛选用户名',
history: { filter: '筛选',
ciChange: 'CI变更', filterOperate: '筛选操作',
relationChange: '关系变更', attribute: '属性',
ciTypeChange: '模型变更', old: '',
triggerHistory: '触发历史', new: '',
opreateTime: '操作时间', noUpdate: '没有修改',
user: '用户', itemsPerPage: '/页',
userTips: '输入筛选用户名', triggerName: '触发器名称',
filter: '筛选', event: '事件',
filterOperate: '筛选操作', action: '作',
attribute: '属性', status: '状态',
old: '', done: '已完成',
new: '', undone: '未完成',
noUpdate: '没有修改', triggerTime: '触发时间',
itemsPerPage: '/页', totalItems: '共 {total} 条记录',
triggerName: '触发器名称', pleaseSelect: '请选择',
event: '事件', startTime: '开始时间',
action: '动作', endTime: '结束时间',
status: '状态', deleteCIType: '删除模型',
done: '已完成', addCIType: '新增模型',
undone: '未完成', updateCIType: '修改模型',
triggerTime: '触发时间', addAttribute: '新增属性',
totalItems: '共 {total} 条记录', updateAttribute: '修改属性',
pleaseSelect: '请选择', deleteAttribute: '删除属性',
startTime: '开始时间', addTrigger: '新增触发器',
endTime: '结束时间', updateTrigger: '修改触发器',
deleteCIType: '删除模型', deleteTrigger: '删除触发器',
addCIType: '新增模型', addUniqueConstraint: '新增联合唯一',
updateCIType: '修改模型', updateUniqueConstraint: '修改联合唯一',
addAttribute: '新增属性', deleteUniqueConstraint: '删除联合唯一',
updateAttribute: '修改属性', addRelation: '新增关系',
deleteAttribute: '删除属性', deleteRelation: '删除关系',
addTrigger: '新增触发器', noModifications: '没有修改',
updateTrigger: '修改触发器', attr: '属性名',
deleteTrigger: '删除触发器', attrId: '属性ID',
addUniqueConstraint: '新增联合唯一', changeDescription: '属性ID{attr_id},提前:{before_days}天,主题:{subject}\n内容{body}\n通知时间{notify_at}'
updateUniqueConstraint: '修改联合唯一', },
deleteUniqueConstraint: '删除联合唯一', relation_type: {
addRelation: '新增关系', addRelationType: '新增关系类型',
deleteRelation: '删除关系', nameTips: '请输入类型名',
noModifications: '没有修改', },
attr: '属性名', ad: {
attrId: '属性ID', upload: '规则导入',
changeDescription: '属性ID{attr_id},提前:{before_days}天,主题:{subject}\n内容{body}\n通知时间{notify_at}' download: '规则导出',
}, accpet: '入库',
relation_type: { accpetBy: '入库人',
addRelationType: '新增关系类型', acceptTime: '入库时间',
nameTips: '请输入类型名', confirmAccept: '确认入库?',
}, accpetSuccess: '入库成功',
ad: { isAccpet: '是否入库',
upload: '规则导入', deleteADC: '确认删除该条数据?',
download: '规则导出', batchDelete: '确认删除这些数据?',
accept: '入库', agent: '内置 & 插件',
acceptBy: '入库人', snmp: '网络设备',
acceptTime: '入库时间', http: '公有云资源',
confirmAccept: '确认入库?', rule: '自动发现规则',
acceptSuccess: '入库成功', timeout: '超时错误',
isAccept: '是否入库', mode: '模式',
deleteADC: '确认删除该条数据?', collectSettings: '采集设置',
batchDelete: '确认删除这些数据?', updateFields: '更新字段',
agent: '内置 & 插件', pluginScript: `# -*- coding:utf-8 -*-
snmp: '网络设备',
http: '公有云资源', import json
rule: '自动发现规则',
timeout: '超时错误',
mode: '模式', class AutoDiscovery(object):
collectSettings: '采集设置',
updateFields: '更新字段', @property
pluginScript: `# -*- coding:utf-8 -*- def unique_key(self):
"""
import json
:return: 返回唯一属性的名字
"""
class AutoDiscovery(object): return
@property @staticmethod
def unique_key(self): def attributes():
""" """
定义属性字段
:return: 返回唯一属性的名字 :return: 返回属性字段列表, 列表项是(名称, 类型, 描述), 名称必须是英文
""" 类型: String Integer Float Date DateTime Time JSON
return 例如:
return [
@staticmethod ("ci_type", "String", "模型名称"),
def attributes(): ("private_ip", "String", "内网IP, 多值逗号分隔")
""" ]
定义属性字段 """
:return: 返回属性字段列表, 列表项是(名称, 类型, 描述), 名称必须是英文 return []
类型: String Integer Float Date DateTime Time JSON
例如: @staticmethod
return [ def run():
("ci_type", "String", "模型名称"), """
("private_ip", "String", "内网IP, 多值逗号分隔") 执行入口, 返回采集的属性值
] :return: 返回一个列表, 列表项是字典, 字典key是属性名称, value是属性值
""" 例如:
return [] return [dict(ci_type="server", private_ip="192.168.1.1")]
"""
@staticmethod return []
def run():
"""
执行入口, 返回采集的属性值 if __name__ == "__main__":
:return: 返回一个列表, 列表项是字典, 字典key是属性名称, value是属性值 result = AutoDiscovery().run()
例如: if isinstance(result, list):
return [dict(ci_type="server", private_ip="192.168.1.1")] print("AutoDiscovery::Result::{}".format(json.dumps(result)))
""" else:
return [] print("ERROR: 采集返回必须是列表")
`,
server: '物理机',
if __name__ == "__main__": vserver: '虚拟机',
result = AutoDiscovery().run() nic: '网卡',
if isinstance(result, list): disk: '硬盘',
print("AutoDiscovery::Result::{}".format(json.dumps(result))) },
else: ci: {
print("ERROR: 采集返回必须是列表") attributeDesc: '属性说明',
`, selectRows: '选取:{rows} 项',
server: '物理机', addRelation: '添加关系',
vserver: '虚拟机', all: '全部',
nic: '网卡', batchUpdate: '批量修改',
disk: '硬盘', batchUpdateConfirm: '确认要批量修改吗?',
}, batchUpdateInProgress: '正在批量修改',
ci: { batchUpdateInProgress2: '正在批量修改,共{total}个,成功{successNum}个,失败{errorNum}个',
attributeDesc: '属性说明', batchDeleting: '正在删除...',
selectRows: '选取:{rows} 项', batchDeleting2: '正在删除,共{total}个,成功{successNum}个,失败{errorNum}个',
addRelation: '添加关系', copyFailed: '复制失败!',
all: '全部', noLevel: '无层级关系!',
batchUpdate: '批量修改', batchAddRelation: '批量添加关系',
batchUpdateConfirm: '确认要批量修改吗?', history: '操作历史',
batchUpdateInProgress: '正在批量修改', topo: '拓扑',
batchUpdateInProgress2: '正在批量修改,共{total}个,成功{successNum}个,失败{errorNum}个', table: '表格',
batchDeleting: '正在删除...', m2mTips: '当前模型关系为多对多,请前往关系视图进行增删操作',
batchDeleting2: '正在删除,共{total}个,成功{successNum}个,失败{errorNum}个', confirmDeleteRelation: '确认删除关系?',
copyFailed: '复制失败!', tips1: '多个值使用,分割',
noLevel: '无层级关系!', tips2: '可根据需要修改字段,当值为 空 时,则该字段 置空',
batchAddRelation: '批量添加关系', tips3: '请选择需要修改的字段',
history: '操作历史', tips4: '必须至少选择一个字段',
topo: '拓扑', tips5: '搜索 名称 | 别名',
table: '表格', tips6: '加快检索, 可以全文搜索, 无需使用条件过滤\n\n json目前不支持建索引 \n\n文本字符长度超过190不能建索引',
m2mTips: '当前模型关系为多对多,请前往关系视图进行增删操作', tips7: '表现形式是下拉框, 值必须在预定义值里',
confirmDeleteRelation: '确认删除关系?', tips8: '多值, 比如内网IP',
tips1: '多个值使用,分割', tips9: '仅针对前端',
tips2: '可根据需要修改字段,当值为 空 时,则该字段 置空', tips10: '模型的其他属性通过表达式的方式计算出来\n\n一个代码片段计算返回的值',
tips3: '请选择需要修改字段', newUpdateField: '新增修改字段',
tips4: '必须至少选择一个字段', attributeSettings: '字段设置',
tips5: '搜索 名称 | 别名', },
tips6: '加快检索, 可以全文搜索, 无需使用条件过滤\n\n json、链接、密码目前不支持建索引 \n\n文本字符长度超过190不能建索引', serviceTree: {
tips7: '表现形式是下拉框, 值必须在预定义值里', deleteNode: '删除节点',
tips8: '多值, 比如内网IP', tips1: 'q=os_version:centos&sort=os_version',
tips9: '仅针对前端', tips2: '表达式搜索',
tips10: '模型的其他属性通过表达式的方式计算出来\n\n一个代码片段计算返回的值', alert1: '管理员 还未配置业务关系, 或者你无权限访问!',
newUpdateField: '新增修改字段', copyFailed: '复制失败',
attributeSettings: '字段设置', deleteRelationConfirm: '确认将选中的 {name} 从当前关系中删除?',
share: '分享', },
noPermission: '暂无权限' tree: {
}, tips1: '请先到 我的订阅 页面完成订阅!',
serviceTree: { subSettings: '订阅设置',
deleteNode: '删除节点', }
tips1: '例q=os_version:centos&sort=os_version', }
tips2: '表达式搜索', export default cmdb_zh
alert1: '管理员 还未配置业务关系, 或者你无权限访问!',
copyFailed: '复制失败',
deleteRelationConfirm: '确认将选中的 {name} 从当前关系中删除?',
batch: '批量操作',
grantTitle: '授权(查看权限)',
userPlaceholder: '请选择用户',
rolePlaceholder: '请选择角色',
grantedByServiceTree: '服务树授权:',
grantedByServiceTreeTips: '请先在服务树里删掉节点授权',
peopleHasRead: '当前有查看权限的人员:',
authorizationPolicy: '实例授权策略:',
idAuthorizationPolicy: '按节点授权的:',
view: '查看权限'
},
tree: {
tips1: '请先到 我的订阅 页面完成订阅!',
subSettings: '订阅设置',
}
}
export default cmdb_zh

View File

@@ -56,13 +56,6 @@ const genCmdbRoutes = async () => {
meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc-selected', keepAlive: false }, meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc-selected', keepAlive: false },
component: () => import('../views/discoveryCI/index.vue') component: () => import('../views/discoveryCI/index.vue')
}, },
{
path: `/cmdb/cidetail/:typeId/:ciId`,
name: 'cmdb_ci_detail',
hidden: true,
meta: { title: 'cmdb.menu.cidetail', keepAlive: false },
component: () => import('../views/ci/ciDetailPage.vue')
},
{ {
path: '/cmdb/disabled2', path: '/cmdb/disabled2',
name: 'cmdb_disabled2', name: 'cmdb_disabled2',
@@ -145,7 +138,7 @@ const genCmdbRoutes = async () => {
const [preference, relation] = await Promise.all([getPreference(), getRelationView()]) const [preference, relation] = await Promise.all([getPreference(), getRelationView()])
preference.forEach(item => { preference.forEach(item => {
routes.children[2].children.push({ routes.children[2].children.unshift({
path: `/cmdb/instances/types/${item.id}`, path: `/cmdb/instances/types/${item.id}`,
component: () => import(`../views/ci/index`), component: () => import(`../views/ci/index`),
name: `cmdb_${item.id}`, name: `cmdb_${item.id}`,

View File

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

View File

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

View File

@@ -57,7 +57,7 @@
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span> <span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span>
</div> </div>
</SearchForm> </SearchForm>
<CiDetailDrawer ref="detail" :typeId="typeId" /> <CiDetail ref="detail" :typeId="typeId" />
<ops-table <ops-table
:id="`cmdb-ci-${typeId}`" :id="`cmdb-ci-${typeId}`"
border border
@@ -101,6 +101,7 @@
:cell-type="col.value_type === '2' ? 'string' : 'auto'" :cell-type="col.value_type === '2' ? 'string' : 'auto'"
:fixed="col.is_fixed ? 'left' : ''" :fixed="col.is_fixed ? 'left' : ''"
> >
<!-- <template #edit="{row}"><a-input v-model="row[col.field]"></a-input></template> -->
<template #header> <template #header>
<span class="vxe-handle"> <span class="vxe-handle">
<OpsMoveIcon <OpsMoveIcon
@@ -109,7 +110,7 @@
<span>{{ col.title }}</span> <span>{{ col.title }}</span>
</span> </span>
</template> </template>
<template v-if="col.is_choice || col.is_password" #edit="{ row }"> <template v-if="col.is_choice || col.is_password || col.is_list" #edit="{ row }">
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" /> <vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
<a-select <a-select
:getPopupContainer="(trigger) => trigger.parentElement" :getPopupContainer="(trigger) => trigger.parentElement"
@@ -146,6 +147,18 @@
</span> </span>
</a-select-option> </a-select-option>
</a-select> </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>
<template <template
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice" v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
@@ -280,6 +293,8 @@
</a-pagination> </a-pagination>
</div> </div>
<create-instance-form ref="create" @reload="reloadData" @submit="batchUpdate" /> <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" /> <JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
<BatchDownload ref="batchDownload" @batchDownload="batchDownload" /> <BatchDownload ref="batchDownload" @batchDownload="batchDownload" />
<MetadataDrawer ref="metadataDrawer" /> <MetadataDrawer ref="metadataDrawer" />
@@ -297,7 +312,7 @@ import router, { resetRouter } from '@/router'
import SearchForm from '../../components/searchForm/SearchForm.vue' import SearchForm from '../../components/searchForm/SearchForm.vue'
import CreateInstanceForm from './modules/CreateInstanceForm' import CreateInstanceForm from './modules/CreateInstanceForm'
import CiDetailDrawer from './modules/ciDetailDrawer.vue' import CiDetail from './modules/CiDetail'
import EditAttrsPopover from './modules/editAttrsPopover' import EditAttrsPopover from './modules/editAttrsPopover'
import JsonEditor from '../../components/JsonEditor/jsonEditor.vue' import JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
import { searchCI, updateCI, deleteCI } from '@/modules/cmdb/api/ci' import { searchCI, updateCI, deleteCI } from '@/modules/cmdb/api/ci'
@@ -320,7 +335,7 @@ export default {
components: { components: {
SearchForm, SearchForm,
CreateInstanceForm, CreateInstanceForm,
CiDetailDrawer, CiDetail,
JsonEditor, JsonEditor,
PasswordField, PasswordField,
EditAttrsPopover, EditAttrsPopover,
@@ -456,6 +471,11 @@ export default {
const regSort = /(?<=sort=).+/g const regSort = /(?<=sort=).+/g
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null 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 let sort
if (sortByTable) { if (sortByTable) {
sort = sortByTable sort = sortByTable
@@ -464,6 +484,7 @@ export default {
} }
const res = await searchCI({ const res = await searchCI({
q: `_type:${this.typeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`, q: `_type:${this.typeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`,
// q: `${this.mergeQ(queryParams)}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`,
count: this.pageSize, count: this.pageSize,
page: this.currentPage, page: this.currentPage,
sort, sort,
@@ -512,17 +533,55 @@ export default {
this.$refs['xTable'].getVxetableRef().setCheckboxRow(rows, true) 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() { async loadPreferenceAttrList() {
const subscribed = await getSubscribeAttributes(this.typeId) const subscribed = await getSubscribeAttributes(this.typeId)
this.preferenceAttrList = subscribed.attributes // All columns that have been subscribed this.preferenceAttrList = subscribed.attributes // All columns that have been subscribed
}, },
onSelectChange() { 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 xTable = this.$refs.xTable.getVxetableRef()
const records = [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()] const records = [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()]
this.selectedRowKeys = records.map((i) => i.ci_id || i._id) this.selectedRowKeys = records.map((i) => i.ci_id || i._id)
}, },
onSelectRangeEnd({ records }) { 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.selectedRowKeys = records.map((i) => i.ci_id || i._id)
// this.setSelectRows()
}, },
reloadData() { reloadData() {
this.loadTableData() this.loadTableData()
@@ -564,7 +623,7 @@ export default {
}) })
.catch((err) => { .catch((err) => {
console.log(err) console.log(err)
this.loadTableData() $table.revertData(row)
}) })
} }
this.columns.forEach((col) => { this.columns.forEach((col) => {
@@ -635,22 +694,12 @@ export default {
} }
}) })
this.$refs.create.visible = false this.$refs.create.visible = false
const key = 'updatable'
let errorMsg = ''
for (let i = 0; i < this.selectedRowKeys.length; i++) { for (let i = 0; i < this.selectedRowKeys.length; i++) {
await updateCI(this.selectedRowKeys[i], payload, false) await updateCI(this.selectedRowKeys[i], payload, false)
.then(() => { .then(() => {
successNum += 1 successNum += 1
}) })
.catch((error) => { .catch(() => {
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 errorNum += 1
}) })
.finally(() => { .finally(() => {

View File

@@ -1,390 +1,344 @@
<template> <template>
<div :style="{ height: '100%' }"> <CustomDrawer
<a-tabs v-if="hasPermission" class="ci-detail-tab" v-model="activeTabKey" @change="changeTab"> width="80%"
<a @click="shareCi" slot="tabBarExtraContent" :style="{ marginRight: '24px' }"> placement="left"
<a-icon type="share-alt" /> @close="
{{ $t('cmdb.ci.share') }} () => {
</a> visible = false
<a-tab-pane key="tab_1"> }
<span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span> "
<div class="ci-detail-attr"> :visible="visible"
<el-descriptions :hasTitle="false"
:title="group.name || $t('other')" :hasFooter="false"
:key="group.name" :bodyStyle="{ padding: 0, height: '100vh' }"
v-for="group in attributeGroups" wrapClassName="ci-detail"
border destroyOnClose
:column="3" >
> <a-tabs v-model="activeTabKey" @change="changeTab">
<el-descriptions-item <a-tab-pane key="tab_1">
:label="`${attr.alias || attr.name}`" <span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span>
:key="attr.name" <div :style="{ maxHeight: `${windowHeight - 44}px`, overflow: 'auto', padding: '24px' }" class="ci-detail-attr">
v-for="attr in group.attributes" <el-descriptions
> :title="group.name || $t('other')"
<CiDetailAttrContent :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" /> :key="group.name"
</el-descriptions-item> v-for="group in attributeGroups"
</el-descriptions> border
</div> :column="3"
</a-tab-pane> >
<a-tab-pane key="tab_2"> <el-descriptions-item
<span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span> :label="`${attr.alias || attr.name}`"
<div :style="{ height: '100%', padding: '24px', overflow: 'auto' }"> :key="attr.name"
<CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" /> v-for="attr in group.attributes"
</div> >
</a-tab-pane> <CiDetailAttrContent :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" />
<a-tab-pane key="tab_3"> </el-descriptions-item>
<span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span> </el-descriptions>
<div :style="{ padding: '24px', height: '100%' }"> </div>
<vxe-table </a-tab-pane>
ref="xTable" <a-tab-pane key="tab_2">
:data="ciHistory" <span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
size="small" <div :style="{ padding: '24px' }">
height="auto" <CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
:span-method="mergeRowMethod" </div>
border </a-tab-pane>
:scroll-y="{ enabled: false }" <a-tab-pane key="tab_3">
class="ops-stripe-table" <span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span>
> <div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }">
<vxe-table-column sortable field="created_at" :title="$t('created_at')"></vxe-table-column> <vxe-table
<vxe-table-column ref="xTable"
field="username" :data="ciHistory"
:title="$t('user')" size="small"
:filters="[]" :max-height="`${windowHeight - 94}px`"
:filter-method="filterUsernameMethod" :span-method="mergeRowMethod"
></vxe-table-column> border
<vxe-table-column :scroll-y="{ enabled: false }"
field="operate_type" class="ops-stripe-table"
:filters="[ >
{ value: 0, label: $t('new') }, <vxe-table-column sortable field="created_at" :title="$t('created_at')"></vxe-table-column>
{ value: 1, label: $t('delete') }, <vxe-table-column
{ value: 3, label: $t('update') }, field="username"
]" :title="$t('user')"
:filter-method="filterOperateMethod" :filters="[]"
:title="$t('operation')" :filter-method="filterUsernameMethod"
> ></vxe-table-column>
<template #default="{ row }"> <vxe-table-column
{{ operateTypeMap[row.operate_type] }} field="operate_type"
</template> :filters="[
</vxe-table-column> { value: 0, label: $t('new') },
<vxe-table-column { value: 1, label: $t('delete') },
field="attr_alias" { value: 3, label: $t('update') },
:title="$t('cmdb.attribute')" ]"
:filters="[]" :filter-method="filterOperateMethod"
:filter-method="filterAttrMethod" :title="$t('operation')"
></vxe-table-column> >
<vxe-table-column field="old" :title="$t('cmdb.history.old')"></vxe-table-column> <template #default="{ row }">
<vxe-table-column field="new" :title="$t('cmdb.history.new')"></vxe-table-column> {{ operateTypeMap[row.operate_type] }}
</vxe-table> </template>
</div> </vxe-table-column>
</a-tab-pane> <vxe-table-column
<a-tab-pane key="tab_4"> field="attr_alias"
<span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span> :title="$t('cmdb.attribute')"
<div :style="{ padding: '24px', height: '100%' }"> :filters="[]"
<TriggerTable :ci_id="ci._id" /> :filter-method="filterAttrMethod"
</div> ></vxe-table-column>
</a-tab-pane> <vxe-table-column field="old" :title="$t('cmdb.history.old')"></vxe-table-column>
</a-tabs> <vxe-table-column field="new" :title="$t('cmdb.history.new')"></vxe-table-column>
<a-empty </vxe-table>
v-else </div>
:image-style="{ </a-tab-pane>
height: '100px', <a-tab-pane key="tab_4">
}" <span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span>
:style="{ paddingTop: '20%' }" <div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }">
> <TriggerTable :ci_id="ci._id" />
<img slot="image" :src="require('@/assets/data_empty.png')" /> </div>
<span slot="description"> {{ $t('cmdb.ci.noPermission') }} </span> </a-tab-pane>
</a-empty> </a-tabs>
</div> </CustomDrawer>
</template> </template>
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import { Descriptions, DescriptionsItem } from 'element-ui' import { Descriptions, DescriptionsItem } from 'element-ui'
import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType' import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
import { getCIHistory } from '@/modules/cmdb/api/history' import { getCIHistory } from '@/modules/cmdb/api/history'
import { getCIById } from '@/modules/cmdb/api/ci' import { getCIById } from '@/modules/cmdb/api/ci'
import CiDetailAttrContent from './ciDetailAttrContent.vue' import CiDetailAttrContent from './ciDetailAttrContent.vue'
import CiDetailRelation from './ciDetailRelation.vue' import CiDetailRelation from './ciDetailRelation.vue'
import TriggerTable from '../../operation_history/modules/triggerTable.vue' import TriggerTable from '../../operation_history/modules/triggerTable.vue'
export default {
name: 'CiDetailTab', export default {
components: { components: {
ElDescriptions: Descriptions, ElDescriptions: Descriptions,
ElDescriptionsItem: DescriptionsItem, ElDescriptionsItem: DescriptionsItem,
CiDetailAttrContent, CiDetailAttrContent,
CiDetailRelation, CiDetailRelation,
TriggerTable, TriggerTable,
}, },
props: { props: {
typeId: { typeId: {
type: Number, type: Number,
required: true, required: true,
}, },
treeViewsLevels: { treeViewsLevels: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
}, },
data() { data() {
return { return {
ci: {}, visible: false,
attributeGroups: [], ci: {},
activeTabKey: 'tab_1', attributeGroups: [],
rowSpanMap: {}, activeTabKey: 'tab_1',
ciHistory: [], rowSpanMap: {},
ciId: null, ciHistory: [],
ci_types: [], ciId: null,
hasPermission: true, ci_types: [],
} }
}, },
computed: { computed: {
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
operateTypeMap() {
return { operateTypeMap() {
0: this.$t('new'), return {
1: this.$t('delete'), 0: this.$t('new'),
2: this.$t('update'), 1: this.$t('delete'),
} 2: this.$t('update'),
}, }
}, },
provide() { },
return { provide() {
ci_types: () => { return {
return this.ci_types ci_types: () => {
}, return this.ci_types
} },
}, }
inject: { },
reload: { inject: ['reload', 'handleSearch', 'attrList'],
from: 'reload', methods: {
default: null, create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
}, this.visible = true
handleSearch: { this.activeTabKey = activeTabKey
from: 'handleSearch', if (activeTabKey === 'tab_2') {
default: null, this.$nextTick(() => {
}, this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey
attrList: { })
from: 'attrList', }
default: () => [], this.ciId = ciId
}, this.getAttributes()
}, this.getCI()
methods: { this.getCIHistory()
async create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') { getCITypes().then((res) => {
this.activeTabKey = activeTabKey this.ci_types = res.ci_types
if (activeTabKey === 'tab_2') { })
this.$nextTick(() => { },
this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey getAttributes() {
}) getCITypeGroupById(this.typeId, { need_other: 1 })
} .then((res) => {
this.ciId = ciId this.attributeGroups = res
await this.getCI() })
if (this.hasPermission) { .catch((e) => {})
this.getAttributes() },
this.getCIHistory() getCI() {
getCITypes().then((res) => { getCIById(this.ciId)
this.ci_types = res.ci_types .then((res) => {
}) // this.ci = res.ci
} this.ci = res.result[0]
}, })
getAttributes() { .catch((e) => {})
getCITypeGroupById(this.typeId, { need_other: 1 }) },
.then((res) => {
this.attributeGroups = res getCIHistory() {
}) getCIHistory(this.ciId)
.catch((e) => {}) .then((res) => {
}, this.ciHistory = res
async getCI() {
await getCIById(this.ciId) const rowSpanMap = {}
.then((res) => { let startIndex = 0
if (res.result.length) { let startCount = 1
this.ci = res.result[0] res.forEach((item, index) => {
} else { if (index === 0) {
this.hasPermission = false return
} }
}) if (res[index].record_id === res[startIndex].record_id) {
.catch((e) => {}) startCount += 1
}, rowSpanMap[index] = 0
if (index === res.length - 1) {
getCIHistory() { rowSpanMap[startIndex] = startCount
getCIHistory(this.ciId) }
.then((res) => { } else {
this.ciHistory = res rowSpanMap[startIndex] = startCount
startIndex = index
const rowSpanMap = {} startCount = 1
let startIndex = 0 if (index === res.length - 1) {
let startCount = 1 rowSpanMap[index] = 1
res.forEach((item, index) => { }
if (index === 0) { }
return })
} this.rowSpanMap = rowSpanMap
if (res[index].record_id === res[startIndex].record_id) { })
startCount += 1 .catch((e) => {
rowSpanMap[index] = 0 console.log(e)
if (index === res.length - 1) { })
rowSpanMap[startIndex] = startCount },
} changeTab(key) {
} else { this.activeTabKey = key
rowSpanMap[startIndex] = startCount if (key === 'tab_3') {
startIndex = index this.$nextTick(() => {
startCount = 1 const $table = this.$refs.xTable
if (index === res.length - 1) { if ($table) {
rowSpanMap[index] = 1 const usernameColumn = $table.getColumnByField('username')
} const attrColumn = $table.getColumnByField('attr_alias')
} if (usernameColumn) {
}) const usernameList = [...new Set(this.ciHistory.map((item) => item.username))]
this.rowSpanMap = rowSpanMap $table.setFilter(
}) usernameColumn,
.catch((e) => { usernameList.map((item) => {
console.log(e) return {
}) value: item,
}, label: item,
changeTab(key) { }
this.activeTabKey = key })
if (key === 'tab_3') { )
this.$nextTick(() => { }
const $table = this.$refs.xTable if (attrColumn) {
if ($table) { $table.setFilter(
const usernameColumn = $table.getColumnByField('username') attrColumn,
const attrColumn = $table.getColumnByField('attr_alias') this.attrList().map((attr) => {
if (usernameColumn) { return { value: attr.alias || attr.name, label: attr.alias || attr.name }
const usernameList = [...new Set(this.ciHistory.map((item) => item.username))] })
$table.setFilter( )
usernameColumn, }
usernameList.map((item) => { }
return { })
value: item, }
label: item, },
} filterUsernameMethod({ value, row, column }) {
}) return row.username === value
) },
} filterOperateMethod({ value, row, column }) {
if (attrColumn) { return Number(row.operate_type) === Number(value)
$table.setFilter( },
attrColumn, filterAttrMethod({ value, row, column }) {
this.attrList().map((attr) => { return row.attr_alias === value
return { value: attr.alias || attr.name, label: attr.alias || attr.name } },
}) refresh(editAttrName) {
) this.getCI()
} const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
} // 修改的字段为树形视图订阅的字段 则全部reload
}) setTimeout(() => {
} if (_find) {
}, this.reload()
filterUsernameMethod({ value, row, column }) { } else {
return row.username === value this.handleSearch()
}, }
filterOperateMethod({ value, row, column }) { }, 500)
return Number(row.operate_type) === Number(value) },
}, mergeRowMethod({ row, _rowIndex, column, visibleData }) {
filterAttrMethod({ value, row, column }) { const fields = ['created_at', 'username']
return row.attr_alias === value const cellValue1 = row['created_at']
}, const cellValue2 = row['username']
refresh(editAttrName) { if (cellValue1 && cellValue2 && fields.includes(column.property)) {
this.getCI() const prevRow = visibleData[_rowIndex - 1]
const _find = this.treeViewsLevels.find((level) => level.name === editAttrName) let nextRow = visibleData[_rowIndex + 1]
// 修改的字段为树形视图订阅的字段 则全部reload if (prevRow && prevRow['created_at'] === cellValue1 && prevRow['username'] === cellValue2) {
setTimeout(() => { return { rowspan: 0, colspan: 0 }
if (_find) { } else {
if (this.reload) { let countRowspan = 1
this.reload() while (nextRow && nextRow['created_at'] === cellValue1 && nextRow['username'] === cellValue2) {
} nextRow = visibleData[++countRowspan + _rowIndex]
} else { }
if (this.handleSearch) { if (countRowspan > 1) {
this.handleSearch() return { rowspan: countRowspan, colspan: 1 }
} }
} }
}, 500) }
}, },
mergeRowMethod({ row, _rowIndex, column, visibleData }) { updateCIByself(params, editAttrName) {
const fields = ['created_at', 'username'] const _ci = { ..._.cloneDeep(this.ci), ...params }
const cellValue1 = row['created_at'] this.ci = _ci
const cellValue2 = row['username'] const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
if (cellValue1 && cellValue2 && fields.includes(column.property)) { // 修改的字段为树形视图订阅的字段 则全部reload
const prevRow = visibleData[_rowIndex - 1] setTimeout(() => {
let nextRow = visibleData[_rowIndex + 1] if (_find) {
if (prevRow && prevRow['created_at'] === cellValue1 && prevRow['username'] === cellValue2) { this.reload()
return { rowspan: 0, colspan: 0 } } else {
} else { this.handleSearch()
let countRowspan = 1 }
while (nextRow && nextRow['created_at'] === cellValue1 && nextRow['username'] === cellValue2) { }, 500)
nextRow = visibleData[++countRowspan + _rowIndex] },
} },
if (countRowspan > 1) { }
return { rowspan: countRowspan, colspan: 1 } </script>
}
} <style lang="less" scoped></style>
} <style lang="less">
}, .ci-detail {
updateCIByself(params, editAttrName) { .ant-tabs-bar {
const _ci = { ..._.cloneDeep(this.ci), ...params } margin: 0;
this.ci = _ci }
const _find = this.treeViewsLevels.find((level) => level.name === editAttrName) .ci-detail-attr {
// 修改的字段为树形视图订阅的字段 则全部reload .el-descriptions-item__content {
setTimeout(() => { cursor: default;
if (_find) { &:hover a {
if (this.reload) { opacity: 1 !important;
this.reload() }
} }
} else { .el-descriptions:first-child > .el-descriptions__header {
if (this.handleSearch) { margin-top: 0;
this.handleSearch() }
} .el-descriptions__header {
} margin-bottom: 5px;
}, 500) margin-top: 20px;
}, }
shareCi() { .ant-form-item {
const text = `${document.location.host}/cmdb/cidetail/${this.typeId}/${this.ciId}` margin-bottom: 0;
this.$copyText(text) }
.then(() => { .ant-form-item-control {
this.$message.success(this.$t('copySuccess')) line-height: 19px;
}) }
.catch(() => { }
this.$message.error(this.$t('cmdb.ci.copyFailed')) }
}) </style>
},
},
}
</script>
<style lang="less">
.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 {
opacity: 1 !important;
}
}
.el-descriptions:first-child > .el-descriptions__header {
margin-top: 0;
}
.el-descriptions__header {
margin-bottom: 5px;
margin-top: 20px;
}
.ant-form-item {
margin-bottom: 0;
}
.ant-form-item-control {
line-height: 19px;
}
}
}
</style>

View File

@@ -1,430 +1,388 @@
<template> <template>
<CustomDrawer <CustomDrawer
:title="title + CIType.alias" :title="title + CIType.alias"
width="800" width="800"
@close="handleClose" @close="handleClose"
:maskClosable="false" :maskClosable="false"
:visible="visible" :visible="visible"
wrapClassName="create-instance-form" wrapClassName="create-instance-form"
:bodyStyle="{ paddingTop: 0 }" :bodyStyle="{ paddingTop: 0 }"
:headerStyle="{ borderBottom: 'none' }" :headerStyle="{ borderBottom: 'none' }"
> >
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="handleClose">{{ $t('cancel') }}</a-button> <a-button @click="handleClose">{{ $t('cancel') }}</a-button>
<a-button type="primary" @click="createInstance">{{ $t('submit') }}</a-button> <a-button type="primary" @click="createInstance">{{ $t('submit') }}</a-button>
</div> </div>
<template v-if="action === 'create'"> <template v-if="action === 'create'">
<template v-for="group in attributesByGroup"> <template v-for="group in attributesByGroup">
<CreateInstanceFormByGroup <CreateInstanceFormByGroup
:ref="`createInstanceFormByGroup_${group.id}`" :ref="`createInstanceFormByGroup_${group.id}`"
:key="group.id || group.name" :key="group.id || group.name"
:group="group" :group="group"
@handleFocusInput="handleFocusInput" @handleFocusInput="handleFocusInput"
:attributeList="attributeList" :attributeList="attributeList"
/> />
</template> </template>
<template v-if="parentsType && parentsType.length"> <template v-if="parentsType && parentsType.length">
<a-divider style="font-size:14px;margin:14px 0;font-weight:700;">{{ <a-divider style="font-size:14px;margin:14px 0;font-weight:700;">{{ $t('cmdb.menu.citypeRelation') }}</a-divider>
$t('cmdb.menu.citypeRelation') <a-form>
}}</a-divider> <a-row :gutter="24" align="top" type="flex">
<a-form> <a-col :span="12" v-for="item in parentsType" :key="item.id">
<a-row :gutter="24" align="top" type="flex"> <a-form-item :label="item.alias || item.name" :colon="false">
<a-col :span="12" v-for="item in parentsType" :key="item.id"> <a-input-group compact style="width: 100%">
<a-form-item :label="item.alias || item.name" :colon="false"> <a-select v-model="parentsForm[item.name].attr">
<a-input-group compact style="width: 100%"> <a-select-option
<a-select v-model="parentsForm[item.name].attr"> :title="attr.alias || attr.name"
<a-select-option v-for="attr in item.attributes"
:title="attr.alias || attr.name" :key="attr.name"
v-for="attr in item.attributes" :value="attr.name"
:key="attr.name" >
:value="attr.name" {{ attr.alias || attr.name }}
> </a-select-option>
{{ attr.alias || attr.name }} </a-select>
</a-select-option> <a-input :placeholder="$t('cmdb.ci.tips1')" v-model="parentsForm[item.name].value" style="width: 50%" />
</a-select> </a-input-group>
<a-input </a-form-item>
:placeholder="$t('cmdb.ci.tips1')" </a-col>
v-model="parentsForm[item.name].value" </a-row>
style="width: 50%" </a-form>
/> </template>
</a-input-group> </template>
</a-form-item> <template v-if="action === 'update'">
</a-col> <a-form :form="form">
</a-row> <p>{{ $t('cmdb.ci.tips2') }}</p>
</a-form> <a-row :gutter="24" v-for="list in batchUpdateLists" :key="list.name">
</template> <a-col :span="11">
</template> <a-form-item>
<template v-if="action === 'update'"> <el-select showSearch size="small" filterable v-model="list.name" :placeholder="$t('cmdb.ci.tips3')">
<a-form :form="form"> <el-option
<p>{{ $t('cmdb.ci.tips2') }}</p> v-for="attr in attributeList"
<a-row :gutter="24" v-for="list in batchUpdateLists" :key="list.name"> :key="attr.name"
<a-col :span="11"> :value="attr.name"
<a-form-item> :disabled="batchUpdateLists.findIndex((item) => item.name === attr.name) > -1"
<el-select showSearch size="small" filterable v-model="list.name" :placeholder="$t('cmdb.ci.tips3')"> :label="attr.alias || attr.name"
<el-option >
v-for="attr in attributeList" </el-option>
:key="attr.name" </el-select>
:value="attr.name" </a-form-item>
:disabled="batchUpdateLists.findIndex((item) => item.name === attr.name) > -1" </a-col>
:label="attr.alias || attr.name" <a-col :span="11">
> <a-form-item>
</el-option> <a-select
</el-select> :style="{ width: '100%' }"
</a-form-item> v-decorator="[list.name, { rules: [{ required: false }] }]"
</a-col> :placeholder="$t('placeholder2')"
<a-col :span="11"> v-if="getFieldType(list.name).split('%%')[0] === 'select'"
<a-form-item> :mode="getFieldType(list.name).split('%%')[1] === 'multiple' ? 'multiple' : 'default'"
<a-select showSearch
:style="{ width: '100%' }" allowClear
v-decorator="[list.name, { rules: [{ required: false }] }]" >
:placeholder="$t('placeholder2')" <a-select-option
v-if="getFieldType(list.name).split('%%')[0] === 'select'" :value="choice[0]"
:mode="getFieldType(list.name).split('%%')[1] === 'multiple' ? 'multiple' : 'default'" :key="'New_' + choice + choice_idx"
showSearch v-for="(choice, choice_idx) in getSelectFieldOptions(list.name)"
allowClear >
> <span :style="choice[1] ? choice[1].style || {} : {}">
<a-select-option <ops-icon
:value="choice[0]" :style="{ color: choice[1].icon.color }"
:key="'New_' + choice + choice_idx" v-if="choice[1] && choice[1].icon && choice[1].icon.name"
v-for="(choice, choice_idx) in getSelectFieldOptions(list.name)" :type="choice[1].icon.name"
> />
<span :style="choice[1] ? choice[1].style || {} : {}"> {{ choice[0] }}
<ops-icon </span>
:style="{ color: choice[1].icon.color }" </a-select-option>
v-if="choice[1] && choice[1].icon && choice[1].icon.name" </a-select>
:type="choice[1].icon.name" <a-input-number
/> v-decorator="[list.name, { rules: [{ required: false }] }]"
{{ choice[0] }} style="width: 100%"
</span> v-if="getFieldType(list.name) === 'input_number'"
</a-select-option> />
</a-select> <a-date-picker
<a-input-number v-decorator="[list.name, { rules: [{ required: false }] }]"
v-decorator="[list.name, { rules: [{ required: false }] }]" style="width: 100%"
style="width: 100%" :format="getFieldType(list.name) == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
v-if="getFieldType(list.name) === 'input_number'" v-if="getFieldType(list.name) === 'date' || getFieldType(list.name) === 'datetime'"
/> :showTime="getFieldType(list.name) === 'date' ? false : { format: 'HH:mm:ss' }"
<a-date-picker />
v-decorator="[list.name, { rules: [{ required: false }] }]" <a-input
style="width: 100%" v-if="getFieldType(list.name) === 'input'"
:format="getFieldType(list.name) == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'" @focus="(e) => handleFocusInput(e, list)"
v-if="getFieldType(list.name) === 'date' || getFieldType(list.name) === 'datetime'" v-decorator="[list.name, { rules: [{ required: false }] }]"
:showTime="getFieldType(list.name) === 'date' ? false : { format: 'HH:mm:ss' }" />
/> </a-form-item>
<a-input </a-col>
v-if="getFieldType(list.name) === 'input'" <a-col :span="2">
@focus="(e) => handleFocusInput(e, list)" <a-form-item>
v-decorator="[list.name, { rules: [{ required: false }] }]" <a :style="{ color: 'red', marginTop: '2px' }" @click="handleDelete(list.name)">
/> <a-icon type="delete" />
</a-form-item> </a>
</a-col> </a-form-item>
<a-col :span="2"> </a-col>
<a-form-item> </a-row>
<a :style="{ color: 'red', marginTop: '2px' }" @click="handleDelete(list.name)"> <a-button type="primary" ghost icon="plus" @click="handleAdd">{{ $t('cmdb.ci.newUpdateField') }}</a-button>
<a-icon type="delete" /> </a-form>
</a> </template>
</a-form-item> <JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
</a-col> </CustomDrawer>
</a-row> </template>
<a-button type="primary" ghost icon="plus" @click="handleAdd">{{ $t('cmdb.ci.newUpdateField') }}</a-button>
</a-form> <script>
</template> import _ from 'lodash'
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" /> import moment from 'moment'
</CustomDrawer> import { Select, Option } from 'element-ui'
</template> import { getCIType, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
import { addCI } from '@/modules/cmdb/api/ci'
<script> import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
import _ from 'lodash' import { valueTypeMap } from '../../../utils/const'
import moment from 'moment' import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue'
import { Select, Option } from 'element-ui' import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
import { getCIType, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
import { addCI } from '@/modules/cmdb/api/ci' export default {
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue' name: 'CreateInstanceForm',
import { valueTypeMap } from '../../../utils/const' components: {
import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue' ElSelect: Select,
import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation' ElOption: Option,
JsonEditor,
export default { CreateInstanceFormByGroup,
name: 'CreateInstanceForm', },
components: { props: {
ElSelect: Select, typeIdFromRelation: {
ElOption: Option, type: Number,
JsonEditor, default: 0,
CreateInstanceFormByGroup, },
}, },
props: { data() {
typeIdFromRelation: { return {
type: Number, action: '',
default: 0, form: this.$form.createForm(this),
}, visible: false,
}, attributeList: [],
data() {
return { CIType: {},
action: '',
form: this.$form.createForm(this), batchUpdateLists: [],
visible: false, editAttr: null,
attributeList: [], attributesByGroup: [],
parentsType: [],
CIType: {}, parentsForm: {},
canEdit: {},
batchUpdateLists: [], }
editAttr: null, },
attributesByGroup: [], computed: {
parentsType: [], title() {
parentsForm: {}, return this.action === 'create' ? this.$t('create') + ' ' : this.$t('cmdb.ci.batchUpdate') + ' '
canEdit: {}, },
} typeId() {
}, if (this.typeIdFromRelation) {
computed: { return this.typeIdFromRelation
title() { }
return this.action === 'create' ? this.$t('create') + ' ' : this.$t('cmdb.ci.batchUpdate') + ' ' return this.$router.currentRoute.meta.typeId
}, },
typeId() { valueTypeMap() {
if (this.typeIdFromRelation) { return valueTypeMap()
return this.typeIdFromRelation },
} },
return this.$router.currentRoute.meta.typeId provide() {
}, return {
valueTypeMap() { getFieldType: this.getFieldType,
return valueTypeMap() }
}, },
}, inject: ['attrList'],
provide() { methods: {
return { moment,
getFieldType: this.getFieldType, async getCIType() {
} await getCIType(this.typeId).then((res) => {
}, this.CIType = res.ci_types[0]
inject: ['attrList'], })
methods: { },
moment, async getAttributeList() {
async getCIType() { const _attrList = this.attrList()
await getCIType(this.typeId).then((res) => { this.attributeList = _attrList.sort((x, y) => y.is_required - x.is_required)
this.CIType = res.ci_types[0] await getCITypeGroupById(this.typeId).then((res1) => {
}) const _attributesByGroup = res1.map((g) => {
}, g.attributes = g.attributes.filter((attr) => !attr.is_computed)
async getAttributeList() { return g
const _attrList = this.attrList() })
this.attributeList = _attrList.sort((x, y) => y.is_required - x.is_required) const attrHasGroupIds = []
await getCITypeGroupById(this.typeId).then((res1) => { res1.forEach((g) => {
const _attributesByGroup = res1.map((g) => { const id = g.attributes.map((attr) => attr.id)
g.attributes = g.attributes.filter((attr) => !attr.is_computed) attrHasGroupIds.push(...id)
return g })
}) const otherGroupAttr = this.attributeList.filter(
const attrHasGroupIds = [] (attr) => !attrHasGroupIds.includes(attr.id) && !attr.is_computed
res1.forEach((g) => { )
const id = g.attributes.map((attr) => attr.id) if (otherGroupAttr.length) {
attrHasGroupIds.push(...id) _attributesByGroup.push({ id: -1, name: this.$t('other'), attributes: otherGroupAttr })
}) }
const otherGroupAttr = this.attributeList.filter( this.attributesByGroup = _attributesByGroup
(attr) => !attrHasGroupIds.includes(attr.id) && !attr.is_computed })
) },
if (otherGroupAttr.length) { createInstance() {
_attributesByGroup.push({ id: -1, name: this.$t('other'), attributes: otherGroupAttr }) const _this = this
} if (_this.action === 'update') {
console.log(otherGroupAttr, _attributesByGroup) this.form.validateFields((err, values) => {
this.attributesByGroup = _attributesByGroup if (err) {
}) return
}, }
createInstance() { Object.keys(values).forEach((k) => {
const _this = this const _tempFind = this.attributeList.find((item) => item.name === k)
if (_this.action === 'update') { if (
this.form.validateFields((err, values) => { _tempFind.value_type === '3' &&
if (err) { values[k] &&
return Object.prototype.toString.call(values[k]) === '[object Object]'
} ) {
Object.keys(values).forEach((k) => { values[k] = values[k].format('YYYY-MM-DD HH:mm:ss')
const _tempFind = this.attributeList.find((item) => item.name === k) }
if ( if (
_tempFind.value_type === '3' && _tempFind.value_type === '4' &&
values[k] && values[k] &&
Object.prototype.toString.call(values[k]) === '[object Object]' Object.prototype.toString.call(values[k]) === '[object Object]'
) { ) {
values[k] = values[k].format('YYYY-MM-DD HH:mm:ss') values[k] = values[k].format('YYYY-MM-DD')
} }
if ( if (_tempFind.value_type === '6') {
_tempFind.value_type === '4' && values[k] = values[k] ? JSON.parse(values[k]) : undefined
values[k] && }
Object.prototype.toString.call(values[k]) === '[object Object]' })
) {
values[k] = values[k].format('YYYY-MM-DD') _this.$emit('submit', values)
} })
if (_tempFind.value_type === '6') { } else {
values[k] = values[k] ? JSON.parse(values[k]) : undefined let values = {}
} for (let i = 0; i < this.attributesByGroup.length; i++) {
}) const data = this.$refs[`createInstanceFormByGroup_${this.attributesByGroup[i].id}`][0].getData()
if (data === 'error') {
_this.$emit('submit', values) return
}) }
} else { values = { ...values, ...data }
let values = {} }
for (let i = 0; i < this.attributesByGroup.length; i++) {
const data = this.$refs[`createInstanceFormByGroup_${this.attributesByGroup[i].id}`][0].getData() Object.keys(values).forEach((k) => {
if (data === 'error') { const _tempFind = this.attributeList.find((item) => item.name === k)
return if (
} _tempFind.value_type === '3' &&
values = { ...values, ...data } values[k] &&
} Object.prototype.toString.call(values[k]) === '[object Object]'
) {
Object.keys(values).forEach((k) => { values[k] = values[k].format('YYYY-MM-DD HH:mm:ss')
const _tempFind = this.attributeList.find((item) => item.name === k) }
if ( if (
_tempFind.value_type === '3' && _tempFind.value_type === '4' &&
values[k] && values[k] &&
Object.prototype.toString.call(values[k]) === '[object Object]' Object.prototype.toString.call(values[k]) === '[object Object]'
) { ) {
values[k] = values[k].format('YYYY-MM-DD HH:mm:ss') values[k] = values[k].format('YYYY-MM-DD')
} }
if ( if (_tempFind.value_type === '6') {
_tempFind.value_type === '4' && values[k] = values[k] ? JSON.parse(values[k]) : undefined
values[k] && }
Object.prototype.toString.call(values[k]) === '[object Object]' })
) { values.ci_type = _this.typeId
values[k] = values[k].format('YYYY-MM-DD') console.log(this.parentsForm)
} Object.keys(this.parentsForm).forEach((type) => {
if (_tempFind.value_type === '6') { if (this.parentsForm[type].value) {
values[k] = values[k] ? JSON.parse(values[k]) : undefined values[`$${type}.${this.parentsForm[type].attr}`] = this.parentsForm[type].value
} }
}) })
values.ci_type = _this.typeId addCI(values).then((res) => {
console.log(this.parentsForm) _this.$message.success(this.$t('addSuccess'))
Object.keys(this.parentsForm).forEach((type) => { _this.visible = false
if (this.parentsForm[type].value) { _this.$emit('reload', { ci_id: res.ci_id })
values[`$${type}.${this.parentsForm[type].attr}`] = this.parentsForm[type].value })
} }
}) },
addCI(values).then((res) => { handleClose() {
_this.$message.success(this.$t('addSuccess')) this.visible = false
_this.visible = false },
_this.$emit('reload', { ci_id: res.ci_id }) handleOpen(visible, action) {
}) this.visible = visible
} this.action = action
this.$nextTick(() => {
// this.form.validateFields((err, values) => { this.form.resetFields()
// if (err) { Promise.all([this.getCIType(), this.getAttributeList()]).then(() => {
// _this.$message.error('字段填写不符合要求!') this.batchUpdateLists = [{ name: this.attributeList[0].name }]
// return })
// } if (action === 'create') {
// Object.keys(values).forEach((k) => { getCITypeParent(this.typeId).then(async (res) => {
// if (Object.prototype.toString.call(values[k]) === '[object Object]' && values[k]) { for (let i = 0; i < res.parents.length; i++) {
// values[k] = values[k].format('YYYY-MM-DD HH:mm:ss') await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => {
// } this.canEdit = {
// const _tempFind = this.attributeList.find((item) => item.name === k) ..._.cloneDeep(this.canEdit),
// if (_tempFind.value_type === '6') { [res.parents[i].id]: p_res.result,
// values[k] = values[k] ? JSON.parse(values[k]) : undefined }
// } })
// }) }
this.parentsType = res.parents.filter((parent) => this.canEdit[parent.id])
// if (_this.action === 'update') { const _parentsForm = {}
// _this.$emit('submit', values) res.parents.forEach((item) => {
// return const _find = item.attributes.find((attr) => attr.id === item.unique_id)
// } _parentsForm[item.name] = { attr: _find.name, value: '' }
// values.ci_type = _this.typeId })
// console.log(values) this.parentsForm = _parentsForm
// this.attributesByGroup.forEach((group) => { })
// this.$refs[`createInstanceFormByGroup_${group.id}`][0].getData() }
// }) })
// console.log(1111) },
// // addCI(values).then((res) => { getFieldType(name) {
// // _this.$message.success('新增成功!') const _find = this.attributeList.find((item) => item.name === name)
// // _this.visible = false if (_find) {
// // _this.$emit('reload') if (_find.is_choice) {
// // }) if (_find.is_list) {
// }) return 'select%%multiple'
}, }
handleClose() { return 'select'
this.visible = false } else if (_find.value_type === '0' || _find.value_type === '1') {
}, return 'input_number'
handleOpen(visible, action) { } else if (_find.value_type === '4' || _find.value_type === '3') {
this.visible = visible return this.valueTypeMap[_find.value_type]
this.action = action } else {
this.$nextTick(() => { return 'input'
this.form.resetFields() }
Promise.all([this.getCIType(), this.getAttributeList()]).then(() => { }
this.batchUpdateLists = [{ name: this.attributeList[0].name }] return 'input'
}) },
if (action === 'create') { getSelectFieldOptions(name) {
getCITypeParent(this.typeId).then(async (res) => { const _find = this.attributeList.find((item) => item.name === name)
for (let i = 0; i < res.parents.length; i++) { if (_find) {
await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => { return _find.choice_value
this.canEdit = { }
..._.cloneDeep(this.canEdit), return []
[res.parents[i].id]: p_res.result, },
} handleAdd() {
}) this.batchUpdateLists.push({ name: undefined })
} },
this.parentsType = res.parents.filter((parent) => this.canEdit[parent.id]) handleDelete(name) {
const _parentsForm = {} const _idx = this.batchUpdateLists.findIndex((item) => item.name === name)
res.parents.forEach((item) => { if (_idx > -1) {
const _find = item.attributes.find((attr) => attr.id === item.unique_id) this.batchUpdateLists.splice(_idx, 1)
_parentsForm[item.name] = { attr: _find.name, value: '' } }
}) },
this.parentsForm = _parentsForm handleFocusInput(e, attr) {
}) console.log(attr)
} const _tempFind = this.attributeList.find((item) => item.name === attr.name)
}) if (_tempFind.value_type === '6') {
}, this.editAttr = attr
getFieldType(name) { e.srcElement.blur()
const _find = this.attributeList.find((item) => item.name === name) const jsonData = this.form.getFieldValue(attr.name)
if (_find) { this.$refs.jsonEditor.open(null, null, jsonData ? JSON.parse(jsonData) : {})
if (_find.is_choice) { } else {
if (_find.is_list) { this.editAttr = null
return 'select%%multiple' }
} },
return 'select' jsonEditorOk(jsonData) {
} else if (_find.value_type === '0' || _find.value_type === '1') { this.form.setFieldsValue({ [this.editAttr.name]: JSON.stringify(jsonData) })
return 'input_number' },
} else if (_find.value_type === '4' || _find.value_type === '3') { },
return this.valueTypeMap[_find.value_type] }
} else { </script>
return 'input' <style lang="less">
} .create-instance-form {
} .ant-form-item {
return 'input' margin-bottom: 5px;
}, }
getSelectFieldOptions(name) { .ant-drawer-body {
const _find = this.attributeList.find((item) => item.name === name) overflow-y: auto;
if (_find) { max-height: calc(100vh - 110px);
return _find.choice_value }
} }
return [] </style>
},
handleAdd() {
this.batchUpdateLists.push({ name: undefined })
},
handleDelete(name) {
const _idx = this.batchUpdateLists.findIndex((item) => item.name === name)
if (_idx > -1) {
this.batchUpdateLists.splice(_idx, 1)
}
},
// filterOption(input, option) {
// return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
// },
handleFocusInput(e, attr) {
console.log(attr)
const _tempFind = this.attributeList.find((item) => item.name === attr.name)
if (_tempFind.value_type === '6') {
this.editAttr = attr
e.srcElement.blur()
const jsonData = this.form.getFieldValue(attr.name)
this.$refs.jsonEditor.open(null, null, jsonData ? JSON.parse(jsonData) : {})
} else {
this.editAttr = null
}
},
jsonEditorOk(jsonData) {
this.form.setFieldsValue({ [this.editAttr.name]: JSON.stringify(jsonData) })
},
},
}
</script>
<style lang="less">
.create-instance-form {
.ant-form-item {
margin-bottom: 5px;
}
.ant-drawer-body {
overflow-y: auto;
max-height: calc(100vh - 110px);
}
}
</style>

View File

@@ -60,7 +60,7 @@
</span> </span>
</template> </template>
<template v-else-if="attr.is_list"> <template v-else-if="attr.is_list">
<span> {{ ci[attr.name] && Array.isArray(ci[attr.name]) ? ci[attr.name].join(',') : ci[attr.name] }}</span> <span> {{ ci[attr.name].join(',') }}</span>
</template> </template>
<template v-else>{{ getName(ci[attr.name]) }}</template> <template v-else>{{ getName(ci[attr.name]) }}</template>
</span> </span>
@@ -105,6 +105,23 @@
</span> </span>
</a-select-option> </a-select-option>
</a-select> </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 <a-input-number
size="small" size="small"
v-decorator="[ v-decorator="[
@@ -114,7 +131,7 @@
}, },
]" ]"
style="width: 100%" style="width: 100%"
v-else-if="(attr.value_type === '0' || attr.value_type === '1') && !attr.is_list" v-else-if="attr.value_type === '0' || attr.value_type === '1'"
/> />
<a-date-picker <a-date-picker
size="small" size="small"
@@ -127,9 +144,22 @@
style="width: 100%" style="width: 100%"
:format="attr.value_type === '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'" :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'" :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') && !attr.is_list" v-else-if="attr.value_type === '4' || attr.value_type === '3'"
:showTime="attr.value_type === '4' ? false : { format: 'HH:mm:ss' }" :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 <a-input
size="small" size="small"
v-decorator="[ v-decorator="[
@@ -211,9 +241,7 @@ export default {
this.$nextTick(async () => { this.$nextTick(async () => {
if (this.attr.is_list && !this.attr.is_choice) { if (this.attr.is_list && !this.attr.is_choice) {
this.form.setFieldsValue({ this.form.setFieldsValue({
[`${this.attr.name}`]: Array.isArray(this.ci[this.attr.name]) [`${this.attr.name}`]: this.ci[this.attr.name] || null,
? this.ci[this.attr.name].join(',')
: this.ci[this.attr.name],
}) })
return return
} }

View File

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

View File

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

View File

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

View File

@@ -22,14 +22,7 @@
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }], rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
initialValue: initialValue: attr.default && attr.default.default ? attr.default.default : attr.is_list ? [] : null,
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')" :placeholder="$t('placeholder2')"
@@ -60,18 +53,19 @@
</span> </span>
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-input <a-select
v-else-if="attr.is_list" v-else-if="attr.is_list"
mode="tags"
:style="{ width: '100%' }" :style="{ width: '100%' }"
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }], rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
initialValue: attr.default && attr.default.default ? attr.default.default : '', initialValue: attr.default && attr.default.default ? attr.default.default : attr.is_list ? [] : null,
}, },
]" ]"
> >
</a-input> </a-select>
<a-input-number <a-input-number
v-decorator="[ v-decorator="[
attr.name, attr.name,

View File

@@ -0,0 +1,103 @@
<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>

View File

@@ -0,0 +1 @@
editAttrsDrawer 这个文件似乎也没用了

View File

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

View File

@@ -13,11 +13,7 @@
<a-form :form="form" :layout="formLayout"> <a-form :form="form" :layout="formLayout">
<a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.basicConfig') }}</a-divider> <a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.basicConfig') }}</a-divider>
<a-col :span="12"> <a-col :span="12">
<a-form-item <a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" :label="$t('cmdb.ciType.AttributeName')">
:label-col="formItemLayout.labelCol"
:wrapper-col="formItemLayout.wrapperCol"
:label="$t('cmdb.ciType.AttributeName')"
>
<a-input <a-input
:disabled="true" :disabled="true"
name="name" name="name"
@@ -39,20 +35,12 @@
</a-col> </a-col>
<a-col <a-col
:span="12" :span="12"
><a-form-item ><a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" :label="$t('alias')">
:label-col="formItemLayout.labelCol"
:wrapper-col="formItemLayout.wrapperCol"
:label="$t('alias')"
>
<a-input name="alias" v-decorator="['alias', { rules: [] }]" /> </a-form-item <a-input name="alias" v-decorator="['alias', { rules: [] }]" /> </a-form-item
></a-col> ></a-col>
<a-col <a-col
:span="12" :span="12"
><a-form-item ><a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" :label="$t('cmdb.ciType.DataType')">
:label-col="formItemLayout.labelCol"
:wrapper-col="formItemLayout.wrapperCol"
:label="$t('cmdb.ciType.DataType')"
>
<a-select <a-select
:disabled="true" :disabled="true"
name="value_type" name="value_type"
@@ -71,12 +59,13 @@
:label="$t('cmdb.ciType.defaultValue')" :label="$t('cmdb.ciType.defaultValue')"
> >
<template> <template>
<a-input <a-select
v-if="form.getFieldValue('is_list')" v-if="form.getFieldValue('is_list')"
mode="tags"
:style="{ width: '100%' }" :style="{ width: '100%' }"
v-decorator="['default_value', { rules: [{ required: false }] }]" v-decorator="['default_value', { rules: [{ required: false }] }]"
> >
</a-input> </a-select>
<a-select <a-select
v-decorator="['default_value', { rules: [{ required: false }] }]" v-decorator="['default_value', { rules: [{ required: false }] }]"
mode="tags" mode="tags"
@@ -171,11 +160,7 @@
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'"> <a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
<a-form-item <a-form-item :label-col="{ span: 8 }" :wrapper-col="horizontalFormItemLayout.wrapperCol" :label="$t('cmdb.ciType.unique')">
:label-col="{ span: 8 }"
:wrapper-col="horizontalFormItemLayout.wrapperCol"
:label="$t('cmdb.ciType.unique')"
>
<a-switch <a-switch
:disabled="isShowComputedArea" :disabled="isShowComputedArea"
@change="onChange" @change="onChange"
@@ -297,11 +282,6 @@
</a-col> </a-col>
<a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider> <a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider>
<a-row> <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-col :span="24">
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')"> <a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')">
<FontArea ref="fontArea" /> <FontArea ref="fontArea" />
@@ -323,7 +303,11 @@
<span <span
style="position:relative;white-space:pre;" style="position:relative;white-space:pre;"
>{{ $t('cmdb.ciType.computedAttribute') }} >{{ $t('cmdb.ciType.computedAttribute') }}
<a-tooltip :title="$t('cmdb.ciType.computedAttributeTips')"> <a-tooltip
:title="
$t('cmdb.ciType.computedAttributeTips')
"
>
<a-icon <a-icon
style="position:absolute;top:3px;left:-17px;color:#2f54eb;" style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
type="question-circle" type="question-circle"
@@ -371,8 +355,6 @@ import _ from 'lodash'
import moment from 'moment' import moment from 'moment'
import vueJsonEditor from 'vue-json-editor' import vueJsonEditor from 'vue-json-editor'
import { import {
// createAttribute,
// createCITypeAttributes,
updateAttributeById, updateAttributeById,
updateCITypeAttributesById, updateCITypeAttributesById,
canDefineComputed, canDefineComputed,
@@ -382,11 +364,10 @@ import { valueTypeMap } from '../../utils/const'
import ComputedArea from './computedArea.vue' import ComputedArea from './computedArea.vue'
import PreValueArea from './preValueArea.vue' import PreValueArea from './preValueArea.vue'
import FontArea from './fontArea.vue' import FontArea from './fontArea.vue'
import RegSelect from '@/components/RegexSelect'
export default { export default {
name: 'AttributeEditForm', name: 'AttributeEditForm',
components: { ComputedArea, PreValueArea, vueJsonEditor, FontArea, RegSelect }, components: { ComputedArea, PreValueArea, vueJsonEditor, FontArea },
props: { props: {
CITypeId: { CITypeId: {
type: Number, type: Number,
@@ -414,7 +395,6 @@ export default {
isShowComputedArea: false, isShowComputedArea: false,
defaultForDatetime: '', defaultForDatetime: '',
re_check: {},
} }
}, },
@@ -537,30 +517,15 @@ export default {
}) })
} }
console.log(_record) 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) { if (_record.default) {
this.$nextTick(() => { this.$nextTick(() => {
if (_record.value_type === '0') { if (_record.value_type === '0') {
if (_record.is_list) { this.form.setFieldsValue({
this.$nextTick(() => { default_value: _record.default.default ? [_record.default.default] : [],
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') { } else if (_record.value_type === '6') {
this.default_value_json = _record?.default?.default || null this.default_value_json = _record?.default?.default || null
} else if ((_record.value_type === '3' || _record.value_type === '4') && !_record.is_list) { } else if (_record.value_type === '3' || _record.value_type === '4') {
if (_record?.default?.default === '$created_at' || _record?.default?.default === '$updated_at') { if (_record?.default?.default === '$created_at' || _record?.default?.default === '$updated_at') {
this.defaultForDatetime = _record.default.default this.defaultForDatetime = _record.default.default
this.form.setFieldsValue({ this.form.setFieldsValue({
@@ -619,9 +584,6 @@ export default {
await this.form.validateFields(async (err, values) => { await this.form.validateFields(async (err, values) => {
if (!err) { if (!err) {
console.log('Received values of form: ', values) 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) { if (this.record.is_required !== values.is_required || this.record.default_show !== values.default_show) {
console.log('changed is_required') console.log('changed is_required')
@@ -636,11 +598,7 @@ export default {
delete values['is_required'] delete values['is_required']
const { default_value } = values const { default_value } = values
if (values.value_type === '0' && default_value) { if (values.value_type === '0' && default_value) {
if (values.is_list) { values.default = { default: default_value[0] || null }
values.default = { default: default_value || null }
} else {
values.default = { default: default_value[0] || null }
}
} else if (values.value_type === '6') { } else if (values.value_type === '6') {
if (this.default_value_json_right) { if (this.default_value_json_right) {
values.default = { default: this.default_value_json } values.default = { default: this.default_value_json }
@@ -648,13 +606,13 @@ export default {
values.default = { default: null } values.default = { default: null }
} }
} else if (default_value || default_value === 0) { } else if (default_value || default_value === 0) {
if (values.value_type === '3' && !values.is_list) { if (values.value_type === '3') {
if (default_value === '$created_at' || default_value === '$updated_at') { if (default_value === '$created_at' || default_value === '$updated_at') {
values.default = { default: default_value } values.default = { default: default_value }
} else { } else {
values.default = { default: moment(default_value).format('YYYY-MM-DD HH:mm:ss') } values.default = { default: moment(default_value).format('YYYY-MM-DD HH:mm:ss') }
} }
} else if (values.value_type === '4' && !values.is_list) { } else if (values.value_type === '4') {
values.default = { default: moment(default_value).format('YYYY-MM-DD') } values.default = { default: moment(default_value).format('YYYY-MM-DD') }
} else { } else {
values.default = { default: default_value } values.default = { default: default_value }
@@ -683,9 +641,6 @@ export default {
values.value_type = '2' values.value_type = '2'
values.is_link = true values.is_link = true
} }
if (values.value_type !== '6') {
values.re_check = this.re_check?.value ?? null
}
if (values.id) { if (values.id) {
await this.updateAttribute(values.id, { ...values, option: { fontOptions } }, isCalcComputed) await this.updateAttribute(values.id, { ...values, option: { fontOptions } }, isCalcComputed)
} else { } else {
@@ -743,21 +698,6 @@ export default {
async handleCalcComputed() { async handleCalcComputed() {
await this.handleSubmit(true) 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: {}, watch: {},
} }

View File

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

View File

@@ -21,10 +21,7 @@
message: $t('cmdb.ciType.attributeNameTips'), message: $t('cmdb.ciType.attributeNameTips'),
pattern: RegExp('^(?!\\d)[a-zA-Z_0-9]+$'), 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)$).*$'),
},
], ],
}, },
]" ]"
@@ -62,12 +59,13 @@
:label="$t('cmdb.ciType.defaultValue')" :label="$t('cmdb.ciType.defaultValue')"
> >
<template> <template>
<a-input <a-select
v-if="form.getFieldValue('is_list')" v-if="form.getFieldValue('is_list')"
mode="tags"
:style="{ width: '100%' }" :style="{ width: '100%' }"
v-decorator="['default_value', { rules: [{ required: false }] }]" v-decorator="['default_value', { rules: [{ required: false }] }]"
> >
</a-input> </a-select>
<a-input-number <a-input-number
style="width: 100%" style="width: 100%"
v-else-if="currentValueType === '1'" v-else-if="currentValueType === '1'"
@@ -164,11 +162,7 @@
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'"> <a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
<a-form-item <a-form-item :label-col="{ span: 8 }" :wrapper-col="horizontalFormItemLayout.wrapperCol" :label="$t('cmdb.ciType.unique')">
:label-col="{ span: 8 }"
:wrapper-col="horizontalFormItemLayout.wrapperCol"
:label="$t('cmdb.ciType.unique')"
>
<a-switch <a-switch
:disabled="isShowComputedArea" :disabled="isShowComputedArea"
@change="onChange" @change="onChange"
@@ -286,11 +280,6 @@
</a-col> </a-col>
<a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider> <a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider>
<a-row> <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-col :span="24">
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')"> <a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')">
<FontArea ref="fontArea" /> <FontArea ref="fontArea" />
@@ -307,7 +296,11 @@
<span <span
style="position:relative;white-space:pre;" style="position:relative;white-space:pre;"
>{{ $t('cmdb.ciType.computedAttribute') }} >{{ $t('cmdb.ciType.computedAttribute') }}
<a-tooltip :title="$t('cmdb.ciType.computedAttributeTips')"> <a-tooltip
:title="
$t('cmdb.ciType.computedAttributeTips')
"
>
<a-icon <a-icon
style="position:absolute;top:3px;left:-17px;color:#2f54eb;" style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
type="question-circle" type="question-circle"
@@ -347,7 +340,6 @@ import { valueTypeMap } from '../../utils/const'
import ComputedArea from './computedArea.vue' import ComputedArea from './computedArea.vue'
import PreValueArea from './preValueArea.vue' import PreValueArea from './preValueArea.vue'
import FontArea from './fontArea.vue' import FontArea from './fontArea.vue'
import RegSelect from '@/components/RegexSelect'
export default { export default {
name: 'CreateNewAttribute', name: 'CreateNewAttribute',
@@ -356,7 +348,6 @@ export default {
PreValueArea, PreValueArea,
vueJsonEditor, vueJsonEditor,
FontArea, FontArea,
RegSelect,
}, },
props: { props: {
hasFooter: { hasFooter: {
@@ -383,8 +374,6 @@ export default {
isShowComputedArea: false, isShowComputedArea: false,
defaultForDatetime: '', defaultForDatetime: '',
re_check: {},
} }
}, },
computed: { computed: {
@@ -408,12 +397,8 @@ export default {
const data = { is_required, default_show } const data = { is_required, default_show }
delete values.is_required delete values.is_required
delete values.default_show delete values.default_show
if (values.value_type === '0' && default_value) { if (values.value_type === '0' && default_value && default_value.length) {
if (values.is_list) { values.default = { default: default_value[0] }
values.default = { default: default_value || null }
} else {
values.default = { default: default_value[0] || null }
}
} else if (values.value_type === '6') { } else if (values.value_type === '6') {
if (this.default_value_json_right) { if (this.default_value_json_right) {
values.default = { default: this.default_value_json } values.default = { default: this.default_value_json }
@@ -421,13 +406,13 @@ export default {
values.default = { default: null } values.default = { default: null }
} }
} else if (default_value || default_value === 0) { } else if (default_value || default_value === 0) {
if (values.value_type === '3' && !values.is_list) { if (values.value_type === '3') {
if (default_value === '$created_at' || default_value === '$updated_at') { if (default_value === '$created_at' || default_value === '$updated_at') {
values.default = { default: default_value } values.default = { default: default_value }
} else { } else {
values.default = { default: moment(default_value).format('YYYY-MM-DD HH:mm:ss') } values.default = { default: moment(default_value).format('YYYY-MM-DD HH:mm:ss') }
} }
} else if (values.value_type === '4' && !values.is_list) { } else if (values.value_type === '4') {
values.default = { default: moment(default_value).format('YYYY-MM-DD') } values.default = { default: moment(default_value).format('YYYY-MM-DD') }
} else { } else {
values.default = { default: default_value } values.default = { default: default_value }
@@ -464,9 +449,6 @@ export default {
values.value_type = '2' values.value_type = '2'
values.is_link = true 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 } }) const { attr_id } = await createAttribute({ ...values, option: { fontOptions } })
this.form.resetFields() this.form.resetFields()
@@ -557,21 +539,6 @@ export default {
default_value: key, 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> </script>

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -79,7 +79,7 @@
<vxe-column <vxe-column
align="center" align="center"
field="is_accept" field="is_accept"
:title="$t('cmdb.ad.isAccept')" :title="$t('cmdb.ad.isAccpet')"
v-bind="columns.length ? { width: '100px' } : { minWidth: '100px' }" v-bind="columns.length ? { width: '100px' } : { minWidth: '100px' }"
:filters="[ :filters="[
{ label: $t('yes'), value: true }, { label: $t('yes'), value: true },
@@ -92,7 +92,7 @@
</vxe-column> </vxe-column>
<vxe-column <vxe-column
field="accept_by" field="accept_by"
:title="$t('cmdb.ad.acceptBy')" :title="$t('cmdb.ad.accpetBy')"
v-bind="columns.length ? { width: '80px' } : { minWidth: '80px' }" v-bind="columns.length ? { width: '80px' } : { minWidth: '80px' }"
:filters="[]" :filters="[]"
></vxe-column> ></vxe-column>
@@ -186,8 +186,8 @@ export default {
this.clickSidebar(Number(_currentType)) this.clickSidebar(Number(_currentType))
return return
} }
if (res && res.length && res[0].ci_types && res[0].ci_types.length) { if (res && res.length) {
this.clickSidebar(res[0].ci_types[0].id) this.clickSidebar(res[0].id)
} }
}) })
}, },
@@ -246,7 +246,7 @@ export default {
content: that.$t('cmdb.ad.confirmAccept'), content: that.$t('cmdb.ad.confirmAccept'),
onOk() { onOk() {
updateADCAccept(row.id).then(() => { updateADCAccept(row.id).then(() => {
that.$message.success(that.$t('cmdb.ad.acceptSuccess')) that.$message.success(that.$t('cmdb.ad.accpetSuccess'))
that.getAdc(false) that.getAdc(false)
}) })
}, },

View File

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

View File

@@ -5,7 +5,7 @@
<span class="cmdb-preference-left-card-title">{{ $t('cmdb.preference.mySub') }}</span> <span class="cmdb-preference-left-card-title">{{ $t('cmdb.preference.mySub') }}</span>
<span <span
class="cmdb-preference-left-card-content" 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 <a-badge
showZero showZero
:count="self.instance.length" :count="self.instance.length"
@@ -16,10 +16,11 @@
height: '23px', height: '23px',
fontSize: '14px', fontSize: '14px',
}" }"
/></span> /></span
>
<span <span
class="cmdb-preference-left-card-content" 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 <a-badge
showZero showZero
:count="self.tree.length" :count="self.tree.length"
@@ -30,67 +31,57 @@
height: '23px', height: '23px',
fontSize: '14px', fontSize: '14px',
}" }"
/></span> /></span
>
</div> </div>
<div class="cmdb-preference-group" v-for="(group, index) in myPreferences" :key="group.name"> <div class="cmdb-preference-group" v-for="(group, index) in myPreferences" :key="group.name">
<div class="cmdb-preference-group-title"> <div class="cmdb-preference-group-title">
<span> <ops-icon :style="{ marginRight: '10px' }" :type="group.icon" />{{ group.name }} </span> <span> <ops-icon :style="{ marginRight: '10px' }" :type="group.icon" />{{ group.name }} </span>
</div> </div>
<draggable <div class="cmdb-preference-group-content" v-for="ciType in group.ci_types" :key="ciType.id">
v-model="group.ci_types" <div
:animation="300" :class="{
@change=" 'cmdb-preference-avatar': true,
(e) => { 'cmdb-preference-avatar-noicon': !ciType.icon,
orderChange(e, group) 'cmdb-preference-avatar-noicon-is_subscribed': !ciType.icon && ciType.is_subscribed,
} }"
" :style="{ width: '30px', height: '30px', marginRight: '10px' }"
> >
<div class="cmdb-preference-group-content" v-for="ciType in group.ci_types" :key="ciType.id"> <template v-if="ciType.icon">
<OpsMoveIcon class="cmdb-preference-move-icon" /> <img
<div v-if="ciType.icon.split('$$')[2]"
:class="{ :src="`/api/common-setting/v1/file/${ciType.icon.split('$$')[3]}`"
'cmdb-preference-avatar': true, :style="{ maxHeight: '30px', maxWidth: '30px' }"
'cmdb-preference-avatar-noicon': !ciType.icon, />
'cmdb-preference-avatar-noicon-is_subscribed': !ciType.icon && ciType.is_subscribed, <ops-icon
}" v-else
:style="{ width: '30px', height: '30px', marginRight: '10px' }" :style="{
> color: ciType.icon.split('$$')[1],
<template v-if="ciType.icon"> fontSize: '14px',
<img }"
v-if="ciType.icon.split('$$')[2]" :type="ciType.icon.split('$$')[0]"
:src="`/api/common-setting/v1/file/${ciType.icon.split('$$')[3]}`" />
:style="{ maxHeight: '30px', maxWidth: '30px' }" </template>
/> <span v-else :style="{ fontSize: '20px' }">{{ ciType.name[0].toUpperCase() }}</span>
<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> </div>
</draggable> <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>
</div> </div>
</div> </div>
<div class="cmdb-preference-right"> <div class="cmdb-preference-right">
@@ -133,12 +124,19 @@
> >
</div> </div>
<div class="cmdb-preference-colleague"> <div class="cmdb-preference-colleague">
<span <template v-if="type_id2users[item.id] && type_id2users[item.id].length">
v-if="type_id2users[item.id] && type_id2users[item.id].length" <span
>{{ type_id2users[item.id].length > 99 ? '99+' : type_id2users[item.id].length >{{ type_id2users[item.id].length > 99 ? '99+' : type_id2users[item.id].length
}}{{ $t('cmdb.preference.peopleSub') }}</span }}{{ $t('cmdb.preference.peopleSub') }}</span
> >
<span v-else>{{ $t('cmdb.preference.noSub') }}</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>
</div> </div>
<div class="cmdb-preference-progress"> <div class="cmdb-preference-progress">
<div class="cmdb-preference-progress-info"> <div class="cmdb-preference-progress-info">
@@ -192,23 +190,15 @@ import router, { resetRouter } from '@/router'
import store from '@/store' import store from '@/store'
import { mapState } from 'vuex' import { mapState } from 'vuex'
import moment from 'moment' import moment from 'moment'
import draggable from 'vuedraggable'
import { getCITypeGroups } from '../../api/ciTypeGroup' import { getCITypeGroups } from '../../api/ciTypeGroup'
import { import { getPreference, getPreference2, subscribeCIType, subscribeTreeView } from '@/modules/cmdb/api/preference'
getPreference,
getPreference2,
subscribeCIType,
subscribeTreeView,
preferenceCitypeOrder,
} from '@/modules/cmdb/api/preference'
import CollapseTransition from '@/components/CollapseTransition' import CollapseTransition from '@/components/CollapseTransition'
import SubscribeSetting from '../../components/subscribeSetting/subscribeSetting' import SubscribeSetting from '../../components/subscribeSetting/subscribeSetting'
import { getCIAdcStatistics } from '../../api/ci' import { getCIAdcStatistics } from '../../api/ci'
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
export default { export default {
name: 'Preference', name: 'Preference',
components: { CollapseTransition, SubscribeSetting, draggable, OpsMoveIcon }, components: { CollapseTransition, SubscribeSetting },
data() { data() {
return { return {
citypeData: [], citypeData: [],
@@ -224,6 +214,7 @@ export default {
computed: { computed: {
...mapState({ ...mapState({
windowHeight: (state) => state.windowHeight, windowHeight: (state) => state.windowHeight,
allUsers: (state) => state.user.allUsers,
}), }),
}, },
mounted() { mounted() {
@@ -286,6 +277,10 @@ export default {
}, 300) }, 300)
} }
}, },
getNameByUid(uid) {
const _find = this.allUsers.find((item) => item.uid === uid)
return _find?.username[0].toUpperCase() || 'A'
},
getsubscribedDays(item) { getsubscribedDays(item) {
const subscribedTime = this.self.type_id2subs_time[item.id] const subscribedTime = this.self.type_id2subs_time[item.id]
moment.duration(moment().diff(moment(subscribedTime))) moment.duration(moment().diff(moment(subscribedTime)))
@@ -356,17 +351,6 @@ export default {
this.expandKeys.push(group.id) 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> </script>
@@ -442,7 +426,7 @@ export default {
align-items: center; align-items: center;
height: 45px; height: 45px;
padding: 0 8px; padding: 0 8px;
cursor: move; cursor: default;
justify-content: flex-start; justify-content: flex-start;
&:hover { &:hover {
background: #ffffff; background: #ffffff;
@@ -453,15 +437,6 @@ export default {
white-space: nowrap; white-space: nowrap;
margin-left: auto; 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 { .cmdb-preference-group-content-title {
flex: 1; flex: 1;

File diff suppressed because it is too large Load Diff

View File

@@ -1,250 +1,224 @@
<template> <template>
<a-modal <a-modal
v-model="visible" v-model="visible"
width="90%" width="90%"
:closable="false" :closable="false"
:centered="true" :centered="true"
:maskClosable="false" :maskClosable="false"
:destroyOnClose="true" :destroyOnClose="true"
@cancel="handleClose" @cancel="handleClose"
@ok="handleOk" @ok="handleOk"
> >
<div :style="{ width: '100%' }" id="add-table-modal"> <div :style="{ width: '100%' }" id="add-table-modal">
<a-spin :spinning="loading"> <a-spin :spinning="loading">
<SearchForm <!-- <a-input
ref="searchForm" v-model="expression"
:typeId="addTypeId" class="ci-searchform-expression"
:preferenceAttrList="preferenceAttrList" :style="{ width, marginBottom: '10px' }"
@refresh="handleSearch" :placeholder="placeholder"
> @focus="
<a-button () => {
@click=" isFocusExpression = true
() => { }
$refs.createInstanceForm.handleOpen(true, 'create') "
} /> -->
" <SearchForm
slot="extraContent" ref="searchForm"
type="primary" :typeId="addTypeId"
size="small" :preferenceAttrList="preferenceAttrList"
>新增</a-button @refresh="handleSearch"
> />
</SearchForm> <!-- <a @click="handleSearch"><a-icon type="search"/></a> -->
<vxe-table <vxe-table
ref="xTable" ref="xTable"
row-id="_id" row-id="_id"
:data="tableData" :data="tableData"
:height="tableHeight" :height="tableHeight"
highlight-hover-row highlight-hover-row
:checkbox-config="{ reserve: true }" :checkbox-config="{ reserve: true }"
@checkbox-change="onSelectChange" @checkbox-change="onSelectChange"
@checkbox-all="onSelectChange" @checkbox-all="onSelectChange"
show-overflow="tooltip" show-overflow="tooltip"
show-header-overflow="tooltip" show-header-overflow="tooltip"
:scroll-y="{ enabled: true, gt: 50 }" :scroll-y="{ enabled: true, gt: 50 }"
:scroll-x="{ enabled: true, gt: 0 }" :scroll-x="{ enabled: true, gt: 0 }"
class="ops-stripe-table" class="ops-stripe-table"
> >
<vxe-column align="center" type="checkbox" width="60" fixed="left"></vxe-column> <vxe-column align="center" type="checkbox" width="60" fixed="left"></vxe-column>
<vxe-table-column <vxe-table-column
v-for="col in columns" v-for="col in columns"
:key="col.field" :key="col.field"
:title="col.title" :title="col.title"
:field="col.field" :field="col.field"
:width="col.width" :width="col.width"
:sortable="col.sortable" :sortable="col.sortable"
> >
<template #default="{row}" v-if="col.value_type === '6'"> <template #default="{row}" v-if="col.value_type === '6'">
<span v-if="col.value_type === '6' && row[col.field]">{{ JSON.stringify(row[col.field]) }}</span> <span v-if="col.value_type === '6' && row[col.field]">{{ JSON.stringify(row[col.field]) }}</span>
</template> </template>
</vxe-table-column> </vxe-table-column>
</vxe-table> </vxe-table>
<a-pagination <a-pagination
v-model="currentPage" v-model="currentPage"
size="small" size="small"
:total="totalNumber" :total="totalNumber"
show-quick-jumper show-quick-jumper
:page-size="50" :page-size="50"
:show-total=" :show-total="
(total, range) => (total, range) =>
$t('pagination.total', { $t('pagination.total', {
range0: range[0], range0: range[0],
range1: range[1], range1: range[1],
total, total,
}) })
" "
:style="{ textAlign: 'right', marginTop: '10px' }" :style="{ textAlign: 'right', marginTop: '10px' }"
@change="handleChangePage" @change="handleChangePage"
/> />
</a-spin> </a-spin>
</div> </div>
<CreateInstanceForm </a-modal>
ref="createInstanceForm" </template>
:typeIdFromRelation="addTypeId"
@reload=" <script>
() => { /* eslint-disable no-useless-escape */
currentPage = 1 import { searchCI } from '@/modules/cmdb/api/ci'
getTableData(true) import { getSubscribeAttributes } from '@/modules/cmdb/api/preference'
} import { batchUpdateCIRelationChildren, batchUpdateCIRelationParents } from '@/modules/cmdb/api/CIRelation'
" import { getCITableColumns } from '../../../utils/helper'
/> import SearchForm from '../../../components/searchForm/SearchForm.vue'
</a-modal> export default {
</template> name: 'AddTableModal',
components: { SearchForm },
<script> data() {
import { searchCI } from '@/modules/cmdb/api/ci' return {
import { getSubscribeAttributes } from '@/modules/cmdb/api/preference' visible: false,
import { batchUpdateCIRelationChildren, batchUpdateCIRelationParents } from '@/modules/cmdb/api/CIRelation' currentPage: 1,
import { getCITableColumns } from '../../../utils/helper' totalNumber: 0,
import SearchForm from '../../../components/searchForm/SearchForm.vue' tableData: [],
import CreateInstanceForm from '../../ci/modules/CreateInstanceForm.vue' columns: [],
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr' ciObj: {},
ciId: null,
export default { addTypeId: null,
name: 'AddTableModal', loading: false,
components: { SearchForm, CreateInstanceForm }, expression: '',
data() { isFocusExpression: false,
return { type: 'children',
visible: false, preferenceAttrList: [],
currentPage: 1, ancestor_ids: undefined,
totalNumber: 0, }
tableData: [], },
columns: [], computed: {
ciObj: {}, tableHeight() {
ciId: null, return this.$store.state.windowHeight - 250
addTypeId: null, },
loading: false, placeholder() {
expression: '', return this.isFocusExpression ? this.$t('cmdb.serviceTreetips1') : this.$t('cmdb.serviceTreetips2')
isFocusExpression: false, },
type: 'children', width() {
preferenceAttrList: [], return this.isFocusExpression ? '500px' : '100px'
ancestor_ids: undefined, },
attrList1: [], },
} watch: {},
}, methods: {
computed: { async openModal(ciObj, ciId, addTypeId, type, ancestor_ids = undefined) {
tableHeight() { console.log(ciObj, ciId, addTypeId, type)
return this.$store.state.windowHeight - 250 this.visible = true
}, this.ciObj = ciObj
placeholder() { this.ciId = ciId
return this.isFocusExpression ? this.$t('cmdb.serviceTreetips1') : this.$t('cmdb.serviceTreetips2') this.addTypeId = addTypeId
}, this.type = type
width() { this.ancestor_ids = ancestor_ids
return this.isFocusExpression ? '500px' : '100px' await getSubscribeAttributes(addTypeId).then((res) => {
}, this.preferenceAttrList = res.attributes // 已经订阅的全部列
}, })
provide() { this.getTableData(true)
return { },
attrList: () => { async getTableData(isInit) {
return this.attrList if (this.addTypeId) {
}, await this.fetchData(isInit)
} }
}, },
watch: {}, async fetchData(isInit) {
methods: { this.loading = true
async openModal(ciObj, ciId, addTypeId, type, ancestor_ids = undefined) { // if (isInit) {
console.log(ciObj, ciId, addTypeId, type) // const subscribed = await getSubscribeAttributes(this.addTypeId)
this.visible = true // this.preferenceAttrList = subscribed.attributes // 已经订阅的全部列
this.ciObj = ciObj // }
this.ciId = ciId let sort, fuzzySearch, expression, exp
this.addTypeId = addTypeId if (!isInit) {
this.type = type fuzzySearch = this.$refs['searchForm'].fuzzySearch
this.ancestor_ids = ancestor_ids expression = this.$refs['searchForm'].expression || ''
await getSubscribeAttributes(addTypeId).then((res) => { const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
this.preferenceAttrList = res.attributes // 已经订阅的全部列
}) exp = expression.match(regQ) ? expression.match(regQ)[0] : null
getCITypeAttributesById(addTypeId).then((res) => { }
this.attrList = res.attributes
}) await searchCI({
this.getTableData(true) q: `_type:${this.addTypeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`,
}, count: 50,
async getTableData(isInit) { page: this.currentPage,
if (this.addTypeId) { sort,
await this.fetchData(isInit) })
} .then((res) => {
}, this.tableData = res.result
async fetchData(isInit) { this.totalNumber = res.numfound
this.loading = true this.columns = this.getColumns(res.result, this.preferenceAttrList)
// if (isInit) { this.$nextTick(() => {
// const subscribed = await getSubscribeAttributes(this.addTypeId) const _table = this.$refs.xTable
// this.preferenceAttrList = subscribed.attributes // 已经订阅的全部列 if (_table) {
// } _table.refreshColumn()
let sort, fuzzySearch, expression, exp }
if (!isInit) { this.loading = false
fuzzySearch = this.$refs['searchForm'].fuzzySearch })
expression = this.$refs['searchForm'].expression || '' })
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g .catch(() => {
this.loading = false
exp = expression.match(regQ) ? expression.match(regQ)[0] : null })
} },
getColumns(data, attrList) {
await searchCI({ const modalDom = document.getElementById('add-table-modal')
q: `_type:${this.addTypeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`, if (modalDom) {
count: 50, const width = modalDom.clientWidth - 50
page: this.currentPage, return getCITableColumns(data, attrList, width)
sort, }
}) return []
.then((res) => { },
this.tableData = res.result onSelectChange() {},
this.totalNumber = res.numfound handleClose() {
this.columns = this.getColumns(res.result, this.preferenceAttrList) this.$refs.xTable.clearCheckboxRow()
this.$nextTick(() => { this.currentPage = 1
const _table = this.$refs.xTable this.expression = ''
if (_table) { this.isFocusExpression = false
_table.refreshColumn() this.visible = false
} },
this.loading = false async handleOk() {
}) const selectRecordsCurrent = this.$refs.xTable.getCheckboxRecords()
}) const selectRecordsReserved = this.$refs.xTable.getCheckboxReserveRecords()
.catch(() => { const ciIds = [...selectRecordsCurrent, ...selectRecordsReserved].map((record) => record._id)
this.loading = false if (ciIds.length) {
}) if (this.type === 'children') {
}, await batchUpdateCIRelationChildren(ciIds, [this.ciId], this.ancestor_ids)
getColumns(data, attrList) { } else {
const modalDom = document.getElementById('add-table-modal') await batchUpdateCIRelationParents(ciIds, [this.ciId])
if (modalDom) { }
const width = modalDom.clientWidth - 50 setTimeout(() => {
return getCITableColumns(data, attrList, width) this.$message.success(this.$t('addSuccess'))
} this.handleClose()
return [] this.$emit('reload')
}, }, 500)
onSelectChange() {}, }
handleClose() { },
this.$refs.xTable.clearCheckboxRow() handleSearch() {
this.currentPage = 1 this.currentPage = 1
this.expression = '' this.fetchData()
this.isFocusExpression = false },
this.visible = false handleChangePage(page, pageSize) {
}, this.currentPage = page
async handleOk() { this.fetchData()
const selectRecordsCurrent = this.$refs.xTable.getCheckboxRecords() },
const selectRecordsReserved = this.$refs.xTable.getCheckboxReserveRecords() },
const ciIds = [...selectRecordsCurrent, ...selectRecordsReserved].map((record) => record._id) }
if (ciIds.length) { </script>
if (this.type === 'children') {
await batchUpdateCIRelationChildren(ciIds, [this.ciId], this.ancestor_ids) <style lang="less" scoped></style>
} else {
await batchUpdateCIRelationParents(ciIds, [this.ciId])
}
setTimeout(() => {
this.$message.success(this.$t('addSuccess'))
this.handleClose()
this.$emit('reload')
}, 500)
} else {
this.handleClose()
this.$emit('reload')
}
},
handleSearch() {
this.currentPage = 1
this.fetchData()
},
handleChangePage(page, pageSize) {
this.currentPage = page
this.fetchData()
},
},
}
</script>
<style lang="less" scoped></style>

View File

@@ -1,253 +1,166 @@
<template> <template>
<div <a-dropdown :trigger="['contextmenu']">
:class="{ <a-menu slot="overlay" @click="({ key: menuKey }) => this.onContextMenuClick(this.treeKey, menuKey)">
'relation-views-node': true, <a-menu-item v-for="item in menuList" :key="item.id">{{ $t('new') }} {{ item.alias }}</a-menu-item>
'relation-views-node-checkbox': showCheckbox, <a-menu-item v-if="showDelete" key="delete">{{ $t('cmdb.serviceTree.deleteNode') }}</a-menu-item>
}" </a-menu>
@click="clickNode" <div
> :style="{
<span> width: '100%',
<a-checkbox @click.stop="clickCheckbox" class="relation-views-node-checkbox" v-if="showCheckbox" /> display: 'inline-flex',
<template v-if="icon"> justifyContent: 'space-between',
<img alignItems: 'center',
v-if="icon.includes('$$') && icon.split('$$')[2]" }"
:src="`/api/common-setting/v1/file/${icon.split('$$')[3]}`" @click="clickNode"
:style="{ maxHeight: '14px', maxWidth: '14px' }" >
/> <span
<ops-icon :style="{
v-else-if="icon.includes('$$') && icon.split('$$')[0]" display: 'flex',
:style="{ overflow: 'hidden',
color: icon.split('$$')[1], width: '100%',
fontSize: '14px', textOverflow: 'ellipsis',
}" whiteSpace: 'nowrap',
:type="icon.split('$$')[0]" alignItems: 'center',
/> }"
<span class="relation-views-node-icon" v-else>{{ icon ? icon[0].toUpperCase() : 'i' }}</span> >
</template> <template v-if="icon">
<span class="relation-views-node-title">{{ this.title }}</span> <img
</span> v-if="icon.split('$$')[2]"
<a-dropdown> :src="`/api/common-setting/v1/file/${icon.split('$$')[3]}`"
<a-menu slot="overlay" @click="({ key: menuKey }) => this.onContextMenuClick(this.treeKey, menuKey)"> :style="{ maxHeight: '14px', maxWidth: '14px' }"
<template v-if="showBatchLevel === null"> />
<a-menu-item <ops-icon
v-for="item in menuList" v-else
:key="item.id" :style="{
><a-icon type="plus-circle" />{{ $t('new') }} {{ item.alias }}</a-menu-item color: icon.split('$$')[1],
> fontSize: '14px',
<a-menu-item }"
v-if="showDelete" :type="icon.split('$$')[0]"
key="delete" />
><ops-icon type="icon-xianxing-delete" />{{ $t('cmdb.serviceTree.deleteNode') }}</a-menu-item </template>
> <span
<a-menu-divider /> :style="{
<a-menu-item key="grant"><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item> display: 'inline-block',
<a-menu-item key="revoke"><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item> width: '16px',
<a-menu-item key="view"><a-icon type="eye" />{{ $t('cmdb.serviceTree.view') }}</a-menu-item> height: '16px',
<a-menu-divider /> borderRadius: '50%',
<a-menu-item backgroundColor: '#d3d3d3',
key="batch" color: '#fff',
><ops-icon type="icon-xianxing-copy" />{{ $t('cmdb.serviceTree.batch') }}</a-menu-item textAlign: 'center',
> lineHeight: '16px',
</template> fontSize: '12px',
<template v-else> }"
<a-menu-item v-else
:disabled="!batchTreeKey || !batchTreeKey.length" >{{ ciTypeName ? ciTypeName[0].toUpperCase() : 'i' }}</span
key="batchGrant" >
><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item <span :style="{ marginLeft: '5px' }">{{ this.title }}</span>
> </span>
<a-menu-item <a-icon :style="{ fontSize: '10px' }" v-if="childLength && !isLeaf" :type="switchIcon"></a-icon>
:disabled="!batchTreeKey || !batchTreeKey.length" </div>
key="batchRevoke" </a-dropdown>
><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item </template>
>
<a-menu-divider /> <script>
<template v-if="showBatchLevel > 0"> export default {
<a-menu-item name: 'ContextMenu',
:disabled="!batchTreeKey || !batchTreeKey.length" props: {
key="batchDelete" title: {
><ops-icon type="icon-xianxing-delete" />{{ $t('delete') }}</a-menu-item type: String,
> default: '',
<a-menu-divider /> },
</template> treeKey: {
<a-menu-item key="batchCancel"><a-icon type="close-circle" />{{ $t('cancel') }}</a-menu-item> type: String,
</template> default: '',
</a-menu> },
<a-icon class="relation-views-node-operation" type="ellipsis" /> levels: {
</a-dropdown> type: Array,
<a-icon :style="{ fontSize: '10px' }" v-if="childLength && !isLeaf" :type="switchIcon"></a-icon> default: () => [],
</div> },
</template> currentViews: {
type: Object,
<script> default: () => {},
export default { },
name: 'ContextMenu', id2type: {
props: { type: Object,
title: { default: () => {},
type: String, },
default: '', isLeaf: {
}, type: Boolean,
treeKey: { default: () => false,
type: String, },
default: '', ciTypes: {
}, type: Array,
levels: { default: () => [],
type: Array, },
default: () => [], },
}, data() {
currentViews: { return {
type: Object, switchIcon: 'down',
default: () => {}, }
}, },
id2type: { computed: {
type: Object, childLength() {
default: () => {}, const reg = /(?<=\()\S+(?=\))/g
}, return Number(this.title.match(reg)[0])
isLeaf: { },
type: Boolean, splitTreeKey() {
default: () => false, return this.treeKey.split('@^@')
}, },
ciTypeIcons: { _tempTree() {
type: Object, return this.splitTreeKey[this.splitTreeKey.length - 1].split('%')
default: () => {}, },
}, _typeIdIdx() {
showBatchLevel: { return this.levels.findIndex((level) => level[0] === Number(this._tempTree[1])) // 当前节点在levels中的index
type: Number, },
default: null, showDelete() {
}, if (this._typeIdIdx === 0) {
batchTreeKey: { // 如果是第一层节点则不能删除
type: Array, return false
default: () => [], }
}, return true
}, },
data() { menuList() {
return { let _menuList = []
switchIcon: 'down', if (this._typeIdIdx > -1 && this._typeIdIdx < this.levels.length - 1) {
} // 不是叶子节点
}, const id = Number(this.levels[this._typeIdIdx + 1])
computed: { _menuList = [
childLength() { {
const reg = /(?<=\()\S+(?=\))/g id,
return Number(this.title.match(reg)[0]) alias: this.id2type[id].alias || this.id2type[id].name,
}, },
splitTreeKey() { ]
return this.treeKey.split('@^@') } else {
}, // 叶子节点
_tempTree() { _menuList = this.currentViews.node2show_types[this._tempTree[1]].map((item) => {
return this.splitTreeKey[this.splitTreeKey.length - 1].split('%') return { id: item.id, alias: item.alias || item.name }
}, })
_typeIdIdx() { }
return this.levels.findIndex((level) => level[0] === Number(this._tempTree[1])) // 当前节点在levels中的index return _menuList
}, },
showDelete() { icon() {
if (this._typeIdIdx === 0) { const _split = this.treeKey.split('@^@')
// 如果是第一层节点则不能删除 const currentNodeTypeId = _split[_split.length - 1].split('%')[1]
return false const _find = this.ciTypes.find((type) => type.id === Number(currentNodeTypeId))
} return _find?.icon || null
return true },
}, ciTypeName() {
menuList() { const _split = this.treeKey.split('@^@')
let _menuList = [] const currentNodeTypeId = _split[_split.length - 1].split('%')[1]
if (this._typeIdIdx > -1 && this._typeIdIdx < this.levels.length - 1) { const _find = this.ciTypes.find((type) => type.id === Number(currentNodeTypeId))
// 不是叶子节点 return _find?.name || ''
const id = Number(this.levels[this._typeIdIdx + 1]) },
_menuList = [ },
{ methods: {
id, onContextMenuClick(treeKey, menuKey) {
alias: this.id2type[id].alias || this.id2type[id].name, this.$emit('onContextMenuClick', treeKey, menuKey)
}, },
] clickNode() {
} else { this.$emit('onNodeClick', this.treeKey)
// 叶子节点 this.switchIcon = this.switchIcon === 'down' ? 'up' : 'down'
_menuList = this.currentViews.node2show_types[this._tempTree[1]].map((item) => { },
return { id: item.id, alias: item.alias || item.name } },
}) }
} </script>
return _menuList
}, <style></style>
icon() {
const _split = this.treeKey.split('@^@')
const currentNodeTypeId = _split[_split.length - 1].split('%')[1]
return this.ciTypeIcons[Number(currentNodeTypeId)] ?? null
},
showCheckbox() {
return this.showBatchLevel === this.treeKey.split('@^@').filter((item) => !!item).length - 1
},
},
methods: {
onContextMenuClick(treeKey, menuKey) {
this.$emit('onContextMenuClick', treeKey, menuKey)
},
clickNode() {
this.$emit('onNodeClick', this.treeKey)
this.switchIcon = this.switchIcon === 'down' ? 'up' : 'down'
},
clickCheckbox() {
this.$emit('clickCheckbox', this.treeKey)
},
},
}
</script>
<style lang="less" scoped>
.relation-views-node {
width: 100%;
display: inline-flex;
justify-content: space-between;
align-items: center;
> span {
display: flex;
overflow: hidden;
align-items: center;
width: 100%;
.relation-views-node-icon {
display: inline-block;
width: 16px;
height: 16px;
border-radius: 50%;
background-color: #d3d3d3;
color: #fff;
text-align: center;
line-height: 16px;
font-size: 12px;
}
.relation-views-node-title {
padding-left: 5px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
width: calc(100% - 16px);
}
}
.relation-views-node-operation {
display: none;
margin-right: 5px;
}
}
.relation-views-node-checkbox,
.relation-views-node-moveright {
> span {
.relation-views-node-checkbox {
margin-right: 10px;
}
.relation-views-node-title {
width: calc(100% - 42px);
}
}
}
</style>
<style lang="less">
.relation-views-left .ant-tree-node-content-wrapper:hover {
.relation-views-node-operation {
display: inline-block;
}
}
.relation-views-left {
ul:has(.relation-views-node-checkbox) > li > ul {
margin-left: 26px;
}
ul:has(.relation-views-node-checkbox) {
margin-left: 0 !important;
}
}
</style>

View File

@@ -1,105 +0,0 @@
<template>
<a-modal
width="600px"
:bodyStyle="{
paddingTop: 0,
}"
:visible="visible"
:footer="null"
@cancel="handleCancel"
:title="$t('view')"
>
<div>
<template v-if="readCIIdFilterPermissions && readCIIdFilterPermissions.length">
<p>
<strong>{{ $t('cmdb.serviceTree.idAuthorizationPolicy') }}</strong>
<a
@click="
() => {
showAllReadCIIdFilterPermissions = !showAllReadCIIdFilterPermissions
}
"
v-if="readCIIdFilterPermissions.length > 10"
><a-icon
:type="showAllReadCIIdFilterPermissions ? 'caret-down' : 'caret-up'"
/></a>
</p>
<a-tag
v-for="item in showAllReadCIIdFilterPermissions
? readCIIdFilterPermissions
: readCIIdFilterPermissions.slice(0, 10)"
:key="item.name"
color="blue"
:style="{ marginBottom: '5px' }"
>{{ item.name }}</a-tag
>
<a-tag
:style="{ marginBottom: '5px' }"
v-if="readCIIdFilterPermissions.length > 10 && !showAllReadCIIdFilterPermissions"
>+{{ readCIIdFilterPermissions.length - 10 }}</a-tag
>
</template>
<a-empty v-else>
<img slot="image" :src="require('@/assets/data_empty.png')" />
<span slot="description"> {{ $t('noData') }} </span>
</a-empty>
</div>
</a-modal>
</template>
<script>
import { ciTypeFilterPermissions, getCIType } from '../../../api/CIType'
import FilterComp from '@/components/CMDBFilterComp'
import { searchRole } from '@/modules/acl/api/role'
export default {
name: 'ReadPermissionsModal',
components: { FilterComp },
data() {
return {
visible: false,
filerPerimissions: {},
readCIIdFilterPermissions: [],
canSearchPreferenceAttrList: [],
showAllReadCIIdFilterPermissions: false,
allRoles: [],
}
},
mounted() {
this.loadRoles()
},
methods: {
async loadRoles() {
const res = await searchRole({ page_size: 9999, app_id: 'cmdb', is_all: true })
this.allRoles = res.roles
},
async open(treeKey) {
this.visible = true
const _splitTreeKey = treeKey.split('@^@').filter((item) => !!item)
const _treeKey = _splitTreeKey.slice(_splitTreeKey.length - 1, _splitTreeKey.length)[0].split('%')
const typeId = _treeKey[1]
const _treeKeyPath = _splitTreeKey.map((item) => item.split('%')[0]).join(',')
await ciTypeFilterPermissions(typeId).then((res) => {
this.filerPerimissions = res
})
const readCIIdFilterPermissions = []
Object.entries(this.filerPerimissions).forEach(([k, v]) => {
const { id_filter } = v
if (id_filter && Object.keys(id_filter).includes(_treeKeyPath)) {
const _find = this.allRoles.find((item) => item.id === Number(k))
readCIIdFilterPermissions.push({ name: _find?.name ?? k, rid: k })
}
})
this.readCIIdFilterPermissions = readCIIdFilterPermissions
console.log(readCIIdFilterPermissions)
},
handleCancel() {
this.showAllReadCIIdFilterPermissions = false
this.visible = false
},
},
}
</script>
<style></style>

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More