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"
|
SQLAlchemy = "==1.4.49"
|
||||||
PyMySQL = "==1.1.0"
|
PyMySQL = "==1.1.0"
|
||||||
redis = "==4.6.0"
|
redis = "==4.6.0"
|
||||||
|
python-redis-lock = "==4.0.0"
|
||||||
# Migrations
|
# Migrations
|
||||||
Flask-Migrate = "==2.5.2"
|
Flask-Migrate = "==2.5.2"
|
||||||
# Deployment
|
# Deployment
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
import click
|
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import requests
|
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import click
|
||||||
|
import requests
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask.cli import with_appcontext
|
from flask.cli import with_appcontext
|
||||||
from flask_login import login_user
|
from flask_login import login_user
|
||||||
@@ -196,6 +197,8 @@ def cmdb_counter():
|
|||||||
CMDBCounterCache.flush_adc_counter()
|
CMDBCounterCache.flush_adc_counter()
|
||||||
i = 0
|
i = 0
|
||||||
|
|
||||||
|
CMDBCounterCache.flush_sub_counter()
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
|
@@ -4,7 +4,7 @@ from flask.cli import with_appcontext
|
|||||||
from werkzeug.datastructures import MultiDict
|
from werkzeug.datastructures import MultiDict
|
||||||
|
|
||||||
from api.lib.common_setting.acl import ACLManager
|
from api.lib.common_setting.acl import ACLManager
|
||||||
from api.lib.common_setting.employee import EmployeeAddForm
|
from api.lib.common_setting.employee import EmployeeAddForm, GrantEmployeeACLPerm
|
||||||
from api.lib.common_setting.resp_format import ErrFormat
|
from api.lib.common_setting.resp_format import ErrFormat
|
||||||
from api.models.common_setting import Employee, Department
|
from api.models.common_setting import Employee, Department
|
||||||
|
|
||||||
@@ -158,50 +158,11 @@ class InitDepartment(object):
|
|||||||
|
|
||||||
def init_backend_resource(self):
|
def init_backend_resource(self):
|
||||||
acl = self.check_app('backend')
|
acl = self.check_app('backend')
|
||||||
resources_types = acl.get_all_resources_types()
|
|
||||||
|
|
||||||
perms = ['read', 'grant', 'delete', 'update']
|
|
||||||
|
|
||||||
acl_rid = self.get_admin_user_rid()
|
acl_rid = self.get_admin_user_rid()
|
||||||
|
|
||||||
results = list(filter(lambda t: t['name'] == '操作权限', resources_types['groups']))
|
if acl_rid == 0:
|
||||||
if len(results) == 0:
|
return
|
||||||
payload = dict(
|
GrantEmployeeACLPerm(acl).grant_by_rid(acl_rid, True)
|
||||||
app_id=acl.app_name,
|
|
||||||
name='操作权限',
|
|
||||||
description='',
|
|
||||||
perms=perms
|
|
||||||
)
|
|
||||||
resource_type = acl.create_resources_type(payload)
|
|
||||||
else:
|
|
||||||
resource_type = results[0]
|
|
||||||
resource_type_id = resource_type['id']
|
|
||||||
existed_perms = resources_types.get('id2perms', {}).get(resource_type_id, [])
|
|
||||||
existed_perms = [p['name'] for p in existed_perms]
|
|
||||||
new_perms = []
|
|
||||||
for perm in perms:
|
|
||||||
if perm not in existed_perms:
|
|
||||||
new_perms.append(perm)
|
|
||||||
if len(new_perms) > 0:
|
|
||||||
resource_type['perms'] = existed_perms + new_perms
|
|
||||||
acl.update_resources_type(resource_type_id, resource_type)
|
|
||||||
|
|
||||||
resource_list = acl.get_resource_by_type(None, None, resource_type['id'])
|
|
||||||
|
|
||||||
for name in ['公司信息', '公司架构', '通知设置']:
|
|
||||||
target = list(filter(lambda r: r['name'] == name, resource_list))
|
|
||||||
if len(target) == 0:
|
|
||||||
payload = dict(
|
|
||||||
type_id=resource_type['id'],
|
|
||||||
app_id=acl.app_name,
|
|
||||||
name=name,
|
|
||||||
)
|
|
||||||
resource = acl.create_resource(payload)
|
|
||||||
else:
|
|
||||||
resource = target[0]
|
|
||||||
|
|
||||||
if acl_rid > 0:
|
|
||||||
acl.grant_resource(acl_rid, resource['id'], perms)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_app(app_name):
|
def check_app(app_name):
|
||||||
|
@@ -89,9 +89,12 @@ def db_setup():
|
|||||||
"""
|
"""
|
||||||
db.create_all()
|
db.create_all()
|
||||||
|
|
||||||
|
try:
|
||||||
db.session.execute("set global sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,"
|
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'")
|
"ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'")
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db.session.execute("set global tidb_enable_noop_functions='ON'")
|
db.session.execute("set global tidb_enable_noop_functions='ON'")
|
||||||
|
@@ -330,8 +330,8 @@ class AutoDiscoveryCICRUD(DBMixin):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_attributes_by_type_id(type_id):
|
def get_attributes_by_type_id(type_id):
|
||||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||||
attributes = [i[1] for i in CITypeAttributesCache.get2(type_id) or []]
|
attributes = [i[1] for i in CITypeAttributeManager.get_all_attributes(type_id) or []]
|
||||||
|
|
||||||
attr_names = set()
|
attr_names = set()
|
||||||
adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id)
|
adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id)
|
||||||
|
@@ -309,7 +309,7 @@ class CMDBCounterCache(object):
|
|||||||
|
|
||||||
s = RelSearch([i[0] for i in type_id_names], level, other_filer or '')
|
s = RelSearch([i[0] for i in type_id_names], level, other_filer or '')
|
||||||
try:
|
try:
|
||||||
stats = s.statistics(type_ids)
|
stats = s.statistics(type_ids, need_filter=False)
|
||||||
except SearchError as e:
|
except SearchError as e:
|
||||||
current_app.logger.error(e)
|
current_app.logger.error(e)
|
||||||
return
|
return
|
||||||
|
@@ -5,6 +5,8 @@ import copy
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
import redis_lock
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
@@ -13,7 +15,6 @@ from werkzeug.exceptions import BadRequest
|
|||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
from api.extensions import rd
|
from api.extensions import rd
|
||||||
from api.lib.cmdb.cache import AttributeCache
|
from api.lib.cmdb.cache import AttributeCache
|
||||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
|
||||||
from api.lib.cmdb.cache import CITypeCache
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
from api.lib.cmdb.cache import CMDBCounterCache
|
from api.lib.cmdb.cache import CMDBCounterCache
|
||||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||||
@@ -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.perm.acl.acl import validate_permission
|
||||||
from api.lib.secrets.inner import InnerCrypt
|
from api.lib.secrets.inner import InnerCrypt
|
||||||
from api.lib.secrets.vault import VaultClient
|
from api.lib.secrets.vault import VaultClient
|
||||||
from api.lib.utils import Lock
|
|
||||||
from api.lib.utils import handle_arg_list
|
from api.lib.utils import handle_arg_list
|
||||||
from api.lib.webhook import webhook_request
|
from api.lib.webhook import webhook_request
|
||||||
from api.models.cmdb import AttributeHistory
|
from api.models.cmdb import AttributeHistory
|
||||||
from api.models.cmdb import AutoDiscoveryCI
|
from api.models.cmdb import AutoDiscoveryCI
|
||||||
from api.models.cmdb import CI
|
from api.models.cmdb import CI
|
||||||
from api.models.cmdb import CIRelation
|
from api.models.cmdb import CIRelation
|
||||||
from api.models.cmdb import CITypeAttribute
|
|
||||||
from api.models.cmdb import CITypeRelation
|
from api.models.cmdb import CITypeRelation
|
||||||
from api.models.cmdb import CITypeTrigger
|
from api.models.cmdb import CITypeTrigger
|
||||||
from api.tasks.cmdb import ci_cache
|
from api.tasks.cmdb import ci_cache
|
||||||
@@ -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_add
|
||||||
from api.tasks.cmdb import ci_relation_cache
|
from api.tasks.cmdb import ci_relation_cache
|
||||||
from api.tasks.cmdb import ci_relation_delete
|
from api.tasks.cmdb import ci_relation_delete
|
||||||
|
from api.tasks.cmdb import delete_id_filter
|
||||||
|
|
||||||
PRIVILEGED_USERS = {"worker", "cmdb_agent", "agent"}
|
|
||||||
PASSWORD_DEFAULT_SHOW = "******"
|
PASSWORD_DEFAULT_SHOW = "******"
|
||||||
|
|
||||||
|
|
||||||
@@ -279,10 +278,10 @@ class CIManager(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _auto_inc_id(attr):
|
def _auto_inc_id(attr):
|
||||||
db.session.remove()
|
db.session.commit()
|
||||||
|
|
||||||
value_table = TableMap(attr_name=attr.name).table
|
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(
|
max_v = value_table.get_by(attr_id=attr.id, only_query=True).order_by(
|
||||||
getattr(value_table, 'value').desc()).first()
|
getattr(value_table, 'value').desc()).first()
|
||||||
if max_v is not None:
|
if max_v is not None:
|
||||||
@@ -309,14 +308,18 @@ class CIManager(object):
|
|||||||
"""
|
"""
|
||||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
ci_type = CITypeManager.check_is_existed(ci_type_name)
|
ci_type = CITypeManager.check_is_existed(ci_type_name)
|
||||||
|
raw_dict = copy.deepcopy(ci_dict)
|
||||||
|
|
||||||
unique_key = AttributeCache.get(ci_type.unique_id) or abort(
|
unique_key = AttributeCache.get(ci_type.unique_id) or abort(
|
||||||
400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id)))
|
400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id)))
|
||||||
|
|
||||||
|
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 = ci_dict.get(unique_key.name) or ci_dict.get(unique_key.alias) or ci_dict.get(unique_key.id)
|
||||||
unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name))
|
unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name))
|
||||||
|
|
||||||
attrs = CITypeAttributesCache.get2(ci_type_name)
|
attrs = CITypeAttributeManager.get_all_attributes(ci_type.id)
|
||||||
ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
|
ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
|
||||||
ci_type_attrs_alias = {attr.alias: attr for _, attr in attrs}
|
ci_type_attrs_alias = {attr.alias: attr for _, attr in attrs}
|
||||||
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
||||||
@@ -324,8 +327,15 @@ class CIManager(object):
|
|||||||
ci = None
|
ci = None
|
||||||
record_id = None
|
record_id = None
|
||||||
password_dict = {}
|
password_dict = {}
|
||||||
need_lock = current_user.username not in current_app.config.get('PRIVILEGED_USERS', PRIVILEGED_USERS)
|
with redis_lock.Lock(rd.r, ci_type.name):
|
||||||
with Lock(ci_type_name, need_lock=need_lock):
|
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)
|
existed = cls.ci_is_exist(unique_key, unique_value, ci_type.id)
|
||||||
if existed is not None:
|
if existed is not None:
|
||||||
if exist_policy == ExistPolicy.REJECT:
|
if exist_policy == ExistPolicy.REJECT:
|
||||||
@@ -346,7 +356,8 @@ class CIManager(object):
|
|||||||
if attr.default.get('default') and attr.default.get('default') in (
|
if attr.default.get('default') and attr.default.get('default') in (
|
||||||
AttributeDefaultValueEnum.CREATED_AT, AttributeDefaultValueEnum.UPDATED_AT):
|
AttributeDefaultValueEnum.CREATED_AT, AttributeDefaultValueEnum.UPDATED_AT):
|
||||||
ci_dict[attr.name] = now
|
ci_dict[attr.name] = now
|
||||||
elif attr.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID:
|
elif (attr.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and
|
||||||
|
not ci_dict.get(attr.name)):
|
||||||
ci_dict[attr.name] = cls._auto_inc_id(attr)
|
ci_dict[attr.name] = cls._auto_inc_id(attr)
|
||||||
elif ((attr.name not in ci_dict and attr.alias not in ci_dict) or (
|
elif ((attr.name not in ci_dict and attr.alias not in ci_dict) or (
|
||||||
ci_dict.get(attr.name) is None and ci_dict.get(attr.alias) is None)):
|
ci_dict.get(attr.name) is None and ci_dict.get(attr.alias) is None)):
|
||||||
@@ -381,7 +392,7 @@ class CIManager(object):
|
|||||||
cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
|
cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
|
||||||
|
|
||||||
ref_ci_dict = dict()
|
ref_ci_dict = dict()
|
||||||
for k in ci_dict:
|
for k in copy.deepcopy(ci_dict):
|
||||||
if k.startswith("$") and "." in k:
|
if k.startswith("$") and "." in k:
|
||||||
ref_ci_dict[k] = ci_dict[k]
|
ref_ci_dict[k] = ci_dict[k]
|
||||||
continue
|
continue
|
||||||
@@ -393,7 +404,10 @@ class CIManager(object):
|
|||||||
_attr_name = ((ci_type_attrs_name.get(k) and ci_type_attrs_name[k].name) or
|
_attr_name = ((ci_type_attrs_name.get(k) and ci_type_attrs_name[k].name) or
|
||||||
(ci_type_attrs_alias.get(k) and ci_type_attrs_alias[k].name))
|
(ci_type_attrs_alias.get(k) and ci_type_attrs_alias[k].name))
|
||||||
if limit_attrs and _attr_name not in limit_attrs:
|
if limit_attrs and _attr_name not in limit_attrs:
|
||||||
|
if k in raw_dict:
|
||||||
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
|
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
|
||||||
|
else:
|
||||||
|
ci_dict.pop(k)
|
||||||
|
|
||||||
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name or k in ci_type_attrs_alias}
|
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name or k in ci_type_attrs_alias}
|
||||||
|
|
||||||
@@ -425,7 +439,9 @@ class CIManager(object):
|
|||||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
ci = self.confirm_ci_existed(ci_id)
|
ci = self.confirm_ci_existed(ci_id)
|
||||||
|
|
||||||
attrs = CITypeAttributesCache.get2(ci.type_id)
|
raw_dict = copy.deepcopy(ci_dict)
|
||||||
|
|
||||||
|
attrs = CITypeAttributeManager.get_all_attributes(ci.type_id)
|
||||||
ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
|
ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
|
||||||
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
||||||
for _, attr in attrs:
|
for _, attr in attrs:
|
||||||
@@ -454,17 +470,21 @@ class CIManager(object):
|
|||||||
limit_attrs = self._valid_ci_for_no_read(ci) if not _is_admin else {}
|
limit_attrs = self._valid_ci_for_no_read(ci) if not _is_admin else {}
|
||||||
|
|
||||||
record_id = None
|
record_id = None
|
||||||
need_lock = current_user.username not in current_app.config.get('PRIVILEGED_USERS', PRIVILEGED_USERS)
|
with redis_lock.Lock(rd.r, ci.ci_type.name):
|
||||||
with Lock(ci.ci_type.name, need_lock=need_lock):
|
db.session.commit()
|
||||||
|
|
||||||
self._valid_unique_constraint(ci.type_id, ci_dict, ci_id)
|
self._valid_unique_constraint(ci.type_id, ci_dict, ci_id)
|
||||||
|
|
||||||
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name}
|
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name}
|
||||||
key2attr = value_manager.valid_attr_value(ci_dict, ci.type_id, ci.id, ci_type_attrs_name,
|
key2attr = value_manager.valid_attr_value(ci_dict, ci.type_id, ci.id, ci_type_attrs_name,
|
||||||
ci_attr2type_attr=ci_attr2type_attr)
|
ci_attr2type_attr=ci_attr2type_attr)
|
||||||
if limit_attrs:
|
if limit_attrs:
|
||||||
for k in ci_dict:
|
for k in copy.deepcopy(ci_dict):
|
||||||
if k not in limit_attrs:
|
if k not in limit_attrs:
|
||||||
|
if k in raw_dict:
|
||||||
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
|
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
|
||||||
|
else:
|
||||||
|
ci_dict.pop(k)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr)
|
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr)
|
||||||
@@ -512,8 +532,7 @@ class CIManager(object):
|
|||||||
|
|
||||||
ci_delete_trigger.apply_async(args=(trigger, OperateType.DELETE, ci_dict), queue=CMDB_QUEUE)
|
ci_delete_trigger.apply_async(args=(trigger, OperateType.DELETE, ci_dict), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
attrs = CITypeAttribute.get_by(type_id=ci.type_id, to_dict=False)
|
attrs = [i for _, i in CITypeAttributeManager.get_all_attributes(type_id=ci.type_id)]
|
||||||
attrs = [AttributeCache.get(attr.attr_id) for attr in attrs]
|
|
||||||
for attr in attrs:
|
for attr in attrs:
|
||||||
value_table = TableMap(attr=attr).table
|
value_table = TableMap(attr=attr).table
|
||||||
for item in value_table.get_by(ci_id=ci_id, to_dict=False):
|
for item in value_table.get_by(ci_id=ci_id, to_dict=False):
|
||||||
@@ -540,6 +559,7 @@ class CIManager(object):
|
|||||||
AttributeHistoryManger.add(None, ci_id, [(None, OperateType.DELETE, ci_dict, None)], ci.type_id)
|
AttributeHistoryManger.add(None, ci_id, [(None, OperateType.DELETE, ci_dict, None)], ci.type_id)
|
||||||
|
|
||||||
ci_delete.apply_async(args=(ci_id,), queue=CMDB_QUEUE)
|
ci_delete.apply_async(args=(ci_id,), queue=CMDB_QUEUE)
|
||||||
|
delete_id_filter.apply_async(args=(ci_id,), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
return ci_id
|
return ci_id
|
||||||
|
|
||||||
@@ -846,6 +866,20 @@ class CIRelationManager(object):
|
|||||||
|
|
||||||
return numfound, len(ci_ids), result
|
return numfound, len(ci_ids), result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def recursive_children(ci_id):
|
||||||
|
result = []
|
||||||
|
|
||||||
|
def _get_children(_id):
|
||||||
|
children = CIRelation.get_by(first_ci_id=_id, to_dict=False)
|
||||||
|
result.extend([i.second_ci_id for i in children])
|
||||||
|
for child in children:
|
||||||
|
_get_children(child.second_ci_id)
|
||||||
|
|
||||||
|
_get_children(ci_id)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _sort_handler(sort_by, query_sql):
|
def _sort_handler(sort_by, query_sql):
|
||||||
|
|
||||||
@@ -901,7 +935,7 @@ class CIRelationManager(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
|
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
|
||||||
db.session.remove()
|
db.session.commit()
|
||||||
if type_relation.constraint == ConstraintEnum.Many2Many:
|
if type_relation.constraint == ConstraintEnum.Many2Many:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -961,7 +995,7 @@ class CIRelationManager(object):
|
|||||||
else:
|
else:
|
||||||
type_relation = CITypeRelation.get_by_id(relation_type_id)
|
type_relation = CITypeRelation.get_by_id(relation_type_id)
|
||||||
|
|
||||||
with 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)
|
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)
|
his_manager.add(cr, operate_type=OperateType.DELETE)
|
||||||
|
|
||||||
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
||||||
|
delete_id_filter.apply_async(args=(cr.second_ci_id,), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
return cr_id
|
return cr_id
|
||||||
|
|
||||||
@@ -1008,9 +1043,13 @@ class CIRelationManager(object):
|
|||||||
to_dict=False,
|
to_dict=False,
|
||||||
first=True)
|
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
|
@classmethod
|
||||||
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
|
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
|
||||||
@@ -1051,7 +1090,7 @@ class CIRelationManager(object):
|
|||||||
class CITriggerManager(object):
|
class CITriggerManager(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get(type_id):
|
def get(type_id):
|
||||||
db.session.remove()
|
db.session.commit()
|
||||||
return CITypeTrigger.get_by(type_id=type_id, to_dict=True)
|
return CITypeTrigger.get_by(type_id=type_id, to_dict=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
import toposort
|
import toposort
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
@@ -41,6 +42,7 @@ from api.models.cmdb import CITypeAttributeGroup
|
|||||||
from api.models.cmdb import CITypeAttributeGroupItem
|
from api.models.cmdb import CITypeAttributeGroupItem
|
||||||
from api.models.cmdb import CITypeGroup
|
from api.models.cmdb import CITypeGroup
|
||||||
from api.models.cmdb import CITypeGroupItem
|
from api.models.cmdb import CITypeGroupItem
|
||||||
|
from api.models.cmdb import CITypeInheritance
|
||||||
from api.models.cmdb import CITypeRelation
|
from api.models.cmdb import CITypeRelation
|
||||||
from api.models.cmdb import CITypeTrigger
|
from api.models.cmdb import CITypeTrigger
|
||||||
from api.models.cmdb import CITypeUniqueConstraint
|
from api.models.cmdb import CITypeUniqueConstraint
|
||||||
@@ -74,6 +76,10 @@ class CITypeManager(object):
|
|||||||
|
|
||||||
return CIType.get_by_id(ci_type.id)
|
return CIType.get_by_id(ci_type.id)
|
||||||
|
|
||||||
|
def get_icons(self):
|
||||||
|
return {i.id: i.icon or i.name for i in db.session.query(
|
||||||
|
self.cls.id, self.cls.icon, self.cls.name).filter(self.cls.deleted.is_(False))}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_ci_types(type_name=None, like=True):
|
def get_ci_types(type_name=None, like=True):
|
||||||
resources = None
|
resources = None
|
||||||
@@ -86,6 +92,7 @@ class CITypeManager(object):
|
|||||||
for type_dict in ci_types:
|
for type_dict in ci_types:
|
||||||
attr = AttributeCache.get(type_dict["unique_id"])
|
attr = AttributeCache.get(type_dict["unique_id"])
|
||||||
type_dict["unique_key"] = attr and attr.name
|
type_dict["unique_key"] = attr and attr.name
|
||||||
|
type_dict['parent_ids'] = CITypeInheritanceManager.get_parents(type_dict['id'])
|
||||||
if resources is None or type_dict['name'] in resources:
|
if resources is None or type_dict['name'] in resources:
|
||||||
res.append(type_dict)
|
res.append(type_dict)
|
||||||
|
|
||||||
@@ -132,8 +139,13 @@ class CITypeManager(object):
|
|||||||
|
|
||||||
kwargs["unique_id"] = unique_key.id
|
kwargs["unique_id"] = unique_key.id
|
||||||
kwargs['uid'] = current_user.uid
|
kwargs['uid'] = current_user.uid
|
||||||
|
|
||||||
|
parent_ids = kwargs.pop('parent_ids', None)
|
||||||
|
|
||||||
ci_type = CIType.create(**kwargs)
|
ci_type = CIType.create(**kwargs)
|
||||||
|
|
||||||
|
CITypeInheritanceManager.add(parent_ids, ci_type.id)
|
||||||
|
|
||||||
CITypeAttributeManager.add(ci_type.id, [unique_key.id], is_required=True)
|
CITypeAttributeManager.add(ci_type.id, [unique_key.id], is_required=True)
|
||||||
|
|
||||||
CITypeCache.clean(ci_type.name)
|
CITypeCache.clean(ci_type.name)
|
||||||
@@ -215,10 +227,12 @@ class CITypeManager(object):
|
|||||||
if item.get('parent_id') == type_id or item.get('child_id') == type_id:
|
if item.get('parent_id') == type_id or item.get('child_id') == type_id:
|
||||||
return abort(400, ErrFormat.ci_relation_view_exists_and_cannot_delete_type.format(rv.name))
|
return abort(400, ErrFormat.ci_relation_view_exists_and_cannot_delete_type.format(rv.name))
|
||||||
|
|
||||||
for item in CITypeRelation.get_by(parent_id=type_id, to_dict=False):
|
for item in (CITypeRelation.get_by(parent_id=type_id, to_dict=False) +
|
||||||
item.soft_delete(commit=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)
|
item.soft_delete(commit=False)
|
||||||
|
|
||||||
for table in [PreferenceTreeView, PreferenceShowAttributes, PreferenceSearchOption, CustomDashboard,
|
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):
|
for item in AutoDiscoveryCI.get_by(type_id=type_id, to_dict=False):
|
||||||
item.delete(commit=False)
|
item.delete(commit=False)
|
||||||
|
|
||||||
|
for item in CITypeInheritance.get_by(parent_id=type_id, to_dict=False):
|
||||||
|
item.delete(commit=False)
|
||||||
|
|
||||||
|
for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False):
|
||||||
|
item.delete(commit=False)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
ci_type.soft_delete()
|
ci_type.soft_delete()
|
||||||
@@ -242,6 +262,100 @@ class CITypeManager(object):
|
|||||||
ACLManager().del_resource(ci_type.name, ResourceTypeEnum.CI)
|
ACLManager().del_resource(ci_type.name, ResourceTypeEnum.CI)
|
||||||
|
|
||||||
|
|
||||||
|
class CITypeInheritanceManager(object):
|
||||||
|
cls = CITypeInheritance
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_parents(cls, type_id):
|
||||||
|
return [i.parent_id for i in cls.cls.get_by(child_id=type_id, to_dict=False)]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def recursive_children(cls, type_id):
|
||||||
|
result = []
|
||||||
|
|
||||||
|
def _get_child(_id):
|
||||||
|
children = [i.child_id for i in cls.cls.get_by(parent_id=_id, to_dict=False)]
|
||||||
|
result.extend(children)
|
||||||
|
for child_id in children:
|
||||||
|
_get_child(child_id)
|
||||||
|
|
||||||
|
_get_child(type_id)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def base(cls, type_id):
|
||||||
|
result = []
|
||||||
|
q = []
|
||||||
|
|
||||||
|
def _get_parents(_type_id):
|
||||||
|
parents = [i.parent_id for i in cls.cls.get_by(child_id=_type_id, to_dict=False)]
|
||||||
|
for i in parents[::-1]:
|
||||||
|
q.append(i)
|
||||||
|
try:
|
||||||
|
out = q.pop(0)
|
||||||
|
except IndexError:
|
||||||
|
return
|
||||||
|
|
||||||
|
result.append(out)
|
||||||
|
|
||||||
|
_get_parents(out)
|
||||||
|
|
||||||
|
_get_parents(type_id)
|
||||||
|
|
||||||
|
return result[::-1]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add(cls, parent_ids, child_id):
|
||||||
|
|
||||||
|
rels = {}
|
||||||
|
for i in cls.cls.get_by(to_dict=False):
|
||||||
|
rels.setdefault(i.child_id, set()).add(i.parent_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
toposort_flatten(rels)
|
||||||
|
except toposort.CircularDependencyError as e:
|
||||||
|
current_app.logger.warning(str(e))
|
||||||
|
return abort(400, ErrFormat.circular_dependency_error)
|
||||||
|
|
||||||
|
for parent_id in parent_ids or []:
|
||||||
|
if parent_id == child_id:
|
||||||
|
return abort(400, ErrFormat.circular_dependency_error)
|
||||||
|
|
||||||
|
existed = cls.cls.get_by(parent_id=parent_id, child_id=child_id, first=True, to_dict=False)
|
||||||
|
if existed is None:
|
||||||
|
rels.setdefault(child_id, set()).add(parent_id)
|
||||||
|
try:
|
||||||
|
toposort_flatten(rels)
|
||||||
|
except toposort.CircularDependencyError as e:
|
||||||
|
current_app.logger.warning(str(e))
|
||||||
|
return abort(400, ErrFormat.circular_dependency_error)
|
||||||
|
|
||||||
|
cls.cls.create(parent_id=parent_id, child_id=child_id, commit=False)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete(cls, parent_id, child_id):
|
||||||
|
|
||||||
|
existed = cls.cls.get_by(parent_id=parent_id, child_id=child_id, first=True, to_dict=False)
|
||||||
|
|
||||||
|
if existed is not None:
|
||||||
|
children = cls.recursive_children(child_id) + [child_id]
|
||||||
|
for _id in children:
|
||||||
|
if CI.get_by(type_id=_id, to_dict=False, first=True) is not None:
|
||||||
|
return abort(400, ErrFormat.ci_exists_and_cannot_delete_inheritance)
|
||||||
|
|
||||||
|
attr_ids = set([i.id for _, i in CITypeAttributeManager.get_all_attributes(parent_id)])
|
||||||
|
for _id in children:
|
||||||
|
for attr_id in attr_ids:
|
||||||
|
for i in PreferenceShowAttributes.get_by(type_id=_id, attr_id=attr_id, to_dict=False):
|
||||||
|
i.soft_delete(commit=False)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
existed.soft_delete()
|
||||||
|
|
||||||
|
|
||||||
class CITypeGroupManager(object):
|
class CITypeGroupManager(object):
|
||||||
cls = CITypeGroup
|
cls = CITypeGroup
|
||||||
|
|
||||||
@@ -262,6 +376,7 @@ class CITypeGroupManager(object):
|
|||||||
ci_type = CITypeCache.get(t['type_id']).to_dict()
|
ci_type = CITypeCache.get(t['type_id']).to_dict()
|
||||||
if resources is None or (ci_type and ci_type['name'] in resources):
|
if resources is None or (ci_type and ci_type['name'] in resources):
|
||||||
ci_type['permissions'] = resources[ci_type['name']] if resources is not None else None
|
ci_type['permissions'] = resources[ci_type['name']] if resources is not None else None
|
||||||
|
ci_type['inherited'] = True if CITypeInheritanceManager.get_parents(ci_type['id']) else False
|
||||||
group.setdefault("ci_types", []).append(ci_type)
|
group.setdefault("ci_types", []).append(ci_type)
|
||||||
group_types.add(t["type_id"])
|
group_types.add(t["type_id"])
|
||||||
|
|
||||||
@@ -271,6 +386,7 @@ class CITypeGroupManager(object):
|
|||||||
for ci_type in ci_types:
|
for ci_type in ci_types:
|
||||||
if ci_type["id"] not in group_types and (resources is None or ci_type['name'] in resources):
|
if ci_type["id"] not in group_types and (resources is None or ci_type['name'] in resources):
|
||||||
ci_type['permissions'] = resources.get(ci_type['name']) if resources is not None else None
|
ci_type['permissions'] = resources.get(ci_type['name']) if resources is not None else None
|
||||||
|
ci_type['inherited'] = True if CITypeInheritanceManager.get_parents(ci_type['id']) else False
|
||||||
other_types['ci_types'].append(ci_type)
|
other_types['ci_types'].append(ci_type)
|
||||||
|
|
||||||
groups.append(other_types)
|
groups.append(other_types)
|
||||||
@@ -362,40 +478,62 @@ class CITypeAttributeManager(object):
|
|||||||
return attr.name
|
return attr.name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_attr_names_by_type_id(type_id):
|
def get_all_attributes(type_id):
|
||||||
return [AttributeCache.get(attr.attr_id).name for attr in CITypeAttributesCache.get(type_id)]
|
parent_ids = CITypeInheritanceManager.base(type_id)
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for _type_id in parent_ids + [type_id]:
|
||||||
|
result.extend(CITypeAttributesCache.get2(_type_id))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_attr_names_by_type_id(cls, type_id):
|
||||||
|
return [attr.name for _, attr in cls.get_all_attributes(type_id)]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_attributes_by_type_id(type_id, choice_web_hook_parse=True, choice_other_parse=True):
|
def get_attributes_by_type_id(type_id, choice_web_hook_parse=True, choice_other_parse=True):
|
||||||
has_config_perm = ACLManager('cmdb').has_permission(
|
has_config_perm = ACLManager('cmdb').has_permission(
|
||||||
CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG)
|
CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG)
|
||||||
|
|
||||||
attrs = CITypeAttributesCache.get(type_id)
|
parent_ids = CITypeInheritanceManager.base(type_id)
|
||||||
|
|
||||||
result = list()
|
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)):
|
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 = AttributeManager().get_attribute(attr.attr_id, choice_web_hook_parse, choice_other_parse)
|
||||||
attr_dict["is_required"] = attr.is_required
|
attr_dict["is_required"] = attr.is_required
|
||||||
attr_dict["order"] = attr.order
|
attr_dict["order"] = attr.order
|
||||||
attr_dict["default_show"] = attr.default_show
|
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:
|
if not has_config_perm:
|
||||||
attr_dict.pop('choice_web_hook', None)
|
attr_dict.pop('choice_web_hook', None)
|
||||||
attr_dict.pop('choice_other', None)
|
attr_dict.pop('choice_other', None)
|
||||||
|
|
||||||
|
if attr_dict['id'] not in id2pos:
|
||||||
|
id2pos[attr_dict['id']] = len(result)
|
||||||
result.append(attr_dict)
|
result.append(attr_dict)
|
||||||
|
else:
|
||||||
|
result[id2pos[attr_dict['id']]] = attr_dict
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def get_common_attributes(type_ids):
|
def get_common_attributes(cls, type_ids):
|
||||||
has_config_perm = False
|
has_config_perm = False
|
||||||
for type_id in type_ids:
|
for type_id in type_ids:
|
||||||
has_config_perm |= ACLManager('cmdb').has_permission(
|
has_config_perm |= ACLManager('cmdb').has_permission(
|
||||||
CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG)
|
CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG)
|
||||||
|
|
||||||
result = CITypeAttribute.get_by(__func_in___key_type_id=list(map(int, type_ids)), to_dict=False)
|
result = {type_id: [i for _, i in cls.get_all_attributes(type_id)] for type_id in type_ids}
|
||||||
attr2types = {}
|
attr2types = {}
|
||||||
for i in result:
|
for type_id in result:
|
||||||
attr2types.setdefault(i.attr_id, []).append(i.type_id)
|
for i in result[type_id]:
|
||||||
|
attr2types.setdefault(i.id, []).append(type_id)
|
||||||
|
|
||||||
attrs = []
|
attrs = []
|
||||||
for attr_id in attr2types:
|
for attr_id in attr2types:
|
||||||
@@ -512,10 +650,30 @@ class CITypeAttributeManager(object):
|
|||||||
existed.soft_delete()
|
existed.soft_delete()
|
||||||
|
|
||||||
for ci in CI.get_by(type_id=type_id, to_dict=False):
|
for ci in CI.get_by(type_id=type_id, to_dict=False):
|
||||||
AttributeValueManager.delete_attr_value(attr_id, ci.id)
|
AttributeValueManager.delete_attr_value(attr_id, ci.id, commit=False)
|
||||||
|
|
||||||
ci_cache.apply_async(args=(ci.id, None, None), queue=CMDB_QUEUE)
|
ci_cache.apply_async(args=(ci.id, None, None), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
|
for item in PreferenceShowAttributes.get_by(type_id=type_id, attr_id=attr_id, to_dict=False):
|
||||||
|
item.soft_delete(commit=False)
|
||||||
|
|
||||||
|
child_ids = CITypeInheritanceManager.recursive_children(type_id)
|
||||||
|
for _type_id in [type_id] + child_ids:
|
||||||
|
for item in CITypeUniqueConstraint.get_by(type_id=_type_id, to_dict=False):
|
||||||
|
if attr_id in item.attr_ids:
|
||||||
|
attr_ids = copy.deepcopy(item.attr_ids)
|
||||||
|
attr_ids.remove(attr_id)
|
||||||
|
|
||||||
|
if attr_ids:
|
||||||
|
item.update(attr_ids=attr_ids, commit=False)
|
||||||
|
else:
|
||||||
|
item.soft_delete(commit=False)
|
||||||
|
|
||||||
|
item = CITypeTrigger.get_by(type_id=_type_id, attr_id=attr_id, to_dict=False, first=True)
|
||||||
|
item and item.soft_delete(commit=False)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
CITypeAttributeCache.clean(type_id, attr_id)
|
CITypeAttributeCache.clean(type_id, attr_id)
|
||||||
|
|
||||||
CITypeHistoryManager.add(CITypeOperateType.DELETE_ATTRIBUTE, type_id, attr_id=attr.id,
|
CITypeHistoryManager.add(CITypeOperateType.DELETE_ATTRIBUTE, type_id, attr_id=attr.id,
|
||||||
@@ -529,8 +687,18 @@ class CITypeAttributeManager(object):
|
|||||||
attr_id = _from.get('attr_id')
|
attr_id = _from.get('attr_id')
|
||||||
from_group_id = _from.get('group_id')
|
from_group_id = _from.get('group_id')
|
||||||
to_group_id = _to.get('group_id')
|
to_group_id = _to.get('group_id')
|
||||||
|
from_group_name = _from.get('group_name')
|
||||||
|
to_group_name = _to.get('group_name')
|
||||||
order = _to.get('order')
|
order = _to.get('order')
|
||||||
|
|
||||||
|
if from_group_name:
|
||||||
|
from_group = CITypeAttributeGroup.get_by(type_id=type_id, name=from_group_name, first=True, to_dict=False)
|
||||||
|
from_group_id = from_group and from_group.id
|
||||||
|
|
||||||
|
if to_group_name:
|
||||||
|
to_group = CITypeAttributeGroup.get_by(type_id=type_id, name=to_group_name, first=True, to_dict=False)
|
||||||
|
to_group_id = to_group and to_group.id
|
||||||
|
|
||||||
if from_group_id != to_group_id:
|
if from_group_id != to_group_id:
|
||||||
if from_group_id is not None:
|
if from_group_id is not None:
|
||||||
CITypeAttributeGroupManager.delete_item(from_group_id, attr_id)
|
CITypeAttributeGroupManager.delete_item(from_group_id, attr_id)
|
||||||
@@ -656,15 +824,15 @@ class CITypeRelationManager(object):
|
|||||||
current_app.logger.warning(str(e))
|
current_app.logger.warning(str(e))
|
||||||
return abort(400, ErrFormat.circular_dependency_error)
|
return abort(400, ErrFormat.circular_dependency_error)
|
||||||
|
|
||||||
if constraint == ConstraintEnum.Many2Many:
|
# if constraint == ConstraintEnum.Many2Many:
|
||||||
other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
|
# other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
|
||||||
to_dict=False, first=True)
|
# to_dict=False, first=True)
|
||||||
other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
|
# other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
|
||||||
to_dict=False, first=True)
|
# to_dict=False, first=True)
|
||||||
if other_c and other_c.child_id != c.id:
|
# if other_c and other_c.child_id != c.id:
|
||||||
return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
|
# return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
|
||||||
if other_p and other_p.parent_id != p.id:
|
# if other_p and other_p.parent_id != p.id:
|
||||||
return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
|
# return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
|
||||||
|
|
||||||
existed = cls._get(p.id, c.id)
|
existed = cls._get(p.id, c.id)
|
||||||
if existed is not None:
|
if existed is not None:
|
||||||
@@ -739,25 +907,66 @@ class CITypeAttributeGroupManager(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_by_type_id(type_id, need_other=False):
|
def get_by_type_id(type_id, need_other=False):
|
||||||
groups = CITypeAttributeGroup.get_by(type_id=type_id)
|
parent_ids = CITypeInheritanceManager.base(type_id)
|
||||||
groups = sorted(groups, key=lambda x: x["order"] or 0)
|
|
||||||
grouped = list()
|
groups = []
|
||||||
|
id2type = {i: CITypeCache.get(i).alias for i in parent_ids}
|
||||||
|
for _type_id in parent_ids + [type_id]:
|
||||||
|
_groups = CITypeAttributeGroup.get_by(type_id=_type_id)
|
||||||
|
_groups = sorted(_groups, key=lambda x: x["order"] or 0)
|
||||||
|
for i in _groups:
|
||||||
|
if type_id != _type_id:
|
||||||
|
i['inherited'] = True
|
||||||
|
i['inherited_from'] = id2type[_type_id]
|
||||||
|
else:
|
||||||
|
i['inherited'] = False
|
||||||
|
|
||||||
|
groups.extend(_groups)
|
||||||
|
|
||||||
|
grouped = set()
|
||||||
|
|
||||||
attributes = CITypeAttributeManager.get_attributes_by_type_id(type_id)
|
attributes = CITypeAttributeManager.get_attributes_by_type_id(type_id)
|
||||||
id2attr = {i.get('id'): i for i in attributes}
|
id2attr = {i.get('id'): i for i in attributes}
|
||||||
|
|
||||||
|
group2pos = dict()
|
||||||
|
attr2pos = dict()
|
||||||
|
result = []
|
||||||
for group in groups:
|
for group in groups:
|
||||||
items = CITypeAttributeGroupItem.get_by(group_id=group["id"], to_dict=False)
|
items = CITypeAttributeGroupItem.get_by(group_id=group["id"], to_dict=False)
|
||||||
items = sorted(items, key=lambda x: x.order or 0)
|
items = sorted(items, key=lambda x: x.order or 0)
|
||||||
group["attributes"] = [id2attr.get(i.attr_id) for i in items if i.attr_id in id2attr]
|
|
||||||
grouped.extend([i.attr_id for i in items])
|
if group['name'] not in group2pos:
|
||||||
|
group_pos = len(result)
|
||||||
|
group['attributes'] = []
|
||||||
|
result.append(group)
|
||||||
|
|
||||||
|
group2pos[group['name']] = group_pos
|
||||||
|
else:
|
||||||
|
group_pos = group2pos[group['name']]
|
||||||
|
|
||||||
|
attr = None
|
||||||
|
for i in items:
|
||||||
|
if i.attr_id in id2attr:
|
||||||
|
attr = id2attr[i.attr_id]
|
||||||
|
attr['inherited'] = group['inherited']
|
||||||
|
attr['inherited_from'] = group.get('inherited_from')
|
||||||
|
result[group_pos]['attributes'].append(attr)
|
||||||
|
|
||||||
|
if i.attr_id in attr2pos:
|
||||||
|
result[attr2pos[i.attr_id][0]]['attributes'].remove(attr2pos[i.attr_id][1])
|
||||||
|
|
||||||
|
attr2pos[i.attr_id] = [group_pos, attr]
|
||||||
|
|
||||||
|
group.pop('inherited_from', None)
|
||||||
|
|
||||||
|
grouped |= set([i.attr_id for i in items])
|
||||||
|
|
||||||
if need_other:
|
if need_other:
|
||||||
grouped = set(grouped)
|
grouped = set(grouped)
|
||||||
other_attributes = [attr for attr in attributes if attr["id"] not in grouped]
|
other_attributes = [attr for attr in attributes if attr["id"] not in grouped]
|
||||||
groups.append(dict(attributes=other_attributes))
|
result.append(dict(attributes=other_attributes))
|
||||||
|
|
||||||
return groups
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_or_update(type_id, name, attr_order, group_order=0, is_update=False):
|
def create_or_update(type_id, name, attr_order, group_order=0, is_update=False):
|
||||||
@@ -891,10 +1100,16 @@ class CITypeAttributeGroupManager(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def transfer(cls, type_id, _from, _to):
|
def transfer(cls, type_id, _from, _to):
|
||||||
current_app.logger.info("CIType[{0}] {1} -> {2}".format(type_id, _from, _to))
|
current_app.logger.info("CIType[{0}] {1} -> {2}".format(type_id, _from, _to))
|
||||||
|
if isinstance(_from, int):
|
||||||
from_group = CITypeAttributeGroup.get_by_id(_from)
|
from_group = CITypeAttributeGroup.get_by_id(_from)
|
||||||
|
else:
|
||||||
|
from_group = CITypeAttributeGroup.get_by(name=_from, first=True, to_dict=False)
|
||||||
from_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_from)))
|
from_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_from)))
|
||||||
|
|
||||||
|
if isinstance(_to, int):
|
||||||
to_group = CITypeAttributeGroup.get_by_id(_to)
|
to_group = CITypeAttributeGroup.get_by_id(_to)
|
||||||
|
else:
|
||||||
|
to_group = CITypeAttributeGroup.get_by(name=_to, first=True, to_dict=False)
|
||||||
to_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_to)))
|
to_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_to)))
|
||||||
|
|
||||||
from_order, to_order = from_group.order, to_group.order
|
from_order, to_order = from_group.order, to_group.order
|
||||||
|
@@ -1,12 +1,15 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
import copy
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
|
import redis_lock
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
|
from api.extensions import db
|
||||||
|
from api.extensions import rd
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.mixin import DBMixin
|
from api.lib.mixin import DBMixin
|
||||||
@@ -40,6 +43,11 @@ class CIFilterPermsCRUD(DBMixin):
|
|||||||
result[i['rid']]['ci_filter'] = ""
|
result[i['rid']]['ci_filter'] = ""
|
||||||
result[i['rid']]['ci_filter'] += (i['ci_filter'] or "")
|
result[i['rid']]['ci_filter'] += (i['ci_filter'] or "")
|
||||||
|
|
||||||
|
if i['id_filter']:
|
||||||
|
if not result[i['rid']]['id_filter']:
|
||||||
|
result[i['rid']]['id_filter'] = {}
|
||||||
|
result[i['rid']]['id_filter'].update(i['id_filter'] or {})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_by_ids(self, _ids, type_id=None):
|
def get_by_ids(self, _ids, type_id=None):
|
||||||
@@ -70,6 +78,11 @@ class CIFilterPermsCRUD(DBMixin):
|
|||||||
result[i['type_id']]['ci_filter'] = ""
|
result[i['type_id']]['ci_filter'] = ""
|
||||||
result[i['type_id']]['ci_filter'] += (i['ci_filter'] or "")
|
result[i['type_id']]['ci_filter'] += (i['ci_filter'] or "")
|
||||||
|
|
||||||
|
if i['id_filter']:
|
||||||
|
if not result[i['type_id']]['id_filter']:
|
||||||
|
result[i['type_id']]['id_filter'] = {}
|
||||||
|
result[i['type_id']]['id_filter'].update(i['id_filter'] or {})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -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)
|
type2filter_perms = cls().get_by_ids(list(map(int, [i['name'] for i in res2])), type_id=type_id)
|
||||||
return type2filter_perms.get(type_id, {}).get('attr_filter') or []
|
return type2filter_perms.get(type_id, {}).get('attr_filter') or []
|
||||||
|
|
||||||
|
def _revoke_children(self, rid, id_filter, rebuild=True):
|
||||||
|
items = self.cls.get_by(rid=rid, ci_filter=None, attr_filter=None, to_dict=False)
|
||||||
|
for item in items:
|
||||||
|
changed, item_id_filter = False, copy.deepcopy(item.id_filter)
|
||||||
|
for prefix in id_filter:
|
||||||
|
for k, v in copy.deepcopy((item.id_filter or {})).items():
|
||||||
|
if k.startswith(prefix) and k != prefix:
|
||||||
|
item_id_filter.pop(k)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if not item_id_filter and current_app.config.get('USE_ACL'):
|
||||||
|
item.soft_delete(commit=False)
|
||||||
|
ACLManager().del_resource(str(item.id), ResourceTypeEnum.CI_FILTER, rebuild=rebuild)
|
||||||
|
elif changed:
|
||||||
|
item.update(id_filter=item_id_filter, commit=False)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
def _revoke_parent(self, rid, parent_path, rebuild=True):
|
||||||
|
parent_path = [i for i in parent_path.split(',') if i] or []
|
||||||
|
revoke_nodes = [','.join(parent_path[:i]) for i in range(len(parent_path), 0, -1)]
|
||||||
|
for node_path in revoke_nodes:
|
||||||
|
delete_item, can_deleted = None, True
|
||||||
|
items = self.cls.get_by(rid=rid, ci_filter=None, attr_filter=None, to_dict=False)
|
||||||
|
for item in items:
|
||||||
|
if node_path in item.id_filter:
|
||||||
|
delete_item = item
|
||||||
|
if any(filter(lambda x: x.startswith(node_path) and x != node_path, item.id_filter.keys())):
|
||||||
|
can_deleted = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if can_deleted and delete_item:
|
||||||
|
id_filter = copy.deepcopy(delete_item.id_filter)
|
||||||
|
id_filter.pop(node_path)
|
||||||
|
delete_item = delete_item.update(id_filter=id_filter, filter_none=False)
|
||||||
|
|
||||||
|
if current_app.config.get('USE_ACL') and not id_filter:
|
||||||
|
ACLManager().del_resource(str(delete_item.id), ResourceTypeEnum.CI_FILTER, rebuild=False)
|
||||||
|
delete_item.soft_delete()
|
||||||
|
items.remove(delete_item)
|
||||||
|
|
||||||
|
if rebuild:
|
||||||
|
from api.tasks.acl import role_rebuild
|
||||||
|
from api.lib.perm.acl.const import ACL_QUEUE
|
||||||
|
from api.lib.perm.acl.cache import AppCache
|
||||||
|
|
||||||
|
role_rebuild.apply_async(args=(rid, AppCache.get('cmdb').id), queue=ACL_QUEUE)
|
||||||
|
|
||||||
def _can_add(self, **kwargs):
|
def _can_add(self, **kwargs):
|
||||||
ci_filter = kwargs.get('ci_filter')
|
ci_filter = kwargs.get('ci_filter')
|
||||||
attr_filter = kwargs.get('attr_filter') or ""
|
attr_filter = kwargs.get('attr_filter') or ""
|
||||||
@@ -102,27 +163,58 @@ class CIFilterPermsCRUD(DBMixin):
|
|||||||
|
|
||||||
def add(self, **kwargs):
|
def add(self, **kwargs):
|
||||||
kwargs = self._can_add(**kwargs) or kwargs
|
kwargs = self._can_add(**kwargs) or kwargs
|
||||||
|
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid'])):
|
||||||
|
request_id_filter = {}
|
||||||
|
if kwargs.get('id_filter'):
|
||||||
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
|
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
|
||||||
rid=kwargs.get('rid'),
|
rid=kwargs.get('rid'),
|
||||||
|
ci_filter=None,
|
||||||
|
attr_filter=None,
|
||||||
first=True, to_dict=False)
|
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 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)
|
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'):
|
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()
|
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:
|
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
|
return
|
||||||
|
|
||||||
|
if request_id_filter:
|
||||||
|
kwargs['id_filter'] = request_id_filter
|
||||||
|
|
||||||
obj = self.cls.create(**kwargs)
|
obj = self.cls.create(**kwargs)
|
||||||
|
|
||||||
if current_app.config.get('USE_ACL'):
|
if current_app.config.get('USE_ACL'): # new resource
|
||||||
try:
|
try:
|
||||||
ACLManager().add_resource(obj.id, ResourceTypeEnum.CI_FILTER)
|
ACLManager().add_resource(obj.id, ResourceTypeEnum.CI_FILTER)
|
||||||
except:
|
except:
|
||||||
@@ -140,8 +232,10 @@ class CIFilterPermsCRUD(DBMixin):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def delete(self, **kwargs):
|
def delete(self, **kwargs):
|
||||||
|
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid'])):
|
||||||
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
|
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
|
||||||
rid=kwargs.get('rid'),
|
rid=kwargs.get('rid'),
|
||||||
|
id_filter=None,
|
||||||
first=True, to_dict=False)
|
first=True, to_dict=False)
|
||||||
|
|
||||||
if obj is not None:
|
if obj is not None:
|
||||||
@@ -153,6 +247,69 @@ class CIFilterPermsCRUD(DBMixin):
|
|||||||
|
|
||||||
return resource
|
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 has_perm_for_ci(arg_name, resource_type, perm, callback=None, app=None):
|
||||||
def decorator_has_perm(func):
|
def decorator_has_perm(func):
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
import six
|
import six
|
||||||
import toposort
|
import toposort
|
||||||
from flask import abort
|
from flask import abort
|
||||||
@@ -14,6 +15,7 @@ from api.lib.cmdb.cache import AttributeCache
|
|||||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||||
from api.lib.cmdb.cache import CITypeCache
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
from api.lib.cmdb.cache import CMDBCounterCache
|
from api.lib.cmdb.cache import CMDBCounterCache
|
||||||
|
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||||
from api.lib.cmdb.const import ConstraintEnum
|
from api.lib.cmdb.const import ConstraintEnum
|
||||||
from api.lib.cmdb.const import PermEnum
|
from api.lib.cmdb.const import PermEnum
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
@@ -112,8 +114,8 @@ class PreferenceManager(object):
|
|||||||
CITypeAttribute, CITypeAttribute.attr_id == PreferenceShowAttributes.attr_id).filter(
|
CITypeAttribute, CITypeAttribute.attr_id == PreferenceShowAttributes.attr_id).filter(
|
||||||
PreferenceShowAttributes.uid == current_user.uid).filter(
|
PreferenceShowAttributes.uid == current_user.uid).filter(
|
||||||
PreferenceShowAttributes.type_id == type_id).filter(
|
PreferenceShowAttributes.type_id == type_id).filter(
|
||||||
PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).filter(
|
PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).group_by(
|
||||||
CITypeAttribute.type_id == type_id).all()
|
CITypeAttribute.attr_id).all()
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for i in sorted(attrs, key=lambda x: x.PreferenceShowAttributes.order):
|
for i in sorted(attrs, key=lambda x: x.PreferenceShowAttributes.order):
|
||||||
@@ -123,17 +125,16 @@ class PreferenceManager(object):
|
|||||||
|
|
||||||
is_subscribed = True
|
is_subscribed = True
|
||||||
if not attrs:
|
if not attrs:
|
||||||
attrs = db.session.query(CITypeAttribute).filter(
|
result = CITypeAttributeManager.get_attributes_by_type_id(type_id,
|
||||||
CITypeAttribute.type_id == type_id).filter(
|
choice_web_hook_parse=False,
|
||||||
CITypeAttribute.deleted.is_(False)).filter(
|
choice_other_parse=False)
|
||||||
CITypeAttribute.default_show.is_(True)).order_by(CITypeAttribute.order)
|
result = [i for i in result if i['default_show']]
|
||||||
result = [i.attr.to_dict() for i in attrs]
|
|
||||||
is_subscribed = False
|
is_subscribed = False
|
||||||
|
|
||||||
for i in result:
|
for i in result:
|
||||||
if i["is_choice"]:
|
if i["is_choice"]:
|
||||||
i.update(dict(choice_value=AttributeManager.get_choice_values(
|
i.update(dict(choice_value=AttributeManager.get_choice_values(
|
||||||
i["id"], i["value_type"], i["choice_web_hook"], i.get("choice_other"))))
|
i["id"], i["value_type"], i.get("choice_web_hook"), i.get("choice_other"))))
|
||||||
|
|
||||||
return is_subscribed, result
|
return is_subscribed, result
|
||||||
|
|
||||||
|
@@ -60,6 +60,8 @@ class ErrFormat(CommonErrFormat):
|
|||||||
only_owner_can_delete = _l("Only the creator can delete it!") # 只有创建人才能删除它!
|
only_owner_can_delete = _l("Only the creator can delete it!") # 只有创建人才能删除它!
|
||||||
ci_exists_and_cannot_delete_type = _l(
|
ci_exists_and_cannot_delete_type = _l(
|
||||||
"The model cannot be deleted because the CI already exists") # 因为CI已经存在,不能删除模型
|
"The model cannot be deleted because the CI already exists") # 因为CI已经存在,不能删除模型
|
||||||
|
ci_exists_and_cannot_delete_inheritance = _l(
|
||||||
|
"The inheritance cannot be deleted because the CI already exists") # 因为CI已经存在,不能删除继承关系
|
||||||
|
|
||||||
# 因为关系视图 {} 引用了该模型,不能删除模型
|
# 因为关系视图 {} 引用了该模型,不能删除模型
|
||||||
ci_relation_view_exists_and_cannot_delete_type = _l(
|
ci_relation_view_exists_and_cannot_delete_type = _l(
|
||||||
|
@@ -16,10 +16,11 @@ def search(query=None,
|
|||||||
ret_key=RetKey.NAME,
|
ret_key=RetKey.NAME,
|
||||||
count=1,
|
count=1,
|
||||||
sort=None,
|
sort=None,
|
||||||
excludes=None):
|
excludes=None,
|
||||||
|
use_id_filter=True):
|
||||||
if current_app.config.get("USE_ES"):
|
if current_app.config.get("USE_ES"):
|
||||||
s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
|
s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
|
||||||
else:
|
else:
|
||||||
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes)
|
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes, use_id_filter=use_id_filter)
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
@@ -62,7 +62,7 @@ QUERY_CI_BY_ATTR_NAME = """
|
|||||||
QUERY_CI_BY_ID = """
|
QUERY_CI_BY_ID = """
|
||||||
SELECT c_cis.id as ci_id
|
SELECT c_cis.id as ci_id
|
||||||
FROM c_cis
|
FROM c_cis
|
||||||
WHERE c_cis.id={}
|
WHERE c_cis.id {}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
QUERY_CI_BY_TYPE = """
|
QUERY_CI_BY_TYPE = """
|
||||||
|
@@ -44,7 +44,10 @@ class Search(object):
|
|||||||
count=1,
|
count=1,
|
||||||
sort=None,
|
sort=None,
|
||||||
ci_ids=None,
|
ci_ids=None,
|
||||||
excludes=None):
|
excludes=None,
|
||||||
|
parent_node_perm_passed=False,
|
||||||
|
use_id_filter=False,
|
||||||
|
use_ci_filter=True):
|
||||||
self.orig_query = query
|
self.orig_query = query
|
||||||
self.fl = fl or []
|
self.fl = fl or []
|
||||||
self.excludes = excludes or []
|
self.excludes = excludes or []
|
||||||
@@ -54,12 +57,17 @@ class Search(object):
|
|||||||
self.count = count
|
self.count = count
|
||||||
self.sort = sort
|
self.sort = sort
|
||||||
self.ci_ids = ci_ids or []
|
self.ci_ids = ci_ids or []
|
||||||
|
self.raw_ci_ids = copy.deepcopy(self.ci_ids)
|
||||||
self.query_sql = ""
|
self.query_sql = ""
|
||||||
self.type_id_list = []
|
self.type_id_list = []
|
||||||
self.only_type_query = False
|
self.only_type_query = False
|
||||||
|
self.parent_node_perm_passed = parent_node_perm_passed
|
||||||
|
self.use_id_filter = use_id_filter
|
||||||
|
self.use_ci_filter = use_ci_filter
|
||||||
|
|
||||||
self.valid_type_names = []
|
self.valid_type_names = []
|
||||||
self.type2filter_perms = dict()
|
self.type2filter_perms = dict()
|
||||||
|
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _operator_proc(key):
|
def _operator_proc(key):
|
||||||
@@ -106,7 +114,7 @@ class Search(object):
|
|||||||
self.type_id_list.append(str(ci_type.id))
|
self.type_id_list.append(str(ci_type.id))
|
||||||
if ci_type.id in self.type2filter_perms:
|
if ci_type.id in self.type2filter_perms:
|
||||||
ci_filter = self.type2filter_perms[ci_type.id].get('ci_filter')
|
ci_filter = self.type2filter_perms[ci_type.id].get('ci_filter')
|
||||||
if ci_filter:
|
if ci_filter and self.use_ci_filter and not self.use_id_filter:
|
||||||
sub = []
|
sub = []
|
||||||
ci_filter = Template(ci_filter).render(user=current_user)
|
ci_filter = Template(ci_filter).render(user=current_user)
|
||||||
for i in ci_filter.split(','):
|
for i in ci_filter.split(','):
|
||||||
@@ -122,6 +130,14 @@ class Search(object):
|
|||||||
self.fl = set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
self.fl = set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
||||||
else:
|
else:
|
||||||
self.fl = set(self.fl) & set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
self.fl = set(self.fl) & set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
||||||
|
|
||||||
|
if self.type2filter_perms[ci_type.id].get('id_filter') and self.use_id_filter:
|
||||||
|
|
||||||
|
if not self.raw_ci_ids:
|
||||||
|
self.ci_ids = list(self.type2filter_perms[ci_type.id]['id_filter'].keys())
|
||||||
|
|
||||||
|
if self.use_id_filter and not self.ci_ids and not self.is_app_admin:
|
||||||
|
self.raw_ci_ids = [0]
|
||||||
else:
|
else:
|
||||||
raise SearchError(ErrFormat.no_permission.format(ci_type.alias, PermEnum.READ))
|
raise SearchError(ErrFormat.no_permission.format(ci_type.alias, PermEnum.READ))
|
||||||
else:
|
else:
|
||||||
@@ -138,7 +154,10 @@ class Search(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _id_query_handler(v):
|
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
|
@staticmethod
|
||||||
def _in_query_handler(attr, v, is_not):
|
def _in_query_handler(attr, v, is_not):
|
||||||
@@ -152,6 +171,7 @@ class Search(object):
|
|||||||
"NOT LIKE" if is_not else "LIKE",
|
"NOT LIKE" if is_not else "LIKE",
|
||||||
_v.replace("*", "%")) for _v in new_v])
|
_v.replace("*", "%")) for _v in new_v])
|
||||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, in_query)
|
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, in_query)
|
||||||
|
|
||||||
return _query_sql
|
return _query_sql
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -167,6 +187,7 @@ class Search(object):
|
|||||||
"NOT BETWEEN" if is_not else "BETWEEN",
|
"NOT BETWEEN" if is_not else "BETWEEN",
|
||||||
start.replace("*", "%"), end.replace("*", "%"))
|
start.replace("*", "%"), end.replace("*", "%"))
|
||||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, range_query)
|
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, range_query)
|
||||||
|
|
||||||
return _query_sql
|
return _query_sql
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -183,6 +204,7 @@ class Search(object):
|
|||||||
|
|
||||||
comparison_query = "{0} '{1}'".format(v[0], v[1:].replace("*", "%"))
|
comparison_query = "{0} '{1}'".format(v[0], v[1:].replace("*", "%"))
|
||||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, comparison_query)
|
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, comparison_query)
|
||||||
|
|
||||||
return _query_sql
|
return _query_sql
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -194,6 +216,7 @@ class Search(object):
|
|||||||
elif field.startswith("-"):
|
elif field.startswith("-"):
|
||||||
field = field[1:]
|
field = field[1:]
|
||||||
sort_type = "DESC"
|
sort_type = "DESC"
|
||||||
|
|
||||||
return field, sort_type
|
return field, sort_type
|
||||||
|
|
||||||
def __sort_by_id(self, sort_type, query_sql):
|
def __sort_by_id(self, sort_type, query_sql):
|
||||||
@@ -322,6 +345,11 @@ class Search(object):
|
|||||||
|
|
||||||
return numfound, res
|
return numfound, res
|
||||||
|
|
||||||
|
def __get_type2filter_perms(self):
|
||||||
|
res2 = ACLManager('cmdb').get_resources(ResourceTypeEnum.CI_FILTER)
|
||||||
|
if res2:
|
||||||
|
self.type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
|
||||||
|
|
||||||
def __get_types_has_read(self):
|
def __get_types_has_read(self):
|
||||||
"""
|
"""
|
||||||
:return: _type:(type1;type2)
|
:return: _type:(type1;type2)
|
||||||
@@ -331,14 +359,23 @@ class Search(object):
|
|||||||
|
|
||||||
self.valid_type_names = {i['name'] for i in res if PermEnum.READ in i['permissions']}
|
self.valid_type_names = {i['name'] for i in res if PermEnum.READ in i['permissions']}
|
||||||
|
|
||||||
res2 = acl.get_resources(ResourceTypeEnum.CI_FILTER)
|
self.__get_type2filter_perms()
|
||||||
if res2:
|
|
||||||
self.type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
|
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))
|
return "_type:({})".format(";".join(self.valid_type_names))
|
||||||
|
|
||||||
def __confirm_type_first(self, queries):
|
def __confirm_type_first(self, queries):
|
||||||
|
|
||||||
has_type = False
|
has_type = False
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
@@ -371,8 +408,10 @@ class Search(object):
|
|||||||
else:
|
else:
|
||||||
result.append(q)
|
result.append(q)
|
||||||
|
|
||||||
_is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
if self.parent_node_perm_passed:
|
||||||
if result and not has_type and not _is_app_admin:
|
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()
|
type_q = self.__get_types_has_read()
|
||||||
if id_query:
|
if id_query:
|
||||||
ci = CIManager.get_by_id(id_query)
|
ci = CIManager.get_by_id(id_query)
|
||||||
@@ -381,13 +420,11 @@ class Search(object):
|
|||||||
result.insert(0, "_type:{}".format(ci.type_id))
|
result.insert(0, "_type:{}".format(ci.type_id))
|
||||||
else:
|
else:
|
||||||
result.insert(0, type_q)
|
result.insert(0, type_q)
|
||||||
elif _is_app_admin:
|
elif self.is_app_admin:
|
||||||
self.valid_type_names = "ALL"
|
self.valid_type_names = "ALL"
|
||||||
else:
|
else:
|
||||||
self.__get_types_has_read()
|
self.__get_types_has_read()
|
||||||
|
|
||||||
current_app.logger.warning(result)
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def __query_by_attr(self, q, queries, alias):
|
def __query_by_attr(self, q, queries, alias):
|
||||||
@@ -479,7 +516,7 @@ class Search(object):
|
|||||||
def _filter_ids(self, query_sql):
|
def _filter_ids(self, query_sql):
|
||||||
if self.ci_ids:
|
if self.ci_ids:
|
||||||
return "SELECT * FROM ({0}) AS IN_QUERY WHERE IN_QUERY.ci_id IN ({1})".format(
|
return "SELECT * FROM ({0}) AS IN_QUERY WHERE IN_QUERY.ci_id IN ({1})".format(
|
||||||
query_sql, ",".join(list(map(str, self.ci_ids))))
|
query_sql, ",".join(list(set(map(str, self.ci_ids)))))
|
||||||
|
|
||||||
return query_sql
|
return query_sql
|
||||||
|
|
||||||
@@ -511,6 +548,9 @@ class Search(object):
|
|||||||
s = time.time()
|
s = time.time()
|
||||||
if query_sql:
|
if query_sql:
|
||||||
query_sql = self._filter_ids(query_sql)
|
query_sql = self._filter_ids(query_sql)
|
||||||
|
if self.raw_ci_ids and not self.ci_ids:
|
||||||
|
return 0, []
|
||||||
|
|
||||||
self.query_sql = query_sql
|
self.query_sql = query_sql
|
||||||
# current_app.logger.debug(query_sql)
|
# current_app.logger.debug(query_sql)
|
||||||
numfound, res = self._execute_sql(query_sql)
|
numfound, res = self._execute_sql(query_sql)
|
||||||
@@ -569,3 +609,8 @@ class Search(object):
|
|||||||
total = len(response)
|
total = len(response)
|
||||||
|
|
||||||
return response, counter, total, self.page, numfound, facet
|
return response, counter, total, self.page, numfound, facet
|
||||||
|
|
||||||
|
def get_ci_ids(self):
|
||||||
|
_, ci_ids = self._query_build_raw()
|
||||||
|
|
||||||
|
return ci_ids
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
import json
|
import json
|
||||||
|
import sys
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
from flask_login import current_user
|
||||||
|
|
||||||
from api.extensions import rd
|
from api.extensions import rd
|
||||||
from api.lib.cmdb.ci import CIRelationManager
|
from api.lib.cmdb.ci import CIRelationManager
|
||||||
@@ -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 ConstraintEnum
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||||
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
|
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||||
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
|
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
|
||||||
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
|
from api.lib.perm.acl.acl import is_app_admin
|
||||||
from api.models.cmdb import CI
|
from api.models.cmdb import CI
|
||||||
from api.models.cmdb import CIRelation
|
|
||||||
|
|
||||||
|
|
||||||
class Search(object):
|
class Search(object):
|
||||||
@@ -29,7 +34,9 @@ class Search(object):
|
|||||||
sort=None,
|
sort=None,
|
||||||
reverse=False,
|
reverse=False,
|
||||||
ancestor_ids=None,
|
ancestor_ids=None,
|
||||||
has_m2m=None):
|
descendant_ids=None,
|
||||||
|
has_m2m=None,
|
||||||
|
root_parent_path=None):
|
||||||
self.orig_query = query
|
self.orig_query = query
|
||||||
self.fl = fl
|
self.fl = fl
|
||||||
self.facet_field = facet_field
|
self.facet_field = facet_field
|
||||||
@@ -46,6 +53,8 @@ class Search(object):
|
|||||||
level[0] if isinstance(level, list) and level else level)
|
level[0] if isinstance(level, list) and level else level)
|
||||||
|
|
||||||
self.ancestor_ids = ancestor_ids
|
self.ancestor_ids = ancestor_ids
|
||||||
|
self.descendant_ids = descendant_ids
|
||||||
|
self.root_parent_path = root_parent_path
|
||||||
self.has_m2m = has_m2m or False
|
self.has_m2m = has_m2m or False
|
||||||
if not self.has_m2m:
|
if not self.has_m2m:
|
||||||
if self.ancestor_ids:
|
if self.ancestor_ids:
|
||||||
@@ -56,27 +65,23 @@ class Search(object):
|
|||||||
if _l < int(level) and c == ConstraintEnum.Many2Many:
|
if _l < int(level) and c == ConstraintEnum.Many2Many:
|
||||||
self.has_m2m = True
|
self.has_m2m = True
|
||||||
|
|
||||||
|
self.type2filter_perms = None
|
||||||
|
|
||||||
|
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||||
|
|
||||||
def _get_ids(self, ids):
|
def _get_ids(self, ids):
|
||||||
if self.level[-1] == 1 and len(ids) == 1:
|
|
||||||
if self.ancestor_ids is None:
|
|
||||||
return [i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0], to_dict=False)]
|
|
||||||
|
|
||||||
else:
|
|
||||||
seconds = {i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0],
|
|
||||||
ancestor_ids=self.ancestor_ids,
|
|
||||||
to_dict=False)}
|
|
||||||
|
|
||||||
return list(seconds)
|
|
||||||
|
|
||||||
merge_ids = []
|
merge_ids = []
|
||||||
key = []
|
key = []
|
||||||
_tmp = []
|
_tmp = []
|
||||||
for level in range(1, sorted(self.level)[-1] + 1):
|
for level in range(1, sorted(self.level)[-1] + 1):
|
||||||
|
if len(self.descendant_ids) >= level and self.type2filter_perms.get(self.descendant_ids[level - 1]):
|
||||||
|
id_filter_limit, _ = self._get_ci_filter(self.type2filter_perms[self.descendant_ids[level - 1]])
|
||||||
|
else:
|
||||||
|
id_filter_limit = {}
|
||||||
|
|
||||||
if not self.has_m2m:
|
if not self.has_m2m:
|
||||||
_tmp = map(lambda x: json.loads(x).keys(),
|
key, prefix = list(map(str, ids)), REDIS_PREFIX_CI_RELATION
|
||||||
filter(lambda x: x is not None, rd.get(ids, REDIS_PREFIX_CI_RELATION) or []))
|
|
||||||
ids = [j for i in _tmp for j in i]
|
|
||||||
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if not self.ancestor_ids:
|
if not self.ancestor_ids:
|
||||||
@@ -92,12 +97,16 @@ class Search(object):
|
|||||||
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
|
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
|
||||||
prefix = REDIS_PREFIX_CI_RELATION2
|
prefix = REDIS_PREFIX_CI_RELATION2
|
||||||
|
|
||||||
_tmp = list(map(lambda x: json.loads(x).keys() if x else [], rd.get(key, prefix) or []))
|
if not key or id_filter_limit is None:
|
||||||
ids = [j for i in _tmp for j in i]
|
|
||||||
|
|
||||||
if not key:
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||||
|
_tmp = [[i[0] for i in x if (not id_filter_limit or (
|
||||||
|
key[idx] not in id_filter_limit or int(i[0]) in id_filter_limit[key[idx]]) or
|
||||||
|
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
||||||
|
|
||||||
|
ids = [j for i in _tmp for j in i]
|
||||||
|
|
||||||
if level in self.level:
|
if level in self.level:
|
||||||
merge_ids.extend(ids)
|
merge_ids.extend(ids)
|
||||||
|
|
||||||
@@ -120,7 +129,28 @@ class Search(object):
|
|||||||
|
|
||||||
return merge_ids
|
return merge_ids
|
||||||
|
|
||||||
|
def _has_read_perm_from_parent_nodes(self):
|
||||||
|
self.root_parent_path = list(map(str, self.root_parent_path))
|
||||||
|
if str(self.root_id).isdigit() and str(self.root_id) not in self.root_parent_path:
|
||||||
|
self.root_parent_path.append(str(self.root_id))
|
||||||
|
self.root_parent_path = set(self.root_parent_path)
|
||||||
|
|
||||||
|
if self.is_app_admin:
|
||||||
|
self.type2filter_perms = {}
|
||||||
|
return True
|
||||||
|
|
||||||
|
res = ACLManager().get_resources(ResourceTypeEnum.CI_FILTER) or {}
|
||||||
|
self.type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res]))) or {}
|
||||||
|
for _, filters in self.type2filter_perms.items():
|
||||||
|
if set((filters.get('id_filter') or {}).keys()) & self.root_parent_path:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def search(self):
|
def search(self):
|
||||||
|
use_ci_filter = len(self.descendant_ids) == self.level[0] - 1
|
||||||
|
parent_node_perm_passed = self._has_read_perm_from_parent_nodes()
|
||||||
|
|
||||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||||
cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids]
|
cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids]
|
||||||
|
|
||||||
@@ -161,42 +191,105 @@ class Search(object):
|
|||||||
page=self.page,
|
page=self.page,
|
||||||
count=self.count,
|
count=self.count,
|
||||||
sort=self.sort,
|
sort=self.sort,
|
||||||
ci_ids=merge_ids).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)
|
self.level = int(self.level)
|
||||||
|
|
||||||
|
acl = ACLManager('cmdb')
|
||||||
|
|
||||||
|
type2filter_perms = dict()
|
||||||
|
if not self.is_app_admin:
|
||||||
|
res2 = acl.get_resources(ResourceTypeEnum.CI_FILTER)
|
||||||
|
if res2:
|
||||||
|
type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
|
||||||
|
|
||||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||||
_tmp = []
|
_tmp = []
|
||||||
level2ids = {}
|
level2ids = {}
|
||||||
for lv in range(1, self.level + 1):
|
for lv in range(1, self.level + 1):
|
||||||
level2ids[lv] = []
|
level2ids[lv] = []
|
||||||
|
|
||||||
|
if need_filter:
|
||||||
|
id_filter_limit, ci_filter_limit = None, None
|
||||||
|
if len(self.descendant_ids or []) >= lv and type2filter_perms.get(self.descendant_ids[lv - 1]):
|
||||||
|
id_filter_limit, _ = self._get_ci_filter(type2filter_perms[self.descendant_ids[lv - 1]])
|
||||||
|
elif type_ids and self.level == lv:
|
||||||
|
ci_filters = [type2filter_perms[type_id] for type_id in type_ids if type_id in type2filter_perms]
|
||||||
|
if ci_filters:
|
||||||
|
id_filter_limit, ci_filter_limit = self._get_ci_filter({}, ci_filters=ci_filters)
|
||||||
|
else:
|
||||||
|
id_filter_limit = {}
|
||||||
|
else:
|
||||||
|
id_filter_limit = {}
|
||||||
|
else:
|
||||||
|
id_filter_limit, ci_filter_limit = {}, {}
|
||||||
|
|
||||||
if lv == 1:
|
if lv == 1:
|
||||||
if not self.has_m2m:
|
if not self.has_m2m:
|
||||||
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
key, prefix = [str(i) for i in ids], REDIS_PREFIX_CI_RELATION
|
||||||
else:
|
|
||||||
if not self.ancestor_ids:
|
|
||||||
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
|
||||||
else:
|
else:
|
||||||
key = ["{},{}".format(self.ancestor_ids, _id) for _id in ids]
|
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
|
prefix = REDIS_PREFIX_CI_RELATION2
|
||||||
|
|
||||||
level2ids[lv] = [[i] for i in key]
|
level2ids[lv] = [[i] for i in key]
|
||||||
|
|
||||||
if not key:
|
if not key or id_filter_limit is None:
|
||||||
_tmp = []
|
_tmp = [[]] * len(ids)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||||
|
_tmp = []
|
||||||
if type_ids and lv == self.level:
|
if type_ids and lv == self.level:
|
||||||
_tmp = list(map(lambda x: [i for i in x if i[1] in type_ids],
|
_tmp = [[i for i in x if i[1] in type_ids and
|
||||||
(map(lambda x: list(json.loads(x).items()),
|
(not id_filter_limit or (key[idx] not in id_filter_limit or
|
||||||
[i or '{}' for i in rd.get(key, prefix) 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:
|
else:
|
||||||
_tmp = list(map(lambda x: list(json.loads(x).items()),
|
_tmp = [[i for i in x if (not id_filter_limit or (key[idx] not in id_filter_limit or
|
||||||
[i or '{}' for i in rd.get(key, prefix) 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:
|
else:
|
||||||
|
|
||||||
for idx, item in enumerate(_tmp):
|
for idx, item in enumerate(_tmp):
|
||||||
if item:
|
if item:
|
||||||
if not self.has_m2m:
|
if not self.has_m2m:
|
||||||
@@ -208,15 +301,22 @@ class Search(object):
|
|||||||
level2ids[lv].append(key)
|
level2ids[lv].append(key)
|
||||||
|
|
||||||
if key:
|
if key:
|
||||||
|
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||||
if type_ids and lv == self.level:
|
if type_ids and lv == self.level:
|
||||||
__tmp = map(lambda x: [(_id, type_id) for _id, type_id in json.loads(x).items()
|
__tmp = [[i for i in x if i[1] in type_ids and
|
||||||
if type_id in type_ids],
|
(not id_filter_limit or (
|
||||||
filter(lambda x: x is not None,
|
key[idx] not in id_filter_limit or
|
||||||
rd.get(key, prefix) 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:
|
else:
|
||||||
__tmp = map(lambda x: list(json.loads(x).items()),
|
__tmp = [[i for i in x if (not id_filter_limit or (
|
||||||
filter(lambda x: x is not None,
|
key[idx] not in id_filter_limit or
|
||||||
rd.get(key, prefix) 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:
|
else:
|
||||||
__tmp = []
|
__tmp = []
|
||||||
|
|
||||||
|
@@ -302,9 +302,9 @@ class AttributeValueManager(object):
|
|||||||
return self.write_change2(changed)
|
return self.write_change2(changed)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_attr_value(attr_id, ci_id):
|
def delete_attr_value(attr_id, ci_id, commit=True):
|
||||||
attr = AttributeCache.get(attr_id)
|
attr = AttributeCache.get(attr_id)
|
||||||
if attr is not None:
|
if attr is not None:
|
||||||
value_table = TableMap(attr=attr).table
|
value_table = TableMap(attr=attr).table
|
||||||
for item in value_table.get_by(attr_id=attr.id, ci_id=ci_id, to_dict=False):
|
for item in value_table.get_by(attr_id=attr.id, ci_id=ci_id, to_dict=False):
|
||||||
item.delete()
|
item.delete(commit=commit)
|
||||||
|
@@ -16,7 +16,7 @@ from wtforms import validators
|
|||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
from api.lib.common_setting.acl import ACLManager
|
from api.lib.common_setting.acl import ACLManager
|
||||||
from api.lib.common_setting.const import OperatorType
|
from api.lib.common_setting.const import OperatorType
|
||||||
from api.lib.cmdb.const import CMDB_QUEUE
|
from api.lib.perm.acl.const import ACL_QUEUE
|
||||||
from api.lib.common_setting.resp_format import ErrFormat
|
from api.lib.common_setting.resp_format import ErrFormat
|
||||||
from api.models.common_setting import Employee, Department
|
from api.models.common_setting import Employee, Department
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ class EmployeeCRUD(object):
|
|||||||
def add(**kwargs):
|
def add(**kwargs):
|
||||||
try:
|
try:
|
||||||
res = CreateEmployee().create_single(**kwargs)
|
res = CreateEmployee().create_single(**kwargs)
|
||||||
refresh_employee_acl_info.apply_async(args=(), queue=CMDB_QUEUE)
|
refresh_employee_acl_info.apply_async(args=(res.employee_id,), queue=ACL_QUEUE)
|
||||||
return res
|
return res
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
abort(400, str(e))
|
abort(400, str(e))
|
||||||
@@ -171,7 +171,7 @@ class EmployeeCRUD(object):
|
|||||||
if len(e_list) > 0:
|
if len(e_list) > 0:
|
||||||
edit_employee_department_in_acl.apply_async(
|
edit_employee_department_in_acl.apply_async(
|
||||||
args=(e_list, new_department_id, current_user.uid),
|
args=(e_list, new_department_id, current_user.uid),
|
||||||
queue=CMDB_QUEUE
|
queue=ACL_QUEUE
|
||||||
)
|
)
|
||||||
|
|
||||||
return existed
|
return existed
|
||||||
@@ -577,7 +577,6 @@ class EmployeeCRUD(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def import_employee(employee_list):
|
def import_employee(employee_list):
|
||||||
res = CreateEmployee().batch_create(employee_list)
|
res = CreateEmployee().batch_create(employee_list)
|
||||||
refresh_employee_acl_info.apply_async(args=(), queue=CMDB_QUEUE)
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -788,9 +787,11 @@ class CreateEmployee(object):
|
|||||||
if existed:
|
if existed:
|
||||||
return existed
|
return existed
|
||||||
|
|
||||||
return Employee.create(
|
res = Employee.create(
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
refresh_employee_acl_info.apply_async(args=(res.employee_id,), queue=ACL_QUEUE)
|
||||||
|
return res
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_department_by_name(d_name):
|
def get_department_by_name(d_name):
|
||||||
@@ -897,3 +898,75 @@ class EmployeeUpdateByUidForm(Form):
|
|||||||
avatar = StringField(validators=[])
|
avatar = StringField(validators=[])
|
||||||
sex = StringField(validators=[])
|
sex = StringField(validators=[])
|
||||||
mobile = StringField(validators=[])
|
mobile = StringField(validators=[])
|
||||||
|
|
||||||
|
|
||||||
|
class GrantEmployeeACLPerm(object):
|
||||||
|
"""
|
||||||
|
Grant ACL Permission After Create New Employee
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, acl=None):
|
||||||
|
self.perms_by_create_resources_type = ['read', 'grant', 'delete', 'update']
|
||||||
|
self.perms_by_common_grant = ['read']
|
||||||
|
self.resource_name_list = ['公司信息', '公司架构', '通知设置']
|
||||||
|
|
||||||
|
self.acl = acl if acl else self.check_app('backend')
|
||||||
|
self.resources_types = self.acl.get_all_resources_types()
|
||||||
|
self.resources_type = self.get_resources_type()
|
||||||
|
self.resource_list = self.acl.get_resource_by_type(None, None, self.resources_type['id'])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_app(app_name):
|
||||||
|
acl = ACLManager(app_name)
|
||||||
|
payload = dict(
|
||||||
|
name=app_name,
|
||||||
|
description=app_name
|
||||||
|
)
|
||||||
|
app = acl.validate_app()
|
||||||
|
if not app:
|
||||||
|
acl.create_app(payload)
|
||||||
|
return acl
|
||||||
|
|
||||||
|
def get_resources_type(self):
|
||||||
|
results = list(filter(lambda t: t['name'] == '操作权限', self.resources_types['groups']))
|
||||||
|
if len(results) == 0:
|
||||||
|
payload = dict(
|
||||||
|
app_id=self.acl.app_name,
|
||||||
|
name='操作权限',
|
||||||
|
description='',
|
||||||
|
perms=self.perms_by_create_resources_type
|
||||||
|
)
|
||||||
|
resource_type = self.acl.create_resources_type(payload)
|
||||||
|
else:
|
||||||
|
resource_type = results[0]
|
||||||
|
resource_type_id = resource_type['id']
|
||||||
|
existed_perms = self.resources_types.get('id2perms', {}).get(resource_type_id, [])
|
||||||
|
existed_perms = [p['name'] for p in existed_perms]
|
||||||
|
new_perms = []
|
||||||
|
for perm in self.perms_by_create_resources_type:
|
||||||
|
if perm not in existed_perms:
|
||||||
|
new_perms.append(perm)
|
||||||
|
if len(new_perms) > 0:
|
||||||
|
resource_type['perms'] = existed_perms + new_perms
|
||||||
|
self.acl.update_resources_type(resource_type_id, resource_type)
|
||||||
|
|
||||||
|
return resource_type
|
||||||
|
|
||||||
|
def grant(self, rid_list):
|
||||||
|
[self.grant_by_rid(rid) for rid in rid_list if rid > 0]
|
||||||
|
|
||||||
|
def grant_by_rid(self, rid, is_admin=False):
|
||||||
|
for name in self.resource_name_list:
|
||||||
|
resource = list(filter(lambda r: r['name'] == name, self.resource_list))
|
||||||
|
if len(resource) == 0:
|
||||||
|
payload = dict(
|
||||||
|
type_id=self.resources_type['id'],
|
||||||
|
app_id=self.acl.app_name,
|
||||||
|
name=name,
|
||||||
|
)
|
||||||
|
resource = self.acl.create_resource(payload)
|
||||||
|
else:
|
||||||
|
resource = resource[0]
|
||||||
|
|
||||||
|
perms = self.perms_by_create_resources_type if is_admin else self.perms_by_common_grant
|
||||||
|
self.acl.grant_resource(rid, resource['id'], perms)
|
||||||
|
@@ -389,6 +389,7 @@ class AuditCRUD(object):
|
|||||||
logout_at=logout_at,
|
logout_at=logout_at,
|
||||||
ip=request.headers.get('X-Real-IP') or request.remote_addr,
|
ip=request.headers.get('X-Real-IP') or request.remote_addr,
|
||||||
browser=request.headers.get('User-Agent'),
|
browser=request.headers.get('User-Agent'),
|
||||||
|
channel=request.values.get('channel', 'web'),
|
||||||
)
|
)
|
||||||
|
|
||||||
if logout_at is None:
|
if logout_at is None:
|
||||||
|
@@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
|
|
||||||
import msgpack
|
import msgpack
|
||||||
|
import redis_lock
|
||||||
|
|
||||||
from api.extensions import cache
|
from api.extensions import cache
|
||||||
|
from api.extensions import rd
|
||||||
from api.lib.decorator import flush_db
|
from api.lib.decorator import flush_db
|
||||||
from api.lib.utils import Lock
|
|
||||||
from api.models.acl import App
|
from api.models.acl import App
|
||||||
from api.models.acl import Permission
|
from api.models.acl import Permission
|
||||||
from api.models.acl import Resource
|
from api.models.acl import Resource
|
||||||
@@ -136,14 +137,14 @@ class HasResourceRoleCache(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add(cls, rid, app_id):
|
def add(cls, rid, app_id):
|
||||||
with Lock('HasResourceRoleCache'):
|
with redis_lock.Lock(rd.r, 'HasResourceRoleCache'):
|
||||||
c = cls.get(app_id)
|
c = cls.get(app_id)
|
||||||
c[rid] = 1
|
c[rid] = 1
|
||||||
cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0)
|
cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def remove(cls, rid, app_id):
|
def remove(cls, rid, app_id):
|
||||||
with Lock('HasResourceRoleCache'):
|
with redis_lock.Lock(rd.r, 'HasResourceRoleCache'):
|
||||||
c = cls.get(app_id)
|
c = cls.get(app_id)
|
||||||
c.pop(rid, None)
|
c.pop(rid, None)
|
||||||
cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0)
|
cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0)
|
||||||
|
@@ -194,7 +194,7 @@ def validate(ticket):
|
|||||||
|
|
||||||
def _parse_tag(string, tag):
|
def _parse_tag(string, tag):
|
||||||
"""
|
"""
|
||||||
Used for parsing xml. Search string for the first occurence of
|
Used for parsing xml. Search string for the first occurrence of
|
||||||
<tag>.....</tag> and return text (stripped of leading and tailing
|
<tag>.....</tag> and return text (stripped of leading and tailing
|
||||||
whitespace) between tags. Return "" if tag not found.
|
whitespace) between tags. Return "" if tag not found.
|
||||||
"""
|
"""
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
from typing import Set
|
from typing import Set
|
||||||
|
|
||||||
import elasticsearch
|
import elasticsearch
|
||||||
@@ -213,52 +211,6 @@ class ESHandler(object):
|
|||||||
return 0, [], {}
|
return 0, [], {}
|
||||||
|
|
||||||
|
|
||||||
class Lock(object):
|
|
||||||
def __init__(self, name, timeout=10, app=None, need_lock=True):
|
|
||||||
self.lock_key = name
|
|
||||||
self.need_lock = need_lock
|
|
||||||
self.timeout = timeout
|
|
||||||
if not app:
|
|
||||||
app = current_app
|
|
||||||
self.app = app
|
|
||||||
try:
|
|
||||||
self.redis = redis.Redis(host=self.app.config.get('CACHE_REDIS_HOST'),
|
|
||||||
port=self.app.config.get('CACHE_REDIS_PORT'),
|
|
||||||
password=self.app.config.get('CACHE_REDIS_PASSWORD'))
|
|
||||||
except:
|
|
||||||
self.app.logger.error("cannot connect redis")
|
|
||||||
raise Exception("cannot connect redis")
|
|
||||||
|
|
||||||
def lock(self, timeout=None):
|
|
||||||
if not timeout:
|
|
||||||
timeout = self.timeout
|
|
||||||
retry = 0
|
|
||||||
while retry < 100:
|
|
||||||
timestamp = time.time() + timeout + 1
|
|
||||||
_lock = self.redis.setnx(self.lock_key, timestamp)
|
|
||||||
if _lock == 1 or (
|
|
||||||
time.time() > float(self.redis.get(self.lock_key) or sys.maxsize) and
|
|
||||||
time.time() > float(self.redis.getset(self.lock_key, timestamp) or sys.maxsize)):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
retry += 1
|
|
||||||
time.sleep(0.6)
|
|
||||||
if retry >= 100:
|
|
||||||
raise Exception("get lock failed...")
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
if time.time() < float(self.redis.get(self.lock_key)):
|
|
||||||
self.redis.delete(self.lock_key)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
if self.need_lock:
|
|
||||||
self.lock()
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
if self.need_lock:
|
|
||||||
self.release()
|
|
||||||
|
|
||||||
|
|
||||||
class AESCrypto(object):
|
class AESCrypto(object):
|
||||||
BLOCK_SIZE = 16 # Bytes
|
BLOCK_SIZE = 16 # Bytes
|
||||||
pad = lambda s: s + ((AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE) *
|
pad = lambda s: s + ((AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE) *
|
||||||
|
@@ -356,7 +356,7 @@ class AuditLoginLog(Model2):
|
|||||||
__tablename__ = "acl_audit_login_logs"
|
__tablename__ = "acl_audit_login_logs"
|
||||||
|
|
||||||
username = db.Column(db.String(64), index=True)
|
username = db.Column(db.String(64), index=True)
|
||||||
channel = db.Column(db.Enum('web', 'api'), default="web")
|
channel = db.Column(db.Enum('web', 'api', 'ssh'), default="web")
|
||||||
ip = db.Column(db.String(15))
|
ip = db.Column(db.String(15))
|
||||||
browser = db.Column(db.String(256))
|
browser = db.Column(db.String(256))
|
||||||
description = db.Column(db.String(128))
|
description = db.Column(db.String(128))
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from sqlalchemy.dialects.mysql import DOUBLE
|
from sqlalchemy.dialects.mysql import DOUBLE
|
||||||
|
|
||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
@@ -56,6 +57,16 @@ class CIType(Model):
|
|||||||
uid = db.Column(db.Integer, index=True)
|
uid = db.Column(db.Integer, index=True)
|
||||||
|
|
||||||
|
|
||||||
|
class CITypeInheritance(Model):
|
||||||
|
__tablename__ = "c_ci_type_inheritance"
|
||||||
|
|
||||||
|
parent_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||||
|
child_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||||
|
|
||||||
|
parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeInheritance.parent_id")
|
||||||
|
child = db.relationship("CIType", primaryjoin="CIType.id==CITypeInheritance.child_id")
|
||||||
|
|
||||||
|
|
||||||
class CITypeRelation(Model):
|
class CITypeRelation(Model):
|
||||||
__tablename__ = "c_ci_type_relations"
|
__tablename__ = "c_ci_type_relations"
|
||||||
|
|
||||||
@@ -558,6 +569,7 @@ class CIFilterPerms(Model):
|
|||||||
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'))
|
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'))
|
||||||
ci_filter = db.Column(db.Text)
|
ci_filter = db.Column(db.Text)
|
||||||
attr_filter = db.Column(db.Text)
|
attr_filter = db.Column(db.Text)
|
||||||
|
id_filter = db.Column(db.JSON) # {node_path: unique_value}
|
||||||
|
|
||||||
rid = db.Column(db.Integer, index=True)
|
rid = db.Column(db.Integer, index=True)
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import redis_lock
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask_login import login_user
|
from flask_login import login_user
|
||||||
|
|
||||||
@@ -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
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||||
|
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||||
from api.lib.decorator import flush_db
|
from api.lib.decorator import flush_db
|
||||||
from api.lib.decorator import reconnect_db
|
from api.lib.decorator import reconnect_db
|
||||||
from api.lib.perm.acl.cache import UserCache
|
from api.lib.perm.acl.cache import UserCache
|
||||||
from api.lib.utils import Lock
|
|
||||||
from api.lib.utils import handle_arg_list
|
from api.lib.utils import handle_arg_list
|
||||||
from api.models.cmdb import CI
|
from api.models.cmdb import CI
|
||||||
from api.models.cmdb import CIRelation
|
from api.models.cmdb import CIRelation
|
||||||
@@ -83,6 +84,13 @@ def ci_delete(ci_id):
|
|||||||
current_app.logger.info("{0} delete..........".format(ci_id))
|
current_app.logger.info("{0} delete..........".format(ci_id))
|
||||||
|
|
||||||
|
|
||||||
|
@celery.task(name="cmdb.delete_id_filter", queue=CMDB_QUEUE)
|
||||||
|
@reconnect_db
|
||||||
|
def delete_id_filter(ci_id):
|
||||||
|
|
||||||
|
CIFilterPermsCRUD().delete_id_filter_by_ci_id(ci_id)
|
||||||
|
|
||||||
|
|
||||||
@celery.task(name="cmdb.ci_delete_trigger", queue=CMDB_QUEUE)
|
@celery.task(name="cmdb.ci_delete_trigger", queue=CMDB_QUEUE)
|
||||||
@reconnect_db
|
@reconnect_db
|
||||||
def ci_delete_trigger(trigger, operate_type, ci_dict):
|
def ci_delete_trigger(trigger, operate_type, ci_dict):
|
||||||
@@ -99,7 +107,7 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
|
|||||||
@flush_db
|
@flush_db
|
||||||
@reconnect_db
|
@reconnect_db
|
||||||
def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
||||||
with Lock("CIRelation_{}".format(parent_id)):
|
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
||||||
if ancestor_ids is None:
|
if ancestor_ids is None:
|
||||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||||
children = json.loads(children) if children is not None else {}
|
children = json.loads(children) if children is not None else {}
|
||||||
@@ -177,7 +185,7 @@ def ci_relation_add(parent_dict, child_id, uid):
|
|||||||
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
|
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
|
||||||
@reconnect_db
|
@reconnect_db
|
||||||
def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
||||||
with Lock("CIRelation_{}".format(parent_id)):
|
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
||||||
if ancestor_ids is None:
|
if ancestor_ids is None:
|
||||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||||
children = json.loads(children) if children is not None else {}
|
children = json.loads(children) if children is not None else {}
|
||||||
|
@@ -3,14 +3,14 @@ from flask import current_app
|
|||||||
|
|
||||||
from api.extensions import celery
|
from api.extensions import celery
|
||||||
from api.lib.common_setting.acl import ACLManager
|
from api.lib.common_setting.acl import ACLManager
|
||||||
from api.lib.cmdb.const import CMDB_QUEUE
|
from api.lib.perm.acl.const import ACL_QUEUE
|
||||||
from api.lib.common_setting.resp_format import ErrFormat
|
from api.lib.common_setting.resp_format import ErrFormat
|
||||||
from api.models.common_setting import Department, Employee
|
from api.models.common_setting import Department, Employee
|
||||||
from api.lib.decorator import flush_db
|
from api.lib.decorator import flush_db
|
||||||
from api.lib.decorator import reconnect_db
|
from api.lib.decorator import reconnect_db
|
||||||
|
|
||||||
|
|
||||||
@celery.task(name="common_setting.edit_employee_department_in_acl", queue=CMDB_QUEUE)
|
@celery.task(name="common_setting.edit_employee_department_in_acl", queue=ACL_QUEUE)
|
||||||
@flush_db
|
@flush_db
|
||||||
@reconnect_db
|
@reconnect_db
|
||||||
def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
||||||
@@ -49,8 +49,7 @@ def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
old_d_rid_in_acl = role_map.get(old_department.department_name, 0)
|
old_d_rid_in_acl = role_map.get(old_department.department_name, 0)
|
||||||
if old_d_rid_in_acl == 0:
|
if old_d_rid_in_acl > 0:
|
||||||
return
|
|
||||||
if old_d_rid_in_acl != old_department.acl_rid:
|
if old_d_rid_in_acl != old_department.acl_rid:
|
||||||
old_department.update(
|
old_department.update(
|
||||||
acl_rid=old_d_rid_in_acl
|
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
|
return result
|
||||||
|
|
||||||
|
|
||||||
@celery.task(name="common_setting.refresh_employee_acl_info", queue=CMDB_QUEUE)
|
@celery.task(name="common_setting.refresh_employee_acl_info", queue=ACL_QUEUE)
|
||||||
@flush_db
|
@flush_db
|
||||||
@reconnect_db
|
@reconnect_db
|
||||||
def refresh_employee_acl_info():
|
def refresh_employee_acl_info(current_employee_id=None):
|
||||||
acl = ACLManager('acl')
|
acl = ACLManager('acl')
|
||||||
role_map = {role['name']: role for role in acl.get_all_roles()}
|
role_map = {role['name']: role for role in acl.get_all_roles()}
|
||||||
|
|
||||||
@@ -90,8 +89,12 @@ def refresh_employee_acl_info():
|
|||||||
query = Employee.query.filter(*criterion).order_by(
|
query = Employee.query.filter(*criterion).order_by(
|
||||||
Employee.created_at.desc()
|
Employee.created_at.desc()
|
||||||
)
|
)
|
||||||
|
current_employee_rid = 0
|
||||||
|
|
||||||
for em in query.all():
|
for em in query.all():
|
||||||
|
if current_employee_id and em.employee_id == current_employee_id:
|
||||||
|
current_employee_rid = em.acl_rid if em.acl_rid else 0
|
||||||
|
|
||||||
if em.acl_uid and em.acl_rid:
|
if em.acl_uid and em.acl_rid:
|
||||||
continue
|
continue
|
||||||
role = role_map.get(em.username, None)
|
role = role_map.get(em.username, None)
|
||||||
@@ -105,6 +108,9 @@ def refresh_employee_acl_info():
|
|||||||
if not em.acl_rid:
|
if not em.acl_rid:
|
||||||
params['acl_rid'] = role.get('id', 0)
|
params['acl_rid'] = role.get('id', 0)
|
||||||
|
|
||||||
|
if current_employee_id and em.employee_id == current_employee_id:
|
||||||
|
current_employee_rid = params['acl_rid'] if params.get('acl_rid', 0) else 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
em.update(**params)
|
em.update(**params)
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
@@ -113,3 +119,12 @@ def refresh_employee_acl_info():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(str(e))
|
current_app.logger.error(str(e))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if current_employee_rid and current_employee_rid > 0:
|
||||||
|
try:
|
||||||
|
from api.lib.common_setting.employee import GrantEmployeeACLPerm
|
||||||
|
|
||||||
|
GrantEmployeeACLPerm().grant_by_rid(current_employee_rid, False)
|
||||||
|
current_app.logger.info(f"GrantEmployeeACLPerm success, current_employee_rid: {current_employee_rid}")
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(str(e))
|
||||||
|
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-01-03 11:39+0800\n"
|
"POT-Creation-Date: 2024-03-01 13:49+0800\n"
|
||||||
"PO-Revision-Date: 2023-12-25 20:21+0800\n"
|
"PO-Revision-Date: 2023-12-25 20:21+0800\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language: zh\n"
|
"Language: zh\n"
|
||||||
@@ -234,205 +234,209 @@ msgstr "只有创建人才能删除它!"
|
|||||||
msgid "The model cannot be deleted because the CI already exists"
|
msgid "The model cannot be deleted because the CI already exists"
|
||||||
msgstr "因为CI已经存在,不能删除模型"
|
msgstr "因为CI已经存在,不能删除模型"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:65
|
#: api/lib/cmdb/resp_format.py:63
|
||||||
|
msgid "The inheritance cannot be deleted because the CI already exists"
|
||||||
|
msgstr "因为CI已经存在,不能删除继承关系"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:67
|
||||||
msgid ""
|
msgid ""
|
||||||
"The model cannot be deleted because the model is referenced by the "
|
"The model cannot be deleted because the model is referenced by the "
|
||||||
"relational view {}"
|
"relational view {}"
|
||||||
msgstr "因为关系视图 {} 引用了该模型,不能删除模型"
|
msgstr "因为关系视图 {} 引用了该模型,不能删除模型"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:67
|
#: api/lib/cmdb/resp_format.py:69
|
||||||
msgid "Model group {} does not exist"
|
msgid "Model group {} does not exist"
|
||||||
msgstr "模型分组 {} 不存在"
|
msgstr "模型分组 {} 不存在"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:68
|
#: api/lib/cmdb/resp_format.py:70
|
||||||
msgid "Model group {} already exists"
|
msgid "Model group {} already exists"
|
||||||
msgstr "模型分组 {} 已经存在"
|
msgstr "模型分组 {} 已经存在"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:69
|
#: api/lib/cmdb/resp_format.py:71
|
||||||
msgid "Model relationship {} does not exist"
|
msgid "Model relationship {} does not exist"
|
||||||
msgstr "模型关系 {} 不存在"
|
msgstr "模型关系 {} 不存在"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:70
|
#: api/lib/cmdb/resp_format.py:72
|
||||||
msgid "Attribute group {} already exists"
|
msgid "Attribute group {} already exists"
|
||||||
msgstr "属性分组 {} 已存在"
|
msgstr "属性分组 {} 已存在"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:71
|
#: api/lib/cmdb/resp_format.py:73
|
||||||
msgid "Attribute group {} does not exist"
|
msgid "Attribute group {} does not exist"
|
||||||
msgstr "属性分组 {} 不存在"
|
msgstr "属性分组 {} 不存在"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:73
|
#: api/lib/cmdb/resp_format.py:75
|
||||||
msgid "Attribute group <{0}> - attribute <{1}> does not exist"
|
msgid "Attribute group <{0}> - attribute <{1}> does not exist"
|
||||||
msgstr "属性组<{0}> - 属性<{1}> 不存在"
|
msgstr "属性组<{0}> - 属性<{1}> 不存在"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:74
|
#: api/lib/cmdb/resp_format.py:76
|
||||||
msgid "The unique constraint already exists!"
|
msgid "The unique constraint already exists!"
|
||||||
msgstr "唯一约束已经存在!"
|
msgstr "唯一约束已经存在!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:76
|
#: api/lib/cmdb/resp_format.py:78
|
||||||
msgid "Uniquely constrained attributes cannot be JSON and multi-valued"
|
msgid "Uniquely constrained attributes cannot be JSON and multi-valued"
|
||||||
msgstr "唯一约束的属性不能是 JSON 和 多值"
|
msgstr "唯一约束的属性不能是 JSON 和 多值"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:77
|
#: api/lib/cmdb/resp_format.py:79
|
||||||
msgid "Duplicated trigger"
|
msgid "Duplicated trigger"
|
||||||
msgstr "重复的触发器"
|
msgstr "重复的触发器"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:78
|
#: api/lib/cmdb/resp_format.py:80
|
||||||
msgid "Trigger {} does not exist"
|
msgid "Trigger {} does not exist"
|
||||||
msgstr "触发器 {} 不存在"
|
msgstr "触发器 {} 不存在"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:80
|
#: api/lib/cmdb/resp_format.py:82
|
||||||
msgid "Operation record {} does not exist"
|
msgid "Operation record {} does not exist"
|
||||||
msgstr "操作记录 {} 不存在"
|
msgstr "操作记录 {} 不存在"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:81
|
#: api/lib/cmdb/resp_format.py:83
|
||||||
msgid "Unique identifier cannot be deleted"
|
msgid "Unique identifier cannot be deleted"
|
||||||
msgstr "不能删除唯一标识"
|
msgstr "不能删除唯一标识"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:82
|
#: api/lib/cmdb/resp_format.py:84
|
||||||
msgid "Cannot delete default sorted attributes"
|
msgid "Cannot delete default sorted attributes"
|
||||||
msgstr "不能删除默认排序的属性"
|
msgstr "不能删除默认排序的属性"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:84
|
#: api/lib/cmdb/resp_format.py:86
|
||||||
msgid "No node selected"
|
msgid "No node selected"
|
||||||
msgstr "没有选择节点"
|
msgstr "没有选择节点"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:85
|
#: api/lib/cmdb/resp_format.py:87
|
||||||
msgid "This search option does not exist!"
|
msgid "This search option does not exist!"
|
||||||
msgstr "该搜索选项不存在!"
|
msgstr "该搜索选项不存在!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:86
|
#: api/lib/cmdb/resp_format.py:88
|
||||||
msgid "This search option has a duplicate name!"
|
msgid "This search option has a duplicate name!"
|
||||||
msgstr "该搜索选项命名重复!"
|
msgstr "该搜索选项命名重复!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:88
|
#: api/lib/cmdb/resp_format.py:90
|
||||||
msgid "Relationship type {} already exists"
|
msgid "Relationship type {} already exists"
|
||||||
msgstr "关系类型 {} 已经存在"
|
msgstr "关系类型 {} 已经存在"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:89
|
#: api/lib/cmdb/resp_format.py:91
|
||||||
msgid "Relationship type {} does not exist"
|
msgid "Relationship type {} does not exist"
|
||||||
msgstr "关系类型 {} 不存在"
|
msgstr "关系类型 {} 不存在"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:91
|
#: api/lib/cmdb/resp_format.py:93
|
||||||
msgid "Invalid attribute value: {}"
|
msgid "Invalid attribute value: {}"
|
||||||
msgstr "无效的属性值: {}"
|
msgstr "无效的属性值: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:92
|
#: api/lib/cmdb/resp_format.py:94
|
||||||
msgid "{} Invalid value: {}"
|
msgid "{} Invalid value: {}"
|
||||||
msgstr "无效的值: {}"
|
msgstr "无效的值: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:93
|
#: api/lib/cmdb/resp_format.py:95
|
||||||
msgid "{} is not in the predefined values"
|
msgid "{} is not in the predefined values"
|
||||||
msgstr "{} 不在预定义值里"
|
msgstr "{} 不在预定义值里"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:95
|
#: api/lib/cmdb/resp_format.py:97
|
||||||
msgid "The value of attribute {} must be unique, {} already exists"
|
msgid "The value of attribute {} must be unique, {} already exists"
|
||||||
msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
|
msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:96
|
#: api/lib/cmdb/resp_format.py:98
|
||||||
msgid "Attribute {} value must exist"
|
msgid "Attribute {} value must exist"
|
||||||
msgstr "属性 {} 值必须存在"
|
msgstr "属性 {} 值必须存在"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:99
|
#: api/lib/cmdb/resp_format.py:101
|
||||||
msgid "Unknown error when adding or modifying attribute value: {}"
|
msgid "Unknown error when adding or modifying attribute value: {}"
|
||||||
msgstr "新增或者修改属性值未知错误: {}"
|
msgstr "新增或者修改属性值未知错误: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:101
|
#: api/lib/cmdb/resp_format.py:103
|
||||||
msgid "Duplicate custom name"
|
msgid "Duplicate custom name"
|
||||||
msgstr "订制名重复"
|
msgstr "订制名重复"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:103
|
#: api/lib/cmdb/resp_format.py:105
|
||||||
msgid "Number of models exceeds limit: {}"
|
msgid "Number of models exceeds limit: {}"
|
||||||
msgstr "模型数超过限制: {}"
|
msgstr "模型数超过限制: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:104
|
#: api/lib/cmdb/resp_format.py:106
|
||||||
msgid "The number of CIs exceeds the limit: {}"
|
msgid "The number of CIs exceeds the limit: {}"
|
||||||
msgstr "CI数超过限制: {}"
|
msgstr "CI数超过限制: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:106
|
#: api/lib/cmdb/resp_format.py:108
|
||||||
msgid "Auto-discovery rule: {} already exists!"
|
msgid "Auto-discovery rule: {} already exists!"
|
||||||
msgstr "自动发现规则: {} 已经存在!"
|
msgstr "自动发现规则: {} 已经存在!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:107
|
#: api/lib/cmdb/resp_format.py:109
|
||||||
msgid "Auto-discovery rule: {} does not exist!"
|
msgid "Auto-discovery rule: {} does not exist!"
|
||||||
msgstr "自动发现规则: {} 不存在!"
|
msgstr "自动发现规则: {} 不存在!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:109
|
#: api/lib/cmdb/resp_format.py:111
|
||||||
msgid "This auto-discovery rule is referenced by the model and cannot be deleted!"
|
msgid "This auto-discovery rule is referenced by the model and cannot be deleted!"
|
||||||
msgstr "该自动发现规则被模型引用, 不能删除!"
|
msgstr "该自动发现规则被模型引用, 不能删除!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:111
|
#: api/lib/cmdb/resp_format.py:113
|
||||||
msgid "The application of auto-discovery rules cannot be defined repeatedly!"
|
msgid "The application of auto-discovery rules cannot be defined repeatedly!"
|
||||||
msgstr "自动发现规则的应用不能重复定义!"
|
msgstr "自动发现规则的应用不能重复定义!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:112
|
#: api/lib/cmdb/resp_format.py:114
|
||||||
msgid "The auto-discovery you want to modify: {} does not exist!"
|
msgid "The auto-discovery you want to modify: {} does not exist!"
|
||||||
msgstr "您要修改的自动发现: {} 不存在!"
|
msgstr "您要修改的自动发现: {} 不存在!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:113
|
#: api/lib/cmdb/resp_format.py:115
|
||||||
msgid "Attribute does not include unique identifier: {}"
|
msgid "Attribute does not include unique identifier: {}"
|
||||||
msgstr "属性字段没有包括唯一标识: {}"
|
msgstr "属性字段没有包括唯一标识: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:114
|
#: api/lib/cmdb/resp_format.py:116
|
||||||
msgid "The auto-discovery instance does not exist!"
|
msgid "The auto-discovery instance does not exist!"
|
||||||
msgstr "自动发现的实例不存在!"
|
msgstr "自动发现的实例不存在!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:115
|
#: api/lib/cmdb/resp_format.py:117
|
||||||
msgid "The model is not associated with this auto-discovery!"
|
msgid "The model is not associated with this auto-discovery!"
|
||||||
msgstr "模型并未关联该自动发现!"
|
msgstr "模型并未关联该自动发现!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:116
|
#: api/lib/cmdb/resp_format.py:118
|
||||||
msgid "Only the creator can modify the Secret!"
|
msgid "Only the creator can modify the Secret!"
|
||||||
msgstr "只有创建人才能修改Secret!"
|
msgstr "只有创建人才能修改Secret!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:118
|
#: api/lib/cmdb/resp_format.py:120
|
||||||
msgid "This rule already has auto-discovery instances and cannot be deleted!"
|
msgid "This rule already has auto-discovery instances and cannot be deleted!"
|
||||||
msgstr "该规则已经有自动发现的实例, 不能被删除!"
|
msgstr "该规则已经有自动发现的实例, 不能被删除!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:120
|
#: api/lib/cmdb/resp_format.py:122
|
||||||
msgid "The default auto-discovery rule is already referenced by model {}!"
|
msgid "The default auto-discovery rule is already referenced by model {}!"
|
||||||
msgstr "该默认的自动发现规则 已经被模型 {} 引用!"
|
msgstr "该默认的自动发现规则 已经被模型 {} 引用!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:122
|
#: api/lib/cmdb/resp_format.py:124
|
||||||
msgid "The unique_key method must return a non-empty string!"
|
msgid "The unique_key method must return a non-empty string!"
|
||||||
msgstr "unique_key方法必须返回非空字符串!"
|
msgstr "unique_key方法必须返回非空字符串!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:123
|
#: api/lib/cmdb/resp_format.py:125
|
||||||
msgid "The attributes method must return a list"
|
msgid "The attributes method must return a list"
|
||||||
msgstr "attributes方法必须返回的是list"
|
msgstr "attributes方法必须返回的是list"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:125
|
#: api/lib/cmdb/resp_format.py:127
|
||||||
msgid "The list returned by the attributes method cannot be empty!"
|
msgid "The list returned by the attributes method cannot be empty!"
|
||||||
msgstr "attributes方法返回的list不能为空!"
|
msgstr "attributes方法返回的list不能为空!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:127
|
#: api/lib/cmdb/resp_format.py:129
|
||||||
msgid "Only administrators can define execution targets as: all nodes!"
|
msgid "Only administrators can define execution targets as: all nodes!"
|
||||||
msgstr "只有管理员才可以定义执行机器为: 所有节点!"
|
msgstr "只有管理员才可以定义执行机器为: 所有节点!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:128
|
#: api/lib/cmdb/resp_format.py:130
|
||||||
msgid "Execute targets permission check failed: {}"
|
msgid "Execute targets permission check failed: {}"
|
||||||
msgstr "执行机器权限检查不通过: {}"
|
msgstr "执行机器权限检查不通过: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:130
|
#: api/lib/cmdb/resp_format.py:132
|
||||||
msgid "CI filter authorization must be named!"
|
msgid "CI filter authorization must be named!"
|
||||||
msgstr "CI过滤授权 必须命名!"
|
msgstr "CI过滤授权 必须命名!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:131
|
#: api/lib/cmdb/resp_format.py:133
|
||||||
msgid "CI filter authorization is currently not supported or query"
|
msgid "CI filter authorization is currently not supported or query"
|
||||||
msgstr "CI过滤授权 暂时不支持 或 查询"
|
msgstr "CI过滤授权 暂时不支持 或 查询"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:134
|
#: api/lib/cmdb/resp_format.py:136
|
||||||
msgid "You do not have permission to operate attribute {}!"
|
msgid "You do not have permission to operate attribute {}!"
|
||||||
msgstr "您没有属性 {} 的操作权限!"
|
msgstr "您没有属性 {} 的操作权限!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:135
|
#: api/lib/cmdb/resp_format.py:137
|
||||||
msgid "You do not have permission to operate this CI!"
|
msgid "You do not have permission to operate this CI!"
|
||||||
msgstr "您没有该CI的操作权限!"
|
msgstr "您没有该CI的操作权限!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:137
|
#: api/lib/cmdb/resp_format.py:139
|
||||||
msgid "Failed to save password: {}"
|
msgid "Failed to save password: {}"
|
||||||
msgstr "保存密码失败: {}"
|
msgstr "保存密码失败: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:138
|
#: api/lib/cmdb/resp_format.py:140
|
||||||
msgid "Failed to get password: {}"
|
msgid "Failed to get password: {}"
|
||||||
msgstr "获取密码失败: {}"
|
msgstr "获取密码失败: {}"
|
||||||
|
|
||||||
|
@@ -11,8 +11,7 @@ from api.lib.cmdb.cache import CITypeCache
|
|||||||
from api.lib.cmdb.ci import CIManager
|
from api.lib.cmdb.ci import CIManager
|
||||||
from api.lib.cmdb.ci import CIRelationManager
|
from api.lib.cmdb.ci import CIRelationManager
|
||||||
from api.lib.cmdb.const import ExistPolicy
|
from api.lib.cmdb.const import ExistPolicy
|
||||||
from api.lib.cmdb.const import PermEnum
|
from api.lib.cmdb.const import ResourceTypeEnum, PermEnum
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum
|
|
||||||
from api.lib.cmdb.const import RetKey
|
from api.lib.cmdb.const import RetKey
|
||||||
from api.lib.cmdb.perms import has_perm_for_ci
|
from api.lib.cmdb.perms import has_perm_for_ci
|
||||||
from api.lib.cmdb.search import SearchError
|
from api.lib.cmdb.search import SearchError
|
||||||
@@ -152,9 +151,10 @@ class CISearchView(APIView):
|
|||||||
ret_key = RetKey.NAME
|
ret_key = RetKey.NAME
|
||||||
facet = handle_arg_list(request.values.get("facet", ""))
|
facet = handle_arg_list(request.values.get("facet", ""))
|
||||||
sort = request.values.get("sort")
|
sort = request.values.get("sort")
|
||||||
|
use_id_filter = request.values.get("use_id_filter", False) in current_app.config.get('BOOL_TRUE')
|
||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
s = search(query, fl, facet, page, ret_key, count, sort, excludes)
|
s = search(query, fl, facet, page, ret_key, count, sort, excludes, use_id_filter=use_id_filter)
|
||||||
try:
|
try:
|
||||||
response, counter, total, page, numfound, facet = s.search()
|
response, counter, total, page, numfound, facet = s.search()
|
||||||
except SearchError as e:
|
except SearchError as e:
|
||||||
|
@@ -13,7 +13,6 @@ from api.lib.cmdb.resp_format import ErrFormat
|
|||||||
from api.lib.cmdb.search import SearchError
|
from api.lib.cmdb.search import SearchError
|
||||||
from api.lib.cmdb.search.ci_relation.search import Search
|
from api.lib.cmdb.search.ci_relation.search import Search
|
||||||
from api.lib.decorator import args_required
|
from api.lib.decorator import args_required
|
||||||
from api.lib.perm.auth import auth_abandoned
|
|
||||||
from api.lib.utils import get_page
|
from api.lib.utils import get_page
|
||||||
from api.lib.utils import get_page_size
|
from api.lib.utils import get_page_size
|
||||||
from api.lib.utils import handle_arg_list
|
from api.lib.utils import handle_arg_list
|
||||||
@@ -36,6 +35,8 @@ class CIRelationSearchView(APIView):
|
|||||||
|
|
||||||
root_id = request.values.get('root_id')
|
root_id = request.values.get('root_id')
|
||||||
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
|
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
|
||||||
|
root_parent_path = handle_arg_list(request.values.get('root_parent_path') or '')
|
||||||
|
descendant_ids = list(map(int, handle_arg_list(request.values.get('descendant_ids', []))))
|
||||||
level = list(map(int, handle_arg_list(request.values.get('level', '1'))))
|
level = list(map(int, handle_arg_list(request.values.get('level', '1'))))
|
||||||
|
|
||||||
query = request.values.get('q', "")
|
query = request.values.get('q', "")
|
||||||
@@ -47,7 +48,8 @@ class CIRelationSearchView(APIView):
|
|||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
s = Search(root_id, level, query, fl, facet, page, count, sort, reverse,
|
s = Search(root_id, level, query, fl, facet, page, count, sort, reverse,
|
||||||
ancestor_ids=ancestor_ids, has_m2m=has_m2m)
|
ancestor_ids=ancestor_ids, has_m2m=has_m2m, root_parent_path=root_parent_path,
|
||||||
|
descendant_ids=descendant_ids)
|
||||||
try:
|
try:
|
||||||
response, counter, total, page, numfound, facet = s.search()
|
response, counter, total, page, numfound, facet = s.search()
|
||||||
except SearchError as e:
|
except SearchError as e:
|
||||||
@@ -65,16 +67,16 @@ class CIRelationSearchView(APIView):
|
|||||||
class CIRelationStatisticsView(APIView):
|
class CIRelationStatisticsView(APIView):
|
||||||
url_prefix = "/ci_relations/statistics"
|
url_prefix = "/ci_relations/statistics"
|
||||||
|
|
||||||
@auth_abandoned
|
|
||||||
def get(self):
|
def get(self):
|
||||||
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
|
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
|
||||||
level = request.values.get('level', 1)
|
level = request.values.get('level', 1)
|
||||||
type_ids = set(map(int, handle_arg_list(request.values.get('type_ids', []))))
|
type_ids = set(map(int, handle_arg_list(request.values.get('type_ids', []))))
|
||||||
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
|
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
|
||||||
|
descendant_ids = list(map(int, handle_arg_list(request.values.get('descendant_ids', []))))
|
||||||
has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE')
|
has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE')
|
||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
s = Search(root_ids, level, ancestor_ids=ancestor_ids, has_m2m=has_m2m)
|
s = Search(root_ids, level, ancestor_ids=ancestor_ids, descendant_ids=descendant_ids, has_m2m=has_m2m)
|
||||||
try:
|
try:
|
||||||
result = s.statistics(type_ids)
|
result = s.statistics(type_ids)
|
||||||
except SearchError as e:
|
except SearchError as e:
|
||||||
|
@@ -14,6 +14,7 @@ from api.lib.cmdb.cache import CITypeCache
|
|||||||
from api.lib.cmdb.ci_type import CITypeAttributeGroupManager
|
from api.lib.cmdb.ci_type import CITypeAttributeGroupManager
|
||||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||||
from api.lib.cmdb.ci_type import CITypeGroupManager
|
from api.lib.cmdb.ci_type import CITypeGroupManager
|
||||||
|
from api.lib.cmdb.ci_type import CITypeInheritanceManager
|
||||||
from api.lib.cmdb.ci_type import CITypeManager
|
from api.lib.cmdb.ci_type import CITypeManager
|
||||||
from api.lib.cmdb.ci_type import CITypeTemplateManager
|
from api.lib.cmdb.ci_type import CITypeTemplateManager
|
||||||
from api.lib.cmdb.ci_type import CITypeTriggerManager
|
from api.lib.cmdb.ci_type import CITypeTriggerManager
|
||||||
@@ -37,15 +38,23 @@ from api.resource import APIView
|
|||||||
|
|
||||||
|
|
||||||
class CITypeView(APIView):
|
class CITypeView(APIView):
|
||||||
url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>")
|
url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>",
|
||||||
|
"/ci_types/icons")
|
||||||
|
|
||||||
def get(self, type_id=None, type_name=None):
|
def get(self, type_id=None, type_name=None):
|
||||||
|
if request.url.endswith("icons"):
|
||||||
|
return self.jsonify(CITypeManager().get_icons())
|
||||||
|
|
||||||
q = request.args.get("type_name")
|
q = request.args.get("type_name")
|
||||||
|
|
||||||
if type_id is not None:
|
if type_id is not None:
|
||||||
ci_types = [CITypeCache.get(type_id).to_dict()]
|
ci_type = CITypeCache.get(type_id).to_dict()
|
||||||
|
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(type_id)
|
||||||
|
ci_types = [ci_type]
|
||||||
elif type_name is not None:
|
elif type_name is not None:
|
||||||
ci_types = [CITypeCache.get(type_name).to_dict()]
|
ci_type = CITypeCache.get(type_name).to_dict()
|
||||||
|
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(ci_type['id'])
|
||||||
|
ci_types = [ci_type]
|
||||||
else:
|
else:
|
||||||
ci_types = CITypeManager().get_ci_types(q)
|
ci_types = CITypeManager().get_ci_types(q)
|
||||||
count = len(ci_types)
|
count = len(ci_types)
|
||||||
@@ -53,7 +62,7 @@ class CITypeView(APIView):
|
|||||||
return self.jsonify(numfound=count, ci_types=ci_types)
|
return self.jsonify(numfound=count, ci_types=ci_types)
|
||||||
|
|
||||||
@args_required("name")
|
@args_required("name")
|
||||||
@args_validate(CITypeManager.cls)
|
@args_validate(CITypeManager.cls, exclude_args=['parent_ids'])
|
||||||
def post(self):
|
def post(self):
|
||||||
params = request.values
|
params = request.values
|
||||||
|
|
||||||
@@ -84,6 +93,26 @@ class CITypeView(APIView):
|
|||||||
return self.jsonify(type_id=type_id)
|
return self.jsonify(type_id=type_id)
|
||||||
|
|
||||||
|
|
||||||
|
class CITypeInheritanceView(APIView):
|
||||||
|
url_prefix = ("/ci_types/inheritance",)
|
||||||
|
|
||||||
|
@args_required("parent_ids")
|
||||||
|
@args_required("child_id")
|
||||||
|
@has_perm_from_args("child_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||||
|
def post(self):
|
||||||
|
CITypeInheritanceManager.add(request.values['parent_ids'], request.values['child_id'])
|
||||||
|
|
||||||
|
return self.jsonify(**request.values)
|
||||||
|
|
||||||
|
@args_required("parent_id")
|
||||||
|
@args_required("child_id")
|
||||||
|
@has_perm_from_args("child_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||||
|
def delete(self):
|
||||||
|
CITypeInheritanceManager.delete(request.values['parent_id'], request.values['child_id'])
|
||||||
|
|
||||||
|
return self.jsonify(**request.values)
|
||||||
|
|
||||||
|
|
||||||
class CITypeGroupView(APIView):
|
class CITypeGroupView(APIView):
|
||||||
url_prefix = ("/ci_types/groups",
|
url_prefix = ("/ci_types/groups",
|
||||||
"/ci_types/groups/config",
|
"/ci_types/groups/config",
|
||||||
@@ -248,8 +277,8 @@ class CITypeAttributeTransferView(APIView):
|
|||||||
@args_required('from')
|
@args_required('from')
|
||||||
@args_required('to')
|
@args_required('to')
|
||||||
def post(self, type_id):
|
def post(self, type_id):
|
||||||
_from = request.values.get('from') # {'attr_id': xx, 'group_id': xx}
|
_from = request.values.get('from') # {'attr_id': xx, 'group_id': xx, 'group_name': xx}
|
||||||
_to = request.values.get('to') # {'group_id': xx, 'order': xxx}
|
_to = request.values.get('to') # {'group_id': xx, 'group_name': xx, 'order': xxx}
|
||||||
|
|
||||||
CITypeAttributeManager.transfer(type_id, _from, _to)
|
CITypeAttributeManager.transfer(type_id, _from, _to)
|
||||||
|
|
||||||
@@ -262,8 +291,8 @@ class CITypeAttributeGroupTransferView(APIView):
|
|||||||
@args_required('from')
|
@args_required('from')
|
||||||
@args_required('to')
|
@args_required('to')
|
||||||
def post(self, type_id):
|
def post(self, type_id):
|
||||||
_from = request.values.get('from') # group_id
|
_from = request.values.get('from') # group_id or group_name
|
||||||
_to = request.values.get('to') # group_id
|
_to = request.values.get('to') # group_id or group_name
|
||||||
|
|
||||||
CITypeAttributeGroupManager.transfer(type_id, _from, _to)
|
CITypeAttributeGroupManager.transfer(type_id, _from, _to)
|
||||||
|
|
||||||
@@ -296,7 +325,7 @@ class CITypeAttributeGroupView(APIView):
|
|||||||
|
|
||||||
attr_order = list(zip(attrs, orders))
|
attr_order = list(zip(attrs, orders))
|
||||||
group = CITypeAttributeGroupManager.create_or_update(type_id, name, attr_order, order)
|
group = CITypeAttributeGroupManager.create_or_update(type_id, name, attr_order, order)
|
||||||
current_app.logger.warning(group.id)
|
|
||||||
return self.jsonify(group_id=group.id)
|
return self.jsonify(group_id=group.id)
|
||||||
|
|
||||||
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||||
@@ -310,11 +339,13 @@ class CITypeAttributeGroupView(APIView):
|
|||||||
|
|
||||||
attr_order = list(zip(attrs, orders))
|
attr_order = list(zip(attrs, orders))
|
||||||
CITypeAttributeGroupManager.update(group_id, name, attr_order, order)
|
CITypeAttributeGroupManager.update(group_id, name, attr_order, order)
|
||||||
|
|
||||||
return self.jsonify(group_id=group_id)
|
return self.jsonify(group_id=group_id)
|
||||||
|
|
||||||
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||||
def delete(self, group_id):
|
def delete(self, group_id):
|
||||||
CITypeAttributeGroupManager.delete(group_id)
|
CITypeAttributeGroupManager.delete(group_id)
|
||||||
|
|
||||||
return self.jsonify(group_id=group_id)
|
return self.jsonify(group_id=group_id)
|
||||||
|
|
||||||
|
|
||||||
@@ -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'):
|
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||||
return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
|
return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
|
||||||
|
|
||||||
|
if perms and not request.values.get('id_filter'):
|
||||||
acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
|
acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
|
||||||
|
|
||||||
resource = None
|
new_resource = None
|
||||||
if 'ci_filter' in request.values or 'attr_filter' in request.values:
|
if 'ci_filter' in request.values or 'attr_filter' in request.values or 'id_filter' in request.values:
|
||||||
resource = CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **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.tasks.acl import role_rebuild
|
||||||
from api.lib.perm.acl.const import ACL_QUEUE
|
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'):
|
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||||
return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
|
return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
|
||||||
|
|
||||||
acl.revoke_resource_from_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
|
|
||||||
|
|
||||||
app_id = AppCache.get('cmdb').id
|
app_id = AppCache.get('cmdb').id
|
||||||
resource = None
|
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:
|
if PermEnum.READ in perms or not perms:
|
||||||
resource = CIFilterPermsCRUD().delete(type_id=type_id, rid=rid)
|
resource = CIFilterPermsCRUD().delete(type_id=type_id, rid=rid)
|
||||||
|
|
||||||
|
@@ -37,6 +37,7 @@ PyMySQL==1.1.0
|
|||||||
ldap3==2.9.1
|
ldap3==2.9.1
|
||||||
PyYAML==6.0.1
|
PyYAML==6.0.1
|
||||||
redis==4.6.0
|
redis==4.6.0
|
||||||
|
python-redis-lock==4.0.0
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
requests_oauthlib==1.3.1
|
requests_oauthlib==1.3.1
|
||||||
markdownify==0.11.6
|
markdownify==0.11.6
|
||||||
|
@@ -20,6 +20,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
:disabled="disabled"
|
||||||
>
|
>
|
||||||
</treeselect>
|
</treeselect>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,6 +43,7 @@
|
|||||||
"
|
"
|
||||||
appendToBody
|
appendToBody
|
||||||
:zIndex="1050"
|
:zIndex="1050"
|
||||||
|
:disabled="disabled"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:title="node.label"
|
:title="node.label"
|
||||||
@@ -80,6 +82,7 @@
|
|||||||
@select="(value) => handleChangeExp(value, item, index)"
|
@select="(value) => handleChangeExp(value, item, index)"
|
||||||
appendToBody
|
appendToBody
|
||||||
:zIndex="1050"
|
:zIndex="1050"
|
||||||
|
:disabled="disabled"
|
||||||
>
|
>
|
||||||
</treeselect>
|
</treeselect>
|
||||||
<treeselect
|
<treeselect
|
||||||
@@ -103,6 +106,7 @@
|
|||||||
"
|
"
|
||||||
appendToBody
|
appendToBody
|
||||||
:zIndex="1050"
|
:zIndex="1050"
|
||||||
|
:disabled="disabled"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:title="node.label"
|
:title="node.label"
|
||||||
@@ -125,6 +129,7 @@
|
|||||||
v-model="item.min"
|
v-model="item.min"
|
||||||
:style="{ width: '78px' }"
|
:style="{ width: '78px' }"
|
||||||
:placeholder="$t('min')"
|
:placeholder="$t('min')"
|
||||||
|
:disabled="disabled"
|
||||||
/>
|
/>
|
||||||
~
|
~
|
||||||
<a-input
|
<a-input
|
||||||
@@ -133,6 +138,7 @@
|
|||||||
v-model="item.max"
|
v-model="item.max"
|
||||||
:style="{ width: '78px' }"
|
:style="{ width: '78px' }"
|
||||||
:placeholder="$t('max')"
|
:placeholder="$t('max')"
|
||||||
|
:disabled="disabled"
|
||||||
/>
|
/>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
<a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }">
|
<a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }">
|
||||||
@@ -155,6 +161,7 @@
|
|||||||
"
|
"
|
||||||
appendToBody
|
appendToBody
|
||||||
:zIndex="1050"
|
:zIndex="1050"
|
||||||
|
:disabled="disabled"
|
||||||
>
|
>
|
||||||
</treeselect>
|
</treeselect>
|
||||||
<a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" />
|
<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: ';' }) : ''"
|
:placeholder="item.exp === 'in' || item.exp === '~in' ? $t('cmdbFilterComp.split', { separator: ';' }) : ''"
|
||||||
class="ops-input"
|
class="ops-input"
|
||||||
:style="{ width: '175px' }"
|
:style="{ width: '175px' }"
|
||||||
|
:disabled="disabled"
|
||||||
></a-input>
|
></a-input>
|
||||||
<div v-else :style="{ width: '175px' }"></div>
|
<div v-else :style="{ width: '175px' }"></div>
|
||||||
|
<template v-if="!disabled">
|
||||||
<a-tooltip :title="$t('copy')">
|
<a-tooltip :title="$t('copy')">
|
||||||
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a>
|
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -177,8 +186,9 @@
|
|||||||
<a-tooltip :title="$t('cmdbFilterComp.addHere')" v-if="needAddHere">
|
<a-tooltip :title="$t('cmdbFilterComp.addHere')" v-if="needAddHere">
|
||||||
<a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a>
|
<a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
</a-space>
|
</a-space>
|
||||||
<div class="table-filter-add">
|
<div class="table-filter-add" v-if="!disabled">
|
||||||
<a @click="handleAddRule">+ {{ $t('new') }}</a>
|
<a @click="handleAddRule">+ {{ $t('new') }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -211,6 +221,10 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
:needAddHere="needAddHere"
|
:needAddHere="needAddHere"
|
||||||
v-model="ruleList"
|
v-model="ruleList"
|
||||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||||
|
:disabled="disabled"
|
||||||
/>
|
/>
|
||||||
<a-divider :style="{ margin: '10px 0' }" />
|
<a-divider :style="{ margin: '10px 0' }" />
|
||||||
<div style="width:554px">
|
<div style="width:554px">
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
v-else
|
v-else
|
||||||
v-model="ruleList"
|
v-model="ruleList"
|
||||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||||
|
:disabled="disabled"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -69,6 +71,10 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@@ -113,6 +113,10 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
const paneLengthPixel = localStorage.getItem(`${this.appName}-paneLengthPixel`)
|
||||||
|
if (paneLengthPixel) {
|
||||||
|
this.$emit('update:paneLengthPixel', Number(paneLengthPixel))
|
||||||
|
}
|
||||||
this.parentContainer = document.querySelector(`.${this.appName}`)
|
this.parentContainer = document.querySelector(`.${this.appName}`)
|
||||||
if (this.isExpanded) {
|
if (this.isExpanded) {
|
||||||
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
||||||
|
@@ -25,6 +25,7 @@ export default {
|
|||||||
deleting: 'Deleting',
|
deleting: 'Deleting',
|
||||||
deletingTip: 'Deleting, total of {total}, {successNum} succeeded, {errorNum} failed',
|
deletingTip: 'Deleting, total of {total}, {successNum} succeeded, {errorNum} failed',
|
||||||
grant: 'Grant',
|
grant: 'Grant',
|
||||||
|
revoke: 'Revoke',
|
||||||
login_at: 'Login At',
|
login_at: 'Login At',
|
||||||
logout_at: 'Logout At',
|
logout_at: 'Logout At',
|
||||||
createSuccess: 'Create Success',
|
createSuccess: 'Create Success',
|
||||||
|
@@ -25,6 +25,7 @@ export default {
|
|||||||
deleting: '正在删除',
|
deleting: '正在删除',
|
||||||
deletingTip: '正在删除,共{total}个,成功{successNum}个,失败{errorNum}个',
|
deletingTip: '正在删除,共{total}个,成功{successNum}个,失败{errorNum}个',
|
||||||
grant: '授权',
|
grant: '授权',
|
||||||
|
revoke: '回收',
|
||||||
login_at: '登录时间',
|
login_at: '登录时间',
|
||||||
logout_at: '登出时间',
|
logout_at: '登出时间',
|
||||||
createSuccess: '创建成功',
|
createSuccess: '创建成功',
|
||||||
|
@@ -205,3 +205,28 @@ export function ciTypeFilterPermissions(type_id) {
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parent_ids, child_id
|
||||||
|
export function postCiTypeInheritance(data) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ci_types/inheritance`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// parent_id, child_id
|
||||||
|
export function deleteCiTypeInheritance(data) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ci_types/inheritance`,
|
||||||
|
method: 'delete',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCITypeIcons() {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ci_types/icons',
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -14,9 +14,16 @@
|
|||||||
<script>
|
<script>
|
||||||
import EmployeeTransfer from '@/components/EmployeeTransfer'
|
import EmployeeTransfer from '@/components/EmployeeTransfer'
|
||||||
import RoleTransfer from '@/components/RoleTransfer'
|
import RoleTransfer from '@/components/RoleTransfer'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'GrantModal',
|
name: 'GrantModal',
|
||||||
components: { EmployeeTransfer, RoleTransfer },
|
components: { EmployeeTransfer, RoleTransfer },
|
||||||
|
props: {
|
||||||
|
customTitle: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
visible: false,
|
visible: false,
|
||||||
@@ -25,6 +32,9 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
title() {
|
title() {
|
||||||
|
if (this.customTitle) {
|
||||||
|
return this.customTitle
|
||||||
|
}
|
||||||
if (this.type === 'depart') {
|
if (this.type === 'depart') {
|
||||||
return this.$t('cmdb.components.grantUser')
|
return this.$t('cmdb.components.grantUser')
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,8 @@
|
|||||||
{ value: 2, label: $t('cmdb.components.customize'), layout: 'vertical' },
|
{ value: 2, label: $t('cmdb.components.customize'), layout: 'vertical' },
|
||||||
{ value: 3, label: $t('cmdb.components.none') },
|
{ value: 3, label: $t('cmdb.components.none') },
|
||||||
]"
|
]"
|
||||||
v-model="radioValue"
|
:value="radioValue"
|
||||||
|
@change="changeRadioValue"
|
||||||
>
|
>
|
||||||
<template slot="extra_2" v-if="radioValue === 2">
|
<template slot="extra_2" v-if="radioValue === 2">
|
||||||
<treeselect
|
<treeselect
|
||||||
@@ -128,6 +129,9 @@ export default {
|
|||||||
this.visible = true
|
this.visible = true
|
||||||
this.colType = colType
|
this.colType = colType
|
||||||
this.row = row
|
this.row = row
|
||||||
|
this.form = {
|
||||||
|
name: '',
|
||||||
|
}
|
||||||
if (this.colType === 'read_ci') {
|
if (this.colType === 'read_ci') {
|
||||||
await getCITypeAttributesByTypeIds({ type_ids: this.CITypeId }).then((res) => {
|
await getCITypeAttributesByTypeIds({ type_ids: this.CITypeId }).then((res) => {
|
||||||
this.canSearchPreferenceAttrList = res.attributes.filter((item) => item.value_type !== '6')
|
this.canSearchPreferenceAttrList = res.attributes.filter((item) => item.value_type !== '6')
|
||||||
@@ -149,10 +153,6 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.form = {
|
|
||||||
name: '',
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async handleOk() {
|
async handleOk() {
|
||||||
@@ -198,6 +198,13 @@ export default {
|
|||||||
}
|
}
|
||||||
this.expression = expression
|
this.expression = expression
|
||||||
},
|
},
|
||||||
|
changeRadioValue(value) {
|
||||||
|
if (this.id_filter) {
|
||||||
|
this.$message.warning(this.$t('cmdb.serviceTree.grantedByServiceTreeTips'))
|
||||||
|
} else {
|
||||||
|
this.radioValue = value
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</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' }"
|
:style="{ color: fuzzySearch ? '#2f54eb' : '', cursor: 'pointer' }"
|
||||||
@click="emitRefresh"
|
@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">
|
<template slot="title">
|
||||||
{{ $t('cmdb.components.ciSearchTips') }}
|
{{ $t('cmdb.components.ciSearchTips') }}
|
||||||
</template>
|
</template>
|
||||||
@@ -97,6 +97,7 @@
|
|||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
<a-space>
|
<a-space>
|
||||||
|
<slot name="extraContent"></slot>
|
||||||
<a-button @click="reset" size="small">{{ $t('reset') }}</a-button>
|
<a-button @click="reset" size="small">{{ $t('reset') }}</a-button>
|
||||||
<a-tooltip :title="$t('cmdb.components.attributeDesc')" v-if="type === 'relationView'">
|
<a-tooltip :title="$t('cmdb.components.attributeDesc')" v-if="type === 'relationView'">
|
||||||
<a
|
<a
|
||||||
@@ -145,7 +146,7 @@ export default {
|
|||||||
selectedRowKeys: {
|
selectedRowKeys: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -179,13 +180,21 @@ export default {
|
|||||||
this.fuzzySearch = ''
|
this.fuzzySearch = ''
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
inject: ['setPreferenceSearchCurrent'],
|
inject: {
|
||||||
|
setPreferenceSearchCurrent: {
|
||||||
|
from: 'setPreferenceSearchCurrent',
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.type === 'resourceSearch') {
|
if (this.type === 'resourceSearch') {
|
||||||
this.getCITypeGroups()
|
this.getCITypeGroups()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// toggleAdvanced() {
|
||||||
|
// this.advanced = !this.advanced
|
||||||
|
// },
|
||||||
getCITypeGroups() {
|
getCITypeGroups() {
|
||||||
getCITypeGroups({ need_other: true }).then((res) => {
|
getCITypeGroups({ need_other: true }).then((res) => {
|
||||||
this.ciTypeGroup = res
|
this.ciTypeGroup = res
|
||||||
@@ -234,7 +243,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
emitRefresh() {
|
emitRefresh() {
|
||||||
|
if (this.setPreferenceSearchCurrent) {
|
||||||
this.setPreferenceSearchCurrent(null)
|
this.setPreferenceSearchCurrent(null)
|
||||||
|
}
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$emit('refresh', true)
|
this.$emit('refresh', true)
|
||||||
})
|
})
|
||||||
|
@@ -88,7 +88,9 @@ export default {
|
|||||||
} catch {}
|
} catch {}
|
||||||
const headers = {}
|
const headers = {}
|
||||||
this.$refs.Header.headers.forEach((item) => {
|
this.$refs.Header.headers.forEach((item) => {
|
||||||
|
if (item.key) {
|
||||||
headers[item.key] = item.value
|
headers[item.key] = item.value
|
||||||
|
}
|
||||||
})
|
})
|
||||||
let authorization = {}
|
let authorization = {}
|
||||||
const type = this.$refs.Authorization.authorizationType
|
const type = this.$refs.Authorization.authorizationType
|
||||||
|
@@ -19,6 +19,7 @@ const cmdb_en = {
|
|||||||
operationHistory: 'Operation Audit',
|
operationHistory: 'Operation Audit',
|
||||||
relationType: 'Relation Type',
|
relationType: 'Relation Type',
|
||||||
ad: 'AutoDiscovery',
|
ad: 'AutoDiscovery',
|
||||||
|
cidetail: 'CI Detail'
|
||||||
},
|
},
|
||||||
ciType: {
|
ciType: {
|
||||||
ciType: 'CIType',
|
ciType: 'CIType',
|
||||||
@@ -45,8 +46,9 @@ const cmdb_en = {
|
|||||||
selectDefaultOrderAttr: 'Select default sorting attributes',
|
selectDefaultOrderAttr: 'Select default sorting attributes',
|
||||||
asec: 'Forward order',
|
asec: 'Forward order',
|
||||||
desc: 'Reverse order',
|
desc: 'Reverse order',
|
||||||
uniqueKey: 'Uniquely Identifies',
|
uniqueKey: 'Unique Identifies',
|
||||||
uniqueKeySelect: 'Please select a unique identifier',
|
uniqueKeySelect: 'Please select a unique identifier',
|
||||||
|
uniqueKeyTips: 'json/password/computed/choice can not be unique identifies',
|
||||||
notfound: 'Can\'t find what you want?',
|
notfound: 'Can\'t find what you want?',
|
||||||
cannotDeleteGroupTips: 'There is data under this group and cannot be deleted!',
|
cannotDeleteGroupTips: 'There is data under this group and cannot be deleted!',
|
||||||
confirmDeleteGroup: 'Are you sure you want to delete group [{groupName}]?',
|
confirmDeleteGroup: 'Are you sure you want to delete group [{groupName}]?',
|
||||||
@@ -175,7 +177,12 @@ const cmdb_en = {
|
|||||||
time: 'Time',
|
time: 'Time',
|
||||||
json: 'JSON',
|
json: 'JSON',
|
||||||
event: 'Event',
|
event: 'Event',
|
||||||
reg: 'Regex'
|
reg: 'Regex',
|
||||||
|
isInherit: 'Inherit',
|
||||||
|
inheritType: 'Inherit Type',
|
||||||
|
inheritTypePlaceholder: 'Please select inherit types',
|
||||||
|
inheritFrom: 'inherit from {name}',
|
||||||
|
groupInheritFrom: 'Please go to the {name} for modification'
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
unselectAttributes: 'Unselected',
|
unselectAttributes: 'Unselected',
|
||||||
@@ -217,7 +224,7 @@ const cmdb_en = {
|
|||||||
pleaseSearch: 'Please search',
|
pleaseSearch: 'Please search',
|
||||||
conditionFilter: 'Conditional filtering',
|
conditionFilter: 'Conditional filtering',
|
||||||
attributeDesc: 'Attribute Description',
|
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*',
|
ciSearchTips2: 'For example: q=hostname:*0.0.0.0*',
|
||||||
subCIType: 'Subscription CIType',
|
subCIType: 'Subscription CIType',
|
||||||
already: 'already',
|
already: 'already',
|
||||||
@@ -460,13 +467,15 @@ const cmdb_en = {
|
|||||||
tips3: 'Please select the fields that need to be modified',
|
tips3: 'Please select the fields that need to be modified',
|
||||||
tips4: 'At least one field must be selected',
|
tips4: 'At least one field must be selected',
|
||||||
tips5: 'Search name | alias',
|
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',
|
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',
|
tips8: 'Multiple values, such as intranet IP',
|
||||||
tips9: 'For front-end only',
|
tips9: 'For front-end only',
|
||||||
tips10: 'Other attributes of the CIType are computed using expressions\n\nA code snippet computes the returned value.',
|
tips10: 'Other attributes of the CIType are computed using expressions\n\nA code snippet computes the returned value.',
|
||||||
newUpdateField: 'Add a Attribute',
|
newUpdateField: 'Add a Attribute',
|
||||||
attributeSettings: 'Attribute Settings',
|
attributeSettings: 'Attribute Settings',
|
||||||
|
share: 'Share',
|
||||||
|
noPermission: 'No Permission'
|
||||||
},
|
},
|
||||||
serviceTree: {
|
serviceTree: {
|
||||||
deleteNode: 'Delete Node',
|
deleteNode: 'Delete Node',
|
||||||
@@ -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!',
|
alert1: 'The administrator has not configured the ServiceTree(relation view), or you do not have permission to access it!',
|
||||||
copyFailed: 'Copy failed',
|
copyFailed: 'Copy failed',
|
||||||
deleteRelationConfirm: 'Confirm to remove selected {name} from current relationship?',
|
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: {
|
tree: {
|
||||||
tips1: 'Please go to Preference page first to complete your subscription!',
|
tips1: 'Please go to Preference page first to complete your subscription!',
|
||||||
|
@@ -19,6 +19,7 @@ const cmdb_zh = {
|
|||||||
operationHistory: '操作审计',
|
operationHistory: '操作审计',
|
||||||
relationType: '关系类型',
|
relationType: '关系类型',
|
||||||
ad: '自动发现',
|
ad: '自动发现',
|
||||||
|
cidetail: 'CI 详情'
|
||||||
},
|
},
|
||||||
ciType: {
|
ciType: {
|
||||||
ciType: '模型',
|
ciType: '模型',
|
||||||
@@ -47,6 +48,7 @@ const cmdb_zh = {
|
|||||||
desc: '倒序',
|
desc: '倒序',
|
||||||
uniqueKey: '唯一标识',
|
uniqueKey: '唯一标识',
|
||||||
uniqueKeySelect: '请选择唯一标识',
|
uniqueKeySelect: '请选择唯一标识',
|
||||||
|
uniqueKeyTips: 'json、密码、计算属性、预定义值属性不能作为唯一标识',
|
||||||
notfound: '找不到想要的?',
|
notfound: '找不到想要的?',
|
||||||
cannotDeleteGroupTips: '该分组下有数据, 不能删除!',
|
cannotDeleteGroupTips: '该分组下有数据, 不能删除!',
|
||||||
confirmDeleteGroup: '确定要删除分组 【{groupName}】 吗?',
|
confirmDeleteGroup: '确定要删除分组 【{groupName}】 吗?',
|
||||||
@@ -175,7 +177,12 @@ const cmdb_zh = {
|
|||||||
time: '时间',
|
time: '时间',
|
||||||
json: 'JSON',
|
json: 'JSON',
|
||||||
event: '事件',
|
event: '事件',
|
||||||
reg: '正则校验'
|
reg: '正则校验',
|
||||||
|
isInherit: '是否继承',
|
||||||
|
inheritType: '继承模型',
|
||||||
|
inheritTypePlaceholder: '请选择继承模型(多选)',
|
||||||
|
inheritFrom: '属性继承自{name}',
|
||||||
|
groupInheritFrom: '请至{name}进行修改'
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
unselectAttributes: '未选属性',
|
unselectAttributes: '未选属性',
|
||||||
@@ -217,7 +224,7 @@ const cmdb_zh = {
|
|||||||
pleaseSearch: '请查找',
|
pleaseSearch: '请查找',
|
||||||
conditionFilter: '条件过滤',
|
conditionFilter: '条件过滤',
|
||||||
attributeDesc: '属性说明',
|
attributeDesc: '属性说明',
|
||||||
ciSearchTips: '1. json属性不能搜索<br />2. 搜索内容包括逗号, 则需转义 ,<br />3. 只搜索索引属性, 非索引属性使用条件过滤',
|
ciSearchTips: '1. json、密码、链接属性不能搜索\n2. 搜索内容包括逗号, 则需转义\n3. 只搜索索引属性, 非索引属性使用条件过滤',
|
||||||
ciSearchTips2: '例: q=hostname:*0.0.0.0*',
|
ciSearchTips2: '例: q=hostname:*0.0.0.0*',
|
||||||
subCIType: '订阅模型',
|
subCIType: '订阅模型',
|
||||||
already: '已',
|
already: '已',
|
||||||
@@ -459,13 +466,15 @@ const cmdb_zh = {
|
|||||||
tips3: '请选择需要修改的字段',
|
tips3: '请选择需要修改的字段',
|
||||||
tips4: '必须至少选择一个字段',
|
tips4: '必须至少选择一个字段',
|
||||||
tips5: '搜索 名称 | 别名',
|
tips5: '搜索 名称 | 别名',
|
||||||
tips6: '加快检索, 可以全文搜索, 无需使用条件过滤\n\n json目前不支持建索引 \n\n文本字符长度超过190不能建索引',
|
tips6: '加快检索, 可以全文搜索, 无需使用条件过滤\n\n json、链接、密码目前不支持建索引 \n\n文本字符长度超过190不能建索引',
|
||||||
tips7: '表现形式是下拉框, 值必须在预定义值里',
|
tips7: '表现形式是下拉框, 值必须在预定义值里',
|
||||||
tips8: '多值, 比如内网IP',
|
tips8: '多值, 比如内网IP',
|
||||||
tips9: '仅针对前端',
|
tips9: '仅针对前端',
|
||||||
tips10: '模型的其他属性通过表达式的方式计算出来\n\n一个代码片段计算返回的值',
|
tips10: '模型的其他属性通过表达式的方式计算出来\n\n一个代码片段计算返回的值',
|
||||||
newUpdateField: '新增修改字段',
|
newUpdateField: '新增修改字段',
|
||||||
attributeSettings: '字段设置',
|
attributeSettings: '字段设置',
|
||||||
|
share: '分享',
|
||||||
|
noPermission: '暂无权限'
|
||||||
},
|
},
|
||||||
serviceTree: {
|
serviceTree: {
|
||||||
deleteNode: '删除节点',
|
deleteNode: '删除节点',
|
||||||
@@ -474,6 +483,16 @@ const cmdb_zh = {
|
|||||||
alert1: '管理员 还未配置业务关系, 或者你无权限访问!',
|
alert1: '管理员 还未配置业务关系, 或者你无权限访问!',
|
||||||
copyFailed: '复制失败',
|
copyFailed: '复制失败',
|
||||||
deleteRelationConfirm: '确认将选中的 {name} 从当前关系中删除?',
|
deleteRelationConfirm: '确认将选中的 {name} 从当前关系中删除?',
|
||||||
|
batch: '批量操作',
|
||||||
|
grantTitle: '授权(查看权限)',
|
||||||
|
userPlaceholder: '请选择用户',
|
||||||
|
rolePlaceholder: '请选择角色',
|
||||||
|
grantedByServiceTree: '服务树授权:',
|
||||||
|
grantedByServiceTreeTips: '请先在服务树里删掉节点授权',
|
||||||
|
peopleHasRead: '当前有查看权限的人员:',
|
||||||
|
authorizationPolicy: '实例授权策略:',
|
||||||
|
idAuthorizationPolicy: '按节点授权的:',
|
||||||
|
view: '查看权限'
|
||||||
},
|
},
|
||||||
tree: {
|
tree: {
|
||||||
tips1: '请先到 我的订阅 页面完成订阅!',
|
tips1: '请先到 我的订阅 页面完成订阅!',
|
||||||
|
@@ -56,6 +56,13 @@ const genCmdbRoutes = async () => {
|
|||||||
meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc-selected', keepAlive: false },
|
meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc-selected', keepAlive: false },
|
||||||
component: () => import('../views/discoveryCI/index.vue')
|
component: () => import('../views/discoveryCI/index.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: `/cmdb/cidetail/:typeId/:ciId`,
|
||||||
|
name: 'cmdb_ci_detail',
|
||||||
|
hidden: true,
|
||||||
|
meta: { title: 'cmdb.menu.cidetail', keepAlive: false },
|
||||||
|
component: () => import('../views/ci/ciDetailPage.vue')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/cmdb/disabled2',
|
path: '/cmdb/disabled2',
|
||||||
name: 'cmdb_disabled2',
|
name: 'cmdb_disabled2',
|
||||||
|
@@ -179,3 +179,13 @@ export const downloadExcel = (data, fileName = `${moment().format('YYYY-MM-DD HH
|
|||||||
// STEP 4: Write Excel file to browser #导出
|
// STEP 4: Write Excel file to browser #导出
|
||||||
XLSXS.writeFile(wb, fileName + '.xlsx')
|
XLSXS.writeFile(wb, fileName + '.xlsx')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getAllParentNodesLabel = (node, label) => {
|
||||||
|
if (node.parentNode) {
|
||||||
|
return getAllParentNodesLabel(node.parentNode, `${node.parentNode.label}-${label}`)
|
||||||
|
}
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
export const getTreeSelectLabel = (node) => {
|
||||||
|
return `${getAllParentNodesLabel(node, node.label)}`
|
||||||
|
}
|
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>
|
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</SearchForm>
|
</SearchForm>
|
||||||
<CiDetail ref="detail" :typeId="typeId" />
|
<CiDetailDrawer ref="detail" :typeId="typeId" />
|
||||||
<ops-table
|
<ops-table
|
||||||
:id="`cmdb-ci-${typeId}`"
|
:id="`cmdb-ci-${typeId}`"
|
||||||
border
|
border
|
||||||
@@ -297,7 +297,7 @@ import router, { resetRouter } from '@/router'
|
|||||||
|
|
||||||
import SearchForm from '../../components/searchForm/SearchForm.vue'
|
import SearchForm from '../../components/searchForm/SearchForm.vue'
|
||||||
import CreateInstanceForm from './modules/CreateInstanceForm'
|
import CreateInstanceForm from './modules/CreateInstanceForm'
|
||||||
import CiDetail from './modules/CiDetail'
|
import CiDetailDrawer from './modules/ciDetailDrawer.vue'
|
||||||
import EditAttrsPopover from './modules/editAttrsPopover'
|
import EditAttrsPopover from './modules/editAttrsPopover'
|
||||||
import JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
|
import JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
|
||||||
import { searchCI, updateCI, deleteCI } from '@/modules/cmdb/api/ci'
|
import { searchCI, updateCI, deleteCI } from '@/modules/cmdb/api/ci'
|
||||||
@@ -320,7 +320,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
SearchForm,
|
SearchForm,
|
||||||
CreateInstanceForm,
|
CreateInstanceForm,
|
||||||
CiDetail,
|
CiDetailDrawer,
|
||||||
JsonEditor,
|
JsonEditor,
|
||||||
PasswordField,
|
PasswordField,
|
||||||
EditAttrsPopover,
|
EditAttrsPopover,
|
||||||
|
@@ -220,6 +220,7 @@ export default {
|
|||||||
if (otherGroupAttr.length) {
|
if (otherGroupAttr.length) {
|
||||||
_attributesByGroup.push({ id: -1, name: this.$t('other'), attributes: otherGroupAttr })
|
_attributesByGroup.push({ id: -1, name: this.$t('other'), attributes: otherGroupAttr })
|
||||||
}
|
}
|
||||||
|
console.log(otherGroupAttr, _attributesByGroup)
|
||||||
this.attributesByGroup = _attributesByGroup
|
this.attributesByGroup = _attributesByGroup
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -296,6 +297,38 @@ export default {
|
|||||||
_this.$emit('reload', { ci_id: res.ci_id })
|
_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() {
|
handleClose() {
|
||||||
this.visible = false
|
this.visible = false
|
||||||
@@ -363,6 +396,9 @@ export default {
|
|||||||
this.batchUpdateLists.splice(_idx, 1)
|
this.batchUpdateLists.splice(_idx, 1)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// filterOption(input, option) {
|
||||||
|
// return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
|
// },
|
||||||
handleFocusInput(e, attr) {
|
handleFocusInput(e, attr) {
|
||||||
console.log(attr)
|
console.log(attr)
|
||||||
const _tempFind = this.attributeList.find((item) => item.name === attr.name)
|
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"
|
class="ops-stripe-table"
|
||||||
>
|
>
|
||||||
<template #operation_default="{ row }">
|
<template #operation_default="{ row }">
|
||||||
<a-popconfirm arrowPointAtCenter :title="$t('cmdb.ci.confirmDeleteRelation')" @confirm="deleteRelation(row._id, ciId)">
|
<a-popconfirm
|
||||||
|
arrowPointAtCenter
|
||||||
|
:title="$t('cmdb.ci.confirmDeleteRelation')"
|
||||||
|
@confirm="deleteRelation(row._id, ciId)"
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
:disabled="!canEdit[parent.id]"
|
:disabled="!canEdit[parent.id]"
|
||||||
:style="{
|
:style="{
|
||||||
@@ -82,7 +86,11 @@
|
|||||||
class="ops-stripe-table"
|
class="ops-stripe-table"
|
||||||
>
|
>
|
||||||
<template #operation_default="{ row }">
|
<template #operation_default="{ row }">
|
||||||
<a-popconfirm arrowPointAtCenter :title="$t('cmdb.ci.confirmDeleteRelation')" @confirm="deleteRelation(ciId, row._id)">
|
<a-popconfirm
|
||||||
|
arrowPointAtCenter
|
||||||
|
:title="$t('cmdb.ci.confirmDeleteRelation')"
|
||||||
|
@confirm="deleteRelation(ciId, row._id)"
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
:disabled="!canEdit[child.id]"
|
:disabled="!canEdit[child.id]"
|
||||||
:style="{
|
:style="{
|
||||||
@@ -416,6 +424,7 @@ export default {
|
|||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.ci-detail-relation {
|
.ci-detail-relation {
|
||||||
|
height: 100%;
|
||||||
.ci-detail-relation-table-title {
|
.ci-detail-relation-table-title {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div
|
<div
|
||||||
id="ci-detail-relation-topo"
|
id="ci-detail-relation-topo"
|
||||||
class="ci-detail-relation-topo"
|
class="ci-detail-relation-topo"
|
||||||
:style="{ width: '100%', marginTop: '20px', height: 'calc(100vh - 136px)' }"
|
:style="{ width: '100%', marginTop: '20px', height: 'calc(100% - 44px)' }"
|
||||||
></div>
|
></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -1,23 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<CustomDrawer
|
<div :style="{ height: '100%' }">
|
||||||
width="80%"
|
<a-tabs v-if="hasPermission" class="ci-detail-tab" v-model="activeTabKey" @change="changeTab">
|
||||||
placement="left"
|
<a @click="shareCi" slot="tabBarExtraContent" :style="{ marginRight: '24px' }">
|
||||||
@close="
|
<a-icon type="share-alt" />
|
||||||
() => {
|
{{ $t('cmdb.ci.share') }}
|
||||||
visible = false
|
</a>
|
||||||
}
|
|
||||||
"
|
|
||||||
:visible="visible"
|
|
||||||
:hasTitle="false"
|
|
||||||
:hasFooter="false"
|
|
||||||
:bodyStyle="{ padding: 0, height: '100vh' }"
|
|
||||||
wrapClassName="ci-detail"
|
|
||||||
destroyOnClose
|
|
||||||
>
|
|
||||||
<a-tabs v-model="activeTabKey" @change="changeTab">
|
|
||||||
<a-tab-pane key="tab_1">
|
<a-tab-pane key="tab_1">
|
||||||
<span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span>
|
<span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span>
|
||||||
<div :style="{ maxHeight: `${windowHeight - 44}px`, overflow: 'auto', padding: '24px' }" class="ci-detail-attr">
|
<div class="ci-detail-attr">
|
||||||
<el-descriptions
|
<el-descriptions
|
||||||
:title="group.name || $t('other')"
|
:title="group.name || $t('other')"
|
||||||
:key="group.name"
|
:key="group.name"
|
||||||
@@ -37,18 +27,18 @@
|
|||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tab_2">
|
<a-tab-pane key="tab_2">
|
||||||
<span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
|
<span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
|
||||||
<div :style="{ padding: '24px' }">
|
<div :style="{ height: '100%', padding: '24px', overflow: 'auto' }">
|
||||||
<CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
|
<CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
|
||||||
</div>
|
</div>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tab_3">
|
<a-tab-pane key="tab_3">
|
||||||
<span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span>
|
<span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span>
|
||||||
<div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }">
|
<div :style="{ padding: '24px', height: '100%' }">
|
||||||
<vxe-table
|
<vxe-table
|
||||||
ref="xTable"
|
ref="xTable"
|
||||||
:data="ciHistory"
|
:data="ciHistory"
|
||||||
size="small"
|
size="small"
|
||||||
:max-height="`${windowHeight - 94}px`"
|
height="auto"
|
||||||
:span-method="mergeRowMethod"
|
:span-method="mergeRowMethod"
|
||||||
border
|
border
|
||||||
:scroll-y="{ enabled: false }"
|
:scroll-y="{ enabled: false }"
|
||||||
@@ -88,12 +78,22 @@
|
|||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tab_4">
|
<a-tab-pane key="tab_4">
|
||||||
<span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span>
|
<span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span>
|
||||||
<div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }">
|
<div :style="{ padding: '24px', height: '100%' }">
|
||||||
<TriggerTable :ci_id="ci._id" />
|
<TriggerTable :ci_id="ci._id" />
|
||||||
</div>
|
</div>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</CustomDrawer>
|
<a-empty
|
||||||
|
v-else
|
||||||
|
:image-style="{
|
||||||
|
height: '100px',
|
||||||
|
}"
|
||||||
|
:style="{ paddingTop: '20%' }"
|
||||||
|
>
|
||||||
|
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||||
|
<span slot="description"> {{ $t('cmdb.ci.noPermission') }} </span>
|
||||||
|
</a-empty>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -105,8 +105,8 @@ import { getCIById } from '@/modules/cmdb/api/ci'
|
|||||||
import CiDetailAttrContent from './ciDetailAttrContent.vue'
|
import CiDetailAttrContent from './ciDetailAttrContent.vue'
|
||||||
import CiDetailRelation from './ciDetailRelation.vue'
|
import CiDetailRelation from './ciDetailRelation.vue'
|
||||||
import TriggerTable from '../../operation_history/modules/triggerTable.vue'
|
import TriggerTable from '../../operation_history/modules/triggerTable.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
name: 'CiDetailTab',
|
||||||
components: {
|
components: {
|
||||||
ElDescriptions: Descriptions,
|
ElDescriptions: Descriptions,
|
||||||
ElDescriptionsItem: DescriptionsItem,
|
ElDescriptionsItem: DescriptionsItem,
|
||||||
@@ -126,7 +126,6 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
visible: false,
|
|
||||||
ci: {},
|
ci: {},
|
||||||
attributeGroups: [],
|
attributeGroups: [],
|
||||||
activeTabKey: 'tab_1',
|
activeTabKey: 'tab_1',
|
||||||
@@ -134,13 +133,13 @@ export default {
|
|||||||
ciHistory: [],
|
ciHistory: [],
|
||||||
ciId: null,
|
ciId: null,
|
||||||
ci_types: [],
|
ci_types: [],
|
||||||
|
hasPermission: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
windowHeight() {
|
windowHeight() {
|
||||||
return this.$store.state.windowHeight
|
return this.$store.state.windowHeight
|
||||||
},
|
},
|
||||||
|
|
||||||
operateTypeMap() {
|
operateTypeMap() {
|
||||||
return {
|
return {
|
||||||
0: this.$t('new'),
|
0: this.$t('new'),
|
||||||
@@ -156,10 +155,22 @@ export default {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inject: ['reload', 'handleSearch', 'attrList'],
|
inject: {
|
||||||
|
reload: {
|
||||||
|
from: 'reload',
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
handleSearch: {
|
||||||
|
from: 'handleSearch',
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
attrList: {
|
||||||
|
from: 'attrList',
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
|
async create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
|
||||||
this.visible = true
|
|
||||||
this.activeTabKey = activeTabKey
|
this.activeTabKey = activeTabKey
|
||||||
if (activeTabKey === 'tab_2') {
|
if (activeTabKey === 'tab_2') {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@@ -167,12 +178,14 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.ciId = ciId
|
this.ciId = ciId
|
||||||
|
await this.getCI()
|
||||||
|
if (this.hasPermission) {
|
||||||
this.getAttributes()
|
this.getAttributes()
|
||||||
this.getCI()
|
|
||||||
this.getCIHistory()
|
this.getCIHistory()
|
||||||
getCITypes().then((res) => {
|
getCITypes().then((res) => {
|
||||||
this.ci_types = res.ci_types
|
this.ci_types = res.ci_types
|
||||||
})
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getAttributes() {
|
getAttributes() {
|
||||||
getCITypeGroupById(this.typeId, { need_other: 1 })
|
getCITypeGroupById(this.typeId, { need_other: 1 })
|
||||||
@@ -181,11 +194,14 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch((e) => {})
|
.catch((e) => {})
|
||||||
},
|
},
|
||||||
getCI() {
|
async getCI() {
|
||||||
getCIById(this.ciId)
|
await getCIById(this.ciId)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
// this.ci = res.ci
|
if (res.result.length) {
|
||||||
this.ci = res.result[0]
|
this.ci = res.result[0]
|
||||||
|
} else {
|
||||||
|
this.hasPermission = false
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => {})
|
.catch((e) => {})
|
||||||
},
|
},
|
||||||
@@ -270,10 +286,14 @@ export default {
|
|||||||
// 修改的字段为树形视图订阅的字段 则全部reload
|
// 修改的字段为树形视图订阅的字段 则全部reload
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (_find) {
|
if (_find) {
|
||||||
|
if (this.reload) {
|
||||||
this.reload()
|
this.reload()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (this.handleSearch) {
|
||||||
this.handleSearch()
|
this.handleSearch()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, 500)
|
}, 500)
|
||||||
},
|
},
|
||||||
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
|
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
|
||||||
@@ -303,23 +323,49 @@ export default {
|
|||||||
// 修改的字段为树形视图订阅的字段 则全部reload
|
// 修改的字段为树形视图订阅的字段 则全部reload
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (_find) {
|
if (_find) {
|
||||||
|
if (this.reload) {
|
||||||
this.reload()
|
this.reload()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (this.handleSearch) {
|
||||||
this.handleSearch()
|
this.handleSearch()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, 500)
|
}, 500)
|
||||||
},
|
},
|
||||||
|
shareCi() {
|
||||||
|
const text = `${document.location.host}/cmdb/cidetail/${this.typeId}/${this.ciId}`
|
||||||
|
this.$copyText(text)
|
||||||
|
.then(() => {
|
||||||
|
this.$message.success(this.$t('copySuccess'))
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.$message.error(this.$t('cmdb.ci.copyFailed'))
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.ci-detail {
|
.ci-detail-tab {
|
||||||
|
height: 100%;
|
||||||
|
.ant-tabs-content {
|
||||||
|
height: calc(100% - 45px);
|
||||||
|
.ant-tabs-tabpane {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
.ant-tabs-bar {
|
.ant-tabs-bar {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
.ant-tabs-extra-content {
|
||||||
|
line-height: 44px;
|
||||||
|
}
|
||||||
.ci-detail-attr {
|
.ci-detail-attr {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 24px;
|
||||||
.el-descriptions-item__content {
|
.el-descriptions-item__content {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
&:hover a {
|
&:hover a {
|
@@ -25,7 +25,9 @@
|
|||||||
initialValue:
|
initialValue:
|
||||||
attr.default && attr.default.default
|
attr.default && attr.default.default
|
||||||
? attr.is_list
|
? attr.is_list
|
||||||
? attr.default.default.split(',')
|
? Array.isArray(attr.default.default)
|
||||||
|
? attr.default.default
|
||||||
|
: attr.default.default.split(',')
|
||||||
: attr.default.default
|
: attr.default.default
|
||||||
: null,
|
: null,
|
||||||
},
|
},
|
||||||
|
@@ -1,7 +1,11 @@
|
|||||||
<template>
|
<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-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" />
|
<ValueTypeIcon :attr="property" />
|
||||||
</div>
|
</div>
|
||||||
<div :class="{ 'attribute-card-content-inner': true, 'attribute-card-name-required': property.is_required }">
|
<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>
|
<a @click="openTrigger"><ops-icon type="ops-trigger"/></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</a-tooltip>
|
||||||
|
|
||||||
<div class="attribute-card-footer">
|
<div class="attribute-card-footer">
|
||||||
<a-popover
|
<a-popover
|
||||||
trigger="click"
|
trigger="click"
|
||||||
@@ -51,7 +57,7 @@
|
|||||||
</a-space>
|
</a-space>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
|
|
||||||
<a-space class="attribute-card-operation">
|
<a-space class="attribute-card-operation" v-if="!inherited">
|
||||||
<a v-if="!isStore"><a-icon type="edit" @click="handleEdit"/></a>
|
<a v-if="!isStore"><a-icon type="edit" @click="handleEdit"/></a>
|
||||||
<a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')">
|
<a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')">
|
||||||
<a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a>
|
<a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a>
|
||||||
@@ -140,6 +146,9 @@ export default {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
inherited() {
|
||||||
|
return this.property.inherited || false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getPropertyStyle,
|
getPropertyStyle,
|
||||||
@@ -211,13 +220,15 @@ export default {
|
|||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
cursor: move;
|
|
||||||
background: #ffffff !important;
|
background: #ffffff !important;
|
||||||
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
|
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
}
|
}
|
||||||
|
.handle {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
.attribute-card-content-inner {
|
.attribute-card-content-inner {
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
@@ -269,6 +280,12 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.attribute-card-inherited {
|
||||||
|
background: #f3f4f7;
|
||||||
|
.attribute-card-footer {
|
||||||
|
background: #eaedf3;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.attribute-card-footer-popover {
|
.attribute-card-footer-popover {
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<a-modal v-model="addGroupModal" :title="$t('cmdb.ciType.addGroup')" @cancel="handleCancelCreateGroup" @ok="handleCreateGroup">
|
<a-modal
|
||||||
|
v-model="addGroupModal"
|
||||||
|
:title="$t('cmdb.ciType.addGroup')"
|
||||||
|
@cancel="handleCancelCreateGroup"
|
||||||
|
@ok="handleCreateGroup"
|
||||||
|
>
|
||||||
<span>
|
<span>
|
||||||
<a-form-item :label="$t('name')" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
<a-form-item :label="$t('name')" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||||
<a-input type="text" v-model.trim="newGroupName" />
|
<a-input type="text" v-model.trim="newGroupName" />
|
||||||
@@ -9,21 +14,12 @@
|
|||||||
</a-modal>
|
</a-modal>
|
||||||
<div class="ci-types-attributes" :style="{ maxHeight: `${windowHeight - 104}px` }">
|
<div class="ci-types-attributes" :style="{ maxHeight: `${windowHeight - 104}px` }">
|
||||||
<a-space style="margin-bottom: 10px">
|
<a-space style="margin-bottom: 10px">
|
||||||
<a-button
|
<a-button type="primary" @click="handleAddGroup" size="small" class="ops-button-primary" icon="plus">{{
|
||||||
type="primary"
|
$t('cmdb.ciType.group')
|
||||||
@click="handleAddGroup"
|
}}</a-button>
|
||||||
size="small"
|
<a-button type="primary" @click="handleOpenUniqueConstraint" size="small" class="ops-button-primary">{{
|
||||||
class="ops-button-primary"
|
$t('cmdb.ciType.uniqueConstraint')
|
||||||
icon="plus"
|
}}</a-button>
|
||||||
>{{ $t('cmdb.ciType.group') }}</a-button
|
|
||||||
>
|
|
||||||
<a-button
|
|
||||||
type="primary"
|
|
||||||
@click="handleOpenUniqueConstraint"
|
|
||||||
size="small"
|
|
||||||
class="ops-button-primary"
|
|
||||||
>{{ $t('cmdb.ciType.uniqueConstraint') }}</a-button
|
|
||||||
>
|
|
||||||
</a-space>
|
</a-space>
|
||||||
<div :key="CITypeGroup.id" v-for="(CITypeGroup, index) in CITypeGroups">
|
<div :key="CITypeGroup.id" v-for="(CITypeGroup, index) in CITypeGroups">
|
||||||
<div>
|
<div>
|
||||||
@@ -33,7 +29,11 @@
|
|||||||
>
|
>
|
||||||
<span style="font-weight:700">{{ CITypeGroup.name }}</span>
|
<span style="font-weight:700">{{ CITypeGroup.name }}</span>
|
||||||
<span style="color: #c3cdd7;margin:0 5px;">({{ CITypeGroup.attributes.length }})</span>
|
<span style="color: #c3cdd7;margin:0 5px;">({{ CITypeGroup.attributes.length }})</span>
|
||||||
<a @click="handleEditGroupName(index, CITypeGroup)">
|
|
||||||
|
<a v-if="!CITypeGroup.inherited" @click="handleEditGroupName(index, CITypeGroup)">
|
||||||
|
<a-icon type="edit" />
|
||||||
|
</a>
|
||||||
|
<a v-else :style="{ cursor: 'not-allowed', color: 'gray' }">
|
||||||
<a-icon type="edit" />
|
<a-icon type="edit" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -71,7 +71,13 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ $t('cmdb.ciType.deleteGroup') }}</template>
|
<template slot="title">{{ $t('cmdb.ciType.deleteGroup') }}</template>
|
||||||
<a style="color:red;"><a-icon type="delete" @click="handleDeleteGroup(CITypeGroup)"/></a>
|
<a
|
||||||
|
:style="{ color: CITypeGroup.inherited ? 'gray' : 'red' }"
|
||||||
|
:disabled="CITypeGroup.inherited"
|
||||||
|
><a-icon
|
||||||
|
type="delete"
|
||||||
|
@click="handleDeleteGroup(CITypeGroup)"
|
||||||
|
/></a>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,7 +88,7 @@
|
|||||||
@start="drag = true"
|
@start="drag = true"
|
||||||
@change="
|
@change="
|
||||||
(e) => {
|
(e) => {
|
||||||
handleChange(e, CITypeGroup.id)
|
handleChange(e, CITypeGroup.name)
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
:filter="'.filter-empty'"
|
:filter="'.filter-empty'"
|
||||||
@@ -124,7 +130,7 @@
|
|||||||
@start="drag = true"
|
@start="drag = true"
|
||||||
@change="
|
@change="
|
||||||
(e) => {
|
(e) => {
|
||||||
handleChange(e, -1)
|
handleChange(e, null)
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
:animation="300"
|
:animation="300"
|
||||||
@@ -144,18 +150,18 @@
|
|||||||
</draggable>
|
</draggable>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<attribute-edit-form
|
<AttributeEditForm
|
||||||
ref="attributeEditForm"
|
ref="attributeEditForm"
|
||||||
:CITypeId="CITypeId"
|
:CITypeId="CITypeId"
|
||||||
:CITypeName="CITypeName"
|
:CITypeName="CITypeName"
|
||||||
@ok="handleOk"
|
@ok="handleOk"
|
||||||
></attribute-edit-form>
|
></AttributeEditForm>
|
||||||
<new-ci-type-attr-modal
|
<NewCiTypeAttrModal
|
||||||
ref="newCiTypeAttrModal"
|
ref="newCiTypeAttrModal"
|
||||||
:CITypeId="CITypeId"
|
:CITypeId="CITypeId"
|
||||||
:linked-ids="linkedIds"
|
:linked-ids="linkedIds"
|
||||||
@ok="handleOk"
|
@ok="handleOk"
|
||||||
></new-ci-type-attr-modal>
|
></NewCiTypeAttrModal>
|
||||||
<UniqueConstraint ref="uniqueConstraint" :CITypeId="CITypeId" />
|
<UniqueConstraint ref="uniqueConstraint" :CITypeId="CITypeId" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -347,8 +353,8 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleMoveGroup(beforeIndex, afterIndex) {
|
handleMoveGroup(beforeIndex, afterIndex) {
|
||||||
const fromGroupId = this.CITypeGroups[beforeIndex].id
|
const fromGroupId = this.CITypeGroups[beforeIndex].name
|
||||||
const toGroupId = this.CITypeGroups[afterIndex].id
|
const toGroupId = this.CITypeGroups[afterIndex].name
|
||||||
transferCITypeGroupIndex(this.CITypeId, { from: fromGroupId, to: toGroupId }).then((res) => {
|
transferCITypeGroupIndex(this.CITypeId, { from: fromGroupId, to: toGroupId }).then((res) => {
|
||||||
this.$message.success(this.$t('operateSuccess'))
|
this.$message.success(this.$t('operateSuccess'))
|
||||||
const beforeGroup = this.CITypeGroups[beforeIndex]
|
const beforeGroup = this.CITypeGroups[beforeIndex]
|
||||||
@@ -414,14 +420,14 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleChange(e, group) {
|
handleChange(e, group) {
|
||||||
console.log('changess')
|
console.log('changess', group)
|
||||||
if (e.hasOwnProperty('moved') && e.moved.oldIndex !== e.moved.newIndex) {
|
if (e.hasOwnProperty('moved') && e.moved.oldIndex !== e.moved.newIndex) {
|
||||||
if (group === -1) {
|
if (group === -1) {
|
||||||
this.$message.error(this.$t('cmdb.ciType.attributeSortedTips'))
|
this.$message.error(this.$t('cmdb.ciType.attributeSortedTips'))
|
||||||
} else {
|
} else {
|
||||||
transferCITypeAttrIndex(this.CITypeId, {
|
transferCITypeAttrIndex(this.CITypeId, {
|
||||||
from: { attr_id: e.moved.element.id, group_id: group > -1 ? group : null },
|
from: { attr_id: e.moved.element.id, group_name: group },
|
||||||
to: { order: e.moved.newIndex, group_id: group > -1 ? group : null },
|
to: { order: e.moved.newIndex, group_name: group },
|
||||||
})
|
})
|
||||||
.then((res) => this.$message.success(this.$t('updateSuccess')))
|
.then((res) => this.$message.success(this.$t('updateSuccess')))
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -431,14 +437,14 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (e.hasOwnProperty('added')) {
|
if (e.hasOwnProperty('added')) {
|
||||||
this.addRemoveGroupFlag = { to: { group_id: group > -1 ? group : null, order: e.added.newIndex }, inited: true }
|
this.addRemoveGroupFlag = { to: { group_name: group, order: e.added.newIndex }, inited: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.hasOwnProperty('removed')) {
|
if (e.hasOwnProperty('removed')) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
transferCITypeAttrIndex(this.CITypeId, {
|
transferCITypeAttrIndex(this.CITypeId, {
|
||||||
from: { attr_id: e.removed.element.id, group_id: group > -1 ? group : null },
|
from: { attr_id: e.removed.element.id, group_name: group },
|
||||||
to: { group_id: this.addRemoveGroupFlag.to.group_id, order: this.addRemoveGroupFlag.to.order },
|
to: { group_name: this.addRemoveGroupFlag.to.group_name, order: this.addRemoveGroupFlag.to.order },
|
||||||
})
|
})
|
||||||
.then((res) => this.$message.success(this.$t('saveSuccess')))
|
.then((res) => this.$message.success(this.$t('saveSuccess')))
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
@@ -139,6 +139,7 @@
|
|||||||
<a><a-icon type="edit" @click="(e) => handleEdit(e, ci)"/></a>
|
<a><a-icon type="edit" @click="(e) => handleEdit(e, ci)"/></a>
|
||||||
<a
|
<a
|
||||||
v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"
|
v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"
|
||||||
|
:disabled="ci.inherited"
|
||||||
@click="(e) => handleDownloadCiType(e, ci)"
|
@click="(e) => handleDownloadCiType(e, ci)"
|
||||||
>
|
>
|
||||||
<a-icon type="download" />
|
<a-icon type="download" />
|
||||||
@@ -176,6 +177,7 @@
|
|||||||
placement="right"
|
placement="right"
|
||||||
width="900px"
|
width="900px"
|
||||||
:destroyOnClose="true"
|
:destroyOnClose="true"
|
||||||
|
:bodyStyle="{ height: 'calc(100vh - 108px)' }"
|
||||||
>
|
>
|
||||||
<a-form
|
<a-form
|
||||||
:form="form"
|
:form="form"
|
||||||
@@ -204,6 +206,35 @@
|
|||||||
<a-form-item :label="$t('alias')">
|
<a-form-item :label="$t('alias')">
|
||||||
<a-input name="alias" v-decorator="['alias', { rules: [] }]" />
|
<a-input name="alias" v-decorator="['alias', { rules: [] }]" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item :label="$t('cmdb.ciType.isInherit')">
|
||||||
|
<a-radio-group v-model="isInherit">
|
||||||
|
<a-radio :value="true">
|
||||||
|
是
|
||||||
|
</a-radio>
|
||||||
|
<a-radio :value="false">
|
||||||
|
否
|
||||||
|
</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="isInherit" :label="$t('cmdb.ciType.inheritType')">
|
||||||
|
<CMDBTypeSelect
|
||||||
|
multiple
|
||||||
|
:placeholder="$t('cmdb.ciType.inheritTypePlaceholder')"
|
||||||
|
v-decorator="[
|
||||||
|
'parent_ids',
|
||||||
|
{ rules: [{ required: true, message: $t('cmdb.ciType.inheritTypePlaceholder') }] },
|
||||||
|
]"
|
||||||
|
selectType="ci_type"
|
||||||
|
:class="{
|
||||||
|
'custom-treeselect': true,
|
||||||
|
}"
|
||||||
|
:style="{
|
||||||
|
'--custom-height': '32px',
|
||||||
|
lineHeight: '32px',
|
||||||
|
'--custom-multiple-lineHeight': '14px',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item :label="$t('icon')">
|
<a-form-item :label="$t('icon')">
|
||||||
<IconArea class="ci_types-icon-area" ref="iconArea" />
|
<IconArea class="ci_types-icon-area" ref="iconArea" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -239,7 +270,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-select>
|
</el-select>
|
||||||
</a-form-item>
|
</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
|
<el-select
|
||||||
size="small"
|
size="small"
|
||||||
filterable
|
filterable
|
||||||
@@ -296,7 +337,14 @@ import router, { resetRouter } from '@/router'
|
|||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import { Select, Option } from 'element-ui'
|
import { Select, Option } from 'element-ui'
|
||||||
import { createCIType, updateCIType, deleteCIType } from '@/modules/cmdb/api/CIType'
|
import {
|
||||||
|
createCIType,
|
||||||
|
updateCIType,
|
||||||
|
deleteCIType,
|
||||||
|
getCIType,
|
||||||
|
postCiTypeInheritance,
|
||||||
|
deleteCiTypeInheritance,
|
||||||
|
} from '@/modules/cmdb/api/CIType'
|
||||||
import {
|
import {
|
||||||
getCITypeGroupsConfig,
|
getCITypeGroupsConfig,
|
||||||
postCITypeGroup,
|
postCITypeGroup,
|
||||||
@@ -316,6 +364,7 @@ import CMDBGrant from '../../components/cmdbGrant'
|
|||||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||||
import AttributeStore from './attributeStore.vue'
|
import AttributeStore from './attributeStore.vue'
|
||||||
import { getAllDepAndEmployee } from '@/api/company'
|
import { getAllDepAndEmployee } from '@/api/company'
|
||||||
|
import CMDBTypeSelect from '../../components/cmdbTypeSelect'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CITypes',
|
name: 'CITypes',
|
||||||
@@ -330,6 +379,7 @@ export default {
|
|||||||
SplitPane,
|
SplitPane,
|
||||||
OpsMoveIcon,
|
OpsMoveIcon,
|
||||||
AttributeStore,
|
AttributeStore,
|
||||||
|
CMDBTypeSelect,
|
||||||
},
|
},
|
||||||
inject: ['reload'],
|
inject: ['reload'],
|
||||||
data() {
|
data() {
|
||||||
@@ -368,6 +418,9 @@ export default {
|
|||||||
default_order_asc: '1',
|
default_order_asc: '1',
|
||||||
|
|
||||||
allTreeDepAndEmp: [],
|
allTreeDepAndEmp: [],
|
||||||
|
|
||||||
|
editCiType: null,
|
||||||
|
isInherit: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -563,6 +616,7 @@ export default {
|
|||||||
this.filterInput = ''
|
this.filterInput = ''
|
||||||
this.form.resetFields()
|
this.form.resetFields()
|
||||||
this.drawerVisible = false
|
this.drawerVisible = false
|
||||||
|
this.isInherit = false
|
||||||
},
|
},
|
||||||
handleCreateNewAttrDone() {
|
handleCreateNewAttrDone() {
|
||||||
this.getAttributes()
|
this.getAttributes()
|
||||||
@@ -583,6 +637,22 @@ export default {
|
|||||||
const icon =
|
const icon =
|
||||||
_icon && _icon.name ? `${_icon.name}$$${_icon.color || ''}$$${_icon.id || ''}$$${_icon.url || ''}` : ''
|
_icon && _icon.name ? `${_icon.name}$$${_icon.color || ''}$$${_icon.id || ''}$$${_icon.url || ''}` : ''
|
||||||
if (values.id) {
|
if (values.id) {
|
||||||
|
const { parent_ids: oldP = [] } = this.editCiType
|
||||||
|
const { parent_ids: newP = [] } = values
|
||||||
|
const { remove, add } = this.compareArrays(newP, oldP)
|
||||||
|
if (add && add.length) {
|
||||||
|
await postCiTypeInheritance({ parent_ids: add, child_id: values.id }).catch(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (remove && remove.length) {
|
||||||
|
for (let i = 0; i < remove.length; i++) {
|
||||||
|
await deleteCiTypeInheritance({ parent_id: remove[i], child_id: values.id }).catch(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete values.parent_ids
|
||||||
await this.updateCIType(values.id, {
|
await this.updateCIType(values.id, {
|
||||||
...values,
|
...values,
|
||||||
icon,
|
icon,
|
||||||
@@ -593,6 +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) {
|
start(g) {
|
||||||
console.log('start', g)
|
console.log('start', g)
|
||||||
this.startId = g.id
|
this.startId = g.id
|
||||||
@@ -767,13 +854,26 @@ export default {
|
|||||||
router.addRoutes(store.getters.appRoutes)
|
router.addRoutes(store.getters.appRoutes)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleEdit(e, record) {
|
async handleEdit(e, record) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
this.drawerTitle = this.$t('cmdb.ciType.editCIType')
|
this.drawerTitle = this.$t('cmdb.ciType.editCIType')
|
||||||
this.drawerVisible = true
|
this.drawerVisible = true
|
||||||
getCITypeAttributesById(record.id).then((res) => {
|
await getCITypeAttributesById(record.id).then((res) => {
|
||||||
this.orderSelectionOptions = res.attributes.filter((item) => item.is_required)
|
this.orderSelectionOptions = res.attributes.filter((item) => item.is_required)
|
||||||
|
})
|
||||||
|
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.$nextTick(() => {
|
||||||
this.default_order_asc = record.default_order_attr && record.default_order_attr.startsWith('-') ? '2' : '1'
|
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.slice(1)
|
||||||
: record.default_order_attr,
|
: record.default_order_attr,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.$refs.iconArea.setIcon(
|
this.$refs.iconArea.setIcon(
|
||||||
record.icon
|
record.icon
|
||||||
? {
|
? {
|
||||||
@@ -798,7 +899,6 @@ export default {
|
|||||||
: {}
|
: {}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
|
||||||
},
|
},
|
||||||
handleCreatNewAttr() {
|
handleCreatNewAttr() {
|
||||||
this.newAttrAreaVisible = !this.newAttrAreaVisible
|
this.newAttrAreaVisible = !this.newAttrAreaVisible
|
||||||
|
@@ -20,7 +20,9 @@
|
|||||||
"
|
"
|
||||||
>{{ $t('cancel') }}</a-button
|
>{{ $t('cancel') }}</a-button
|
||||||
>
|
>
|
||||||
<a-button :loading="confirmLoading" @click="handleSubmit(false)" type="primary">{{ $t('cmdb.ciType.continueAdd') }}</a-button>
|
<a-button :loading="confirmLoading" @click="handleSubmit(false)" type="primary">{{
|
||||||
|
$t('cmdb.ciType.continueAdd')
|
||||||
|
}}</a-button>
|
||||||
<a-button :loading="confirmLoading" type="primary" @click="handleSubmit">{{ $t('confirm') }}</a-button>
|
<a-button :loading="confirmLoading" type="primary" @click="handleSubmit">{{ $t('confirm') }}</a-button>
|
||||||
</template>
|
</template>
|
||||||
<a-tabs v-model="activeKey">
|
<a-tabs v-model="activeKey">
|
||||||
@@ -47,7 +49,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { searchAttributes, createCITypeAttributes, updateCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
import { searchAttributes, createCITypeAttributes, updateCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||||
import { updateCITypeGroupById, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
|
import { createCITypeGroupById, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
|
||||||
import CreateNewAttribute from './ceateNewAttribute.vue'
|
import CreateNewAttribute from './ceateNewAttribute.vue'
|
||||||
import { valueTypeMap } from '../../utils/const'
|
import { valueTypeMap } from '../../utils/const'
|
||||||
import AttributesTransfer from '../../components/attributesTransfer'
|
import AttributesTransfer from '../../components/attributesTransfer'
|
||||||
@@ -102,11 +104,11 @@ export default {
|
|||||||
if (this.currentGroup) {
|
if (this.currentGroup) {
|
||||||
await this.updateCurrentGroup()
|
await this.updateCurrentGroup()
|
||||||
const { id, name, order, attributes } = this.currentGroup
|
const { id, name, order, attributes } = this.currentGroup
|
||||||
const attrIds = attributes.map((i) => i.id)
|
const attrIds = attributes.filter((i) => !i.inherited).map((i) => i.id)
|
||||||
this.targetKeys.forEach((key) => {
|
this.targetKeys.forEach((key) => {
|
||||||
attrIds.push(Number(key))
|
attrIds.push(Number(key))
|
||||||
})
|
})
|
||||||
await updateCITypeGroupById(id, { name, order, attributes: [...new Set(attrIds)] })
|
await createCITypeGroupById(this.CITypeId, { name, order, attributes: [...new Set(attrIds)] })
|
||||||
}
|
}
|
||||||
this.confirmLoading = false
|
this.confirmLoading = false
|
||||||
this.handleClose(isCloseModal)
|
this.handleClose(isCloseModal)
|
||||||
@@ -140,9 +142,9 @@ export default {
|
|||||||
if (this.currentGroup) {
|
if (this.currentGroup) {
|
||||||
await this.updateCurrentGroup()
|
await this.updateCurrentGroup()
|
||||||
const { id, name, order, attributes } = this.currentGroup
|
const { id, name, order, attributes } = this.currentGroup
|
||||||
const attrIds = attributes.map((i) => i.id)
|
const attrIds = attributes.filter((i) => !i.inherited).map((i) => i.id)
|
||||||
attrIds.push(newAttrId)
|
attrIds.push(newAttrId)
|
||||||
await updateCITypeGroupById(id, { name, order, attributes: attrIds })
|
await createCITypeGroupById(this.CITypeId, { name, order, attributes: attrIds })
|
||||||
}
|
}
|
||||||
this.confirmLoading = false
|
this.confirmLoading = false
|
||||||
this.loadTotalAttrs()
|
this.loadTotalAttrs()
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div :style="{ height: '100%' }">
|
||||||
<vxe-table
|
<vxe-table
|
||||||
show-overflow
|
show-overflow
|
||||||
show-header-overflow
|
show-header-overflow
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
class="ops-stripe-table"
|
class="ops-stripe-table"
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
v-bind="ci_id ? { maxHeight: `${windowHeight - 94}px` } : { height: `${windowHeight - 225}px` }"
|
v-bind="ci_id ? { height: 'auto' } : { height: `${windowHeight - 225}px` }"
|
||||||
>
|
>
|
||||||
<vxe-column field="trigger_name" :title="$t('cmdb.history.triggerName')"> </vxe-column>
|
<vxe-column field="trigger_name" :title="$t('cmdb.history.triggerName')"> </vxe-column>
|
||||||
<vxe-column field="type" :title="$t('type')">
|
<vxe-column field="type" :title="$t('type')">
|
||||||
|
@@ -2,14 +2,55 @@
|
|||||||
<div :style="{ marginBottom: '-24px', overflow: 'hidden' }">
|
<div :style="{ marginBottom: '-24px', overflow: 'hidden' }">
|
||||||
<div v-if="relationViews.name2id && relationViews.name2id.length" class="relation-views-wrapper">
|
<div v-if="relationViews.name2id && relationViews.name2id.length" class="relation-views-wrapper">
|
||||||
<div class="cmdb-views-header">
|
<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>
|
<a-button size="small" icon="user-add" type="primary" ghost @click="handlePerm">{{ $t('grant') }}</a-button>
|
||||||
</div>
|
</div>
|
||||||
<SplitPane
|
<SplitPane
|
||||||
:min="200"
|
:min="200"
|
||||||
:max="500"
|
:max="500"
|
||||||
:paneLengthPixel.sync="paneLengthPixel"
|
:paneLengthPixel.sync="paneLengthPixel"
|
||||||
appName="cmdb-relation-views"
|
:appName="`cmdb-relation-views-${viewId}`"
|
||||||
triggerColor="#F0F5FF"
|
triggerColor="#F0F5FF"
|
||||||
:triggerLength="18"
|
:triggerLength="18"
|
||||||
>
|
>
|
||||||
@@ -24,7 +65,6 @@
|
|||||||
@drop="onDrop"
|
@drop="onDrop"
|
||||||
:expandedKeys="expandedKeys"
|
:expandedKeys="expandedKeys"
|
||||||
>
|
>
|
||||||
<a-icon slot="switcherIcon" type="down" />
|
|
||||||
<template #title="{ key: treeKey, title, isLeaf }">
|
<template #title="{ key: treeKey, title, isLeaf }">
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
:title="title"
|
:title="title"
|
||||||
@@ -35,7 +75,10 @@
|
|||||||
:id2type="relationViews.id2type"
|
:id2type="relationViews.id2type"
|
||||||
@onContextMenuClick="onContextMenuClick"
|
@onContextMenuClick="onContextMenuClick"
|
||||||
@onNodeClick="onNodeClick"
|
@onNodeClick="onNodeClick"
|
||||||
:ciTypes="ciTypes"
|
:ciTypeIcons="ciTypeIcons"
|
||||||
|
:showBatchLevel="showBatchLevel"
|
||||||
|
:batchTreeKey="batchTreeKey"
|
||||||
|
@clickCheckbox="clickCheckbox"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</a-tree>
|
</a-tree>
|
||||||
@@ -313,10 +356,9 @@
|
|||||||
v-else-if="relationViews.name2id && !relationViews.name2id.length"
|
v-else-if="relationViews.name2id && !relationViews.name2id.length"
|
||||||
></a-alert>
|
></a-alert>
|
||||||
<AddTableModal ref="addTableModal" @reload="reload" />
|
<AddTableModal ref="addTableModal" @reload="reload" />
|
||||||
<!-- <GrantDrawer ref="grantDrawer" resourceTypeName="RelationView" app_id="cmdb" /> -->
|
|
||||||
<CMDBGrant ref="cmdbGrant" resourceType="RelationView" app_id="cmdb" />
|
<CMDBGrant ref="cmdbGrant" resourceType="RelationView" app_id="cmdb" />
|
||||||
|
<GrantModal ref="grantModal" @handleOk="onRelationViewGrant" :customTitle="$t('cmdb.serviceTree.grantTitle')" />
|
||||||
<ci-detail ref="detail" :typeId="Number(currentTypeId[0])" />
|
<CiDetailDrawer ref="detail" :typeId="Number(currentTypeId[0])" />
|
||||||
<create-instance-form
|
<create-instance-form
|
||||||
ref="create"
|
ref="create"
|
||||||
:typeIdFromRelation="Number(currentTypeId[0])"
|
:typeIdFromRelation="Number(currentTypeId[0])"
|
||||||
@@ -325,11 +367,12 @@
|
|||||||
/>
|
/>
|
||||||
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
||||||
<BatchDownload ref="batchDownload" @batchDownload="batchDownload" />
|
<BatchDownload ref="batchDownload" @batchDownload="batchDownload" />
|
||||||
|
<ReadPermissionsModal ref="readPermissionsModal" />
|
||||||
|
<RevokeModal ref="revokeModal" @handleRevoke="handleRevoke" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
/* eslint-disable no-useless-escape */
|
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { Tree } from 'element-ui'
|
import { Tree } from 'element-ui'
|
||||||
import Sortable from 'sortablejs'
|
import Sortable from 'sortablejs'
|
||||||
@@ -349,20 +392,23 @@ import {
|
|||||||
|
|
||||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||||
import { searchCI2, updateCI, deleteCI } from '@/modules/cmdb/api/ci'
|
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 { roleHasPermissionToGrant } from '@/modules/acl/api/permission'
|
||||||
import { searchResourceType } from '@/modules/acl/api/resource'
|
import { searchResourceType } from '@/modules/acl/api/resource'
|
||||||
import SplitPane from '@/components/SplitPane'
|
import SplitPane from '@/components/SplitPane'
|
||||||
import EditAttrsPopover from '../ci/modules/editAttrsPopover.vue'
|
import EditAttrsPopover from '../ci/modules/editAttrsPopover.vue'
|
||||||
import CiDetail from '../ci/modules/CiDetail'
|
import CiDetailDrawer from '../ci/modules/ciDetailDrawer.vue'
|
||||||
import CreateInstanceForm from '../ci/modules/CreateInstanceForm'
|
import CreateInstanceForm from '../ci/modules/CreateInstanceForm'
|
||||||
import JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
|
import JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
|
||||||
import BatchDownload from '../../components/batchDownload/batchDownload.vue'
|
import BatchDownload from '../../components/batchDownload/batchDownload.vue'
|
||||||
import PasswordField from '../../components/passwordField/index.vue'
|
import PasswordField from '../../components/passwordField/index.vue'
|
||||||
import PreferenceSearch from '../../components/preferenceSearch/preferenceSearch.vue'
|
import PreferenceSearch from '../../components/preferenceSearch/preferenceSearch.vue'
|
||||||
import CMDBGrant from '../../components/cmdbGrant'
|
import CMDBGrant from '../../components/cmdbGrant'
|
||||||
|
import GrantModal from '../../components/cmdbGrant/grantModal.vue'
|
||||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||||
import { getAttrPassword } from '../../api/CITypeAttr'
|
import { getAttrPassword } from '../../api/CITypeAttr'
|
||||||
|
import ReadPermissionsModal from './modules/ReadPermissionsModal.vue'
|
||||||
|
import RevokeModal from '../../components/cmdbGrant/revokeModal.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'RelationViews',
|
name: 'RelationViews',
|
||||||
@@ -370,25 +416,27 @@ export default {
|
|||||||
SearchForm,
|
SearchForm,
|
||||||
AddTableModal,
|
AddTableModal,
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
// GrantDrawer,
|
|
||||||
CMDBGrant,
|
CMDBGrant,
|
||||||
|
GrantModal,
|
||||||
SplitPane,
|
SplitPane,
|
||||||
ElTree: Tree,
|
ElTree: Tree,
|
||||||
EditAttrsPopover,
|
EditAttrsPopover,
|
||||||
CiDetail,
|
CiDetailDrawer,
|
||||||
CreateInstanceForm,
|
CreateInstanceForm,
|
||||||
JsonEditor,
|
JsonEditor,
|
||||||
BatchDownload,
|
BatchDownload,
|
||||||
PasswordField,
|
PasswordField,
|
||||||
PreferenceSearch,
|
PreferenceSearch,
|
||||||
OpsMoveIcon,
|
OpsMoveIcon,
|
||||||
|
ReadPermissionsModal,
|
||||||
|
RevokeModal,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
treeData: [],
|
treeData: [],
|
||||||
triggerSelect: false,
|
triggerSelect: false,
|
||||||
treeNode: null,
|
treeNode: null,
|
||||||
ciTypes: [],
|
ciTypeIcons: {},
|
||||||
relationViews: {},
|
relationViews: {},
|
||||||
levels: [],
|
levels: [],
|
||||||
showTypeIds: [],
|
showTypeIds: [],
|
||||||
@@ -430,6 +478,10 @@ export default {
|
|||||||
passwordValue: {},
|
passwordValue: {},
|
||||||
lastEditCiId: null,
|
lastEditCiId: null,
|
||||||
isContinueCloseEdit: true,
|
isContinueCloseEdit: true,
|
||||||
|
|
||||||
|
contextMenuKey: null,
|
||||||
|
showBatchLevel: null,
|
||||||
|
batchTreeKey: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -452,6 +504,21 @@ export default {
|
|||||||
isShowBatchIcon() {
|
isShowBatchIcon() {
|
||||||
return !!this.selectedRowKeys.length
|
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() {
|
provide() {
|
||||||
return {
|
return {
|
||||||
@@ -505,8 +572,8 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
getCITypesList() {
|
getCITypesList() {
|
||||||
getCITypes().then((res) => {
|
getCITypeIcons().then((res) => {
|
||||||
this.ciTypes = res.ci_types
|
this.ciTypeIcons = res
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
refreshTable() {
|
refreshTable() {
|
||||||
@@ -572,33 +639,38 @@ export default {
|
|||||||
q = q.slice(1)
|
q = q.slice(1)
|
||||||
}
|
}
|
||||||
if (this.treeKeys.length === 0) {
|
if (this.treeKeys.length === 0) {
|
||||||
await this.judgeCITypes(q)
|
// await this.judgeCITypes(q)
|
||||||
if (!refreshType) {
|
if (!refreshType) {
|
||||||
this.loadRoot()
|
await this.loadRoot()
|
||||||
}
|
}
|
||||||
|
|
||||||
const fuzzySearch = (this.$refs['search'] || {}).fuzzySearch || ''
|
// const fuzzySearch = (this.$refs['search'] || {}).fuzzySearch || ''
|
||||||
if (fuzzySearch) {
|
// if (fuzzySearch) {
|
||||||
q = `q=_type:${this.currentTypeId[0]},*${fuzzySearch}*,` + q
|
// q = `q=_type:${this.currentTypeId[0]},*${fuzzySearch}*,` + q
|
||||||
} else {
|
// } else {
|
||||||
q = `q=_type:${this.currentTypeId[0]},` + q
|
// q = `q=_type:${this.currentTypeId[0]},` + q
|
||||||
}
|
// }
|
||||||
if (this.currentTypeId[0]) {
|
// if (this.currentTypeId[0] && this.treeData && this.treeData.length) {
|
||||||
const res = await searchCI2(q)
|
// // default select first node
|
||||||
this.pageNo = res.page
|
// this.onNodeClick(this.treeData[0].key)
|
||||||
this.numfound = res.numfound
|
// const res = await searchCI2(q)
|
||||||
res.result.forEach((item, index) => (item.key = item._id))
|
// const root_id = this.treeData.map((item) => item.id).join(',')
|
||||||
const jsonAttrList = this.preferenceAttrList.filter((attr) => attr.value_type === '6')
|
// q += `&root_id=${root_id}`
|
||||||
console.log(jsonAttrList)
|
|
||||||
this.instanceList = res['result'].map((item) => {
|
// this.pageNo = res.page
|
||||||
jsonAttrList.forEach(
|
// this.numfound = res.numfound
|
||||||
(jsonAttr) => (item[jsonAttr.name] = item[jsonAttr.name] ? JSON.stringify(item[jsonAttr.name]) : '')
|
// res.result.forEach((item, index) => (item.key = item._id))
|
||||||
)
|
// const jsonAttrList = this.preferenceAttrList.filter((attr) => attr.value_type === '6')
|
||||||
return { ..._.cloneDeep(item) }
|
// console.log(jsonAttrList)
|
||||||
})
|
// this.instanceList = res['result'].map((item) => {
|
||||||
this.initialInstanceList = _.cloneDeep(this.instanceList)
|
// jsonAttrList.forEach(
|
||||||
this.calcColumns()
|
// (jsonAttr) => (item[jsonAttr.name] = item[jsonAttr.name] ? JSON.stringify(item[jsonAttr.name]) : '')
|
||||||
}
|
// )
|
||||||
|
// return { ..._.cloneDeep(item) }
|
||||||
|
// })
|
||||||
|
// this.initialInstanceList = _.cloneDeep(this.instanceList)
|
||||||
|
// this.calcColumns()
|
||||||
|
// }
|
||||||
} else {
|
} else {
|
||||||
q += `&root_id=${this.treeKeys[this.treeKeys.length - 1].split('%')[0]}`
|
q += `&root_id=${this.treeKeys[this.treeKeys.length - 1].split('%')[0]}`
|
||||||
|
|
||||||
@@ -634,10 +706,10 @@ export default {
|
|||||||
level = [1]
|
level = [1]
|
||||||
}
|
}
|
||||||
q += `&level=${level.join(',')}`
|
q += `&level=${level.join(',')}`
|
||||||
|
await this.judgeCITypes(q)
|
||||||
if (!refreshType) {
|
if (!refreshType) {
|
||||||
this.loadNoRoot(this.treeKeys[this.treeKeys.length - 1], level)
|
this.loadNoRoot(this.treeKeys[this.treeKeys.length - 1], level)
|
||||||
}
|
}
|
||||||
await this.judgeCITypes(q)
|
|
||||||
const fuzzySearch = (this.$refs['search'] || {}).fuzzySearch || ''
|
const fuzzySearch = (this.$refs['search'] || {}).fuzzySearch || ''
|
||||||
if (fuzzySearch) {
|
if (fuzzySearch) {
|
||||||
q = `q=_type:${this.currentTypeId[0]},*${fuzzySearch}*,` + q
|
q = `q=_type:${this.currentTypeId[0]},*${fuzzySearch}*,` + q
|
||||||
@@ -645,8 +717,12 @@ export default {
|
|||||||
q = `q=_type:${this.currentTypeId[0]},` + q
|
q = `q=_type:${this.currentTypeId[0]},` + q
|
||||||
}
|
}
|
||||||
if (Object.values(this.level2constraint).includes('2')) {
|
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]) {
|
if (this.currentTypeId[0]) {
|
||||||
const res = await searchCIRelation(q)
|
const res = await searchCIRelation(q)
|
||||||
|
|
||||||
@@ -666,7 +742,6 @@ export default {
|
|||||||
|
|
||||||
this.calcColumns()
|
this.calcColumns()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refreshType === 'refreshNumber') {
|
if (refreshType === 'refreshNumber') {
|
||||||
const promises = this.treeKeys.map((key, index) => {
|
const promises = this.treeKeys.map((key, index) => {
|
||||||
let ancestor_ids
|
let ancestor_ids
|
||||||
@@ -684,8 +759,9 @@ export default {
|
|||||||
ancestor_ids,
|
ancestor_ids,
|
||||||
root_ids: key.split('%')[0],
|
root_ids: key.split('%')[0],
|
||||||
level: this.treeKeys.length - index,
|
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')),
|
has_m2m: Number(Object.values(this.level2constraint).includes('2')),
|
||||||
|
descendant_ids: this.descendant_ids_for_statistics,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
let result
|
let result
|
||||||
const getTreeItem = (data, id) => {
|
const getTreeItem = (data, id) => {
|
||||||
@@ -741,22 +817,25 @@ export default {
|
|||||||
const promises = _showTypeIds.map((typeId) => {
|
const promises = _showTypeIds.map((typeId) => {
|
||||||
let _q = (`q=_type:${typeId},` + q).replace(/count=\d*/, 'count=1')
|
let _q = (`q=_type:${typeId},` + q).replace(/count=\d*/, 'count=1')
|
||||||
if (Object.values(this.level2constraint).includes('2')) {
|
if (Object.values(this.level2constraint).includes('2')) {
|
||||||
_q = _q + `&&has_m2m=1`
|
_q = _q + `&has_m2m=1`
|
||||||
}
|
}
|
||||||
console.log(_q)
|
if (this.root_parent_path) {
|
||||||
if (this.treeKeys.length === 0) {
|
_q = _q + `&root_parent_path=${this.root_parent_path}`
|
||||||
return searchCI2(_q).then((res) => {
|
|
||||||
if (res.numfound !== 0) {
|
|
||||||
showTypeIds.push(typeId)
|
|
||||||
}
|
}
|
||||||
})
|
// if (this.treeKeys.length === 0) {
|
||||||
} else {
|
// 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) => {
|
return searchCIRelation(_q).then((res) => {
|
||||||
if (res.numfound !== 0) {
|
if (res.numfound !== 0) {
|
||||||
showTypeIds.push(typeId)
|
showTypeIds.push(typeId)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
// }
|
||||||
})
|
})
|
||||||
await Promise.all(promises).then(async () => {
|
await Promise.all(promises).then(async () => {
|
||||||
if (showTypeIds.length && showTypeIds.sort().join(',') !== this.showTypeIds.sort().join(',')) {
|
if (showTypeIds.length && showTypeIds.sort().join(',') !== this.showTypeIds.sort().join(',')) {
|
||||||
@@ -780,7 +859,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async loadRoot() {
|
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 facet = []
|
||||||
const ciIds = []
|
const ciIds = []
|
||||||
res.result.forEach((item) => {
|
res.result.forEach((item) => {
|
||||||
@@ -797,8 +876,9 @@ export default {
|
|||||||
return statisticsCIRelation({
|
return statisticsCIRelation({
|
||||||
root_ids: ciIds.join(','),
|
root_ids: ciIds.join(','),
|
||||||
level: level,
|
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')),
|
has_m2m: Number(Object.values(this.level2constraint).includes('2')),
|
||||||
|
descendant_ids: this.descendant_ids_for_statistics,
|
||||||
}).then((num) => {
|
}).then((num) => {
|
||||||
facet.forEach((item, idx) => {
|
facet.forEach((item, idx) => {
|
||||||
item[1] += num[ciIds[idx] + '']
|
item[1] += num[ciIds[idx] + '']
|
||||||
@@ -806,16 +886,17 @@ export default {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
await Promise.all(promises)
|
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) {
|
async loadNoRoot(rootIdAndTypeId, level) {
|
||||||
const rootId = rootIdAndTypeId.split('%')[0]
|
const rootId = rootIdAndTypeId.split('%')[0]
|
||||||
const typeId = Number(rootIdAndTypeId.split('%')[1])
|
const typeId = Number(rootIdAndTypeId.split('%')[1])
|
||||||
const topo_flatten = this.relationViews?.views[this.$route.meta.name]?.topo_flatten ?? []
|
const index = this.topo_flatten.findIndex((id) => id === typeId)
|
||||||
const index = topo_flatten.findIndex((id) => id === typeId)
|
const _type = this.topo_flatten[index + 1]
|
||||||
const _type = topo_flatten[index + 1]
|
|
||||||
if (_type) {
|
if (_type) {
|
||||||
let q = `q=_type:${_type}&root_id=${rootId}&level=1&count=10000`
|
let q = `q=_type:${_type}&root_id=${rootId}&level=1&count=10000`
|
||||||
if (
|
if (
|
||||||
@@ -829,8 +910,12 @@ export default {
|
|||||||
.join(',')}`
|
.join(',')}`
|
||||||
}
|
}
|
||||||
if (Object.values(this.level2constraint).includes('2')) {
|
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) => {
|
searchCIRelation(q).then(async (res) => {
|
||||||
const facet = []
|
const facet = []
|
||||||
const ciIds = []
|
const ciIds = []
|
||||||
@@ -852,8 +937,9 @@ export default {
|
|||||||
ancestor_ids,
|
ancestor_ids,
|
||||||
root_ids: ciIds.join(','),
|
root_ids: ciIds.join(','),
|
||||||
level: _level - 1,
|
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')),
|
has_m2m: Number(Object.values(this.level2constraint).includes('2')),
|
||||||
|
descendant_ids: this.descendant_ids_for_statistics,
|
||||||
}).then((num) => {
|
}).then((num) => {
|
||||||
facet.forEach((item, idx) => {
|
facet.forEach((item, idx) => {
|
||||||
item[1] += num[ciIds[idx] + '']
|
item[1] += num[ciIds[idx] + '']
|
||||||
@@ -862,7 +948,7 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
await Promise.all(promises)
|
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.treeKeys = treeNode.eventKey.split('@^@').filter((item) => item !== '')
|
||||||
this.treeNode = treeNode
|
this.treeNode = treeNode
|
||||||
|
// this.refreshTable()
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -979,8 +1066,7 @@ export default {
|
|||||||
this.$refs.xTable.refreshColumn()
|
this.$refs.xTable.refreshColumn()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onContextMenuClick(treeKey, menuKey) {
|
calculateParamsFromTreeKey(treeKey, menuKey) {
|
||||||
if (treeKey) {
|
|
||||||
const splitTreeKey = treeKey.split('@^@')
|
const splitTreeKey = treeKey.split('@^@')
|
||||||
const _tempTree = splitTreeKey[splitTreeKey.length - 1].split('%')
|
const _tempTree = splitTreeKey[splitTreeKey.length - 1].split('%')
|
||||||
const firstCIObj = JSON.parse(_tempTree[2])
|
const firstCIObj = JSON.parse(_tempTree[2])
|
||||||
@@ -996,10 +1082,21 @@ export default {
|
|||||||
.slice(0, menuKey === 'delete' ? treeKey.split('@^@').length - 2 : treeKey.split('@^@').length - 1)
|
.slice(0, menuKey === 'delete' ? treeKey.split('@^@').length - 2 : treeKey.split('@^@').length - 1)
|
||||||
ancestor_ids = ancestor.map((item) => item.split('%')[0]).join(',')
|
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') {
|
if (menuKey === 'delete') {
|
||||||
const _tempTreeParent = splitTreeKey[splitTreeKey.length - 2].split('%')
|
const _tempTreeParent = splitTreeKey[splitTreeKey.length - 2].split('%')
|
||||||
const that = this
|
const that = this
|
||||||
|
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: that.$t('warning'),
|
title: that.$t('warning'),
|
||||||
content: (h) => <div>{that.$t('confirmDelete2', { name: Object.values(firstCIObj)[0] })}</div>,
|
content: (h) => <div>{that.$t('confirmDelete2', { name: Object.values(firstCIObj)[0] })}</div>,
|
||||||
@@ -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 {
|
} else {
|
||||||
const childTypeId = menuKey
|
const childTypeId = menuKey
|
||||||
this.$refs.addTableModal.openModal(firstCIObj, firstCIId, childTypeId, 'children', ancestor_ids)
|
this.$refs.addTableModal.openModal(firstCIObj, firstCIId, childTypeId, 'children', ancestor_ids)
|
||||||
@@ -1066,8 +1181,10 @@ export default {
|
|||||||
const _splitTargetKey = targetKey.split('@^@').filter((item) => item !== '')
|
const _splitTargetKey = targetKey.split('@^@').filter((item) => item !== '')
|
||||||
if (_splitDragKey.length - 1 === _splitTargetKey.length) {
|
if (_splitDragKey.length - 1 === _splitTargetKey.length) {
|
||||||
const dragId = _splitDragKey[_splitDragKey.length - 1].split('%')[0]
|
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]
|
const targetId = _splitTargetKey[_splitTargetKey.length - 1].split('%')[0]
|
||||||
console.log(_splitDragKey)
|
console.log(_splitDragKey)
|
||||||
|
// TODO 拖拽这里不造咋弄 等等再说吧
|
||||||
batchUpdateCIRelationChildren([dragId], [targetId]).then((res) => {
|
batchUpdateCIRelationChildren([dragId], [targetId]).then((res) => {
|
||||||
this.reload()
|
this.reload()
|
||||||
})
|
})
|
||||||
@@ -1438,6 +1555,138 @@ export default {
|
|||||||
this.$message.error(this.$t('cmdb.serviceTreecopyFailed'))
|
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>
|
</script>
|
||||||
|
@@ -11,24 +11,24 @@
|
|||||||
>
|
>
|
||||||
<div :style="{ width: '100%' }" id="add-table-modal">
|
<div :style="{ width: '100%' }" id="add-table-modal">
|
||||||
<a-spin :spinning="loading">
|
<a-spin :spinning="loading">
|
||||||
<!-- <a-input
|
|
||||||
v-model="expression"
|
|
||||||
class="ci-searchform-expression"
|
|
||||||
:style="{ width, marginBottom: '10px' }"
|
|
||||||
:placeholder="placeholder"
|
|
||||||
@focus="
|
|
||||||
() => {
|
|
||||||
isFocusExpression = true
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/> -->
|
|
||||||
<SearchForm
|
<SearchForm
|
||||||
ref="searchForm"
|
ref="searchForm"
|
||||||
:typeId="addTypeId"
|
:typeId="addTypeId"
|
||||||
:preferenceAttrList="preferenceAttrList"
|
:preferenceAttrList="preferenceAttrList"
|
||||||
@refresh="handleSearch"
|
@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
|
<vxe-table
|
||||||
ref="xTable"
|
ref="xTable"
|
||||||
row-id="_id"
|
row-id="_id"
|
||||||
@@ -77,19 +77,31 @@
|
|||||||
/>
|
/>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</div>
|
</div>
|
||||||
|
<CreateInstanceForm
|
||||||
|
ref="createInstanceForm"
|
||||||
|
:typeIdFromRelation="addTypeId"
|
||||||
|
@reload="
|
||||||
|
() => {
|
||||||
|
currentPage = 1
|
||||||
|
getTableData(true)
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
/* eslint-disable no-useless-escape */
|
|
||||||
import { searchCI } from '@/modules/cmdb/api/ci'
|
import { searchCI } from '@/modules/cmdb/api/ci'
|
||||||
import { getSubscribeAttributes } from '@/modules/cmdb/api/preference'
|
import { getSubscribeAttributes } from '@/modules/cmdb/api/preference'
|
||||||
import { batchUpdateCIRelationChildren, batchUpdateCIRelationParents } from '@/modules/cmdb/api/CIRelation'
|
import { batchUpdateCIRelationChildren, batchUpdateCIRelationParents } from '@/modules/cmdb/api/CIRelation'
|
||||||
import { getCITableColumns } from '../../../utils/helper'
|
import { getCITableColumns } from '../../../utils/helper'
|
||||||
import SearchForm from '../../../components/searchForm/SearchForm.vue'
|
import SearchForm from '../../../components/searchForm/SearchForm.vue'
|
||||||
|
import CreateInstanceForm from '../../ci/modules/CreateInstanceForm.vue'
|
||||||
|
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AddTableModal',
|
name: 'AddTableModal',
|
||||||
components: { SearchForm },
|
components: { SearchForm, CreateInstanceForm },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
visible: false,
|
visible: false,
|
||||||
@@ -106,6 +118,7 @@ export default {
|
|||||||
type: 'children',
|
type: 'children',
|
||||||
preferenceAttrList: [],
|
preferenceAttrList: [],
|
||||||
ancestor_ids: undefined,
|
ancestor_ids: undefined,
|
||||||
|
attrList1: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -119,6 +132,13 @@ export default {
|
|||||||
return this.isFocusExpression ? '500px' : '100px'
|
return this.isFocusExpression ? '500px' : '100px'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
attrList: () => {
|
||||||
|
return this.attrList
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
watch: {},
|
watch: {},
|
||||||
methods: {
|
methods: {
|
||||||
async openModal(ciObj, ciId, addTypeId, type, ancestor_ids = undefined) {
|
async openModal(ciObj, ciId, addTypeId, type, ancestor_ids = undefined) {
|
||||||
@@ -132,6 +152,9 @@ export default {
|
|||||||
await getSubscribeAttributes(addTypeId).then((res) => {
|
await getSubscribeAttributes(addTypeId).then((res) => {
|
||||||
this.preferenceAttrList = res.attributes // 已经订阅的全部列
|
this.preferenceAttrList = res.attributes // 已经订阅的全部列
|
||||||
})
|
})
|
||||||
|
getCITypeAttributesById(addTypeId).then((res) => {
|
||||||
|
this.attrList = res.attributes
|
||||||
|
})
|
||||||
this.getTableData(true)
|
this.getTableData(true)
|
||||||
},
|
},
|
||||||
async getTableData(isInit) {
|
async getTableData(isInit) {
|
||||||
@@ -207,6 +230,9 @@ export default {
|
|||||||
this.handleClose()
|
this.handleClose()
|
||||||
this.$emit('reload')
|
this.$emit('reload')
|
||||||
}, 500)
|
}, 500)
|
||||||
|
} else {
|
||||||
|
this.handleClose()
|
||||||
|
this.$emit('reload')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleSearch() {
|
handleSearch() {
|
||||||
|
@@ -1,63 +1,81 @@
|
|||||||
<template>
|
<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
|
<div
|
||||||
:style="{
|
:class="{
|
||||||
width: '100%',
|
'relation-views-node': true,
|
||||||
display: 'inline-flex',
|
'relation-views-node-checkbox': showCheckbox,
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
}"
|
}"
|
||||||
@click="clickNode"
|
@click="clickNode"
|
||||||
>
|
>
|
||||||
<span
|
<span>
|
||||||
:style="{
|
<a-checkbox @click.stop="clickCheckbox" class="relation-views-node-checkbox" v-if="showCheckbox" />
|
||||||
display: 'flex',
|
|
||||||
overflow: 'hidden',
|
|
||||||
width: '100%',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
alignItems: 'center',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template v-if="icon">
|
<template v-if="icon">
|
||||||
<img
|
<img
|
||||||
v-if="icon.split('$$')[2]"
|
v-if="icon.includes('$$') && icon.split('$$')[2]"
|
||||||
:src="`/api/common-setting/v1/file/${icon.split('$$')[3]}`"
|
:src="`/api/common-setting/v1/file/${icon.split('$$')[3]}`"
|
||||||
:style="{ maxHeight: '14px', maxWidth: '14px' }"
|
:style="{ maxHeight: '14px', maxWidth: '14px' }"
|
||||||
/>
|
/>
|
||||||
<ops-icon
|
<ops-icon
|
||||||
v-else
|
v-else-if="icon.includes('$$') && icon.split('$$')[0]"
|
||||||
:style="{
|
:style="{
|
||||||
color: icon.split('$$')[1],
|
color: icon.split('$$')[1],
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
}"
|
}"
|
||||||
:type="icon.split('$$')[0]"
|
:type="icon.split('$$')[0]"
|
||||||
/>
|
/>
|
||||||
|
<span class="relation-views-node-icon" v-else>{{ icon ? icon[0].toUpperCase() : 'i' }}</span>
|
||||||
</template>
|
</template>
|
||||||
<span
|
<span class="relation-views-node-title">{{ this.title }}</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>
|
</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>
|
<a-icon :style="{ fontSize: '10px' }" v-if="childLength && !isLeaf" :type="switchIcon"></a-icon>
|
||||||
</div>
|
</div>
|
||||||
</a-dropdown>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -88,7 +106,15 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: () => false,
|
default: () => false,
|
||||||
},
|
},
|
||||||
ciTypes: {
|
ciTypeIcons: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
showBatchLevel: {
|
||||||
|
type: Number,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
batchTreeKey: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
@@ -141,14 +167,10 @@ export default {
|
|||||||
icon() {
|
icon() {
|
||||||
const _split = this.treeKey.split('@^@')
|
const _split = this.treeKey.split('@^@')
|
||||||
const currentNodeTypeId = _split[_split.length - 1].split('%')[1]
|
const currentNodeTypeId = _split[_split.length - 1].split('%')[1]
|
||||||
const _find = this.ciTypes.find((type) => type.id === Number(currentNodeTypeId))
|
return this.ciTypeIcons[Number(currentNodeTypeId)] ?? null
|
||||||
return _find?.icon || null
|
|
||||||
},
|
},
|
||||||
ciTypeName() {
|
showCheckbox() {
|
||||||
const _split = this.treeKey.split('@^@')
|
return this.showBatchLevel === this.treeKey.split('@^@').filter((item) => !!item).length - 1
|
||||||
const currentNodeTypeId = _split[_split.length - 1].split('%')[1]
|
|
||||||
const _find = this.ciTypes.find((type) => type.id === Number(currentNodeTypeId))
|
|
||||||
return _find?.name || ''
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -159,8 +181,73 @@ export default {
|
|||||||
this.$emit('onNodeClick', this.treeKey)
|
this.$emit('onNodeClick', this.treeKey)
|
||||||
this.switchIcon = this.switchIcon === 'down' ? 'up' : 'down'
|
this.switchIcon = this.switchIcon === 'down' ? 'up' : 'down'
|
||||||
},
|
},
|
||||||
|
clickCheckbox() {
|
||||||
|
this.$emit('clickCheckbox', this.treeKey)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</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 _ from 'lodash'
|
||||||
import SearchForm from '../../components/searchForm/SearchForm.vue'
|
import SearchForm from '../../components/searchForm/SearchForm.vue'
|
||||||
import { searchCI } from '../../api/ci'
|
import { searchCI } from '../../api/ci'
|
||||||
import { searchAttributes, getCITypeAttributesByTypeIds } from '../../api/CITypeAttr'
|
import { searchAttributes, getCITypeAttributesByTypeIds, getCITypeAttributesById } from '../../api/CITypeAttr'
|
||||||
import { getCITypes } from '../../api/CIType'
|
import { getCITypes } from '../../api/CIType'
|
||||||
import { getSubscribeAttributes } from '../../api/preference'
|
import { getSubscribeAttributes } from '../../api/preference'
|
||||||
import { getCITableColumns } from '../../utils/helper'
|
import { getCITableColumns } from '../../utils/helper'
|
||||||
@@ -284,6 +284,10 @@ export default {
|
|||||||
const regSort = /(?<=sort=).+/g
|
const regSort = /(?<=sort=).+/g
|
||||||
|
|
||||||
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
|
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
|
||||||
|
// if (exp) {
|
||||||
|
// exp = exp.replace(/(\:)/g, '$1*')
|
||||||
|
// exp = exp.replace(/(\,)/g, '*$1')
|
||||||
|
// }
|
||||||
// 如果是表格点击的排序 以表格为准
|
// 如果是表格点击的排序 以表格为准
|
||||||
let sort
|
let sort
|
||||||
if (sortByTable) {
|
if (sortByTable) {
|
||||||
@@ -314,44 +318,47 @@ export default {
|
|||||||
this.columnsGroup = []
|
this.columnsGroup = []
|
||||||
this.instanceList = []
|
this.instanceList = []
|
||||||
this.totalNumber = res['numfound']
|
this.totalNumber = res['numfound']
|
||||||
|
if (!res['numfound']) {
|
||||||
const oldData = res.result
|
return
|
||||||
|
}
|
||||||
function allKeys(data) {
|
const { attributes: resAllAttributes } = await getCITypeAttributesByTypeIds({
|
||||||
const keys = {}
|
type_ids: Object.keys(res.counter).join(','),
|
||||||
const ignoreAttr = ['_id', '_type', 'ci_type', 'ci_type_alias', 'unique', 'unique_alias']
|
})
|
||||||
data.forEach((item) => {
|
const _columnsGroup = Object.keys(res.counter).map((key) => {
|
||||||
Object.keys(item).forEach((key) => {
|
const _find = this.ciTypes.find((item) => item.name === key)
|
||||||
if (!ignoreAttr.includes(key)) {
|
return {
|
||||||
keys[key] = ''
|
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 = {}
|
const common = {}
|
||||||
data.forEach((item) => {
|
Object.keys(outputKeys).forEach((key) => {
|
||||||
const tmp = {}
|
Object.entries(ciTypeAttribute).forEach(([type, attrs]) => {
|
||||||
Object.keys(outputKeys).forEach((j) => {
|
if (attrs.find((a) => a.name === key)) {
|
||||||
if (j in item) {
|
if (key in common) {
|
||||||
tmp[j] = item[j]
|
common[key][type] = ''
|
||||||
// 提取common
|
|
||||||
{
|
|
||||||
const key = item['ci_type_alias']
|
|
||||||
if (j in common) {
|
|
||||||
common[j][[key]] = ''
|
|
||||||
} else {
|
} else {
|
||||||
common[j] = { [key]: '' }
|
common[key] = { [type]: '' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
tmp[j] = null
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const commonObject = {}
|
const commonObject = {}
|
||||||
const commonKeys = []
|
const commonKeys = []
|
||||||
// 整理common
|
// 整理common
|
||||||
@@ -366,10 +373,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return { commonObject, commonKeys }
|
|
||||||
}
|
|
||||||
|
|
||||||
const { commonObject, commonKeys } = tidy(oldData)
|
|
||||||
const _commonColumnsGroup = Object.keys(commonObject).map((key) => {
|
const _commonColumnsGroup = Object.keys(commonObject).map((key) => {
|
||||||
return {
|
return {
|
||||||
id: `parent-${key}`,
|
id: `parent-${key}`,
|
||||||
@@ -385,24 +389,14 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const _columnsGroup = Object.keys(res.counter).map((key) => {
|
const promises1 = _columnsGroup.map((item) => {
|
||||||
const _find = this.ciTypes.find((item) => item.name === key)
|
|
||||||
return {
|
|
||||||
id: `parent-${_find.id}`,
|
|
||||||
value: key,
|
|
||||||
label: _find?.alias || _find?.name,
|
|
||||||
isCiType: true,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const promises = _columnsGroup.map((item) => {
|
|
||||||
return getSubscribeAttributes(item.id.split('-')[1]).then((res1) => {
|
return getSubscribeAttributes(item.id.split('-')[1]).then((res1) => {
|
||||||
item.children = this.getColumns(res.result, res1.attributes).filter(
|
item.children = this.getColumns(res.result, res1.attributes).filter(
|
||||||
(col) => !commonKeys.includes(col.field)
|
(col) => !commonKeys.includes(col.field)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
await Promise.all(promises).then(() => {
|
await Promise.all(promises1).then(() => {
|
||||||
this.columnsGroup = [..._commonColumnsGroup, ..._columnsGroup]
|
this.columnsGroup = [..._commonColumnsGroup, ..._columnsGroup]
|
||||||
this.instanceList = res['result']
|
this.instanceList = res['result']
|
||||||
})
|
})
|
||||||
|
@@ -371,7 +371,7 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<ci-detail ref="detail" :typeId="Number(typeId)" :treeViewsLevels="treeViewsLevels" />
|
<CiDetailDrawer ref="detail" :typeId="Number(typeId)" :treeViewsLevels="treeViewsLevels" />
|
||||||
<create-instance-form
|
<create-instance-form
|
||||||
ref="create"
|
ref="create"
|
||||||
:typeIdFromRelation="Number(typeId)"
|
:typeIdFromRelation="Number(typeId)"
|
||||||
@@ -404,7 +404,7 @@ import PasswordField from '../../components/passwordField/index.vue'
|
|||||||
import SplitPane from '@/components/SplitPane'
|
import SplitPane from '@/components/SplitPane'
|
||||||
import TreeViewsNode from './modules/treeViewsNode.vue'
|
import TreeViewsNode from './modules/treeViewsNode.vue'
|
||||||
import EditAttrsPopover from '../ci/modules/editAttrsPopover.vue'
|
import EditAttrsPopover from '../ci/modules/editAttrsPopover.vue'
|
||||||
import CiDetail from '../ci/modules/CiDetail'
|
import CiDetailDrawer from '../ci/modules/ciDetailDrawer.vue'
|
||||||
import CreateInstanceForm from '../ci/modules/CreateInstanceForm'
|
import CreateInstanceForm from '../ci/modules/CreateInstanceForm'
|
||||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||||
import JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
|
import JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
|
||||||
@@ -424,7 +424,7 @@ export default {
|
|||||||
SplitPane,
|
SplitPane,
|
||||||
TreeViewsNode,
|
TreeViewsNode,
|
||||||
EditAttrsPopover,
|
EditAttrsPopover,
|
||||||
CiDetail,
|
CiDetailDrawer,
|
||||||
CreateInstanceForm,
|
CreateInstanceForm,
|
||||||
JsonEditor,
|
JsonEditor,
|
||||||
BatchDownload,
|
BatchDownload,
|
||||||
|
@@ -32,10 +32,25 @@ body {
|
|||||||
&.userLayout {
|
&.userLayout {
|
||||||
overflow: auto;
|
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 {
|
.ant-layout {
|
||||||
background-color: #custom_colors() [color_2];
|
background-color: #f7f8fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout.ant-layout {
|
.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 {
|
.ant-layout-content {
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
// background: @layout-background-color-light;
|
|
||||||
//按钮样式
|
|
||||||
.ant-btn {
|
|
||||||
// border-radius: 2px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// footer
|
// footer
|
||||||
@@ -504,12 +508,7 @@ body {
|
|||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
.ops-side-bar.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
|
.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: @layout-sidebar-selected-color;
|
||||||
// background-size: 228px 38px;
|
|
||||||
// background-position-x: -10px;
|
|
||||||
// background-position-y: center;
|
|
||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
.ops-side-bar.ant-menu .ant-menu-submenu .ant-menu-item.ant-menu-item-selected {
|
.ops-side-bar.ant-menu .ant-menu-submenu .ant-menu-item.ant-menu-item-selected {
|
||||||
@@ -518,24 +517,16 @@ body {
|
|||||||
|
|
||||||
@keyframes wordsLoop {
|
@keyframes wordsLoop {
|
||||||
0% {
|
0% {
|
||||||
// transform: translateX(100%);
|
|
||||||
// -webkit-transform: translateX(100%);
|
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
// transform: translateX(-100%);
|
|
||||||
// -webkit-transform: translateX(-100%);
|
|
||||||
margin-left: -300%;
|
margin-left: -300%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-side-bar.ant-menu-light {
|
.ops-side-bar.ant-menu-light {
|
||||||
border-right-color: transparent;
|
border-right-color: transparent;
|
||||||
// background: @layout-background-light-color;
|
|
||||||
// background: url('../assets/sidebar_background.png');
|
|
||||||
background: @layout-sidebar-color;
|
background: @layout-sidebar-color;
|
||||||
// background-position-x: center;
|
|
||||||
// background-position-y: center;
|
|
||||||
background-repeat: no-repeat !important;
|
background-repeat: no-repeat !important;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
.ant-menu-inline.ant-menu-sub {
|
.ant-menu-inline.ant-menu-sub {
|
||||||
@@ -543,14 +534,12 @@ body {
|
|||||||
}
|
}
|
||||||
.ant-menu-submenu-content .ant-menu-item,
|
.ant-menu-submenu-content .ant-menu-item,
|
||||||
.ant-menu-item {
|
.ant-menu-item {
|
||||||
// margin: 0;
|
|
||||||
> a {
|
> a {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: @layout-sidebar-font-color;
|
color: @layout-sidebar-font-color;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
// background: #0000000a;
|
|
||||||
.scroll {
|
.scroll {
|
||||||
animation: 5s wordsLoop linear infinite normal;
|
animation: 5s wordsLoop linear infinite normal;
|
||||||
}
|
}
|
||||||
@@ -828,13 +817,13 @@ body {
|
|||||||
.el-button.is-plain:hover,
|
.el-button.is-plain:hover,
|
||||||
.el-input.is-active .el-input__inner,
|
.el-input.is-active .el-input__inner,
|
||||||
.el-input__inner:focus {
|
.el-input__inner:focus {
|
||||||
border-color: #custom_colors() [color_1] !important;
|
border-color: @primary-color !important;
|
||||||
}
|
}
|
||||||
.el-button--text,
|
.el-button--text,
|
||||||
.el-select-dropdown__item.selected,
|
.el-select-dropdown__item.selected,
|
||||||
.el-button.is-plain:focus,
|
.el-button.is-plain:focus,
|
||||||
.el-button.is-plain:hover {
|
.el-button.is-plain:hover {
|
||||||
color: #custom_colors() [color_1] !important;
|
color: @primary-color !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-tabs-nav .ant-tabs-tab {
|
.ant-tabs-nav .ant-tabs-tab {
|
||||||
@@ -863,7 +852,7 @@ body {
|
|||||||
.ant-layout-sider {
|
.ant-layout-sider {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
.ant-layout-sider-children {
|
.ant-layout-sider-children {
|
||||||
background: #custom_colors[color_2];
|
background: @primary-color_5;
|
||||||
.ant-menu {
|
.ant-menu {
|
||||||
display: none;
|
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;
|
background-color: @bgColor;
|
||||||
border: @border;
|
border: @border;
|
||||||
}
|
}
|
||||||
@@ -913,6 +902,9 @@ body {
|
|||||||
border: none;
|
border: none;
|
||||||
box-shadow: 0px 4px 6px rgba(78, 94, 160, 0.25) !important;
|
box-shadow: 0px 4px 6px rgba(78, 94, 160, 0.25) !important;
|
||||||
}
|
}
|
||||||
|
.vue-treeselect__limit-tip-text {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自定义背景颜色和border
|
// 自定义背景颜色和border
|
||||||
@@ -934,30 +926,30 @@ body {
|
|||||||
}
|
}
|
||||||
.vue-treeselect__option--highlight,
|
.vue-treeselect__option--highlight,
|
||||||
.vue-treeselect__option--selected {
|
.vue-treeselect__option--selected {
|
||||||
color: #custom_colors[color_1];
|
color: @primary-color;
|
||||||
background-color: #custom_colors() [color_2] !important;
|
background-color: @primary-color_5 !important;
|
||||||
}
|
}
|
||||||
.vue-treeselect__checkbox--checked,
|
.vue-treeselect__checkbox--checked,
|
||||||
.vue-treeselect__checkbox--indeterminate {
|
.vue-treeselect__checkbox--indeterminate {
|
||||||
border-color: #custom_colors() [color_1] !important;
|
border-color: @primary-color !important;
|
||||||
background: #custom_colors() [color_1] !important;
|
background: @primary-color !important;
|
||||||
}
|
}
|
||||||
.vue-treeselect__label-container:hover {
|
.vue-treeselect__label-container:hover {
|
||||||
.vue-treeselect__checkbox--checked,
|
.vue-treeselect__checkbox--checked,
|
||||||
.vue-treeselect__checkbox--indeterminate {
|
.vue-treeselect__checkbox--indeterminate {
|
||||||
border-color: #custom_colors() [color_1] !important;
|
border-color: @primary-color !important;
|
||||||
background: #custom_colors() [color_1] !important;
|
background: @primary-color !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.vue-treeselect__multi-value-item {
|
.vue-treeselect__multi-value-item {
|
||||||
background: #custom_colors() [color_2] !important;
|
background: @primary-color_5 !important;
|
||||||
color: #custom_colors() [color_1] !important;
|
color: @primary-color !important;
|
||||||
}
|
}
|
||||||
.vue-treeselect__value-remove {
|
.vue-treeselect__value-remove {
|
||||||
color: #custom_colors() [color_1] !important;
|
color: @primary-color !important;
|
||||||
}
|
}
|
||||||
.vue-treeselect__label-container:hover .vue-treeselect__checkbox--unchecked {
|
.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;
|
border: none !important;
|
||||||
}
|
}
|
||||||
.vxe-table--header-wrapper {
|
.vxe-table--header-wrapper {
|
||||||
background-color: #custom_colors() [color_2] !important;
|
background-color: @primary-color_5 !important;
|
||||||
}
|
}
|
||||||
.vxe-header--row .vxe-header--column:hover {
|
.vxe-header--row .vxe-header--column:hover {
|
||||||
background: #2f54eb1f !important;
|
background: #2f54eb1f !important;
|
||||||
@@ -991,7 +983,7 @@ body {
|
|||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
.vxe-table--header-wrapper {
|
.vxe-table--header-wrapper {
|
||||||
background-color: #custom_colors() [color_2] !important;
|
background-color: @primary-color_5 !important;
|
||||||
}
|
}
|
||||||
// .vxe-table--header-wrapper.body--wrapper {
|
// .vxe-table--header-wrapper.body--wrapper {
|
||||||
// border-radius: 8px !important;
|
// border-radius: 8px !important;
|
||||||
@@ -1025,12 +1017,12 @@ body {
|
|||||||
.ops-input {
|
.ops-input {
|
||||||
.ant-input,
|
.ant-input,
|
||||||
.ant-time-picker-input {
|
.ant-time-picker-input {
|
||||||
background-color: #custom_colors[color_2];
|
background-color: @primary-color_5;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ops-input.ant-input {
|
.ops-input.ant-input {
|
||||||
background-color: #custom_colors[color_2];
|
background-color: @primary-color_5;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
.ops-input.ant-input[disabled] {
|
.ops-input.ant-input[disabled] {
|
||||||
@@ -1101,7 +1093,7 @@ body {
|
|||||||
.vxe-pager .vxe-pager--prev-btn:not(.is--disabled):focus,
|
.vxe-pager .vxe-pager--prev-btn:not(.is--disabled):focus,
|
||||||
.vxe-button.type--text:not(.is--disabled):hover,
|
.vxe-button.type--text:not(.is--disabled):hover,
|
||||||
.vxe-table--filter-footer > button:hover {
|
.vxe-table--filter-footer > button:hover {
|
||||||
color: #custom_colors() [color_1] !important;
|
color: @primary-color !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vxe-cell .vxe-default-input:focus,
|
.vxe-cell .vxe-default-input:focus,
|
||||||
@@ -1112,13 +1104,13 @@ body {
|
|||||||
.vxe-table--filter-wrapper .vxe-default-textarea:focus,
|
.vxe-table--filter-wrapper .vxe-default-textarea:focus,
|
||||||
.vxe-select.is--active:not(.is--filter) > .vxe-input .vxe-input--inner,
|
.vxe-select.is--active:not(.is--filter) > .vxe-input .vxe-input--inner,
|
||||||
.vxe-input:not(.is--disabled).is--active .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 {
|
.ops-list-batch-action {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: #custom_colors[color_2];
|
background-color: @primary-color_5;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: rgba(0, 0, 0, 0.55);
|
color: rgba(0, 0, 0, 0.55);
|
||||||
> span {
|
> span {
|
||||||
@@ -1126,11 +1118,11 @@ body {
|
|||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #custom_colors[color_1];
|
color: @primary-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
> span:last-child {
|
> span:last-child {
|
||||||
color: #custom_colors[color_1];
|
color: @primary-color;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1139,17 +1131,17 @@ body {
|
|||||||
.ops-tab.ant-tabs.ant-tabs-card {
|
.ops-tab.ant-tabs.ant-tabs-card {
|
||||||
.ant-tabs-card-bar {
|
.ant-tabs-card-bar {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
border-bottom: none;
|
||||||
.ant-tabs-nav-container {
|
.ant-tabs-nav-container {
|
||||||
background-color: #fff;
|
|
||||||
.ant-tabs-tab {
|
.ant-tabs-tab {
|
||||||
border: none;
|
border: none;
|
||||||
border-top-left-radius: 4px;
|
border-top-left-radius: 4px;
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
background: rgba(255, 255, 255, 0.5);
|
background: @primary-color_6;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
.ant-tabs-tab-active {
|
.ant-tabs-tab-active {
|
||||||
background: #custom_colors[color_2];
|
background: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1157,9 +1149,10 @@ body {
|
|||||||
|
|
||||||
//button
|
//button
|
||||||
.ops-button-primary {
|
.ops-button-primary {
|
||||||
background-color: #custom_colors[color_2];
|
background-color: @primary-color_4;
|
||||||
border-color: #custom_colors[color_2];
|
border-color: @primary-color_4;
|
||||||
color: #custom_colors[color_1];
|
color: @primary-color;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
//select
|
//select
|
||||||
@@ -1181,8 +1174,8 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ant-select-selection {
|
.ant-select-selection {
|
||||||
background-color: #custom_colors[color_2];
|
background-color: @primary-color_5;
|
||||||
border-color: #custom_colors[color_2];
|
border-color: @primary-color_5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1190,8 +1183,8 @@ body {
|
|||||||
.ops-dropdown {
|
.ops-dropdown {
|
||||||
.ant-dropdown-menu-item:hover,
|
.ant-dropdown-menu-item:hover,
|
||||||
.ant-dropdown-menu-submenu-title:hover {
|
.ant-dropdown-menu-submenu-title:hover {
|
||||||
background-color: #custom_colors[color_2];
|
background-color: @primary-color_5;
|
||||||
color: #custom_colors[color_1];
|
color: @primary-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1203,7 +1196,7 @@ body {
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
.ant-modal-title {
|
.ant-modal-title {
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
border-left: 4px solid #custom_colors[color_1];
|
border-left: 4px solid @primary-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ant-modal-footer {
|
.ant-modal-footer {
|
||||||
@@ -1216,7 +1209,7 @@ body {
|
|||||||
width: 276px;
|
width: 276px;
|
||||||
}
|
}
|
||||||
.ant-tooltip-inner {
|
.ant-tooltip-inner {
|
||||||
background-color: #custom_colors[color_3];
|
background-color: @primary-color_3;
|
||||||
border-radius: '4px';
|
border-radius: '4px';
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@@ -1231,7 +1224,7 @@ body {
|
|||||||
.ant-tooltip-arrow::before {
|
.ant-tooltip-arrow::before {
|
||||||
width: 7px;
|
width: 7px;
|
||||||
height: 7px;
|
height: 7px;
|
||||||
background-color: #custom_colors[color_3];
|
background-color: @primary-color_3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1241,7 +1234,7 @@ body {
|
|||||||
|
|
||||||
.el-tabs__header {
|
.el-tabs__header {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
background-color: #custom_colors[color_2];
|
background-color: @primary-color_5;
|
||||||
border-radius: 8px 8px 0px 0px;
|
border-radius: 8px 8px 0px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1252,7 +1245,7 @@ body {
|
|||||||
|
|
||||||
.el-tabs__header .el-tabs__item.is-active {
|
.el-tabs__header .el-tabs__item.is-active {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
color: #custom_colors[color_1];
|
color: @primary-color;
|
||||||
}
|
}
|
||||||
.el-tabs__header .el-tabs__item:first-child.is-active {
|
.el-tabs__header .el-tabs__item:first-child.is-active {
|
||||||
border-top-left-radius: 8px;
|
border-top-left-radius: 8px;
|
||||||
@@ -1263,12 +1256,12 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-radio__input.is-checked .el-radio__inner {
|
.el-radio__input.is-checked .el-radio__inner {
|
||||||
background-color: #custom_colors[color_1];
|
background-color: @primary-color;
|
||||||
border-color: #custom_colors[color_1];
|
border-color: @primary-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-radio__input.is-checked + .el-radio__label {
|
.el-radio__input.is-checked + .el-radio__label {
|
||||||
color: #custom_colors[color_1];
|
color: @primary-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tab-pane {
|
.el-tab-pane {
|
||||||
@@ -1306,14 +1299,14 @@ body {
|
|||||||
|
|
||||||
// a-drop-down
|
// a-drop-down
|
||||||
.ant-dropdown-menu-item-active {
|
.ant-dropdown-menu-item-active {
|
||||||
color: #custom_colors[color_1];
|
color: @primary-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-tag {
|
.ant-tag {
|
||||||
&.ops-perm-tag {
|
&.ops-perm-tag {
|
||||||
border: none;
|
border: none;
|
||||||
background-color: #custom_colors[color_2];
|
background-color: @primary-color_5;
|
||||||
color: #custom_colors[color_1];
|
color: @primary-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1332,7 +1325,7 @@ body {
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
div.jsoneditor-menu {
|
div.jsoneditor-menu {
|
||||||
border-bottom-color: #custom_colors[color_1];
|
border-bottom-color: @primary-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// .ant-menu.ant-menu-light {
|
// .ant-menu.ant-menu-light {
|
||||||
|
@@ -1,5 +1,20 @@
|
|||||||
@border-radius-base: 2px; // 组件/浮层圆角
|
@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);
|
@scrollbar-color: rgba(47, 122, 235, 0.2);
|
||||||
|
|
||||||
@layout-header-background: #fff;
|
@layout-header-background: #fff;
|
||||||
@@ -28,7 +43,7 @@
|
|||||||
color_3: #d2e2ff;
|
color_3: #d2e2ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops_display_wrapper(@backgroundColor:#custom_colors()[color_2]) {
|
.ops_display_wrapper(@backgroundColor:@primary-color_5) {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 5px 8px;
|
padding: 5px 8px;
|
||||||
background-color: @backgroundColor;
|
background-color: @backgroundColor;
|
||||||
@@ -42,10 +57,10 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #custom_colors[color_2];
|
background-color: @primary-color_5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ops_popover_item_selected() {
|
.ops_popover_item_selected() {
|
||||||
background-color: #custom_colors[color_2];
|
background-color: @primary-color_5;
|
||||||
color: #custom_colors[color_1];
|
color: @primary-color;
|
||||||
}
|
}
|
||||||
|
@@ -10,11 +10,12 @@
|
|||||||
:noOptionsText="$t('cs.components.empty')"
|
:noOptionsText="$t('cs.components.empty')"
|
||||||
:class="className ? className : 'ops-setting-treeselect'"
|
:class="className ? className : 'ops-setting-treeselect'"
|
||||||
value-consists-of="LEAF_PRIORITY"
|
value-consists-of="LEAF_PRIORITY"
|
||||||
:limit="20"
|
:limit="limit"
|
||||||
:limitText="(count) => `+ ${count}`"
|
:limitText="(count) => `+ ${count}`"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
appendToBody
|
appendToBody
|
||||||
:zIndex="1050"
|
:zIndex="1050"
|
||||||
|
:flat="flat"
|
||||||
>
|
>
|
||||||
</treeselect>
|
</treeselect>
|
||||||
</template>
|
</template>
|
||||||
@@ -60,6 +61,14 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: 'employee_id',
|
default: 'employee_id',
|
||||||
},
|
},
|
||||||
|
limit: {
|
||||||
|
type: Number,
|
||||||
|
default: 20,
|
||||||
|
},
|
||||||
|
flat: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {}
|
||||||
|
@@ -139,7 +139,7 @@ export default {
|
|||||||
console.log('login form', values)
|
console.log('login form', values)
|
||||||
const loginParams = { ...values }
|
const loginParams = { ...values }
|
||||||
delete loginParams.username
|
delete loginParams.username
|
||||||
loginParams[!state.loginType ? 'email' : 'username'] = values.username
|
loginParams.username = values.username
|
||||||
loginParams.password = appConfig.useEncryption ? md5(values.password) : values.password
|
loginParams.password = appConfig.useEncryption ? md5(values.password) : values.password
|
||||||
localStorage.setItem('ops_auth_type', '')
|
localStorage.setItem('ops_auth_type', '')
|
||||||
Login({ userInfo: loginParams })
|
Login({ userInfo: loginParams })
|
||||||
|
@@ -33,7 +33,7 @@ services:
|
|||||||
- redis
|
- redis
|
||||||
|
|
||||||
cmdb-api:
|
cmdb-api:
|
||||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.3.10
|
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.3.13
|
||||||
# build:
|
# build:
|
||||||
# context: .
|
# context: .
|
||||||
# target: cmdb-api
|
# target: cmdb-api
|
||||||
@@ -57,6 +57,8 @@ services:
|
|||||||
nohup flask cmdb-trigger > trigger.log 2>&1 &
|
nohup flask cmdb-trigger > trigger.log 2>&1 &
|
||||||
flask cmdb-init-cache
|
flask cmdb-init-cache
|
||||||
flask cmdb-init-acl
|
flask cmdb-init-acl
|
||||||
|
flask init-import-user-from-acl
|
||||||
|
flask init-department
|
||||||
flask cmdb-counter > counter.log 2>&1
|
flask cmdb-counter > counter.log 2>&1
|
||||||
|
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -68,7 +70,7 @@ services:
|
|||||||
- cmdb-api
|
- cmdb-api
|
||||||
|
|
||||||
cmdb-ui:
|
cmdb-ui:
|
||||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.3.10
|
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.3.13
|
||||||
# build:
|
# build:
|
||||||
# context: .
|
# context: .
|
||||||
# target: cmdb-ui
|
# 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**
|
**set database in config file cmdb-api/settings.py**
|
||||||
|
|
||||||
- In cmdb directory,start in order as follows:
|
- In cmdb directory,start in order as follows:
|
||||||
- enviroment: `make env`
|
- environment: `make env`
|
||||||
- start API: `make api`
|
- start API: `make api`
|
||||||
- start UI: `make ui`
|
- start UI: `make ui`
|
||||||
- start worker: `make worker`
|
- start worker: `make worker`
|
||||||
|
Reference in New Issue
Block a user