mirror of
https://github.com/veops/cmdb.git
synced 2025-09-15 16:56:52 +08:00
Compare commits
43 Commits
dependabot
...
2.3.13
Author | SHA1 | Date | |
---|---|---|---|
|
4117cf87ec | ||
|
9e0fe0b818 | ||
|
2a8f1ab9a4 | ||
|
c0fe99b8c7 | ||
|
42feb4b862 | ||
|
482d34993b | ||
|
7ff309b8b8 | ||
|
98eb47d44f | ||
|
9ab0f624ef | ||
|
3f3eda8b3c | ||
|
f788adc8cf | ||
|
693ae4ff05 | ||
|
a1a9d99eb4 | ||
|
e045e0fb43 | ||
|
09376dbd2b | ||
|
7fda5a1e7b | ||
|
113b84763f | ||
|
190170acad | ||
|
513d2af4b8 | ||
|
4588bd8996 | ||
|
082da5fade | ||
|
013b116eb5 | ||
|
208d29165b | ||
|
d510330cde | ||
|
ea4f0fc2a5 | ||
|
9bcdaacdc4 | ||
|
5045581ddf | ||
|
232913172c | ||
|
157e1809ed | ||
|
ab8ccf7d1b | ||
|
ae1f0f6b4f | ||
|
ff47e0ade6 | ||
|
0dd272fb04 | ||
|
6bc5c1516d | ||
|
4a4b9e6ef0 | ||
|
6e3f9478b3 | ||
|
691051c254 | ||
|
8f066e95a6 | ||
|
75bca39bf6 | ||
|
3360e4d0fe | ||
|
521fcd0ba2 | ||
|
fc113425cb | ||
|
81a76a9632 |
@@ -5,8 +5,8 @@ name = "pypi"
|
||||
|
||||
[packages]
|
||||
# Flask
|
||||
Flask = "==2.3.2"
|
||||
Werkzeug = ">=2.3.6"
|
||||
Flask = "==2.2.5"
|
||||
Werkzeug = "==2.2.3"
|
||||
click = ">=5.0"
|
||||
# Api
|
||||
Flask-RESTful = "==0.3.10"
|
||||
@@ -15,6 +15,7 @@ Flask-SQLAlchemy = "==2.5.0"
|
||||
SQLAlchemy = "==1.4.49"
|
||||
PyMySQL = "==1.1.0"
|
||||
redis = "==4.6.0"
|
||||
python-redis-lock = "==4.0.0"
|
||||
# Migrations
|
||||
Flask-Migrate = "==2.5.2"
|
||||
# Deployment
|
||||
|
@@ -50,7 +50,7 @@ def add_user():
|
||||
|
||||
if is_admin:
|
||||
app = AppCache.get('acl') or App.create(name='acl')
|
||||
acl_admin = RoleCache.get('acl_admin') or RoleCRUD.add_role('acl_admin', app.id, True)
|
||||
acl_admin = RoleCache.get_by_name(app.id, 'acl_admin') or RoleCRUD.add_role('acl_admin', app.id, True)
|
||||
rid = RoleCache.get_by_name(None, username).id
|
||||
|
||||
RoleRelationCRUD.add(acl_admin, acl_admin.id, [rid], app.id)
|
||||
|
@@ -115,6 +115,8 @@ def cmdb_init_acl():
|
||||
_app = AppCache.get('cmdb') or App.create(name='cmdb')
|
||||
app_id = _app.id
|
||||
|
||||
current_app.test_request_context().push()
|
||||
|
||||
# 1. add resource type
|
||||
for resource_type in ResourceTypeEnum.all():
|
||||
try:
|
||||
@@ -183,11 +185,21 @@ def cmdb_counter():
|
||||
UserCRUD.add(username='worker', password=uuid.uuid4().hex, email='worker@xxx.com')
|
||||
|
||||
login_user(UserCache.get('worker'))
|
||||
|
||||
i = 0
|
||||
while True:
|
||||
try:
|
||||
db.session.remove()
|
||||
|
||||
CMDBCounterCache.reset()
|
||||
|
||||
if i % 5 == 0:
|
||||
CMDBCounterCache.flush_adc_counter()
|
||||
i = 0
|
||||
|
||||
CMDBCounterCache.flush_sub_counter()
|
||||
|
||||
i += 1
|
||||
except:
|
||||
import traceback
|
||||
print(traceback.format_exc())
|
||||
|
@@ -4,7 +4,7 @@ from flask.cli import with_appcontext
|
||||
from werkzeug.datastructures import MultiDict
|
||||
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.common_setting.employee import EmployeeAddForm
|
||||
from api.lib.common_setting.employee import EmployeeAddForm, GrantEmployeeACLPerm
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.models.common_setting import Employee, Department
|
||||
|
||||
@@ -158,50 +158,11 @@ class InitDepartment(object):
|
||||
|
||||
def init_backend_resource(self):
|
||||
acl = self.check_app('backend')
|
||||
resources_types = acl.get_all_resources_types()
|
||||
|
||||
perms = ['read', 'grant', 'delete', 'update']
|
||||
|
||||
acl_rid = self.get_admin_user_rid()
|
||||
|
||||
results = list(filter(lambda t: t['name'] == '操作权限', resources_types['groups']))
|
||||
if len(results) == 0:
|
||||
payload = dict(
|
||||
app_id=acl.app_name,
|
||||
name='操作权限',
|
||||
description='',
|
||||
perms=perms
|
||||
)
|
||||
resource_type = acl.create_resources_type(payload)
|
||||
else:
|
||||
resource_type = results[0]
|
||||
resource_type_id = resource_type['id']
|
||||
existed_perms = resources_types.get('id2perms', {}).get(resource_type_id, [])
|
||||
existed_perms = [p['name'] for p in existed_perms]
|
||||
new_perms = []
|
||||
for perm in perms:
|
||||
if perm not in existed_perms:
|
||||
new_perms.append(perm)
|
||||
if len(new_perms) > 0:
|
||||
resource_type['perms'] = existed_perms + new_perms
|
||||
acl.update_resources_type(resource_type_id, resource_type)
|
||||
|
||||
resource_list = acl.get_resource_by_type(None, None, resource_type['id'])
|
||||
|
||||
for name in ['公司信息', '公司架构', '通知设置']:
|
||||
target = list(filter(lambda r: r['name'] == name, resource_list))
|
||||
if len(target) == 0:
|
||||
payload = dict(
|
||||
type_id=resource_type['id'],
|
||||
app_id=acl.app_name,
|
||||
name=name,
|
||||
)
|
||||
resource = acl.create_resource(payload)
|
||||
else:
|
||||
resource = target[0]
|
||||
|
||||
if acl_rid > 0:
|
||||
acl.grant_resource(acl_rid, resource['id'], perms)
|
||||
if acl_rid == 0:
|
||||
return
|
||||
GrantEmployeeACLPerm(acl).grant_by_rid(acl_rid, True)
|
||||
|
||||
@staticmethod
|
||||
def check_app(app_name):
|
||||
|
@@ -89,6 +89,19 @@ def db_setup():
|
||||
"""
|
||||
db.create_all()
|
||||
|
||||
try:
|
||||
db.session.execute("set global sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,"
|
||||
"ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'")
|
||||
db.session.commit()
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
db.session.execute("set global tidb_enable_noop_functions='ON'")
|
||||
db.session.commit()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@click.group()
|
||||
def translate():
|
||||
|
@@ -330,8 +330,8 @@ class AutoDiscoveryCICRUD(DBMixin):
|
||||
|
||||
@staticmethod
|
||||
def get_attributes_by_type_id(type_id):
|
||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
attributes = [i[1] for i in CITypeAttributesCache.get2(type_id) or []]
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
attributes = [i[1] for i in CITypeAttributeManager.get_all_attributes(type_id) or []]
|
||||
|
||||
attr_names = set()
|
||||
adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id)
|
||||
|
@@ -5,10 +5,14 @@ from __future__ import unicode_literals
|
||||
from flask import current_app
|
||||
|
||||
from api.extensions import cache
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.custom_dashboard import CustomDashboardManager
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIType
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
from api.models.cmdb import PreferenceTreeView
|
||||
from api.models.cmdb import RelationType
|
||||
|
||||
|
||||
@@ -226,7 +230,9 @@ class CITypeAttributeCache(object):
|
||||
|
||||
|
||||
class CMDBCounterCache(object):
|
||||
KEY = 'CMDB::Counter'
|
||||
KEY = 'CMDB::Counter::dashboard'
|
||||
KEY2 = 'CMDB::Counter::adc'
|
||||
KEY3 = 'CMDB::Counter::sub'
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
@@ -303,7 +309,7 @@ class CMDBCounterCache(object):
|
||||
|
||||
s = RelSearch([i[0] for i in type_id_names], level, other_filer or '')
|
||||
try:
|
||||
stats = s.statistics(type_ids)
|
||||
stats = s.statistics(type_ids, need_filter=False)
|
||||
except SearchError as e:
|
||||
current_app.logger.error(e)
|
||||
return
|
||||
@@ -429,3 +435,47 @@ class CMDBCounterCache(object):
|
||||
return
|
||||
|
||||
return numfound
|
||||
|
||||
@classmethod
|
||||
def flush_adc_counter(cls):
|
||||
res = db.session.query(CI.type_id, CI.is_auto_discovery)
|
||||
result = dict()
|
||||
for i in res:
|
||||
result.setdefault(i.type_id, dict(total=0, auto_discovery=0))
|
||||
result[i.type_id]['total'] += 1
|
||||
if i.is_auto_discovery:
|
||||
result[i.type_id]['auto_discovery'] += 1
|
||||
|
||||
cache.set(cls.KEY2, result, timeout=0)
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_adc_counter(cls):
|
||||
return cache.get(cls.KEY2) or cls.flush_adc_counter()
|
||||
|
||||
@classmethod
|
||||
def flush_sub_counter(cls):
|
||||
result = dict(type_id2users=dict())
|
||||
|
||||
types = db.session.query(PreferenceShowAttributes.type_id,
|
||||
PreferenceShowAttributes.uid, PreferenceShowAttributes.created_at).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
||||
PreferenceShowAttributes.uid, PreferenceShowAttributes.type_id)
|
||||
for i in types:
|
||||
result['type_id2users'].setdefault(i.type_id, []).append(i.uid)
|
||||
|
||||
types = PreferenceTreeView.get_by(to_dict=False)
|
||||
for i in types:
|
||||
|
||||
result['type_id2users'].setdefault(i.type_id, [])
|
||||
if i.uid not in result['type_id2users'][i.type_id]:
|
||||
result['type_id2users'][i.type_id].append(i.uid)
|
||||
|
||||
cache.set(cls.KEY3, result, timeout=0)
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_sub_counter(cls):
|
||||
return cache.get(cls.KEY3) or cls.flush_sub_counter()
|
||||
|
@@ -6,6 +6,7 @@ import datetime
|
||||
import json
|
||||
import threading
|
||||
|
||||
import redis_lock
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
@@ -14,8 +15,8 @@ from werkzeug.exceptions import BadRequest
|
||||
from api.extensions import db
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.cache import CMDBCounterCache
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
@@ -45,14 +46,12 @@ from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import validate_permission
|
||||
from api.lib.secrets.inner import InnerCrypt
|
||||
from api.lib.secrets.vault import VaultClient
|
||||
from api.lib.utils import Lock
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.lib.webhook import webhook_request
|
||||
from api.models.cmdb import AttributeHistory
|
||||
from api.models.cmdb import AutoDiscoveryCI
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.models.cmdb import CITypeTrigger
|
||||
from api.tasks.cmdb import ci_cache
|
||||
@@ -61,8 +60,8 @@ from api.tasks.cmdb import ci_delete_trigger
|
||||
from api.tasks.cmdb import ci_relation_add
|
||||
from api.tasks.cmdb import ci_relation_cache
|
||||
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 = "******"
|
||||
|
||||
|
||||
@@ -218,15 +217,7 @@ class CIManager(object):
|
||||
|
||||
@classmethod
|
||||
def get_ad_statistics(cls):
|
||||
res = CI.get_by(to_dict=False)
|
||||
result = dict()
|
||||
for i in res:
|
||||
result.setdefault(i.type_id, dict(total=0, auto_discovery=0))
|
||||
result[i.type_id]['total'] += 1
|
||||
if i.is_auto_discovery:
|
||||
result[i.type_id]['auto_discovery'] += 1
|
||||
|
||||
return result
|
||||
return CMDBCounterCache.get_adc_counter()
|
||||
|
||||
@staticmethod
|
||||
def ci_is_exist(unique_key, unique_value, type_id):
|
||||
@@ -287,10 +278,10 @@ class CIManager(object):
|
||||
|
||||
@staticmethod
|
||||
def _auto_inc_id(attr):
|
||||
db.session.remove()
|
||||
db.session.commit()
|
||||
|
||||
value_table = TableMap(attr_name=attr.name).table
|
||||
with Lock("auto_inc_id_{}".format(attr.name), need_lock=True):
|
||||
with redis_lock.Lock(rd.r, "auto_inc_id_{}".format(attr.name)):
|
||||
max_v = value_table.get_by(attr_id=attr.id, only_query=True).order_by(
|
||||
getattr(value_table, 'value').desc()).first()
|
||||
if max_v is not None:
|
||||
@@ -317,14 +308,18 @@ class CIManager(object):
|
||||
"""
|
||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
ci_type = CITypeManager.check_is_existed(ci_type_name)
|
||||
raw_dict = copy.deepcopy(ci_dict)
|
||||
|
||||
unique_key = AttributeCache.get(ci_type.unique_id) or abort(
|
||||
400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id)))
|
||||
|
||||
unique_value = None
|
||||
if not (unique_key.default and unique_key.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and
|
||||
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 = CITypeAttributesCache.get2(ci_type_name)
|
||||
attrs = CITypeAttributeManager.get_all_attributes(ci_type.id)
|
||||
ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
|
||||
ci_type_attrs_alias = {attr.alias: attr for _, attr in attrs}
|
||||
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
||||
@@ -332,8 +327,15 @@ class CIManager(object):
|
||||
ci = None
|
||||
record_id = None
|
||||
password_dict = {}
|
||||
need_lock = current_user.username not in current_app.config.get('PRIVILEGED_USERS', PRIVILEGED_USERS)
|
||||
with Lock(ci_type_name, need_lock=need_lock):
|
||||
with redis_lock.Lock(rd.r, ci_type.name):
|
||||
db.session.commit()
|
||||
|
||||
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)
|
||||
if existed is not None:
|
||||
if exist_policy == ExistPolicy.REJECT:
|
||||
@@ -354,7 +356,8 @@ class CIManager(object):
|
||||
if attr.default.get('default') and attr.default.get('default') in (
|
||||
AttributeDefaultValueEnum.CREATED_AT, AttributeDefaultValueEnum.UPDATED_AT):
|
||||
ci_dict[attr.name] = now
|
||||
elif attr.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID:
|
||||
elif (attr.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and
|
||||
not ci_dict.get(attr.name)):
|
||||
ci_dict[attr.name] = cls._auto_inc_id(attr)
|
||||
elif ((attr.name not in ci_dict and attr.alias not in ci_dict) or (
|
||||
ci_dict.get(attr.name) is None and ci_dict.get(attr.alias) is None)):
|
||||
@@ -368,6 +371,8 @@ class CIManager(object):
|
||||
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
ci_dict[attr.name] = now
|
||||
|
||||
value_manager = AttributeValueManager()
|
||||
|
||||
computed_attrs = []
|
||||
for _, attr in attrs:
|
||||
if attr.is_computed:
|
||||
@@ -378,7 +383,8 @@ class CIManager(object):
|
||||
elif attr.alias in ci_dict:
|
||||
password_dict[attr.id] = ci_dict.pop(attr.alias)
|
||||
|
||||
value_manager = AttributeValueManager()
|
||||
if attr.re_check and password_dict.get(attr.id):
|
||||
value_manager.check_re(attr.re_check, password_dict[attr.id])
|
||||
|
||||
if computed_attrs:
|
||||
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
|
||||
@@ -386,7 +392,7 @@ class CIManager(object):
|
||||
cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
|
||||
|
||||
ref_ci_dict = dict()
|
||||
for k in ci_dict:
|
||||
for k in copy.deepcopy(ci_dict):
|
||||
if k.startswith("$") and "." in k:
|
||||
ref_ci_dict[k] = ci_dict[k]
|
||||
continue
|
||||
@@ -398,7 +404,10 @@ class CIManager(object):
|
||||
_attr_name = ((ci_type_attrs_name.get(k) and ci_type_attrs_name[k].name) or
|
||||
(ci_type_attrs_alias.get(k) and ci_type_attrs_alias[k].name))
|
||||
if limit_attrs and _attr_name not in limit_attrs:
|
||||
if k in raw_dict:
|
||||
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
|
||||
else:
|
||||
ci_dict.pop(k)
|
||||
|
||||
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name or k in ci_type_attrs_alias}
|
||||
|
||||
@@ -430,13 +439,17 @@ class CIManager(object):
|
||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
ci = self.confirm_ci_existed(ci_id)
|
||||
|
||||
attrs = CITypeAttributesCache.get2(ci.type_id)
|
||||
raw_dict = copy.deepcopy(ci_dict)
|
||||
|
||||
attrs = CITypeAttributeManager.get_all_attributes(ci.type_id)
|
||||
ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
|
||||
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
||||
for _, attr in attrs:
|
||||
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
ci_dict[attr.name] = now
|
||||
|
||||
value_manager = AttributeValueManager()
|
||||
|
||||
password_dict = dict()
|
||||
computed_attrs = list()
|
||||
for _, attr in attrs:
|
||||
@@ -448,7 +461,8 @@ class CIManager(object):
|
||||
elif attr.alias in ci_dict:
|
||||
password_dict[attr.id] = ci_dict.pop(attr.alias)
|
||||
|
||||
value_manager = AttributeValueManager()
|
||||
if attr.re_check and password_dict.get(attr.id):
|
||||
value_manager.check_re(attr.re_check, password_dict[attr.id])
|
||||
|
||||
if computed_attrs:
|
||||
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
|
||||
@@ -456,17 +470,21 @@ class CIManager(object):
|
||||
limit_attrs = self._valid_ci_for_no_read(ci) if not _is_admin else {}
|
||||
|
||||
record_id = None
|
||||
need_lock = current_user.username not in current_app.config.get('PRIVILEGED_USERS', PRIVILEGED_USERS)
|
||||
with Lock(ci.ci_type.name, need_lock=need_lock):
|
||||
with redis_lock.Lock(rd.r, ci.ci_type.name):
|
||||
db.session.commit()
|
||||
|
||||
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}
|
||||
key2attr = value_manager.valid_attr_value(ci_dict, ci.type_id, ci.id, ci_type_attrs_name,
|
||||
ci_attr2type_attr=ci_attr2type_attr)
|
||||
if limit_attrs:
|
||||
for k in ci_dict:
|
||||
for k in copy.deepcopy(ci_dict):
|
||||
if k not in limit_attrs:
|
||||
if k in raw_dict:
|
||||
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
|
||||
else:
|
||||
ci_dict.pop(k)
|
||||
|
||||
try:
|
||||
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr)
|
||||
@@ -514,8 +532,7 @@ class CIManager(object):
|
||||
|
||||
ci_delete_trigger.apply_async(args=(trigger, OperateType.DELETE, ci_dict), queue=CMDB_QUEUE)
|
||||
|
||||
attrs = CITypeAttribute.get_by(type_id=ci.type_id, to_dict=False)
|
||||
attrs = [AttributeCache.get(attr.attr_id) for attr in attrs]
|
||||
attrs = [i for _, i in CITypeAttributeManager.get_all_attributes(type_id=ci.type_id)]
|
||||
for attr in attrs:
|
||||
value_table = TableMap(attr=attr).table
|
||||
for item in value_table.get_by(ci_id=ci_id, to_dict=False):
|
||||
@@ -542,6 +559,7 @@ class CIManager(object):
|
||||
AttributeHistoryManger.add(None, ci_id, [(None, OperateType.DELETE, ci_dict, None)], ci.type_id)
|
||||
|
||||
ci_delete.apply_async(args=(ci_id,), queue=CMDB_QUEUE)
|
||||
delete_id_filter.apply_async(args=(ci_id,), queue=CMDB_QUEUE)
|
||||
|
||||
return ci_id
|
||||
|
||||
@@ -848,6 +866,20 @@ class CIRelationManager(object):
|
||||
|
||||
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
|
||||
def _sort_handler(sort_by, query_sql):
|
||||
|
||||
@@ -903,7 +935,7 @@ class CIRelationManager(object):
|
||||
|
||||
@staticmethod
|
||||
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
|
||||
db.session.remove()
|
||||
db.session.commit()
|
||||
if type_relation.constraint == ConstraintEnum.Many2Many:
|
||||
return
|
||||
|
||||
@@ -963,7 +995,7 @@ class CIRelationManager(object):
|
||||
else:
|
||||
type_relation = CITypeRelation.get_by_id(relation_type_id)
|
||||
|
||||
with Lock("ci_relation_add_{}_{}".format(first_ci.type_id, second_ci.type_id), need_lock=True):
|
||||
with redis_lock.Lock(rd.r, "ci_relation_add_{}_{}".format(first_ci.type_id, second_ci.type_id)):
|
||||
|
||||
cls._check_constraint(first_ci_id, first_ci.type_id, second_ci_id, second_ci.type_id, type_relation)
|
||||
|
||||
@@ -999,6 +1031,7 @@ class CIRelationManager(object):
|
||||
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)
|
||||
delete_id_filter.apply_async(args=(cr.second_ci_id,), queue=CMDB_QUEUE)
|
||||
|
||||
return cr_id
|
||||
|
||||
@@ -1010,9 +1043,13 @@ class CIRelationManager(object):
|
||||
to_dict=False,
|
||||
first=True)
|
||||
|
||||
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
|
||||
if cr is not None:
|
||||
cls.delete(cr.id)
|
||||
|
||||
return cr and cls.delete(cr.id)
|
||||
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
|
||||
|
||||
@classmethod
|
||||
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
|
||||
@@ -1053,7 +1090,7 @@ class CIRelationManager(object):
|
||||
class CITriggerManager(object):
|
||||
@staticmethod
|
||||
def get(type_id):
|
||||
db.session.remove()
|
||||
db.session.commit()
|
||||
return CITypeTrigger.get_by(type_id=type_id, to_dict=True)
|
||||
|
||||
@staticmethod
|
||||
|
@@ -42,10 +42,12 @@ from api.models.cmdb import CITypeAttributeGroup
|
||||
from api.models.cmdb import CITypeAttributeGroupItem
|
||||
from api.models.cmdb import CITypeGroup
|
||||
from api.models.cmdb import CITypeGroupItem
|
||||
from api.models.cmdb import CITypeInheritance
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.models.cmdb import CITypeTrigger
|
||||
from api.models.cmdb import CITypeUniqueConstraint
|
||||
from api.models.cmdb import CustomDashboard
|
||||
from api.models.cmdb import PreferenceCITypeOrder
|
||||
from api.models.cmdb import PreferenceRelationView
|
||||
from api.models.cmdb import PreferenceSearchOption
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
@@ -74,17 +76,23 @@ class CITypeManager(object):
|
||||
|
||||
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
|
||||
def get_ci_types(type_name=None):
|
||||
def get_ci_types(type_name=None, like=True):
|
||||
resources = None
|
||||
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
|
||||
resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.CI_TYPE)])
|
||||
|
||||
ci_types = CIType.get_by() if type_name is None else CIType.get_by_like(name=type_name)
|
||||
ci_types = CIType.get_by() if type_name is None else (
|
||||
CIType.get_by_like(name=type_name) if like else CIType.get_by(name=type_name))
|
||||
res = list()
|
||||
for type_dict in ci_types:
|
||||
attr = AttributeCache.get(type_dict["unique_id"])
|
||||
type_dict["unique_key"] = attr and attr.name
|
||||
type_dict['parent_ids'] = CITypeInheritanceManager.get_parents(type_dict['id'])
|
||||
if resources is None or type_dict['name'] in resources:
|
||||
res.append(type_dict)
|
||||
|
||||
@@ -131,8 +139,13 @@ class CITypeManager(object):
|
||||
|
||||
kwargs["unique_id"] = unique_key.id
|
||||
kwargs['uid'] = current_user.uid
|
||||
|
||||
parent_ids = kwargs.pop('parent_ids', None)
|
||||
|
||||
ci_type = CIType.create(**kwargs)
|
||||
|
||||
CITypeInheritanceManager.add(parent_ids, ci_type.id)
|
||||
|
||||
CITypeAttributeManager.add(ci_type.id, [unique_key.id], is_required=True)
|
||||
|
||||
CITypeCache.clean(ci_type.name)
|
||||
@@ -214,21 +227,29 @@ class CITypeManager(object):
|
||||
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))
|
||||
|
||||
for item in CITypeRelation.get_by(parent_id=type_id, to_dict=False):
|
||||
item.soft_delete(commit=False)
|
||||
for item in (CITypeRelation.get_by(parent_id=type_id, to_dict=False) +
|
||||
CITypeRelation.get_by(child_id=type_id, to_dict=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)
|
||||
|
||||
for table in [PreferenceTreeView, PreferenceShowAttributes, PreferenceSearchOption, CustomDashboard,
|
||||
CITypeGroupItem, CITypeAttributeGroup, CITypeAttribute, CITypeUniqueConstraint, CITypeTrigger,
|
||||
AutoDiscoveryCIType, CIFilterPerms]:
|
||||
AutoDiscoveryCIType, CIFilterPerms, PreferenceCITypeOrder]:
|
||||
for item in table.get_by(type_id=type_id, to_dict=False):
|
||||
item.soft_delete(commit=False)
|
||||
|
||||
for item in AutoDiscoveryCI.get_by(type_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
for item in CITypeInheritance.get_by(parent_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
ci_type.soft_delete()
|
||||
@@ -241,6 +262,100 @@ class CITypeManager(object):
|
||||
ACLManager().del_resource(ci_type.name, ResourceTypeEnum.CI)
|
||||
|
||||
|
||||
class CITypeInheritanceManager(object):
|
||||
cls = CITypeInheritance
|
||||
|
||||
@classmethod
|
||||
def get_parents(cls, type_id):
|
||||
return [i.parent_id for i in cls.cls.get_by(child_id=type_id, to_dict=False)]
|
||||
|
||||
@classmethod
|
||||
def recursive_children(cls, type_id):
|
||||
result = []
|
||||
|
||||
def _get_child(_id):
|
||||
children = [i.child_id for i in cls.cls.get_by(parent_id=_id, to_dict=False)]
|
||||
result.extend(children)
|
||||
for child_id in children:
|
||||
_get_child(child_id)
|
||||
|
||||
_get_child(type_id)
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def base(cls, type_id):
|
||||
result = []
|
||||
q = []
|
||||
|
||||
def _get_parents(_type_id):
|
||||
parents = [i.parent_id for i in cls.cls.get_by(child_id=_type_id, to_dict=False)]
|
||||
for i in parents[::-1]:
|
||||
q.append(i)
|
||||
try:
|
||||
out = q.pop(0)
|
||||
except IndexError:
|
||||
return
|
||||
|
||||
result.append(out)
|
||||
|
||||
_get_parents(out)
|
||||
|
||||
_get_parents(type_id)
|
||||
|
||||
return result[::-1]
|
||||
|
||||
@classmethod
|
||||
def add(cls, parent_ids, child_id):
|
||||
|
||||
rels = {}
|
||||
for i in cls.cls.get_by(to_dict=False):
|
||||
rels.setdefault(i.child_id, set()).add(i.parent_id)
|
||||
|
||||
try:
|
||||
toposort_flatten(rels)
|
||||
except toposort.CircularDependencyError as e:
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.circular_dependency_error)
|
||||
|
||||
for parent_id in parent_ids or []:
|
||||
if parent_id == child_id:
|
||||
return abort(400, ErrFormat.circular_dependency_error)
|
||||
|
||||
existed = cls.cls.get_by(parent_id=parent_id, child_id=child_id, first=True, to_dict=False)
|
||||
if existed is None:
|
||||
rels.setdefault(child_id, set()).add(parent_id)
|
||||
try:
|
||||
toposort_flatten(rels)
|
||||
except toposort.CircularDependencyError as e:
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.circular_dependency_error)
|
||||
|
||||
cls.cls.create(parent_id=parent_id, child_id=child_id, commit=False)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
@classmethod
|
||||
def delete(cls, parent_id, child_id):
|
||||
|
||||
existed = cls.cls.get_by(parent_id=parent_id, child_id=child_id, first=True, to_dict=False)
|
||||
|
||||
if existed is not None:
|
||||
children = cls.recursive_children(child_id) + [child_id]
|
||||
for _id in children:
|
||||
if CI.get_by(type_id=_id, to_dict=False, first=True) is not None:
|
||||
return abort(400, ErrFormat.ci_exists_and_cannot_delete_inheritance)
|
||||
|
||||
attr_ids = set([i.id for _, i in CITypeAttributeManager.get_all_attributes(parent_id)])
|
||||
for _id in children:
|
||||
for attr_id in attr_ids:
|
||||
for i in PreferenceShowAttributes.get_by(type_id=_id, attr_id=attr_id, to_dict=False):
|
||||
i.soft_delete(commit=False)
|
||||
db.session.commit()
|
||||
|
||||
existed.soft_delete()
|
||||
|
||||
|
||||
class CITypeGroupManager(object):
|
||||
cls = CITypeGroup
|
||||
|
||||
@@ -261,6 +376,7 @@ class CITypeGroupManager(object):
|
||||
ci_type = CITypeCache.get(t['type_id']).to_dict()
|
||||
if resources is None or (ci_type and ci_type['name'] in resources):
|
||||
ci_type['permissions'] = resources[ci_type['name']] if resources is not None else None
|
||||
ci_type['inherited'] = True if CITypeInheritanceManager.get_parents(ci_type['id']) else False
|
||||
group.setdefault("ci_types", []).append(ci_type)
|
||||
group_types.add(t["type_id"])
|
||||
|
||||
@@ -270,6 +386,7 @@ class CITypeGroupManager(object):
|
||||
for ci_type in ci_types:
|
||||
if ci_type["id"] not in group_types and (resources is None or ci_type['name'] in resources):
|
||||
ci_type['permissions'] = resources.get(ci_type['name']) if resources is not None else None
|
||||
ci_type['inherited'] = True if CITypeInheritanceManager.get_parents(ci_type['id']) else False
|
||||
other_types['ci_types'].append(ci_type)
|
||||
|
||||
groups.append(other_types)
|
||||
@@ -361,40 +478,62 @@ class CITypeAttributeManager(object):
|
||||
return attr.name
|
||||
|
||||
@staticmethod
|
||||
def get_attr_names_by_type_id(type_id):
|
||||
return [AttributeCache.get(attr.attr_id).name for attr in CITypeAttributesCache.get(type_id)]
|
||||
def get_all_attributes(type_id):
|
||||
parent_ids = CITypeInheritanceManager.base(type_id)
|
||||
|
||||
result = []
|
||||
for _type_id in parent_ids + [type_id]:
|
||||
result.extend(CITypeAttributesCache.get2(_type_id))
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_attr_names_by_type_id(cls, type_id):
|
||||
return [attr.name for _, attr in cls.get_all_attributes(type_id)]
|
||||
|
||||
@staticmethod
|
||||
def get_attributes_by_type_id(type_id, choice_web_hook_parse=True, choice_other_parse=True):
|
||||
has_config_perm = ACLManager('cmdb').has_permission(
|
||||
CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG)
|
||||
|
||||
attrs = CITypeAttributesCache.get(type_id)
|
||||
parent_ids = CITypeInheritanceManager.base(type_id)
|
||||
|
||||
result = list()
|
||||
id2pos = dict()
|
||||
type2name = {i: CITypeCache.get(i) for i in parent_ids}
|
||||
for _type_id in parent_ids + [type_id]:
|
||||
attrs = CITypeAttributesCache.get(_type_id)
|
||||
for attr in sorted(attrs, key=lambda x: (x.order, x.id)):
|
||||
attr_dict = AttributeManager().get_attribute(attr.attr_id, choice_web_hook_parse, choice_other_parse)
|
||||
attr_dict["is_required"] = attr.is_required
|
||||
attr_dict["order"] = attr.order
|
||||
attr_dict["default_show"] = attr.default_show
|
||||
attr_dict["inherited"] = False if _type_id == type_id else True
|
||||
attr_dict["inherited_from"] = type2name.get(_type_id) and type2name[_type_id].alias
|
||||
if not has_config_perm:
|
||||
attr_dict.pop('choice_web_hook', None)
|
||||
attr_dict.pop('choice_other', None)
|
||||
|
||||
if attr_dict['id'] not in id2pos:
|
||||
id2pos[attr_dict['id']] = len(result)
|
||||
result.append(attr_dict)
|
||||
else:
|
||||
result[id2pos[attr_dict['id']]] = attr_dict
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def get_common_attributes(type_ids):
|
||||
@classmethod
|
||||
def get_common_attributes(cls, type_ids):
|
||||
has_config_perm = False
|
||||
for type_id in type_ids:
|
||||
has_config_perm |= ACLManager('cmdb').has_permission(
|
||||
CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG)
|
||||
|
||||
result = CITypeAttribute.get_by(__func_in___key_type_id=list(map(int, type_ids)), to_dict=False)
|
||||
result = {type_id: [i for _, i in cls.get_all_attributes(type_id)] for type_id in type_ids}
|
||||
attr2types = {}
|
||||
for i in result:
|
||||
attr2types.setdefault(i.attr_id, []).append(i.type_id)
|
||||
for type_id in result:
|
||||
for i in result[type_id]:
|
||||
attr2types.setdefault(i.id, []).append(type_id)
|
||||
|
||||
attrs = []
|
||||
for attr_id in attr2types:
|
||||
@@ -511,10 +650,30 @@ class CITypeAttributeManager(object):
|
||||
existed.soft_delete()
|
||||
|
||||
for ci in CI.get_by(type_id=type_id, to_dict=False):
|
||||
AttributeValueManager.delete_attr_value(attr_id, ci.id)
|
||||
AttributeValueManager.delete_attr_value(attr_id, ci.id, commit=False)
|
||||
|
||||
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)
|
||||
|
||||
CITypeHistoryManager.add(CITypeOperateType.DELETE_ATTRIBUTE, type_id, attr_id=attr.id,
|
||||
@@ -528,8 +687,18 @@ class CITypeAttributeManager(object):
|
||||
attr_id = _from.get('attr_id')
|
||||
from_group_id = _from.get('group_id')
|
||||
to_group_id = _to.get('group_id')
|
||||
from_group_name = _from.get('group_name')
|
||||
to_group_name = _to.get('group_name')
|
||||
order = _to.get('order')
|
||||
|
||||
if from_group_name:
|
||||
from_group = CITypeAttributeGroup.get_by(type_id=type_id, name=from_group_name, first=True, to_dict=False)
|
||||
from_group_id = from_group and from_group.id
|
||||
|
||||
if to_group_name:
|
||||
to_group = CITypeAttributeGroup.get_by(type_id=type_id, name=to_group_name, first=True, to_dict=False)
|
||||
to_group_id = to_group and to_group.id
|
||||
|
||||
if from_group_id != to_group_id:
|
||||
if from_group_id is not None:
|
||||
CITypeAttributeGroupManager.delete_item(from_group_id, attr_id)
|
||||
@@ -655,15 +824,15 @@ class CITypeRelationManager(object):
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.circular_dependency_error)
|
||||
|
||||
if constraint == ConstraintEnum.Many2Many:
|
||||
other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
|
||||
to_dict=False, first=True)
|
||||
other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
|
||||
to_dict=False, first=True)
|
||||
if other_c and other_c.child_id != c.id:
|
||||
return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
|
||||
if other_p and other_p.parent_id != p.id:
|
||||
return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
|
||||
# if constraint == ConstraintEnum.Many2Many:
|
||||
# other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
|
||||
# to_dict=False, first=True)
|
||||
# other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
|
||||
# to_dict=False, first=True)
|
||||
# if other_c and other_c.child_id != c.id:
|
||||
# return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
|
||||
# if other_p and other_p.parent_id != p.id:
|
||||
# return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
|
||||
|
||||
existed = cls._get(p.id, c.id)
|
||||
if existed is not None:
|
||||
@@ -738,25 +907,66 @@ class CITypeAttributeGroupManager(object):
|
||||
|
||||
@staticmethod
|
||||
def get_by_type_id(type_id, need_other=False):
|
||||
groups = CITypeAttributeGroup.get_by(type_id=type_id)
|
||||
groups = sorted(groups, key=lambda x: x["order"] or 0)
|
||||
grouped = list()
|
||||
parent_ids = CITypeInheritanceManager.base(type_id)
|
||||
|
||||
groups = []
|
||||
id2type = {i: CITypeCache.get(i).alias for i in parent_ids}
|
||||
for _type_id in parent_ids + [type_id]:
|
||||
_groups = CITypeAttributeGroup.get_by(type_id=_type_id)
|
||||
_groups = sorted(_groups, key=lambda x: x["order"] or 0)
|
||||
for i in _groups:
|
||||
if type_id != _type_id:
|
||||
i['inherited'] = True
|
||||
i['inherited_from'] = id2type[_type_id]
|
||||
else:
|
||||
i['inherited'] = False
|
||||
|
||||
groups.extend(_groups)
|
||||
|
||||
grouped = set()
|
||||
|
||||
attributes = CITypeAttributeManager.get_attributes_by_type_id(type_id)
|
||||
id2attr = {i.get('id'): i for i in attributes}
|
||||
|
||||
group2pos = dict()
|
||||
attr2pos = dict()
|
||||
result = []
|
||||
for group in groups:
|
||||
items = CITypeAttributeGroupItem.get_by(group_id=group["id"], to_dict=False)
|
||||
items = sorted(items, key=lambda x: x.order or 0)
|
||||
group["attributes"] = [id2attr.get(i.attr_id) for i in items if i.attr_id in id2attr]
|
||||
grouped.extend([i.attr_id for i in items])
|
||||
|
||||
if group['name'] not in group2pos:
|
||||
group_pos = len(result)
|
||||
group['attributes'] = []
|
||||
result.append(group)
|
||||
|
||||
group2pos[group['name']] = group_pos
|
||||
else:
|
||||
group_pos = group2pos[group['name']]
|
||||
|
||||
attr = None
|
||||
for i in items:
|
||||
if i.attr_id in id2attr:
|
||||
attr = id2attr[i.attr_id]
|
||||
attr['inherited'] = group['inherited']
|
||||
attr['inherited_from'] = group.get('inherited_from')
|
||||
result[group_pos]['attributes'].append(attr)
|
||||
|
||||
if i.attr_id in attr2pos:
|
||||
result[attr2pos[i.attr_id][0]]['attributes'].remove(attr2pos[i.attr_id][1])
|
||||
|
||||
attr2pos[i.attr_id] = [group_pos, attr]
|
||||
|
||||
group.pop('inherited_from', None)
|
||||
|
||||
grouped |= set([i.attr_id for i in items])
|
||||
|
||||
if need_other:
|
||||
grouped = set(grouped)
|
||||
other_attributes = [attr for attr in attributes if attr["id"] not in grouped]
|
||||
groups.append(dict(attributes=other_attributes))
|
||||
result.append(dict(attributes=other_attributes))
|
||||
|
||||
return groups
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def create_or_update(type_id, name, attr_order, group_order=0, is_update=False):
|
||||
@@ -890,10 +1100,16 @@ class CITypeAttributeGroupManager(object):
|
||||
@classmethod
|
||||
def transfer(cls, type_id, _from, _to):
|
||||
current_app.logger.info("CIType[{0}] {1} -> {2}".format(type_id, _from, _to))
|
||||
if isinstance(_from, int):
|
||||
from_group = CITypeAttributeGroup.get_by_id(_from)
|
||||
else:
|
||||
from_group = CITypeAttributeGroup.get_by(name=_from, first=True, to_dict=False)
|
||||
from_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_from)))
|
||||
|
||||
if isinstance(_to, int):
|
||||
to_group = CITypeAttributeGroup.get_by_id(_to)
|
||||
else:
|
||||
to_group = CITypeAttributeGroup.get_by(name=_to, first=True, to_dict=False)
|
||||
to_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_to)))
|
||||
|
||||
from_order, to_order = from_group.order, to_group.order
|
||||
@@ -1274,7 +1490,7 @@ class CITypeTemplateManager(object):
|
||||
from api.lib.common_setting.upload_file import CommonFileCRUD
|
||||
|
||||
tpt = dict(
|
||||
ci_types=CITypeManager.get_ci_types(type_name=ci_type.name),
|
||||
ci_types=CITypeManager.get_ci_types(type_name=ci_type.name, like=False),
|
||||
ci_type_auto_discovery_rules=list(),
|
||||
type2attributes=dict(),
|
||||
type2attribute_group=dict(),
|
||||
|
@@ -1,12 +1,15 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import copy
|
||||
import functools
|
||||
|
||||
import redis_lock
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
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.resp_format import ErrFormat
|
||||
from api.lib.mixin import DBMixin
|
||||
@@ -40,6 +43,11 @@ class CIFilterPermsCRUD(DBMixin):
|
||||
result[i['rid']]['ci_filter'] = ""
|
||||
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
|
||||
|
||||
def get_by_ids(self, _ids, type_id=None):
|
||||
@@ -70,6 +78,11 @@ class CIFilterPermsCRUD(DBMixin):
|
||||
result[i['type_id']]['ci_filter'] = ""
|
||||
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
|
||||
|
||||
@classmethod
|
||||
@@ -82,6 +95,54 @@ class CIFilterPermsCRUD(DBMixin):
|
||||
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 []
|
||||
|
||||
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):
|
||||
ci_filter = kwargs.get('ci_filter')
|
||||
attr_filter = kwargs.get('attr_filter') or ""
|
||||
@@ -102,25 +163,58 @@ class CIFilterPermsCRUD(DBMixin):
|
||||
|
||||
def add(self, **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():
|
||||
key = ",".join(([v['parent_path']] if v.get('parent_path') else []) + [str(_id)])
|
||||
request_id_filter[key] = v['name']
|
||||
|
||||
else:
|
||||
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:
|
||||
|
||||
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)
|
||||
ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER, rebuild=False)
|
||||
|
||||
obj.soft_delete()
|
||||
|
||||
else:
|
||||
if not kwargs.get('ci_filter') and not kwargs.get('attr_filter'):
|
||||
if not is_recursive and request_id_filter:
|
||||
self._revoke_children(obj.rid, request_id_filter, rebuild=False)
|
||||
|
||||
return
|
||||
|
||||
else:
|
||||
if not kwargs.get('ci_filter') and not kwargs.get('attr_filter') and not kwargs.get('id_filter'):
|
||||
return
|
||||
|
||||
if request_id_filter:
|
||||
kwargs['id_filter'] = request_id_filter
|
||||
|
||||
obj = self.cls.create(**kwargs)
|
||||
|
||||
if current_app.config.get('USE_ACL'):
|
||||
if current_app.config.get('USE_ACL'): # new resource
|
||||
try:
|
||||
ACLManager().add_resource(obj.id, ResourceTypeEnum.CI_FILTER)
|
||||
except:
|
||||
@@ -138,8 +232,10 @@ class CIFilterPermsCRUD(DBMixin):
|
||||
pass
|
||||
|
||||
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'),
|
||||
rid=kwargs.get('rid'),
|
||||
id_filter=None,
|
||||
first=True, to_dict=False)
|
||||
|
||||
if obj is not None:
|
||||
@@ -151,6 +247,69 @@ class CIFilterPermsCRUD(DBMixin):
|
||||
|
||||
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']
|
||||
|
||||
resource = None
|
||||
if obj is not None:
|
||||
|
||||
id_filter = {}
|
||||
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
|
||||
|
||||
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 decorator_has_perm(func):
|
||||
|
@@ -14,6 +14,8 @@ from api.lib.cmdb.attribute import AttributeManager
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.cache import CMDBCounterCache
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
from api.lib.cmdb.const import ConstraintEnum
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
@@ -24,6 +26,7 @@ from api.lib.exception import AbortException
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.models.cmdb import PreferenceCITypeOrder
|
||||
from api.models.cmdb import PreferenceRelationView
|
||||
from api.models.cmdb import PreferenceSearchOption
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
@@ -38,13 +41,22 @@ class PreferenceManager(object):
|
||||
|
||||
@staticmethod
|
||||
def get_types(instance=False, tree=False):
|
||||
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, to_dict=False), key=lambda x: x.order)
|
||||
|
||||
types = db.session.query(PreferenceShowAttributes.type_id).filter(
|
||||
PreferenceShowAttributes.uid == current_user.uid).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
||||
PreferenceShowAttributes.type_id).all() if instance else []
|
||||
types = sorted(types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
|
||||
ci_type_order) if not i.is_tree}.get(x.type_id, 1))
|
||||
|
||||
tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else []
|
||||
type_ids = set([i.type_id for i in types + tree_types])
|
||||
tree_types = sorted(tree_types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
|
||||
ci_type_order) if i.is_tree}.get(x.type_id, 1))
|
||||
|
||||
type_ids = [i.type_id for i in types + tree_types]
|
||||
if types and tree_types:
|
||||
type_ids = set(type_ids)
|
||||
|
||||
return [CITypeCache.get(type_id).to_dict() for type_id in type_ids]
|
||||
|
||||
@@ -59,32 +71,36 @@ class PreferenceManager(object):
|
||||
:param tree:
|
||||
:return:
|
||||
"""
|
||||
result = dict(self=dict(instance=[], tree=[], type_id2subs_time=dict()),
|
||||
type_id2users=dict())
|
||||
result = dict(self=dict(instance=[], tree=[], type_id2subs_time=dict()))
|
||||
|
||||
result.update(CMDBCounterCache.get_sub_counter())
|
||||
|
||||
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, to_dict=False), key=lambda x: x.order)
|
||||
if instance:
|
||||
types = db.session.query(PreferenceShowAttributes.type_id,
|
||||
PreferenceShowAttributes.uid, PreferenceShowAttributes.created_at).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
||||
PreferenceShowAttributes.deleted.is_(False)).filter(
|
||||
PreferenceShowAttributes.uid == current_user.uid).group_by(
|
||||
PreferenceShowAttributes.uid, PreferenceShowAttributes.type_id)
|
||||
for i in types:
|
||||
if i.uid == current_user.uid:
|
||||
result['self']['instance'].append(i.type_id)
|
||||
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
|
||||
result['self']['type_id2subs_time'][i.type_id] = i.created_at
|
||||
|
||||
result['type_id2users'].setdefault(i.type_id, []).append(i.uid)
|
||||
instance_order = [i.type_id for i in ci_type_order if not i.is_tree]
|
||||
if len(instance_order) == len(result['self']['instance']):
|
||||
result['self']['instance'] = instance_order
|
||||
|
||||
if tree:
|
||||
types = PreferenceTreeView.get_by(to_dict=False)
|
||||
types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False)
|
||||
for i in types:
|
||||
if i.uid == current_user.uid:
|
||||
result['self']['tree'].append(i.type_id)
|
||||
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
|
||||
result['self']['type_id2subs_time'][i.type_id] = i.created_at
|
||||
|
||||
result['type_id2users'].setdefault(i.type_id, [])
|
||||
if i.uid not in result['type_id2users'][i.type_id]:
|
||||
result['type_id2users'][i.type_id].append(i.uid)
|
||||
tree_order = [i.type_id for i in ci_type_order if i.is_tree]
|
||||
if len(tree_order) == len(result['self']['tree']):
|
||||
result['self']['tree'] = tree_order
|
||||
|
||||
return result
|
||||
|
||||
@@ -98,8 +114,8 @@ class PreferenceManager(object):
|
||||
CITypeAttribute, CITypeAttribute.attr_id == PreferenceShowAttributes.attr_id).filter(
|
||||
PreferenceShowAttributes.uid == current_user.uid).filter(
|
||||
PreferenceShowAttributes.type_id == type_id).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).filter(
|
||||
CITypeAttribute.type_id == type_id).all()
|
||||
PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).group_by(
|
||||
CITypeAttribute.attr_id).all()
|
||||
|
||||
result = []
|
||||
for i in sorted(attrs, key=lambda x: x.PreferenceShowAttributes.order):
|
||||
@@ -109,17 +125,16 @@ class PreferenceManager(object):
|
||||
|
||||
is_subscribed = True
|
||||
if not attrs:
|
||||
attrs = db.session.query(CITypeAttribute).filter(
|
||||
CITypeAttribute.type_id == type_id).filter(
|
||||
CITypeAttribute.deleted.is_(False)).filter(
|
||||
CITypeAttribute.default_show.is_(True)).order_by(CITypeAttribute.order)
|
||||
result = [i.attr.to_dict() for i in attrs]
|
||||
result = CITypeAttributeManager.get_attributes_by_type_id(type_id,
|
||||
choice_web_hook_parse=False,
|
||||
choice_other_parse=False)
|
||||
result = [i for i in result if i['default_show']]
|
||||
is_subscribed = False
|
||||
|
||||
for i in result:
|
||||
if i["is_choice"]:
|
||||
i.update(dict(choice_value=AttributeManager.get_choice_values(
|
||||
i["id"], i["value_type"], i["choice_web_hook"], i.get("choice_other"))))
|
||||
i["id"], i["value_type"], i.get("choice_web_hook"), i.get("choice_other"))))
|
||||
|
||||
return is_subscribed, result
|
||||
|
||||
@@ -151,9 +166,22 @@ class PreferenceManager(object):
|
||||
if i.attr_id not in attr_dict:
|
||||
i.soft_delete()
|
||||
|
||||
if not existed_all and attr_order:
|
||||
cls.add_ci_type_order_item(type_id, is_tree=False)
|
||||
|
||||
elif not PreferenceShowAttributes.get_by(type_id=type_id, uid=current_user.uid, to_dict=False):
|
||||
cls.delete_ci_type_order_item(type_id, is_tree=False)
|
||||
|
||||
@staticmethod
|
||||
def get_tree_view():
|
||||
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, is_tree=True, to_dict=False),
|
||||
key=lambda x: x.order)
|
||||
|
||||
res = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=True)
|
||||
if ci_type_order:
|
||||
res = sorted(res, key=lambda x: {ii.type_id: idx for idx, ii in enumerate(
|
||||
ci_type_order)}.get(x['type_id'], 1))
|
||||
|
||||
for item in res:
|
||||
if item["levels"]:
|
||||
ci_type = CITypeCache.get(item['type_id']).to_dict()
|
||||
@@ -172,8 +200,8 @@ class PreferenceManager(object):
|
||||
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def create_or_update_tree_view(type_id, levels):
|
||||
@classmethod
|
||||
def create_or_update_tree_view(cls, type_id, levels):
|
||||
attrs = CITypeAttributesCache.get(type_id)
|
||||
for idx, i in enumerate(levels):
|
||||
for attr in attrs:
|
||||
@@ -185,9 +213,12 @@ class PreferenceManager(object):
|
||||
if existed is not None:
|
||||
if not levels:
|
||||
existed.soft_delete()
|
||||
cls.delete_ci_type_order_item(type_id, is_tree=True)
|
||||
return existed
|
||||
return existed.update(levels=levels)
|
||||
elif levels:
|
||||
cls.add_ci_type_order_item(type_id, is_tree=True)
|
||||
|
||||
return PreferenceTreeView.create(levels=levels, type_id=type_id, uid=current_user.uid)
|
||||
|
||||
@staticmethod
|
||||
@@ -356,6 +387,9 @@ class PreferenceManager(object):
|
||||
for i in PreferenceTreeView.get_by(type_id=type_id, uid=uid, to_dict=False):
|
||||
i.soft_delete()
|
||||
|
||||
for i in PreferenceCITypeOrder.get_by(type_id=type_id, uid=uid, to_dict=False):
|
||||
i.soft_delete()
|
||||
|
||||
@staticmethod
|
||||
def can_edit_relation(parent_id, child_id):
|
||||
views = PreferenceRelationView.get_by(to_dict=False)
|
||||
@@ -381,3 +415,36 @@ class PreferenceManager(object):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def add_ci_type_order_item(type_id, is_tree=False):
|
||||
max_order = PreferenceCITypeOrder.get_by(
|
||||
uid=current_user.uid, is_tree=is_tree, only_query=True).order_by(PreferenceCITypeOrder.order.desc()).first()
|
||||
order = (max_order and max_order.order + 1) or 1
|
||||
|
||||
PreferenceCITypeOrder.create(type_id=type_id, is_tree=is_tree, uid=current_user.uid, order=order)
|
||||
|
||||
@staticmethod
|
||||
def delete_ci_type_order_item(type_id, is_tree=False):
|
||||
existed = PreferenceCITypeOrder.get_by(uid=current_user.uid, type_id=type_id, is_tree=is_tree,
|
||||
first=True, to_dict=False)
|
||||
|
||||
existed and existed.soft_delete()
|
||||
|
||||
@staticmethod
|
||||
def upsert_ci_type_order(type_ids, is_tree=False):
|
||||
for idx, type_id in enumerate(type_ids):
|
||||
order = idx + 1
|
||||
existed = PreferenceCITypeOrder.get_by(uid=current_user.uid, type_id=type_id, is_tree=is_tree,
|
||||
to_dict=False, first=True)
|
||||
if existed is not None:
|
||||
existed.update(order=order, flush=True)
|
||||
else:
|
||||
PreferenceCITypeOrder.create(uid=current_user.uid, type_id=type_id, is_tree=is_tree, order=order,
|
||||
flush=True)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("upsert citype order failed: {}".format(e))
|
||||
return abort(400, ErrFormat.unknown_error)
|
||||
|
@@ -60,6 +60,8 @@ class ErrFormat(CommonErrFormat):
|
||||
only_owner_can_delete = _l("Only the creator can delete it!") # 只有创建人才能删除它!
|
||||
ci_exists_and_cannot_delete_type = _l(
|
||||
"The model cannot be deleted because the CI already exists") # 因为CI已经存在,不能删除模型
|
||||
ci_exists_and_cannot_delete_inheritance = _l(
|
||||
"The inheritance cannot be deleted because the CI already exists") # 因为CI已经存在,不能删除继承关系
|
||||
|
||||
# 因为关系视图 {} 引用了该模型,不能删除模型
|
||||
ci_relation_view_exists_and_cannot_delete_type = _l(
|
||||
|
@@ -16,10 +16,11 @@ def search(query=None,
|
||||
ret_key=RetKey.NAME,
|
||||
count=1,
|
||||
sort=None,
|
||||
excludes=None):
|
||||
excludes=None,
|
||||
use_id_filter=True):
|
||||
if current_app.config.get("USE_ES"):
|
||||
s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
|
||||
else:
|
||||
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes)
|
||||
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes, use_id_filter=use_id_filter)
|
||||
|
||||
return s
|
||||
|
@@ -62,7 +62,7 @@ QUERY_CI_BY_ATTR_NAME = """
|
||||
QUERY_CI_BY_ID = """
|
||||
SELECT c_cis.id as ci_id
|
||||
FROM c_cis
|
||||
WHERE c_cis.id={}
|
||||
WHERE c_cis.id {}
|
||||
"""
|
||||
|
||||
QUERY_CI_BY_TYPE = """
|
||||
|
@@ -44,7 +44,10 @@ class Search(object):
|
||||
count=1,
|
||||
sort=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.fl = fl or []
|
||||
self.excludes = excludes or []
|
||||
@@ -54,12 +57,17 @@ class Search(object):
|
||||
self.count = count
|
||||
self.sort = sort
|
||||
self.ci_ids = ci_ids or []
|
||||
self.raw_ci_ids = copy.deepcopy(self.ci_ids)
|
||||
self.query_sql = ""
|
||||
self.type_id_list = []
|
||||
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.type2filter_perms = dict()
|
||||
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||
|
||||
@staticmethod
|
||||
def _operator_proc(key):
|
||||
@@ -106,7 +114,7 @@ class Search(object):
|
||||
self.type_id_list.append(str(ci_type.id))
|
||||
if ci_type.id in self.type2filter_perms:
|
||||
ci_filter = self.type2filter_perms[ci_type.id].get('ci_filter')
|
||||
if ci_filter:
|
||||
if ci_filter and self.use_ci_filter and not self.use_id_filter:
|
||||
sub = []
|
||||
ci_filter = Template(ci_filter).render(user=current_user)
|
||||
for i in ci_filter.split(','):
|
||||
@@ -122,6 +130,14 @@ class Search(object):
|
||||
self.fl = set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
||||
else:
|
||||
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:
|
||||
raise SearchError(ErrFormat.no_permission.format(ci_type.alias, PermEnum.READ))
|
||||
else:
|
||||
@@ -138,7 +154,10 @@ class Search(object):
|
||||
|
||||
@staticmethod
|
||||
def _id_query_handler(v):
|
||||
return QUERY_CI_BY_ID.format(v)
|
||||
if ";" in v:
|
||||
return QUERY_CI_BY_ID.format("in {}".format(v.replace(';', ',')))
|
||||
else:
|
||||
return QUERY_CI_BY_ID.format("= {}".format(v))
|
||||
|
||||
@staticmethod
|
||||
def _in_query_handler(attr, v, is_not):
|
||||
@@ -152,6 +171,7 @@ class Search(object):
|
||||
"NOT LIKE" if is_not else "LIKE",
|
||||
_v.replace("*", "%")) for _v in new_v])
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, in_query)
|
||||
|
||||
return _query_sql
|
||||
|
||||
@staticmethod
|
||||
@@ -167,6 +187,7 @@ class Search(object):
|
||||
"NOT BETWEEN" if is_not else "BETWEEN",
|
||||
start.replace("*", "%"), end.replace("*", "%"))
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, range_query)
|
||||
|
||||
return _query_sql
|
||||
|
||||
@staticmethod
|
||||
@@ -183,6 +204,7 @@ class Search(object):
|
||||
|
||||
comparison_query = "{0} '{1}'".format(v[0], v[1:].replace("*", "%"))
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, comparison_query)
|
||||
|
||||
return _query_sql
|
||||
|
||||
@staticmethod
|
||||
@@ -194,6 +216,7 @@ class Search(object):
|
||||
elif field.startswith("-"):
|
||||
field = field[1:]
|
||||
sort_type = "DESC"
|
||||
|
||||
return field, sort_type
|
||||
|
||||
def __sort_by_id(self, sort_type, query_sql):
|
||||
@@ -322,6 +345,11 @@ class Search(object):
|
||||
|
||||
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):
|
||||
"""
|
||||
:return: _type:(type1;type2)
|
||||
@@ -331,14 +359,23 @@ class Search(object):
|
||||
|
||||
self.valid_type_names = {i['name'] for i in res if PermEnum.READ in i['permissions']}
|
||||
|
||||
res2 = acl.get_resources(ResourceTypeEnum.CI_FILTER)
|
||||
if res2:
|
||||
self.type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
|
||||
self.__get_type2filter_perms()
|
||||
|
||||
for type_id in self.type2filter_perms:
|
||||
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))
|
||||
|
||||
def __confirm_type_first(self, queries):
|
||||
|
||||
has_type = False
|
||||
|
||||
result = []
|
||||
@@ -371,8 +408,10 @@ class Search(object):
|
||||
else:
|
||||
result.append(q)
|
||||
|
||||
_is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||
if result and not has_type and not _is_app_admin:
|
||||
if self.parent_node_perm_passed:
|
||||
self.__get_type2filter_perms()
|
||||
self.valid_type_names = "ALL"
|
||||
elif result and not has_type and not self.is_app_admin:
|
||||
type_q = self.__get_types_has_read()
|
||||
if id_query:
|
||||
ci = CIManager.get_by_id(id_query)
|
||||
@@ -381,13 +420,11 @@ class Search(object):
|
||||
result.insert(0, "_type:{}".format(ci.type_id))
|
||||
else:
|
||||
result.insert(0, type_q)
|
||||
elif _is_app_admin:
|
||||
elif self.is_app_admin:
|
||||
self.valid_type_names = "ALL"
|
||||
else:
|
||||
self.__get_types_has_read()
|
||||
|
||||
current_app.logger.warning(result)
|
||||
|
||||
return result
|
||||
|
||||
def __query_by_attr(self, q, queries, alias):
|
||||
@@ -479,7 +516,7 @@ class Search(object):
|
||||
def _filter_ids(self, query_sql):
|
||||
if self.ci_ids:
|
||||
return "SELECT * FROM ({0}) AS IN_QUERY WHERE IN_QUERY.ci_id IN ({1})".format(
|
||||
query_sql, ",".join(list(map(str, self.ci_ids))))
|
||||
query_sql, ",".join(list(set(map(str, self.ci_ids)))))
|
||||
|
||||
return query_sql
|
||||
|
||||
@@ -511,6 +548,9 @@ class Search(object):
|
||||
s = time.time()
|
||||
if 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
|
||||
# current_app.logger.debug(query_sql)
|
||||
numfound, res = self._execute_sql(query_sql)
|
||||
@@ -569,3 +609,8 @@ class Search(object):
|
||||
total = len(response)
|
||||
|
||||
return response, counter, total, self.page, numfound, facet
|
||||
|
||||
def get_ci_ids(self):
|
||||
_, ci_ids = self._query_build_raw()
|
||||
|
||||
return ci_ids
|
||||
|
@@ -1,9 +1,11 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
import json
|
||||
import sys
|
||||
from collections import Counter
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
@@ -11,11 +13,14 @@ from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
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_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.search.ci.db.search import Search as SearchFromDB
|
||||
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 CIRelation
|
||||
|
||||
|
||||
class Search(object):
|
||||
@@ -29,7 +34,9 @@ class Search(object):
|
||||
sort=None,
|
||||
reverse=False,
|
||||
ancestor_ids=None,
|
||||
has_m2m=None):
|
||||
descendant_ids=None,
|
||||
has_m2m=None,
|
||||
root_parent_path=None):
|
||||
self.orig_query = query
|
||||
self.fl = fl
|
||||
self.facet_field = facet_field
|
||||
@@ -46,6 +53,8 @@ class Search(object):
|
||||
level[0] if isinstance(level, list) and level else level)
|
||||
|
||||
self.ancestor_ids = ancestor_ids
|
||||
self.descendant_ids = descendant_ids
|
||||
self.root_parent_path = root_parent_path
|
||||
self.has_m2m = has_m2m or False
|
||||
if not self.has_m2m:
|
||||
if self.ancestor_ids:
|
||||
@@ -56,27 +65,23 @@ class Search(object):
|
||||
if _l < int(level) and c == ConstraintEnum.Many2Many:
|
||||
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):
|
||||
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 = []
|
||||
key = []
|
||||
_tmp = []
|
||||
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:
|
||||
_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
|
||||
key, prefix = list(map(str, ids)), REDIS_PREFIX_CI_RELATION
|
||||
|
||||
else:
|
||||
if not self.ancestor_ids:
|
||||
@@ -92,12 +97,16 @@ class Search(object):
|
||||
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
|
||||
prefix = REDIS_PREFIX_CI_RELATION2
|
||||
|
||||
_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:
|
||||
if not key or id_filter_limit is None:
|
||||
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:
|
||||
merge_ids.extend(ids)
|
||||
|
||||
@@ -120,7 +129,28 @@ class Search(object):
|
||||
|
||||
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):
|
||||
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
|
||||
cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids]
|
||||
|
||||
@@ -161,42 +191,105 @@ class Search(object):
|
||||
page=self.page,
|
||||
count=self.count,
|
||||
sort=self.sort,
|
||||
ci_ids=merge_ids).search()
|
||||
ci_ids=merge_ids,
|
||||
parent_node_perm_passed=parent_node_perm_passed,
|
||||
use_ci_filter=use_ci_filter).search()
|
||||
|
||||
def statistics(self, type_ids):
|
||||
def _get_ci_filter(self, filter_perms, ci_filters=None):
|
||||
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)
|
||||
|
||||
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
|
||||
_tmp = []
|
||||
level2ids = {}
|
||||
for lv in range(1, self.level + 1):
|
||||
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 not self.has_m2m:
|
||||
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
||||
else:
|
||||
if not self.ancestor_ids:
|
||||
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
||||
key, prefix = [str(i) for i in ids], REDIS_PREFIX_CI_RELATION
|
||||
else:
|
||||
key = ["{},{}".format(self.ancestor_ids, _id) for _id in ids]
|
||||
if not self.ancestor_ids:
|
||||
key, prefix = [str(i) for i in ids], REDIS_PREFIX_CI_RELATION
|
||||
else:
|
||||
prefix = REDIS_PREFIX_CI_RELATION2
|
||||
|
||||
level2ids[lv] = [[i] for i in key]
|
||||
|
||||
if not key:
|
||||
_tmp = []
|
||||
if not key or id_filter_limit is None:
|
||||
_tmp = [[]] * len(ids)
|
||||
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:
|
||||
_tmp = list(map(lambda x: [i for i in x if i[1] in type_ids],
|
||||
(map(lambda x: list(json.loads(x).items()),
|
||||
[i or '{}' for i in rd.get(key, prefix) or []]))))
|
||||
_tmp = [[i for i in x if i[1] in type_ids and
|
||||
(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)]
|
||||
else:
|
||||
_tmp = list(map(lambda x: list(json.loads(x).items()),
|
||||
[i or '{}' for i in rd.get(key, prefix) or []]))
|
||||
_tmp = [[i 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)]
|
||||
|
||||
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:
|
||||
|
||||
for idx, item in enumerate(_tmp):
|
||||
if item:
|
||||
if not self.has_m2m:
|
||||
@@ -208,15 +301,22 @@ class Search(object):
|
||||
level2ids[lv].append(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:
|
||||
__tmp = map(lambda x: [(_id, type_id) for _id, type_id in json.loads(x).items()
|
||||
if type_id in type_ids],
|
||||
filter(lambda x: x is not None,
|
||||
rd.get(key, prefix) or []))
|
||||
__tmp = [[i for i in x if i[1] in type_ids and
|
||||
(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)]
|
||||
else:
|
||||
__tmp = map(lambda x: list(json.loads(x).items()),
|
||||
filter(lambda x: x is not None,
|
||||
rd.get(key, prefix) or []))
|
||||
__tmp = [[i 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)]
|
||||
|
||||
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:
|
||||
__tmp = []
|
||||
|
||||
|
@@ -5,10 +5,10 @@ from __future__ import unicode_literals
|
||||
|
||||
import copy
|
||||
import imp
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import jinja2
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from jinja2schema import infer
|
||||
@@ -117,6 +117,11 @@ class AttributeValueManager(object):
|
||||
if type_attr and type_attr.is_required and not value and value != 0:
|
||||
return abort(400, ErrFormat.attribute_value_required.format(attr.alias))
|
||||
|
||||
@staticmethod
|
||||
def check_re(expr, value):
|
||||
if not re.compile(expr).match(str(value)):
|
||||
return abort(400, ErrFormat.attribute_value_invalid.format(value))
|
||||
|
||||
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
|
||||
ci = ci or {}
|
||||
v = self._deserialize_value(attr.value_type, value)
|
||||
@@ -130,6 +135,9 @@ class AttributeValueManager(object):
|
||||
if v == "" and attr.value_type not in (ValueTypeEnum.TEXT,):
|
||||
v = None
|
||||
|
||||
if attr.re_check and value:
|
||||
self.check_re(attr.re_check, value)
|
||||
|
||||
return v
|
||||
|
||||
@staticmethod
|
||||
@@ -294,9 +302,9 @@ class AttributeValueManager(object):
|
||||
return self.write_change2(changed)
|
||||
|
||||
@staticmethod
|
||||
def delete_attr_value(attr_id, ci_id):
|
||||
def delete_attr_value(attr_id, ci_id, commit=True):
|
||||
attr = AttributeCache.get(attr_id)
|
||||
if attr is not None:
|
||||
value_table = TableMap(attr=attr).table
|
||||
for item in value_table.get_by(attr_id=attr.id, ci_id=ci_id, to_dict=False):
|
||||
item.delete()
|
||||
item.delete(commit=commit)
|
||||
|
@@ -16,7 +16,7 @@ from wtforms import validators
|
||||
from api.extensions import db
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.common_setting.const import OperatorType
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.models.common_setting import Employee, Department
|
||||
|
||||
@@ -141,7 +141,7 @@ class EmployeeCRUD(object):
|
||||
def add(**kwargs):
|
||||
try:
|
||||
res = CreateEmployee().create_single(**kwargs)
|
||||
refresh_employee_acl_info.apply_async(args=(), queue=CMDB_QUEUE)
|
||||
refresh_employee_acl_info.apply_async(args=(res.employee_id,), queue=ACL_QUEUE)
|
||||
return res
|
||||
except Exception as e:
|
||||
abort(400, str(e))
|
||||
@@ -171,7 +171,7 @@ class EmployeeCRUD(object):
|
||||
if len(e_list) > 0:
|
||||
edit_employee_department_in_acl.apply_async(
|
||||
args=(e_list, new_department_id, current_user.uid),
|
||||
queue=CMDB_QUEUE
|
||||
queue=ACL_QUEUE
|
||||
)
|
||||
|
||||
return existed
|
||||
@@ -577,7 +577,6 @@ class EmployeeCRUD(object):
|
||||
@staticmethod
|
||||
def import_employee(employee_list):
|
||||
res = CreateEmployee().batch_create(employee_list)
|
||||
refresh_employee_acl_info.apply_async(args=(), queue=CMDB_QUEUE)
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
@@ -788,9 +787,11 @@ class CreateEmployee(object):
|
||||
if existed:
|
||||
return existed
|
||||
|
||||
return Employee.create(
|
||||
res = Employee.create(
|
||||
**kwargs
|
||||
)
|
||||
refresh_employee_acl_info.apply_async(args=(res.employee_id,), queue=ACL_QUEUE)
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def get_department_by_name(d_name):
|
||||
@@ -897,3 +898,75 @@ class EmployeeUpdateByUidForm(Form):
|
||||
avatar = StringField(validators=[])
|
||||
sex = StringField(validators=[])
|
||||
mobile = StringField(validators=[])
|
||||
|
||||
|
||||
class GrantEmployeeACLPerm(object):
|
||||
"""
|
||||
Grant ACL Permission After Create New Employee
|
||||
"""
|
||||
|
||||
def __init__(self, acl=None):
|
||||
self.perms_by_create_resources_type = ['read', 'grant', 'delete', 'update']
|
||||
self.perms_by_common_grant = ['read']
|
||||
self.resource_name_list = ['公司信息', '公司架构', '通知设置']
|
||||
|
||||
self.acl = acl if acl else self.check_app('backend')
|
||||
self.resources_types = self.acl.get_all_resources_types()
|
||||
self.resources_type = self.get_resources_type()
|
||||
self.resource_list = self.acl.get_resource_by_type(None, None, self.resources_type['id'])
|
||||
|
||||
@staticmethod
|
||||
def check_app(app_name):
|
||||
acl = ACLManager(app_name)
|
||||
payload = dict(
|
||||
name=app_name,
|
||||
description=app_name
|
||||
)
|
||||
app = acl.validate_app()
|
||||
if not app:
|
||||
acl.create_app(payload)
|
||||
return acl
|
||||
|
||||
def get_resources_type(self):
|
||||
results = list(filter(lambda t: t['name'] == '操作权限', self.resources_types['groups']))
|
||||
if len(results) == 0:
|
||||
payload = dict(
|
||||
app_id=self.acl.app_name,
|
||||
name='操作权限',
|
||||
description='',
|
||||
perms=self.perms_by_create_resources_type
|
||||
)
|
||||
resource_type = self.acl.create_resources_type(payload)
|
||||
else:
|
||||
resource_type = results[0]
|
||||
resource_type_id = resource_type['id']
|
||||
existed_perms = self.resources_types.get('id2perms', {}).get(resource_type_id, [])
|
||||
existed_perms = [p['name'] for p in existed_perms]
|
||||
new_perms = []
|
||||
for perm in self.perms_by_create_resources_type:
|
||||
if perm not in existed_perms:
|
||||
new_perms.append(perm)
|
||||
if len(new_perms) > 0:
|
||||
resource_type['perms'] = existed_perms + new_perms
|
||||
self.acl.update_resources_type(resource_type_id, resource_type)
|
||||
|
||||
return resource_type
|
||||
|
||||
def grant(self, rid_list):
|
||||
[self.grant_by_rid(rid) for rid in rid_list if rid > 0]
|
||||
|
||||
def grant_by_rid(self, rid, is_admin=False):
|
||||
for name in self.resource_name_list:
|
||||
resource = list(filter(lambda r: r['name'] == name, self.resource_list))
|
||||
if len(resource) == 0:
|
||||
payload = dict(
|
||||
type_id=self.resources_type['id'],
|
||||
app_id=self.acl.app_name,
|
||||
name=name,
|
||||
)
|
||||
resource = self.acl.create_resource(payload)
|
||||
else:
|
||||
resource = resource[0]
|
||||
|
||||
perms = self.perms_by_create_resources_type if is_admin else self.perms_by_common_grant
|
||||
self.acl.grant_resource(rid, resource['id'], perms)
|
||||
|
@@ -389,6 +389,7 @@ class AuditCRUD(object):
|
||||
logout_at=logout_at,
|
||||
ip=request.headers.get('X-Real-IP') or request.remote_addr,
|
||||
browser=request.headers.get('User-Agent'),
|
||||
channel=request.values.get('channel', 'web'),
|
||||
)
|
||||
|
||||
if logout_at is None:
|
||||
|
@@ -2,10 +2,11 @@
|
||||
|
||||
|
||||
import msgpack
|
||||
import redis_lock
|
||||
|
||||
from api.extensions import cache
|
||||
from api.extensions import rd
|
||||
from api.lib.decorator import flush_db
|
||||
from api.lib.utils import Lock
|
||||
from api.models.acl import App
|
||||
from api.models.acl import Permission
|
||||
from api.models.acl import Resource
|
||||
@@ -136,14 +137,14 @@ class HasResourceRoleCache(object):
|
||||
|
||||
@classmethod
|
||||
def add(cls, rid, app_id):
|
||||
with Lock('HasResourceRoleCache'):
|
||||
with redis_lock.Lock(rd.r, 'HasResourceRoleCache'):
|
||||
c = cls.get(app_id)
|
||||
c[rid] = 1
|
||||
cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0)
|
||||
|
||||
@classmethod
|
||||
def remove(cls, rid, app_id):
|
||||
with Lock('HasResourceRoleCache'):
|
||||
with redis_lock.Lock(rd.r, 'HasResourceRoleCache'):
|
||||
c = cls.get(app_id)
|
||||
c.pop(rid, None)
|
||||
cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0)
|
||||
|
@@ -194,7 +194,7 @@ def validate(ticket):
|
||||
|
||||
def _parse_tag(string, tag):
|
||||
"""
|
||||
Used for parsing xml. Search string for the first occurence of
|
||||
Used for parsing xml. Search string for the first occurrence of
|
||||
<tag>.....</tag> and return text (stripped of leading and tailing
|
||||
whitespace) between tags. Return "" if tag not found.
|
||||
"""
|
||||
|
@@ -1,8 +1,6 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import base64
|
||||
import sys
|
||||
import time
|
||||
from typing import Set
|
||||
|
||||
import elasticsearch
|
||||
@@ -213,52 +211,6 @@ class ESHandler(object):
|
||||
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):
|
||||
BLOCK_SIZE = 16 # Bytes
|
||||
pad = lambda s: s + ((AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE) *
|
||||
|
@@ -356,7 +356,7 @@ class AuditLoginLog(Model2):
|
||||
__tablename__ = "acl_audit_login_logs"
|
||||
|
||||
username = db.Column(db.String(64), index=True)
|
||||
channel = db.Column(db.Enum('web', 'api'), default="web")
|
||||
channel = db.Column(db.Enum('web', 'api', 'ssh'), default="web")
|
||||
ip = db.Column(db.String(15))
|
||||
browser = db.Column(db.String(256))
|
||||
description = db.Column(db.String(128))
|
||||
|
@@ -57,6 +57,16 @@ class CIType(Model):
|
||||
uid = db.Column(db.Integer, index=True)
|
||||
|
||||
|
||||
class CITypeInheritance(Model):
|
||||
__tablename__ = "c_ci_type_inheritance"
|
||||
|
||||
parent_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||
child_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||
|
||||
parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeInheritance.parent_id")
|
||||
child = db.relationship("CIType", primaryjoin="CIType.id==CITypeInheritance.child_id")
|
||||
|
||||
|
||||
class CITypeRelation(Model):
|
||||
__tablename__ = "c_ci_type_relations"
|
||||
|
||||
@@ -94,6 +104,8 @@ class Attribute(Model):
|
||||
_choice_web_hook = db.Column('choice_web_hook', db.JSON)
|
||||
choice_other = db.Column(db.JSON)
|
||||
|
||||
re_check = db.Column(db.Text)
|
||||
|
||||
uid = db.Column(db.Integer, index=True)
|
||||
|
||||
option = db.Column(db.JSON)
|
||||
@@ -464,6 +476,15 @@ class PreferenceSearchOption(Model):
|
||||
option = db.Column(db.JSON)
|
||||
|
||||
|
||||
class PreferenceCITypeOrder(Model):
|
||||
__tablename__ = "c_pcto"
|
||||
|
||||
uid = db.Column(db.Integer, index=True, nullable=False)
|
||||
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'))
|
||||
order = db.Column(db.SmallInteger, default=0)
|
||||
is_tree = db.Column(db.Boolean, default=False) # True is tree view, False is resource view
|
||||
|
||||
|
||||
# custom
|
||||
class CustomDashboard(Model):
|
||||
__tablename__ = "c_c_d"
|
||||
@@ -548,6 +569,7 @@ class CIFilterPerms(Model):
|
||||
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'))
|
||||
ci_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)
|
||||
|
||||
|
@@ -4,6 +4,7 @@
|
||||
import json
|
||||
import time
|
||||
|
||||
import redis_lock
|
||||
from flask import current_app
|
||||
from flask_login import login_user
|
||||
|
||||
@@ -17,10 +18,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_RELATION
|
||||
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 reconnect_db
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.utils import Lock
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
@@ -83,6 +84,13 @@ def ci_delete(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)
|
||||
@reconnect_db
|
||||
def ci_delete_trigger(trigger, operate_type, ci_dict):
|
||||
@@ -99,7 +107,7 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
||||
with Lock("CIRelation_{}".format(parent_id)):
|
||||
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
||||
if ancestor_ids is None:
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
@@ -177,7 +185,7 @@ def ci_relation_add(parent_dict, child_id, uid):
|
||||
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
|
||||
@reconnect_db
|
||||
def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
||||
with Lock("CIRelation_{}".format(parent_id)):
|
||||
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
||||
if ancestor_ids is None:
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
|
@@ -3,14 +3,14 @@ from flask import current_app
|
||||
|
||||
from api.extensions import celery
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.models.common_setting import Department, Employee
|
||||
from api.lib.decorator import flush_db
|
||||
from api.lib.decorator import reconnect_db
|
||||
|
||||
|
||||
@celery.task(name="common_setting.edit_employee_department_in_acl", queue=CMDB_QUEUE)
|
||||
@celery.task(name="common_setting.edit_employee_department_in_acl", queue=ACL_QUEUE)
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
||||
@@ -49,8 +49,7 @@ def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
||||
continue
|
||||
|
||||
old_d_rid_in_acl = role_map.get(old_department.department_name, 0)
|
||||
if old_d_rid_in_acl == 0:
|
||||
return
|
||||
if old_d_rid_in_acl > 0:
|
||||
if old_d_rid_in_acl != old_department.acl_rid:
|
||||
old_department.update(
|
||||
acl_rid=old_d_rid_in_acl
|
||||
@@ -77,10 +76,10 @@ def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
||||
return result
|
||||
|
||||
|
||||
@celery.task(name="common_setting.refresh_employee_acl_info", queue=CMDB_QUEUE)
|
||||
@celery.task(name="common_setting.refresh_employee_acl_info", queue=ACL_QUEUE)
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def refresh_employee_acl_info():
|
||||
def refresh_employee_acl_info(current_employee_id=None):
|
||||
acl = ACLManager('acl')
|
||||
role_map = {role['name']: role for role in acl.get_all_roles()}
|
||||
|
||||
@@ -90,8 +89,12 @@ def refresh_employee_acl_info():
|
||||
query = Employee.query.filter(*criterion).order_by(
|
||||
Employee.created_at.desc()
|
||||
)
|
||||
current_employee_rid = 0
|
||||
|
||||
for em in query.all():
|
||||
if current_employee_id and em.employee_id == current_employee_id:
|
||||
current_employee_rid = em.acl_rid if em.acl_rid else 0
|
||||
|
||||
if em.acl_uid and em.acl_rid:
|
||||
continue
|
||||
role = role_map.get(em.username, None)
|
||||
@@ -105,6 +108,9 @@ def refresh_employee_acl_info():
|
||||
if not em.acl_rid:
|
||||
params['acl_rid'] = role.get('id', 0)
|
||||
|
||||
if current_employee_id and em.employee_id == current_employee_id:
|
||||
current_employee_rid = params['acl_rid'] if params.get('acl_rid', 0) else 0
|
||||
|
||||
try:
|
||||
em.update(**params)
|
||||
current_app.logger.info(
|
||||
@@ -113,3 +119,12 @@ def refresh_employee_acl_info():
|
||||
except Exception as e:
|
||||
current_app.logger.error(str(e))
|
||||
continue
|
||||
|
||||
if current_employee_rid and current_employee_rid > 0:
|
||||
try:
|
||||
from api.lib.common_setting.employee import GrantEmployeeACLPerm
|
||||
|
||||
GrantEmployeeACLPerm().grant_by_rid(current_employee_rid, False)
|
||||
current_app.logger.info(f"GrantEmployeeACLPerm success, current_employee_rid: {current_employee_rid}")
|
||||
except Exception as e:
|
||||
current_app.logger.error(str(e))
|
||||
|
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2024-01-03 11:39+0800\n"
|
||||
"POT-Creation-Date: 2024-03-01 13:49+0800\n"
|
||||
"PO-Revision-Date: 2023-12-25 20:21+0800\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: zh\n"
|
||||
@@ -234,205 +234,209 @@ msgstr "只有创建人才能删除它!"
|
||||
msgid "The model cannot be deleted because the CI already exists"
|
||||
msgstr "因为CI已经存在,不能删除模型"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:65
|
||||
#: api/lib/cmdb/resp_format.py:63
|
||||
msgid "The inheritance cannot be deleted because the CI already exists"
|
||||
msgstr "因为CI已经存在,不能删除继承关系"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:67
|
||||
msgid ""
|
||||
"The model cannot be deleted because the model is referenced by the "
|
||||
"relational view {}"
|
||||
msgstr "因为关系视图 {} 引用了该模型,不能删除模型"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:67
|
||||
#: api/lib/cmdb/resp_format.py:69
|
||||
msgid "Model group {} does not exist"
|
||||
msgstr "模型分组 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:68
|
||||
#: api/lib/cmdb/resp_format.py:70
|
||||
msgid "Model group {} already exists"
|
||||
msgstr "模型分组 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:69
|
||||
#: api/lib/cmdb/resp_format.py:71
|
||||
msgid "Model relationship {} does not exist"
|
||||
msgstr "模型关系 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:70
|
||||
#: api/lib/cmdb/resp_format.py:72
|
||||
msgid "Attribute group {} already exists"
|
||||
msgstr "属性分组 {} 已存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:71
|
||||
#: api/lib/cmdb/resp_format.py:73
|
||||
msgid "Attribute group {} does not exist"
|
||||
msgstr "属性分组 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:73
|
||||
#: api/lib/cmdb/resp_format.py:75
|
||||
msgid "Attribute group <{0}> - attribute <{1}> does not exist"
|
||||
msgstr "属性组<{0}> - 属性<{1}> 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:74
|
||||
#: api/lib/cmdb/resp_format.py:76
|
||||
msgid "The unique constraint already exists!"
|
||||
msgstr "唯一约束已经存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:76
|
||||
#: api/lib/cmdb/resp_format.py:78
|
||||
msgid "Uniquely constrained attributes cannot be JSON and multi-valued"
|
||||
msgstr "唯一约束的属性不能是 JSON 和 多值"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:77
|
||||
#: api/lib/cmdb/resp_format.py:79
|
||||
msgid "Duplicated trigger"
|
||||
msgstr "重复的触发器"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:78
|
||||
#: api/lib/cmdb/resp_format.py:80
|
||||
msgid "Trigger {} does not exist"
|
||||
msgstr "触发器 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:80
|
||||
#: api/lib/cmdb/resp_format.py:82
|
||||
msgid "Operation record {} does not exist"
|
||||
msgstr "操作记录 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:81
|
||||
#: api/lib/cmdb/resp_format.py:83
|
||||
msgid "Unique identifier cannot be deleted"
|
||||
msgstr "不能删除唯一标识"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:82
|
||||
#: api/lib/cmdb/resp_format.py:84
|
||||
msgid "Cannot delete default sorted attributes"
|
||||
msgstr "不能删除默认排序的属性"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:84
|
||||
#: api/lib/cmdb/resp_format.py:86
|
||||
msgid "No node selected"
|
||||
msgstr "没有选择节点"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:85
|
||||
#: api/lib/cmdb/resp_format.py:87
|
||||
msgid "This search option does not exist!"
|
||||
msgstr "该搜索选项不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:86
|
||||
#: api/lib/cmdb/resp_format.py:88
|
||||
msgid "This search option has a duplicate name!"
|
||||
msgstr "该搜索选项命名重复!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:88
|
||||
#: api/lib/cmdb/resp_format.py:90
|
||||
msgid "Relationship type {} already exists"
|
||||
msgstr "关系类型 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:89
|
||||
#: api/lib/cmdb/resp_format.py:91
|
||||
msgid "Relationship type {} does not exist"
|
||||
msgstr "关系类型 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:91
|
||||
#: api/lib/cmdb/resp_format.py:93
|
||||
msgid "Invalid attribute value: {}"
|
||||
msgstr "无效的属性值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:92
|
||||
#: api/lib/cmdb/resp_format.py:94
|
||||
msgid "{} Invalid value: {}"
|
||||
msgstr "无效的值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:93
|
||||
#: api/lib/cmdb/resp_format.py:95
|
||||
msgid "{} is not in the predefined values"
|
||||
msgstr "{} 不在预定义值里"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:95
|
||||
#: api/lib/cmdb/resp_format.py:97
|
||||
msgid "The value of attribute {} must be unique, {} already exists"
|
||||
msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:96
|
||||
#: api/lib/cmdb/resp_format.py:98
|
||||
msgid "Attribute {} value must exist"
|
||||
msgstr "属性 {} 值必须存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:99
|
||||
#: api/lib/cmdb/resp_format.py:101
|
||||
msgid "Unknown error when adding or modifying attribute value: {}"
|
||||
msgstr "新增或者修改属性值未知错误: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:101
|
||||
#: api/lib/cmdb/resp_format.py:103
|
||||
msgid "Duplicate custom name"
|
||||
msgstr "订制名重复"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:103
|
||||
#: api/lib/cmdb/resp_format.py:105
|
||||
msgid "Number of models exceeds limit: {}"
|
||||
msgstr "模型数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:104
|
||||
#: api/lib/cmdb/resp_format.py:106
|
||||
msgid "The number of CIs exceeds the limit: {}"
|
||||
msgstr "CI数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:106
|
||||
#: api/lib/cmdb/resp_format.py:108
|
||||
msgid "Auto-discovery rule: {} already exists!"
|
||||
msgstr "自动发现规则: {} 已经存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:107
|
||||
#: api/lib/cmdb/resp_format.py:109
|
||||
msgid "Auto-discovery rule: {} does not exist!"
|
||||
msgstr "自动发现规则: {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:109
|
||||
#: api/lib/cmdb/resp_format.py:111
|
||||
msgid "This auto-discovery rule is referenced by the model and cannot be deleted!"
|
||||
msgstr "该自动发现规则被模型引用, 不能删除!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:111
|
||||
#: api/lib/cmdb/resp_format.py:113
|
||||
msgid "The application of auto-discovery rules cannot be defined repeatedly!"
|
||||
msgstr "自动发现规则的应用不能重复定义!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:112
|
||||
#: api/lib/cmdb/resp_format.py:114
|
||||
msgid "The auto-discovery you want to modify: {} does not exist!"
|
||||
msgstr "您要修改的自动发现: {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:113
|
||||
#: api/lib/cmdb/resp_format.py:115
|
||||
msgid "Attribute does not include unique identifier: {}"
|
||||
msgstr "属性字段没有包括唯一标识: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:114
|
||||
#: api/lib/cmdb/resp_format.py:116
|
||||
msgid "The auto-discovery instance does not exist!"
|
||||
msgstr "自动发现的实例不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:115
|
||||
#: api/lib/cmdb/resp_format.py:117
|
||||
msgid "The model is not associated with this auto-discovery!"
|
||||
msgstr "模型并未关联该自动发现!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:116
|
||||
#: api/lib/cmdb/resp_format.py:118
|
||||
msgid "Only the creator can modify the Secret!"
|
||||
msgstr "只有创建人才能修改Secret!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:118
|
||||
#: api/lib/cmdb/resp_format.py:120
|
||||
msgid "This rule already has auto-discovery instances and cannot be deleted!"
|
||||
msgstr "该规则已经有自动发现的实例, 不能被删除!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:120
|
||||
#: api/lib/cmdb/resp_format.py:122
|
||||
msgid "The default auto-discovery rule is already referenced by model {}!"
|
||||
msgstr "该默认的自动发现规则 已经被模型 {} 引用!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:122
|
||||
#: api/lib/cmdb/resp_format.py:124
|
||||
msgid "The unique_key method must return a non-empty string!"
|
||||
msgstr "unique_key方法必须返回非空字符串!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:123
|
||||
#: api/lib/cmdb/resp_format.py:125
|
||||
msgid "The attributes method must return a list"
|
||||
msgstr "attributes方法必须返回的是list"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:125
|
||||
#: api/lib/cmdb/resp_format.py:127
|
||||
msgid "The list returned by the attributes method cannot be empty!"
|
||||
msgstr "attributes方法返回的list不能为空!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:127
|
||||
#: api/lib/cmdb/resp_format.py:129
|
||||
msgid "Only administrators can define execution targets as: all nodes!"
|
||||
msgstr "只有管理员才可以定义执行机器为: 所有节点!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:128
|
||||
#: api/lib/cmdb/resp_format.py:130
|
||||
msgid "Execute targets permission check failed: {}"
|
||||
msgstr "执行机器权限检查不通过: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:130
|
||||
#: api/lib/cmdb/resp_format.py:132
|
||||
msgid "CI filter authorization must be named!"
|
||||
msgstr "CI过滤授权 必须命名!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:131
|
||||
#: api/lib/cmdb/resp_format.py:133
|
||||
msgid "CI filter authorization is currently not supported or query"
|
||||
msgstr "CI过滤授权 暂时不支持 或 查询"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:134
|
||||
#: api/lib/cmdb/resp_format.py:136
|
||||
msgid "You do not have permission to operate attribute {}!"
|
||||
msgstr "您没有属性 {} 的操作权限!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:135
|
||||
#: api/lib/cmdb/resp_format.py:137
|
||||
msgid "You do not have permission to operate this CI!"
|
||||
msgstr "您没有该CI的操作权限!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:137
|
||||
#: api/lib/cmdb/resp_format.py:139
|
||||
msgid "Failed to save password: {}"
|
||||
msgstr "保存密码失败: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:138
|
||||
#: api/lib/cmdb/resp_format.py:140
|
||||
msgid "Failed to get password: {}"
|
||||
msgstr "获取密码失败: {}"
|
||||
|
||||
|
@@ -11,8 +11,7 @@ from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.const import ExistPolicy
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum, PermEnum
|
||||
from api.lib.cmdb.const import RetKey
|
||||
from api.lib.cmdb.perms import has_perm_for_ci
|
||||
from api.lib.cmdb.search import SearchError
|
||||
@@ -152,9 +151,10 @@ class CISearchView(APIView):
|
||||
ret_key = RetKey.NAME
|
||||
facet = handle_arg_list(request.values.get("facet", ""))
|
||||
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()
|
||||
s = search(query, fl, facet, page, ret_key, count, sort, excludes)
|
||||
s = search(query, fl, facet, page, ret_key, count, sort, excludes, use_id_filter=use_id_filter)
|
||||
try:
|
||||
response, counter, total, page, numfound, facet = s.search()
|
||||
except SearchError as e:
|
||||
|
@@ -13,7 +13,6 @@ from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci_relation.search import Search
|
||||
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_size
|
||||
from api.lib.utils import handle_arg_list
|
||||
@@ -36,6 +35,8 @@ class CIRelationSearchView(APIView):
|
||||
|
||||
root_id = request.values.get('root_id')
|
||||
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'))))
|
||||
|
||||
query = request.values.get('q', "")
|
||||
@@ -47,7 +48,8 @@ class CIRelationSearchView(APIView):
|
||||
|
||||
start = time.time()
|
||||
s = Search(root_id, level, query, fl, facet, page, count, sort, reverse,
|
||||
ancestor_ids=ancestor_ids, has_m2m=has_m2m)
|
||||
ancestor_ids=ancestor_ids, has_m2m=has_m2m, root_parent_path=root_parent_path,
|
||||
descendant_ids=descendant_ids)
|
||||
try:
|
||||
response, counter, total, page, numfound, facet = s.search()
|
||||
except SearchError as e:
|
||||
@@ -65,16 +67,16 @@ class CIRelationSearchView(APIView):
|
||||
class CIRelationStatisticsView(APIView):
|
||||
url_prefix = "/ci_relations/statistics"
|
||||
|
||||
@auth_abandoned
|
||||
def get(self):
|
||||
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
|
||||
level = request.values.get('level', 1)
|
||||
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
|
||||
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')
|
||||
|
||||
start = time.time()
|
||||
s = Search(root_ids, level, ancestor_ids=ancestor_ids, has_m2m=has_m2m)
|
||||
s = Search(root_ids, level, ancestor_ids=ancestor_ids, descendant_ids=descendant_ids, has_m2m=has_m2m)
|
||||
try:
|
||||
result = s.statistics(type_ids)
|
||||
except SearchError as e:
|
||||
|
@@ -14,6 +14,7 @@ from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeGroupManager
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
from api.lib.cmdb.ci_type import CITypeGroupManager
|
||||
from api.lib.cmdb.ci_type import CITypeInheritanceManager
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.ci_type import CITypeTemplateManager
|
||||
from api.lib.cmdb.ci_type import CITypeTriggerManager
|
||||
@@ -37,15 +38,23 @@ from api.resource import 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):
|
||||
if request.url.endswith("icons"):
|
||||
return self.jsonify(CITypeManager().get_icons())
|
||||
|
||||
q = request.args.get("type_name")
|
||||
|
||||
if type_id is not None:
|
||||
ci_types = [CITypeCache.get(type_id).to_dict()]
|
||||
ci_type = CITypeCache.get(type_id).to_dict()
|
||||
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(type_id)
|
||||
ci_types = [ci_type]
|
||||
elif type_name is not None:
|
||||
ci_types = [CITypeCache.get(type_name).to_dict()]
|
||||
ci_type = CITypeCache.get(type_name).to_dict()
|
||||
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(ci_type['id'])
|
||||
ci_types = [ci_type]
|
||||
else:
|
||||
ci_types = CITypeManager().get_ci_types(q)
|
||||
count = len(ci_types)
|
||||
@@ -53,7 +62,7 @@ class CITypeView(APIView):
|
||||
return self.jsonify(numfound=count, ci_types=ci_types)
|
||||
|
||||
@args_required("name")
|
||||
@args_validate(CITypeManager.cls)
|
||||
@args_validate(CITypeManager.cls, exclude_args=['parent_ids'])
|
||||
def post(self):
|
||||
params = request.values
|
||||
|
||||
@@ -84,6 +93,26 @@ class CITypeView(APIView):
|
||||
return self.jsonify(type_id=type_id)
|
||||
|
||||
|
||||
class CITypeInheritanceView(APIView):
|
||||
url_prefix = ("/ci_types/inheritance",)
|
||||
|
||||
@args_required("parent_ids")
|
||||
@args_required("child_id")
|
||||
@has_perm_from_args("child_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||
def post(self):
|
||||
CITypeInheritanceManager.add(request.values['parent_ids'], request.values['child_id'])
|
||||
|
||||
return self.jsonify(**request.values)
|
||||
|
||||
@args_required("parent_id")
|
||||
@args_required("child_id")
|
||||
@has_perm_from_args("child_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||
def delete(self):
|
||||
CITypeInheritanceManager.delete(request.values['parent_id'], request.values['child_id'])
|
||||
|
||||
return self.jsonify(**request.values)
|
||||
|
||||
|
||||
class CITypeGroupView(APIView):
|
||||
url_prefix = ("/ci_types/groups",
|
||||
"/ci_types/groups/config",
|
||||
@@ -248,8 +277,8 @@ class CITypeAttributeTransferView(APIView):
|
||||
@args_required('from')
|
||||
@args_required('to')
|
||||
def post(self, type_id):
|
||||
_from = request.values.get('from') # {'attr_id': xx, 'group_id': xx}
|
||||
_to = request.values.get('to') # {'group_id': xx, 'order': xxx}
|
||||
_from = request.values.get('from') # {'attr_id': xx, 'group_id': xx, 'group_name': xx}
|
||||
_to = request.values.get('to') # {'group_id': xx, 'group_name': xx, 'order': xxx}
|
||||
|
||||
CITypeAttributeManager.transfer(type_id, _from, _to)
|
||||
|
||||
@@ -262,8 +291,8 @@ class CITypeAttributeGroupTransferView(APIView):
|
||||
@args_required('from')
|
||||
@args_required('to')
|
||||
def post(self, type_id):
|
||||
_from = request.values.get('from') # group_id
|
||||
_to = request.values.get('to') # group_id
|
||||
_from = request.values.get('from') # group_id or group_name
|
||||
_to = request.values.get('to') # group_id or group_name
|
||||
|
||||
CITypeAttributeGroupManager.transfer(type_id, _from, _to)
|
||||
|
||||
@@ -296,7 +325,7 @@ class CITypeAttributeGroupView(APIView):
|
||||
|
||||
attr_order = list(zip(attrs, orders))
|
||||
group = CITypeAttributeGroupManager.create_or_update(type_id, name, attr_order, order)
|
||||
current_app.logger.warning(group.id)
|
||||
|
||||
return self.jsonify(group_id=group.id)
|
||||
|
||||
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||
@@ -310,11 +339,13 @@ class CITypeAttributeGroupView(APIView):
|
||||
|
||||
attr_order = list(zip(attrs, orders))
|
||||
CITypeAttributeGroupManager.update(group_id, name, attr_order, order)
|
||||
|
||||
return self.jsonify(group_id=group_id)
|
||||
|
||||
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||
def delete(self, group_id):
|
||||
CITypeAttributeGroupManager.delete(group_id)
|
||||
|
||||
return self.jsonify(group_id=group_id)
|
||||
|
||||
|
||||
@@ -463,18 +494,19 @@ class CITypeGrantView(APIView):
|
||||
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))
|
||||
|
||||
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)
|
||||
|
||||
if request.values.get('ci_filter') or request.values.get('attr_filter'):
|
||||
CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values)
|
||||
else:
|
||||
new_resource = None
|
||||
if 'ci_filter' in request.values or 'attr_filter' in request.values or 'id_filter' in request.values:
|
||||
new_resource = CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values)
|
||||
|
||||
if not new_resource:
|
||||
from api.tasks.acl import role_rebuild
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
|
||||
app_id = AppCache.get('cmdb').id
|
||||
current_app.logger.info((rid, app_id))
|
||||
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE)
|
||||
current_app.logger.info('done')
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
@@ -495,10 +527,18 @@ class CITypeRevokeView(APIView):
|
||||
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))
|
||||
|
||||
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 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)
|
||||
|
||||
if PermEnum.READ in perms or not perms:
|
||||
resource = CIFilterPermsCRUD().delete(type_id=type_id, rid=rid)
|
||||
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
@@ -187,3 +188,15 @@ class PreferenceRelationRevokeView(APIView):
|
||||
acl.revoke_resource_from_role_by_rid(name, rid, ResourceTypeEnum.RELATION_VIEW, perms)
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
|
||||
class PreferenceCITypeOrderView(APIView):
|
||||
url_prefix = ("/preference/ci_types/order",)
|
||||
|
||||
def post(self):
|
||||
type_ids = request.values.get("type_ids")
|
||||
is_tree = request.values.get("is_tree") in current_app.config.get('BOOL_TRUE')
|
||||
|
||||
PreferenceManager.upsert_ci_type_order(type_ids, is_tree)
|
||||
|
||||
return self.jsonify(type_ids=type_ids, is_tree=is_tree)
|
||||
|
@@ -8,7 +8,7 @@ elasticsearch==7.17.9
|
||||
email-validator==1.3.1
|
||||
environs==4.2.0
|
||||
flasgger==0.9.5
|
||||
Flask==2.3.2
|
||||
Flask==2.2.5
|
||||
Flask-Bcrypt==1.0.1
|
||||
flask-babel==4.0.0
|
||||
Flask-Caching==2.0.2
|
||||
@@ -37,6 +37,7 @@ PyMySQL==1.1.0
|
||||
ldap3==2.9.1
|
||||
PyYAML==6.0.1
|
||||
redis==4.6.0
|
||||
python-redis-lock==4.0.0
|
||||
requests==2.31.0
|
||||
requests_oauthlib==1.3.1
|
||||
markdownify==0.11.6
|
||||
@@ -46,7 +47,7 @@ supervisor==4.0.3
|
||||
timeout-decorator==0.5.0
|
||||
toposort==1.10
|
||||
treelib==1.6.1
|
||||
Werkzeug>=2.3.6
|
||||
Werkzeug==2.2.3
|
||||
WTForms==3.0.0
|
||||
shamir~=17.12.0
|
||||
pycryptodomex>=3.19.0
|
||||
|
@@ -13,7 +13,7 @@ const getAntdSerials = (color) => {
|
||||
|
||||
const themePluginOption = {
|
||||
fileName: 'css/theme-colors-[contenthash:8].css',
|
||||
matchColors: getAntdSerials('#1890ff'), // 主色系列
|
||||
matchColors: getAntdSerials('#2f54eb'), // 主色系列
|
||||
// 改变样式选择器,解决样式覆盖问题
|
||||
changeSelector (selector) {
|
||||
switch (selector) {
|
||||
|
@@ -20,6 +20,7 @@
|
||||
}
|
||||
}
|
||||
"
|
||||
:disabled="disabled"
|
||||
>
|
||||
</treeselect>
|
||||
</div>
|
||||
@@ -42,6 +43,7 @@
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
@@ -80,6 +82,7 @@
|
||||
@select="(value) => handleChangeExp(value, item, index)"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
@@ -103,6 +106,7 @@
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
@@ -125,6 +129,7 @@
|
||||
v-model="item.min"
|
||||
:style="{ width: '78px' }"
|
||||
:placeholder="$t('min')"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
~
|
||||
<a-input
|
||||
@@ -133,6 +138,7 @@
|
||||
v-model="item.max"
|
||||
:style="{ width: '78px' }"
|
||||
:placeholder="$t('max')"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</a-input-group>
|
||||
<a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }">
|
||||
@@ -155,6 +161,7 @@
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
>
|
||||
</treeselect>
|
||||
<a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" />
|
||||
@@ -166,8 +173,10 @@
|
||||
:placeholder="item.exp === 'in' || item.exp === '~in' ? $t('cmdbFilterComp.split', { separator: ';' }) : ''"
|
||||
class="ops-input"
|
||||
:style="{ width: '175px' }"
|
||||
:disabled="disabled"
|
||||
></a-input>
|
||||
<div v-else :style="{ width: '175px' }"></div>
|
||||
<template v-if="!disabled">
|
||||
<a-tooltip :title="$t('copy')">
|
||||
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a>
|
||||
</a-tooltip>
|
||||
@@ -177,8 +186,9 @@
|
||||
<a-tooltip :title="$t('cmdbFilterComp.addHere')" v-if="needAddHere">
|
||||
<a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-space>
|
||||
<div class="table-filter-add">
|
||||
<div class="table-filter-add" v-if="!disabled">
|
||||
<a @click="handleAddRule">+ {{ $t('new') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -211,6 +221,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@@ -16,6 +16,7 @@
|
||||
:needAddHere="needAddHere"
|
||||
v-model="ruleList"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
<a-divider :style="{ margin: '10px 0' }" />
|
||||
<div style="width:554px">
|
||||
@@ -31,6 +32,7 @@
|
||||
v-else
|
||||
v-model="ruleList"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -69,6 +71,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@@ -135,4 +135,3 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
19
cmdb-ui/src/components/RegexSelect/constants.js
Normal file
19
cmdb-ui/src/components/RegexSelect/constants.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/* eslint-disable no-useless-escape */
|
||||
|
||||
import i18n from '@/lang'
|
||||
export const regList = () => {
|
||||
return [
|
||||
{ id: 'letter', label: i18n.t('regexSelect.letter'), value: '^[A-Za-z]+$', message: '请输入字母' },
|
||||
{ id: 'number', label: i18n.t('regexSelect.number'), value: '^-?(?!0\\d+)\\d+(\\.\\d+)?$', message: '请输入数字' },
|
||||
{ id: 'letterAndNumber', label: i18n.t('regexSelect.letterAndNumber'), value: '^[A-Za-z0-9.]+$', message: '请输入字母和数字' },
|
||||
{ id: 'phone', label: i18n.t('regexSelect.phone'), value: '^1[3-9]\\d{9}$', message: '请输入正确手机号码' },
|
||||
{ id: 'landline', label: i18n.t('regexSelect.landline'), value: '^(?:(?:\\d{3}-)?\\d{8}|^(?:\\d{4}-)?\\d{7,8})(?:-\\d+)?$', message: '请输入正确座机' },
|
||||
{ id: 'zipCode', label: i18n.t('regexSelect.zipCode'), value: '^(0[1-7]|1[0-356]|2[0-7]|3[0-6]|4[0-7]|5[1-7]|6[1-7]|7[0-5]|8[013-6])\\d{4}$', message: '请输入正确邮政编码' },
|
||||
{ id: 'IDCard', label: i18n.t('regexSelect.IDCard'), value: '(^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}$)', message: '请输入正确身份证号' },
|
||||
{ id: 'ip', label: i18n.t('regexSelect.ip'), value: '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$', message: '请输入正确IP地址' },
|
||||
{ id: 'email', label: i18n.t('regexSelect.email'), value: '^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\.\\w+([-.]\\w+)*$', message: '请输入正确邮箱' },
|
||||
{ id: 'link', label: i18n.t('regexSelect.link'), value: '^(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z0-9]{2,}(\.[a-zA-Z0-9]{2,})(\.[a-zA-Z0-9]{2,})?$', message: '请输入链接' },
|
||||
{ id: 'monetaryAmount', label: i18n.t('regexSelect.monetaryAmount'), value: '^-?\\d+(,\\d{3})*(\.\\d{1,2})?$', message: '请输入货币金额' },
|
||||
{ id: 'custom', label: i18n.t('regexSelect.custom'), value: '', message: '' }
|
||||
]
|
||||
}
|
2
cmdb-ui/src/components/RegexSelect/index.js
Normal file
2
cmdb-ui/src/components/RegexSelect/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import RegexSelect from './regexSelect.vue'
|
||||
export default RegexSelect
|
208
cmdb-ui/src/components/RegexSelect/regexSelect.vue
Normal file
208
cmdb-ui/src/components/RegexSelect/regexSelect.vue
Normal file
@@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<a-popover
|
||||
trigger="click"
|
||||
placement="bottom"
|
||||
ref="regexSelect"
|
||||
overlayClassName="regex-select-wrapper"
|
||||
:overlayStyle="{ '--overlay-width': `${width}px` }"
|
||||
@visibleChange="visibleChange"
|
||||
>
|
||||
<div class="regex-select" slot="content">
|
||||
<div class="regex-select-left">
|
||||
<div class="regex-select-left-header">{{ $t('regexSelect.limitedFormat') }}</div>
|
||||
<div
|
||||
@click="
|
||||
() => {
|
||||
current = reg
|
||||
testInput = ''
|
||||
showMessage = false
|
||||
}
|
||||
"
|
||||
:class="{
|
||||
'regex-select-left-reg': true,
|
||||
'regex-select-left-reg-selected': current && current.label === reg.label,
|
||||
}"
|
||||
v-for="(reg, index) in regList"
|
||||
:key="reg.label"
|
||||
>
|
||||
<a-divider :style="{ margin: '2px -12px', width: 'calc(100% + 24px)' }" v-if="index === regList.length - 1" />
|
||||
{{ reg.label }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="regex-select-right">
|
||||
<template v-if="current">
|
||||
<div class="regex-select-right-header">{{ $t('regexSelect.regExp') }}</div>
|
||||
<div
|
||||
v-if="current.label !== $t('regexSelect.custom')"
|
||||
:style="{ color: '#000', fontSize: '12px', margin: '12px 0' }"
|
||||
>
|
||||
{{ current.value }}
|
||||
</div>
|
||||
<a-input
|
||||
:style="{ margin: '12px 0' }"
|
||||
size="small"
|
||||
v-else
|
||||
v-model="current.value"
|
||||
@change="
|
||||
() => {
|
||||
this.$emit('change', current)
|
||||
}
|
||||
"
|
||||
/>
|
||||
<template v-if="isShowErrorMsg">
|
||||
<div class="regex-select-right-header">{{ $t('regexSelect.errMsg') }}</div>
|
||||
<a-input :style="{ margin: '12px 0' }" size="small" v-model="current.message" />
|
||||
</template>
|
||||
<div class="regex-select-right-header">{{ $t('regexSelect.test') }}</div>
|
||||
<a-input v-model="testInput" :style="{ margin: '12px 0 4px' }" size="small" @change="validate" />
|
||||
<span :style="{ color: 'red', fontSize: '12px' }" v-if="showMessage">{{
|
||||
locale === 'zh' ? current.message || '错误' : $t('regexSelect.error')
|
||||
}}</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<a-input ref="regInput" :placeholder="$t('regexSelect.placeholder')" :value="current.label" @change="changeLabel">
|
||||
</a-input>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { regList } from './constants'
|
||||
export default {
|
||||
name: 'RegexSelect',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
isShowErrorMsg: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
limitedFormat: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showMessage: false,
|
||||
width: 370,
|
||||
testInput: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['locale']),
|
||||
regList() {
|
||||
if (this.limitedFormat.length) {
|
||||
return regList().filter((item) => this.limitedFormat.includes(item.id))
|
||||
}
|
||||
return regList()
|
||||
},
|
||||
current: {
|
||||
get() {
|
||||
if (this.value?.value && !this.value?.label) {
|
||||
const _find = this.regList.find((reg) => reg.value === this.value?.value)
|
||||
return { ...this.value, label: _find?.label ?? this.$t('regexSelect.custom') }
|
||||
}
|
||||
return this.value ?? {}
|
||||
},
|
||||
set(val) {
|
||||
this.showMessage = false
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
const regInput = this.$refs.regInput.$refs.input
|
||||
this.width = regInput.offsetWidth || 370
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
validate(e) {
|
||||
const reg = RegExp(this.current.value, 'g')
|
||||
this.showMessage = !reg.test(e.target.value)
|
||||
},
|
||||
changeLabel(e) {
|
||||
this.current = {}
|
||||
},
|
||||
visibleChange(visible) {
|
||||
if (visible) {
|
||||
this.$nextTick(() => {
|
||||
this.testInput = ''
|
||||
this.showMessage = false
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.regex-select {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
display: flex;
|
||||
.regex-select-left {
|
||||
width: 40%;
|
||||
height: 100%;
|
||||
border: 1px solid #cacdd9;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
&-reg {
|
||||
padding-left: 2px;
|
||||
cursor: pointer;
|
||||
&-selected,
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
}
|
||||
}
|
||||
}
|
||||
&-right {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
border: 1px solid #cacdd9;
|
||||
border-radius: 4px;
|
||||
margin-left: 8px;
|
||||
padding: 12px;
|
||||
}
|
||||
&-left,
|
||||
&-right {
|
||||
&-header {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #000000;
|
||||
border-left: 2px solid #custom_colors[color_1];
|
||||
padding-left: 6px;
|
||||
margin-left: -6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.regex-select-wrapper {
|
||||
.ant-popover-arrow {
|
||||
display: none;
|
||||
}
|
||||
.ant-popover-inner-content {
|
||||
padding: 0;
|
||||
min-width: 370px;
|
||||
width: var(--overlay-width);
|
||||
}
|
||||
}
|
||||
.regex-select-wrapper.ant-popover-placement-bottom .ant-popover-content {
|
||||
margin-top: -8px;
|
||||
}
|
||||
.regex-select-wrapper.ant-popover-placement-top .ant-popover-content {
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
</style>
|
@@ -113,6 +113,10 @@ export default {
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const paneLengthPixel = localStorage.getItem(`${this.appName}-paneLengthPixel`)
|
||||
if (paneLengthPixel) {
|
||||
this.$emit('update:paneLengthPixel', Number(paneLengthPixel))
|
||||
}
|
||||
this.parentContainer = document.querySelector(`.${this.appName}`)
|
||||
if (this.isExpanded) {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
||||
|
@@ -119,7 +119,8 @@ export default {
|
||||
border-radius: 4px;
|
||||
color: @layout-header-font-color;
|
||||
height: @layout-header-height;
|
||||
line-height: @layout-header-height;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
&:hover {
|
||||
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
|
||||
color: @layout-header-font-selected-color;
|
||||
|
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
primaryColor: '#1890ff', // primary color of ant design
|
||||
primaryColor: '#2f54eb', // primary color of ant design
|
||||
navTheme: 'dark', // theme for nav menu
|
||||
layout: 'sidemenu', // nav menu position: sidemenu or topmenu
|
||||
contentWidth: 'Fixed', // layout of content: Fluid or Fixed, only works when layout is topmenu
|
||||
|
@@ -25,6 +25,7 @@ export default {
|
||||
deleting: 'Deleting',
|
||||
deletingTip: 'Deleting, total of {total}, {successNum} succeeded, {errorNum} failed',
|
||||
grant: 'Grant',
|
||||
revoke: 'Revoke',
|
||||
login_at: 'Login At',
|
||||
logout_at: 'Logout At',
|
||||
createSuccess: 'Create Success',
|
||||
@@ -145,6 +146,26 @@ export default {
|
||||
sizeLimit: 'The image size cannot exceed 2MB!',
|
||||
nodata: 'There are currently no custom icons available. Click here to upload'
|
||||
},
|
||||
regexSelect: {
|
||||
limitedFormat: 'Limited Format',
|
||||
regExp: 'RegExp',
|
||||
errMsg: 'Error Message',
|
||||
test: 'Test',
|
||||
placeholder: 'Please Select RegExp',
|
||||
error: 'Error',
|
||||
letter: 'letter',
|
||||
number: 'number',
|
||||
letterAndNumber: 'letter&number',
|
||||
phone: 'phone',
|
||||
landline: 'landline',
|
||||
zipCode: 'zip code',
|
||||
IDCard: 'ID card',
|
||||
ip: 'IP',
|
||||
email: 'email',
|
||||
link: 'link',
|
||||
monetaryAmount: 'monetary amount',
|
||||
custom: 'custom',
|
||||
},
|
||||
cmdb: cmdb_en,
|
||||
cs: cs_en,
|
||||
acl: acl_en,
|
||||
|
@@ -25,6 +25,7 @@ export default {
|
||||
deleting: '正在删除',
|
||||
deletingTip: '正在删除,共{total}个,成功{successNum}个,失败{errorNum}个',
|
||||
grant: '授权',
|
||||
revoke: '回收',
|
||||
login_at: '登录时间',
|
||||
logout_at: '登出时间',
|
||||
createSuccess: '创建成功',
|
||||
@@ -145,6 +146,26 @@ export default {
|
||||
sizeLimit: '图片大小不可超过2MB!',
|
||||
nodata: '暂无自定义图标,点击此处上传'
|
||||
},
|
||||
regexSelect: {
|
||||
limitedFormat: '限定格式',
|
||||
regExp: '正则表达式',
|
||||
errMsg: '错误时提示',
|
||||
test: '测试',
|
||||
placeholder: '请选择正则表达式',
|
||||
error: '错误',
|
||||
letter: '字母',
|
||||
number: '数字',
|
||||
letterAndNumber: '字母和数字',
|
||||
phone: '手机号码',
|
||||
landline: '座机',
|
||||
zipCode: '邮政编码',
|
||||
IDCard: '身份证号',
|
||||
ip: 'IP地址',
|
||||
email: '邮箱',
|
||||
link: '链接',
|
||||
monetaryAmount: '货币金额',
|
||||
custom: '自定义',
|
||||
},
|
||||
cmdb: cmdb_zh,
|
||||
cs: cs_zh,
|
||||
acl: acl_zh,
|
||||
|
@@ -57,7 +57,7 @@
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="operate" :title="$t('batchOperate')">
|
||||
<vxe-column field="operate" :title="$t('acl.batchOperate')">
|
||||
<template #default="{row}">
|
||||
<a-button size="small" type="danger" @click="handleClearAll(row)">
|
||||
{{ $t('clear') }}
|
||||
|
@@ -57,17 +57,20 @@
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</ops-table>
|
||||
<vxe-pager
|
||||
<a-pagination
|
||||
size="small"
|
||||
:layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']"
|
||||
:current-page.sync="tablePage.currentPage"
|
||||
:page-size.sync="tablePage.pageSize"
|
||||
show-size-changer
|
||||
show-quick-jumper
|
||||
:current="tablePage.currentPage"
|
||||
:total="tablePage.total"
|
||||
:page-sizes="pageSizeOptions"
|
||||
@page-change="handlePageChange"
|
||||
:style="{ marginTop: '10px' }"
|
||||
>
|
||||
</vxe-pager>
|
||||
:show-total="(total, range) => `当前展示 ${range[0]}-${range[1]} 条数据, 共 ${total} 条`"
|
||||
:page-size="tablePage.pageSize"
|
||||
:default-current="1"
|
||||
:page-size-options="pageSizeOptions"
|
||||
@change="pageOrSizeChange"
|
||||
@showSizeChange="pageOrSizeChange"
|
||||
:style="{ marginTop: '10px', textAlign: 'right' }"
|
||||
/>
|
||||
</a-spin>
|
||||
|
||||
<resourceTypeForm ref="resourceTypeForm" :handleOk="handleOk"> </resourceTypeForm>
|
||||
@@ -89,7 +92,7 @@ export default {
|
||||
loading: false,
|
||||
groups: [],
|
||||
id2perms: {},
|
||||
pageSizeOptions: [10, 25, 50, 100],
|
||||
pageSizeOptions: ['20', '50', '100', '200'],
|
||||
tablePage: {
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
@@ -176,7 +179,7 @@ export default {
|
||||
this.handleOk()
|
||||
})
|
||||
},
|
||||
handlePageChange({ currentPage, pageSize }) {
|
||||
pageOrSizeChange(currentPage, pageSize) {
|
||||
this.tablePage.currentPage = currentPage
|
||||
this.tablePage.pageSize = pageSize
|
||||
this.searchData()
|
||||
|
@@ -86,10 +86,12 @@
|
||||
<vxe-table-column field="user" :title="$t('acl.creator')" :min-widh="100"> </vxe-table-column>
|
||||
|
||||
<!-- 4 -->
|
||||
<vxe-table-column field="created_at" :title="$t('created_at')" :min-widh="220" align="center"> </vxe-table-column>
|
||||
<vxe-table-column field="created_at" :title="$t('created_at')" :min-widh="220" align="center">
|
||||
</vxe-table-column>
|
||||
|
||||
<!-- 5 -->
|
||||
<vxe-table-column field="updated_at" :title="$t('updated_at')" :min-widh="220" fixed="center"> </vxe-table-column>
|
||||
<vxe-table-column field="updated_at" :title="$t('updated_at')" :min-widh="220" fixed="center">
|
||||
</vxe-table-column>
|
||||
|
||||
<!-- 6 -->
|
||||
|
||||
@@ -99,7 +101,8 @@
|
||||
:min-widh="200"
|
||||
fixed="right"
|
||||
align="center"
|
||||
show-overflow>
|
||||
show-overflow
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span v-show="isGroup">
|
||||
<a @click="handleDisplayMember(row)">{{ $t('acl.member') }}</a>
|
||||
@@ -117,23 +120,32 @@
|
||||
</a>
|
||||
</a-tooltip>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm :title="$t('confirmDelete')" @confirm="handleDelete(row)" @cancel="cancel" :okText="$t('yes')" :cancelText="$t('no')">
|
||||
<a-popconfirm
|
||||
:title="$t('confirmDelete')"
|
||||
@confirm="handleDelete(row)"
|
||||
@cancel="cancel"
|
||||
:okText="$t('yes')"
|
||||
:cancelText="$t('no')"
|
||||
>
|
||||
<a style="color: red"><a-icon type="delete"/></a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</ops-table>
|
||||
<vxe-pager
|
||||
<a-pagination
|
||||
size="small"
|
||||
:layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']"
|
||||
:current-page.sync="tablePage.currentPage"
|
||||
:page-size.sync="tablePage.pageSize"
|
||||
show-size-changer
|
||||
show-quick-jumper
|
||||
:current="tablePage.currentPage"
|
||||
:total="tablePage.total"
|
||||
:page-sizes="pageSizeOptions"
|
||||
@page-change="handlePageChange"
|
||||
:style="{ marginTop: '10px' }"
|
||||
>
|
||||
</vxe-pager>
|
||||
:show-total="(total, range) => `当前展示 ${range[0]}-${range[1]} 条数据, 共 ${total} 条`"
|
||||
:page-size="tablePage.pageSize"
|
||||
:default-current="1"
|
||||
:page-size-options="pageSizeOptions"
|
||||
@change="pageOrSizeChange"
|
||||
@showSizeChange="pageOrSizeChange"
|
||||
:style="{ marginTop: '10px', textAlign: 'right' }"
|
||||
/>
|
||||
</a-spin>
|
||||
</div>
|
||||
<div v-else style="text-align: center; margin-top: 20%">
|
||||
@@ -191,7 +203,7 @@ export default {
|
||||
isGroup: false,
|
||||
allResourceTypes: [],
|
||||
currentType: { id: 0 },
|
||||
pageSizeOptions: [10, 25, 50, 100],
|
||||
pageSizeOptions: ['20', '50', '100', '200'],
|
||||
searchName: '',
|
||||
selectedRows: [],
|
||||
}
|
||||
@@ -291,7 +303,7 @@ export default {
|
||||
}
|
||||
},
|
||||
cancel() {},
|
||||
handlePageChange({ currentPage, pageSize }) {
|
||||
pageOrSizeChange(currentPage, pageSize) {
|
||||
this.tablePage.currentPage = currentPage
|
||||
this.tablePage.pageSize = pageSize
|
||||
this.searchData()
|
||||
@@ -302,7 +314,6 @@ export default {
|
||||
.getVxetableRef()
|
||||
.getCheckboxRecords()
|
||||
.concat(this.$refs.xTable.getVxetableRef().getCheckboxReserveRecords())
|
||||
console.log(this.selectedRows)
|
||||
},
|
||||
onSelectRangeEnd({ records }) {
|
||||
this.selectedRows = records
|
||||
|
@@ -104,17 +104,20 @@
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</ops-table>
|
||||
<vxe-pager
|
||||
<a-pagination
|
||||
size="small"
|
||||
:layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']"
|
||||
:current-page.sync="tablePage.currentPage"
|
||||
:page-size.sync="tablePage.pageSize"
|
||||
show-size-changer
|
||||
show-quick-jumper
|
||||
:current="tablePage.currentPage"
|
||||
:total="tablePage.total"
|
||||
:page-sizes="pageSizeOptions"
|
||||
@page-change="handlePageChange"
|
||||
:style="{ marginTop: '10px' }"
|
||||
>
|
||||
</vxe-pager>
|
||||
:show-total="(total, range) => `当前展示 ${range[0]}-${range[1]} 条数据, 共 ${total} 条`"
|
||||
:page-size="tablePage.pageSize"
|
||||
:default-current="1"
|
||||
:page-size-options="pageSizeOptions"
|
||||
@change="pageOrSizeChange"
|
||||
@showSizeChange="pageOrSizeChange"
|
||||
:style="{ marginTop: '10px', textAlign: 'right' }"
|
||||
/>
|
||||
</a-spin>
|
||||
|
||||
<roleForm ref="roleForm" :allRoles="allRoles" :id2parents="id2parents" :handleOk="handleOk"></roleForm>
|
||||
@@ -149,7 +152,7 @@ export default {
|
||||
tableData: [],
|
||||
allRoles: [],
|
||||
id2parents: {},
|
||||
pageSizeOptions: [10, 25, 50, 100],
|
||||
pageSizeOptions: ['20', '50', '100', '200'],
|
||||
searchName: '',
|
||||
filterTableValue: { user_role: 1, user_only: 0 },
|
||||
}
|
||||
@@ -254,7 +257,7 @@ export default {
|
||||
cancel(e) {
|
||||
return false
|
||||
},
|
||||
handlePageChange({ currentPage, pageSize }) {
|
||||
pageOrSizeChange(currentPage, pageSize) {
|
||||
this.tablePage.currentPage = currentPage
|
||||
this.tablePage.pageSize = pageSize
|
||||
this.loadData()
|
||||
|
@@ -205,3 +205,28 @@ export function ciTypeFilterPermissions(type_id) {
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
// parent_ids, child_id
|
||||
export function postCiTypeInheritance(data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/inheritance`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// parent_id, child_id
|
||||
export function deleteCiTypeInheritance(data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/inheritance`,
|
||||
method: 'delete',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function getCITypeIcons() {
|
||||
return axios({
|
||||
url: '/v0.1/ci_types/icons',
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
@@ -114,3 +114,12 @@ export function revokeRelationView(rid, data) {
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// preference citype order
|
||||
export function preferenceCitypeOrder(data) {
|
||||
return axios({
|
||||
url: `/v0.1/preference/ci_types/order`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
@@ -14,9 +14,16 @@
|
||||
<script>
|
||||
import EmployeeTransfer from '@/components/EmployeeTransfer'
|
||||
import RoleTransfer from '@/components/RoleTransfer'
|
||||
|
||||
export default {
|
||||
name: 'GrantModal',
|
||||
components: { EmployeeTransfer, RoleTransfer },
|
||||
props: {
|
||||
customTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
@@ -25,6 +32,9 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
if (this.customTitle) {
|
||||
return this.customTitle
|
||||
}
|
||||
if (this.type === 'depart') {
|
||||
return this.$t('cmdb.components.grantUser')
|
||||
}
|
||||
|
@@ -6,7 +6,8 @@
|
||||
{ value: 2, label: $t('cmdb.components.customize'), layout: 'vertical' },
|
||||
{ value: 3, label: $t('cmdb.components.none') },
|
||||
]"
|
||||
v-model="radioValue"
|
||||
:value="radioValue"
|
||||
@change="changeRadioValue"
|
||||
>
|
||||
<template slot="extra_2" v-if="radioValue === 2">
|
||||
<treeselect
|
||||
@@ -128,6 +129,9 @@ export default {
|
||||
this.visible = true
|
||||
this.colType = colType
|
||||
this.row = row
|
||||
this.form = {
|
||||
name: '',
|
||||
}
|
||||
if (this.colType === 'read_ci') {
|
||||
await getCITypeAttributesByTypeIds({ type_ids: this.CITypeId }).then((res) => {
|
||||
this.canSearchPreferenceAttrList = res.attributes.filter((item) => item.value_type !== '6')
|
||||
@@ -149,10 +153,6 @@ export default {
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.form = {
|
||||
name: '',
|
||||
}
|
||||
}
|
||||
},
|
||||
async handleOk() {
|
||||
@@ -198,6 +198,13 @@ export default {
|
||||
}
|
||||
this.expression = expression
|
||||
},
|
||||
changeRadioValue(value) {
|
||||
if (this.id_filter) {
|
||||
this.$message.warning(this.$t('cmdb.serviceTree.grantedByServiceTreeTips'))
|
||||
} else {
|
||||
this.radioValue = value
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
122
cmdb-ui/src/modules/cmdb/components/cmdbGrant/revokeModal.vue
Normal file
122
cmdb-ui/src/modules/cmdb/components/cmdbGrant/revokeModal.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<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>
|
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<treeselect
|
||||
:disabled="disabled"
|
||||
ref="cmdb_type_select"
|
||||
:disable-branch-nodes="true"
|
||||
class="custom-treeselect custom-treeselect-bgcAndBorder"
|
||||
:style="{
|
||||
'--custom-height': '30px',
|
||||
lineHeight: '30px',
|
||||
'--custom-bg-color': '#fff',
|
||||
'--custom-border': '1px solid #d9d9d9',
|
||||
}"
|
||||
v-model="currenCiType"
|
||||
:multiple="multiple"
|
||||
:clearable="true"
|
||||
searchable
|
||||
:options="ciTypeGroup"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
:placeholder="placeholder || `${$t(`placeholder2`)}`"
|
||||
:load-options="loadOptions"
|
||||
@select="
|
||||
(node, instanceId) => {
|
||||
$emit('select', node, instanceId)
|
||||
}
|
||||
"
|
||||
@deselect="
|
||||
(node, instanceId) => {
|
||||
$emit('deselect', node, instanceId)
|
||||
}
|
||||
"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.id || -1,
|
||||
label: node.alias || node.name || '其他',
|
||||
title: node.alias || node.name || '其他',
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
{{ node.label }}
|
||||
</div>
|
||||
<div slot="value-label" slot-scope="{ node }">{{ getTreeSelectLabel(node) }}</div>
|
||||
</treeselect>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { getCITypeGroupsConfig } from '@/modules/cmdb/api/ciTypeGroup'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { getTreeSelectLabel } from '../../utils/helper'
|
||||
export default {
|
||||
name: 'CMDBTypeSelect',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number, Array],
|
||||
default: null,
|
||||
},
|
||||
selectType: {
|
||||
type: String,
|
||||
default: 'attributes',
|
||||
},
|
||||
attrIdkey: {
|
||||
type: String,
|
||||
default: 'id',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ciTypeGroup: [],
|
||||
childrenOptions: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currenCiType: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
if (this.value) {
|
||||
const typeId = this.value.split('-')[0]
|
||||
await getCITypeAttributesById(this.value.split('-')[0]).then((res) => {
|
||||
this.childrenOptions = res.attributes.map((item) => ({ ...item, id: `${typeId}-${item[this.attrIdkey]}` }))
|
||||
})
|
||||
}
|
||||
this.getCITypeGroups()
|
||||
},
|
||||
methods: {
|
||||
getTreeSelectLabel,
|
||||
getCITypeGroups() {
|
||||
getCITypeGroupsConfig({ need_other: true }).then((res) => {
|
||||
this.ciTypeGroup = res
|
||||
.filter((item) => item.ci_types && item.ci_types.length)
|
||||
.map((item) => {
|
||||
item.id = `type_${item.id || -1}`
|
||||
item.children = item.ci_types.map((type) => {
|
||||
const obj = { ...type }
|
||||
if (this.selectType === 'attributes') {
|
||||
obj.children = this.value && type.id === Number(this.value.split('-')[0]) ? this.childrenOptions : null
|
||||
}
|
||||
return obj
|
||||
})
|
||||
return { ..._.cloneDeep(item) }
|
||||
})
|
||||
})
|
||||
},
|
||||
loadOptions({ action, parentNode, callback }) {
|
||||
getCITypeAttributesById(parentNode.id).then((res) => {
|
||||
parentNode.children = res.attributes.map((item) => ({
|
||||
...item,
|
||||
id: `${parentNode.id}-${item[this.attrIdkey]}`,
|
||||
}))
|
||||
callback()
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@@ -0,0 +1,2 @@
|
||||
import CMDBTypeSelect from './cmdbTypeSelect.vue'
|
||||
export default CMDBTypeSelect
|
@@ -52,7 +52,7 @@
|
||||
:style="{ color: fuzzySearch ? '#2f54eb' : '', cursor: 'pointer' }"
|
||||
@click="emitRefresh"
|
||||
/>
|
||||
<a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px' }">
|
||||
<a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px', whiteSpace: 'pre-line' }">
|
||||
<template slot="title">
|
||||
{{ $t('cmdb.components.ciSearchTips') }}
|
||||
</template>
|
||||
@@ -97,6 +97,7 @@
|
||||
</a-space>
|
||||
</div>
|
||||
<a-space>
|
||||
<slot name="extraContent"></slot>
|
||||
<a-button @click="reset" size="small">{{ $t('reset') }}</a-button>
|
||||
<a-tooltip :title="$t('cmdb.components.attributeDesc')" v-if="type === 'relationView'">
|
||||
<a
|
||||
@@ -145,7 +146,7 @@ export default {
|
||||
selectedRowKeys: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -179,13 +180,21 @@ export default {
|
||||
this.fuzzySearch = ''
|
||||
},
|
||||
},
|
||||
inject: ['setPreferenceSearchCurrent'],
|
||||
inject: {
|
||||
setPreferenceSearchCurrent: {
|
||||
from: 'setPreferenceSearchCurrent',
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.type === 'resourceSearch') {
|
||||
this.getCITypeGroups()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// toggleAdvanced() {
|
||||
// this.advanced = !this.advanced
|
||||
// },
|
||||
getCITypeGroups() {
|
||||
getCITypeGroups({ need_other: true }).then((res) => {
|
||||
this.ciTypeGroup = res
|
||||
@@ -234,7 +243,9 @@ export default {
|
||||
}
|
||||
},
|
||||
emitRefresh() {
|
||||
if (this.setPreferenceSearchCurrent) {
|
||||
this.setPreferenceSearchCurrent(null)
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$emit('refresh', true)
|
||||
})
|
||||
|
@@ -88,7 +88,9 @@ export default {
|
||||
} catch {}
|
||||
const headers = {}
|
||||
this.$refs.Header.headers.forEach((item) => {
|
||||
if (item.key) {
|
||||
headers[item.key] = item.value
|
||||
}
|
||||
})
|
||||
let authorization = {}
|
||||
const type = this.$refs.Authorization.authorizationType
|
||||
|
@@ -19,6 +19,7 @@ const cmdb_en = {
|
||||
operationHistory: 'Operation Audit',
|
||||
relationType: 'Relation Type',
|
||||
ad: 'AutoDiscovery',
|
||||
cidetail: 'CI Detail'
|
||||
},
|
||||
ciType: {
|
||||
ciType: 'CIType',
|
||||
@@ -45,8 +46,9 @@ const cmdb_en = {
|
||||
selectDefaultOrderAttr: 'Select default sorting attributes',
|
||||
asec: 'Forward order',
|
||||
desc: 'Reverse order',
|
||||
uniqueKey: 'Uniquely Identifies',
|
||||
uniqueKey: 'Unique Identifies',
|
||||
uniqueKeySelect: 'Please select a unique identifier',
|
||||
uniqueKeyTips: 'json/password/computed/choice can not be unique identifies',
|
||||
notfound: 'Can\'t find what you want?',
|
||||
cannotDeleteGroupTips: 'There is data under this group and cannot be deleted!',
|
||||
confirmDeleteGroup: 'Are you sure you want to delete group [{groupName}]?',
|
||||
@@ -174,7 +176,13 @@ const cmdb_en = {
|
||||
date: 'Date',
|
||||
time: 'Time',
|
||||
json: 'JSON',
|
||||
event: 'Event'
|
||||
event: 'Event',
|
||||
reg: 'Regex',
|
||||
isInherit: 'Inherit',
|
||||
inheritType: 'Inherit Type',
|
||||
inheritTypePlaceholder: 'Please select inherit types',
|
||||
inheritFrom: 'inherit from {name}',
|
||||
groupInheritFrom: 'Please go to the {name} for modification'
|
||||
},
|
||||
components: {
|
||||
unselectAttributes: 'Unselected',
|
||||
@@ -216,7 +224,7 @@ const cmdb_en = {
|
||||
pleaseSearch: 'Please search',
|
||||
conditionFilter: 'Conditional filtering',
|
||||
attributeDesc: 'Attribute Description',
|
||||
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',
|
||||
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',
|
||||
ciSearchTips2: 'For example: q=hostname:*0.0.0.0*',
|
||||
subCIType: 'Subscription CIType',
|
||||
already: 'already',
|
||||
@@ -366,12 +374,12 @@ const cmdb_en = {
|
||||
ad: {
|
||||
upload: 'Import',
|
||||
download: 'Export',
|
||||
accpet: 'Accept',
|
||||
accpetBy: 'Accept By',
|
||||
accept: 'Accept',
|
||||
acceptBy: 'Accept By',
|
||||
acceptTime: 'Accept Time',
|
||||
confirmAccept: 'Confirm Accept?',
|
||||
accpetSuccess: 'Accept successfully',
|
||||
isAccpet: 'Is accept',
|
||||
acceptSuccess: 'Accept successfully',
|
||||
isAccept: 'Is accept',
|
||||
deleteADC: 'Confirm to delete this data?',
|
||||
batchDelete: 'Confirm to delete this data?',
|
||||
agent: 'Built-in & Plug-ins',
|
||||
@@ -459,13 +467,15 @@ const cmdb_en = {
|
||||
tips3: 'Please select the fields that need to be modified',
|
||||
tips4: 'At least one field must be selected',
|
||||
tips5: 'Search name | alias',
|
||||
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',
|
||||
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',
|
||||
tips7: 'The form of expression is a drop-down box, and the value must be in the predefined value',
|
||||
tips8: 'Multiple values, such as intranet IP',
|
||||
tips9: 'For front-end only',
|
||||
tips10: 'Other attributes of the CIType are computed using expressions\n\nA code snippet computes the returned value.',
|
||||
newUpdateField: 'Add a Attribute',
|
||||
attributeSettings: 'Attribute Settings',
|
||||
share: 'Share',
|
||||
noPermission: 'No Permission'
|
||||
},
|
||||
serviceTree: {
|
||||
deleteNode: 'Delete Node',
|
||||
@@ -474,6 +484,16 @@ const 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!',
|
||||
|
@@ -19,6 +19,7 @@ const cmdb_zh = {
|
||||
operationHistory: '操作审计',
|
||||
relationType: '关系类型',
|
||||
ad: '自动发现',
|
||||
cidetail: 'CI 详情'
|
||||
},
|
||||
ciType: {
|
||||
ciType: '模型',
|
||||
@@ -47,6 +48,7 @@ const cmdb_zh = {
|
||||
desc: '倒序',
|
||||
uniqueKey: '唯一标识',
|
||||
uniqueKeySelect: '请选择唯一标识',
|
||||
uniqueKeyTips: 'json、密码、计算属性、预定义值属性不能作为唯一标识',
|
||||
notfound: '找不到想要的?',
|
||||
cannotDeleteGroupTips: '该分组下有数据, 不能删除!',
|
||||
confirmDeleteGroup: '确定要删除分组 【{groupName}】 吗?',
|
||||
@@ -174,7 +176,13 @@ const cmdb_zh = {
|
||||
date: '日期',
|
||||
time: '时间',
|
||||
json: 'JSON',
|
||||
event: '事件'
|
||||
event: '事件',
|
||||
reg: '正则校验',
|
||||
isInherit: '是否继承',
|
||||
inheritType: '继承模型',
|
||||
inheritTypePlaceholder: '请选择继承模型(多选)',
|
||||
inheritFrom: '属性继承自{name}',
|
||||
groupInheritFrom: '请至{name}进行修改'
|
||||
},
|
||||
components: {
|
||||
unselectAttributes: '未选属性',
|
||||
@@ -216,7 +224,7 @@ const cmdb_zh = {
|
||||
pleaseSearch: '请查找',
|
||||
conditionFilter: '条件过滤',
|
||||
attributeDesc: '属性说明',
|
||||
ciSearchTips: '1. json属性不能搜索<br />2. 搜索内容包括逗号, 则需转义 ,<br />3. 只搜索索引属性, 非索引属性使用条件过滤',
|
||||
ciSearchTips: '1. json、密码、链接属性不能搜索\n2. 搜索内容包括逗号, 则需转义\n3. 只搜索索引属性, 非索引属性使用条件过滤',
|
||||
ciSearchTips2: '例: q=hostname:*0.0.0.0*',
|
||||
subCIType: '订阅模型',
|
||||
already: '已',
|
||||
@@ -366,12 +374,12 @@ const cmdb_zh = {
|
||||
ad: {
|
||||
upload: '规则导入',
|
||||
download: '规则导出',
|
||||
accpet: '入库',
|
||||
accpetBy: '入库人',
|
||||
accept: '入库',
|
||||
acceptBy: '入库人',
|
||||
acceptTime: '入库时间',
|
||||
confirmAccept: '确认入库?',
|
||||
accpetSuccess: '入库成功',
|
||||
isAccpet: '是否入库',
|
||||
acceptSuccess: '入库成功',
|
||||
isAccept: '是否入库',
|
||||
deleteADC: '确认删除该条数据?',
|
||||
batchDelete: '确认删除这些数据?',
|
||||
agent: '内置 & 插件',
|
||||
@@ -458,13 +466,15 @@ const cmdb_zh = {
|
||||
tips3: '请选择需要修改的字段',
|
||||
tips4: '必须至少选择一个字段',
|
||||
tips5: '搜索 名称 | 别名',
|
||||
tips6: '加快检索, 可以全文搜索, 无需使用条件过滤\n\n json目前不支持建索引 \n\n文本字符长度超过190不能建索引',
|
||||
tips6: '加快检索, 可以全文搜索, 无需使用条件过滤\n\n json、链接、密码目前不支持建索引 \n\n文本字符长度超过190不能建索引',
|
||||
tips7: '表现形式是下拉框, 值必须在预定义值里',
|
||||
tips8: '多值, 比如内网IP',
|
||||
tips9: '仅针对前端',
|
||||
tips10: '模型的其他属性通过表达式的方式计算出来\n\n一个代码片段计算返回的值',
|
||||
newUpdateField: '新增修改字段',
|
||||
attributeSettings: '字段设置',
|
||||
share: '分享',
|
||||
noPermission: '暂无权限'
|
||||
},
|
||||
serviceTree: {
|
||||
deleteNode: '删除节点',
|
||||
@@ -473,6 +483,16 @@ const cmdb_zh = {
|
||||
alert1: '管理员 还未配置业务关系, 或者你无权限访问!',
|
||||
copyFailed: '复制失败',
|
||||
deleteRelationConfirm: '确认将选中的 {name} 从当前关系中删除?',
|
||||
batch: '批量操作',
|
||||
grantTitle: '授权(查看权限)',
|
||||
userPlaceholder: '请选择用户',
|
||||
rolePlaceholder: '请选择角色',
|
||||
grantedByServiceTree: '服务树授权:',
|
||||
grantedByServiceTreeTips: '请先在服务树里删掉节点授权',
|
||||
peopleHasRead: '当前有查看权限的人员:',
|
||||
authorizationPolicy: '实例授权策略:',
|
||||
idAuthorizationPolicy: '按节点授权的:',
|
||||
view: '查看权限'
|
||||
},
|
||||
tree: {
|
||||
tips1: '请先到 我的订阅 页面完成订阅!',
|
||||
|
@@ -56,6 +56,13 @@ const genCmdbRoutes = async () => {
|
||||
meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc-selected', keepAlive: false },
|
||||
component: () => import('../views/discoveryCI/index.vue')
|
||||
},
|
||||
{
|
||||
path: `/cmdb/cidetail/:typeId/:ciId`,
|
||||
name: 'cmdb_ci_detail',
|
||||
hidden: true,
|
||||
meta: { title: 'cmdb.menu.cidetail', keepAlive: false },
|
||||
component: () => import('../views/ci/ciDetailPage.vue')
|
||||
},
|
||||
{
|
||||
path: '/cmdb/disabled2',
|
||||
name: 'cmdb_disabled2',
|
||||
@@ -138,7 +145,7 @@ const genCmdbRoutes = async () => {
|
||||
const [preference, relation] = await Promise.all([getPreference(), getRelationView()])
|
||||
|
||||
preference.forEach(item => {
|
||||
routes.children[2].children.unshift({
|
||||
routes.children[2].children.push({
|
||||
path: `/cmdb/instances/types/${item.id}`,
|
||||
component: () => import(`../views/ci/index`),
|
||||
name: `cmdb_${item.id}`,
|
||||
|
@@ -179,3 +179,13 @@ export const downloadExcel = (data, fileName = `${moment().format('YYYY-MM-DD HH
|
||||
// STEP 4: Write Excel file to browser #导出
|
||||
XLSXS.writeFile(wb, fileName + '.xlsx')
|
||||
}
|
||||
|
||||
export const getAllParentNodesLabel = (node, label) => {
|
||||
if (node.parentNode) {
|
||||
return getAllParentNodesLabel(node.parentNode, `${node.parentNode.label}-${label}`)
|
||||
}
|
||||
return label
|
||||
}
|
||||
export const getTreeSelectLabel = (node) => {
|
||||
return `${getAllParentNodesLabel(node, node.label)}`
|
||||
}
|
71
cmdb-ui/src/modules/cmdb/views/ci/ciDetailPage.vue
Normal file
71
cmdb-ui/src/modules/cmdb/views/ci/ciDetailPage.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="ci-detail-header">{{ this.type.alias }}</div>
|
||||
<div class="ci-detail-page">
|
||||
<CiDetailTab ref="ciDetailTab" :typeId="typeId" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CiDetailTab from './modules/ciDetailTab.vue'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { getCIType } from '@/modules/cmdb/api/CIType'
|
||||
|
||||
export default {
|
||||
name: 'CiDetailPage',
|
||||
components: { CiDetailTab },
|
||||
data() {
|
||||
return {
|
||||
typeId: Number(this.$route.params.typeId),
|
||||
type: {},
|
||||
attrList: [],
|
||||
attributes: {},
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
attrList: () => {
|
||||
return this.attrList
|
||||
},
|
||||
attributes: () => {
|
||||
return this.attributes
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const { ciId = undefined } = this.$route.params
|
||||
if (ciId) {
|
||||
this.$refs.ciDetailTab.create(Number(ciId))
|
||||
}
|
||||
getCIType(this.typeId).then((res) => {
|
||||
this.type = res.ci_types[0]
|
||||
})
|
||||
this.getAttributeList()
|
||||
},
|
||||
methods: {
|
||||
async getAttributeList() {
|
||||
await getCITypeAttributesById(this.typeId).then((res) => {
|
||||
this.attrList = res.attributes
|
||||
this.attributes = res
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.ci-detail-header {
|
||||
border-left: 3px solid @primary-color;
|
||||
padding-left: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.ci-detail-page {
|
||||
background-color: #fff;
|
||||
height: calc(100vh - 122px);
|
||||
}
|
||||
</style>
|
@@ -57,7 +57,7 @@
|
||||
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span>
|
||||
</div>
|
||||
</SearchForm>
|
||||
<CiDetail ref="detail" :typeId="typeId" />
|
||||
<CiDetailDrawer ref="detail" :typeId="typeId" />
|
||||
<ops-table
|
||||
:id="`cmdb-ci-${typeId}`"
|
||||
border
|
||||
@@ -101,7 +101,6 @@
|
||||
:cell-type="col.value_type === '2' ? 'string' : 'auto'"
|
||||
:fixed="col.is_fixed ? 'left' : ''"
|
||||
>
|
||||
<!-- <template #edit="{row}"><a-input v-model="row[col.field]"></a-input></template> -->
|
||||
<template #header>
|
||||
<span class="vxe-handle">
|
||||
<OpsMoveIcon
|
||||
@@ -110,7 +109,7 @@
|
||||
<span>{{ col.title }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="col.is_choice || col.is_password || col.is_list" #edit="{ row }">
|
||||
<template v-if="col.is_choice || col.is_password" #edit="{ row }">
|
||||
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
|
||||
<a-select
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
@@ -147,18 +146,6 @@
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
:style="{ width: '100%', height: '32px' }"
|
||||
v-model="row[col.field]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
v-else-if="col.is_list"
|
||||
:showArrow="false"
|
||||
mode="tags"
|
||||
class="ci-table-edit-select"
|
||||
allowClear
|
||||
>
|
||||
</a-select>
|
||||
</template>
|
||||
<template
|
||||
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
|
||||
@@ -293,8 +280,6 @@
|
||||
</a-pagination>
|
||||
</div>
|
||||
<create-instance-form ref="create" @reload="reloadData" @submit="batchUpdate" />
|
||||
<!-- <EditAttrsDrawer ref="editAttrsDrawer" @refresh="refreshAfterEditAttrs" /> -->
|
||||
<!-- <batch-update-relation :typeId="typeId" ref="batchUpdateRelation" @submit="batchUpdateRelation" /> -->
|
||||
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
||||
<BatchDownload ref="batchDownload" @batchDownload="batchDownload" />
|
||||
<MetadataDrawer ref="metadataDrawer" />
|
||||
@@ -312,7 +297,7 @@ import router, { resetRouter } from '@/router'
|
||||
|
||||
import SearchForm from '../../components/searchForm/SearchForm.vue'
|
||||
import CreateInstanceForm from './modules/CreateInstanceForm'
|
||||
import CiDetail from './modules/CiDetail'
|
||||
import CiDetailDrawer from './modules/ciDetailDrawer.vue'
|
||||
import EditAttrsPopover from './modules/editAttrsPopover'
|
||||
import JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
|
||||
import { searchCI, updateCI, deleteCI } from '@/modules/cmdb/api/ci'
|
||||
@@ -335,7 +320,7 @@ export default {
|
||||
components: {
|
||||
SearchForm,
|
||||
CreateInstanceForm,
|
||||
CiDetail,
|
||||
CiDetailDrawer,
|
||||
JsonEditor,
|
||||
PasswordField,
|
||||
EditAttrsPopover,
|
||||
@@ -471,11 +456,6 @@ export default {
|
||||
const regSort = /(?<=sort=).+/g
|
||||
|
||||
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
|
||||
// if (exp) {
|
||||
// exp = exp.replace(/(\:)/g, '$1*')
|
||||
// exp = exp.replace(/(\,)/g, '*$1')
|
||||
// }
|
||||
// If the sorting is done by clicking on the table, the table will prevail.
|
||||
let sort
|
||||
if (sortByTable) {
|
||||
sort = sortByTable
|
||||
@@ -484,7 +464,6 @@ export default {
|
||||
}
|
||||
const res = await searchCI({
|
||||
q: `_type:${this.typeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`,
|
||||
// q: `${this.mergeQ(queryParams)}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`,
|
||||
count: this.pageSize,
|
||||
page: this.currentPage,
|
||||
sort,
|
||||
@@ -533,55 +512,17 @@ export default {
|
||||
this.$refs['xTable'].getVxetableRef().setCheckboxRow(rows, true)
|
||||
}
|
||||
},
|
||||
// mergeQ(params) {
|
||||
// let q = `_type:${this.typeId}`
|
||||
// Object.keys(params).forEach((key) => {
|
||||
// if (!['pageNo', 'pageSize', 'sortField', 'sortOrder'].includes(key) && params[key] + '' !== '') {
|
||||
// if (typeof params[key] === 'object' && params[key] && params[key].length > 1) {
|
||||
// q += `,${key}:(${params[key].join(';')})`
|
||||
// } else if (params[key]) {
|
||||
// q += `,${key}:*${params[key]}*`
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// return q
|
||||
// },
|
||||
|
||||
async loadPreferenceAttrList() {
|
||||
const subscribed = await getSubscribeAttributes(this.typeId)
|
||||
this.preferenceAttrList = subscribed.attributes // All columns that have been subscribed
|
||||
},
|
||||
|
||||
onSelectChange() {
|
||||
// const current = records.map((i) => i.ci_id || i._id)
|
||||
|
||||
// const cached = new Set(this.selectedRowKeys)
|
||||
// if (checked) {
|
||||
// current.forEach((i) => {
|
||||
// cached.add(i)
|
||||
// })
|
||||
// } else {
|
||||
// if (row) {
|
||||
// cached.delete(row.ci_id || row._id)
|
||||
// } else {
|
||||
// this.instanceList.map((row) => {
|
||||
// cached.delete(row.ci_id || row._id)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
const xTable = this.$refs.xTable.getVxetableRef()
|
||||
const records = [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()]
|
||||
this.selectedRowKeys = records.map((i) => i.ci_id || i._id)
|
||||
},
|
||||
onSelectRangeEnd({ records }) {
|
||||
// const current = records.map((i) => i.ci_id || i._id)
|
||||
// const cached = new Set(this.selectedRowKeys)
|
||||
// current.forEach((i) => {
|
||||
// cached.add(i)
|
||||
// })
|
||||
this.selectedRowKeys = records.map((i) => i.ci_id || i._id)
|
||||
|
||||
// this.setSelectRows()
|
||||
},
|
||||
reloadData() {
|
||||
this.loadTableData()
|
||||
@@ -623,7 +564,7 @@ export default {
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
$table.revertData(row)
|
||||
this.loadTableData()
|
||||
})
|
||||
}
|
||||
this.columns.forEach((col) => {
|
||||
@@ -694,12 +635,22 @@ export default {
|
||||
}
|
||||
})
|
||||
this.$refs.create.visible = false
|
||||
const key = 'updatable'
|
||||
let errorMsg = ''
|
||||
for (let i = 0; i < this.selectedRowKeys.length; i++) {
|
||||
await updateCI(this.selectedRowKeys[i], payload, false)
|
||||
.then(() => {
|
||||
successNum += 1
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((error) => {
|
||||
errorMsg = errorMsg + '\n' + `${this.selectedRowKeys[i]}:${error.response?.data?.message ?? ''}`
|
||||
this.$notification.warning({
|
||||
key,
|
||||
message: this.$t('warning'),
|
||||
description: errorMsg,
|
||||
duration: 0,
|
||||
style: { whiteSpace: 'break-spaces' },
|
||||
})
|
||||
errorNum += 1
|
||||
})
|
||||
.finally(() => {
|
||||
|
@@ -24,7 +24,9 @@
|
||||
/>
|
||||
</template>
|
||||
<template v-if="parentsType && parentsType.length">
|
||||
<a-divider style="font-size:14px;margin:14px 0;font-weight:700;">{{ $t('cmdb.menu.citypeRelation') }}</a-divider>
|
||||
<a-divider style="font-size:14px;margin:14px 0;font-weight:700;">{{
|
||||
$t('cmdb.menu.citypeRelation')
|
||||
}}</a-divider>
|
||||
<a-form>
|
||||
<a-row :gutter="24" align="top" type="flex">
|
||||
<a-col :span="12" v-for="item in parentsType" :key="item.id">
|
||||
@@ -40,7 +42,11 @@
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-input :placeholder="$t('cmdb.ci.tips1')" v-model="parentsForm[item.name].value" style="width: 50%" />
|
||||
<a-input
|
||||
:placeholder="$t('cmdb.ci.tips1')"
|
||||
v-model="parentsForm[item.name].value"
|
||||
style="width: 50%"
|
||||
/>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
@@ -214,6 +220,7 @@ export default {
|
||||
if (otherGroupAttr.length) {
|
||||
_attributesByGroup.push({ id: -1, name: this.$t('other'), attributes: otherGroupAttr })
|
||||
}
|
||||
console.log(otherGroupAttr, _attributesByGroup)
|
||||
this.attributesByGroup = _attributesByGroup
|
||||
})
|
||||
},
|
||||
@@ -290,6 +297,38 @@ export default {
|
||||
_this.$emit('reload', { ci_id: res.ci_id })
|
||||
})
|
||||
}
|
||||
|
||||
// this.form.validateFields((err, values) => {
|
||||
// if (err) {
|
||||
// _this.$message.error('字段填写不符合要求!')
|
||||
// return
|
||||
// }
|
||||
// Object.keys(values).forEach((k) => {
|
||||
// if (Object.prototype.toString.call(values[k]) === '[object Object]' && values[k]) {
|
||||
// values[k] = values[k].format('YYYY-MM-DD HH:mm:ss')
|
||||
// }
|
||||
// const _tempFind = this.attributeList.find((item) => item.name === k)
|
||||
// if (_tempFind.value_type === '6') {
|
||||
// values[k] = values[k] ? JSON.parse(values[k]) : undefined
|
||||
// }
|
||||
// })
|
||||
|
||||
// if (_this.action === 'update') {
|
||||
// _this.$emit('submit', values)
|
||||
// return
|
||||
// }
|
||||
// values.ci_type = _this.typeId
|
||||
// console.log(values)
|
||||
// this.attributesByGroup.forEach((group) => {
|
||||
// this.$refs[`createInstanceFormByGroup_${group.id}`][0].getData()
|
||||
// })
|
||||
// console.log(1111)
|
||||
// // addCI(values).then((res) => {
|
||||
// // _this.$message.success('新增成功!')
|
||||
// // _this.visible = false
|
||||
// // _this.$emit('reload')
|
||||
// // })
|
||||
// })
|
||||
},
|
||||
handleClose() {
|
||||
this.visible = false
|
||||
@@ -357,6 +396,9 @@ export default {
|
||||
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)
|
||||
|
@@ -60,7 +60,7 @@
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="attr.is_list">
|
||||
<span> {{ ci[attr.name].join(',') }}</span>
|
||||
<span> {{ ci[attr.name] && Array.isArray(ci[attr.name]) ? ci[attr.name].join(',') : ci[attr.name] }}</span>
|
||||
</template>
|
||||
<template v-else>{{ getName(ci[attr.name]) }}</template>
|
||||
</span>
|
||||
@@ -105,23 +105,6 @@
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
:style="{ width: '100%' }"
|
||||
v-decorator="[
|
||||
attr.name,
|
||||
{
|
||||
rules: [{ required: attr.is_required }],
|
||||
},
|
||||
]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
v-else-if="attr.is_list"
|
||||
mode="tags"
|
||||
showSearch
|
||||
allowClear
|
||||
size="small"
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
>
|
||||
</a-select>
|
||||
<a-input-number
|
||||
size="small"
|
||||
v-decorator="[
|
||||
@@ -131,7 +114,7 @@
|
||||
},
|
||||
]"
|
||||
style="width: 100%"
|
||||
v-else-if="attr.value_type === '0' || attr.value_type === '1'"
|
||||
v-else-if="(attr.value_type === '0' || attr.value_type === '1') && !attr.is_list"
|
||||
/>
|
||||
<a-date-picker
|
||||
size="small"
|
||||
@@ -144,22 +127,9 @@
|
||||
style="width: 100%"
|
||||
:format="attr.value_type === '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
|
||||
:valueFormat="attr.value_type === '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
|
||||
v-else-if="attr.value_type === '4' || attr.value_type === '3'"
|
||||
v-else-if="(attr.value_type === '4' || attr.value_type === '3') && !attr.is_list"
|
||||
:showTime="attr.value_type === '4' ? false : { format: 'HH:mm:ss' }"
|
||||
/>
|
||||
<!-- <a-input
|
||||
size="small"
|
||||
@focus="(e) => handleFocusInput(e, attr)"
|
||||
v-decorator="[
|
||||
attr.name,
|
||||
{
|
||||
validateTrigger: ['submit'],
|
||||
rules: [{ required: attr.is_required }],
|
||||
},
|
||||
]"
|
||||
style="width: 100%"
|
||||
v-else-if="attr.value_type === '6'"
|
||||
/> -->
|
||||
<a-input
|
||||
size="small"
|
||||
v-decorator="[
|
||||
@@ -241,7 +211,9 @@ export default {
|
||||
this.$nextTick(async () => {
|
||||
if (this.attr.is_list && !this.attr.is_choice) {
|
||||
this.form.setFieldsValue({
|
||||
[`${this.attr.name}`]: this.ci[this.attr.name] || null,
|
||||
[`${this.attr.name}`]: Array.isArray(this.ci[this.attr.name])
|
||||
? this.ci[this.attr.name].join(',')
|
||||
: this.ci[this.attr.name],
|
||||
})
|
||||
return
|
||||
}
|
||||
|
50
cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailDrawer.vue
Normal file
50
cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailDrawer.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
width="80%"
|
||||
placement="left"
|
||||
@close="
|
||||
() => {
|
||||
visible = false
|
||||
}
|
||||
"
|
||||
:visible="visible"
|
||||
:hasTitle="false"
|
||||
:hasFooter="false"
|
||||
:bodyStyle="{ padding: 0, height: '100vh' }"
|
||||
destroyOnClose
|
||||
>
|
||||
<CiDetailTab ref="ciDetailTab" :typeId="typeId" :treeViewsLevels="treeViewsLevels" />
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CiDetailTab from './ciDetailTab.vue'
|
||||
export default {
|
||||
name: 'CiDetailDrawer',
|
||||
components: { CiDetailTab },
|
||||
props: {
|
||||
typeId: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
treeViewsLevels: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
|
||||
this.visible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.ciDetailTab.create(ciId, activeTabKey, ciDetailRelationKey)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
@@ -39,7 +39,11 @@
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<template #operation_default="{ row }">
|
||||
<a-popconfirm arrowPointAtCenter :title="$t('cmdb.ci.confirmDeleteRelation')" @confirm="deleteRelation(row._id, ciId)">
|
||||
<a-popconfirm
|
||||
arrowPointAtCenter
|
||||
:title="$t('cmdb.ci.confirmDeleteRelation')"
|
||||
@confirm="deleteRelation(row._id, ciId)"
|
||||
>
|
||||
<a
|
||||
:disabled="!canEdit[parent.id]"
|
||||
:style="{
|
||||
@@ -82,7 +86,11 @@
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<template #operation_default="{ row }">
|
||||
<a-popconfirm arrowPointAtCenter :title="$t('cmdb.ci.confirmDeleteRelation')" @confirm="deleteRelation(ciId, row._id)">
|
||||
<a-popconfirm
|
||||
arrowPointAtCenter
|
||||
:title="$t('cmdb.ci.confirmDeleteRelation')"
|
||||
@confirm="deleteRelation(ciId, row._id)"
|
||||
>
|
||||
<a
|
||||
:disabled="!canEdit[child.id]"
|
||||
:style="{
|
||||
@@ -416,6 +424,7 @@ export default {
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-detail-relation {
|
||||
height: 100%;
|
||||
.ci-detail-relation-table-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div
|
||||
id="ci-detail-relation-topo"
|
||||
class="ci-detail-relation-topo"
|
||||
:style="{ width: '100%', marginTop: '20px', height: 'calc(100vh - 136px)' }"
|
||||
:style="{ width: '100%', marginTop: '20px', height: 'calc(100% - 44px)' }"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
|
@@ -1,23 +1,13 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
width="80%"
|
||||
placement="left"
|
||||
@close="
|
||||
() => {
|
||||
visible = false
|
||||
}
|
||||
"
|
||||
:visible="visible"
|
||||
:hasTitle="false"
|
||||
:hasFooter="false"
|
||||
:bodyStyle="{ padding: 0, height: '100vh' }"
|
||||
wrapClassName="ci-detail"
|
||||
destroyOnClose
|
||||
>
|
||||
<a-tabs v-model="activeTabKey" @change="changeTab">
|
||||
<div :style="{ height: '100%' }">
|
||||
<a-tabs v-if="hasPermission" class="ci-detail-tab" v-model="activeTabKey" @change="changeTab">
|
||||
<a @click="shareCi" slot="tabBarExtraContent" :style="{ marginRight: '24px' }">
|
||||
<a-icon type="share-alt" />
|
||||
{{ $t('cmdb.ci.share') }}
|
||||
</a>
|
||||
<a-tab-pane key="tab_1">
|
||||
<span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span>
|
||||
<div :style="{ maxHeight: `${windowHeight - 44}px`, overflow: 'auto', padding: '24px' }" class="ci-detail-attr">
|
||||
<div class="ci-detail-attr">
|
||||
<el-descriptions
|
||||
:title="group.name || $t('other')"
|
||||
:key="group.name"
|
||||
@@ -37,18 +27,18 @@
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_2">
|
||||
<span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
|
||||
<div :style="{ padding: '24px' }">
|
||||
<div :style="{ height: '100%', padding: '24px', overflow: 'auto' }">
|
||||
<CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_3">
|
||||
<span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span>
|
||||
<div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }">
|
||||
<div :style="{ padding: '24px', height: '100%' }">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
:data="ciHistory"
|
||||
size="small"
|
||||
:max-height="`${windowHeight - 94}px`"
|
||||
height="auto"
|
||||
:span-method="mergeRowMethod"
|
||||
border
|
||||
:scroll-y="{ enabled: false }"
|
||||
@@ -88,12 +78,22 @@
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_4">
|
||||
<span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span>
|
||||
<div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }">
|
||||
<div :style="{ padding: '24px', height: '100%' }">
|
||||
<TriggerTable :ci_id="ci._id" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</CustomDrawer>
|
||||
<a-empty
|
||||
v-else
|
||||
:image-style="{
|
||||
height: '100px',
|
||||
}"
|
||||
:style="{ paddingTop: '20%' }"
|
||||
>
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<span slot="description"> {{ $t('cmdb.ci.noPermission') }} </span>
|
||||
</a-empty>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -105,8 +105,8 @@ import { getCIById } from '@/modules/cmdb/api/ci'
|
||||
import CiDetailAttrContent from './ciDetailAttrContent.vue'
|
||||
import CiDetailRelation from './ciDetailRelation.vue'
|
||||
import TriggerTable from '../../operation_history/modules/triggerTable.vue'
|
||||
|
||||
export default {
|
||||
name: 'CiDetailTab',
|
||||
components: {
|
||||
ElDescriptions: Descriptions,
|
||||
ElDescriptionsItem: DescriptionsItem,
|
||||
@@ -126,7 +126,6 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
ci: {},
|
||||
attributeGroups: [],
|
||||
activeTabKey: 'tab_1',
|
||||
@@ -134,13 +133,13 @@ export default {
|
||||
ciHistory: [],
|
||||
ciId: null,
|
||||
ci_types: [],
|
||||
hasPermission: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
|
||||
operateTypeMap() {
|
||||
return {
|
||||
0: this.$t('new'),
|
||||
@@ -156,10 +155,22 @@ export default {
|
||||
},
|
||||
}
|
||||
},
|
||||
inject: ['reload', 'handleSearch', 'attrList'],
|
||||
inject: {
|
||||
reload: {
|
||||
from: 'reload',
|
||||
default: null,
|
||||
},
|
||||
handleSearch: {
|
||||
from: 'handleSearch',
|
||||
default: null,
|
||||
},
|
||||
attrList: {
|
||||
from: 'attrList',
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
|
||||
this.visible = true
|
||||
async create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
|
||||
this.activeTabKey = activeTabKey
|
||||
if (activeTabKey === 'tab_2') {
|
||||
this.$nextTick(() => {
|
||||
@@ -167,12 +178,14 @@ export default {
|
||||
})
|
||||
}
|
||||
this.ciId = ciId
|
||||
await this.getCI()
|
||||
if (this.hasPermission) {
|
||||
this.getAttributes()
|
||||
this.getCI()
|
||||
this.getCIHistory()
|
||||
getCITypes().then((res) => {
|
||||
this.ci_types = res.ci_types
|
||||
})
|
||||
}
|
||||
},
|
||||
getAttributes() {
|
||||
getCITypeGroupById(this.typeId, { need_other: 1 })
|
||||
@@ -181,11 +194,14 @@ export default {
|
||||
})
|
||||
.catch((e) => {})
|
||||
},
|
||||
getCI() {
|
||||
getCIById(this.ciId)
|
||||
async getCI() {
|
||||
await getCIById(this.ciId)
|
||||
.then((res) => {
|
||||
// this.ci = res.ci
|
||||
if (res.result.length) {
|
||||
this.ci = res.result[0]
|
||||
} else {
|
||||
this.hasPermission = false
|
||||
}
|
||||
})
|
||||
.catch((e) => {})
|
||||
},
|
||||
@@ -270,10 +286,14 @@ export default {
|
||||
// 修改的字段为树形视图订阅的字段 则全部reload
|
||||
setTimeout(() => {
|
||||
if (_find) {
|
||||
if (this.reload) {
|
||||
this.reload()
|
||||
}
|
||||
} else {
|
||||
if (this.handleSearch) {
|
||||
this.handleSearch()
|
||||
}
|
||||
}
|
||||
}, 500)
|
||||
},
|
||||
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
|
||||
@@ -303,23 +323,49 @@ export default {
|
||||
// 修改的字段为树形视图订阅的字段 则全部reload
|
||||
setTimeout(() => {
|
||||
if (_find) {
|
||||
if (this.reload) {
|
||||
this.reload()
|
||||
}
|
||||
} else {
|
||||
if (this.handleSearch) {
|
||||
this.handleSearch()
|
||||
}
|
||||
}
|
||||
}, 500)
|
||||
},
|
||||
shareCi() {
|
||||
const text = `${document.location.host}/cmdb/cidetail/${this.typeId}/${this.ciId}`
|
||||
this.$copyText(text)
|
||||
.then(() => {
|
||||
this.$message.success(this.$t('copySuccess'))
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error(this.$t('cmdb.ci.copyFailed'))
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less">
|
||||
.ci-detail {
|
||||
.ci-detail-tab {
|
||||
height: 100%;
|
||||
.ant-tabs-content {
|
||||
height: calc(100% - 45px);
|
||||
.ant-tabs-tabpane {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.ant-tabs-bar {
|
||||
margin: 0;
|
||||
}
|
||||
.ant-tabs-extra-content {
|
||||
line-height: 44px;
|
||||
}
|
||||
.ci-detail-attr {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding: 24px;
|
||||
.el-descriptions-item__content {
|
||||
cursor: default;
|
||||
&:hover a {
|
@@ -22,7 +22,14 @@
|
||||
attr.name,
|
||||
{
|
||||
rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
|
||||
initialValue: attr.default && attr.default.default ? attr.default.default : attr.is_list ? [] : null,
|
||||
initialValue:
|
||||
attr.default && attr.default.default
|
||||
? attr.is_list
|
||||
? Array.isArray(attr.default.default)
|
||||
? attr.default.default
|
||||
: attr.default.default.split(',')
|
||||
: attr.default.default
|
||||
: null,
|
||||
},
|
||||
]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
@@ -53,19 +60,18 @@
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
<a-input
|
||||
v-else-if="attr.is_list"
|
||||
mode="tags"
|
||||
:style="{ width: '100%' }"
|
||||
v-decorator="[
|
||||
attr.name,
|
||||
{
|
||||
rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
|
||||
initialValue: attr.default && attr.default.default ? attr.default.default : attr.is_list ? [] : null,
|
||||
initialValue: attr.default && attr.default.default ? attr.default.default : '',
|
||||
},
|
||||
]"
|
||||
>
|
||||
</a-select>
|
||||
</a-input>
|
||||
<a-input-number
|
||||
v-decorator="[
|
||||
attr.name,
|
||||
|
@@ -1,103 +0,0 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:visible="visible"
|
||||
width="600"
|
||||
@close="
|
||||
() => {
|
||||
visible = false
|
||||
}
|
||||
"
|
||||
:title="$t('cmdb.ci.attributeSettings')"
|
||||
>
|
||||
<CustomTransfer
|
||||
ref="customTransfer"
|
||||
:dataSource="attrList"
|
||||
:showSearch="true"
|
||||
:listStyle="{
|
||||
width: '230px',
|
||||
height: '500px',
|
||||
}"
|
||||
:titles="[$t('cmdb.components.unselectAttributes'), $t('cmdb.components.selectAttributes')]"
|
||||
:render="item => item.title"
|
||||
:targetKeys="selectedAttrList"
|
||||
@change="handleChange"
|
||||
@selectChange="selectChange"
|
||||
>
|
||||
<span slot="notFoundContent">{{ $t('noData') }}</span>
|
||||
</CustomTransfer>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="handleSubmit" type="primary">{{ $t('confirm') }}</a-button>
|
||||
</div>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { subscribeCIType, getSubscribeAttributes } from '@/modules/cmdb/api/preference'
|
||||
import { getCITypeAttributesByName } from '@/modules/cmdb/api/CITypeAttr'
|
||||
export default {
|
||||
name: 'EditAttrsDrawer',
|
||||
data() {
|
||||
return {
|
||||
attrList: [],
|
||||
typeId: null,
|
||||
visible: false,
|
||||
selectedAttrList: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open(typeId) {
|
||||
this.typeId = typeId
|
||||
this.getAttrs()
|
||||
},
|
||||
getAttrs() {
|
||||
getCITypeAttributesByName(this.typeId).then(res => {
|
||||
const attributes = res.attributes
|
||||
getSubscribeAttributes(this.typeId).then(_res => {
|
||||
const attrList = []
|
||||
const selectedAttrList = []
|
||||
const subAttributes = _res.attributes
|
||||
this.instanceSubscribed = _res.is_subscribed
|
||||
subAttributes.forEach(item => {
|
||||
selectedAttrList.push(item.id.toString())
|
||||
})
|
||||
|
||||
attributes.forEach(item => {
|
||||
const data = {
|
||||
key: item.id.toString(),
|
||||
title: item.alias || item.name,
|
||||
}
|
||||
attrList.push(data)
|
||||
})
|
||||
|
||||
this.attrList = attrList
|
||||
this.selectedAttrList = selectedAttrList
|
||||
this.visible = true
|
||||
})
|
||||
})
|
||||
},
|
||||
handleChange(targetKeys, direction, moveKeys) {
|
||||
this.selectedAttrList = targetKeys
|
||||
},
|
||||
handleSubmit() {
|
||||
const that = this
|
||||
if (this.selectedAttrList.length) {
|
||||
subscribeCIType(this.typeId, this.selectedAttrList).then(res => {
|
||||
this.$message.success(this.$t('cmdb.components.subSuccess'))
|
||||
this.visible = false
|
||||
this.$emit('refresh')
|
||||
})
|
||||
} else {
|
||||
this.$confirm({
|
||||
title: that.$t('warning'),
|
||||
content: that.$t('cmdb.ci.tips4'),
|
||||
})
|
||||
}
|
||||
},
|
||||
selectChange(sourceSelectedKeys, targetSelectedKeys) {
|
||||
this.$refs.customTransfer.dbClick(sourceSelectedKeys, targetSelectedKeys, 'title', 'key')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@@ -1 +0,0 @@
|
||||
editAttrsDrawer 这个文件似乎也没用了
|
@@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<div class="attribute-card">
|
||||
<div :class="{ 'attribute-card': true, 'attribute-card-inherited': inherited }">
|
||||
<a-tooltip :title="inherited ? $t('cmdb.ciType.inheritFrom', { name: property.inherited_from }) : ''">
|
||||
<div class="attribute-card-content">
|
||||
<div class="attribute-card-value-type-icon handle" :style="{ ...getPropertyStyle(property) }">
|
||||
<div
|
||||
:class="{ 'attribute-card-value-type-icon': true, handle: !inherited }"
|
||||
:style="{ ...getPropertyStyle(property) }"
|
||||
>
|
||||
<ValueTypeIcon :attr="property" />
|
||||
</div>
|
||||
<div :class="{ 'attribute-card-content-inner': true, 'attribute-card-name-required': property.is_required }">
|
||||
@@ -19,6 +23,8 @@
|
||||
<a @click="openTrigger"><ops-icon type="ops-trigger"/></a>
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
|
||||
<div class="attribute-card-footer">
|
||||
<a-popover
|
||||
trigger="click"
|
||||
@@ -51,7 +57,7 @@
|
||||
</a-space>
|
||||
</a-popover>
|
||||
|
||||
<a-space class="attribute-card-operation">
|
||||
<a-space class="attribute-card-operation" v-if="!inherited">
|
||||
<a v-if="!isStore"><a-icon type="edit" @click="handleEdit"/></a>
|
||||
<a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')">
|
||||
<a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a>
|
||||
@@ -140,6 +146,9 @@ export default {
|
||||
},
|
||||
]
|
||||
},
|
||||
inherited() {
|
||||
return this.property.inherited || false
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getPropertyStyle,
|
||||
@@ -211,13 +220,15 @@ export default {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 12px;
|
||||
cursor: move;
|
||||
background: #ffffff !important;
|
||||
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
|
||||
border-radius: 2px;
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
}
|
||||
.handle {
|
||||
cursor: move;
|
||||
}
|
||||
.attribute-card-content-inner {
|
||||
padding-left: 12px;
|
||||
font-weight: 400;
|
||||
@@ -269,6 +280,12 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
.attribute-card-inherited {
|
||||
background: #f3f4f7;
|
||||
.attribute-card-footer {
|
||||
background: #eaedf3;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.attribute-card-footer-popover {
|
||||
|
@@ -13,7 +13,11 @@
|
||||
<a-form :form="form" :layout="formLayout">
|
||||
<a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.basicConfig') }}</a-divider>
|
||||
<a-col :span="12">
|
||||
<a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" :label="$t('cmdb.ciType.AttributeName')">
|
||||
<a-form-item
|
||||
:label-col="formItemLayout.labelCol"
|
||||
:wrapper-col="formItemLayout.wrapperCol"
|
||||
:label="$t('cmdb.ciType.AttributeName')"
|
||||
>
|
||||
<a-input
|
||||
:disabled="true"
|
||||
name="name"
|
||||
@@ -35,12 +39,20 @@
|
||||
</a-col>
|
||||
<a-col
|
||||
:span="12"
|
||||
><a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" :label="$t('alias')">
|
||||
><a-form-item
|
||||
:label-col="formItemLayout.labelCol"
|
||||
:wrapper-col="formItemLayout.wrapperCol"
|
||||
:label="$t('alias')"
|
||||
>
|
||||
<a-input name="alias" v-decorator="['alias', { rules: [] }]" /> </a-form-item
|
||||
></a-col>
|
||||
<a-col
|
||||
:span="12"
|
||||
><a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" :label="$t('cmdb.ciType.DataType')">
|
||||
><a-form-item
|
||||
:label-col="formItemLayout.labelCol"
|
||||
:wrapper-col="formItemLayout.wrapperCol"
|
||||
:label="$t('cmdb.ciType.DataType')"
|
||||
>
|
||||
<a-select
|
||||
:disabled="true"
|
||||
name="value_type"
|
||||
@@ -59,13 +71,12 @@
|
||||
:label="$t('cmdb.ciType.defaultValue')"
|
||||
>
|
||||
<template>
|
||||
<a-select
|
||||
<a-input
|
||||
v-if="form.getFieldValue('is_list')"
|
||||
mode="tags"
|
||||
:style="{ width: '100%' }"
|
||||
v-decorator="['default_value', { rules: [{ required: false }] }]"
|
||||
>
|
||||
</a-select>
|
||||
</a-input>
|
||||
<a-select
|
||||
v-decorator="['default_value', { rules: [{ required: false }] }]"
|
||||
mode="tags"
|
||||
@@ -160,7 +171,11 @@
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
|
||||
<a-form-item :label-col="{ span: 8 }" :wrapper-col="horizontalFormItemLayout.wrapperCol" :label="$t('cmdb.ciType.unique')">
|
||||
<a-form-item
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="horizontalFormItemLayout.wrapperCol"
|
||||
:label="$t('cmdb.ciType.unique')"
|
||||
>
|
||||
<a-switch
|
||||
:disabled="isShowComputedArea"
|
||||
@change="onChange"
|
||||
@@ -282,6 +297,11 @@
|
||||
</a-col>
|
||||
<a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider>
|
||||
<a-row>
|
||||
<a-col :span="24" v-if="!['6'].includes(currentValueType)">
|
||||
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 12 }" :label="$t('cmdb.ciType.reg')">
|
||||
<RegSelect :isShowErrorMsg="false" v-model="re_check" :limitedFormat="getLimitedFormat()" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')">
|
||||
<FontArea ref="fontArea" />
|
||||
@@ -303,11 +323,7 @@
|
||||
<span
|
||||
style="position:relative;white-space:pre;"
|
||||
>{{ $t('cmdb.ciType.computedAttribute') }}
|
||||
<a-tooltip
|
||||
:title="
|
||||
$t('cmdb.ciType.computedAttributeTips')
|
||||
"
|
||||
>
|
||||
<a-tooltip :title="$t('cmdb.ciType.computedAttributeTips')">
|
||||
<a-icon
|
||||
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
@@ -355,6 +371,8 @@ import _ from 'lodash'
|
||||
import moment from 'moment'
|
||||
import vueJsonEditor from 'vue-json-editor'
|
||||
import {
|
||||
// createAttribute,
|
||||
// createCITypeAttributes,
|
||||
updateAttributeById,
|
||||
updateCITypeAttributesById,
|
||||
canDefineComputed,
|
||||
@@ -364,10 +382,11 @@ import { valueTypeMap } from '../../utils/const'
|
||||
import ComputedArea from './computedArea.vue'
|
||||
import PreValueArea from './preValueArea.vue'
|
||||
import FontArea from './fontArea.vue'
|
||||
import RegSelect from '@/components/RegexSelect'
|
||||
|
||||
export default {
|
||||
name: 'AttributeEditForm',
|
||||
components: { ComputedArea, PreValueArea, vueJsonEditor, FontArea },
|
||||
components: { ComputedArea, PreValueArea, vueJsonEditor, FontArea, RegSelect },
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
@@ -395,6 +414,7 @@ export default {
|
||||
isShowComputedArea: false,
|
||||
|
||||
defaultForDatetime: '',
|
||||
re_check: {},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -517,15 +537,30 @@ export default {
|
||||
})
|
||||
}
|
||||
console.log(_record)
|
||||
if (!['6'].includes(_record.value_type) && _record.re_check) {
|
||||
this.re_check = {
|
||||
value: _record.re_check,
|
||||
}
|
||||
} else {
|
||||
this.re_check = {}
|
||||
}
|
||||
if (_record.default) {
|
||||
this.$nextTick(() => {
|
||||
if (_record.value_type === '0') {
|
||||
if (_record.is_list) {
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
default_value: _record.default.default ? _record.default.default : '',
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.form.setFieldsValue({
|
||||
default_value: _record.default.default ? [_record.default.default] : [],
|
||||
})
|
||||
}
|
||||
} else if (_record.value_type === '6') {
|
||||
this.default_value_json = _record?.default?.default || null
|
||||
} else if (_record.value_type === '3' || _record.value_type === '4') {
|
||||
} else if ((_record.value_type === '3' || _record.value_type === '4') && !_record.is_list) {
|
||||
if (_record?.default?.default === '$created_at' || _record?.default?.default === '$updated_at') {
|
||||
this.defaultForDatetime = _record.default.default
|
||||
this.form.setFieldsValue({
|
||||
@@ -584,6 +619,9 @@ export default {
|
||||
await this.form.validateFields(async (err, values) => {
|
||||
if (!err) {
|
||||
console.log('Received values of form: ', values)
|
||||
// if (values.choice_value) {
|
||||
// values.choice_value = values.choice_value.split('\n')
|
||||
// }
|
||||
|
||||
if (this.record.is_required !== values.is_required || this.record.default_show !== values.default_show) {
|
||||
console.log('changed is_required')
|
||||
@@ -598,7 +636,11 @@ export default {
|
||||
delete values['is_required']
|
||||
const { default_value } = values
|
||||
if (values.value_type === '0' && default_value) {
|
||||
if (values.is_list) {
|
||||
values.default = { default: default_value || null }
|
||||
} else {
|
||||
values.default = { default: default_value[0] || null }
|
||||
}
|
||||
} else if (values.value_type === '6') {
|
||||
if (this.default_value_json_right) {
|
||||
values.default = { default: this.default_value_json }
|
||||
@@ -606,13 +648,13 @@ export default {
|
||||
values.default = { default: null }
|
||||
}
|
||||
} else if (default_value || default_value === 0) {
|
||||
if (values.value_type === '3') {
|
||||
if (values.value_type === '3' && !values.is_list) {
|
||||
if (default_value === '$created_at' || default_value === '$updated_at') {
|
||||
values.default = { default: default_value }
|
||||
} else {
|
||||
values.default = { default: moment(default_value).format('YYYY-MM-DD HH:mm:ss') }
|
||||
}
|
||||
} else if (values.value_type === '4') {
|
||||
} else if (values.value_type === '4' && !values.is_list) {
|
||||
values.default = { default: moment(default_value).format('YYYY-MM-DD') }
|
||||
} else {
|
||||
values.default = { default: default_value }
|
||||
@@ -641,6 +683,9 @@ export default {
|
||||
values.value_type = '2'
|
||||
values.is_link = true
|
||||
}
|
||||
if (values.value_type !== '6') {
|
||||
values.re_check = this.re_check?.value ?? null
|
||||
}
|
||||
if (values.id) {
|
||||
await this.updateAttribute(values.id, { ...values, option: { fontOptions } }, isCalcComputed)
|
||||
} else {
|
||||
@@ -698,6 +743,21 @@ export default {
|
||||
async handleCalcComputed() {
|
||||
await this.handleSubmit(true)
|
||||
},
|
||||
getLimitedFormat() {
|
||||
if (['0'].includes(this.currentValueType)) {
|
||||
return ['number', 'phone', 'landline', 'zipCode', 'IDCard', 'monetaryAmount', 'custom']
|
||||
}
|
||||
if (['1'].includes(this.currentValueType)) {
|
||||
return ['number', 'monetaryAmount', 'custom']
|
||||
}
|
||||
if (['3', '4', '5'].includes(this.currentValueType)) {
|
||||
return ['custom']
|
||||
}
|
||||
if (this.currentValueType === '8') {
|
||||
return ['link', 'custom']
|
||||
}
|
||||
return []
|
||||
},
|
||||
},
|
||||
watch: {},
|
||||
}
|
||||
|
@@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-modal v-model="addGroupModal" :title="$t('cmdb.ciType.addGroup')" @cancel="handleCancelCreateGroup" @ok="handleCreateGroup">
|
||||
<a-modal
|
||||
v-model="addGroupModal"
|
||||
:title="$t('cmdb.ciType.addGroup')"
|
||||
@cancel="handleCancelCreateGroup"
|
||||
@ok="handleCreateGroup"
|
||||
>
|
||||
<span>
|
||||
<a-form-item :label="$t('name')" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-input type="text" v-model.trim="newGroupName" />
|
||||
@@ -9,21 +14,12 @@
|
||||
</a-modal>
|
||||
<div class="ci-types-attributes" :style="{ maxHeight: `${windowHeight - 104}px` }">
|
||||
<a-space style="margin-bottom: 10px">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleAddGroup"
|
||||
size="small"
|
||||
class="ops-button-primary"
|
||||
icon="plus"
|
||||
>{{ $t('cmdb.ciType.group') }}</a-button
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleOpenUniqueConstraint"
|
||||
size="small"
|
||||
class="ops-button-primary"
|
||||
>{{ $t('cmdb.ciType.uniqueConstraint') }}</a-button
|
||||
>
|
||||
<a-button type="primary" @click="handleAddGroup" size="small" class="ops-button-primary" icon="plus">{{
|
||||
$t('cmdb.ciType.group')
|
||||
}}</a-button>
|
||||
<a-button type="primary" @click="handleOpenUniqueConstraint" size="small" class="ops-button-primary">{{
|
||||
$t('cmdb.ciType.uniqueConstraint')
|
||||
}}</a-button>
|
||||
</a-space>
|
||||
<div :key="CITypeGroup.id" v-for="(CITypeGroup, index) in CITypeGroups">
|
||||
<div>
|
||||
@@ -33,7 +29,11 @@
|
||||
>
|
||||
<span style="font-weight:700">{{ CITypeGroup.name }}</span>
|
||||
<span style="color: #c3cdd7;margin:0 5px;">({{ CITypeGroup.attributes.length }})</span>
|
||||
<a @click="handleEditGroupName(index, CITypeGroup)">
|
||||
|
||||
<a v-if="!CITypeGroup.inherited" @click="handleEditGroupName(index, CITypeGroup)">
|
||||
<a-icon type="edit" />
|
||||
</a>
|
||||
<a v-else :style="{ cursor: 'not-allowed', color: 'gray' }">
|
||||
<a-icon type="edit" />
|
||||
</a>
|
||||
</div>
|
||||
@@ -71,7 +71,13 @@
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ $t('cmdb.ciType.deleteGroup') }}</template>
|
||||
<a style="color:red;"><a-icon type="delete" @click="handleDeleteGroup(CITypeGroup)"/></a>
|
||||
<a
|
||||
:style="{ color: CITypeGroup.inherited ? 'gray' : 'red' }"
|
||||
:disabled="CITypeGroup.inherited"
|
||||
><a-icon
|
||||
type="delete"
|
||||
@click="handleDeleteGroup(CITypeGroup)"
|
||||
/></a>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</div>
|
||||
@@ -82,7 +88,7 @@
|
||||
@start="drag = true"
|
||||
@change="
|
||||
(e) => {
|
||||
handleChange(e, CITypeGroup.id)
|
||||
handleChange(e, CITypeGroup.name)
|
||||
}
|
||||
"
|
||||
:filter="'.filter-empty'"
|
||||
@@ -124,7 +130,7 @@
|
||||
@start="drag = true"
|
||||
@change="
|
||||
(e) => {
|
||||
handleChange(e, -1)
|
||||
handleChange(e, null)
|
||||
}
|
||||
"
|
||||
:animation="300"
|
||||
@@ -144,18 +150,18 @@
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
<attribute-edit-form
|
||||
<AttributeEditForm
|
||||
ref="attributeEditForm"
|
||||
:CITypeId="CITypeId"
|
||||
:CITypeName="CITypeName"
|
||||
@ok="handleOk"
|
||||
></attribute-edit-form>
|
||||
<new-ci-type-attr-modal
|
||||
></AttributeEditForm>
|
||||
<NewCiTypeAttrModal
|
||||
ref="newCiTypeAttrModal"
|
||||
:CITypeId="CITypeId"
|
||||
:linked-ids="linkedIds"
|
||||
@ok="handleOk"
|
||||
></new-ci-type-attr-modal>
|
||||
></NewCiTypeAttrModal>
|
||||
<UniqueConstraint ref="uniqueConstraint" :CITypeId="CITypeId" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -347,8 +353,8 @@ export default {
|
||||
},
|
||||
|
||||
handleMoveGroup(beforeIndex, afterIndex) {
|
||||
const fromGroupId = this.CITypeGroups[beforeIndex].id
|
||||
const toGroupId = this.CITypeGroups[afterIndex].id
|
||||
const fromGroupId = this.CITypeGroups[beforeIndex].name
|
||||
const toGroupId = this.CITypeGroups[afterIndex].name
|
||||
transferCITypeGroupIndex(this.CITypeId, { from: fromGroupId, to: toGroupId }).then((res) => {
|
||||
this.$message.success(this.$t('operateSuccess'))
|
||||
const beforeGroup = this.CITypeGroups[beforeIndex]
|
||||
@@ -414,14 +420,14 @@ export default {
|
||||
})
|
||||
},
|
||||
handleChange(e, group) {
|
||||
console.log('changess')
|
||||
console.log('changess', group)
|
||||
if (e.hasOwnProperty('moved') && e.moved.oldIndex !== e.moved.newIndex) {
|
||||
if (group === -1) {
|
||||
this.$message.error(this.$t('cmdb.ciType.attributeSortedTips'))
|
||||
} else {
|
||||
transferCITypeAttrIndex(this.CITypeId, {
|
||||
from: { attr_id: e.moved.element.id, group_id: group > -1 ? group : null },
|
||||
to: { order: e.moved.newIndex, group_id: group > -1 ? group : null },
|
||||
from: { attr_id: e.moved.element.id, group_name: group },
|
||||
to: { order: e.moved.newIndex, group_name: group },
|
||||
})
|
||||
.then((res) => this.$message.success(this.$t('updateSuccess')))
|
||||
.catch(() => {
|
||||
@@ -431,14 +437,14 @@ export default {
|
||||
}
|
||||
|
||||
if (e.hasOwnProperty('added')) {
|
||||
this.addRemoveGroupFlag = { to: { group_id: group > -1 ? group : null, order: e.added.newIndex }, inited: true }
|
||||
this.addRemoveGroupFlag = { to: { group_name: group, order: e.added.newIndex }, inited: true }
|
||||
}
|
||||
|
||||
if (e.hasOwnProperty('removed')) {
|
||||
this.$nextTick(() => {
|
||||
transferCITypeAttrIndex(this.CITypeId, {
|
||||
from: { attr_id: e.removed.element.id, group_id: group > -1 ? group : null },
|
||||
to: { group_id: this.addRemoveGroupFlag.to.group_id, order: this.addRemoveGroupFlag.to.order },
|
||||
from: { attr_id: e.removed.element.id, group_name: group },
|
||||
to: { group_name: this.addRemoveGroupFlag.to.group_name, order: this.addRemoveGroupFlag.to.order },
|
||||
})
|
||||
.then((res) => this.$message.success(this.$t('saveSuccess')))
|
||||
.catch(() => {
|
||||
|
@@ -21,7 +21,10 @@
|
||||
message: $t('cmdb.ciType.attributeNameTips'),
|
||||
pattern: RegExp('^(?!\\d)[a-zA-Z_0-9]+$'),
|
||||
},
|
||||
{ message: $t('cmdb.ciType.buildinAttribute'), pattern: RegExp('^(?!(id|_id|ci_id|type|_type|ci_type)$).*$') },
|
||||
{
|
||||
message: $t('cmdb.ciType.buildinAttribute'),
|
||||
pattern: RegExp('^(?!(id|_id|ci_id|type|_type|ci_type)$).*$'),
|
||||
},
|
||||
],
|
||||
},
|
||||
]"
|
||||
@@ -59,13 +62,12 @@
|
||||
:label="$t('cmdb.ciType.defaultValue')"
|
||||
>
|
||||
<template>
|
||||
<a-select
|
||||
<a-input
|
||||
v-if="form.getFieldValue('is_list')"
|
||||
mode="tags"
|
||||
:style="{ width: '100%' }"
|
||||
v-decorator="['default_value', { rules: [{ required: false }] }]"
|
||||
>
|
||||
</a-select>
|
||||
</a-input>
|
||||
<a-input-number
|
||||
style="width: 100%"
|
||||
v-else-if="currentValueType === '1'"
|
||||
@@ -162,7 +164,11 @@
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
|
||||
<a-form-item :label-col="{ span: 8 }" :wrapper-col="horizontalFormItemLayout.wrapperCol" :label="$t('cmdb.ciType.unique')">
|
||||
<a-form-item
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="horizontalFormItemLayout.wrapperCol"
|
||||
:label="$t('cmdb.ciType.unique')"
|
||||
>
|
||||
<a-switch
|
||||
:disabled="isShowComputedArea"
|
||||
@change="onChange"
|
||||
@@ -280,6 +286,11 @@
|
||||
</a-col>
|
||||
<a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider>
|
||||
<a-row>
|
||||
<a-col :span="24" v-if="!['6'].includes(currentValueType)">
|
||||
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 12 }" :label="$t('cmdb.ciType.reg')">
|
||||
<RegSelect :isShowErrorMsg="false" v-model="re_check" :limitedFormat="getLimitedFormat()" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')">
|
||||
<FontArea ref="fontArea" />
|
||||
@@ -296,11 +307,7 @@
|
||||
<span
|
||||
style="position:relative;white-space:pre;"
|
||||
>{{ $t('cmdb.ciType.computedAttribute') }}
|
||||
<a-tooltip
|
||||
:title="
|
||||
$t('cmdb.ciType.computedAttributeTips')
|
||||
"
|
||||
>
|
||||
<a-tooltip :title="$t('cmdb.ciType.computedAttributeTips')">
|
||||
<a-icon
|
||||
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
@@ -340,6 +347,7 @@ import { valueTypeMap } from '../../utils/const'
|
||||
import ComputedArea from './computedArea.vue'
|
||||
import PreValueArea from './preValueArea.vue'
|
||||
import FontArea from './fontArea.vue'
|
||||
import RegSelect from '@/components/RegexSelect'
|
||||
|
||||
export default {
|
||||
name: 'CreateNewAttribute',
|
||||
@@ -348,6 +356,7 @@ export default {
|
||||
PreValueArea,
|
||||
vueJsonEditor,
|
||||
FontArea,
|
||||
RegSelect,
|
||||
},
|
||||
props: {
|
||||
hasFooter: {
|
||||
@@ -374,6 +383,8 @@ export default {
|
||||
isShowComputedArea: false,
|
||||
|
||||
defaultForDatetime: '',
|
||||
|
||||
re_check: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -397,8 +408,12 @@ export default {
|
||||
const data = { is_required, default_show }
|
||||
delete values.is_required
|
||||
delete values.default_show
|
||||
if (values.value_type === '0' && default_value && default_value.length) {
|
||||
values.default = { default: default_value[0] }
|
||||
if (values.value_type === '0' && default_value) {
|
||||
if (values.is_list) {
|
||||
values.default = { default: default_value || null }
|
||||
} else {
|
||||
values.default = { default: default_value[0] || null }
|
||||
}
|
||||
} else if (values.value_type === '6') {
|
||||
if (this.default_value_json_right) {
|
||||
values.default = { default: this.default_value_json }
|
||||
@@ -406,13 +421,13 @@ export default {
|
||||
values.default = { default: null }
|
||||
}
|
||||
} else if (default_value || default_value === 0) {
|
||||
if (values.value_type === '3') {
|
||||
if (values.value_type === '3' && !values.is_list) {
|
||||
if (default_value === '$created_at' || default_value === '$updated_at') {
|
||||
values.default = { default: default_value }
|
||||
} else {
|
||||
values.default = { default: moment(default_value).format('YYYY-MM-DD HH:mm:ss') }
|
||||
}
|
||||
} else if (values.value_type === '4') {
|
||||
} else if (values.value_type === '4' && !values.is_list) {
|
||||
values.default = { default: moment(default_value).format('YYYY-MM-DD') }
|
||||
} else {
|
||||
values.default = { default: default_value }
|
||||
@@ -449,6 +464,9 @@ export default {
|
||||
values.value_type = '2'
|
||||
values.is_link = true
|
||||
}
|
||||
if (values.value_type !== '6') {
|
||||
values.re_check = this.re_check?.value ?? null
|
||||
}
|
||||
const { attr_id } = await createAttribute({ ...values, option: { fontOptions } })
|
||||
|
||||
this.form.resetFields()
|
||||
@@ -539,6 +557,21 @@ export default {
|
||||
default_value: key,
|
||||
})
|
||||
},
|
||||
getLimitedFormat() {
|
||||
if (['0'].includes(this.currentValueType)) {
|
||||
return ['number', 'phone', 'landline', 'zipCode', 'IDCard', 'monetaryAmount', 'custom']
|
||||
}
|
||||
if (['1'].includes(this.currentValueType)) {
|
||||
return ['number', 'monetaryAmount', 'custom']
|
||||
}
|
||||
if (['3', '4', '5'].includes(this.currentValueType)) {
|
||||
return ['custom']
|
||||
}
|
||||
if (this.currentValueType === '8') {
|
||||
return ['link', 'custom']
|
||||
}
|
||||
return []
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@@ -53,7 +53,10 @@
|
||||
</a-menu-item>
|
||||
<a-menu-item key="1">
|
||||
<a-space>
|
||||
<a href="/api/v0.1/ci_types/template/export/file"><a-icon type="download" /> {{ $t('download') }}</a>
|
||||
<a
|
||||
href="/api/v0.1/ci_types/template/export/file"
|
||||
><a-icon type="download" /> {{ $t('download') }}</a
|
||||
>
|
||||
</a-space>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
@@ -63,9 +66,11 @@
|
||||
<draggable class="ci-types-left-content" :list="CITypeGroups" @end="handleChangeGroups" filter=".undraggable">
|
||||
<div v-for="g in CITypeGroups" :key="g.id || g.name">
|
||||
<div
|
||||
:class="`${currentGId === g.id && !currentCId ? 'selected' : ''} ci-types-left-group ${
|
||||
:class="
|
||||
`${currentGId === g.id && !currentCId ? 'selected' : ''} ci-types-left-group ${
|
||||
g.id === -1 ? 'undraggable' : ''
|
||||
}`"
|
||||
}`
|
||||
"
|
||||
@click="handleClickGroup(g.id)"
|
||||
>
|
||||
<div>
|
||||
@@ -134,7 +139,9 @@
|
||||
<a><a-icon type="edit" @click="(e) => handleEdit(e, ci)"/></a>
|
||||
<a
|
||||
v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"
|
||||
@click="(e) => handleDownloadCiType(e, ci)">
|
||||
:disabled="ci.inherited"
|
||||
@click="(e) => handleDownloadCiType(e, ci)"
|
||||
>
|
||||
<a-icon type="download" />
|
||||
</a>
|
||||
<a style="color: red" @click="(e) => handleDelete(e, ci)"><a-icon type="delete"/></a>
|
||||
@@ -170,6 +177,7 @@
|
||||
placement="right"
|
||||
width="900px"
|
||||
:destroyOnClose="true"
|
||||
:bodyStyle="{ height: 'calc(100vh - 108px)' }"
|
||||
>
|
||||
<a-form
|
||||
:form="form"
|
||||
@@ -198,6 +206,35 @@
|
||||
<a-form-item :label="$t('alias')">
|
||||
<a-input name="alias" v-decorator="['alias', { rules: [] }]" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('cmdb.ciType.isInherit')">
|
||||
<a-radio-group v-model="isInherit">
|
||||
<a-radio :value="true">
|
||||
是
|
||||
</a-radio>
|
||||
<a-radio :value="false">
|
||||
否
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="isInherit" :label="$t('cmdb.ciType.inheritType')">
|
||||
<CMDBTypeSelect
|
||||
multiple
|
||||
:placeholder="$t('cmdb.ciType.inheritTypePlaceholder')"
|
||||
v-decorator="[
|
||||
'parent_ids',
|
||||
{ rules: [{ required: true, message: $t('cmdb.ciType.inheritTypePlaceholder') }] },
|
||||
]"
|
||||
selectType="ci_type"
|
||||
:class="{
|
||||
'custom-treeselect': true,
|
||||
}"
|
||||
:style="{
|
||||
'--custom-height': '32px',
|
||||
lineHeight: '32px',
|
||||
'--custom-multiple-lineHeight': '14px',
|
||||
}"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('icon')">
|
||||
<IconArea class="ci_types-icon-area" ref="iconArea" />
|
||||
</a-form-item>
|
||||
@@ -233,7 +270,17 @@
|
||||
</div>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('cmdb.ciType.uniqueKey')">
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip :title="$t('cmdb.ciType.uniqueKeyTips')">
|
||||
<a-icon
|
||||
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
theme="filled"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<span>{{ $t('cmdb.ciType.uniqueKey') }}</span>
|
||||
</template>
|
||||
<el-select
|
||||
size="small"
|
||||
filterable
|
||||
@@ -242,6 +289,11 @@
|
||||
:filter-method="filterOption"
|
||||
v-decorator="['unique_key', { rules: [{ required: true, message: $t('cmdb.ciType.uniqueKeySelect') }] }]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
@visible-change="
|
||||
() => {
|
||||
filterInput = ''
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
:key="item.id"
|
||||
@@ -285,7 +337,14 @@ import router, { resetRouter } from '@/router'
|
||||
import store from '@/store'
|
||||
import draggable from 'vuedraggable'
|
||||
import { Select, Option } from 'element-ui'
|
||||
import { createCIType, updateCIType, deleteCIType } from '@/modules/cmdb/api/CIType'
|
||||
import {
|
||||
createCIType,
|
||||
updateCIType,
|
||||
deleteCIType,
|
||||
getCIType,
|
||||
postCiTypeInheritance,
|
||||
deleteCiTypeInheritance,
|
||||
} from '@/modules/cmdb/api/CIType'
|
||||
import {
|
||||
getCITypeGroupsConfig,
|
||||
postCITypeGroup,
|
||||
@@ -305,6 +364,7 @@ import CMDBGrant from '../../components/cmdbGrant'
|
||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||
import AttributeStore from './attributeStore.vue'
|
||||
import { getAllDepAndEmployee } from '@/api/company'
|
||||
import CMDBTypeSelect from '../../components/cmdbTypeSelect'
|
||||
|
||||
export default {
|
||||
name: 'CITypes',
|
||||
@@ -319,6 +379,7 @@ export default {
|
||||
SplitPane,
|
||||
OpsMoveIcon,
|
||||
AttributeStore,
|
||||
CMDBTypeSelect,
|
||||
},
|
||||
inject: ['reload'],
|
||||
data() {
|
||||
@@ -357,6 +418,9 @@ export default {
|
||||
default_order_asc: '1',
|
||||
|
||||
allTreeDepAndEmp: [],
|
||||
|
||||
editCiType: null,
|
||||
isInherit: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -549,8 +613,10 @@ export default {
|
||||
})
|
||||
},
|
||||
onClose() {
|
||||
this.filterInput = ''
|
||||
this.form.resetFields()
|
||||
this.drawerVisible = false
|
||||
this.isInherit = false
|
||||
},
|
||||
handleCreateNewAttrDone() {
|
||||
this.getAttributes()
|
||||
@@ -571,6 +637,22 @@ export default {
|
||||
const icon =
|
||||
_icon && _icon.name ? `${_icon.name}$$${_icon.color || ''}$$${_icon.id || ''}$$${_icon.url || ''}` : ''
|
||||
if (values.id) {
|
||||
const { parent_ids: oldP = [] } = this.editCiType
|
||||
const { parent_ids: newP = [] } = values
|
||||
const { remove, add } = this.compareArrays(newP, oldP)
|
||||
if (add && add.length) {
|
||||
await postCiTypeInheritance({ parent_ids: add, child_id: values.id }).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
if (remove && remove.length) {
|
||||
for (let i = 0; i < remove.length; i++) {
|
||||
await deleteCiTypeInheritance({ parent_id: remove[i], child_id: values.id }).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
delete values.parent_ids
|
||||
await this.updateCIType(values.id, {
|
||||
...values,
|
||||
icon,
|
||||
@@ -581,6 +663,23 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
compareArrays(newArr, oldArr) {
|
||||
const remove = []
|
||||
const add = []
|
||||
for (let i = 0; i < oldArr.length; i++) {
|
||||
const item = oldArr[i]
|
||||
if (newArr.indexOf(item) === -1) {
|
||||
remove.push(item)
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < newArr.length; i++) {
|
||||
const item = newArr[i]
|
||||
if (oldArr.indexOf(item) === -1) {
|
||||
add.push(item)
|
||||
}
|
||||
}
|
||||
return { remove, add }
|
||||
},
|
||||
start(g) {
|
||||
console.log('start', g)
|
||||
this.startId = g.id
|
||||
@@ -755,13 +854,26 @@ export default {
|
||||
router.addRoutes(store.getters.appRoutes)
|
||||
})
|
||||
},
|
||||
handleEdit(e, record) {
|
||||
async handleEdit(e, record) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
this.drawerTitle = this.$t('cmdb.ciType.editCIType')
|
||||
this.drawerVisible = true
|
||||
getCITypeAttributesById(record.id).then((res) => {
|
||||
await getCITypeAttributesById(record.id).then((res) => {
|
||||
this.orderSelectionOptions = res.attributes.filter((item) => item.is_required)
|
||||
})
|
||||
await getCIType(record.id).then((res) => {
|
||||
const ci_type = res.ci_types[0]
|
||||
this.editCiType = ci_type ?? null
|
||||
if (ci_type.parent_ids && ci_type.parent_ids.length) {
|
||||
this.isInherit = true
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
parent_ids: ci_type.parent_ids,
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
this.default_order_asc = record.default_order_attr && record.default_order_attr.startsWith('-') ? '2' : '1'
|
||||
|
||||
@@ -775,6 +887,7 @@ export default {
|
||||
? record.default_order_attr.slice(1)
|
||||
: record.default_order_attr,
|
||||
})
|
||||
|
||||
this.$refs.iconArea.setIcon(
|
||||
record.icon
|
||||
? {
|
||||
@@ -786,7 +899,6 @@ export default {
|
||||
: {}
|
||||
)
|
||||
})
|
||||
})
|
||||
},
|
||||
handleCreatNewAttr() {
|
||||
this.newAttrAreaVisible = !this.newAttrAreaVisible
|
||||
|
@@ -20,7 +20,9 @@
|
||||
"
|
||||
>{{ $t('cancel') }}</a-button
|
||||
>
|
||||
<a-button :loading="confirmLoading" @click="handleSubmit(false)" type="primary">{{ $t('cmdb.ciType.continueAdd') }}</a-button>
|
||||
<a-button :loading="confirmLoading" @click="handleSubmit(false)" type="primary">{{
|
||||
$t('cmdb.ciType.continueAdd')
|
||||
}}</a-button>
|
||||
<a-button :loading="confirmLoading" type="primary" @click="handleSubmit">{{ $t('confirm') }}</a-button>
|
||||
</template>
|
||||
<a-tabs v-model="activeKey">
|
||||
@@ -47,7 +49,7 @@
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { searchAttributes, createCITypeAttributes, updateCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { updateCITypeGroupById, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
|
||||
import { createCITypeGroupById, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
|
||||
import CreateNewAttribute from './ceateNewAttribute.vue'
|
||||
import { valueTypeMap } from '../../utils/const'
|
||||
import AttributesTransfer from '../../components/attributesTransfer'
|
||||
@@ -102,11 +104,11 @@ export default {
|
||||
if (this.currentGroup) {
|
||||
await this.updateCurrentGroup()
|
||||
const { id, name, order, attributes } = this.currentGroup
|
||||
const attrIds = attributes.map((i) => i.id)
|
||||
const attrIds = attributes.filter((i) => !i.inherited).map((i) => i.id)
|
||||
this.targetKeys.forEach((key) => {
|
||||
attrIds.push(Number(key))
|
||||
})
|
||||
await updateCITypeGroupById(id, { name, order, attributes: [...new Set(attrIds)] })
|
||||
await createCITypeGroupById(this.CITypeId, { name, order, attributes: [...new Set(attrIds)] })
|
||||
}
|
||||
this.confirmLoading = false
|
||||
this.handleClose(isCloseModal)
|
||||
@@ -140,9 +142,9 @@ export default {
|
||||
if (this.currentGroup) {
|
||||
await this.updateCurrentGroup()
|
||||
const { id, name, order, attributes } = this.currentGroup
|
||||
const attrIds = attributes.map((i) => i.id)
|
||||
const attrIds = attributes.filter((i) => !i.inherited).map((i) => i.id)
|
||||
attrIds.push(newAttrId)
|
||||
await updateCITypeGroupById(id, { name, order, attributes: attrIds })
|
||||
await createCITypeGroupById(this.CITypeId, { name, order, attributes: attrIds })
|
||||
}
|
||||
this.confirmLoading = false
|
||||
this.loadTotalAttrs()
|
||||
|
@@ -79,7 +79,7 @@
|
||||
<vxe-column
|
||||
align="center"
|
||||
field="is_accept"
|
||||
:title="$t('cmdb.ad.isAccpet')"
|
||||
:title="$t('cmdb.ad.isAccept')"
|
||||
v-bind="columns.length ? { width: '100px' } : { minWidth: '100px' }"
|
||||
:filters="[
|
||||
{ label: $t('yes'), value: true },
|
||||
@@ -92,7 +92,7 @@
|
||||
</vxe-column>
|
||||
<vxe-column
|
||||
field="accept_by"
|
||||
:title="$t('cmdb.ad.accpetBy')"
|
||||
:title="$t('cmdb.ad.acceptBy')"
|
||||
v-bind="columns.length ? { width: '80px' } : { minWidth: '80px' }"
|
||||
:filters="[]"
|
||||
></vxe-column>
|
||||
@@ -186,8 +186,8 @@ export default {
|
||||
this.clickSidebar(Number(_currentType))
|
||||
return
|
||||
}
|
||||
if (res && res.length) {
|
||||
this.clickSidebar(res[0].id)
|
||||
if (res && res.length && res[0].ci_types && res[0].ci_types.length) {
|
||||
this.clickSidebar(res[0].ci_types[0].id)
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -246,7 +246,7 @@ export default {
|
||||
content: that.$t('cmdb.ad.confirmAccept'),
|
||||
onOk() {
|
||||
updateADCAccept(row.id).then(() => {
|
||||
that.$message.success(that.$t('cmdb.ad.accpetSuccess'))
|
||||
that.$message.success(that.$t('cmdb.ad.acceptSuccess'))
|
||||
that.getAdc(false)
|
||||
})
|
||||
},
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div :style="{ height: '100%' }">
|
||||
<vxe-table
|
||||
show-overflow
|
||||
show-header-overflow
|
||||
@@ -7,7 +7,7 @@
|
||||
size="small"
|
||||
class="ops-stripe-table"
|
||||
:data="tableData"
|
||||
v-bind="ci_id ? { maxHeight: `${windowHeight - 94}px` } : { height: `${windowHeight - 225}px` }"
|
||||
v-bind="ci_id ? { height: 'auto' } : { height: `${windowHeight - 225}px` }"
|
||||
>
|
||||
<vxe-column field="trigger_name" :title="$t('cmdb.history.triggerName')"> </vxe-column>
|
||||
<vxe-column field="type" :title="$t('type')">
|
||||
|
@@ -16,8 +16,7 @@
|
||||
height: '23px',
|
||||
fontSize: '14px',
|
||||
}"
|
||||
/></span
|
||||
>
|
||||
/></span>
|
||||
<span
|
||||
class="cmdb-preference-left-card-content"
|
||||
><ops-icon type="cmdb-tree" :style="{ marginRight: '5px' }"/>{{ $t('cmdb.menu.ciTree') }}:
|
||||
@@ -31,14 +30,23 @@
|
||||
height: '23px',
|
||||
fontSize: '14px',
|
||||
}"
|
||||
/></span
|
||||
>
|
||||
/></span>
|
||||
</div>
|
||||
<div class="cmdb-preference-group" v-for="(group, index) in myPreferences" :key="group.name">
|
||||
<div class="cmdb-preference-group-title">
|
||||
<span> <ops-icon :style="{ marginRight: '10px' }" :type="group.icon" />{{ group.name }} </span>
|
||||
</div>
|
||||
<draggable
|
||||
v-model="group.ci_types"
|
||||
:animation="300"
|
||||
@change="
|
||||
(e) => {
|
||||
orderChange(e, group)
|
||||
}
|
||||
"
|
||||
>
|
||||
<div class="cmdb-preference-group-content" v-for="ciType in group.ci_types" :key="ciType.id">
|
||||
<OpsMoveIcon class="cmdb-preference-move-icon" />
|
||||
<div
|
||||
:class="{
|
||||
'cmdb-preference-avatar': true,
|
||||
@@ -82,6 +90,7 @@
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cmdb-preference-right">
|
||||
@@ -124,19 +133,12 @@
|
||||
>
|
||||
</div>
|
||||
<div class="cmdb-preference-colleague">
|
||||
<template v-if="type_id2users[item.id] && type_id2users[item.id].length">
|
||||
<span
|
||||
v-if="type_id2users[item.id] && type_id2users[item.id].length"
|
||||
>{{ type_id2users[item.id].length > 99 ? '99+' : type_id2users[item.id].length
|
||||
}}{{ $t('cmdb.preference.peopleSub') }}</span
|
||||
>
|
||||
<span class="cmdb-preference-colleague-name">
|
||||
<span v-for="uid in type_id2users[item.id].slice(0, 4)" :key="uid">
|
||||
{{ getNameByUid(uid) }}
|
||||
</span>
|
||||
<span class="cmdb-preference-colleague-ellipsis" v-if="type_id2users[item.id].length > 4">...</span>
|
||||
</span>
|
||||
</template>
|
||||
<span v-else :style="{ marginLeft: 'auto' }">{{ $t('cmdb.preference.noSub') }}</span>
|
||||
<span v-else>{{ $t('cmdb.preference.noSub') }}</span>
|
||||
</div>
|
||||
<div class="cmdb-preference-progress">
|
||||
<div class="cmdb-preference-progress-info">
|
||||
@@ -190,15 +192,23 @@ import router, { resetRouter } from '@/router'
|
||||
import store from '@/store'
|
||||
import { mapState } from 'vuex'
|
||||
import moment from 'moment'
|
||||
import draggable from 'vuedraggable'
|
||||
import { getCITypeGroups } from '../../api/ciTypeGroup'
|
||||
import { getPreference, getPreference2, subscribeCIType, subscribeTreeView } from '@/modules/cmdb/api/preference'
|
||||
import {
|
||||
getPreference,
|
||||
getPreference2,
|
||||
subscribeCIType,
|
||||
subscribeTreeView,
|
||||
preferenceCitypeOrder,
|
||||
} from '@/modules/cmdb/api/preference'
|
||||
import CollapseTransition from '@/components/CollapseTransition'
|
||||
import SubscribeSetting from '../../components/subscribeSetting/subscribeSetting'
|
||||
import { getCIAdcStatistics } from '../../api/ci'
|
||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||
|
||||
export default {
|
||||
name: 'Preference',
|
||||
components: { CollapseTransition, SubscribeSetting },
|
||||
components: { CollapseTransition, SubscribeSetting, draggable, OpsMoveIcon },
|
||||
data() {
|
||||
return {
|
||||
citypeData: [],
|
||||
@@ -214,7 +224,6 @@ export default {
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
allUsers: (state) => state.user.allUsers,
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
@@ -277,10 +286,6 @@ export default {
|
||||
}, 300)
|
||||
}
|
||||
},
|
||||
getNameByUid(uid) {
|
||||
const _find = this.allUsers.find((item) => item.uid === uid)
|
||||
return _find?.username[0].toUpperCase() || 'A'
|
||||
},
|
||||
getsubscribedDays(item) {
|
||||
const subscribedTime = this.self.type_id2subs_time[item.id]
|
||||
moment.duration(moment().diff(moment(subscribedTime)))
|
||||
@@ -351,6 +356,17 @@ export default {
|
||||
this.expandKeys.push(group.id)
|
||||
}
|
||||
},
|
||||
orderChange(e, group) {
|
||||
preferenceCitypeOrder({ type_ids: group.ci_types.map((type) => type.id), is_tree: group.type !== 'ci' })
|
||||
.then(() => {
|
||||
if (group.type === 'ci') {
|
||||
this.resetRoute()
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.getCITypes(false)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -426,7 +442,7 @@ export default {
|
||||
align-items: center;
|
||||
height: 45px;
|
||||
padding: 0 8px;
|
||||
cursor: default;
|
||||
cursor: move;
|
||||
justify-content: flex-start;
|
||||
&:hover {
|
||||
background: #ffffff;
|
||||
@@ -437,6 +453,15 @@ export default {
|
||||
white-space: nowrap;
|
||||
margin-left: auto;
|
||||
}
|
||||
.cmdb-preference-move-icon {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.cmdb-preference-move-icon {
|
||||
width: 14px;
|
||||
height: 20px;
|
||||
cursor: move;
|
||||
visibility: hidden;
|
||||
}
|
||||
.cmdb-preference-group-content-title {
|
||||
flex: 1;
|
||||
|
@@ -2,14 +2,55 @@
|
||||
<div :style="{ marginBottom: '-24px', overflow: 'hidden' }">
|
||||
<div v-if="relationViews.name2id && relationViews.name2id.length" class="relation-views-wrapper">
|
||||
<div class="cmdb-views-header">
|
||||
<span class="cmdb-views-header-title">{{ $route.meta.name }}</span>
|
||||
<span
|
||||
class="cmdb-views-header-title"
|
||||
>{{ $route.meta.name }}
|
||||
<div
|
||||
class="ops-list-batch-action"
|
||||
:style="{ backgroundColor: '#c0ceeb' }"
|
||||
v-if="showBatchLevel !== null && batchTreeKey && batchTreeKey.length"
|
||||
>
|
||||
<span
|
||||
@click="
|
||||
() => {
|
||||
$refs.grantModal.open('depart')
|
||||
}
|
||||
"
|
||||
>{{ $t('grant') }}</span
|
||||
>
|
||||
<a-divider type="vertical" />
|
||||
<span
|
||||
@click="
|
||||
() => {
|
||||
$refs.revokeModal.open()
|
||||
}
|
||||
"
|
||||
>{{ $t('revoke') }}</span
|
||||
>
|
||||
<template v-if="showBatchLevel > 0">
|
||||
<a-divider type="vertical" />
|
||||
<span @click="batchDeleteCIRelationFromTree">{{ $t('delete') }}</span>
|
||||
</template>
|
||||
<a-divider type="vertical" />
|
||||
<span
|
||||
@click="
|
||||
() => {
|
||||
showBatchLevel = null
|
||||
batchTreeKey = []
|
||||
}
|
||||
"
|
||||
>{{ $t('cancel') }}</span
|
||||
>
|
||||
<span>{{ $t('selectRows', { rows: batchTreeKey.length }) }}</span>
|
||||
</div>
|
||||
</span>
|
||||
<a-button size="small" icon="user-add" type="primary" ghost @click="handlePerm">{{ $t('grant') }}</a-button>
|
||||
</div>
|
||||
<SplitPane
|
||||
:min="200"
|
||||
:max="500"
|
||||
:paneLengthPixel.sync="paneLengthPixel"
|
||||
appName="cmdb-relation-views"
|
||||
:appName="`cmdb-relation-views-${viewId}`"
|
||||
triggerColor="#F0F5FF"
|
||||
:triggerLength="18"
|
||||
>
|
||||
@@ -24,7 +65,6 @@
|
||||
@drop="onDrop"
|
||||
:expandedKeys="expandedKeys"
|
||||
>
|
||||
<a-icon slot="switcherIcon" type="down" />
|
||||
<template #title="{ key: treeKey, title, isLeaf }">
|
||||
<ContextMenu
|
||||
:title="title"
|
||||
@@ -35,7 +75,10 @@
|
||||
:id2type="relationViews.id2type"
|
||||
@onContextMenuClick="onContextMenuClick"
|
||||
@onNodeClick="onNodeClick"
|
||||
:ciTypes="ciTypes"
|
||||
:ciTypeIcons="ciTypeIcons"
|
||||
:showBatchLevel="showBatchLevel"
|
||||
:batchTreeKey="batchTreeKey"
|
||||
@clickCheckbox="clickCheckbox"
|
||||
/>
|
||||
</template>
|
||||
</a-tree>
|
||||
@@ -58,13 +101,9 @@
|
||||
/>
|
||||
<div class="relation-views-right-bar">
|
||||
<a-space>
|
||||
<a-button
|
||||
v-if="isLeaf"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="$refs.create.handleOpen(true, 'create')"
|
||||
>{{ $t('create') }}</a-button
|
||||
>
|
||||
<a-button v-if="isLeaf" type="primary" size="small" @click="$refs.create.handleOpen(true, 'create')">{{
|
||||
$t('create')
|
||||
}}</a-button>
|
||||
|
||||
<div class="ops-list-batch-action" v-if="isLeaf && isShowBatchIcon">
|
||||
<template v-if="selectedRowKeys.length">
|
||||
@@ -135,7 +174,7 @@
|
||||
{{ col.title }}</span
|
||||
>
|
||||
</template>
|
||||
<template v-if="col.is_choice || col.is_password || col.is_list" #edit="{ row }">
|
||||
<template v-if="col.is_choice || col.is_password" #edit="{ row }">
|
||||
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
|
||||
<a-select
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
@@ -172,18 +211,6 @@
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
:style="{ width: '100%', height: '32px' }"
|
||||
v-model="row[col.field]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
v-else-if="col.is_list"
|
||||
:showArrow="false"
|
||||
mode="tags"
|
||||
class="ci-table-edit-select"
|
||||
allowClear
|
||||
>
|
||||
</a-select>
|
||||
</template>
|
||||
<template
|
||||
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
|
||||
@@ -329,10 +356,9 @@
|
||||
v-else-if="relationViews.name2id && !relationViews.name2id.length"
|
||||
></a-alert>
|
||||
<AddTableModal ref="addTableModal" @reload="reload" />
|
||||
<!-- <GrantDrawer ref="grantDrawer" resourceTypeName="RelationView" app_id="cmdb" /> -->
|
||||
<CMDBGrant ref="cmdbGrant" resourceType="RelationView" app_id="cmdb" />
|
||||
|
||||
<ci-detail ref="detail" :typeId="Number(currentTypeId[0])" />
|
||||
<GrantModal ref="grantModal" @handleOk="onRelationViewGrant" :customTitle="$t('cmdb.serviceTree.grantTitle')" />
|
||||
<CiDetailDrawer ref="detail" :typeId="Number(currentTypeId[0])" />
|
||||
<create-instance-form
|
||||
ref="create"
|
||||
:typeIdFromRelation="Number(currentTypeId[0])"
|
||||
@@ -341,11 +367,12 @@
|
||||
/>
|
||||
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
||||
<BatchDownload ref="batchDownload" @batchDownload="batchDownload" />
|
||||
<ReadPermissionsModal ref="readPermissionsModal" />
|
||||
<RevokeModal ref="revokeModal" @handleRevoke="handleRevoke" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable no-useless-escape */
|
||||
import _ from 'lodash'
|
||||
import { Tree } from 'element-ui'
|
||||
import Sortable from 'sortablejs'
|
||||
@@ -364,21 +391,24 @@ import {
|
||||
} from '@/modules/cmdb/api/CIRelation'
|
||||
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { searchCI2, updateCI, deleteCI, searchCI } from '@/modules/cmdb/api/ci'
|
||||
import { getCITypes } from '../../api/CIType'
|
||||
import { searchCI2, updateCI, deleteCI } from '@/modules/cmdb/api/ci'
|
||||
import { getCITypeIcons, grantCiType, revokeCiType } from '../../api/CIType'
|
||||
import { roleHasPermissionToGrant } from '@/modules/acl/api/permission'
|
||||
import { searchResourceType } from '@/modules/acl/api/resource'
|
||||
import SplitPane from '@/components/SplitPane'
|
||||
import EditAttrsPopover from '../ci/modules/editAttrsPopover.vue'
|
||||
import CiDetail from '../ci/modules/CiDetail'
|
||||
import CiDetailDrawer from '../ci/modules/ciDetailDrawer.vue'
|
||||
import CreateInstanceForm from '../ci/modules/CreateInstanceForm'
|
||||
import JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
|
||||
import BatchDownload from '../../components/batchDownload/batchDownload.vue'
|
||||
import PasswordField from '../../components/passwordField/index.vue'
|
||||
import PreferenceSearch from '../../components/preferenceSearch/preferenceSearch.vue'
|
||||
import CMDBGrant from '../../components/cmdbGrant'
|
||||
import GrantModal from '../../components/cmdbGrant/grantModal.vue'
|
||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||
import { getAttrPassword } from '../../api/CITypeAttr'
|
||||
import ReadPermissionsModal from './modules/ReadPermissionsModal.vue'
|
||||
import RevokeModal from '../../components/cmdbGrant/revokeModal.vue'
|
||||
|
||||
export default {
|
||||
name: 'RelationViews',
|
||||
@@ -386,25 +416,27 @@ export default {
|
||||
SearchForm,
|
||||
AddTableModal,
|
||||
ContextMenu,
|
||||
// GrantDrawer,
|
||||
CMDBGrant,
|
||||
GrantModal,
|
||||
SplitPane,
|
||||
ElTree: Tree,
|
||||
EditAttrsPopover,
|
||||
CiDetail,
|
||||
CiDetailDrawer,
|
||||
CreateInstanceForm,
|
||||
JsonEditor,
|
||||
BatchDownload,
|
||||
PasswordField,
|
||||
PreferenceSearch,
|
||||
OpsMoveIcon,
|
||||
ReadPermissionsModal,
|
||||
RevokeModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
treeData: [],
|
||||
triggerSelect: false,
|
||||
treeNode: null,
|
||||
ciTypes: [],
|
||||
ciTypeIcons: {},
|
||||
relationViews: {},
|
||||
levels: [],
|
||||
showTypeIds: [],
|
||||
@@ -446,6 +478,10 @@ export default {
|
||||
passwordValue: {},
|
||||
lastEditCiId: null,
|
||||
isContinueCloseEdit: true,
|
||||
|
||||
contextMenuKey: null,
|
||||
showBatchLevel: null,
|
||||
batchTreeKey: [],
|
||||
}
|
||||
},
|
||||
|
||||
@@ -468,6 +504,21 @@ export default {
|
||||
isShowBatchIcon() {
|
||||
return !!this.selectedRowKeys.length
|
||||
},
|
||||
topo_flatten() {
|
||||
return this.relationViews?.views[this.$route.meta.name]?.topo_flatten ?? []
|
||||
},
|
||||
descendant_ids() {
|
||||
return this.topo_flatten.slice(this.treeKeys.length).join(',')
|
||||
},
|
||||
descendant_ids_for_statistics() {
|
||||
return this.topo_flatten.slice(this.treeKeys.length + 1).join(',')
|
||||
},
|
||||
root_parent_path() {
|
||||
return this.treeKeys
|
||||
.slice(0, this.treeKeys.length)
|
||||
.map((item) => item.split('%')[0])
|
||||
.join(',')
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
@@ -521,8 +572,8 @@ export default {
|
||||
})
|
||||
},
|
||||
getCITypesList() {
|
||||
getCITypes().then((res) => {
|
||||
this.ciTypes = res.ci_types
|
||||
getCITypeIcons().then((res) => {
|
||||
this.ciTypeIcons = res
|
||||
})
|
||||
},
|
||||
refreshTable() {
|
||||
@@ -588,33 +639,38 @@ export default {
|
||||
q = q.slice(1)
|
||||
}
|
||||
if (this.treeKeys.length === 0) {
|
||||
await this.judgeCITypes(q)
|
||||
// await this.judgeCITypes(q)
|
||||
if (!refreshType) {
|
||||
this.loadRoot()
|
||||
await this.loadRoot()
|
||||
}
|
||||
|
||||
const fuzzySearch = (this.$refs['search'] || {}).fuzzySearch || ''
|
||||
if (fuzzySearch) {
|
||||
q = `q=_type:${this.currentTypeId[0]},*${fuzzySearch}*,` + q
|
||||
} else {
|
||||
q = `q=_type:${this.currentTypeId[0]},` + q
|
||||
}
|
||||
if (this.currentTypeId[0]) {
|
||||
const res = await searchCI2(q)
|
||||
this.pageNo = res.page
|
||||
this.numfound = res.numfound
|
||||
res.result.forEach((item, index) => (item.key = item._id))
|
||||
const jsonAttrList = this.preferenceAttrList.filter((attr) => attr.value_type === '6')
|
||||
console.log(jsonAttrList)
|
||||
this.instanceList = res['result'].map((item) => {
|
||||
jsonAttrList.forEach(
|
||||
(jsonAttr) => (item[jsonAttr.name] = item[jsonAttr.name] ? JSON.stringify(item[jsonAttr.name]) : '')
|
||||
)
|
||||
return { ..._.cloneDeep(item) }
|
||||
})
|
||||
this.initialInstanceList = _.cloneDeep(this.instanceList)
|
||||
this.calcColumns()
|
||||
}
|
||||
// const fuzzySearch = (this.$refs['search'] || {}).fuzzySearch || ''
|
||||
// if (fuzzySearch) {
|
||||
// q = `q=_type:${this.currentTypeId[0]},*${fuzzySearch}*,` + q
|
||||
// } else {
|
||||
// q = `q=_type:${this.currentTypeId[0]},` + q
|
||||
// }
|
||||
// if (this.currentTypeId[0] && this.treeData && this.treeData.length) {
|
||||
// // default select first node
|
||||
// this.onNodeClick(this.treeData[0].key)
|
||||
// const res = await searchCI2(q)
|
||||
// const root_id = this.treeData.map((item) => item.id).join(',')
|
||||
// q += `&root_id=${root_id}`
|
||||
|
||||
// this.pageNo = res.page
|
||||
// this.numfound = res.numfound
|
||||
// res.result.forEach((item, index) => (item.key = item._id))
|
||||
// const jsonAttrList = this.preferenceAttrList.filter((attr) => attr.value_type === '6')
|
||||
// console.log(jsonAttrList)
|
||||
// this.instanceList = res['result'].map((item) => {
|
||||
// jsonAttrList.forEach(
|
||||
// (jsonAttr) => (item[jsonAttr.name] = item[jsonAttr.name] ? JSON.stringify(item[jsonAttr.name]) : '')
|
||||
// )
|
||||
// return { ..._.cloneDeep(item) }
|
||||
// })
|
||||
// this.initialInstanceList = _.cloneDeep(this.instanceList)
|
||||
// this.calcColumns()
|
||||
// }
|
||||
} else {
|
||||
q += `&root_id=${this.treeKeys[this.treeKeys.length - 1].split('%')[0]}`
|
||||
|
||||
@@ -650,10 +706,10 @@ export default {
|
||||
level = [1]
|
||||
}
|
||||
q += `&level=${level.join(',')}`
|
||||
await this.judgeCITypes(q)
|
||||
if (!refreshType) {
|
||||
this.loadNoRoot(this.treeKeys[this.treeKeys.length - 1], level)
|
||||
}
|
||||
await this.judgeCITypes(q)
|
||||
const fuzzySearch = (this.$refs['search'] || {}).fuzzySearch || ''
|
||||
if (fuzzySearch) {
|
||||
q = `q=_type:${this.currentTypeId[0]},*${fuzzySearch}*,` + q
|
||||
@@ -661,8 +717,12 @@ export default {
|
||||
q = `q=_type:${this.currentTypeId[0]},` + q
|
||||
}
|
||||
if (Object.values(this.level2constraint).includes('2')) {
|
||||
q = q + `&&has_m2m=1`
|
||||
q = q + `&has_m2m=1`
|
||||
}
|
||||
if (this.root_parent_path) {
|
||||
q = q + `&root_parent_path=${this.root_parent_path}`
|
||||
}
|
||||
q = q + `&descendant_ids=${this.descendant_ids}`
|
||||
if (this.currentTypeId[0]) {
|
||||
const res = await searchCIRelation(q)
|
||||
|
||||
@@ -682,7 +742,6 @@ export default {
|
||||
|
||||
this.calcColumns()
|
||||
}
|
||||
|
||||
if (refreshType === 'refreshNumber') {
|
||||
const promises = this.treeKeys.map((key, index) => {
|
||||
let ancestor_ids
|
||||
@@ -700,8 +759,9 @@ export default {
|
||||
ancestor_ids,
|
||||
root_ids: key.split('%')[0],
|
||||
level: this.treeKeys.length - index,
|
||||
type_ids: this.showTypes.map((type) => type.id).join(','),
|
||||
type_ids: this.leaf2showTypes[this.leaf[0]].join(','),
|
||||
has_m2m: Number(Object.values(this.level2constraint).includes('2')),
|
||||
descendant_ids: this.descendant_ids_for_statistics,
|
||||
}).then((res) => {
|
||||
let result
|
||||
const getTreeItem = (data, id) => {
|
||||
@@ -757,22 +817,25 @@ export default {
|
||||
const promises = _showTypeIds.map((typeId) => {
|
||||
let _q = (`q=_type:${typeId},` + q).replace(/count=\d*/, 'count=1')
|
||||
if (Object.values(this.level2constraint).includes('2')) {
|
||||
_q = _q + `&&has_m2m=1`
|
||||
_q = _q + `&has_m2m=1`
|
||||
}
|
||||
console.log(_q)
|
||||
if (this.treeKeys.length === 0) {
|
||||
return searchCI2(_q).then((res) => {
|
||||
if (res.numfound !== 0) {
|
||||
showTypeIds.push(typeId)
|
||||
if (this.root_parent_path) {
|
||||
_q = _q + `&root_parent_path=${this.root_parent_path}`
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// if (this.treeKeys.length === 0) {
|
||||
// return searchCI2(_q).then((res) => {
|
||||
// if (res.numfound !== 0) {
|
||||
// showTypeIds.push(typeId)
|
||||
// }
|
||||
// })
|
||||
// } else {
|
||||
_q = _q + `&descendant_ids=${this.descendant_ids}`
|
||||
return searchCIRelation(_q).then((res) => {
|
||||
if (res.numfound !== 0) {
|
||||
showTypeIds.push(typeId)
|
||||
}
|
||||
})
|
||||
}
|
||||
// }
|
||||
})
|
||||
await Promise.all(promises).then(async () => {
|
||||
if (showTypeIds.length && showTypeIds.sort().join(',') !== this.showTypeIds.sort().join(',')) {
|
||||
@@ -796,7 +859,7 @@ export default {
|
||||
},
|
||||
|
||||
async loadRoot() {
|
||||
searchCI2(`q=_type:(${this.levels[0].join(';')})&count=10000`).then(async (res) => {
|
||||
await searchCI2(`q=_type:(${this.levels[0].join(';')})&count=10000&use_id_filter=1`).then(async (res) => {
|
||||
const facet = []
|
||||
const ciIds = []
|
||||
res.result.forEach((item) => {
|
||||
@@ -813,8 +876,9 @@ export default {
|
||||
return statisticsCIRelation({
|
||||
root_ids: ciIds.join(','),
|
||||
level: level,
|
||||
type_ids: this.showTypes.map((type) => type.id).join(','),
|
||||
type_ids: this.leaf2showTypes[this.leaf[0]].join(','),
|
||||
has_m2m: Number(Object.values(this.level2constraint).includes('2')),
|
||||
descendant_ids: this.descendant_ids_for_statistics,
|
||||
}).then((num) => {
|
||||
facet.forEach((item, idx) => {
|
||||
item[1] += num[ciIds[idx] + '']
|
||||
@@ -822,16 +886,17 @@ export default {
|
||||
})
|
||||
})
|
||||
await Promise.all(promises)
|
||||
this.wrapTreeData(facet, 'loadRoot')
|
||||
this.wrapTreeData(facet)
|
||||
// default select first node
|
||||
this.onNodeClick(this.treeData[0].key)
|
||||
})
|
||||
},
|
||||
|
||||
async loadNoRoot(rootIdAndTypeId, level) {
|
||||
const rootId = rootIdAndTypeId.split('%')[0]
|
||||
const typeId = Number(rootIdAndTypeId.split('%')[1])
|
||||
const topo_flatten = this.relationViews?.views[this.$route.meta.name]?.topo_flatten ?? []
|
||||
const index = topo_flatten.findIndex((id) => id === typeId)
|
||||
const _type = topo_flatten[index + 1]
|
||||
const index = this.topo_flatten.findIndex((id) => id === typeId)
|
||||
const _type = this.topo_flatten[index + 1]
|
||||
if (_type) {
|
||||
let q = `q=_type:${_type}&root_id=${rootId}&level=1&count=10000`
|
||||
if (
|
||||
@@ -845,8 +910,12 @@ export default {
|
||||
.join(',')}`
|
||||
}
|
||||
if (Object.values(this.level2constraint).includes('2')) {
|
||||
q = q + `&&has_m2m=1`
|
||||
q = q + `&has_m2m=1`
|
||||
}
|
||||
if (this.root_parent_path) {
|
||||
q = q + `&root_parent_path=${this.root_parent_path}`
|
||||
}
|
||||
q = q + `&descendant_ids=${this.descendant_ids}`
|
||||
searchCIRelation(q).then(async (res) => {
|
||||
const facet = []
|
||||
const ciIds = []
|
||||
@@ -868,8 +937,9 @@ export default {
|
||||
ancestor_ids,
|
||||
root_ids: ciIds.join(','),
|
||||
level: _level - 1,
|
||||
type_ids: this.showTypes.map((type) => type.id).join(','),
|
||||
type_ids: this.leaf2showTypes[this.leaf[0]].join(','),
|
||||
has_m2m: Number(Object.values(this.level2constraint).includes('2')),
|
||||
descendant_ids: this.descendant_ids_for_statistics,
|
||||
}).then((num) => {
|
||||
facet.forEach((item, idx) => {
|
||||
item[1] += num[ciIds[idx] + '']
|
||||
@@ -878,7 +948,7 @@ export default {
|
||||
}
|
||||
})
|
||||
await Promise.all(promises)
|
||||
this.wrapTreeData(facet, 'loadNoRoot')
|
||||
this.wrapTreeData(facet)
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -933,6 +1003,7 @@ export default {
|
||||
}
|
||||
this.treeKeys = treeNode.eventKey.split('@^@').filter((item) => item !== '')
|
||||
this.treeNode = treeNode
|
||||
// this.refreshTable()
|
||||
resolve()
|
||||
})
|
||||
},
|
||||
@@ -995,8 +1066,7 @@ export default {
|
||||
this.$refs.xTable.refreshColumn()
|
||||
})
|
||||
},
|
||||
onContextMenuClick(treeKey, menuKey) {
|
||||
if (treeKey) {
|
||||
calculateParamsFromTreeKey(treeKey, menuKey) {
|
||||
const splitTreeKey = treeKey.split('@^@')
|
||||
const _tempTree = splitTreeKey[splitTreeKey.length - 1].split('%')
|
||||
const firstCIObj = JSON.parse(_tempTree[2])
|
||||
@@ -1012,17 +1082,24 @@ export default {
|
||||
.slice(0, menuKey === 'delete' ? treeKey.split('@^@').length - 2 : treeKey.split('@^@').length - 1)
|
||||
ancestor_ids = ancestor.map((item) => item.split('%')[0]).join(',')
|
||||
}
|
||||
return { splitTreeKey, firstCIObj, firstCIId, _tempTree, ancestor_ids }
|
||||
},
|
||||
onContextMenuClick(treeKey, menuKey) {
|
||||
if (treeKey) {
|
||||
if (!['batchGrant', 'batchRevoke', 'batchDelete', 'batchCancel'].includes(menuKey)) {
|
||||
this.contextMenuKey = treeKey
|
||||
}
|
||||
|
||||
const { splitTreeKey, firstCIObj, firstCIId, _tempTree, ancestor_ids } = this.calculateParamsFromTreeKey(
|
||||
treeKey,
|
||||
menuKey
|
||||
)
|
||||
if (menuKey === 'delete') {
|
||||
const _tempTreeParent = splitTreeKey[splitTreeKey.length - 2].split('%')
|
||||
const that = this
|
||||
|
||||
this.$confirm({
|
||||
title: that.$t('warning'),
|
||||
content: (h) => (
|
||||
<div>
|
||||
{that.$t('confirmDelete2', { name: Object.values(firstCIObj)[0] })}
|
||||
</div>
|
||||
),
|
||||
content: (h) => <div>{that.$t('confirmDelete2', { name: Object.values(firstCIObj)[0] })}</div>,
|
||||
onOk() {
|
||||
deleteCIRelationView(_tempTreeParent[0], _tempTree[0], { ancestor_ids }).then((res) => {
|
||||
that.$message.success(that.$t('deleteSuccess'))
|
||||
@@ -1032,6 +1109,24 @@ export default {
|
||||
})
|
||||
},
|
||||
})
|
||||
} else if (menuKey === 'grant') {
|
||||
this.$refs.grantModal.open('depart')
|
||||
} else if (menuKey === 'revoke') {
|
||||
this.$refs.revokeModal.open()
|
||||
} else if (menuKey === 'view') {
|
||||
this.$refs.readPermissionsModal.open(treeKey)
|
||||
} else if (menuKey === 'batch') {
|
||||
this.showBatchLevel = splitTreeKey.filter((item) => !!item).length - 1
|
||||
this.batchTreeKey = []
|
||||
} else if (menuKey === 'batchGrant') {
|
||||
this.$refs.grantModal.open('depart')
|
||||
} else if (menuKey === 'batchRevoke') {
|
||||
this.$refs.revokeModal.open()
|
||||
} else if (menuKey === 'batchDelete') {
|
||||
this.batchDeleteCIRelationFromTree()
|
||||
} else if (menuKey === 'batchCancel') {
|
||||
this.showBatchLevel = null
|
||||
this.batchTreeKey = []
|
||||
} else {
|
||||
const childTypeId = menuKey
|
||||
this.$refs.addTableModal.openModal(firstCIObj, firstCIId, childTypeId, 'children', ancestor_ids)
|
||||
@@ -1048,7 +1143,7 @@ export default {
|
||||
title: that.$t('warning'),
|
||||
content: (h) => (
|
||||
<div>
|
||||
{that.$t('cmdb.serviceTreedeleteRelationConfirm', { name: currentShowType.alias || currentShowType.name })}
|
||||
{that.$t('cmdb.serviceTree.deleteRelationConfirm', { name: currentShowType.alias || currentShowType.name })}
|
||||
</div>
|
||||
),
|
||||
onOk() {
|
||||
@@ -1086,8 +1181,10 @@ export default {
|
||||
const _splitTargetKey = targetKey.split('@^@').filter((item) => item !== '')
|
||||
if (_splitDragKey.length - 1 === _splitTargetKey.length) {
|
||||
const dragId = _splitDragKey[_splitDragKey.length - 1].split('%')[0]
|
||||
// const targetObj = JSON.parse(_splitTargetKey[_splitTargetKey.length - 1].split('%')[2])
|
||||
const targetId = _splitTargetKey[_splitTargetKey.length - 1].split('%')[0]
|
||||
console.log(_splitDragKey)
|
||||
// TODO 拖拽这里不造咋弄 等等再说吧
|
||||
batchUpdateCIRelationChildren([dragId], [targetId]).then((res) => {
|
||||
this.reload()
|
||||
})
|
||||
@@ -1458,6 +1555,138 @@ export default {
|
||||
this.$message.error(this.$t('cmdb.serviceTreecopyFailed'))
|
||||
})
|
||||
},
|
||||
async onRelationViewGrant({ department, user }, type) {
|
||||
const result = []
|
||||
if (this.showBatchLevel !== null && this.batchTreeKey && this.batchTreeKey.length) {
|
||||
for (let i = 0; i < this.batchTreeKey.length; i++) {
|
||||
await this.relationViewGrant({ department, user }, this.batchTreeKey[i], (_result) => {
|
||||
result.push(..._result)
|
||||
})
|
||||
}
|
||||
this.showBatchLevel = null
|
||||
this.batchTreeKey = []
|
||||
} else {
|
||||
await this.relationViewGrant({ department, user }, this.contextMenuKey, (_result) => {
|
||||
result.push(..._result)
|
||||
})
|
||||
}
|
||||
if (result.every((r) => r.status === 'fulfilled')) {
|
||||
this.$message.success(this.$t('operateSuccess'))
|
||||
}
|
||||
},
|
||||
async relationViewGrant({ department, user }, nodeKey, callback) {
|
||||
const needGrantNodes = nodeKey
|
||||
.split('@^@')
|
||||
.filter((item) => !!item)
|
||||
.reverse()
|
||||
console.log(needGrantNodes)
|
||||
|
||||
const needGrantRids = [...department, ...user]
|
||||
const floor = Math.ceil(needGrantRids.length / 6)
|
||||
const result = []
|
||||
for (let i = 0; i < needGrantNodes.length; i++) {
|
||||
const grantNode = needGrantNodes[i]
|
||||
const _grantNode = grantNode.split('%')
|
||||
const ciId = _grantNode[0]
|
||||
const typeId = _grantNode[1]
|
||||
const uniqueValue = Object.entries(JSON.parse(_grantNode[2]))[0][1]
|
||||
const parent_path = needGrantNodes
|
||||
.slice(i + 1)
|
||||
.map((item) => {
|
||||
return Number(item.split('%')[0])
|
||||
})
|
||||
.reverse()
|
||||
.join(',')
|
||||
for (let j = 0; j < floor; j++) {
|
||||
const itemList = needGrantRids.slice(6 * j, 6 * j + 6)
|
||||
const promises = itemList.map((rid) =>
|
||||
grantCiType(typeId, rid, {
|
||||
id_filter: { [ciId]: { name: uniqueValue, parent_path } },
|
||||
is_recursive: Number(i > 0),
|
||||
})
|
||||
)
|
||||
const _result = await Promise.allSettled(promises)
|
||||
result.push(..._result)
|
||||
}
|
||||
}
|
||||
callback(result)
|
||||
},
|
||||
clickCheckbox(treeKey) {
|
||||
const _idx = this.batchTreeKey.findIndex((item) => item === treeKey)
|
||||
if (_idx > -1) {
|
||||
this.batchTreeKey.splice(_idx, 1)
|
||||
} else {
|
||||
this.batchTreeKey.push(treeKey)
|
||||
}
|
||||
},
|
||||
batchDeleteCIRelationFromTree() {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: that.$t('warning'),
|
||||
content: (h) => <div>{that.$t('confirmDelete')}</div>,
|
||||
async onOk() {
|
||||
for (let i = 0; i < that.batchTreeKey.length; i++) {
|
||||
const { splitTreeKey, firstCIObj, firstCIId, _tempTree, ancestor_ids } = that.calculateParamsFromTreeKey(
|
||||
that.batchTreeKey[i],
|
||||
'delete'
|
||||
)
|
||||
const _tempTreeParent = splitTreeKey[splitTreeKey.length - 2].split('%')
|
||||
await deleteCIRelationView(_tempTreeParent[0], _tempTree[0], { ancestor_ids }).then((res) => {})
|
||||
}
|
||||
that.$message.success(that.$t('deleteSuccess'))
|
||||
that.showBatchLevel = null
|
||||
that.batchTreeKey = []
|
||||
setTimeout(() => {
|
||||
that.reload()
|
||||
}, 500)
|
||||
},
|
||||
})
|
||||
},
|
||||
async handleSingleRevoke({ users = [], roles = [] }, treeKey, callback) {
|
||||
const rids = [...users.map((item) => Number(item.split('-')[1])), ...roles]
|
||||
const treeKeyPath = treeKey.split('@^@').filter((item) => !!item)
|
||||
const _treeKey = treeKeyPath.pop(-1).split('%')
|
||||
const id_filter = {}
|
||||
const typeId = _treeKey[1]
|
||||
const ciId = _treeKey[0]
|
||||
const uniqueValue = Object.entries(JSON.parse(_treeKey[2]))[0][1]
|
||||
|
||||
const parent_path = treeKeyPath
|
||||
.map((item) => {
|
||||
return Number(item.split('%')[0])
|
||||
})
|
||||
.join(',')
|
||||
id_filter[ciId] = { name: uniqueValue, parent_path }
|
||||
const floor = Math.ceil(rids.length / 6)
|
||||
const result = []
|
||||
for (let j = 0; j < floor; j++) {
|
||||
const itemList = rids.slice(6 * j, 6 * j + 6)
|
||||
const promises = itemList.map((rid) => revokeCiType(typeId, rid, { id_filter, perms: ['read'], parent_path }))
|
||||
const _result = await Promise.allSettled(promises)
|
||||
result.push(..._result)
|
||||
}
|
||||
callback(result)
|
||||
},
|
||||
async handleRevoke({ users = [], roles = [] }) {
|
||||
const result = []
|
||||
if (this.showBatchLevel !== null && this.batchTreeKey && this.batchTreeKey.length) {
|
||||
for (let i = 0; i < this.batchTreeKey.length; i++) {
|
||||
const treeKey = this.batchTreeKey[i]
|
||||
await this.handleSingleRevoke({ users, roles }, treeKey, (_result) => {
|
||||
result.push(..._result)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
await this.handleSingleRevoke({ users, roles }, this.contextMenuKey, (_result) => {
|
||||
result.push(..._result)
|
||||
})
|
||||
}
|
||||
if (result.every((r) => r.status === 'fulfilled')) {
|
||||
this.$message.success(this.$t('operateSuccess'))
|
||||
}
|
||||
this.showBatchLevel = null
|
||||
this.batchTreeKey = []
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@@ -11,24 +11,24 @@
|
||||
>
|
||||
<div :style="{ width: '100%' }" id="add-table-modal">
|
||||
<a-spin :spinning="loading">
|
||||
<!-- <a-input
|
||||
v-model="expression"
|
||||
class="ci-searchform-expression"
|
||||
:style="{ width, marginBottom: '10px' }"
|
||||
:placeholder="placeholder"
|
||||
@focus="
|
||||
() => {
|
||||
isFocusExpression = true
|
||||
}
|
||||
"
|
||||
/> -->
|
||||
<SearchForm
|
||||
ref="searchForm"
|
||||
:typeId="addTypeId"
|
||||
:preferenceAttrList="preferenceAttrList"
|
||||
@refresh="handleSearch"
|
||||
/>
|
||||
<!-- <a @click="handleSearch"><a-icon type="search"/></a> -->
|
||||
>
|
||||
<a-button
|
||||
@click="
|
||||
() => {
|
||||
$refs.createInstanceForm.handleOpen(true, 'create')
|
||||
}
|
||||
"
|
||||
slot="extraContent"
|
||||
type="primary"
|
||||
size="small"
|
||||
>新增</a-button
|
||||
>
|
||||
</SearchForm>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
row-id="_id"
|
||||
@@ -77,19 +77,31 @@
|
||||
/>
|
||||
</a-spin>
|
||||
</div>
|
||||
<CreateInstanceForm
|
||||
ref="createInstanceForm"
|
||||
:typeIdFromRelation="addTypeId"
|
||||
@reload="
|
||||
() => {
|
||||
currentPage = 1
|
||||
getTableData(true)
|
||||
}
|
||||
"
|
||||
/>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable no-useless-escape */
|
||||
import { searchCI } from '@/modules/cmdb/api/ci'
|
||||
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'
|
||||
import CreateInstanceForm from '../../ci/modules/CreateInstanceForm.vue'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
|
||||
export default {
|
||||
name: 'AddTableModal',
|
||||
components: { SearchForm },
|
||||
components: { SearchForm, CreateInstanceForm },
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
@@ -106,6 +118,7 @@ export default {
|
||||
type: 'children',
|
||||
preferenceAttrList: [],
|
||||
ancestor_ids: undefined,
|
||||
attrList1: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -119,6 +132,13 @@ export default {
|
||||
return this.isFocusExpression ? '500px' : '100px'
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
attrList: () => {
|
||||
return this.attrList
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {},
|
||||
methods: {
|
||||
async openModal(ciObj, ciId, addTypeId, type, ancestor_ids = undefined) {
|
||||
@@ -132,6 +152,9 @@ export default {
|
||||
await getSubscribeAttributes(addTypeId).then((res) => {
|
||||
this.preferenceAttrList = res.attributes // 已经订阅的全部列
|
||||
})
|
||||
getCITypeAttributesById(addTypeId).then((res) => {
|
||||
this.attrList = res.attributes
|
||||
})
|
||||
this.getTableData(true)
|
||||
},
|
||||
async getTableData(isInit) {
|
||||
@@ -207,6 +230,9 @@ export default {
|
||||
this.handleClose()
|
||||
this.$emit('reload')
|
||||
}, 500)
|
||||
} else {
|
||||
this.handleClose()
|
||||
this.$emit('reload')
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
|
@@ -1,63 +1,81 @@
|
||||
<template>
|
||||
<a-dropdown :trigger="['contextmenu']">
|
||||
<a-menu slot="overlay" @click="({ key: menuKey }) => this.onContextMenuClick(this.treeKey, menuKey)">
|
||||
<a-menu-item v-for="item in menuList" :key="item.id">{{ $t('new') }} {{ item.alias }}</a-menu-item>
|
||||
<a-menu-item v-if="showDelete" key="delete">{{ $t('cmdb.serviceTree.deleteNode') }}</a-menu-item>
|
||||
</a-menu>
|
||||
<div
|
||||
:style="{
|
||||
width: '100%',
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
:class="{
|
||||
'relation-views-node': true,
|
||||
'relation-views-node-checkbox': showCheckbox,
|
||||
}"
|
||||
@click="clickNode"
|
||||
>
|
||||
<span
|
||||
:style="{
|
||||
display: 'flex',
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
alignItems: 'center',
|
||||
}"
|
||||
>
|
||||
<span>
|
||||
<a-checkbox @click.stop="clickCheckbox" class="relation-views-node-checkbox" v-if="showCheckbox" />
|
||||
<template v-if="icon">
|
||||
<img
|
||||
v-if="icon.split('$$')[2]"
|
||||
v-if="icon.includes('$$') && icon.split('$$')[2]"
|
||||
:src="`/api/common-setting/v1/file/${icon.split('$$')[3]}`"
|
||||
:style="{ maxHeight: '14px', maxWidth: '14px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
v-else-if="icon.includes('$$') && icon.split('$$')[0]"
|
||||
:style="{
|
||||
color: icon.split('$$')[1],
|
||||
fontSize: '14px',
|
||||
}"
|
||||
:type="icon.split('$$')[0]"
|
||||
/>
|
||||
<span class="relation-views-node-icon" v-else>{{ icon ? icon[0].toUpperCase() : 'i' }}</span>
|
||||
</template>
|
||||
<span
|
||||
:style="{
|
||||
display: 'inline-block',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: '#d3d3d3',
|
||||
color: '#fff',
|
||||
textAlign: 'center',
|
||||
lineHeight: '16px',
|
||||
fontSize: '12px',
|
||||
}"
|
||||
v-else
|
||||
>{{ ciTypeName ? ciTypeName[0].toUpperCase() : 'i' }}</span
|
||||
>
|
||||
<span :style="{ marginLeft: '5px' }">{{ this.title }}</span>
|
||||
<span class="relation-views-node-title">{{ this.title }}</span>
|
||||
</span>
|
||||
<a-dropdown>
|
||||
<a-menu slot="overlay" @click="({ key: menuKey }) => this.onContextMenuClick(this.treeKey, menuKey)">
|
||||
<template v-if="showBatchLevel === null">
|
||||
<a-menu-item
|
||||
v-for="item in menuList"
|
||||
:key="item.id"
|
||||
><a-icon type="plus-circle" />{{ $t('new') }} {{ item.alias }}</a-menu-item
|
||||
>
|
||||
<a-menu-item
|
||||
v-if="showDelete"
|
||||
key="delete"
|
||||
><ops-icon type="icon-xianxing-delete" />{{ $t('cmdb.serviceTree.deleteNode') }}</a-menu-item
|
||||
>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="grant"><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item>
|
||||
<a-menu-item key="revoke"><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item>
|
||||
<a-menu-item key="view"><a-icon type="eye" />{{ $t('cmdb.serviceTree.view') }}</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item
|
||||
key="batch"
|
||||
><ops-icon type="icon-xianxing-copy" />{{ $t('cmdb.serviceTree.batch') }}</a-menu-item
|
||||
>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-menu-item
|
||||
:disabled="!batchTreeKey || !batchTreeKey.length"
|
||||
key="batchGrant"
|
||||
><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item
|
||||
>
|
||||
<a-menu-item
|
||||
:disabled="!batchTreeKey || !batchTreeKey.length"
|
||||
key="batchRevoke"
|
||||
><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item
|
||||
>
|
||||
<a-menu-divider />
|
||||
<template v-if="showBatchLevel > 0">
|
||||
<a-menu-item
|
||||
:disabled="!batchTreeKey || !batchTreeKey.length"
|
||||
key="batchDelete"
|
||||
><ops-icon type="icon-xianxing-delete" />{{ $t('delete') }}</a-menu-item
|
||||
>
|
||||
<a-menu-divider />
|
||||
</template>
|
||||
<a-menu-item key="batchCancel"><a-icon type="close-circle" />{{ $t('cancel') }}</a-menu-item>
|
||||
</template>
|
||||
</a-menu>
|
||||
<a-icon class="relation-views-node-operation" type="ellipsis" />
|
||||
</a-dropdown>
|
||||
<a-icon :style="{ fontSize: '10px' }" v-if="childLength && !isLeaf" :type="switchIcon"></a-icon>
|
||||
</div>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -88,7 +106,15 @@ export default {
|
||||
type: Boolean,
|
||||
default: () => false,
|
||||
},
|
||||
ciTypes: {
|
||||
ciTypeIcons: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
showBatchLevel: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
batchTreeKey: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
@@ -141,14 +167,10 @@ export default {
|
||||
icon() {
|
||||
const _split = this.treeKey.split('@^@')
|
||||
const currentNodeTypeId = _split[_split.length - 1].split('%')[1]
|
||||
const _find = this.ciTypes.find((type) => type.id === Number(currentNodeTypeId))
|
||||
return _find?.icon || null
|
||||
return this.ciTypeIcons[Number(currentNodeTypeId)] ?? null
|
||||
},
|
||||
ciTypeName() {
|
||||
const _split = this.treeKey.split('@^@')
|
||||
const currentNodeTypeId = _split[_split.length - 1].split('%')[1]
|
||||
const _find = this.ciTypes.find((type) => type.id === Number(currentNodeTypeId))
|
||||
return _find?.name || ''
|
||||
showCheckbox() {
|
||||
return this.showBatchLevel === this.treeKey.split('@^@').filter((item) => !!item).length - 1
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@@ -159,8 +181,73 @@ export default {
|
||||
this.$emit('onNodeClick', this.treeKey)
|
||||
this.switchIcon = this.switchIcon === 'down' ? 'up' : 'down'
|
||||
},
|
||||
clickCheckbox() {
|
||||
this.$emit('clickCheckbox', this.treeKey)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<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>
|
||||
|
@@ -0,0 +1,105 @@
|
||||
<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>
|
@@ -206,7 +206,7 @@
|
||||
import _ from 'lodash'
|
||||
import SearchForm from '../../components/searchForm/SearchForm.vue'
|
||||
import { searchCI } from '../../api/ci'
|
||||
import { searchAttributes, getCITypeAttributesByTypeIds } from '../../api/CITypeAttr'
|
||||
import { searchAttributes, getCITypeAttributesByTypeIds, getCITypeAttributesById } from '../../api/CITypeAttr'
|
||||
import { getCITypes } from '../../api/CIType'
|
||||
import { getSubscribeAttributes } from '../../api/preference'
|
||||
import { getCITableColumns } from '../../utils/helper'
|
||||
@@ -284,6 +284,10 @@ export default {
|
||||
const regSort = /(?<=sort=).+/g
|
||||
|
||||
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
|
||||
// if (exp) {
|
||||
// exp = exp.replace(/(\:)/g, '$1*')
|
||||
// exp = exp.replace(/(\,)/g, '*$1')
|
||||
// }
|
||||
// 如果是表格点击的排序 以表格为准
|
||||
let sort
|
||||
if (sortByTable) {
|
||||
@@ -314,44 +318,47 @@ export default {
|
||||
this.columnsGroup = []
|
||||
this.instanceList = []
|
||||
this.totalNumber = res['numfound']
|
||||
|
||||
const oldData = res.result
|
||||
|
||||
function allKeys(data) {
|
||||
const keys = {}
|
||||
const ignoreAttr = ['_id', '_type', 'ci_type', 'ci_type_alias', 'unique', 'unique_alias']
|
||||
data.forEach((item) => {
|
||||
Object.keys(item).forEach((key) => {
|
||||
if (!ignoreAttr.includes(key)) {
|
||||
keys[key] = ''
|
||||
if (!res['numfound']) {
|
||||
return
|
||||
}
|
||||
const { attributes: resAllAttributes } = await getCITypeAttributesByTypeIds({
|
||||
type_ids: Object.keys(res.counter).join(','),
|
||||
})
|
||||
const _columnsGroup = Object.keys(res.counter).map((key) => {
|
||||
const _find = this.ciTypes.find((item) => item.name === key)
|
||||
return {
|
||||
id: `parent-${_find.id}`,
|
||||
value: key,
|
||||
label: _find?.alias || _find?.name,
|
||||
isCiType: true,
|
||||
}
|
||||
})
|
||||
const ciTypeAttribute = {}
|
||||
const promises = _columnsGroup.map((item) => {
|
||||
return getCITypeAttributesById(item.id.split('-')[1]).then((res) => {
|
||||
ciTypeAttribute[item.label] = res.attributes
|
||||
})
|
||||
})
|
||||
await Promise.all(promises)
|
||||
|
||||
const outputKeys = {}
|
||||
resAllAttributes.forEach((attr) => {
|
||||
outputKeys[attr.name] = ''
|
||||
})
|
||||
return keys
|
||||
}
|
||||
|
||||
function tidy(data) {
|
||||
const outputKeys = allKeys(data)
|
||||
const common = {}
|
||||
data.forEach((item) => {
|
||||
const tmp = {}
|
||||
Object.keys(outputKeys).forEach((j) => {
|
||||
if (j in item) {
|
||||
tmp[j] = item[j]
|
||||
// 提取common
|
||||
{
|
||||
const key = item['ci_type_alias']
|
||||
if (j in common) {
|
||||
common[j][[key]] = ''
|
||||
Object.keys(outputKeys).forEach((key) => {
|
||||
Object.entries(ciTypeAttribute).forEach(([type, attrs]) => {
|
||||
if (attrs.find((a) => a.name === key)) {
|
||||
if (key in common) {
|
||||
common[key][type] = ''
|
||||
} else {
|
||||
common[j] = { [key]: '' }
|
||||
common[key] = { [type]: '' }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tmp[j] = null
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const commonObject = {}
|
||||
const commonKeys = []
|
||||
// 整理common
|
||||
@@ -366,10 +373,7 @@ export default {
|
||||
}
|
||||
}
|
||||
})
|
||||
return { commonObject, commonKeys }
|
||||
}
|
||||
|
||||
const { commonObject, commonKeys } = tidy(oldData)
|
||||
const _commonColumnsGroup = Object.keys(commonObject).map((key) => {
|
||||
return {
|
||||
id: `parent-${key}`,
|
||||
@@ -385,24 +389,14 @@ export default {
|
||||
}
|
||||
})
|
||||
|
||||
const _columnsGroup = Object.keys(res.counter).map((key) => {
|
||||
const _find = this.ciTypes.find((item) => item.name === key)
|
||||
return {
|
||||
id: `parent-${_find.id}`,
|
||||
value: key,
|
||||
label: _find?.alias || _find?.name,
|
||||
isCiType: true,
|
||||
}
|
||||
})
|
||||
|
||||
const promises = _columnsGroup.map((item) => {
|
||||
const promises1 = _columnsGroup.map((item) => {
|
||||
return getSubscribeAttributes(item.id.split('-')[1]).then((res1) => {
|
||||
item.children = this.getColumns(res.result, res1.attributes).filter(
|
||||
(col) => !commonKeys.includes(col.field)
|
||||
)
|
||||
})
|
||||
})
|
||||
await Promise.all(promises).then(() => {
|
||||
await Promise.all(promises1).then(() => {
|
||||
this.columnsGroup = [..._commonColumnsGroup, ..._columnsGroup]
|
||||
this.instanceList = res['result']
|
||||
})
|
||||
|
@@ -32,32 +32,24 @@
|
||||
>
|
||||
<template #one>
|
||||
<div class="tree-views-left" :style="{ height: `${windowHeight - 115}px` }">
|
||||
<a-collapse
|
||||
:activeKey="current"
|
||||
accordion
|
||||
@change="handleChangeCi"
|
||||
:bordered="false"
|
||||
:destroyInactivePanel="true"
|
||||
>
|
||||
<a-collapse-panel
|
||||
v-for="ciType in subscribeTreeViewCiTypes"
|
||||
:key="String(ciType.type_id)"
|
||||
:showArrow="false"
|
||||
:style="{
|
||||
borderRadius: '4px',
|
||||
marginBottom: '5px',
|
||||
border: 0,
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
}"
|
||||
<draggable
|
||||
v-model="subscribeTreeViewCiTypes"
|
||||
:animation="300"
|
||||
@change="
|
||||
(e) => {
|
||||
orderChange(e, subscribeTreeViewCiTypes)
|
||||
}
|
||||
"
|
||||
>
|
||||
<div v-for="ciType in subscribeTreeViewCiTypes" :key="ciType.type_id">
|
||||
<div
|
||||
slot="header"
|
||||
@click="handleChangeCi(ciType.type_id)"
|
||||
:class="{
|
||||
'custom-header': true,
|
||||
'custom-header-selected': Number(ciType.type_id) === Number(typeId),
|
||||
'custom-header-selected': Number(ciType.type_id) === Number(typeId) && !treeKeys.length,
|
||||
}"
|
||||
>
|
||||
<OpsMoveIcon class="move-icon" />
|
||||
<span class="tree-views-left-header-icon">
|
||||
<template v-if="ciType.icon">
|
||||
<img
|
||||
@@ -95,6 +87,7 @@
|
||||
:tree-data="treeData"
|
||||
:load-data="onLoadData"
|
||||
:expandedKeys="expandedKeys"
|
||||
v-if="Number(ciType.type_id) === Number(typeId)"
|
||||
>
|
||||
<a-icon slot="switcherIcon" type="down" />
|
||||
<template #title="{ key: treeKey, title, isLeaf }">
|
||||
@@ -107,8 +100,8 @@
|
||||
/>
|
||||
</template>
|
||||
</a-tree>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
</template>
|
||||
<template #two>
|
||||
@@ -189,7 +182,7 @@
|
||||
{{ col.title }}</span
|
||||
>
|
||||
</template>
|
||||
<template v-if="col.is_choice || col.is_password || col.is_list" #edit="{ row }">
|
||||
<template v-if="col.is_choice || col.is_password" #edit="{ row }">
|
||||
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
|
||||
<a-select
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
@@ -226,18 +219,6 @@
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
:style="{ width: '100%', height: '32px' }"
|
||||
v-model="row[col.field]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
v-else-if="col.is_list"
|
||||
:showArrow="false"
|
||||
mode="tags"
|
||||
class="ci-table-edit-select"
|
||||
allowClear
|
||||
>
|
||||
</a-select>
|
||||
</template>
|
||||
<template
|
||||
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
|
||||
@@ -390,7 +371,7 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
<ci-detail ref="detail" :typeId="Number(typeId)" :treeViewsLevels="treeViewsLevels" />
|
||||
<CiDetailDrawer ref="detail" :typeId="Number(typeId)" :treeViewsLevels="treeViewsLevels" />
|
||||
<create-instance-form
|
||||
ref="create"
|
||||
:typeIdFromRelation="Number(typeId)"
|
||||
@@ -407,7 +388,13 @@
|
||||
/* eslint-disable no-useless-escape */
|
||||
import _ from 'lodash'
|
||||
import Sortable from 'sortablejs'
|
||||
import { getSubscribeTreeView, getSubscribeAttributes, subscribeTreeView } from '@/modules/cmdb/api/preference'
|
||||
import draggable from 'vuedraggable'
|
||||
import {
|
||||
getSubscribeTreeView,
|
||||
getSubscribeAttributes,
|
||||
subscribeTreeView,
|
||||
preferenceCitypeOrder,
|
||||
} from '@/modules/cmdb/api/preference'
|
||||
import { searchCI, updateCI, deleteCI } from '@/modules/cmdb/api/ci'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getCITableColumns } from '../../utils/helper'
|
||||
@@ -417,7 +404,7 @@ import PasswordField from '../../components/passwordField/index.vue'
|
||||
import SplitPane from '@/components/SplitPane'
|
||||
import TreeViewsNode from './modules/treeViewsNode.vue'
|
||||
import EditAttrsPopover from '../ci/modules/editAttrsPopover.vue'
|
||||
import CiDetail from '../ci/modules/CiDetail'
|
||||
import CiDetailDrawer from '../ci/modules/ciDetailDrawer.vue'
|
||||
import CreateInstanceForm from '../ci/modules/CreateInstanceForm'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
|
||||
@@ -437,13 +424,14 @@ export default {
|
||||
SplitPane,
|
||||
TreeViewsNode,
|
||||
EditAttrsPopover,
|
||||
CiDetail,
|
||||
CiDetailDrawer,
|
||||
CreateInstanceForm,
|
||||
JsonEditor,
|
||||
BatchDownload,
|
||||
PreferenceSearch,
|
||||
MetadataDrawer,
|
||||
OpsMoveIcon,
|
||||
draggable,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -463,7 +451,6 @@ export default {
|
||||
pageSize: 50,
|
||||
currentPage: 1,
|
||||
totalNumber: 0,
|
||||
current: '', // 当前页面的type_id
|
||||
currentAttrList: [],
|
||||
trigger: false,
|
||||
newLoad: true,
|
||||
@@ -571,7 +558,6 @@ export default {
|
||||
this.subscribeTreeViewCiTypes = res
|
||||
if (this.subscribeTreeViewCiTypes.length) {
|
||||
this.typeId = this.$route.params.typeId || this.subscribeTreeViewCiTypes[0].type_id
|
||||
this.current = `${this.typeId}`
|
||||
this.selectedRowKeys = []
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
@@ -600,7 +586,6 @@ export default {
|
||||
async loadCurrentView() {
|
||||
if (this.subscribeTreeViewCiTypes.length) {
|
||||
this.typeId = this.$route.params.typeId || this.subscribeTreeViewCiTypes[0].type_id
|
||||
this.current = String(this.typeId)
|
||||
this.selectedRowKeys = []
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
@@ -746,9 +731,14 @@ export default {
|
||||
name: 'cmdb_tree_views_item',
|
||||
params: { typeId: Number(value) },
|
||||
})
|
||||
this.typeId = Number(value)
|
||||
} else {
|
||||
this.typeId = null
|
||||
this.$nextTick(() => {
|
||||
this.typeId = Number(value)
|
||||
this.newLoad = true
|
||||
this.initPage()
|
||||
})
|
||||
}
|
||||
this.isSetDataNodes = []
|
||||
},
|
||||
@@ -1136,12 +1126,22 @@ export default {
|
||||
}
|
||||
})
|
||||
this.$refs.create.visible = false
|
||||
const key = 'updatable'
|
||||
let errorMsg = ''
|
||||
for (let i = 0; i < this.selectedRowKeys.length; i++) {
|
||||
await updateCI(this.selectedRowKeys[i], payload, false)
|
||||
.then(() => {
|
||||
successNum += 1
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((error) => {
|
||||
errorMsg = errorMsg + '\n' + `${this.selectedRowKeys[i]}:${error.response?.data?.message ?? ''}`
|
||||
this.$notification.warning({
|
||||
key,
|
||||
message: this.$t('warning'),
|
||||
description: errorMsg,
|
||||
duration: 0,
|
||||
style: { whiteSpace: 'break-spaces' },
|
||||
})
|
||||
errorNum += 1
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -1209,6 +1209,13 @@ export default {
|
||||
this.$message.error(this.$t('cmdb.ci.copyFailed'))
|
||||
})
|
||||
},
|
||||
orderChange(e, subscribeTreeViewCiTypes) {
|
||||
preferenceCitypeOrder({ type_ids: subscribeTreeViewCiTypes.map((type) => type.type_id), is_tree: true }).catch(
|
||||
() => {
|
||||
this.getTreeViews()
|
||||
}
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1230,21 +1237,6 @@ export default {
|
||||
&:hover {
|
||||
overflow: auto;
|
||||
}
|
||||
.ant-collapse-borderless {
|
||||
background-color: #fff;
|
||||
}
|
||||
.ant-collapse-item:has(.custom-header-selected):not(:has(.ant-tree-treenode-selected)) > .ant-collapse-header,
|
||||
.ant-collapse-item-active:not(:has(.ant-tree-treenode-selected)) > .ant-collapse-header {
|
||||
background-color: #d6e4ff;
|
||||
}
|
||||
.ant-collapse-header {
|
||||
padding: 8px 12px 4px;
|
||||
&:hover {
|
||||
background-color: #f0f5ff;
|
||||
}
|
||||
&:hover > .custom-header > .actions {
|
||||
display: inherit;
|
||||
}
|
||||
.custom-header {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
@@ -1252,6 +1244,25 @@ export default {
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 8px 0 8px 12px;
|
||||
cursor: move;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
&:hover {
|
||||
background-color: #f0f5ff;
|
||||
> .actions,
|
||||
> .move-icon {
|
||||
display: inherit;
|
||||
}
|
||||
}
|
||||
.move-icon {
|
||||
width: 14px;
|
||||
height: 20px;
|
||||
cursor: move;
|
||||
position: absolute;
|
||||
display: none;
|
||||
left: 0;
|
||||
}
|
||||
.tree-views-left-header-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -1274,6 +1285,7 @@ export default {
|
||||
.actions {
|
||||
display: none;
|
||||
margin-left: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
.action {
|
||||
display: inline-block;
|
||||
@@ -1285,10 +1297,8 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-collapse > .ant-collapse-item > .ant-collapse-header {
|
||||
white-space: nowrap;
|
||||
.custom-header-selected {
|
||||
background-color: #d3e3fd !important;
|
||||
}
|
||||
.ant-tree li {
|
||||
padding: 2px 0;
|
||||
|
@@ -12,13 +12,13 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(47, 122, 235, 0.2);
|
||||
background-color: @scrollbar-color;
|
||||
background-clip: padding-box;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(47, 122, 235, 0.2);
|
||||
background-color: @scrollbar-color;
|
||||
}
|
||||
|
||||
@import '~ant-design-vue/dist/antd.less';
|
||||
@@ -32,10 +32,25 @@ body {
|
||||
&.userLayout {
|
||||
overflow: auto;
|
||||
}
|
||||
.text-color-1 {
|
||||
color: @text-color_1;
|
||||
}
|
||||
.text-color-2 {
|
||||
color: @text-color_2;
|
||||
}
|
||||
.text-color-3 {
|
||||
color: @text-color_3;
|
||||
}
|
||||
.text-color-4 {
|
||||
color: @text-color_4;
|
||||
}
|
||||
.border-radius-box {
|
||||
border-radius: @border-radius-box;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-layout {
|
||||
background-color: #f0f5ff;
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
|
||||
.layout.ant-layout {
|
||||
@@ -99,12 +114,8 @@ body {
|
||||
height: @layout-header-icon-height;
|
||||
line-height: @layout-header-icon-height;
|
||||
display: inline-flex;
|
||||
align-items: flex-end;
|
||||
align-items: center;
|
||||
margin-right: 10px;
|
||||
&:hover {
|
||||
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
|
||||
color: @layout-header-font-selected-color;
|
||||
}
|
||||
}
|
||||
|
||||
.topmenu {
|
||||
@@ -192,12 +203,6 @@ body {
|
||||
color: @layout-header-font-color;
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
|
||||
color: @layout-header-font-selected-color;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-right: 5px;
|
||||
color: @layout-header-font-color;
|
||||
@@ -349,8 +354,6 @@ body {
|
||||
}
|
||||
|
||||
&.light {
|
||||
background-color: #225686;
|
||||
|
||||
.header-index-wide {
|
||||
.header-index-left {
|
||||
.logo {
|
||||
@@ -364,19 +367,8 @@ body {
|
||||
}
|
||||
|
||||
// 内容区
|
||||
// .layout-content {
|
||||
// margin: 24px 24px 0px;
|
||||
// height: 100%;
|
||||
// height: 64px;
|
||||
// padding: 0 12px 0 0;
|
||||
// }
|
||||
.ant-layout-content {
|
||||
padding: 0 24px;
|
||||
// background: @layout-background-color-light;
|
||||
//按钮样式
|
||||
.ant-btn {
|
||||
// border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// footer
|
||||
@@ -425,7 +417,6 @@ body {
|
||||
min-height: 100vh;
|
||||
|
||||
.ant-layout-sider-children {
|
||||
background: #225686; //浅色系左边菜单栏 深色系需删除
|
||||
overflow-y: hidden;
|
||||
> .ant-menu {
|
||||
height: calc(100vh - 40px);
|
||||
@@ -517,12 +508,7 @@ body {
|
||||
transition: none;
|
||||
}
|
||||
.ops-side-bar.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
|
||||
// background-image: url('../assets/sidebar_selected.png') !important;
|
||||
// background-repeat: no-repeat !important;
|
||||
background: #3e4a71;
|
||||
// background-size: 228px 38px;
|
||||
// background-position-x: -10px;
|
||||
// background-position-y: center;
|
||||
background: @layout-sidebar-selected-color;
|
||||
transition: none;
|
||||
}
|
||||
.ops-side-bar.ant-menu .ant-menu-submenu .ant-menu-item.ant-menu-item-selected {
|
||||
@@ -531,39 +517,29 @@ body {
|
||||
|
||||
@keyframes wordsLoop {
|
||||
0% {
|
||||
// transform: translateX(100%);
|
||||
// -webkit-transform: translateX(100%);
|
||||
margin-left: 0;
|
||||
}
|
||||
100% {
|
||||
// transform: translateX(-100%);
|
||||
// -webkit-transform: translateX(-100%);
|
||||
margin-left: -300%;
|
||||
}
|
||||
}
|
||||
|
||||
.ops-side-bar.ant-menu-light {
|
||||
border-right-color: transparent;
|
||||
// background: @layout-background-light-color;
|
||||
// background: url('../assets/sidebar_background.png');
|
||||
background: #1d264c;
|
||||
// background-position-x: center;
|
||||
// background-position-y: center;
|
||||
background: @layout-sidebar-color;
|
||||
background-repeat: no-repeat !important;
|
||||
background-size: cover;
|
||||
.ant-menu-inline.ant-menu-sub {
|
||||
background-color: #000c37;
|
||||
background-color: @layout-sidebar-sub-color;
|
||||
}
|
||||
.ant-menu-submenu-content .ant-menu-item,
|
||||
.ant-menu-item {
|
||||
// margin: 0;
|
||||
> a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
color: @layout-sidebar-font-color;
|
||||
}
|
||||
&:hover {
|
||||
// background: #0000000a;
|
||||
.scroll {
|
||||
animation: 5s wordsLoop linear infinite normal;
|
||||
}
|
||||
@@ -578,7 +554,7 @@ body {
|
||||
white-space: nowrap;
|
||||
}
|
||||
a:hover {
|
||||
color: #fff;
|
||||
color: @layout-sidebar-font-color;
|
||||
font-weight: 600;
|
||||
}
|
||||
&:hover .custom-menu-extra-ellipsis {
|
||||
@@ -614,7 +590,7 @@ body {
|
||||
.ant-menu-item-selected {
|
||||
a,
|
||||
a:hover {
|
||||
color: #fff;
|
||||
color: @layout-sidebar-font-color;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
@@ -624,20 +600,20 @@ body {
|
||||
}
|
||||
|
||||
.ant-menu-submenu {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
color: @layout-sidebar-font-color;
|
||||
}
|
||||
.ant-menu-submenu-title:hover {
|
||||
color: #fff;
|
||||
background: #0000000a;
|
||||
color: @layout-sidebar-font-color;
|
||||
background: @layout-sidebar-selected-color;
|
||||
font-weight: 600;
|
||||
.ant-menu-submenu-arrow::before,
|
||||
.ant-menu-submenu-arrow::after {
|
||||
background: #fff;
|
||||
background-image: linear-gradient(to right, #2e2e2e, #2e2e2e);
|
||||
background: @layout-sidebar-arrow-color;
|
||||
}
|
||||
}
|
||||
.ant-menu-submenu-selected {
|
||||
> .ant-menu-submenu-title {
|
||||
color: #fff;
|
||||
color: @layout-sidebar-font-color;
|
||||
font-weight: 800;
|
||||
}
|
||||
}
|
||||
@@ -650,7 +626,7 @@ body {
|
||||
padding-left: 0 !important;
|
||||
> a {
|
||||
padding-left: 10px;
|
||||
color: rgba(255, 255, 255, 0.6) !important;
|
||||
color: @layout-sidebar-disabled-font-color !important;
|
||||
font-size: 12px;
|
||||
}
|
||||
&:hover {
|
||||
@@ -659,8 +635,11 @@ body {
|
||||
}
|
||||
.ant-menu-submenu-arrow::after,
|
||||
.ant-menu-submenu-arrow::before {
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
background-image: linear-gradient(to right, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.6)) !important;
|
||||
background: @layout-sidebar-arrow-color;
|
||||
}
|
||||
|
||||
.ant-menu-item.ant-menu-item-active:hover {
|
||||
background: @layout-sidebar-selected-color;
|
||||
}
|
||||
}
|
||||
// 侧边栏折叠时
|
||||
@@ -668,7 +647,7 @@ body {
|
||||
.ant-menu-submenu.ant-menu-submenu-placement-rightTop {
|
||||
> .ant-menu {
|
||||
// background: url('../assets/sidebar_background.png');
|
||||
background: #1d264c;
|
||||
background: @layout-sidebar-color;
|
||||
background-position-x: center;
|
||||
background-position-y: center;
|
||||
background-repeat: no-repeat !important;
|
||||
@@ -830,14 +809,21 @@ body {
|
||||
.el-input__inner {
|
||||
border-radius: 2px !important;
|
||||
}
|
||||
.el-input__inner:hover {
|
||||
border-color: #4596de !important;
|
||||
|
||||
.el-input__inner:hover,
|
||||
.el-select .el-input.is-focus .el-input__inner,
|
||||
.el-select .el-input__inner:focus,
|
||||
.el-button.is-plain:focus,
|
||||
.el-button.is-plain:hover,
|
||||
.el-input.is-active .el-input__inner,
|
||||
.el-input__inner:focus {
|
||||
border-color: @primary-color !important;
|
||||
}
|
||||
.el-select .el-input.is-focus .el-input__inner {
|
||||
border-color: #4596de !important;
|
||||
}
|
||||
.el-select-dropdown__item.selected {
|
||||
color: #4596de !important;
|
||||
.el-button--text,
|
||||
.el-select-dropdown__item.selected,
|
||||
.el-button.is-plain:focus,
|
||||
.el-button.is-plain:hover {
|
||||
color: @primary-color !important;
|
||||
}
|
||||
|
||||
.ant-tabs-nav .ant-tabs-tab {
|
||||
@@ -866,7 +852,7 @@ body {
|
||||
.ant-layout-sider {
|
||||
box-shadow: none;
|
||||
.ant-layout-sider-children {
|
||||
background: #custom_colors[color_2];
|
||||
background: @primary-color_5;
|
||||
.ant-menu {
|
||||
display: none;
|
||||
}
|
||||
@@ -897,7 +883,7 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.custom-vue-treeselect__control(@bgColor:#f0f5ff,@border:none) {
|
||||
.custom-vue-treeselect__control(@bgColor:@primary-color_5,@border:none) {
|
||||
background-color: @bgColor;
|
||||
border: @border;
|
||||
}
|
||||
@@ -916,6 +902,9 @@ body {
|
||||
border: none;
|
||||
box-shadow: 0px 4px 6px rgba(78, 94, 160, 0.25) !important;
|
||||
}
|
||||
.vue-treeselect__limit-tip-text {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义背景颜色和border
|
||||
@@ -937,30 +926,30 @@ body {
|
||||
}
|
||||
.vue-treeselect__option--highlight,
|
||||
.vue-treeselect__option--selected {
|
||||
color: #custom_colors[color_1];
|
||||
background-color: #f0f5ff !important;
|
||||
color: @primary-color;
|
||||
background-color: @primary-color_5 !important;
|
||||
}
|
||||
.vue-treeselect__checkbox--checked,
|
||||
.vue-treeselect__checkbox--indeterminate {
|
||||
border-color: #2f54eb !important;
|
||||
background: #2f54eb !important;
|
||||
border-color: @primary-color !important;
|
||||
background: @primary-color !important;
|
||||
}
|
||||
.vue-treeselect__label-container:hover {
|
||||
.vue-treeselect__checkbox--checked,
|
||||
.vue-treeselect__checkbox--indeterminate {
|
||||
border-color: #2f54eb !important;
|
||||
background: #2f54eb !important;
|
||||
border-color: @primary-color !important;
|
||||
background: @primary-color !important;
|
||||
}
|
||||
}
|
||||
.vue-treeselect__multi-value-item {
|
||||
background: #f0f5ff !important;
|
||||
color: #2f54eb !important;
|
||||
background: @primary-color_5 !important;
|
||||
color: @primary-color !important;
|
||||
}
|
||||
.vue-treeselect__value-remove {
|
||||
color: #2f54eb !important;
|
||||
color: @primary-color !important;
|
||||
}
|
||||
.vue-treeselect__label-container:hover .vue-treeselect__checkbox--unchecked {
|
||||
border-color: #2f54eb !important;
|
||||
border-color: @primary-color !important;
|
||||
}
|
||||
|
||||
//表格样式
|
||||
@@ -970,7 +959,7 @@ body {
|
||||
border: none !important;
|
||||
}
|
||||
.vxe-table--header-wrapper {
|
||||
background-color: #f0f5ff !important;
|
||||
background-color: @primary-color_5 !important;
|
||||
}
|
||||
.vxe-header--row .vxe-header--column:hover {
|
||||
background: #2f54eb1f !important;
|
||||
@@ -994,7 +983,7 @@ body {
|
||||
border: none !important;
|
||||
}
|
||||
.vxe-table--header-wrapper {
|
||||
background-color: #f0f5ff !important;
|
||||
background-color: @primary-color_5 !important;
|
||||
}
|
||||
// .vxe-table--header-wrapper.body--wrapper {
|
||||
// border-radius: 8px !important;
|
||||
@@ -1028,12 +1017,12 @@ body {
|
||||
.ops-input {
|
||||
.ant-input,
|
||||
.ant-time-picker-input {
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
.ops-input.ant-input {
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
border: none;
|
||||
}
|
||||
.ops-input.ant-input[disabled] {
|
||||
@@ -1088,14 +1077,40 @@ body {
|
||||
.vxe-table--render-default .vxe-cell--checkbox:not(.is--disabled):hover .vxe-checkbox--icon,
|
||||
.is--filter-active .vxe-cell--filter .vxe-filter--btn,
|
||||
.vxe-table .vxe-sort--asc-btn.sort--active,
|
||||
.vxe-table .vxe-sort--desc-btn.sort--active {
|
||||
color: #2f54eb !important;
|
||||
.vxe-table .vxe-sort--desc-btn.sort--active,
|
||||
.vxe-select-option.is--selected,
|
||||
.vxe-loading > .vxe-loading--chunk,
|
||||
.vxe-loading > .vxe-loading--warpper,
|
||||
.vxe-pager .vxe-pager--jump-next:not(.is--disabled).is--active,
|
||||
.vxe-pager .vxe-pager--jump-next:not(.is--disabled):focus,
|
||||
.vxe-pager .vxe-pager--jump-prev:not(.is--disabled).is--active,
|
||||
.vxe-pager .vxe-pager--jump-prev:not(.is--disabled):focus,
|
||||
.vxe-pager .vxe-pager--next-btn:not(.is--disabled).is--active,
|
||||
.vxe-pager .vxe-pager--next-btn:not(.is--disabled):focus,
|
||||
.vxe-pager .vxe-pager--num-btn:not(.is--disabled).is--active,
|
||||
.vxe-pager .vxe-pager--num-btn:not(.is--disabled):focus,
|
||||
.vxe-pager .vxe-pager--prev-btn:not(.is--disabled).is--active,
|
||||
.vxe-pager .vxe-pager--prev-btn:not(.is--disabled):focus,
|
||||
.vxe-button.type--text:not(.is--disabled):hover,
|
||||
.vxe-table--filter-footer > button:hover {
|
||||
color: @primary-color !important;
|
||||
}
|
||||
|
||||
.vxe-cell .vxe-default-input:focus,
|
||||
.vxe-cell .vxe-default-select:focus,
|
||||
.vxe-cell .vxe-default-textarea:focus,
|
||||
.vxe-table--filter-wrapper .vxe-default-input:focus,
|
||||
.vxe-table--filter-wrapper .vxe-default-select:focus,
|
||||
.vxe-table--filter-wrapper .vxe-default-textarea:focus,
|
||||
.vxe-select.is--active:not(.is--filter) > .vxe-input .vxe-input--inner,
|
||||
.vxe-input:not(.is--disabled).is--active .vxe-input--inner {
|
||||
border-color: @primary-color !important;
|
||||
}
|
||||
|
||||
//批量操作
|
||||
.ops-list-batch-action {
|
||||
display: inline-block;
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
> span {
|
||||
@@ -1103,11 +1118,11 @@ body {
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
> span:last-child {
|
||||
color: rgba(47, 84, 235, 0.55);
|
||||
color: @primary-color;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
@@ -1116,27 +1131,28 @@ body {
|
||||
.ops-tab.ant-tabs.ant-tabs-card {
|
||||
.ant-tabs-card-bar {
|
||||
margin: 0;
|
||||
border-bottom: none;
|
||||
.ant-tabs-nav-container {
|
||||
background-color: #custom_colors[color_2];
|
||||
.ant-tabs-tab {
|
||||
border: none;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
background: @primary-color_6;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.ant-tabs-tab-active {
|
||||
background: #fff !important;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//button
|
||||
.ops-button-primary {
|
||||
background-color: #custom_colors[color_2];
|
||||
border-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_4;
|
||||
border-color: @primary-color_4;
|
||||
color: @primary-color;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
//select
|
||||
@@ -1158,8 +1174,8 @@ body {
|
||||
}
|
||||
}
|
||||
.ant-select-selection {
|
||||
background-color: #custom_colors[color_2];
|
||||
border-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
border-color: @primary-color_5;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1167,8 +1183,8 @@ body {
|
||||
.ops-dropdown {
|
||||
.ant-dropdown-menu-item:hover,
|
||||
.ant-dropdown-menu-submenu-title:hover {
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1180,7 +1196,7 @@ body {
|
||||
border-bottom: none;
|
||||
.ant-modal-title {
|
||||
padding-left: 10px;
|
||||
border-left: 4px solid #custom_colors[color_1];
|
||||
border-left: 4px solid @primary-color;
|
||||
}
|
||||
}
|
||||
.ant-modal-footer {
|
||||
@@ -1193,7 +1209,7 @@ body {
|
||||
width: 276px;
|
||||
}
|
||||
.ant-tooltip-inner {
|
||||
background-color: #custom_colors[color_3];
|
||||
background-color: @primary-color_3;
|
||||
border-radius: '4px';
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
@@ -1208,7 +1224,7 @@ body {
|
||||
.ant-tooltip-arrow::before {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
background-color: #custom_colors[color_3];
|
||||
background-color: @primary-color_3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1218,7 +1234,7 @@ body {
|
||||
|
||||
.el-tabs__header {
|
||||
border-bottom: none;
|
||||
background-color: #f0f5ff;
|
||||
background-color: @primary-color_5;
|
||||
border-radius: 8px 8px 0px 0px;
|
||||
}
|
||||
|
||||
@@ -1229,7 +1245,7 @@ body {
|
||||
|
||||
.el-tabs__header .el-tabs__item.is-active {
|
||||
background-color: white;
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
.el-tabs__header .el-tabs__item:first-child.is-active {
|
||||
border-top-left-radius: 8px;
|
||||
@@ -1240,12 +1256,12 @@ body {
|
||||
}
|
||||
|
||||
.el-radio__input.is-checked .el-radio__inner {
|
||||
background-color: #custom_colors[color_1];
|
||||
border-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
border-color: @primary-color;
|
||||
}
|
||||
|
||||
.el-radio__input.is-checked + .el-radio__label {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
.el-tab-pane {
|
||||
@@ -1283,14 +1299,14 @@ body {
|
||||
|
||||
// a-drop-down
|
||||
.ant-dropdown-menu-item-active {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
.ant-tag {
|
||||
&.ops-perm-tag {
|
||||
border: none;
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1303,6 +1319,15 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
// json editor
|
||||
.jsoneditor-vue {
|
||||
div.jsoneditor {
|
||||
border: none;
|
||||
}
|
||||
div.jsoneditor-menu {
|
||||
border-bottom-color: @primary-color;
|
||||
}
|
||||
}
|
||||
// .ant-menu.ant-menu-light {
|
||||
// &.ops-menu {
|
||||
// background-color: white;
|
||||
|
@@ -1,7 +1,22 @@
|
||||
@border-radius-base: 2px; // 组件/浮层圆角
|
||||
@primary-color: #2f54eb; // 全局主色
|
||||
@border-radius-box: 4px; // big box radius
|
||||
|
||||
@primary-color: #2f54eb; // 全局主色 六大品牌色
|
||||
@primary-color_2: #7f97fa;
|
||||
@primary-color_3: #d3e3fd;
|
||||
@primary-color_4: #e1efff;
|
||||
@primary-color_5: #f0f5ff;
|
||||
@primary-color_6: #f9fbff;
|
||||
|
||||
@text-color_1: #1d2119;
|
||||
@text-color_2: #4e5969;
|
||||
@text-color_3: #86909c;
|
||||
@text-color_4: #a5a9bc;
|
||||
|
||||
@border-color: #e4e7ed;
|
||||
|
||||
@scrollbar-color: rgba(47, 122, 235, 0.2);
|
||||
|
||||
// @layout-header-background: #1a3652;
|
||||
@layout-header-background: #fff;
|
||||
@layout-header-height: 40px;
|
||||
@layout-header-icon-height: 34px;
|
||||
@@ -15,13 +30,20 @@
|
||||
@layout-background-light-color: #fafafa;
|
||||
@layout-background-light-color-light: #f0f0f0;
|
||||
|
||||
@layout-sidebar-color: #1d264c; //bg
|
||||
@layout-sidebar-sub-color: #000c37; //bg
|
||||
@layout-sidebar-selected-color: #3e4a71; //selected bg
|
||||
@layout-sidebar-arrow-color: rgba(255, 255, 255, 0.6);
|
||||
@layout-sidebar-font-color: #fff;
|
||||
@layout-sidebar-disabled-font-color: rgba(255, 255, 255, 0.6);
|
||||
|
||||
#custom_colors() {
|
||||
color_1: #2f54eb;
|
||||
color_2: #f0f5ff;
|
||||
color_3: #D2E2FF;
|
||||
color_1: #2f54eb; //primary color
|
||||
color_2: #f0f5ff; //light background color
|
||||
color_3: #d2e2ff;
|
||||
}
|
||||
|
||||
.ops_display_wrapper(@backgroundColor:#f0f5ff) {
|
||||
.ops_display_wrapper(@backgroundColor:@primary-color_5) {
|
||||
cursor: pointer;
|
||||
padding: 5px 8px;
|
||||
background-color: @backgroundColor;
|
||||
@@ -35,10 +57,10 @@
|
||||
cursor: pointer;
|
||||
padding: 5px 10px;
|
||||
&:hover {
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
}
|
||||
}
|
||||
.ops_popover_item_selected() {
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
}
|
||||
|
@@ -76,7 +76,8 @@ const AppDeviceEnquire = {
|
||||
const mixinPermissions = {
|
||||
computed: {
|
||||
...mapState({
|
||||
detailPermissions: state => state.user.detailPermissions
|
||||
detailPermissions: state => state.user.detailPermissions,
|
||||
roles: state => state.user.roles
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
@@ -85,7 +86,7 @@ const mixinPermissions = {
|
||||
hasDetailPermission(appName, resourceName, perms = []) {
|
||||
const appNamePer = this.detailPermissions[`${appName}`]
|
||||
const _findResourcePermissions = appNamePer.find(item => item.name === resourceName)
|
||||
return _findResourcePermissions.permissions.some(item => perms.includes(item))
|
||||
return this.roles?.permissions.includes('acl_admin') || this.roles?.permissions.includes('backend_admin') || _findResourcePermissions?.permissions.some(item => perms.includes(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -60,34 +60,6 @@
|
||||
<vxe-column field="mobile" :title="$t('cs.companyStructure.mobile')" min-width="80"></vxe-column>
|
||||
<vxe-column field="position_name" :title="$t('cs.companyStructure.positionName')" min-width="80"></vxe-column>
|
||||
<vxe-column field="department_name" :title="$t('cs.companyStructure.departmentName')" min-width="80"></vxe-column>
|
||||
<vxe-column field="current_company" v-if="useDFC" :title="$t('cs.companyStructure.currentCompany')" min-width="120"></vxe-column>
|
||||
<vxe-column field="dfc_entry_date" v-if="useDFC" :title="$t('cs.companyStructure.dfcEntryDate')" min-width="120"></vxe-column>
|
||||
<vxe-column field="entry_date" :title="$t('cs.companyStructure.entryDate')" min-width="120"></vxe-column>
|
||||
<vxe-column field="is_internship" :title="$t('cs.companyStructure.isInternship')" min-width="120"></vxe-column>
|
||||
<vxe-column field="leave_date" :title="$t('cs.companyStructure.leaveDate')" min-width="120"></vxe-column>
|
||||
<vxe-column field="id_card" :title="$t('cs.companyStructure.idCard')" min-width="120"></vxe-column>
|
||||
<vxe-column field="nation" :title="$t('cs.companyStructure.nation')" min-width="80"></vxe-column>
|
||||
<vxe-column field="id_place" :title="$t('cs.companyStructure.idPlace')" min-width="80"></vxe-column>
|
||||
<vxe-column field="party" :title="$t('cs.companyStructure.party')" min-width="80"></vxe-column>
|
||||
<vxe-column field="household_registration_type" :title="$t('cs.companyStructure.householdRegistrationType')" min-width="80"></vxe-column>
|
||||
<vxe-column field="hometown" :title="$t('cs.companyStructure.homewtown')" min-width="80"></vxe-column>
|
||||
<vxe-column field="marry" :title="$t('cs.companyStructure.marry')" min-width="80"></vxe-column>
|
||||
<vxe-column field="max_degree" :title="$t('cs.companyStructure.maxDegree')" min-width="80"></vxe-column>
|
||||
<vxe-column field="emergency_person" :title="$t('cs.companyStructure.emergencyPerson')" min-width="120"></vxe-column>
|
||||
<vxe-column field="emergency_phone" :title="$t('cs.companyStructure.emergencyPhone')" min-width="120"></vxe-column>
|
||||
<vxe-column field="bank_card_number" :title="$t('cs.companyStructure.bankCardNumber')" min-width="120"></vxe-column>
|
||||
<vxe-column field="bank_card_name" :title="$t('cs.companyStructure.bankCardName')" min-width="80"></vxe-column>
|
||||
<vxe-column field="opening_bank" :title="$t('cs.companyStructure.openingBank')" min-width="80"></vxe-column>
|
||||
<vxe-column field="account_opening_location" :title="$t('cs.companyStructure.accountOpeningLocation')" min-width="120"></vxe-column>
|
||||
<vxe-column field="school" :title="$t('cs.companyStructure.school')" min-width="80"></vxe-column>
|
||||
<vxe-column field="major" :title="$t('cs.companyStructure.major')" min-width="80"></vxe-column>
|
||||
<vxe-column field="education" :title="$t('cs.companyStructure.education')" min-width="80"></vxe-column>
|
||||
<vxe-column field="graduation_year" :title="$t('cs.companyStructure.graduationYear')" min-width="120"></vxe-column>
|
||||
<vxe-column field="birth_date" :title="$t('cs.companyStructure.birthDate')" min-width="120"></vxe-column>
|
||||
<vxe-column field="birth_place" :title="$t('cs.companyStructure.birthPlace')" min-width="120"></vxe-column>
|
||||
<vxe-column field="nationality_region" :title="$t('cs.companyStructure.nationalityRegion')" min-width="120"></vxe-column>
|
||||
<vxe-column field="first_entry_date" :title="$t('cs.companyStructure.firstEntryDate')" min-width="120"></vxe-column>
|
||||
<vxe-column field="estimated_departure_date" :title="$t('cs.companyStructure.estimatedDepartureDate')" min-width="120"></vxe-column>
|
||||
<vxe-column v-if="has_error" field="err" :title="$t('cs.companyStructure.importFailedReason')" min-width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<span :style="{ color: '#D81E06' }">{{ row.err }}</span>
|
||||
@@ -303,98 +275,6 @@ export default {
|
||||
v: '部门',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '目前所属主体',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '初始入职日期',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '目前主体入职日期',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '正式/实习生',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '离职日期',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '身份证号码',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '民族',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '籍贯',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '组织关系',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '户籍类型',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '户口所在地',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '婚姻情况',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '最高学历',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '紧急联系人',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '紧急联系电话',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '卡号',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '银行',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '开户行',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '开户地',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '学校',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '专业',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '学历',
|
||||
t: 's',
|
||||
},
|
||||
{
|
||||
v: '毕业年份',
|
||||
t: 's',
|
||||
},
|
||||
],
|
||||
]
|
||||
data[1] = data[1].filter((item) => item['v'] !== '目前所属主体')
|
||||
|
@@ -372,7 +372,7 @@ export default {
|
||||
{ label: this.$t('cs.companyStructure.mobile'), value: 'mobile' },
|
||||
{ label: this.$t('cs.companyStructure.departmentName'), value: 'department_name' },
|
||||
{ label: this.$t('cs.companyStructure.positionName'), value: 'position_name' },
|
||||
{ label: this.$t('cs.companyStructure.departmentDirector'), value: 'direct_supervisor_id' },
|
||||
{ label: this.$t('cs.companyStructure.supervisor'), value: 'direct_supervisor_id' },
|
||||
]
|
||||
},
|
||||
sceneList () {
|
||||
|
@@ -10,11 +10,12 @@
|
||||
:noOptionsText="$t('cs.components.empty')"
|
||||
:class="className ? className : 'ops-setting-treeselect'"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
:limit="20"
|
||||
:limit="limit"
|
||||
:limitText="(count) => `+ ${count}`"
|
||||
v-bind="$attrs"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
:flat="flat"
|
||||
>
|
||||
</treeselect>
|
||||
</template>
|
||||
@@ -60,6 +61,14 @@ export default {
|
||||
type: String,
|
||||
default: 'employee_id',
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
flat: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
|
@@ -139,7 +139,7 @@ export default {
|
||||
console.log('login form', values)
|
||||
const loginParams = { ...values }
|
||||
delete loginParams.username
|
||||
loginParams[!state.loginType ? 'email' : 'username'] = values.username
|
||||
loginParams.username = values.username
|
||||
loginParams.password = appConfig.useEncryption ? md5(values.password) : values.password
|
||||
localStorage.setItem('ops_auth_type', '')
|
||||
Login({ userInfo: loginParams })
|
||||
|
@@ -18,7 +18,7 @@ module.exports = {
|
||||
// TODO 需要增加根据环境不开启主题需求
|
||||
new ThemeColorReplacer({
|
||||
fileName: 'css/theme-colors-[contenthash:8].css',
|
||||
matchColors: getAntdSerials('#1890ff'), // 主色系列
|
||||
matchColors: getAntdSerials('#2f54eb'), // 主色系列
|
||||
// 改变样式选择器,解决样式覆盖问题
|
||||
changeSelector(selector) {
|
||||
switch (selector) {
|
||||
@@ -83,11 +83,9 @@ module.exports = {
|
||||
less: {
|
||||
modifyVars: {
|
||||
/* less 变量覆盖,用于自定义 ant design 主题 */
|
||||
/*
|
||||
'primary-color': '#F5222D',
|
||||
'link-color': '#F5222D',
|
||||
'border-radius-base': '4px',
|
||||
*/
|
||||
'primary-color': '#2f54eb',
|
||||
// 'link-color': '#F5222D',
|
||||
// 'border-radius-base': '4px',
|
||||
},
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
|
@@ -33,7 +33,7 @@ services:
|
||||
- redis
|
||||
|
||||
cmdb-api:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.3.10
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.3.13
|
||||
# build:
|
||||
# context: .
|
||||
# target: cmdb-api
|
||||
@@ -57,6 +57,8 @@ services:
|
||||
nohup flask cmdb-trigger > trigger.log 2>&1 &
|
||||
flask cmdb-init-cache
|
||||
flask cmdb-init-acl
|
||||
flask init-import-user-from-acl
|
||||
flask init-department
|
||||
flask cmdb-counter > counter.log 2>&1
|
||||
|
||||
depends_on:
|
||||
@@ -68,7 +70,7 @@ services:
|
||||
- cmdb-api
|
||||
|
||||
cmdb-ui:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.3.10
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.3.13
|
||||
# build:
|
||||
# context: .
|
||||
# target: cmdb-ui
|
||||
|
130
docs/cmdb.sql
130
docs/cmdb.sql
File diff suppressed because one or more lines are too long
@@ -13,7 +13,7 @@
|
||||
**set database in config file cmdb-api/settings.py**
|
||||
|
||||
- In cmdb directory,start in order as follows:
|
||||
- enviroment: `make env`
|
||||
- environment: `make env`
|
||||
- start API: `make api`
|
||||
- start UI: `make ui`
|
||||
- start worker: `make worker`
|
||||
|
Reference in New Issue
Block a user