mirror of
https://github.com/veops/cmdb.git
synced 2025-09-13 23:16:54 +08:00
Compare commits
29 Commits
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 |
@@ -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
|
||||
|
@@ -1,13 +1,14 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import click
|
||||
import copy
|
||||
import datetime
|
||||
import json
|
||||
import requests
|
||||
import time
|
||||
import uuid
|
||||
|
||||
import click
|
||||
import requests
|
||||
from flask import current_app
|
||||
from flask.cli import with_appcontext
|
||||
from flask_login import login_user
|
||||
@@ -196,6 +197,8 @@ def cmdb_counter():
|
||||
CMDBCounterCache.flush_adc_counter()
|
||||
i = 0
|
||||
|
||||
CMDBCounterCache.flush_sub_counter()
|
||||
|
||||
i += 1
|
||||
except:
|
||||
import traceback
|
||||
|
@@ -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,9 +89,12 @@ 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'")
|
||||
|
@@ -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)
|
||||
|
@@ -309,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
|
||||
|
@@ -5,6 +5,8 @@ import copy
|
||||
import datetime
|
||||
import json
|
||||
import threading
|
||||
|
||||
import redis_lock
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
@@ -13,7 +15,6 @@ 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
|
||||
@@ -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 = "******"
|
||||
|
||||
|
||||
@@ -279,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:
|
||||
@@ -309,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}
|
||||
@@ -324,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:
|
||||
@@ -346,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)):
|
||||
@@ -381,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
|
||||
@@ -393,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}
|
||||
|
||||
@@ -425,7 +439,9 @@ 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:
|
||||
@@ -454,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)
|
||||
@@ -512,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):
|
||||
@@ -540,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
|
||||
|
||||
@@ -846,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):
|
||||
|
||||
@@ -901,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
|
||||
|
||||
@@ -961,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)
|
||||
|
||||
@@ -997,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
|
||||
|
||||
@@ -1008,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):
|
||||
@@ -1051,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
|
||||
|
@@ -1,6 +1,7 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import copy
|
||||
|
||||
import toposort
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
@@ -41,6 +42,7 @@ from api.models.cmdb import CITypeAttributeGroup
|
||||
from api.models.cmdb import CITypeAttributeGroupItem
|
||||
from api.models.cmdb import 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
|
||||
@@ -74,6 +76,10 @@ 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, like=True):
|
||||
resources = None
|
||||
@@ -86,6 +92,7 @@ class CITypeManager(object):
|
||||
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)
|
||||
|
||||
@@ -132,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)
|
||||
@@ -215,10 +227,12 @@ 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,
|
||||
@@ -230,6 +244,12 @@ class CITypeManager(object):
|
||||
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()
|
||||
@@ -242,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
|
||||
|
||||
@@ -262,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"])
|
||||
|
||||
@@ -271,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)
|
||||
@@ -362,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:
|
||||
@@ -512,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,
|
||||
@@ -529,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)
|
||||
@@ -656,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:
|
||||
@@ -739,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):
|
||||
@@ -891,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
|
||||
|
@@ -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,27 +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()
|
||||
|
||||
return obj
|
||||
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'):
|
||||
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:
|
||||
@@ -140,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:
|
||||
@@ -153,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):
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
import copy
|
||||
|
||||
import six
|
||||
import toposort
|
||||
from flask import abort
|
||||
@@ -14,6 +15,7 @@ from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
from api.lib.cmdb.cache import 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
|
||||
@@ -112,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):
|
||||
@@ -123,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
|
||||
|
||||
|
@@ -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 = []
|
||||
|
||||
|
@@ -302,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))
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
from sqlalchemy.dialects.mysql import DOUBLE
|
||||
|
||||
from api.extensions import db
|
||||
@@ -56,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"
|
||||
|
||||
@@ -558,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,13 +494,14 @@ 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)
|
||||
|
||||
resource = None
|
||||
if 'ci_filter' in request.values or 'attr_filter' in request.values:
|
||||
resource = CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values)
|
||||
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 resource:
|
||||
if not new_resource:
|
||||
from api.tasks.acl import role_rebuild
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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 {
|
||||
|
@@ -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'
|
||||
|
@@ -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',
|
||||
|
@@ -25,6 +25,7 @@ export default {
|
||||
deleting: '正在删除',
|
||||
deletingTip: '正在删除,共{total}个,成功{successNum}个,失败{errorNum}个',
|
||||
grant: '授权',
|
||||
revoke: '回收',
|
||||
login_at: '登录时间',
|
||||
logout_at: '登出时间',
|
||||
createSuccess: '创建成功',
|
||||
|
@@ -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',
|
||||
})
|
||||
}
|
||||
|
@@ -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}]?',
|
||||
@@ -175,7 +177,12 @@ const cmdb_en = {
|
||||
time: 'Time',
|
||||
json: 'JSON',
|
||||
event: 'Event',
|
||||
reg: 'Regex'
|
||||
reg: 'Regex',
|
||||
isInherit: 'Inherit',
|
||||
inheritType: 'Inherit Type',
|
||||
inheritTypePlaceholder: 'Please select inherit types',
|
||||
inheritFrom: 'inherit from {name}',
|
||||
groupInheritFrom: 'Please go to the {name} for modification'
|
||||
},
|
||||
components: {
|
||||
unselectAttributes: 'Unselected',
|
||||
@@ -217,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',
|
||||
@@ -460,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',
|
||||
@@ -475,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}】 吗?',
|
||||
@@ -175,7 +177,12 @@ const cmdb_zh = {
|
||||
time: '时间',
|
||||
json: 'JSON',
|
||||
event: '事件',
|
||||
reg: '正则校验'
|
||||
reg: '正则校验',
|
||||
isInherit: '是否继承',
|
||||
inheritType: '继承模型',
|
||||
inheritTypePlaceholder: '请选择继承模型(多选)',
|
||||
inheritFrom: '属性继承自{name}',
|
||||
groupInheritFrom: '请至{name}进行修改'
|
||||
},
|
||||
components: {
|
||||
unselectAttributes: '未选属性',
|
||||
@@ -217,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: '已',
|
||||
@@ -459,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: '删除节点',
|
||||
@@ -474,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',
|
||||
|
@@ -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
|
||||
@@ -297,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'
|
||||
@@ -320,7 +320,7 @@ export default {
|
||||
components: {
|
||||
SearchForm,
|
||||
CreateInstanceForm,
|
||||
CiDetail,
|
||||
CiDetailDrawer,
|
||||
JsonEditor,
|
||||
PasswordField,
|
||||
EditAttrsPopover,
|
||||
|
@@ -220,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
|
||||
})
|
||||
},
|
||||
@@ -296,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
|
||||
@@ -363,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)
|
||||
|
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 {
|
@@ -25,7 +25,9 @@
|
||||
initialValue:
|
||||
attr.default && attr.default.default
|
||||
? attr.is_list
|
||||
? attr.default.default.split(',')
|
||||
? Array.isArray(attr.default.default)
|
||||
? attr.default.default
|
||||
: attr.default.default.split(',')
|
||||
: attr.default.default
|
||||
: null,
|
||||
},
|
||||
|
@@ -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 {
|
||||
|
@@ -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(() => {
|
||||
|
@@ -139,6 +139,7 @@
|
||||
<a><a-icon type="edit" @click="(e) => handleEdit(e, ci)"/></a>
|
||||
<a
|
||||
v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"
|
||||
:disabled="ci.inherited"
|
||||
@click="(e) => handleDownloadCiType(e, ci)"
|
||||
>
|
||||
<a-icon type="download" />
|
||||
@@ -176,6 +177,7 @@
|
||||
placement="right"
|
||||
width="900px"
|
||||
:destroyOnClose="true"
|
||||
:bodyStyle="{ height: 'calc(100vh - 108px)' }"
|
||||
>
|
||||
<a-form
|
||||
:form="form"
|
||||
@@ -204,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>
|
||||
@@ -239,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
|
||||
@@ -296,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,
|
||||
@@ -316,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',
|
||||
@@ -330,6 +379,7 @@ export default {
|
||||
SplitPane,
|
||||
OpsMoveIcon,
|
||||
AttributeStore,
|
||||
CMDBTypeSelect,
|
||||
},
|
||||
inject: ['reload'],
|
||||
data() {
|
||||
@@ -368,6 +418,9 @@ export default {
|
||||
default_order_asc: '1',
|
||||
|
||||
allTreeDepAndEmp: [],
|
||||
|
||||
editCiType: null,
|
||||
isInherit: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -563,6 +616,7 @@ export default {
|
||||
this.filterInput = ''
|
||||
this.form.resetFields()
|
||||
this.drawerVisible = false
|
||||
this.isInherit = false
|
||||
},
|
||||
handleCreateNewAttrDone() {
|
||||
this.getAttributes()
|
||||
@@ -583,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,
|
||||
@@ -593,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
|
||||
@@ -767,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'
|
||||
|
||||
@@ -787,6 +887,7 @@ export default {
|
||||
? record.default_order_attr.slice(1)
|
||||
: record.default_order_attr,
|
||||
})
|
||||
|
||||
this.$refs.iconArea.setIcon(
|
||||
record.icon
|
||||
? {
|
||||
@@ -798,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()
|
||||
|
@@ -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')">
|
||||
|
@@ -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>
|
||||
@@ -313,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])"
|
||||
@@ -325,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'
|
||||
@@ -349,20 +392,23 @@ import {
|
||||
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { searchCI2, updateCI, deleteCI } from '@/modules/cmdb/api/ci'
|
||||
import { getCITypes } from '../../api/CIType'
|
||||
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',
|
||||
@@ -370,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: [],
|
||||
@@ -430,6 +478,10 @@ export default {
|
||||
passwordValue: {},
|
||||
lastEditCiId: null,
|
||||
isContinueCloseEdit: true,
|
||||
|
||||
contextMenuKey: null,
|
||||
showBatchLevel: null,
|
||||
batchTreeKey: [],
|
||||
}
|
||||
},
|
||||
|
||||
@@ -452,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 {
|
||||
@@ -505,8 +572,8 @@ export default {
|
||||
})
|
||||
},
|
||||
getCITypesList() {
|
||||
getCITypes().then((res) => {
|
||||
this.ciTypes = res.ci_types
|
||||
getCITypeIcons().then((res) => {
|
||||
this.ciTypeIcons = res
|
||||
})
|
||||
},
|
||||
refreshTable() {
|
||||
@@ -572,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]}`
|
||||
|
||||
@@ -634,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
|
||||
@@ -645,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)
|
||||
|
||||
@@ -666,7 +742,6 @@ export default {
|
||||
|
||||
this.calcColumns()
|
||||
}
|
||||
|
||||
if (refreshType === 'refreshNumber') {
|
||||
const promises = this.treeKeys.map((key, index) => {
|
||||
let ancestor_ids
|
||||
@@ -684,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) => {
|
||||
@@ -741,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(',')) {
|
||||
@@ -780,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) => {
|
||||
@@ -797,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] + '']
|
||||
@@ -806,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 (
|
||||
@@ -829,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 = []
|
||||
@@ -852,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] + '']
|
||||
@@ -862,7 +948,7 @@ export default {
|
||||
}
|
||||
})
|
||||
await Promise.all(promises)
|
||||
this.wrapTreeData(facet, 'loadNoRoot')
|
||||
this.wrapTreeData(facet)
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -917,6 +1003,7 @@ export default {
|
||||
}
|
||||
this.treeKeys = treeNode.eventKey.split('@^@').filter((item) => item !== '')
|
||||
this.treeNode = treeNode
|
||||
// this.refreshTable()
|
||||
resolve()
|
||||
})
|
||||
},
|
||||
@@ -979,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])
|
||||
@@ -996,10 +1082,21 @@ 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>,
|
||||
@@ -1012,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)
|
||||
@@ -1066,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()
|
||||
})
|
||||
@@ -1438,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']
|
||||
})
|
||||
|
@@ -371,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)"
|
||||
@@ -404,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'
|
||||
@@ -424,7 +424,7 @@ export default {
|
||||
SplitPane,
|
||||
TreeViewsNode,
|
||||
EditAttrsPopover,
|
||||
CiDetail,
|
||||
CiDetailDrawer,
|
||||
CreateInstanceForm,
|
||||
JsonEditor,
|
||||
BatchDownload,
|
||||
|
@@ -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: #custom_colors() [color_2];
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
|
||||
.layout.ant-layout {
|
||||
@@ -352,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
|
||||
@@ -504,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: @layout-sidebar-selected-color;
|
||||
// background-size: 228px 38px;
|
||||
// background-position-x: -10px;
|
||||
// background-position-y: center;
|
||||
transition: none;
|
||||
}
|
||||
.ops-side-bar.ant-menu .ant-menu-submenu .ant-menu-item.ant-menu-item-selected {
|
||||
@@ -518,24 +517,16 @@ 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: @layout-sidebar-color;
|
||||
// background-position-x: center;
|
||||
// background-position-y: center;
|
||||
background-repeat: no-repeat !important;
|
||||
background-size: cover;
|
||||
.ant-menu-inline.ant-menu-sub {
|
||||
@@ -543,14 +534,12 @@ body {
|
||||
}
|
||||
.ant-menu-submenu-content .ant-menu-item,
|
||||
.ant-menu-item {
|
||||
// margin: 0;
|
||||
> a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: @layout-sidebar-font-color;
|
||||
}
|
||||
&:hover {
|
||||
// background: #0000000a;
|
||||
.scroll {
|
||||
animation: 5s wordsLoop linear infinite normal;
|
||||
}
|
||||
@@ -828,13 +817,13 @@ body {
|
||||
.el-button.is-plain:hover,
|
||||
.el-input.is-active .el-input__inner,
|
||||
.el-input__inner:focus {
|
||||
border-color: #custom_colors() [color_1] !important;
|
||||
border-color: @primary-color !important;
|
||||
}
|
||||
.el-button--text,
|
||||
.el-select-dropdown__item.selected,
|
||||
.el-button.is-plain:focus,
|
||||
.el-button.is-plain:hover {
|
||||
color: #custom_colors() [color_1] !important;
|
||||
color: @primary-color !important;
|
||||
}
|
||||
|
||||
.ant-tabs-nav .ant-tabs-tab {
|
||||
@@ -863,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;
|
||||
}
|
||||
@@ -894,7 +883,7 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.custom-vue-treeselect__control(@bgColor:#custom_colors()[color_2],@border:none) {
|
||||
.custom-vue-treeselect__control(@bgColor:@primary-color_5,@border:none) {
|
||||
background-color: @bgColor;
|
||||
border: @border;
|
||||
}
|
||||
@@ -913,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
|
||||
@@ -934,30 +926,30 @@ body {
|
||||
}
|
||||
.vue-treeselect__option--highlight,
|
||||
.vue-treeselect__option--selected {
|
||||
color: #custom_colors[color_1];
|
||||
background-color: #custom_colors() [color_2] !important;
|
||||
color: @primary-color;
|
||||
background-color: @primary-color_5 !important;
|
||||
}
|
||||
.vue-treeselect__checkbox--checked,
|
||||
.vue-treeselect__checkbox--indeterminate {
|
||||
border-color: #custom_colors() [color_1] !important;
|
||||
background: #custom_colors() [color_1] !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: #custom_colors() [color_1] !important;
|
||||
background: #custom_colors() [color_1] !important;
|
||||
border-color: @primary-color !important;
|
||||
background: @primary-color !important;
|
||||
}
|
||||
}
|
||||
.vue-treeselect__multi-value-item {
|
||||
background: #custom_colors() [color_2] !important;
|
||||
color: #custom_colors() [color_1] !important;
|
||||
background: @primary-color_5 !important;
|
||||
color: @primary-color !important;
|
||||
}
|
||||
.vue-treeselect__value-remove {
|
||||
color: #custom_colors() [color_1] !important;
|
||||
color: @primary-color !important;
|
||||
}
|
||||
.vue-treeselect__label-container:hover .vue-treeselect__checkbox--unchecked {
|
||||
border-color: #custom_colors() [color_1] !important;
|
||||
border-color: @primary-color !important;
|
||||
}
|
||||
|
||||
//表格样式
|
||||
@@ -967,7 +959,7 @@ body {
|
||||
border: none !important;
|
||||
}
|
||||
.vxe-table--header-wrapper {
|
||||
background-color: #custom_colors() [color_2] !important;
|
||||
background-color: @primary-color_5 !important;
|
||||
}
|
||||
.vxe-header--row .vxe-header--column:hover {
|
||||
background: #2f54eb1f !important;
|
||||
@@ -991,7 +983,7 @@ body {
|
||||
border: none !important;
|
||||
}
|
||||
.vxe-table--header-wrapper {
|
||||
background-color: #custom_colors() [color_2] !important;
|
||||
background-color: @primary-color_5 !important;
|
||||
}
|
||||
// .vxe-table--header-wrapper.body--wrapper {
|
||||
// border-radius: 8px !important;
|
||||
@@ -1025,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] {
|
||||
@@ -1101,7 +1093,7 @@ body {
|
||||
.vxe-pager .vxe-pager--prev-btn:not(.is--disabled):focus,
|
||||
.vxe-button.type--text:not(.is--disabled):hover,
|
||||
.vxe-table--filter-footer > button:hover {
|
||||
color: #custom_colors() [color_1] !important;
|
||||
color: @primary-color !important;
|
||||
}
|
||||
|
||||
.vxe-cell .vxe-default-input:focus,
|
||||
@@ -1112,13 +1104,13 @@ body {
|
||||
.vxe-table--filter-wrapper .vxe-default-textarea:focus,
|
||||
.vxe-select.is--active:not(.is--filter) > .vxe-input .vxe-input--inner,
|
||||
.vxe-input:not(.is--disabled).is--active .vxe-input--inner {
|
||||
border-color: #custom_colors() [color_1] !important;
|
||||
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 {
|
||||
@@ -1126,11 +1118,11 @@ body {
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
> span:last-child {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
@@ -1139,17 +1131,17 @@ body {
|
||||
.ops-tab.ant-tabs.ant-tabs-card {
|
||||
.ant-tabs-card-bar {
|
||||
margin: 0;
|
||||
border-bottom: none;
|
||||
.ant-tabs-nav-container {
|
||||
background-color: #fff;
|
||||
.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: #custom_colors[color_2];
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1157,9 +1149,10 @@ body {
|
||||
|
||||
//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
|
||||
@@ -1181,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1190,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1203,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 {
|
||||
@@ -1216,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;
|
||||
@@ -1231,7 +1224,7 @@ body {
|
||||
.ant-tooltip-arrow::before {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
background-color: #custom_colors[color_3];
|
||||
background-color: @primary-color_3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1241,7 +1234,7 @@ body {
|
||||
|
||||
.el-tabs__header {
|
||||
border-bottom: none;
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
border-radius: 8px 8px 0px 0px;
|
||||
}
|
||||
|
||||
@@ -1252,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;
|
||||
@@ -1263,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 {
|
||||
@@ -1306,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1332,7 +1325,7 @@ body {
|
||||
border: none;
|
||||
}
|
||||
div.jsoneditor-menu {
|
||||
border-bottom-color: #custom_colors[color_1];
|
||||
border-bottom-color: @primary-color;
|
||||
}
|
||||
}
|
||||
// .ant-menu.ant-menu-light {
|
||||
|
@@ -1,5 +1,20 @@
|
||||
@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: #fff;
|
||||
@@ -28,7 +43,7 @@
|
||||
color_3: #d2e2ff;
|
||||
}
|
||||
|
||||
.ops_display_wrapper(@backgroundColor:#custom_colors()[color_2]) {
|
||||
.ops_display_wrapper(@backgroundColor:@primary-color_5) {
|
||||
cursor: pointer;
|
||||
padding: 5px 8px;
|
||||
background-color: @backgroundColor;
|
||||
@@ -42,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;
|
||||
}
|
||||
|
@@ -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 })
|
||||
|
@@ -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