Compare commits

..

65 Commits

Author SHA1 Message Date
pycook
5266cb5b88 release: 2.4.2 2024-04-03 15:55:13 +08:00
dagongren
c7d4bec988 feat(cmdb-ui): attributes relation (#463) 2024-04-03 15:27:54 +08:00
pycook
099ddd6ca9 feat(api): rebuild relation by attribute (#462) 2024-04-03 15:13:43 +08:00
pycook
bd813174b1 feat(api): build relation by attributes (#461) 2024-04-02 09:19:51 +08:00
dagongren
0a43680d6e feat:add icons (#460) 2024-04-01 17:37:00 +08:00
dagongren
976c6cfe91 fix:topmenu shake & change logo (#459) 2024-04-01 15:11:24 +08:00
pycook
cf594f04ba fix(api): import CIType 2024-03-29 15:50:07 +08:00
ivonGwy
4232094aed fix: discover scripts (#458)
Co-authored-by: wang-liang0615 <dhuwl0615@163.com>
2024-03-29 15:48:46 +08:00
dagongren
d08827d086 style and 文案变更 (#457) 2024-03-29 15:02:18 +08:00
pycook
d25ae532cd fix(api): CIType template import 2024-03-29 14:20:56 +08:00
pycook
9fbb6ee64d docs: docker-compose changed to docker compose 2024-03-29 13:27:23 +08:00
pycook
b62f0e96fd Merge branch 'master' of github.com:veops/cmdb 2024-03-29 13:14:00 +08:00
pycook
c1bcd0ce45 fix(acl): del resource 2024-03-29 13:13:38 +08:00
dagongren
c8b55c34eb i18n (#456) 2024-03-29 13:11:52 +08:00
pycook
4b5906770f release: v2.4.1 2024-03-29 12:47:23 +08:00
dagongren
4188ac7252 i18n (#455) 2024-03-29 12:23:23 +08:00
dagongren
2efbc6474a style && service tree define (#454) 2024-03-29 11:53:43 +08:00
pycook
03eac0c4d2 pref(api): error tips for out of range value (#453) 2024-03-29 11:46:50 +08:00
dagongren
2a861250eb fix:icon/filter/router...and some bugs (#451) 2024-03-29 10:50:14 +08:00
dagongren
8fc19d8b7c icon font && opsTable (#450) 2024-03-28 20:59:32 +08:00
dagongren
430d2ff6d0 fix:cmdbgrant (#449) 2024-03-28 20:16:47 +08:00
dagongren
2517009d70 fix:Login (#448) 2024-03-28 19:56:54 +08:00
dagongren
67da360d80 Dev UI 240328 (#447)
* feat:ui 全面升级

* feat:ui全面升级
2024-03-28 19:53:54 +08:00
dagongren
24c56fb259 feat:ui 全面升级 (#446)
* feat:ui 全面升级

* feat:ui全面升级
2024-03-28 19:47:46 +08:00
pycook
37d5da65de Dev api 240328 (#445)
* feat(api): login api supports parameter auth_with_ldap

* fix(api): transfer attribute
2024-03-28 19:12:47 +08:00
dagongren
2224ebd533 feat:ui 全面升级 (#444) 2024-03-28 18:38:15 +08:00
pycook
bf6331d215 fix(api): batch import ci relation 2024-03-26 20:38:39 +08:00
pycook
b18b90ab4e fix(api): import CIType
fix(api): import CIType
2024-03-26 16:53:10 +08:00
pycook
702e17a7a4 fix(api): revoke service tree node permissions
fix(api): revoke service tree node permissions
2024-03-26 12:05:22 +08:00
pycook
a7586aa140 feat(api): support service tree editing (#437) 2024-03-26 10:58:11 +08:00
simontigers
ad3f96431c Merge pull request #431 from simontigers/common_employee_edit_department_in_acl
fix(api): common_employee_edit department in acl role
2024-03-25 11:46:30 +08:00
hu.sima
1515820713 fix(api): common_employee_edit department in acl role 2024-03-25 11:46:04 +08:00
simontigers
7728b57878 Merge pull request #430 from simontigers/common_file_ext_check
fix(api): check file ext with magic
2024-03-25 11:17:36 +08:00
hu.sima
a419eefd72 fix(api): check file ext with magic 2024-03-25 11:16:04 +08:00
simontigers
a44e5f6cf1 Merge pull request #429 from simontigers/common_check_new_columns
fix(api): common check new columns
2024-03-22 17:52:19 +08:00
simontigers
7d46e92c2d fix(api): common check new columns 2024-03-22 16:48:16 +08:00
pycook
4117cf87ec Merge branch 'master' of github.com:veops/cmdb 2024-03-20 11:56:49 +08:00
pycook
9e0fe0b818 fix: custom dashboard 2024-03-20 11:56:39 +08:00
dagongren
2a8f1ab9a4 style:update global.less (#426) 2024-03-19 10:01:38 +08:00
pycook
c0fe99b8c7 release: 2.3.13 2024-03-18 20:35:51 +08:00
dagongren
42feb4b862 feat(cmdb-ui):service tree grant (#425) 2024-03-18 19:59:16 +08:00
pycook
482d34993b Dev api 0308 (#424)
* feat(api): grant by node in relation view

* fix(api): When removing attributes, remove the unique constraint

* feat(api): grant by service tree
2024-03-18 19:57:25 +08:00
simontigers
7ff309b8b8 fix(api): edit employee depart with rid=0 (#420) 2024-03-12 17:46:50 +08:00
rustrover
98eb47d44f fix: some typos (#415)
Signed-off-by: gcmutator <329964069@qq.com>
Co-authored-by: gcmutator <329964069@qq.com>
2024-03-11 15:04:38 +08:00
pycook
9ab0f624ef fix(api): remove ACL resources when deleting CIType (#414) 2024-03-08 16:31:03 +08:00
pycook
3f3eda8b3c fix(api): issule #412, unique value restrictions (#413) 2024-03-05 16:21:27 +08:00
pycook
f788adc8cf feat(api): multi-id search (#411)
_id:(id1;id2)
2024-03-04 15:15:34 +08:00
simontigers
693ae4ff05 fix: deploy init common (#407) 2024-03-01 17:21:32 +08:00
pycook
a1a9d99eb4 release: v2.3.12 2024-03-01 17:04:38 +08:00
dagongren
e045e0fb43 fix(cmdb-ui):to lowercase (#406) 2024-03-01 13:52:30 +08:00
pycook
09376dbd2b feat(api): CIType inheritance (#405) 2024-03-01 13:51:13 +08:00
dagongren
7fda5a1e7b feat(cmdb-ui):ci type inherit (#404) 2024-03-01 13:39:20 +08:00
dagongren
113b84763f feat:ci detail share (#403) 2024-02-27 16:13:28 +08:00
dagongren
190170acad fix(cmdb-ui):triggers webhook headers (#402) 2024-02-26 13:46:40 +08:00
pycook
513d2af4b8 feat(api): Remove many-to-many restrictions (#401) 2024-02-26 10:17:53 +08:00
pycook
4588bd8996 fix(api): db-setup commands (#399)
fix(api): db-setup commands
2024-02-23 11:05:11 +08:00
dagongren
082da5fade fix(cmdb-ui):resource search common attrs (#397) 2024-02-22 16:19:12 +08:00
pycook
013b116eb5 feat(acl): login channel add ssh options (#396) 2024-02-21 18:10:44 +08:00
simontigers
208d29165b fix: grant common perm after create new employee (#394) 2024-02-04 13:48:02 +08:00
dagongren
d510330cde fix(cmdb-ui):fix multiple default value (#395) 2024-02-04 11:49:44 +08:00
wang-liang0615
ea4f0fc2a5 fix(ui):login email-》username (#393) 2024-01-31 15:52:27 +08:00
pycook
9bcdaacdc4 docs: update init sql
docs: update init sql
2024-01-26 13:57:36 +08:00
pycook
5045581ddf feat(api): Auto-increment id can be used as primary key (#391) 2024-01-26 13:12:17 +08:00
simontigers
232913172c fix: change common_setting task queue (#390) 2024-01-25 17:39:52 +08:00
pycook
157e1809ed release: 2.3.11 2024-01-13 15:06:51 +08:00
155 changed files with 14115 additions and 8858 deletions

View File

@@ -74,17 +74,17 @@
### Docker 一键快速构建 ### Docker 一键快速构建
> 方法一 > 方法一
- 第一步: 先安装 docker 环境, 以及docker-compose - 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
- 第二步: 拷贝项目 - 第二步: 拷贝项目
```shell ```shell
git clone https://github.com/veops/cmdb.git git clone https://github.com/veops/cmdb.git
``` ```
- 第三步:进入主目录,执行: - 第三步:进入主目录,执行:
``` ```
docker-compose up -d docker compose up -d
``` ```
> 方法二, 该方法适用于linux系统 > 方法二, 该方法适用于linux系统
- 第一步: 先安装 docker 环境, 以及docker-compose - 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载` - 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
```shell ```shell
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/master/install.sh curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/master/install.sh

View File

@@ -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
@@ -65,6 +66,7 @@ hvac = "==2.0.0"
colorama = ">=0.4.6" colorama = ">=0.4.6"
pycryptodomex = ">=3.19.0" pycryptodomex = ">=3.19.0"
lz4 = ">=4.3.2" lz4 = ">=4.3.2"
python-magic = "==0.4.27"
[dev-packages] [dev-packages]
# Testing # Testing

View File

@@ -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

View File

@@ -4,7 +4,7 @@ from flask.cli import with_appcontext
from werkzeug.datastructures import MultiDict from werkzeug.datastructures import MultiDict
from api.lib.common_setting.acl import ACLManager from api.lib.common_setting.acl import ACLManager
from api.lib.common_setting.employee import EmployeeAddForm 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):
@@ -263,7 +224,7 @@ def common_check_new_columns():
column_type = new_column.type.compile(engine.dialect) column_type = new_column.type.compile(engine.dialect)
default_value = new_column.default.arg if new_column.default else None default_value = new_column.default.arg if new_column.default else None
sql = "ALTER TABLE " + target_table_name + " ADD COLUMN " + new_column.name + " " + column_type sql = "ALTER TABLE " + target_table_name + " ADD COLUMN " + f"`{new_column.name}`" + " " + column_type
if new_column.comment: if new_column.comment:
sql += f" comment '{new_column.comment}'" sql += f" comment '{new_column.comment}'"

View File

@@ -89,9 +89,12 @@ def db_setup():
""" """
db.create_all() db.create_all()
db.session.execute("set global sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE," try:
"ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'") db.session.execute("set global sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,"
db.session.commit() "ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'")
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'")

View File

@@ -330,15 +330,15 @@ 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 for i in CITypeAttributeManager.get_attributes_by_type_id(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)
for adt in adts: for adt in adts:
attr_names |= set((adt.attributes or {}).values()) attr_names |= set((adt.attributes or {}).values())
return [attr.to_dict() for attr in attributes if attr.name in attr_names] return [attr for attr in attributes if attr['name'] in attr_names]
@classmethod @classmethod
def search(cls, page, page_size, fl=None, **kwargs): def search(cls, page, page_size, fl=None, **kwargs):

View File

@@ -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

View File

@@ -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,16 +278,16 @@ 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:
return int(max_v.value) + 1 return int(max_v.value) + 1
return 1 return 1
@classmethod @classmethod
def add(cls, ci_type_name, def add(cls, ci_type_name,
@@ -296,6 +295,7 @@ class CIManager(object):
_no_attribute_policy=ExistPolicy.IGNORE, _no_attribute_policy=ExistPolicy.IGNORE,
is_auto_discovery=False, is_auto_discovery=False,
_is_admin=False, _is_admin=False,
ticket_id=None,
**ci_dict): **ci_dict):
""" """
add ci add ci
@@ -304,19 +304,24 @@ class CIManager(object):
:param _no_attribute_policy: ignore or reject :param _no_attribute_policy: ignore or reject
:param is_auto_discovery: default is False :param is_auto_discovery: default is False
:param _is_admin: default is False :param _is_admin: default is False
:param ticket_id:
:param ci_dict: :param ci_dict:
:return: :return:
""" """
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 = ci_dict.get(unique_key.name) or ci_dict.get(unique_key.alias) or ci_dict.get(unique_key.id) unique_value = None
unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name)) if not (unique_key.default and unique_key.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and
not ci_dict.get(unique_key.name)): # primary key is not auto inc id
unique_value = ci_dict.get(unique_key.name) or ci_dict.get(unique_key.alias) or ci_dict.get(unique_key.id)
unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name))
attrs = CITypeAttributesCache.get2(ci_type_name) attrs = CITypeAttributeManager.get_all_attributes(ci_type.id)
ci_type_attrs_name = {attr.name: attr for _, attr in attrs} ci_type_attrs_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 +329,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 +358,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 +394,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 +406,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:
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k)) if k in raw_dict:
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
else:
ci_dict.pop(k)
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name or k in ci_type_attrs_alias} 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}
@@ -403,7 +419,7 @@ class CIManager(object):
operate_type = OperateType.UPDATE if ci is not None else OperateType.ADD operate_type = OperateType.UPDATE if ci is not None else OperateType.ADD
try: try:
ci = ci or CI.create(type_id=ci_type.id, is_auto_discovery=is_auto_discovery) ci = ci or CI.create(type_id=ci_type.id, is_auto_discovery=is_auto_discovery)
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, ticket_id=ticket_id)
except BadRequest as e: except BadRequest as e:
if existed is None: if existed is None:
cls.delete(ci.id) cls.delete(ci.id)
@@ -421,11 +437,13 @@ class CIManager(object):
return ci.id return ci.id
def update(self, ci_id, _is_admin=False, **ci_dict): def update(self, ci_id, _is_admin=False, ticket_id=None, **ci_dict):
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,20 +472,24 @@ 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:
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k)) if k in raw_dict:
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
else:
ci_dict.pop(k)
try: 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, ticket_id=ticket_id)
except BadRequest as e: except BadRequest as e:
raise e raise e
@@ -512,8 +534,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 +561,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 +868,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 +937,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
@@ -924,7 +960,7 @@ class CIRelationManager(object):
return abort(400, ErrFormat.relation_constraint.format("1-N")) return abort(400, ErrFormat.relation_constraint.format("1-N"))
@classmethod @classmethod
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, ancestor_ids=None): def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, ancestor_ids=None, valid=True):
first_ci = CIManager.confirm_ci_existed(first_ci_id) first_ci = CIManager.confirm_ci_existed(first_ci_id)
second_ci = CIManager.confirm_ci_existed(second_ci_id) second_ci = CIManager.confirm_ci_existed(second_ci_id)
@@ -949,7 +985,7 @@ class CIRelationManager(object):
relation_type_id or abort(404, ErrFormat.relation_not_found.format("{} -> {}".format( relation_type_id or abort(404, ErrFormat.relation_not_found.format("{} -> {}".format(
first_ci.ci_type.name, second_ci.ci_type.name))) first_ci.ci_type.name, second_ci.ci_type.name)))
if current_app.config.get('USE_ACL'): if current_app.config.get('USE_ACL') and valid:
resource_name = CITypeRelationManager.acl_resource_name(first_ci.ci_type.name, resource_name = CITypeRelationManager.acl_resource_name(first_ci.ci_type.name,
second_ci.ci_type.name) second_ci.ci_type.name)
if not ACLManager().has_permission( if not ACLManager().has_permission(
@@ -961,7 +997,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 +1033,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 +1045,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):
@@ -1047,11 +1088,62 @@ class CIRelationManager(object):
for ci_id in ci_ids: for ci_id in ci_ids:
cls.delete_2(parent_id, ci_id, ancestor_ids=ancestor_ids) cls.delete_2(parent_id, ci_id, ancestor_ids=ancestor_ids)
@classmethod
def build_by_attribute(cls, ci_dict):
type_id = ci_dict['_type']
child_items = CITypeRelation.get_by(parent_id=type_id, only_query=True).filter(
CITypeRelation.parent_attr_id.isnot(None))
for item in child_items:
parent_attr = AttributeCache.get(item.parent_attr_id)
child_attr = AttributeCache.get(item.child_attr_id)
attr_value = ci_dict.get(parent_attr.name)
value_table = TableMap(attr=child_attr).table
for child in value_table.get_by(attr_id=child_attr.id, value=attr_value, only_query=True).join(
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.child_id):
CIRelationManager.add(ci_dict['_id'], child.ci_id, valid=False)
parent_items = CITypeRelation.get_by(child_id=type_id, only_query=True).filter(
CITypeRelation.child_attr_id.isnot(None))
for item in parent_items:
parent_attr = AttributeCache.get(item.parent_attr_id)
child_attr = AttributeCache.get(item.child_attr_id)
attr_value = ci_dict.get(child_attr.name)
value_table = TableMap(attr=parent_attr).table
for parent in value_table.get_by(attr_id=parent_attr.id, value=attr_value, only_query=True).join(
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.parent_id):
CIRelationManager.add(parent.ci_id, ci_dict['_id'], valid=False)
@classmethod
def rebuild_all_by_attribute(cls, ci_type_relation):
parent_attr = AttributeCache.get(ci_type_relation['parent_attr_id'])
child_attr = AttributeCache.get(ci_type_relation['child_attr_id'])
if not parent_attr or not child_attr:
return
parent_value_table = TableMap(attr=parent_attr).table
child_value_table = TableMap(attr=child_attr).table
parent_values = parent_value_table.get_by(attr_id=parent_attr.id, only_query=True).join(
CI, CI.id == parent_value_table.ci_id).filter(CI.type_id == ci_type_relation['parent_id'])
child_values = child_value_table.get_by(attr_id=child_attr.id, only_query=True).join(
CI, CI.id == child_value_table.ci_id).filter(CI.type_id == ci_type_relation['child_id'])
child_value2ci_ids = {}
for child in child_values:
child_value2ci_ids.setdefault(child.value, []).append(child.ci_id)
for parent in parent_values:
for child_ci_id in child_value2ci_ids.get(parent.value, []):
try:
cls.add(parent.ci_id, child_ci_id, valid=False)
except:
pass
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

View File

@@ -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()
for attr in sorted(attrs, key=lambda x: (x.order, x.id)):
attr_dict = AttributeManager().get_attribute(attr.attr_id, choice_web_hook_parse, choice_other_parse)
attr_dict["is_required"] = attr.is_required
attr_dict["order"] = attr.order
attr_dict["default_show"] = attr.default_show
if not has_config_perm:
attr_dict.pop('choice_web_hook', None)
attr_dict.pop('choice_other', None)
result.append(attr_dict) result = list()
id2pos = dict()
type2name = {i: CITypeCache.get(i) for i in parent_ids}
for _type_id in parent_ids + [type_id]:
attrs = CITypeAttributesCache.get(_type_id)
for attr in sorted(attrs, key=lambda x: (x.order, x.id)):
attr_dict = AttributeManager().get_attribute(attr.attr_id, choice_web_hook_parse, choice_other_parse)
attr_dict["is_required"] = attr.is_required
attr_dict["order"] = attr.order
attr_dict["default_show"] = attr.default_show
attr_dict["inherited"] = False if _type_id == type_id else True
attr_dict["inherited_from"] = type2name.get(_type_id) and type2name[_type_id].alias
if not has_config_perm:
attr_dict.pop('choice_web_hook', None)
attr_dict.pop('choice_other', None)
if attr_dict['id'] not in id2pos:
id2pos[attr_dict['id']] = len(result)
result.append(attr_dict)
else:
result[id2pos[attr_dict['id']]] = attr_dict
return result 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,34 @@ 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)
for item in (CITypeRelation.get_by(parent_id=type_id, parent_attr_id=attr_id, to_dict=False) +
CITypeRelation.get_by(child_id=type_id, child_attr_id=attr_id, to_dict=False)):
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 +691,22 @@ 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 not to_group_id and CITypeInheritance.get_by(child_id=type_id, to_dict=False):
to_group = CITypeAttributeGroup.create(type_id=type_id, name=to_group_name)
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)
@@ -558,14 +734,19 @@ class CITypeRelationManager(object):
@staticmethod @staticmethod
def get(): def get():
res = CITypeRelation.get_by(to_dict=False) res = CITypeRelation.get_by(to_dict=False)
type2attributes = dict()
for idx, item in enumerate(res): for idx, item in enumerate(res):
_item = item.to_dict() _item = item.to_dict()
res[idx] = _item res[idx] = _item
res[idx]['parent'] = item.parent.to_dict() res[idx]['parent'] = item.parent.to_dict()
if item.parent_id not in type2attributes:
type2attributes[item.parent_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.parent_id)]
res[idx]['child'] = item.child.to_dict() res[idx]['child'] = item.child.to_dict()
if item.child_id not in type2attributes:
type2attributes[item.child_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.child_id)]
res[idx]['relation_type'] = item.relation_type.to_dict() res[idx]['relation_type'] = item.relation_type.to_dict()
return res return res, type2attributes
@staticmethod @staticmethod
def get_child_type_ids(type_id, level): def get_child_type_ids(type_id, level):
@@ -597,6 +778,8 @@ class CITypeRelationManager(object):
ci_type_dict["relation_type"] = relation_inst.relation_type.name ci_type_dict["relation_type"] = relation_inst.relation_type.name
ci_type_dict["constraint"] = relation_inst.constraint ci_type_dict["constraint"] = relation_inst.constraint
ci_type_dict["parent_attr_id"] = relation_inst.parent_attr_id
ci_type_dict["child_attr_id"] = relation_inst.child_attr_id
return ci_type_dict return ci_type_dict
@@ -641,7 +824,8 @@ class CITypeRelationManager(object):
return "{} -> {}".format(first_name, second_name) return "{} -> {}".format(first_name, second_name)
@classmethod @classmethod
def add(cls, parent, child, relation_type_id, constraint=ConstraintEnum.One2Many): def add(cls, parent, child, relation_type_id, constraint=ConstraintEnum.One2Many,
parent_attr_id=None, child_attr_id=None):
p = CITypeManager.check_is_existed(parent) p = CITypeManager.check_is_existed(parent)
c = CITypeManager.check_is_existed(child) c = CITypeManager.check_is_existed(child)
@@ -656,24 +840,21 @@ 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: old_parent_attr_id = None
other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
to_dict=False, first=True)
other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
to_dict=False, first=True)
if other_c and other_c.child_id != c.id:
return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
if other_p and other_p.parent_id != p.id:
return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
existed = cls._get(p.id, c.id) existed = cls._get(p.id, c.id)
if existed is not None: if existed is not None:
existed.update(relation_type_id=relation_type_id, old_parent_attr_id = existed.parent_attr_id
constraint=constraint) existed = existed.update(relation_type_id=relation_type_id,
constraint=constraint,
parent_attr_id=parent_attr_id,
child_attr_id=child_attr_id,
filter_none=False)
else: else:
existed = CITypeRelation.create(parent_id=p.id, existed = CITypeRelation.create(parent_id=p.id,
child_id=c.id, child_id=c.id,
relation_type_id=relation_type_id, relation_type_id=relation_type_id,
parent_attr_id=parent_attr_id,
child_attr_id=child_attr_id,
constraint=constraint) constraint=constraint)
if current_app.config.get("USE_ACL"): if current_app.config.get("USE_ACL"):
@@ -687,6 +868,11 @@ class CITypeRelationManager(object):
current_user.username, current_user.username,
ResourceTypeEnum.CI_TYPE_RELATION) ResourceTypeEnum.CI_TYPE_RELATION)
if parent_attr_id and parent_attr_id != old_parent_attr_id:
if parent_attr_id and parent_attr_id != existed.parent_attr_id:
from api.tasks.cmdb import rebuild_relation_for_attribute_changed
rebuild_relation_for_attribute_changed.apply_async(args=(existed.to_dict()))
CITypeHistoryManager.add(CITypeOperateType.ADD_RELATION, p.id, CITypeHistoryManager.add(CITypeOperateType.ADD_RELATION, p.id,
change=dict(parent=p.to_dict(), child=c.to_dict(), relation_type_id=relation_type_id)) change=dict(parent=p.to_dict(), child=c.to_dict(), relation_type_id=relation_type_id))
@@ -739,25 +925,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 +1118,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))
from_group = CITypeAttributeGroup.get_by_id(_from) if isinstance(_from, int):
from_group = CITypeAttributeGroup.get_by_id(_from)
else:
from_group = CITypeAttributeGroup.get_by(name=_from, first=True, to_dict=False)
from_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_from))) from_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_from)))
to_group = CITypeAttributeGroup.get_by_id(_to) if isinstance(_to, int):
to_group = CITypeAttributeGroup.get_by_id(_to)
else:
to_group = CITypeAttributeGroup.get_by(name=_to, first=True, to_dict=False)
to_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_to))) 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
@@ -932,6 +1165,8 @@ class CITypeTemplateManager(object):
id2obj_dicts[added_id].get('child_id'), id2obj_dicts[added_id].get('child_id'),
id2obj_dicts[added_id].get('relation_type_id'), id2obj_dicts[added_id].get('relation_type_id'),
id2obj_dicts[added_id].get('constraint'), id2obj_dicts[added_id].get('constraint'),
id2obj_dicts[added_id].get('parent_attr_id'),
id2obj_dicts[added_id].get('child_attr_id'),
) )
else: else:
obj = cls.create(flush=True, **id2obj_dicts[added_id]) obj = cls.create(flush=True, **id2obj_dicts[added_id])
@@ -973,6 +1208,8 @@ class CITypeTemplateManager(object):
i.pop('choice_web_hook', None) i.pop('choice_web_hook', None)
i.pop('choice_other', None) i.pop('choice_other', None)
i.pop('order', None) i.pop('order', None)
i.pop('inherited', None)
i.pop('inherited_from', None)
choice_value = i.pop('choice_value', None) choice_value = i.pop('choice_value', None)
if not choice_value: if not choice_value:
i['is_choice'] = False i['is_choice'] = False
@@ -1084,6 +1321,8 @@ class CITypeTemplateManager(object):
_group = copy.deepcopy(group) _group = copy.deepcopy(group)
_group.pop('attributes', None) _group.pop('attributes', None)
_group.pop('id', None) _group.pop('id', None)
_group.pop('inherited', None)
_group.pop('inherited_from', None)
existed = CITypeAttributeGroup.get_by(name=_group['name'], existed = CITypeAttributeGroup.get_by(name=_group['name'],
type_id=type_id_map.get(_group['type_id'], _group['type_id']), type_id=type_id_map.get(_group['type_id'], _group['type_id']),
first=True, to_dict=False) first=True, to_dict=False)
@@ -1139,9 +1378,13 @@ class CITypeTemplateManager(object):
rule.pop("id", None) rule.pop("id", None)
rule.pop("created_at", None) rule.pop("created_at", None)
rule.pop("updated_at", None) rule.pop("updated_at", None)
rule.pop("relation", None)
rule['uid'] = current_user.uid rule['uid'] = current_user.uid
if not rule.get('attributes'):
continue
existed = False existed = False
for i in AutoDiscoveryCIType.get_by(type_id=ci_type.id, adr_id=rule['adr_id'], to_dict=False): for i in AutoDiscoveryCIType.get_by(type_id=ci_type.id, adr_id=rule['adr_id'], to_dict=False):
if ((i.extra_option or {}).get('alias') or None) == ( if ((i.extra_option or {}).get('alias') or None) == (
@@ -1214,7 +1457,7 @@ class CITypeTemplateManager(object):
ci_types=CITypeManager.get_ci_types(), ci_types=CITypeManager.get_ci_types(),
ci_type_groups=CITypeGroupManager.get(), ci_type_groups=CITypeGroupManager.get(),
relation_types=[i.to_dict() for i in RelationTypeManager.get_all()], relation_types=[i.to_dict() for i in RelationTypeManager.get_all()],
ci_type_relations=CITypeRelationManager.get(), ci_type_relations=CITypeRelationManager.get()[0],
ci_type_auto_discovery_rules=list(), ci_type_auto_discovery_rules=list(),
type2attributes=dict(), type2attributes=dict(),
type2attribute_group=dict(), type2attribute_group=dict(),

View File

@@ -167,6 +167,7 @@ class AttributeHistoryManger(object):
new=hist.new, new=hist.new,
created_at=record.created_at.strftime('%Y-%m-%d %H:%M:%S'), created_at=record.created_at.strftime('%Y-%m-%d %H:%M:%S'),
record_id=record.id, record_id=record.id,
ticket_id=record.ticket_id,
hid=hist.id hid=hist.id
) )
result.append(item) result.append(item)
@@ -200,9 +201,9 @@ class AttributeHistoryManger(object):
return username, timestamp, attr_dict, rel_dict return username, timestamp, attr_dict, rel_dict
@staticmethod @staticmethod
def add(record_id, ci_id, history_list, type_id=None, flush=False, commit=True): def add(record_id, ci_id, history_list, type_id=None, ticket_id=None, flush=False, commit=True):
if record_id is None: if record_id is None:
record = OperationRecord.create(uid=current_user.uid, type_id=type_id) record = OperationRecord.create(uid=current_user.uid, type_id=type_id, ticket_id=ticket_id)
record_id = record.id record_id = record.id
for attr_id, operate_type, old, new in history_list or []: for attr_id, operate_type, old, new in history_list or []:

View File

@@ -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,36 +163,67 @@ class CIFilterPermsCRUD(DBMixin):
def add(self, **kwargs): def add(self, **kwargs):
kwargs = self._can_add(**kwargs) or kwargs kwargs = self._can_add(**kwargs) or kwargs
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid'])):
request_id_filter = {}
if kwargs.get('id_filter'):
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
rid=kwargs.get('rid'),
ci_filter=None,
attr_filter=None,
first=True, to_dict=False)
obj = self.cls.get_by(type_id=kwargs.get('type_id'), for _id, v in (kwargs.get('id_filter') or {}).items():
rid=kwargs.get('rid'), key = ",".join(([v['parent_path']] if v.get('parent_path') else []) + [str(_id)])
first=True, to_dict=False) request_id_filter[key] = v['name']
if obj is not None:
obj = obj.update(filter_none=False, **kwargs)
if not obj.attr_filter and not obj.ci_filter:
if current_app.config.get('USE_ACL'):
ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER)
obj.soft_delete() else:
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
rid=kwargs.get('rid'),
id_filter=None,
first=True, to_dict=False)
return obj is_recursive = kwargs.pop('is_recursive', 0)
if obj is not None:
if obj.id_filter and isinstance(kwargs.get('id_filter'), dict):
obj_id_filter = copy.deepcopy(obj.id_filter)
for k, v in request_id_filter.items():
obj_id_filter[k] = v
kwargs['id_filter'] = obj_id_filter
obj = obj.update(filter_none=False, **kwargs)
if not obj.attr_filter and not obj.ci_filter and not obj.id_filter:
if current_app.config.get('USE_ACL'):
ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER, rebuild=False)
obj.soft_delete()
if not is_recursive and request_id_filter:
self._revoke_children(obj.rid, request_id_filter, rebuild=False)
else:
if not kwargs.get('ci_filter') and not kwargs.get('attr_filter'):
return return
obj = self.cls.create(**kwargs) else:
if not kwargs.get('ci_filter') and not kwargs.get('attr_filter') and not kwargs.get('id_filter'):
return
if current_app.config.get('USE_ACL'): if request_id_filter:
try: kwargs['id_filter'] = request_id_filter
ACLManager().add_resource(obj.id, ResourceTypeEnum.CI_FILTER)
except:
pass
ACLManager().grant_resource_to_role_by_rid(obj.id,
kwargs.get('rid'),
ResourceTypeEnum.CI_FILTER)
return obj obj = self.cls.create(**kwargs)
if current_app.config.get('USE_ACL'): # new resource
try:
ACLManager().add_resource(obj.id, ResourceTypeEnum.CI_FILTER)
except:
pass
ACLManager().grant_resource_to_role_by_rid(obj.id,
kwargs.get('rid'),
ResourceTypeEnum.CI_FILTER)
return obj
def _can_update(self, **kwargs): def _can_update(self, **kwargs):
pass pass
@@ -140,19 +232,84 @@ class CIFilterPermsCRUD(DBMixin):
pass pass
def delete(self, **kwargs): def delete(self, **kwargs):
obj = self.cls.get_by(type_id=kwargs.get('type_id'), with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid'])):
rid=kwargs.get('rid'), obj = self.cls.get_by(type_id=kwargs.get('type_id'),
first=True, to_dict=False) rid=kwargs.get('rid'),
id_filter=None,
first=True, to_dict=False)
if obj is not None:
resource = None
if current_app.config.get('USE_ACL'):
resource = ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER)
obj.soft_delete()
return resource
def delete2(self, **kwargs):
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid'])):
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
rid=kwargs.get('rid'),
ci_filter=None,
attr_filter=None,
first=True, to_dict=False)
request_id_filter = {}
for _id, v in (kwargs.get('id_filter') or {}).items():
key = ",".join(([v['parent_path']] if v.get('parent_path') else []) + [str(_id)])
request_id_filter[key] = v['name']
if obj is not None:
resource = None resource = None
if current_app.config.get('USE_ACL'): if obj is not None:
resource = ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER)
obj.soft_delete() 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 return resource
def delete_id_filter_by_ci_id(self, ci_id):
items = self.cls.get_by(ci_filter=None, attr_filter=None, to_dict=False)
rebuild_roles = set()
for item in items:
id_filter = copy.deepcopy(item.id_filter)
changed = False
for node_path in item.id_filter:
if str(ci_id) in node_path:
id_filter.pop(node_path)
changed = True
if changed:
rebuild_roles.add(item.rid)
if not id_filter:
item.soft_delete(commit=False)
else:
item.update(id_filter=id_filter, commit=False)
db.session.commit()
if rebuild_roles:
from api.tasks.acl import role_rebuild
from api.lib.perm.acl.const import ACL_QUEUE
from api.lib.perm.acl.cache import AppCache
for rid in rebuild_roles:
role_rebuild.apply_async(args=(rid, AppCache.get('cmdb').id), queue=ACL_QUEUE)
def has_perm_for_ci(arg_name, resource_type, perm, callback=None, app=None): def has_perm_for_ci(arg_name, resource_type, perm, callback=None, app=None):
def decorator_has_perm(func): def decorator_has_perm(func):

View File

@@ -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
@@ -237,11 +238,13 @@ class PreferenceManager(object):
views = _views views = _views
view2cr_ids = dict() view2cr_ids = dict()
name2view = dict()
result = dict() result = dict()
name2id = list() name2id = list()
for view in views: for view in views:
view2cr_ids.setdefault(view['name'], []).extend(view['cr_ids']) view2cr_ids.setdefault(view['name'], []).extend(view['cr_ids'])
name2id.append([view['name'], view['id']]) name2id.append([view['name'], view['id']])
name2view[view['name']] = view
id2type = dict() id2type = dict()
for view_name in view2cr_ids: for view_name in view2cr_ids:
@@ -285,6 +288,8 @@ class PreferenceManager(object):
topo_flatten=topo_flatten, topo_flatten=topo_flatten,
level2constraint=level2constraint, level2constraint=level2constraint,
leaf=leaf, leaf=leaf,
option=name2view[view_name]['option'],
is_public=name2view[view_name]['is_public'],
leaf2show_types=leaf2show_types, leaf2show_types=leaf2show_types,
node2show_types=node2show_types, node2show_types=node2show_types,
show_types=[CITypeCache.get(j).to_dict() show_types=[CITypeCache.get(j).to_dict()
@@ -296,14 +301,18 @@ class PreferenceManager(object):
return result, id2type, sorted(name2id, key=lambda x: x[1]) return result, id2type, sorted(name2id, key=lambda x: x[1])
@classmethod @classmethod
def create_or_update_relation_view(cls, name, cr_ids, is_public=False): def create_or_update_relation_view(cls, name=None, cr_ids=None, _id=None, is_public=False, option=None):
if not cr_ids: if not cr_ids:
return abort(400, ErrFormat.preference_relation_view_node_required) return abort(400, ErrFormat.preference_relation_view_node_required)
existed = PreferenceRelationView.get_by(name=name, to_dict=False, first=True) if _id is None:
existed = PreferenceRelationView.get_by(name=name, to_dict=False, first=True)
else:
existed = PreferenceRelationView.get_by_id(_id)
current_app.logger.debug(existed) current_app.logger.debug(existed)
if existed is None: if existed is None:
PreferenceRelationView.create(name=name, cr_ids=cr_ids, uid=current_user.uid, is_public=is_public) PreferenceRelationView.create(name=name, cr_ids=cr_ids, uid=current_user.uid,
is_public=is_public, option=option)
if current_app.config.get("USE_ACL"): if current_app.config.get("USE_ACL"):
ACLManager().add_resource(name, ResourceTypeEnum.RELATION_VIEW) ACLManager().add_resource(name, ResourceTypeEnum.RELATION_VIEW)
@@ -311,6 +320,11 @@ class PreferenceManager(object):
RoleEnum.CMDB_READ_ALL, RoleEnum.CMDB_READ_ALL,
ResourceTypeEnum.RELATION_VIEW, ResourceTypeEnum.RELATION_VIEW,
permissions=[PermEnum.READ]) permissions=[PermEnum.READ])
else:
if existed.name != name and current_app.config.get("USE_ACL"):
ACLManager().update_resource(existed.name, name, ResourceTypeEnum.RELATION_VIEW)
existed.update(name=name, cr_ids=cr_ids, is_public=is_public, option=option)
return cls.get_relation_view() return cls.get_relation_view()

View File

@@ -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(
@@ -94,7 +96,7 @@ class ErrFormat(CommonErrFormat):
# 属性 {} 的值必须是唯一的, 当前值 {} 已存在 # 属性 {} 的值必须是唯一的, 当前值 {} 已存在
attribute_value_unique_required = _l("The value of attribute {} must be unique, {} already exists") attribute_value_unique_required = _l("The value of attribute {} must be unique, {} already exists")
attribute_value_required = _l("Attribute {} value must exist") # 属性 {} 值必须存在 attribute_value_required = _l("Attribute {} value must exist") # 属性 {} 值必须存在
attribute_value_out_of_range = _l("Out of range value, the maximum value is 2147483647")
# 新增或者修改属性值未知错误: {} # 新增或者修改属性值未知错误: {}
attribute_value_unknown_error = _l("Unknown error when adding or modifying attribute value: {}") attribute_value_unknown_error = _l("Unknown error when adding or modifying attribute value: {}")

View File

@@ -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=False):
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

View File

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

View File

@@ -44,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

View File

@@ -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: else:
key = ["{},{}".format(self.ancestor_ids, _id) for _id in ids]
if not self.ancestor_ids: if not self.ancestor_ids:
key, prefix = ids, REDIS_PREFIX_CI_RELATION key, prefix = [str(i) for i in ids], REDIS_PREFIX_CI_RELATION
else: else:
key = ["{},{}".format(self.ancestor_ids, _id) for _id in ids]
prefix = REDIS_PREFIX_CI_RELATION2 prefix = REDIS_PREFIX_CI_RELATION2
level2ids[lv] = [[i] for i in key] level2ids[lv] = [[i] for i in key]
if not key: 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,19 +301,27 @@ 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 = []
_tmp[idx] = [j for i in __tmp for j in i] if __tmp:
_tmp[idx] = [j for i in __tmp for j in i]
else: else:
_tmp[idx] = [] _tmp[idx] = []
level2ids[lv].append([]) level2ids[lv].append([])

View File

@@ -11,12 +11,21 @@ import six
import api.models.cmdb as model import api.models.cmdb as model
from api.lib.cmdb.cache import AttributeCache from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.const import ValueTypeEnum from api.lib.cmdb.const import ValueTypeEnum
from api.lib.cmdb.resp_format import ErrFormat
TIME_RE = re.compile(r'(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d') TIME_RE = re.compile(r'(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d')
class ValueDeserializeError(Exception):
pass
def string2int(x): def string2int(x):
return int(float(x)) v = int(float(x))
if v > 2147483647:
raise ValueDeserializeError(ErrFormat.attribute_value_out_of_range)
return v
def str2datetime(x): def str2datetime(x):

View File

@@ -5,14 +5,16 @@ from __future__ import unicode_literals
import copy import copy
import imp import imp
import jinja2
import os import os
import re import re
import tempfile import tempfile
import jinja2
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from jinja2schema import infer from jinja2schema import infer
from jinja2schema import to_json_schema from jinja2schema import to_json_schema
from werkzeug.exceptions import BadRequest
from api.extensions import db from api.extensions import db
from api.lib.cmdb.attribute import AttributeManager from api.lib.cmdb.attribute import AttributeManager
@@ -23,6 +25,7 @@ from api.lib.cmdb.const import ValueTypeEnum
from api.lib.cmdb.history import AttributeHistoryManger from api.lib.cmdb.history import AttributeHistoryManger
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.utils import TableMap from api.lib.cmdb.utils import TableMap
from api.lib.cmdb.utils import ValueDeserializeError
from api.lib.cmdb.utils import ValueTypeMap from api.lib.cmdb.utils import ValueTypeMap
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
@@ -80,7 +83,7 @@ class AttributeValueManager(object):
return res return res
@staticmethod @staticmethod
def _deserialize_value(value_type, value): def _deserialize_value(alias, value_type, value):
if not value: if not value:
return value return value
@@ -88,6 +91,8 @@ class AttributeValueManager(object):
try: try:
v = deserialize(value) v = deserialize(value)
return v return v
except ValueDeserializeError as e:
return abort(400, ErrFormat.attribute_value_invalid2.format(alias, e))
except ValueError: except ValueError:
return abort(400, ErrFormat.attribute_value_invalid.format(value)) return abort(400, ErrFormat.attribute_value_invalid.format(value))
@@ -124,7 +129,7 @@ class AttributeValueManager(object):
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None): def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
ci = ci or {} ci = ci or {}
v = self._deserialize_value(attr.value_type, value) v = self._deserialize_value(attr.alias, attr.value_type, value)
attr.is_choice and value and self._check_is_choice(attr, attr.value_type, v) attr.is_choice and value and self._check_is_choice(attr, attr.value_type, v)
attr.is_unique and self._check_is_unique( attr.is_unique and self._check_is_unique(
@@ -145,9 +150,10 @@ class AttributeValueManager(object):
return AttributeHistoryManger.add(record_id, ci_id, [(attr_id, operate_type, old, new)], type_id) return AttributeHistoryManger.add(record_id, ci_id, [(attr_id, operate_type, old, new)], type_id)
@staticmethod @staticmethod
def write_change2(changed, record_id=None): def write_change2(changed, record_id=None, ticket_id=None):
for ci_id, attr_id, operate_type, old, new, type_id in changed: for ci_id, attr_id, operate_type, old, new, type_id in changed:
record_id = AttributeHistoryManger.add(record_id, ci_id, [(attr_id, operate_type, old, new)], type_id, record_id = AttributeHistoryManger.add(record_id, ci_id, [(attr_id, operate_type, old, new)], type_id,
ticket_id=ticket_id,
commit=False, flush=False) commit=False, flush=False)
try: try:
db.session.commit() db.session.commit()
@@ -240,6 +246,8 @@ class AttributeValueManager(object):
value = self._validate(attr, value, value_table, ci=None, type_id=type_id, ci_id=ci_id, value = self._validate(attr, value, value_table, ci=None, type_id=type_id, ci_id=ci_id,
type_attr=ci_attr2type_attr.get(attr.id)) type_attr=ci_attr2type_attr.get(attr.id))
ci_dict[key] = value ci_dict[key] = value
except BadRequest as e:
raise
except Exception as e: except Exception as e:
current_app.logger.warning(str(e)) current_app.logger.warning(str(e))
@@ -248,12 +256,13 @@ class AttributeValueManager(object):
return key2attr return key2attr
def create_or_update_attr_value(self, ci, ci_dict, key2attr): def create_or_update_attr_value(self, ci, ci_dict, key2attr, ticket_id=None):
""" """
add or update attribute value, then write history add or update attribute value, then write history
:param ci: instance object :param ci: instance object
:param ci_dict: attribute dict :param ci_dict: attribute dict
:param key2attr: attr key to attr :param key2attr: attr key to attr
:param ticket_id:
:return: :return:
""" """
changed = [] changed = []
@@ -299,12 +308,12 @@ class AttributeValueManager(object):
current_app.logger.warning(str(e)) current_app.logger.warning(str(e))
return abort(400, ErrFormat.attribute_value_unknown_error.format(e.args[0])) return abort(400, ErrFormat.attribute_value_unknown_error.format(e.args[0]))
return self.write_change2(changed) return self.write_change2(changed, ticket_id=ticket_id)
@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)

View File

@@ -35,3 +35,32 @@ AuthCommonConfigAutoRedirect = 'auto_redirect'
class TestType(BaseEnum): class TestType(BaseEnum):
Connect = 'connect' Connect = 'connect'
Login = 'login' Login = 'login'
MIMEExtMap = {
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx',
'application/msword': '.doc',
'application/vnd.ms-word.document.macroEnabled.12': '.docm',
'application/vnd.ms-excel': '.xls',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
'application/vnd.ms-excel.sheet.macroEnabled.12': '.xlsm',
'application/vnd.ms-powerpoint': '.ppt',
'application/vnd.openxmlformats-officedocument.presentationml.presentation': '.pptx',
'application/vnd.ms-powerpoint.presentation.macroEnabled.12': '.pptm',
'application/zip': '.zip',
'application/x-7z-compressed': '.7z',
'application/json': '.json',
'application/pdf': '.pdf',
'image/png': '.png',
'image/bmp': '.bmp',
'image/prs.btif': '.btif',
'image/gif': '.gif',
'image/jpeg': '.jpg',
'image/tiff': '.tif',
'image/vnd.microsoft.icon': '.ico',
'image/webp': '.webp',
'image/svg+xml': '.svg',
'image/vnd.adobe.photoshop': '.psd',
'text/plain': '.txt',
'text/csv': '.csv',
}

View File

@@ -470,8 +470,58 @@ class EditDepartmentInACL(object):
return f"edit_department_name_in_acl, rid: {d_rid}, success" return f"edit_department_name_in_acl, rid: {d_rid}, success"
@classmethod
def remove_from_old_department_role(cls, e_list, acl):
result = []
for employee in e_list:
employee_acl_rid = employee.get('e_acl_rid')
if employee_acl_rid == 0:
result.append(f"employee_acl_rid == 0")
continue
cls.remove_single_employee_from_old_department(acl, employee, result)
@staticmethod @staticmethod
def edit_employee_department_in_acl(e_list: list, new_d_id: int, op_uid: int): def remove_single_employee_from_old_department(acl, employee, result):
from api.models.acl import Role
old_department = DepartmentCRUD.get_department_by_id(employee.get('department_id'), False)
if not old_department:
return False
old_role = Role.get_by(first=True, name=old_department.department_name, app_id=None)
old_d_rid_in_acl = old_role.get('id') if old_role else 0
if old_d_rid_in_acl == 0:
return False
d_acl_rid = old_department.acl_rid if old_d_rid_in_acl == old_department.acl_rid else old_d_rid_in_acl
payload = {
'app_id': 'acl',
'parent_id': d_acl_rid,
}
try:
acl.remove_user_from_role(employee.get('e_acl_rid'), payload)
current_app.logger.info(f"remove {employee.get('e_acl_rid')} from {d_acl_rid}")
except Exception as e:
result.append(
f"remove_user_from_role employee_acl_rid: {employee.get('e_acl_rid')}, parent_id: {d_acl_rid}, err: {e}")
return True
@staticmethod
def add_employee_to_new_department(acl, employee_acl_rid, new_department_acl_rid, result):
payload = {
'app_id': 'acl',
'child_ids': [employee_acl_rid],
}
try:
acl.add_user_to_role(new_department_acl_rid, payload)
current_app.logger.info(f"add {employee_acl_rid} to {new_department_acl_rid}")
except Exception as e:
result.append(
f"add_user_to_role employee_acl_rid: {employee_acl_rid}, parent_id: {new_department_acl_rid}, \
err: {e}")
@classmethod
def edit_employee_department_in_acl(cls, e_list: list, new_d_id: int, op_uid: int):
result = [] result = []
new_department = DepartmentCRUD.get_department_by_id(new_d_id, False) new_department = DepartmentCRUD.get_department_by_id(new_d_id, False)
if not new_department: if not new_department:
@@ -481,7 +531,11 @@ class EditDepartmentInACL(object):
from api.models.acl import Role from api.models.acl import Role
new_role = Role.get_by(first=True, name=new_department.department_name, app_id=None) new_role = Role.get_by(first=True, name=new_department.department_name, app_id=None)
new_d_rid_in_acl = new_role.get('id') if new_role else 0 new_d_rid_in_acl = new_role.get('id') if new_role else 0
acl = ACLManager('acl', str(op_uid))
if new_d_rid_in_acl == 0: if new_d_rid_in_acl == 0:
# only remove from old department role
cls.remove_from_old_department_role(e_list, acl)
return return
if new_d_rid_in_acl != new_department.acl_rid: if new_d_rid_in_acl != new_department.acl_rid:
@@ -491,43 +545,15 @@ class EditDepartmentInACL(object):
new_department_acl_rid = new_department.acl_rid if new_d_rid_in_acl == new_department.acl_rid else \ new_department_acl_rid = new_department.acl_rid if new_d_rid_in_acl == new_department.acl_rid else \
new_d_rid_in_acl new_d_rid_in_acl
acl = ACLManager('acl', str(op_uid))
for employee in e_list: for employee in e_list:
old_department = DepartmentCRUD.get_department_by_id(employee.get('department_id'), False)
if not old_department:
continue
employee_acl_rid = employee.get('e_acl_rid') employee_acl_rid = employee.get('e_acl_rid')
if employee_acl_rid == 0: if employee_acl_rid == 0:
result.append(f"employee_acl_rid == 0") result.append(f"employee_acl_rid == 0")
continue continue
old_role = Role.get_by(first=True, name=old_department.department_name, app_id=None) cls.remove_single_employee_from_old_department(acl, employee, result)
old_d_rid_in_acl = old_role.get('id') if old_role else 0
if old_d_rid_in_acl == 0:
return
if old_d_rid_in_acl != old_department.acl_rid:
old_department.update(
acl_rid=old_d_rid_in_acl
)
d_acl_rid = old_department.acl_rid if old_d_rid_in_acl == old_department.acl_rid else old_d_rid_in_acl
payload = {
'app_id': 'acl',
'parent_id': d_acl_rid,
}
try:
acl.remove_user_from_role(employee_acl_rid, payload)
except Exception as e:
result.append(
f"remove_user_from_role employee_acl_rid: {employee_acl_rid}, parent_id: {d_acl_rid}, err: {e}")
payload = { # 在新部门中添加员工
'app_id': 'acl', cls.add_employee_to_new_department(acl, employee_acl_rid, new_department_acl_rid, result)
'child_ids': [employee_acl_rid],
}
try:
acl.add_user_to_role(new_department_acl_rid, payload)
except Exception as e:
result.append(
f"add_user_to_role employee_acl_rid: {employee_acl_rid}, parent_id: {d_acl_rid}, err: {e}")
return result return result

View File

@@ -16,7 +16,7 @@ from wtforms import validators
from api.extensions import db from api.extensions import db
from api.lib.common_setting.acl import ACLManager from api.lib.common_setting.acl import ACLManager
from api.lib.common_setting.const import OperatorType from api.lib.common_setting.const import OperatorType
from api.lib.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)

View File

@@ -148,10 +148,10 @@ class ACLManager(object):
if group: if group:
PermissionCRUD.revoke(rid, permissions, group_id=group.id, rebuild=rebuild) PermissionCRUD.revoke(rid, permissions, group_id=group.id, rebuild=rebuild)
def del_resource(self, name, resource_type_name=None): def del_resource(self, name, resource_type_name=None, rebuild=True):
resource = self._get_resource(name, resource_type_name) resource = self._get_resource(name, resource_type_name)
if resource: if resource:
return ResourceCRUD.delete(resource.id) return ResourceCRUD.delete(resource.id, rebuild=rebuild)
def has_permission(self, resource_name, resource_type, perm, resource_id=None): def has_permission(self, resource_name, resource_type, perm, resource_id=None):
if is_app_admin(self.app_id): if is_app_admin(self.app_id):

View File

@@ -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:

View File

@@ -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)

View File

@@ -309,7 +309,7 @@ class ResourceCRUD(object):
return resource return resource
@staticmethod @staticmethod
def delete(_id): def delete(_id, rebuild=True):
resource = Resource.get_by_id(_id) or abort(404, ErrFormat.resource_not_found.format("id={}".format(_id))) resource = Resource.get_by_id(_id) or abort(404, ErrFormat.resource_not_found.format("id={}".format(_id)))
origin = resource.to_dict() origin = resource.to_dict()
@@ -322,8 +322,9 @@ class ResourceCRUD(object):
i.soft_delete() i.soft_delete()
rebuilds.append((i.rid, i.app_id)) rebuilds.append((i.rid, i.app_id))
for rid, app_id in set(rebuilds): if rebuild:
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE) for rid, app_id in set(rebuilds):
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE)
AuditCRUD.add_resource_log(resource.app_id, AuditOperateType.delete, AuditCRUD.add_resource_log(resource.app_id, AuditOperateType.delete,
AuditScope.resource, resource.id, origin, {}, {}) AuditScope.resource, resource.id, origin, {}, {})

View File

@@ -194,7 +194,7 @@ def validate(ticket):
def _parse_tag(string, tag): def _parse_tag(string, tag):
""" """
Used for parsing xml. Search string for the first 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.
""" """

View File

@@ -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) *

View File

@@ -356,7 +356,7 @@ class AuditLoginLog(Model2):
__tablename__ = "acl_audit_login_logs" __tablename__ = "acl_audit_login_logs"
username = db.Column(db.String(64), index=True) username = db.Column(db.String(64), index=True)
channel = db.Column(db.Enum('web', 'api'), 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))

View File

@@ -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"
@@ -64,6 +75,9 @@ class CITypeRelation(Model):
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False) relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
constraint = db.Column(db.Enum(*ConstraintEnum.all()), default=ConstraintEnum.One2Many) constraint = db.Column(db.Enum(*ConstraintEnum.all()), default=ConstraintEnum.One2Many)
parent_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
child_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.parent_id") parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.parent_id")
child = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.child_id") child = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.child_id")
relation_type = db.relationship("RelationType", backref="c_ci_type_relations.relation_type_id") relation_type = db.relationship("RelationType", backref="c_ci_type_relations.relation_type_id")
@@ -449,6 +463,7 @@ class PreferenceRelationView(Model):
name = db.Column(db.String(64), index=True, nullable=False) name = db.Column(db.String(64), index=True, nullable=False)
cr_ids = db.Column(db.JSON) # [{parent_id: x, child_id: y}] cr_ids = db.Column(db.JSON) # [{parent_id: x, child_id: y}]
is_public = db.Column(db.Boolean, default=False) is_public = db.Column(db.Boolean, default=False)
option = db.Column(db.JSON)
class PreferenceSearchOption(Model): class PreferenceSearchOption(Model):
@@ -558,6 +573,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)

View File

@@ -2,8 +2,8 @@
import json import json
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 +17,10 @@ from api.lib.cmdb.const import CMDB_QUEUE
from api.lib.cmdb.const import REDIS_PREFIX_CI from api.lib.cmdb.const import REDIS_PREFIX_CI
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2 from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.decorator import flush_db from api.lib.decorator import flush_db
from api.lib.decorator import reconnect_db from api.lib.decorator import reconnect_db
from api.lib.perm.acl.cache import UserCache from api.lib.perm.acl.cache import UserCache
from api.lib.utils import Lock
from api.lib.utils import handle_arg_list from api.lib.utils import handle_arg_list
from api.models.cmdb import CI from api.models.cmdb import CI
from api.models.cmdb import CIRelation from api.models.cmdb import CIRelation
@@ -32,8 +32,7 @@ from api.models.cmdb import CITypeAttribute
@reconnect_db @reconnect_db
def ci_cache(ci_id, operate_type, record_id): def ci_cache(ci_id, operate_type, record_id):
from api.lib.cmdb.ci import CITriggerManager from api.lib.cmdb.ci import CITriggerManager
from api.lib.cmdb.ci import CIRelationManager
time.sleep(0.01)
m = api.lib.cmdb.ci.CIManager() m = api.lib.cmdb.ci.CIManager()
ci_dict = m.get_ci_by_id_from_db(ci_id, need_children=False, use_master=False) ci_dict = m.get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
@@ -51,13 +50,21 @@ def ci_cache(ci_id, operate_type, record_id):
CITriggerManager.fire(operate_type, ci_dict, record_id) CITriggerManager.fire(operate_type, ci_dict, record_id)
ci_dict and CIRelationManager.build_by_attribute(ci_dict)
@celery.task(name="cmdb.rebuild_relation_for_attribute_changed", queue=CMDB_QUEUE)
@reconnect_db
def rebuild_relation_for_attribute_changed(ci_type_relation):
from api.lib.cmdb.ci import CIRelationManager
CIRelationManager.rebuild_all_by_attribute(ci_type_relation)
@celery.task(name="cmdb.batch_ci_cache", queue=CMDB_QUEUE) @celery.task(name="cmdb.batch_ci_cache", queue=CMDB_QUEUE)
@flush_db @flush_db
@reconnect_db @reconnect_db
def batch_ci_cache(ci_ids, ): # only for attribute change index def batch_ci_cache(ci_ids, ): # only for attribute change index
time.sleep(1)
for ci_id in ci_ids: for ci_id in ci_ids:
m = api.lib.cmdb.ci.CIManager() m = api.lib.cmdb.ci.CIManager()
ci_dict = m.get_ci_by_id_from_db(ci_id, need_children=False, use_master=False) ci_dict = m.get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
@@ -83,6 +90,12 @@ 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 +112,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 +190,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 {}

View File

@@ -3,14 +3,14 @@ from flask import current_app
from api.extensions import celery from api.extensions import celery
from api.lib.common_setting.acl import ACLManager from api.lib.common_setting.acl import ACLManager
from api.lib.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,21 +49,20 @@ 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 )
) d_acl_rid = old_department.acl_rid if old_d_rid_in_acl == old_department.acl_rid else old_d_rid_in_acl
d_acl_rid = old_department.acl_rid if old_d_rid_in_acl == old_department.acl_rid else old_d_rid_in_acl payload = {
payload = { 'app_id': 'acl',
'app_id': 'acl', 'parent_id': d_acl_rid,
'parent_id': d_acl_rid, }
} try:
try: acl.remove_user_from_role(employee_acl_rid, payload)
acl.remove_user_from_role(employee_acl_rid, payload) except Exception as e:
except Exception as e: result.append(ErrFormat.acl_remove_user_from_role_failed.format(str(e)))
result.append(ErrFormat.acl_remove_user_from_role_failed.format(str(e)))
payload = { payload = {
'app_id': 'acl', 'app_id': '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))

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-01-03 11:39+0800\n" "POT-Creation-Date: 2024-03-29 10:42+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,213 @@ 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:99
msgid "Out of range value, the maximum value is 2147483647"
msgstr "超过最大值限制, 最大值是2147483647"
#: 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 "获取密码失败: {}"

View File

@@ -38,8 +38,9 @@ class LoginView(APIView):
username = request.values.get("username") or request.values.get("email") username = request.values.get("username") or request.values.get("email")
password = request.values.get("password") password = request.values.get("password")
_role = None _role = None
auth_with_ldap = request.values.get('auth_with_ldap', True)
config = AuthenticateDataCRUD(AuthenticateType.LDAP).get() config = AuthenticateDataCRUD(AuthenticateType.LDAP).get()
if config.get('enabled') or config.get('enable'): if (config.get('enabled') or config.get('enable')) and auth_with_ldap:
from api.lib.perm.authentication.ldap import authenticate_with_ldap from api.lib.perm.authentication.ldap import authenticate_with_ldap
user, authenticated = authenticate_with_ldap(username, password) user, authenticated = authenticate_with_ldap(username, password)
else: else:

View File

@@ -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
@@ -77,6 +76,7 @@ class CIView(APIView):
@has_perm_for_ci("ci_type", ResourceTypeEnum.CI, PermEnum.ADD, lambda x: CITypeCache.get(x)) @has_perm_for_ci("ci_type", ResourceTypeEnum.CI, PermEnum.ADD, lambda x: CITypeCache.get(x))
def post(self): def post(self):
ci_type = request.values.get("ci_type") ci_type = request.values.get("ci_type")
ticket_id = request.values.pop("ticket_id", None)
_no_attribute_policy = request.values.get("no_attribute_policy", ExistPolicy.IGNORE) _no_attribute_policy = request.values.get("no_attribute_policy", ExistPolicy.IGNORE)
exist_policy = request.values.pop('exist_policy', None) exist_policy = request.values.pop('exist_policy', None)
@@ -88,6 +88,7 @@ class CIView(APIView):
exist_policy=exist_policy or ExistPolicy.REJECT, exist_policy=exist_policy or ExistPolicy.REJECT,
_no_attribute_policy=_no_attribute_policy, _no_attribute_policy=_no_attribute_policy,
_is_admin=request.values.pop('__is_admin', None) or False, _is_admin=request.values.pop('__is_admin', None) or False,
ticket_id=ticket_id,
**ci_dict) **ci_dict)
return self.jsonify(ci_id=ci_id) return self.jsonify(ci_id=ci_id)
@@ -96,6 +97,7 @@ class CIView(APIView):
def put(self, ci_id=None): def put(self, ci_id=None):
args = request.values args = request.values
ci_type = args.get("ci_type") ci_type = args.get("ci_type")
ticket_id = request.values.pop("ticket_id", None)
_no_attribute_policy = args.get("no_attribute_policy", ExistPolicy.IGNORE) _no_attribute_policy = args.get("no_attribute_policy", ExistPolicy.IGNORE)
ci_dict = self._wrap_ci_dict() ci_dict = self._wrap_ci_dict()
@@ -103,6 +105,7 @@ class CIView(APIView):
if ci_id is not None: if ci_id is not None:
manager.update(ci_id, manager.update(ci_id,
_is_admin=request.values.pop('__is_admin', None) or False, _is_admin=request.values.pop('__is_admin', None) or False,
ticket_id=ticket_id,
**ci_dict) **ci_dict)
else: else:
request.values.pop('exist_policy', None) request.values.pop('exist_policy', None)
@@ -110,6 +113,7 @@ class CIView(APIView):
exist_policy=ExistPolicy.REPLACE, exist_policy=ExistPolicy.REPLACE,
_no_attribute_policy=_no_attribute_policy, _no_attribute_policy=_no_attribute_policy,
_is_admin=request.values.pop('__is_admin', None) or False, _is_admin=request.values.pop('__is_admin', None) or False,
ticket_id=ticket_id,
**ci_dict) **ci_dict)
return self.jsonify(ci_id=ci_id) return self.jsonify(ci_id=ci_id)
@@ -152,9 +156,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:
@@ -221,7 +226,6 @@ class CIHeartbeatView(APIView):
class CIFlushView(APIView): class CIFlushView(APIView):
url_prefix = ("/ci/flush", "/ci/<int:ci_id>/flush") url_prefix = ("/ci/flush", "/ci/<int:ci_id>/flush")
# @auth_abandoned
def get(self, ci_id=None): def get(self, ci_id=None):
from api.tasks.cmdb import ci_cache from api.tasks.cmdb import ci_cache
from api.lib.cmdb.const import CMDB_QUEUE from api.lib.cmdb.const import CMDB_QUEUE

View File

@@ -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:

View File

@@ -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))
acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False) if perms and not request.values.get('id_filter'):
acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
resource = None 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)

View File

@@ -43,16 +43,19 @@ class CITypeRelationView(APIView):
@role_required(RoleEnum.CONFIG) @role_required(RoleEnum.CONFIG)
def get(self): def get(self):
res = CITypeRelationManager.get() res, type2attributes = CITypeRelationManager.get()
return self.jsonify(res) return self.jsonify(relations=res, type2attributes=type2attributes)
@has_perm_from_args("parent_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id) @has_perm_from_args("parent_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
@args_required("relation_type_id") @args_required("relation_type_id")
def post(self, parent_id, child_id): def post(self, parent_id, child_id):
relation_type_id = request.values.get("relation_type_id") relation_type_id = request.values.get("relation_type_id")
constraint = request.values.get("constraint") constraint = request.values.get("constraint")
ctr_id = CITypeRelationManager.add(parent_id, child_id, relation_type_id, constraint) parent_attr_id = request.values.get("parent_attr_id")
child_attr_id = request.values.get("child_attr_id")
ctr_id = CITypeRelationManager.add(parent_id, child_id, relation_type_id, constraint,
parent_attr_id, child_attr_id)
return self.jsonify(ctr_id=ctr_id) return self.jsonify(ctr_id=ctr_id)

View File

@@ -97,7 +97,7 @@ class PreferenceTreeApiView(APIView):
class PreferenceRelationApiView(APIView): class PreferenceRelationApiView(APIView):
url_prefix = "/preference/relation/view" url_prefix = ("/preference/relation/view", "/preference/relation/view/<int:_id>")
def get(self): def get(self):
views, id2type, name2id = PreferenceManager.get_relation_view() views, id2type, name2id = PreferenceManager.get_relation_view()
@@ -110,14 +110,20 @@ class PreferenceRelationApiView(APIView):
@args_validate(PreferenceManager.pref_rel_cls) @args_validate(PreferenceManager.pref_rel_cls)
def post(self): def post(self):
name = request.values.get("name") name = request.values.get("name")
is_public = request.values.get("is_public") in current_app.config.get('BOOL_TRUE')
cr_ids = request.values.get("cr_ids") cr_ids = request.values.get("cr_ids")
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(name, cr_ids) option = request.values.get("option") or None
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(name, cr_ids, is_public=is_public,
option=option)
return self.jsonify(views=views, id2type=id2type, name2id=name2id) return self.jsonify(views=views, id2type=id2type, name2id=name2id)
@role_required(RoleEnum.CONFIG) @role_required(RoleEnum.CONFIG)
def put(self): @args_required("name")
return self.post() def put(self, _id):
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(_id=_id, **request.values)
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
@role_required(RoleEnum.CONFIG) @role_required(RoleEnum.CONFIG)
@args_required("name") @args_required("name")

View File

@@ -1,10 +1,10 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import os from flask import request, abort, current_app
from flask import request, abort, current_app, send_from_directory
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
import lz4.frame import lz4.frame
import magic
from api.lib.common_setting.const import MIMEExtMap
from api.lib.common_setting.resp_format import ErrFormat from api.lib.common_setting.resp_format import ErrFormat
from api.lib.common_setting.upload_file import allowed_file, generate_new_file_name, CommonFileCRUD from api.lib.common_setting.upload_file import allowed_file, generate_new_file_name, CommonFileCRUD
from api.resource import APIView from api.resource import APIView
@@ -45,32 +45,35 @@ class PostFileView(APIView):
if not file: if not file:
abort(400, ErrFormat.file_is_required) abort(400, ErrFormat.file_is_required)
extension = file.mimetype.split('/')[-1]
if '+' in extension:
extension = file.filename.split('.')[-1]
if file.filename == '':
filename = f'.{extension}'
else:
if extension not in file.filename:
filename = file.filename + f".{extension}"
else:
filename = file.filename
if allowed_file(filename, current_app.config.get('ALLOWED_EXTENSIONS', ALLOWED_EXTENSIONS)): m_type = magic.from_buffer(file.read(2048), mime=True)
new_filename = generate_new_file_name(filename) file.seek(0)
new_filename = secure_filename(new_filename)
file_content = file.read()
compressed_data = lz4.frame.compress(file_content)
try:
CommonFileCRUD.add_file(
origin_name=filename,
file_name=new_filename,
binary=compressed_data,
)
return self.jsonify(file_name=new_filename) if m_type == 'application/octet-stream':
except Exception as e: m_type = file.mimetype
current_app.logger.error(e) elif m_type == 'text/plain':
abort(400, ErrFormat.upload_failed.format(e)) # https://github.com/ahupp/python-magic/issues/193
m_type = m_type if file.mimetype == m_type else file.mimetype
abort(400, ErrFormat.file_type_not_allowed.format(filename)) extension = MIMEExtMap.get(m_type, None)
if extension is None:
abort(400, f"不支持的文件类型: {m_type}")
filename = file.filename if file.filename and file.filename.endswith(extension) else file.filename + extension
new_filename = generate_new_file_name(filename)
new_filename = secure_filename(new_filename)
file_content = file.read()
compressed_data = lz4.frame.compress(file_content)
try:
CommonFileCRUD.add_file(
origin_name=filename,
file_name=new_filename,
binary=compressed_data,
)
return self.jsonify(file_name=new_filename)
except Exception as e:
current_app.logger.error(e)
abort(400, ErrFormat.upload_failed.format(e))

View File

@@ -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
@@ -51,4 +52,5 @@ WTForms==3.0.0
shamir~=17.12.0 shamir~=17.12.0
pycryptodomex>=3.19.0 pycryptodomex>=3.19.0
colorama>=0.4.6 colorama>=0.4.6
lz4>=4.3.2 lz4>=4.3.2
python-magic==0.4.27

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 3857903 */ font-family: "iconfont"; /* Project id 3857903 */
src: url('iconfont.woff2?t=1702544951995') format('woff2'), src: url('iconfont.woff2?t=1711963254221') format('woff2'),
url('iconfont.woff?t=1702544951995') format('woff'), url('iconfont.woff?t=1711963254221') format('woff'),
url('iconfont.ttf?t=1702544951995') format('truetype'); url('iconfont.ttf?t=1711963254221') format('truetype');
} }
.iconfont { .iconfont {
@@ -13,6 +13,234 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.caise-VPC:before {
content: "\e910";
}
.caise-CDN:before {
content: "\e911";
}
.caise-OOS:before {
content: "\e90f";
}
.Google_Cloud_Platform:before {
content: "\e90b";
}
.Ctyun:before {
content: "\e90c";
}
.Alibaba_Cloud:before {
content: "\e90d";
}
.Azure:before {
content: "\e90e";
}
.ZStack:before {
content: "\e904";
}
.Tencent_Cloud:before {
content: "\e905";
}
.Nutanix:before {
content: "\e906";
}
.OpenStack:before {
content: "\e907";
}
.Huawei_Cloud:before {
content: "\e908";
}
.Bytecloud:before {
content: "\e909";
}
.UCloud:before {
content: "\e90a";
}
.AWS:before {
content: "\e901";
}
.ECloud:before {
content: "\e902";
}
.JDCloud:before {
content: "\e903";
}
.veops-more:before {
content: "\e900";
}
.duose-date:before {
content: "\e8ff";
}
.duose-shishu:before {
content: "\e8fd";
}
.duose-wenben:before {
content: "\e8fe";
}
.duose-json:before {
content: "\e8f7";
}
.duose-fudianshu:before {
content: "\e8f8";
}
.duose-time:before {
content: "\e8f9";
}
.duose-password:before {
content: "\e8fa";
}
.duose-link:before {
content: "\e8fb";
}
.duose-datetime:before {
content: "\e8fc";
}
.veops-setting2:before {
content: "\e8f6";
}
.veops-search:before {
content: "\e8f5";
}
.veops-delete:before {
content: "\e8f4";
}
.veops-refresh:before {
content: "\e8f3";
}
.veops-filter:before {
content: "\e8f2";
}
.veops-reduce:before {
content: "\e8ed";
}
.veops-increase:before {
content: "\e8ee";
}
.veops-configuration_table:before {
content: "\e8ef";
}
.veops-copy:before {
content: "\e8f0";
}
.veops-save:before {
content: "\e8f1";
}
.veops-setting:before {
content: "\e8ec";
}
.veops-default_avatar:before {
content: "\e8ea";
}
.veops-notice:before {
content: "\e8eb";
}
.itsm-quickStart:before {
content: "\e8e9";
}
.itsm-associatedWith:before {
content: "\e8e8";
}
.itsm-folder:before {
content: "\e8e7";
}
.report:before {
content: "\e8e5";
}
.folder:before {
content: "\e8e6";
}
.itsm-refresh:before {
content: "\e8e4";
}
.itsm-add_table:before {
content: "\e8e2";
}
.itsm-delete_page:before {
content: "\e8e3";
}
.oneterm-secret_key:before {
content: "\e8e0";
}
.oneterm-password:before {
content: "\e8e1";
}
.itsm-sla_timeout_not_handled:before {
content: "\e8dd";
}
.itsm-sla_not_timeout:before {
content: "\e8de";
}
.itsm-SLA:before {
content: "\e8df";
}
.itsm-sla_timeout_handled:before {
content: "\e8dc";
}
.itsm-sla_all:before {
content: "\e8da";
}
.itsm-generate_by_node_id:before {
content: "\e8db";
}
.cmdb-MySQL:before {
content: "\e8d9";
}
.OAUTH2:before { .OAUTH2:before {
content: "\e8d8"; content: "\e8d8";
} }
@@ -33,7 +261,7 @@
content: "\e8d4"; content: "\e8d4";
} }
.a-itsm-knowledge2:before { .itsm-knowledge2:before {
content: "\e8d2"; content: "\e8d2";
} }

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,405 @@
"css_prefix_text": "", "css_prefix_text": "",
"description": "", "description": "",
"glyphs": [ "glyphs": [
{
"icon_id": "39782649",
"name": "VPC",
"font_class": "caise-VPC",
"unicode": "e910",
"unicode_decimal": 59664
},
{
"icon_id": "39782643",
"name": "CDN",
"font_class": "caise-CDN",
"unicode": "e911",
"unicode_decimal": 59665
},
{
"icon_id": "39782632",
"name": "OOS",
"font_class": "caise-OOS",
"unicode": "e90f",
"unicode_decimal": 59663
},
{
"icon_id": "39729980",
"name": "Google Cloud Platform",
"font_class": "Google_Cloud_Platform",
"unicode": "e90b",
"unicode_decimal": 59659
},
{
"icon_id": "39729978",
"name": "Ctyun",
"font_class": "Ctyun",
"unicode": "e90c",
"unicode_decimal": 59660
},
{
"icon_id": "39729977",
"name": "Alibaba Cloud",
"font_class": "Alibaba_Cloud",
"unicode": "e90d",
"unicode_decimal": 59661
},
{
"icon_id": "39729976",
"name": "Azure",
"font_class": "Azure",
"unicode": "e90e",
"unicode_decimal": 59662
},
{
"icon_id": "39729985",
"name": "ZStack",
"font_class": "ZStack",
"unicode": "e904",
"unicode_decimal": 59652
},
{
"icon_id": "39729986",
"name": "Tencent Cloud",
"font_class": "Tencent_Cloud",
"unicode": "e905",
"unicode_decimal": 59653
},
{
"icon_id": "39729981",
"name": "Nutanix",
"font_class": "Nutanix",
"unicode": "e906",
"unicode_decimal": 59654
},
{
"icon_id": "39729983",
"name": "OpenStack",
"font_class": "OpenStack",
"unicode": "e907",
"unicode_decimal": 59655
},
{
"icon_id": "39729982",
"name": "Huawei Cloud",
"font_class": "Huawei_Cloud",
"unicode": "e908",
"unicode_decimal": 59656
},
{
"icon_id": "39729979",
"name": "Bytecloud",
"font_class": "Bytecloud",
"unicode": "e909",
"unicode_decimal": 59657
},
{
"icon_id": "39729984",
"name": "UCloud",
"font_class": "UCloud",
"unicode": "e90a",
"unicode_decimal": 59658
},
{
"icon_id": "39729988",
"name": "AWS",
"font_class": "AWS",
"unicode": "e901",
"unicode_decimal": 59649
},
{
"icon_id": "39729989",
"name": "ECloud",
"font_class": "ECloud",
"unicode": "e902",
"unicode_decimal": 59650
},
{
"icon_id": "39729987",
"name": "JDCloud",
"font_class": "JDCloud",
"unicode": "e903",
"unicode_decimal": 59651
},
{
"icon_id": "39713390",
"name": "veops-more",
"font_class": "veops-more",
"unicode": "e900",
"unicode_decimal": 59648
},
{
"icon_id": "39712276",
"name": "duose-date",
"font_class": "duose-date",
"unicode": "e8ff",
"unicode_decimal": 59647
},
{
"icon_id": "39712289",
"name": "duose-shishu",
"font_class": "duose-shishu",
"unicode": "e8fd",
"unicode_decimal": 59645
},
{
"icon_id": "39712286",
"name": "duose-wenben",
"font_class": "duose-wenben",
"unicode": "e8fe",
"unicode_decimal": 59646
},
{
"icon_id": "39712314",
"name": "duose-json",
"font_class": "duose-json",
"unicode": "e8f7",
"unicode_decimal": 59639
},
{
"icon_id": "39712315",
"name": "duose-fudianshu",
"font_class": "duose-fudianshu",
"unicode": "e8f8",
"unicode_decimal": 59640
},
{
"icon_id": "39712312",
"name": "duose-time",
"font_class": "duose-time",
"unicode": "e8f9",
"unicode_decimal": 59641
},
{
"icon_id": "39712313",
"name": "duose-password",
"font_class": "duose-password",
"unicode": "e8fa",
"unicode_decimal": 59642
},
{
"icon_id": "39712311",
"name": "duose-link",
"font_class": "duose-link",
"unicode": "e8fb",
"unicode_decimal": 59643
},
{
"icon_id": "39712310",
"name": "duose-datetime",
"font_class": "duose-datetime",
"unicode": "e8fc",
"unicode_decimal": 59644
},
{
"icon_id": "39705895",
"name": "veops-setting2",
"font_class": "veops-setting2",
"unicode": "e8f6",
"unicode_decimal": 59638
},
{
"icon_id": "39692404",
"name": "veops-search",
"font_class": "veops-search",
"unicode": "e8f5",
"unicode_decimal": 59637
},
{
"icon_id": "39680289",
"name": "veops-delete",
"font_class": "veops-delete",
"unicode": "e8f4",
"unicode_decimal": 59636
},
{
"icon_id": "39677378",
"name": "veops-refresh",
"font_class": "veops-refresh",
"unicode": "e8f3",
"unicode_decimal": 59635
},
{
"icon_id": "39677152",
"name": "veops-filter",
"font_class": "veops-filter",
"unicode": "e8f2",
"unicode_decimal": 59634
},
{
"icon_id": "39677216",
"name": "veops-reduce",
"font_class": "veops-reduce",
"unicode": "e8ed",
"unicode_decimal": 59629
},
{
"icon_id": "39677215",
"name": "veops-increase",
"font_class": "veops-increase",
"unicode": "e8ee",
"unicode_decimal": 59630
},
{
"icon_id": "39677211",
"name": "veops-configuration_table",
"font_class": "veops-configuration_table",
"unicode": "e8ef",
"unicode_decimal": 59631
},
{
"icon_id": "39677210",
"name": "veops-copy",
"font_class": "veops-copy",
"unicode": "e8f0",
"unicode_decimal": 59632
},
{
"icon_id": "39677207",
"name": "veops-save",
"font_class": "veops-save",
"unicode": "e8f1",
"unicode_decimal": 59633
},
{
"icon_id": "39664281",
"name": "veops-setting",
"font_class": "veops-setting",
"unicode": "e8ec",
"unicode_decimal": 59628
},
{
"icon_id": "39664295",
"name": "veops-default_avatar",
"font_class": "veops-default_avatar",
"unicode": "e8ea",
"unicode_decimal": 59626
},
{
"icon_id": "39664288",
"name": "veops-notice",
"font_class": "veops-notice",
"unicode": "e8eb",
"unicode_decimal": 59627
},
{
"icon_id": "39603135",
"name": "quickly_initiate",
"font_class": "itsm-quickStart",
"unicode": "e8e9",
"unicode_decimal": 59625
},
{
"icon_id": "39588554",
"name": "itsm-associated",
"font_class": "itsm-associatedWith",
"unicode": "e8e8",
"unicode_decimal": 59624
},
{
"icon_id": "39517726",
"name": "itsm-folder",
"font_class": "itsm-folder",
"unicode": "e8e7",
"unicode_decimal": 59623
},
{
"icon_id": "39517542",
"name": "report",
"font_class": "report",
"unicode": "e8e5",
"unicode_decimal": 59621
},
{
"icon_id": "39517539",
"name": "folder",
"font_class": "folder",
"unicode": "e8e6",
"unicode_decimal": 59622
},
{
"icon_id": "39123642",
"name": "itsm-refresh (1)",
"font_class": "itsm-refresh",
"unicode": "e8e4",
"unicode_decimal": 59620
},
{
"icon_id": "39123405",
"name": "itsm-add_table (1)",
"font_class": "itsm-add_table",
"unicode": "e8e2",
"unicode_decimal": 59618
},
{
"icon_id": "39123409",
"name": "itsm-delete_page",
"font_class": "itsm-delete_page",
"unicode": "e8e3",
"unicode_decimal": 59619
},
{
"icon_id": "39117681",
"name": "oneterm-secret_key",
"font_class": "oneterm-secret_key",
"unicode": "e8e0",
"unicode_decimal": 59616
},
{
"icon_id": "39117679",
"name": "oneterm-password",
"font_class": "oneterm-password",
"unicode": "e8e1",
"unicode_decimal": 59617
},
{
"icon_id": "39079529",
"name": "itsm-unprocessed",
"font_class": "itsm-sla_timeout_not_handled",
"unicode": "e8dd",
"unicode_decimal": 59613
},
{
"icon_id": "39079522",
"name": "itsm-not_timeout",
"font_class": "itsm-sla_not_timeout",
"unicode": "e8de",
"unicode_decimal": 59614
},
{
"icon_id": "39079520",
"name": "itsm-SLA",
"font_class": "itsm-SLA",
"unicode": "e8df",
"unicode_decimal": 59615
},
{
"icon_id": "39079538",
"name": "itsm-processed",
"font_class": "itsm-sla_timeout_handled",
"unicode": "e8dc",
"unicode_decimal": 59612
},
{
"icon_id": "39079519",
"name": "itsm-all_SLA",
"font_class": "itsm-sla_all",
"unicode": "e8da",
"unicode_decimal": 59610
},
{
"icon_id": "38970103",
"name": "itsm-generate_by_node_id",
"font_class": "itsm-generate_by_node_id",
"unicode": "e8db",
"unicode_decimal": 59611
},
{
"icon_id": "38806676",
"name": "cmdb-MySQL",
"font_class": "cmdb-MySQL",
"unicode": "e8d9",
"unicode_decimal": 59609
},
{ {
"icon_id": "38566548", "icon_id": "38566548",
"name": "OAuth2.0", "name": "OAuth2.0",
@@ -43,7 +442,7 @@
{ {
"icon_id": "38533133", "icon_id": "38533133",
"name": "itsm-knowledge (2)", "name": "itsm-knowledge (2)",
"font_class": "a-itsm-knowledge2", "font_class": "itsm-knowledge2",
"unicode": "e8d2", "unicode": "e8d2",
"unicode_decimal": 59602 "unicode_decimal": 59602
}, },
@@ -1841,7 +2240,7 @@
}, },
{ {
"icon_id": "35341667", "icon_id": "35341667",
"name": "caise-chajian ", "name": "caise-chajian",
"font_class": "caise-chajian", "font_class": "caise-chajian",
"unicode": "e7d2", "unicode": "e7d2",
"unicode_decimal": 59346 "unicode_decimal": 59346

Binary file not shown.

View File

@@ -30,9 +30,9 @@ export function getAuthDataEnable() {
}) })
} }
export function testLDAP(test_type, data) { export function testLDAP(data) {
return axios({ return axios({
url: `/common-setting/v1/auth_config/LDAP/test?test_type=${test_type}`, url: `/common-setting/v1/auth_config/LDAP/test`,
method: 'post', method: 'post',
data, data,
}) })

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

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

View File

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

View File

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

View File

@@ -17,25 +17,29 @@ export default {
getPropertyIcon(attr) { getPropertyIcon(attr) {
switch (attr.value_type) { switch (attr.value_type) {
case '0': case '0':
return 'icon-xianxing-shishu' return 'duose-shishu'
case '1': case '1':
return 'icon-xianxing-fudianshu' return 'duose-fudianshu'
case '2': case '2':
if (attr.is_password) { if (attr.is_password) {
return 'icon-xianxing-password' return 'duose-password'
} }
if (attr.is_link) { if (attr.is_link) {
return 'icon-xianxing-link' return 'duose-link'
} }
return 'icon-xianxing-wenben' return 'duose-wenben'
case '3': case '3':
return 'icon-xianxing-datetime' return 'duose-datetime'
case '4': case '4':
return 'icon-xianxing-date' return 'duose-date'
case '5': case '5':
return 'icon-xianxing-time' return 'duose-time'
case '6': case '6':
return 'icon-xianxing-json' return 'duose-json'
case '7':
return 'duose-password'
case '8':
return 'duose-link'
} }
}, },
}, },

View File

@@ -759,6 +759,52 @@ export const multicolorIconList = [
value: 'caise-redis', value: 'caise-redis',
label: 'redis' label: 'redis'
}] }]
}, {
value: 'cloud',
label: '云',
list: [{
value: 'AWS',
label: 'AWS'
}, {
value: 'Azure',
label: 'Azure'
}, {
value: 'Google_Cloud_Platform',
label: 'Google Cloud Platform'
}, {
value: 'Alibaba_Cloud',
label: '阿里云'
}, {
value: 'Huawei_Cloud',
label: '华为云'
}, {
value: 'Tencent_Cloud',
label: '腾讯云'
}, {
value: 'UCloud',
label: 'UCloud'
}, {
value: 'Ctyun',
label: '天翼云'
}, {
value: 'ECloud',
label: '移动云'
}, {
value: 'JDCloud',
label: '京东云'
}, {
value: 'Bytecloud',
label: '字节云'
}, {
value: 'OpenStack',
label: 'OpenStack'
}, {
value: 'ZStack',
label: 'ZStack'
}, {
value: 'Nutanix',
label: 'Nutanix'
}]
}, { }, {
value: 'system', value: 'system',
label: '操作系统', label: '操作系统',
@@ -976,17 +1022,14 @@ export const multicolorIconList = [
value: 'caise-tomcat', value: 'caise-tomcat',
label: 'Tomcat' label: 'Tomcat'
}, { }, {
value: 'caise-aliyun', value: 'caise-VPC',
label: '阿里云' label: 'VPC'
}, { }, {
value: 'caise-tengxunyun', value: 'caise-CDN',
label: '腾讯云' label: 'CDN'
}, { }, {
value: 'caise-huaweiyun', value: 'caise-OOS',
label: '华为云' label: '对象存储'
}, {
value: 'caise-aws',
label: 'AWS'
}] }]
}, { }, {
value: 'data', value: 'data',

View File

@@ -1,7 +1,7 @@
<template> <template>
<a-layout-sider <a-layout-sider
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null]" :class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null]"
width="200px" width="220px"
:collapsible="collapsible" :collapsible="collapsible"
v-model="collapsed" v-model="collapsed"
:trigger="null" :trigger="null"
@@ -15,6 +15,7 @@
@select="onSelect" @select="onSelect"
style="padding: 16px 0px;" style="padding: 16px 0px;"
></s-menu> ></s-menu>
<!-- <OpsDocs :collapsed="collapsed" /> -->
</a-layout-sider> </a-layout-sider>
</template> </template>
@@ -22,10 +23,13 @@
import Logo from '@/components/tools/Logo' import Logo from '@/components/tools/Logo'
import SMenu from './index' import SMenu from './index'
import { mixin, mixinDevice } from '@/utils/mixin' import { mixin, mixinDevice } from '@/utils/mixin'
// import OpsDocs from '@/modules/docs/index.vue'
export default { export default {
name: 'SideMenu', name: 'SideMenu',
components: { Logo, SMenu }, components: { Logo, SMenu,
// OpsDocs
},
mixins: [mixin, mixinDevice], mixins: [mixin, mixinDevice],
props: { props: {
mode: { mode: {

View File

@@ -1,121 +1,121 @@
<template> <template>
<vxe-table v-bind="$attrs" v-on="new$listeners" ref="xTable"> <vxe-table v-bind="$attrs" v-on="new$listeners" ref="xTable">
<slot></slot> <slot></slot>
<template #empty> <template #empty>
<slot name="empty"> <slot name="empty">
<div :style="{ paddingTop: '10px' }"> <div :style="{ paddingTop: '10px' }">
<img :style="{ width: '100px', height: '90px' }" :src="require('@/assets/data_empty.png')" /> <img :style="{ width: '140px', height: '90px' }" :src="require('@/assets/data_empty.png')" />
<div>{{ $t('noData') }}</div> <div>{{ $t('noData') }}</div>
</div> </div>
</slot> </slot>
</template> </template>
<template #loading> <template #loading>
<slot name="loading"></slot> <slot name="loading"></slot>
</template> </template>
</vxe-table> </vxe-table>
</template> </template>
<script> <script>
import _ from 'lodash' import _ from 'lodash'
// 该组件使用方法与vxe-table一致但调用它的方法时需先调用getVxetableRef()获取到vxe-table实体 // 该组件使用方法与vxe-table一致但调用它的方法时需先调用getVxetableRef()获取到vxe-table实体
export default { export default {
name: 'OpsTable', name: 'OpsTable',
data() { data() {
return { return {
// isShifting: false, // isShifting: false,
// lastIndex: -1, // lastIndex: -1,
lastSelected: [], lastSelected: [],
currentSelected: [], currentSelected: [],
} }
}, },
computed: { computed: {
new$listeners() { new$listeners() {
if (!Object.keys(this.$listeners).length) { if (!Object.keys(this.$listeners).length) {
return this.$listeners return this.$listeners
} }
return Object.assign(this.$listeners, { return Object.assign(this.$listeners, {
// 在这里覆盖原有的change事件 // 在这里覆盖原有的change事件
// 'checkbox-change': this.selectChangeEvent, // 'checkbox-change': this.selectChangeEvent,
'checkbox-range-change': this.checkboxRangeChange, 'checkbox-range-change': this.checkboxRangeChange,
'checkbox-range-start': this.checkboxRangeStart, 'checkbox-range-start': this.checkboxRangeStart,
'checkbox-range-end': this.checkboxRangeEnd, 'checkbox-range-end': this.checkboxRangeEnd,
}) })
}, },
}, },
mounted() { mounted() {
// window.onkeydown = (e) => { // window.onkeydown = (e) => {
// if (e.key === 'Shift') { // if (e.key === 'Shift') {
// this.isShifting = true // this.isShifting = true
// } // }
// } // }
// window.onkeyup = (e) => { // window.onkeyup = (e) => {
// if (e.key === 'Shift') { // if (e.key === 'Shift') {
// this.isShifting = false // this.isShifting = false
// this.lastIndex = -1 // this.lastIndex = -1
// } // }
// } // }
}, },
beforeDestroy() { beforeDestroy() {
// window.onkeydown = '' // window.onkeydown = ''
// window.onkeyup = '' // window.onkeyup = ''
}, },
methods: { methods: {
getVxetableRef() { getVxetableRef() {
return this.$refs.xTable return this.$refs.xTable
}, },
// selectChangeEvent(e) { // selectChangeEvent(e) {
// const xTable = this.$refs.xTable // const xTable = this.$refs.xTable
// const { lastIndex } = this // const { lastIndex } = this
// const currentIndex = e.rowIndex // const currentIndex = e.rowIndex
// const { tableData } = xTable.getTableData() // const { tableData } = xTable.getTableData()
// if (lastIndex > -1 && this.isShifting) { // if (lastIndex > -1 && this.isShifting) {
// let start = lastIndex // let start = lastIndex
// let end = currentIndex // let end = currentIndex
// if (lastIndex > currentIndex) { // if (lastIndex > currentIndex) {
// start = currentIndex // start = currentIndex
// end = lastIndex // end = lastIndex
// } // }
// const rangeData = tableData.slice(start, end + 1) // const rangeData = tableData.slice(start, end + 1)
// xTable.setCheckboxRow(rangeData, true) // xTable.setCheckboxRow(rangeData, true)
// } // }
// this.lastIndex = currentIndex // this.lastIndex = currentIndex
// this.$emit('checkbox-change', { ...e, records: xTable.getCheckboxRecords() }) // this.$emit('checkbox-change', { ...e, records: xTable.getCheckboxRecords() })
// }, // },
checkboxRangeStart(e) { checkboxRangeStart(e) {
const xTable = this.$refs.xTable const xTable = this.$refs.xTable
const lastSelected = xTable.getCheckboxRecords() const lastSelected = xTable.getCheckboxRecords()
const selectedReserve = xTable.getCheckboxReserveRecords() const selectedReserve = xTable.getCheckboxReserveRecords()
this.lastSelected = [...lastSelected, ...selectedReserve] this.lastSelected = [...lastSelected, ...selectedReserve]
this.$emit('checkbox-range-start', e) this.$emit('checkbox-range-start', e)
}, },
checkboxRangeChange(e) { checkboxRangeChange(e) {
const xTable = this.$refs.xTable const xTable = this.$refs.xTable
xTable.setCheckboxRow(this.lastSelected, true) xTable.setCheckboxRow(this.lastSelected, true)
this.currentSelected = e.records this.currentSelected = e.records
// this.lastSelected = [...new Set([...this.lastSelected, ...e.records])] // this.lastSelected = [...new Set([...this.lastSelected, ...e.records])]
this.$emit('checkbox-range-change', { this.$emit('checkbox-range-change', {
...e, ...e,
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()], records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
}) })
}, },
checkboxRangeEnd(e) { checkboxRangeEnd(e) {
const xTable = this.$refs.xTable const xTable = this.$refs.xTable
const isAllSelected = this.currentSelected.every((item) => { const isAllSelected = this.currentSelected.every((item) => {
const _idx = this.lastSelected.findIndex((ele) => _.isEqual(ele, item)) const _idx = this.lastSelected.findIndex((ele) => _.isEqual(ele, item))
return _idx > -1 return _idx > -1
}) })
if (isAllSelected) { if (isAllSelected) {
xTable.setCheckboxRow(this.currentSelected, false) xTable.setCheckboxRow(this.currentSelected, false)
} }
this.currentSelected = [] this.currentSelected = []
this.lastSelected = [] this.lastSelected = []
this.$emit('checkbox-range-end', { this.$emit('checkbox-range-end', {
...e, ...e,
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()], records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
}) })
}, },
}, },
} }
</script> </script>
<style lang="less"></style> <style lang="less"></style>

View File

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

View File

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

View File

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

View File

@@ -35,7 +35,7 @@ export default {
}, },
triggerColor: { triggerColor: {
type: String, type: String,
default: '#F0F5FF', default: '#f7f8fa',
}, },
}, },
data() { data() {
@@ -52,22 +52,21 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.two-column-layout { .two-column-layout {
margin-bottom: -24px; margin-bottom: -24px;
width: 100%; width: 100%;
.two-column-layout-sidebar { .two-column-layout-sidebar {
height: 100%; height: 100%;
padding: 15px 7px;
border-radius: 15px;
overflow-y: auto; overflow-y: auto;
background-color: #fff;
} }
.two-column-layout-main { .two-column-layout-main {
height: 100%; height: 100%;
padding: 12px; padding: 12px;
background-color: #fff; background-color: #fff;
overflow-y: auto; overflow-y: auto;
border-radius: 15px; border-radius: @border-radius-box;
} }
} }
</style> </style>

View File

@@ -1,15 +1,11 @@
<template> <template>
<div class="top-menu" v-if="routes.length > 2"> <div class="top-menu" v-if="routes.length > 2">
<!-- <a-menu v-model="current" mode="horizontal">
<a-menu-item :key="route.name" v-for="route in routes.slice(0, routes.length - 1)">
<router-link :to="{ name: route.name }">{{ route.meta.title }}</router-link>
</a-menu-item>
</a-menu>-->
<span <span
:class="current === route.name ? 'top-menu-selected' : ''" :class="current === route.name ? 'top-menu-selected' : ''"
v-for="route in defaultShowRoutes" v-for="route in defaultShowRoutes"
:key="route.name" :key="route.name"
@click="() => handleClick(route)" @click="() => handleClick(route)"
:title="$t(route.meta.title)"
> >
{{ route.meta.title }} {{ route.meta.title }}
</span> </span>
@@ -43,6 +39,7 @@
<script> <script>
import store from '@/store' import store from '@/store'
import { gridSvg, top_agent, top_acl } from '@/core/icons' import { gridSvg, top_agent, top_acl } from '@/core/icons'
import { getPreference } from '@/modules/cmdb/api/preference'
export default { export default {
name: 'TopMenu', name: 'TopMenu',
components: { gridSvg, top_agent, top_acl }, components: { gridSvg, top_agent, top_acl },
@@ -78,10 +75,20 @@ export default {
this.current = this.$route.matched[0].name this.current = this.$route.matched[0].name
}, },
methods: { methods: {
handleClick(route) { async handleClick(route) {
this.visible = false this.visible = false
if (route.name !== this.current) { if (route.name !== this.current) {
this.$router.push(route.redirect) if (route.name === 'cmdb') {
const preference = await getPreference()
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
if (lastTypeId && preference.some((item) => item.id === Number(lastTypeId))) {
this.$router.push(`/cmdb/instances/types/${lastTypeId}`)
} else {
this.$router.push('/cmdb/dashboard')
}
} else {
this.$router.push(route.redirect)
}
// this.current = route.name // this.current = route.name
} }
}, },
@@ -91,15 +98,6 @@ export default {
<style lang="less"> <style lang="less">
@import '../../style/static.less'; @import '../../style/static.less';
// .top-menu {
// display: inline-block;
// }
// .ant-menu-horizontal {
// border-bottom: 0 !important;
// }
// .ant-menu-horizontal > .ant-menu-item {
// border-bottom: 0;
// }
.top-menu { .top-menu {
display: inline-flex; display: inline-flex;
@@ -110,33 +108,29 @@ export default {
line-height: @layout-header-icon-height; line-height: @layout-header-icon-height;
border-radius: 4px !important; border-radius: 4px !important;
display: inline-flex; display: inline-flex;
align-items: flex-end; align-items: center;
} }
> span { > span {
cursor: pointer; cursor: pointer;
padding: 4px 10px; padding: 4px 10px;
margin: 0 5px; margin: 0 5px;
border-radius: 4px;
color: @layout-header-font-color; color: @layout-header-font-color;
height: @layout-header-height; height: @layout-header-height;
display: inline-flex; line-height: @layout-header-line-height;
align-items: center; display: inline-block;
&:hover {
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
color: @layout-header-font-selected-color;
border-radius: 3px 3px 0px 0px;
}
} }
> span:hover,
.top-menu-selected { .top-menu-selected {
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%); font-weight: bold;
color: @layout-header-font-selected-color; color: @layout-header-font-selected-color;
border-radius: 3px 3px 0px 0px; }
border-bottom: 3px solid @layout-header-font-selected-color; > span::before {
&:hover { display: block;
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%); content: attr(title);
color: @layout-header-font-selected-color; font-weight: bold;
border-radius: 3px 3px 0px 0px; height: 0;
} overflow: hidden;
visibility: hidden;
} }
} }

View File

@@ -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',

View File

@@ -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: '创建成功',

View File

@@ -87,21 +87,21 @@ export default {
computed: { computed: {
...mapState({ ...mapState({
// 动态主路由 // 动态主路由
mainMenu: state => state.routes.appRoutes, mainMenu: (state) => state.routes.appRoutes,
}), }),
contentPaddingLeft() { contentPaddingLeft() {
if (!this.fixSidebar || this.isMobile()) { if (!this.fixSidebar || this.isMobile()) {
return '0' return '0'
} }
if (this.sidebarOpened) { if (this.sidebarOpened) {
return '200px' return '220px'
} }
return '80px' return '80px'
}, },
sideBarMenu() { sideBarMenu() {
const sideMenus = this.mainMenu.filter(item => this.$route.path.startsWith(item.path)) const sideMenus = this.mainMenu.filter((item) => this.$route.path.startsWith(item.path))
if (sideMenus.length > 1) { if (sideMenus.length > 1) {
return sideMenus.find(item => item.path !== '/').children return sideMenus.find((item) => item.path !== '/').children
} else { } else {
return sideMenus[0].children return sideMenus[0].children
} }
@@ -110,6 +110,9 @@ export default {
provide() { provide() {
return { return {
reloadBoard: this.reload, reloadBoard: this.reload,
collapsed: () => {
return this.collapsed
},
} }
}, },
watch: { watch: {
@@ -146,15 +149,6 @@ export default {
this.alive = true this.alive = true
}) })
}, },
paddingCalc() {
let left = ''
if (this.sidebarOpened) {
left = this.isDesktop() ? '200px' : '80px'
} else {
left = (this.isMobile() && '0') || (this.fixSidebar && '80px') || '0'
}
return left
},
menuSelect() { menuSelect() {
if (!this.isDesktop()) { if (!this.isDesktop()) {
this.collapsed = false this.collapsed = false

View File

@@ -233,8 +233,10 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.acl-history { .acl-history {
border-radius: 15px; border-radius: @border-radius-box;
height: calc(100vh - 64px); height: calc(100vh - 64px);
margin-bottom: -24px; margin-bottom: -24px;
padding: 24px; padding: 24px;

View File

@@ -290,7 +290,6 @@ export default {
const str = ` ${key} : -> ${newVal} ` const str = ` ${key} : -> ${newVal} `
item.description += str item.description += str
} else { } else {
const str = ` ${key} : ${oldVal} -> ${newVal} `
item.description += ` ${key} : ${oldVal} -> ${newVal} ` item.description += ` ${key} : ${oldVal} -> ${newVal} `
} }
} }

View File

@@ -241,7 +241,6 @@ export default {
const str = ` ${key} : -> ${newVal} ` const str = ` ${key} : -> ${newVal} `
item.changeDescription += str item.changeDescription += str
} else { } else {
const str = ` ${key} : ${oldVal} -> ${newVal} `
item.changeDescription += ` ${key} : ${oldVal} -> ${newVal} ` item.changeDescription += ` ${key} : ${oldVal} -> ${newVal} `
} }
} }

View File

@@ -32,8 +32,10 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.acl-operation-history { .acl-operation-history {
border-radius: 15px; border-radius: @border-radius-box;
height: calc(100vh - 64px); height: calc(100vh - 64px);
margin-bottom: -24px; margin-bottom: -24px;
padding: 24px; padding: 24px;

View File

@@ -189,8 +189,10 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.acl-resource-types { .acl-resource-types {
border-radius: 15px; border-radius: @border-radius-box;
background-color: #fff; background-color: #fff;
height: calc(100vh - 64px); height: calc(100vh - 64px);
margin-bottom: -24px; margin-bottom: -24px;

View File

@@ -352,8 +352,10 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.acl-resources { .acl-resources {
border-radius: 15px; border-radius: @border-radius-box;
background-color: #fff; background-color: #fff;
height: calc(100vh - 64px); height: calc(100vh - 64px);
margin-bottom: -24px; margin-bottom: -24px;

View File

@@ -285,8 +285,10 @@ export default {
</script> </script>
<style lang="less"> <style lang="less">
@import '~@/style/static.less';
.acl-roles { .acl-roles {
border-radius: 15px; border-radius: @border-radius-box;
background-color: #fff; background-color: #fff;
height: calc(100vh - 64px); height: calc(100vh - 64px);
margin-bottom: -24px; margin-bottom: -24px;

View File

@@ -88,10 +88,12 @@ export default {
</script> </script>
<style lang="less"> <style lang="less">
@import '~@/style/static.less';
.acl-secret-key { .acl-secret-key {
background-color: #fff; background-color: #fff;
padding: 24px; padding: 24px;
border-radius: 15px; border-radius: @border-radius-box;
height: calc(100% + 24px); height: calc(100% + 24px);
.ant-input[disabled] { .ant-input[disabled] {
color: rgba(0, 0, 0, 0.5); color: rgba(0, 0, 0, 0.5);

View File

@@ -320,8 +320,10 @@ export default {
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.acl-trigger { .acl-trigger {
border-radius: 15px; border-radius: @border-radius-box;
background-color: #fff; background-color: #fff;
height: calc(100vh - 64px); height: calc(100vh - 64px);
margin-bottom: -24px; margin-bottom: -24px;

View File

@@ -188,8 +188,10 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.acl-users { .acl-users {
border-radius: 15px; border-radius: @border-radius-box;
background-color: #fff; background-color: #fff;
height: calc(100vh - 64px); height: calc(100vh - 64px);
margin-bottom: -24px; margin-bottom: -24px;

View File

@@ -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',
})
}

View File

@@ -30,11 +30,11 @@ export function getRelationTypes(CITypeID, parameter) {
}) })
} }
export function createRelation(parentId, childrenId, relationTypeId, constraint) { export function createRelation(parentId, childrenId, data) {
return axios({ return axios({
url: `/v0.1/ci_type_relations/${parentId}/${childrenId}`, url: `/v0.1/ci_type_relations/${parentId}/${childrenId}`,
method: 'post', method: 'post',
data: { relation_type_id: relationTypeId, constraint } data
}) })
} }
@@ -42,7 +42,6 @@ export function deleteRelation(parentId, childrenId) {
return axios({ return axios({
url: `/v0.1/ci_type_relations/${parentId}/${childrenId}`, url: `/v0.1/ci_type_relations/${parentId}/${childrenId}`,
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -71,6 +71,14 @@ export function subscribeRelationView(payload) {
}) })
} }
export function putRelationView(id, data) {
return axios({
url: `/v0.1/preference/relation/view/${id}`,
method: 'put',
data
})
}
// 用户保存条件过滤选项 // 用户保存条件过滤选项
export function getPreferenceSearch(payload) { export function getPreferenceSearch(payload) {
// 参数有prv_id: 关系视图的id ptv_id: 层级视图的id, type_id: 模型id // 参数有prv_id: 关系视图的id ptv_id: 层级视图的id, type_id: 模型id

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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>

View File

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

View File

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

View File

@@ -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>

View File

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

View File

@@ -11,7 +11,7 @@
@blur="handleInputConfirm" @blur="handleInputConfirm"
@keyup.enter="handleInputConfirm" @keyup.enter="handleInputConfirm"
/> />
<a-button v-else type="primary" size="small" ghost @click="showInput">{{ $t('cmdb.components.saveQuery') }}</a-button> <a v-else @click="showInput"> {{ $t('cmdb.components.saveQuery') }}</a>
</span> </span>
<template v-for="(item, index) in preferenceSearchList.slice(0, 3)"> <template v-for="(item, index) in preferenceSearchList.slice(0, 3)">
<span <span
@@ -178,10 +178,10 @@ export default {
<style lang="less" scoped> <style lang="less" scoped>
.preference-search-tag { .preference-search-tag {
cursor: pointer; cursor: pointer;
border-radius: 5px; border-radius: 2px;
border: none; border: 1px solid #d9d9d9;
display: inline-block; display: inline-block;
padding: 0 7px; padding: 2px 7px;
margin-right: 8px; margin-right: 8px;
> span { > span {
margin-right: 4px; margin-right: 4px;

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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',
@@ -149,8 +156,8 @@ const genCmdbRoutes = async () => {
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
if (lastTypeId && preference.some(item => item.id === Number(lastTypeId))) { if (lastTypeId && preference.some(item => item.id === Number(lastTypeId))) {
routes.redirect = `/cmdb/instances/types/${lastTypeId}` routes.redirect = `/cmdb/instances/types/${lastTypeId}`
} else if (routes.children[2].children.length > 0) { } else if (routes.children[2]?.children?.length > 0) {
routes.redirect = routes.children[2].children.find(item => !item.hidden).path routes.redirect = routes.children[2].children.find(item => !item.hidden)?.path
} else { } else {
routes.redirect = '/cmdb/dashboard' routes.redirect = '/cmdb/dashboard'
} }

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