Compare commits
114 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
27c733aa2c | ||
|
2a8e9e684e | ||
|
095190a785 | ||
|
ef25c94b5d | ||
|
06ae1bcf13 | ||
|
9ead4e7d8d | ||
|
994a28dd25 | ||
|
74b587e46c | ||
|
091cd882bd | ||
|
73093db467 | ||
|
66e268ce68 | ||
|
a41d1a5e97 | ||
|
b4b728fe28 | ||
|
d16462d8b7 | ||
|
de7d98c0b4 | ||
|
51332c7236 | ||
|
bf1076fe4a | ||
|
3454a98cfb | ||
|
506dcbb40e | ||
|
5ac4517187 | ||
|
761e98884b | ||
|
073654624e | ||
|
df54244ff1 | ||
|
27e9919198 | ||
|
dc8b1a5de2 | ||
|
d8a7728f1d | ||
|
82881965fb | ||
|
1bb62022f1 | ||
|
ed445a8d82 | ||
|
3626b1a97e | ||
|
32529fba9b | ||
|
a042b4fe39 | ||
|
a0631414dc | ||
|
5266cb5b88 | ||
|
c7d4bec988 | ||
|
099ddd6ca9 | ||
|
bd813174b1 | ||
|
0a43680d6e | ||
|
976c6cfe91 | ||
|
cf594f04ba | ||
|
4232094aed | ||
|
d08827d086 | ||
|
d25ae532cd | ||
|
9fbb6ee64d | ||
|
b62f0e96fd | ||
|
c1bcd0ce45 | ||
|
c8b55c34eb | ||
|
4b5906770f | ||
|
4188ac7252 | ||
|
2efbc6474a | ||
|
03eac0c4d2 | ||
|
2a861250eb | ||
|
8fc19d8b7c | ||
|
430d2ff6d0 | ||
|
2517009d70 | ||
|
67da360d80 | ||
|
24c56fb259 | ||
|
37d5da65de | ||
|
2224ebd533 | ||
|
bf6331d215 | ||
|
b18b90ab4e | ||
|
702e17a7a4 | ||
|
a7586aa140 | ||
|
ad3f96431c | ||
|
1515820713 | ||
|
7728b57878 | ||
|
a419eefd72 | ||
|
a44e5f6cf1 | ||
|
7d46e92c2d | ||
|
4117cf87ec | ||
|
9e0fe0b818 | ||
|
2a8f1ab9a4 | ||
|
c0fe99b8c7 | ||
|
42feb4b862 | ||
|
482d34993b | ||
|
7ff309b8b8 | ||
|
98eb47d44f | ||
|
9ab0f624ef | ||
|
3f3eda8b3c | ||
|
f788adc8cf | ||
|
693ae4ff05 | ||
|
a1a9d99eb4 | ||
|
e045e0fb43 | ||
|
09376dbd2b | ||
|
7fda5a1e7b | ||
|
113b84763f | ||
|
190170acad | ||
|
513d2af4b8 | ||
|
4588bd8996 | ||
|
082da5fade | ||
|
013b116eb5 | ||
|
208d29165b | ||
|
d510330cde | ||
|
ea4f0fc2a5 | ||
|
9bcdaacdc4 | ||
|
5045581ddf | ||
|
232913172c | ||
|
157e1809ed | ||
|
ab8ccf7d1b | ||
|
ae1f0f6b4f | ||
|
ff47e0ade6 | ||
|
0dd272fb04 | ||
|
6bc5c1516d | ||
|
4a4b9e6ef0 | ||
|
6e3f9478b3 | ||
|
691051c254 | ||
|
8f066e95a6 | ||
|
75bca39bf6 | ||
|
3360e4d0fe | ||
|
521fcd0ba2 | ||
|
fc113425cb | ||
|
81a76a9632 | ||
|
9ec105ca37 | ||
|
1e1c92a3ef |
@@ -74,20 +74,20 @@
|
||||
|
||||
### Docker 一键快速构建
|
||||
> 方法一
|
||||
- 第一步: 先安装 docker 环境, 以及docker-compose
|
||||
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
|
||||
- 第二步: 拷贝项目
|
||||
```shell
|
||||
git clone https://github.com/veops/cmdb.git
|
||||
```
|
||||
- 第三步:进入主目录,执行:
|
||||
```
|
||||
docker-compose up -d
|
||||
docker compose up -d
|
||||
```
|
||||
> 方法二, 该方法适用于linux系统
|
||||
- 第一步: 先安装 docker 环境, 以及docker-compose
|
||||
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
|
||||
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
|
||||
```shell
|
||||
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/master/install.sh
|
||||
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/deploy_on_kylin_docker/install.sh
|
||||
sh install.sh install
|
||||
```
|
||||
|
||||
|
@@ -5,8 +5,8 @@ name = "pypi"
|
||||
|
||||
[packages]
|
||||
# Flask
|
||||
Flask = "==2.3.2"
|
||||
Werkzeug = ">=2.3.6"
|
||||
Flask = "==2.2.5"
|
||||
Werkzeug = "==2.2.3"
|
||||
click = ">=5.0"
|
||||
# Api
|
||||
Flask-RESTful = "==0.3.10"
|
||||
@@ -15,6 +15,7 @@ Flask-SQLAlchemy = "==2.5.0"
|
||||
SQLAlchemy = "==1.4.49"
|
||||
PyMySQL = "==1.1.0"
|
||||
redis = "==4.6.0"
|
||||
python-redis-lock = "==4.0.0"
|
||||
# Migrations
|
||||
Flask-Migrate = "==2.5.2"
|
||||
# Deployment
|
||||
@@ -65,6 +66,7 @@ hvac = "==2.0.0"
|
||||
colorama = ">=0.4.6"
|
||||
pycryptodomex = ">=3.19.0"
|
||||
lz4 = ">=4.3.2"
|
||||
python-magic = "==0.4.27"
|
||||
|
||||
[dev-packages]
|
||||
# Testing
|
||||
|
@@ -50,7 +50,7 @@ def add_user():
|
||||
|
||||
if is_admin:
|
||||
app = AppCache.get('acl') or App.create(name='acl')
|
||||
acl_admin = RoleCache.get('acl_admin') or RoleCRUD.add_role('acl_admin', app.id, True)
|
||||
acl_admin = RoleCache.get_by_name(app.id, 'acl_admin') or RoleCRUD.add_role('acl_admin', app.id, True)
|
||||
rid = RoleCache.get_by_name(None, username).id
|
||||
|
||||
RoleRelationCRUD.add(acl_admin, acl_admin.id, [rid], app.id)
|
||||
|
@@ -55,9 +55,12 @@ def cmdb_init_cache():
|
||||
for cr in ci_relations:
|
||||
relations.setdefault(cr.first_ci_id, {}).update({cr.second_ci_id: cr.second_ci.type_id})
|
||||
if cr.ancestor_ids:
|
||||
relations2.setdefault(cr.ancestor_ids, {}).update({cr.second_ci_id: cr.second_ci.type_id})
|
||||
relations2.setdefault('{},{}'.format(cr.ancestor_ids, cr.first_ci_id), {}).update(
|
||||
{cr.second_ci_id: cr.second_ci.type_id})
|
||||
for i in relations:
|
||||
relations[i] = json.dumps(relations[i])
|
||||
for i in relations2:
|
||||
relations2[i] = json.dumps(relations2[i])
|
||||
if relations:
|
||||
rd.create_or_update(relations, REDIS_PREFIX_CI_RELATION)
|
||||
if relations2:
|
||||
@@ -115,6 +118,8 @@ def cmdb_init_acl():
|
||||
_app = AppCache.get('cmdb') or App.create(name='cmdb')
|
||||
app_id = _app.id
|
||||
|
||||
current_app.test_request_context().push()
|
||||
|
||||
# 1. add resource type
|
||||
for resource_type in ResourceTypeEnum.all():
|
||||
try:
|
||||
@@ -183,11 +188,21 @@ def cmdb_counter():
|
||||
UserCRUD.add(username='worker', password=uuid.uuid4().hex, email='worker@xxx.com')
|
||||
|
||||
login_user(UserCache.get('worker'))
|
||||
|
||||
i = 0
|
||||
while True:
|
||||
try:
|
||||
db.session.remove()
|
||||
|
||||
CMDBCounterCache.reset()
|
||||
|
||||
if i % 5 == 0:
|
||||
CMDBCounterCache.flush_adc_counter()
|
||||
i = 0
|
||||
|
||||
CMDBCounterCache.flush_sub_counter()
|
||||
|
||||
i += 1
|
||||
except:
|
||||
import traceback
|
||||
print(traceback.format_exc())
|
||||
@@ -345,13 +360,13 @@ def cmdb_inner_secrets_unseal(address):
|
||||
"""
|
||||
unseal the secrets feature
|
||||
"""
|
||||
if not valid_address(address):
|
||||
return
|
||||
# if not valid_address(address):
|
||||
# return
|
||||
address = "{}/api/v0.1/secrets/unseal".format(address.strip("/"))
|
||||
for i in range(global_key_threshold):
|
||||
token = click.prompt(f'Enter unseal token {i + 1}', hide_input=True, confirmation_prompt=False)
|
||||
assert token is not None
|
||||
resp = requests.post(address, headers={"Unseal-Token": token})
|
||||
resp = requests.post(address, headers={"Unseal-Token": token}, timeout=5)
|
||||
if resp.status_code == 200:
|
||||
KeyManage.print_response(resp.json())
|
||||
if resp.json().get("status") in ["success", "skip"]:
|
||||
|
@@ -4,8 +4,9 @@ from flask.cli import with_appcontext
|
||||
from werkzeug.datastructures import MultiDict
|
||||
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.common_setting.employee import EmployeeAddForm
|
||||
from api.lib.common_setting.employee import EmployeeAddForm, GrantEmployeeACLPerm
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.lib.common_setting.utils import CheckNewColumn
|
||||
from api.models.common_setting import Employee, Department
|
||||
|
||||
|
||||
@@ -158,50 +159,11 @@ class InitDepartment(object):
|
||||
|
||||
def init_backend_resource(self):
|
||||
acl = self.check_app('backend')
|
||||
resources_types = acl.get_all_resources_types()
|
||||
|
||||
perms = ['read', 'grant', 'delete', 'update']
|
||||
|
||||
acl_rid = self.get_admin_user_rid()
|
||||
|
||||
results = list(filter(lambda t: t['name'] == '操作权限', resources_types['groups']))
|
||||
if len(results) == 0:
|
||||
payload = dict(
|
||||
app_id=acl.app_name,
|
||||
name='操作权限',
|
||||
description='',
|
||||
perms=perms
|
||||
)
|
||||
resource_type = acl.create_resources_type(payload)
|
||||
else:
|
||||
resource_type = results[0]
|
||||
resource_type_id = resource_type['id']
|
||||
existed_perms = resources_types.get('id2perms', {}).get(resource_type_id, [])
|
||||
existed_perms = [p['name'] for p in existed_perms]
|
||||
new_perms = []
|
||||
for perm in perms:
|
||||
if perm not in existed_perms:
|
||||
new_perms.append(perm)
|
||||
if len(new_perms) > 0:
|
||||
resource_type['perms'] = existed_perms + new_perms
|
||||
acl.update_resources_type(resource_type_id, resource_type)
|
||||
|
||||
resource_list = acl.get_resource_by_type(None, None, resource_type['id'])
|
||||
|
||||
for name in ['公司信息', '公司架构', '通知设置']:
|
||||
target = list(filter(lambda r: r['name'] == name, resource_list))
|
||||
if len(target) == 0:
|
||||
payload = dict(
|
||||
type_id=resource_type['id'],
|
||||
app_id=acl.app_name,
|
||||
name=name,
|
||||
)
|
||||
resource = acl.create_resource(payload)
|
||||
else:
|
||||
resource = target[0]
|
||||
|
||||
if acl_rid > 0:
|
||||
acl.grant_resource(acl_rid, resource['id'], perms)
|
||||
if acl_rid == 0:
|
||||
return
|
||||
GrantEmployeeACLPerm(acl).grant_by_rid(acl_rid, True)
|
||||
|
||||
@staticmethod
|
||||
def check_app(app_name):
|
||||
@@ -248,57 +210,7 @@ def common_check_new_columns():
|
||||
"""
|
||||
add new columns to tables
|
||||
"""
|
||||
from api.extensions import db
|
||||
from sqlalchemy import inspect, text
|
||||
|
||||
def get_model_by_table_name(_table_name):
|
||||
registry = getattr(db.Model, 'registry', None)
|
||||
class_registry = getattr(registry, '_class_registry', None)
|
||||
for _model in class_registry.values():
|
||||
if hasattr(_model, '__tablename__') and _model.__tablename__ == _table_name:
|
||||
return _model
|
||||
return None
|
||||
|
||||
def add_new_column(target_table_name, new_column):
|
||||
column_type = new_column.type.compile(engine.dialect)
|
||||
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
|
||||
if new_column.comment:
|
||||
sql += f" comment '{new_column.comment}'"
|
||||
|
||||
if column_type == 'JSON':
|
||||
pass
|
||||
elif default_value:
|
||||
if column_type.startswith('VAR') or column_type.startswith('Text'):
|
||||
if default_value is None or len(default_value) == 0:
|
||||
pass
|
||||
else:
|
||||
sql += f" DEFAULT {default_value}"
|
||||
|
||||
sql = text(sql)
|
||||
db.session.execute(sql)
|
||||
|
||||
engine = db.get_engine()
|
||||
inspector = inspect(engine)
|
||||
table_names = inspector.get_table_names()
|
||||
for table_name in table_names:
|
||||
existed_columns = inspector.get_columns(table_name)
|
||||
existed_column_name_list = [c['name'] for c in existed_columns]
|
||||
|
||||
model = get_model_by_table_name(table_name)
|
||||
if model is None:
|
||||
continue
|
||||
|
||||
model_columns = getattr(getattr(getattr(model, '__table__'), 'columns'), '_all_columns')
|
||||
for column in model_columns:
|
||||
if column.name not in existed_column_name_list:
|
||||
try:
|
||||
add_new_column(table_name, column)
|
||||
current_app.logger.info(f"add new column [{column.name}] in table [{table_name}] success.")
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"add new column [{column.name}] in table [{table_name}] err:")
|
||||
current_app.logger.error(e)
|
||||
CheckNewColumn().run()
|
||||
|
||||
|
||||
@click.command()
|
||||
|
@@ -89,6 +89,19 @@ def db_setup():
|
||||
"""
|
||||
db.create_all()
|
||||
|
||||
try:
|
||||
db.session.execute("set global sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,"
|
||||
"ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'")
|
||||
db.session.commit()
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
db.session.execute("set global tidb_enable_noop_functions='ON'")
|
||||
db.session.commit()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@click.group()
|
||||
def translate():
|
||||
|
@@ -330,15 +330,15 @@ class AutoDiscoveryCICRUD(DBMixin):
|
||||
|
||||
@staticmethod
|
||||
def get_attributes_by_type_id(type_id):
|
||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
attributes = [i[1] for i in CITypeAttributesCache.get2(type_id) or []]
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
attributes = [i for i in CITypeAttributeManager.get_attributes_by_type_id(type_id) or []]
|
||||
|
||||
attr_names = set()
|
||||
adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id)
|
||||
for adt in adts:
|
||||
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
|
||||
def search(cls, page, page_size, fl=None, **kwargs):
|
||||
|
@@ -5,10 +5,14 @@ from __future__ import unicode_literals
|
||||
from flask import current_app
|
||||
|
||||
from api.extensions import cache
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.custom_dashboard import CustomDashboardManager
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIType
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
from api.models.cmdb import PreferenceTreeView
|
||||
from api.models.cmdb import RelationType
|
||||
|
||||
|
||||
@@ -226,7 +230,9 @@ class CITypeAttributeCache(object):
|
||||
|
||||
|
||||
class CMDBCounterCache(object):
|
||||
KEY = 'CMDB::Counter'
|
||||
KEY = 'CMDB::Counter::dashboard'
|
||||
KEY2 = 'CMDB::Counter::adc'
|
||||
KEY3 = 'CMDB::Counter::sub'
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
@@ -303,7 +309,7 @@ class CMDBCounterCache(object):
|
||||
|
||||
s = RelSearch([i[0] for i in type_id_names], level, other_filer or '')
|
||||
try:
|
||||
stats = s.statistics(type_ids)
|
||||
stats = s.statistics(type_ids, need_filter=False)
|
||||
except SearchError as e:
|
||||
current_app.logger.error(e)
|
||||
return
|
||||
@@ -429,3 +435,47 @@ class CMDBCounterCache(object):
|
||||
return
|
||||
|
||||
return numfound
|
||||
|
||||
@classmethod
|
||||
def flush_adc_counter(cls):
|
||||
res = db.session.query(CI.type_id, CI.is_auto_discovery)
|
||||
result = dict()
|
||||
for i in res:
|
||||
result.setdefault(i.type_id, dict(total=0, auto_discovery=0))
|
||||
result[i.type_id]['total'] += 1
|
||||
if i.is_auto_discovery:
|
||||
result[i.type_id]['auto_discovery'] += 1
|
||||
|
||||
cache.set(cls.KEY2, result, timeout=0)
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_adc_counter(cls):
|
||||
return cache.get(cls.KEY2) or cls.flush_adc_counter()
|
||||
|
||||
@classmethod
|
||||
def flush_sub_counter(cls):
|
||||
result = dict(type_id2users=dict())
|
||||
|
||||
types = db.session.query(PreferenceShowAttributes.type_id,
|
||||
PreferenceShowAttributes.uid, PreferenceShowAttributes.created_at).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
||||
PreferenceShowAttributes.uid, PreferenceShowAttributes.type_id)
|
||||
for i in types:
|
||||
result['type_id2users'].setdefault(i.type_id, []).append(i.uid)
|
||||
|
||||
types = PreferenceTreeView.get_by(to_dict=False)
|
||||
for i in types:
|
||||
|
||||
result['type_id2users'].setdefault(i.type_id, [])
|
||||
if i.uid not in result['type_id2users'][i.type_id]:
|
||||
result['type_id2users'][i.type_id].append(i.uid)
|
||||
|
||||
cache.set(cls.KEY3, result, timeout=0)
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_sub_counter(cls):
|
||||
return cache.get(cls.KEY3) or cls.flush_sub_counter()
|
||||
|
@@ -6,6 +6,7 @@ import datetime
|
||||
import json
|
||||
import threading
|
||||
|
||||
import redis_lock
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
@@ -14,8 +15,8 @@ from werkzeug.exceptions import BadRequest
|
||||
from api.extensions import db
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.cache import CMDBCounterCache
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
@@ -45,14 +46,12 @@ from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import validate_permission
|
||||
from api.lib.secrets.inner import InnerCrypt
|
||||
from api.lib.secrets.vault import VaultClient
|
||||
from api.lib.utils import Lock
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.lib.webhook import webhook_request
|
||||
from api.models.cmdb import AttributeHistory
|
||||
from api.models.cmdb import AutoDiscoveryCI
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.models.cmdb import CITypeTrigger
|
||||
from api.tasks.cmdb import ci_cache
|
||||
@@ -61,8 +60,8 @@ from api.tasks.cmdb import ci_delete_trigger
|
||||
from api.tasks.cmdb import ci_relation_add
|
||||
from api.tasks.cmdb import ci_relation_cache
|
||||
from api.tasks.cmdb import ci_relation_delete
|
||||
from api.tasks.cmdb import delete_id_filter
|
||||
|
||||
PRIVILEGED_USERS = {"worker", "cmdb_agent", "agent"}
|
||||
PASSWORD_DEFAULT_SHOW = "******"
|
||||
|
||||
|
||||
@@ -218,15 +217,7 @@ class CIManager(object):
|
||||
|
||||
@classmethod
|
||||
def get_ad_statistics(cls):
|
||||
res = CI.get_by(to_dict=False)
|
||||
result = dict()
|
||||
for i in res:
|
||||
result.setdefault(i.type_id, dict(total=0, auto_discovery=0))
|
||||
result[i.type_id]['total'] += 1
|
||||
if i.is_auto_discovery:
|
||||
result[i.type_id]['auto_discovery'] += 1
|
||||
|
||||
return result
|
||||
return CMDBCounterCache.get_adc_counter()
|
||||
|
||||
@staticmethod
|
||||
def ci_is_exist(unique_key, unique_value, type_id):
|
||||
@@ -272,10 +263,11 @@ class CIManager(object):
|
||||
ci_ids = None
|
||||
for attr_id in constraint.attr_ids:
|
||||
value_table = TableMap(attr_name=id2name[attr_id]).table
|
||||
|
||||
_ci_ids = set([i.ci_id for i in value_table.get_by(attr_id=attr_id,
|
||||
to_dict=False,
|
||||
value=ci_dict.get(id2name[attr_id]) or None)])
|
||||
values = value_table.get_by(attr_id=attr_id,
|
||||
value=ci_dict.get(id2name[attr_id]) or None,
|
||||
only_query=True).join(
|
||||
CI, CI.id == value_table.ci_id).filter(CI.type_id == type_id)
|
||||
_ci_ids = set([i.ci_id for i in values])
|
||||
if ci_ids is None:
|
||||
ci_ids = _ci_ids
|
||||
else:
|
||||
@@ -287,16 +279,16 @@ class CIManager(object):
|
||||
|
||||
@staticmethod
|
||||
def _auto_inc_id(attr):
|
||||
db.session.remove()
|
||||
db.session.commit()
|
||||
|
||||
value_table = TableMap(attr_name=attr.name).table
|
||||
with Lock("auto_inc_id_{}".format(attr.name), need_lock=True):
|
||||
with redis_lock.Lock(rd.r, "auto_inc_id_{}".format(attr.name)):
|
||||
max_v = value_table.get_by(attr_id=attr.id, only_query=True).order_by(
|
||||
getattr(value_table, 'value').desc()).first()
|
||||
if max_v is not None:
|
||||
return int(max_v.value) + 1
|
||||
|
||||
return 1
|
||||
return 1
|
||||
|
||||
@classmethod
|
||||
def add(cls, ci_type_name,
|
||||
@@ -304,6 +296,7 @@ class CIManager(object):
|
||||
_no_attribute_policy=ExistPolicy.IGNORE,
|
||||
is_auto_discovery=False,
|
||||
_is_admin=False,
|
||||
ticket_id=None,
|
||||
**ci_dict):
|
||||
"""
|
||||
add ci
|
||||
@@ -312,19 +305,24 @@ class CIManager(object):
|
||||
:param _no_attribute_policy: ignore or reject
|
||||
:param is_auto_discovery: default is False
|
||||
:param _is_admin: default is False
|
||||
:param ticket_id:
|
||||
:param ci_dict:
|
||||
:return:
|
||||
"""
|
||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
ci_type = CITypeManager.check_is_existed(ci_type_name)
|
||||
raw_dict = copy.deepcopy(ci_dict)
|
||||
|
||||
unique_key = AttributeCache.get(ci_type.unique_id) or abort(
|
||||
400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id)))
|
||||
|
||||
unique_value = ci_dict.get(unique_key.name) or ci_dict.get(unique_key.alias) or ci_dict.get(unique_key.id)
|
||||
unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name))
|
||||
unique_value = None
|
||||
if not (unique_key.default and unique_key.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and
|
||||
not ci_dict.get(unique_key.name)): # primary key is not auto inc id
|
||||
unique_value = ci_dict.get(unique_key.name) or ci_dict.get(unique_key.alias) or ci_dict.get(unique_key.id)
|
||||
unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name))
|
||||
|
||||
attrs = CITypeAttributesCache.get2(ci_type_name)
|
||||
attrs = CITypeAttributeManager.get_all_attributes(ci_type.id)
|
||||
ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
|
||||
ci_type_attrs_alias = {attr.alias: attr for _, attr in attrs}
|
||||
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
||||
@@ -332,8 +330,15 @@ class CIManager(object):
|
||||
ci = None
|
||||
record_id = None
|
||||
password_dict = {}
|
||||
need_lock = current_user.username not in current_app.config.get('PRIVILEGED_USERS', PRIVILEGED_USERS)
|
||||
with Lock(ci_type_name, need_lock=need_lock):
|
||||
with redis_lock.Lock(rd.r, ci_type.name):
|
||||
db.session.commit()
|
||||
|
||||
if (unique_key.default and unique_key.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and
|
||||
not ci_dict.get(unique_key.name)):
|
||||
ci_dict[unique_key.name] = cls._auto_inc_id(unique_key)
|
||||
current_app.logger.info(ci_dict[unique_key.name])
|
||||
unique_value = ci_dict[unique_key.name]
|
||||
|
||||
existed = cls.ci_is_exist(unique_key, unique_value, ci_type.id)
|
||||
if existed is not None:
|
||||
if exist_policy == ExistPolicy.REJECT:
|
||||
@@ -354,7 +359,8 @@ class CIManager(object):
|
||||
if attr.default.get('default') and attr.default.get('default') in (
|
||||
AttributeDefaultValueEnum.CREATED_AT, AttributeDefaultValueEnum.UPDATED_AT):
|
||||
ci_dict[attr.name] = now
|
||||
elif attr.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID:
|
||||
elif (attr.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and
|
||||
not ci_dict.get(attr.name)):
|
||||
ci_dict[attr.name] = cls._auto_inc_id(attr)
|
||||
elif ((attr.name not in ci_dict and attr.alias not in ci_dict) or (
|
||||
ci_dict.get(attr.name) is None and ci_dict.get(attr.alias) is None)):
|
||||
@@ -368,6 +374,8 @@ class CIManager(object):
|
||||
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
ci_dict[attr.name] = now
|
||||
|
||||
value_manager = AttributeValueManager()
|
||||
|
||||
computed_attrs = []
|
||||
for _, attr in attrs:
|
||||
if attr.is_computed:
|
||||
@@ -378,7 +386,8 @@ class CIManager(object):
|
||||
elif attr.alias in ci_dict:
|
||||
password_dict[attr.id] = ci_dict.pop(attr.alias)
|
||||
|
||||
value_manager = AttributeValueManager()
|
||||
if attr.re_check and password_dict.get(attr.id):
|
||||
value_manager.check_re(attr.re_check, password_dict[attr.id])
|
||||
|
||||
if computed_attrs:
|
||||
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
|
||||
@@ -386,7 +395,7 @@ class CIManager(object):
|
||||
cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
|
||||
|
||||
ref_ci_dict = dict()
|
||||
for k in ci_dict:
|
||||
for k in copy.deepcopy(ci_dict):
|
||||
if k.startswith("$") and "." in k:
|
||||
ref_ci_dict[k] = ci_dict[k]
|
||||
continue
|
||||
@@ -398,7 +407,10 @@ class CIManager(object):
|
||||
_attr_name = ((ci_type_attrs_name.get(k) and ci_type_attrs_name[k].name) or
|
||||
(ci_type_attrs_alias.get(k) and ci_type_attrs_alias[k].name))
|
||||
if limit_attrs and _attr_name not in limit_attrs:
|
||||
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}
|
||||
|
||||
@@ -408,7 +420,7 @@ class CIManager(object):
|
||||
operate_type = OperateType.UPDATE if ci is not None else OperateType.ADD
|
||||
try:
|
||||
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:
|
||||
if existed is None:
|
||||
cls.delete(ci.id)
|
||||
@@ -426,17 +438,24 @@ class CIManager(object):
|
||||
|
||||
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')
|
||||
ci = self.confirm_ci_existed(ci_id)
|
||||
|
||||
attrs = CITypeAttributesCache.get2(ci.type_id)
|
||||
attrs = CITypeAttributeManager.get_all_attributes(ci.type_id)
|
||||
ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
|
||||
ci_type_attrs_alias2name = {attr.alias: attr.name for _, attr in attrs}
|
||||
ci_dict = {ci_type_attrs_alias2name[k] if k in ci_type_attrs_alias2name else k: v for k, v in ci_dict.items()}
|
||||
|
||||
raw_dict = copy.deepcopy(ci_dict)
|
||||
|
||||
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
||||
for _, attr in attrs:
|
||||
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
ci_dict[attr.name] = now
|
||||
|
||||
value_manager = AttributeValueManager()
|
||||
|
||||
password_dict = dict()
|
||||
computed_attrs = list()
|
||||
for _, attr in attrs:
|
||||
@@ -448,7 +467,8 @@ class CIManager(object):
|
||||
elif attr.alias in ci_dict:
|
||||
password_dict[attr.id] = ci_dict.pop(attr.alias)
|
||||
|
||||
value_manager = AttributeValueManager()
|
||||
if attr.re_check and password_dict.get(attr.id):
|
||||
value_manager.check_re(attr.re_check, password_dict[attr.id])
|
||||
|
||||
if computed_attrs:
|
||||
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
|
||||
@@ -456,20 +476,24 @@ class CIManager(object):
|
||||
limit_attrs = self._valid_ci_for_no_read(ci) if not _is_admin else {}
|
||||
|
||||
record_id = None
|
||||
need_lock = current_user.username not in current_app.config.get('PRIVILEGED_USERS', PRIVILEGED_USERS)
|
||||
with Lock(ci.ci_type.name, need_lock=need_lock):
|
||||
with redis_lock.Lock(rd.r, ci.ci_type.name):
|
||||
db.session.commit()
|
||||
|
||||
self._valid_unique_constraint(ci.type_id, ci_dict, ci_id)
|
||||
|
||||
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name}
|
||||
key2attr = value_manager.valid_attr_value(ci_dict, ci.type_id, ci.id, ci_type_attrs_name,
|
||||
ci_attr2type_attr=ci_attr2type_attr)
|
||||
if limit_attrs:
|
||||
for k in ci_dict:
|
||||
for k in copy.deepcopy(ci_dict):
|
||||
if k not in limit_attrs:
|
||||
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:
|
||||
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:
|
||||
raise e
|
||||
|
||||
@@ -514,8 +538,7 @@ class CIManager(object):
|
||||
|
||||
ci_delete_trigger.apply_async(args=(trigger, OperateType.DELETE, ci_dict), queue=CMDB_QUEUE)
|
||||
|
||||
attrs = CITypeAttribute.get_by(type_id=ci.type_id, to_dict=False)
|
||||
attrs = [AttributeCache.get(attr.attr_id) for attr in attrs]
|
||||
attrs = [i for _, i in CITypeAttributeManager.get_all_attributes(type_id=ci.type_id)]
|
||||
for attr in attrs:
|
||||
value_table = TableMap(attr=attr).table
|
||||
for item in value_table.get_by(ci_id=ci_id, to_dict=False):
|
||||
@@ -542,6 +565,7 @@ class CIManager(object):
|
||||
AttributeHistoryManger.add(None, ci_id, [(None, OperateType.DELETE, ci_dict, None)], ci.type_id)
|
||||
|
||||
ci_delete.apply_async(args=(ci_id,), queue=CMDB_QUEUE)
|
||||
delete_id_filter.apply_async(args=(ci_id,), queue=CMDB_QUEUE)
|
||||
|
||||
return ci_id
|
||||
|
||||
@@ -805,6 +829,105 @@ class CIManager(object):
|
||||
|
||||
return data.get('v')
|
||||
|
||||
def baseline(self, ci_ids, before_date):
|
||||
ci_list = self.get_cis_by_ids(ci_ids, ret_key=RetKey.ALIAS)
|
||||
if not ci_list:
|
||||
return dict()
|
||||
|
||||
ci2changed = dict()
|
||||
changed = AttributeHistoryManger.get_records_for_attributes(
|
||||
before_date, None, None, 1, 100000, None, None, ci_ids=ci_ids, more=True)[1]
|
||||
for records in changed:
|
||||
for change in records[1]:
|
||||
if change['is_computed'] or change['is_password']:
|
||||
continue
|
||||
|
||||
if change.get('default') and change['default'].get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
continue
|
||||
|
||||
ci2changed.setdefault(change['ci_id'], {})
|
||||
item = (change['old'],
|
||||
change['new'],
|
||||
change.get('is_list'),
|
||||
change.get('value_type'),
|
||||
change['operate_type'])
|
||||
if change.get('is_list'):
|
||||
ci2changed[change['ci_id']].setdefault(change.get('attr_alias'), []).append(item)
|
||||
else:
|
||||
ci2changed[change['ci_id']].update({change.get('attr_alias'): item})
|
||||
|
||||
type2show_name = {}
|
||||
result = []
|
||||
for ci in ci_list:
|
||||
list_attr2item = {}
|
||||
for alias_name, v in (ci2changed.get(ci['_id']) or {}).items():
|
||||
if not alias_name:
|
||||
continue
|
||||
if alias_name == ci.get('unique_alias'):
|
||||
continue
|
||||
|
||||
if ci.get('_type') not in type2show_name:
|
||||
ci_type = CITypeCache.get(ci.get('_type'))
|
||||
show_id = ci_type.show_id or ci_type.unique_id
|
||||
type2show_name[ci['_type']] = AttributeCache.get(show_id).alias
|
||||
|
||||
if isinstance(v, list):
|
||||
for old, new, is_list, value_type, operate_type in v:
|
||||
if alias_name not in list_attr2item:
|
||||
list_attr2item[alias_name] = dict(instance=ci.get(type2show_name[ci['_type']]),
|
||||
attr_name=alias_name,
|
||||
value_type=value_type,
|
||||
is_list=is_list,
|
||||
ci_type=ci.get('ci_type'),
|
||||
unique_alias=ci.get('unique_alias'),
|
||||
unique_value=ci.get(ci['unique_alias']),
|
||||
cur=copy.deepcopy(ci.get(alias_name)),
|
||||
to=ci.get(alias_name) or [])
|
||||
|
||||
old = ValueTypeMap.deserialize[value_type](old) if old else old
|
||||
new = ValueTypeMap.deserialize[value_type](new) if new else new
|
||||
if operate_type == OperateType.ADD:
|
||||
list_attr2item[alias_name]['to'].remove(new)
|
||||
elif operate_type == OperateType.DELETE and old not in list_attr2item[alias_name]['to']:
|
||||
list_attr2item[alias_name]['to'].append(old)
|
||||
continue
|
||||
|
||||
old, value_type = v[0], v[3]
|
||||
old = ValueTypeMap.deserialize[value_type](old) if old else old
|
||||
if isinstance(old, (datetime.datetime, datetime.date)):
|
||||
old = str(old)
|
||||
if ci.get(alias_name) != old:
|
||||
item = dict(instance=ci.get(type2show_name[ci['_type']]),
|
||||
attr_name=alias_name,
|
||||
value_type=value_type,
|
||||
ci_type=ci.get('ci_type'),
|
||||
unique_alias=ci.get('unique_alias'),
|
||||
unique_value=ci.get(ci['unique_alias']),
|
||||
cur=ci.get(alias_name),
|
||||
to=old)
|
||||
result.append(item)
|
||||
|
||||
for alias_name, item in list_attr2item.items():
|
||||
if sorted(item['cur'] or []) != sorted(item['to'] or []):
|
||||
result.append(item)
|
||||
|
||||
return result
|
||||
|
||||
def rollback(self, ci_id, before_date):
|
||||
baseline_ci = self.baseline([ci_id], before_date)
|
||||
|
||||
payload = dict()
|
||||
for item in baseline_ci:
|
||||
payload[item.get('attr_name')] = item.get('to')
|
||||
|
||||
if payload:
|
||||
payload['ci_type'] = baseline_ci[0]['ci_type']
|
||||
payload[baseline_ci[0]['unique_alias']] = baseline_ci[0]['unique_value']
|
||||
|
||||
self.update(ci_id, **payload)
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
class CIRelationManager(object):
|
||||
"""
|
||||
@@ -848,6 +971,20 @@ class CIRelationManager(object):
|
||||
|
||||
return numfound, len(ci_ids), result
|
||||
|
||||
@staticmethod
|
||||
def recursive_children(ci_id):
|
||||
result = []
|
||||
|
||||
def _get_children(_id):
|
||||
children = CIRelation.get_by(first_ci_id=_id, to_dict=False)
|
||||
result.extend([i.second_ci_id for i in children])
|
||||
for child in children:
|
||||
_get_children(child.second_ci_id)
|
||||
|
||||
_get_children(ci_id)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _sort_handler(sort_by, query_sql):
|
||||
|
||||
@@ -903,7 +1040,7 @@ class CIRelationManager(object):
|
||||
|
||||
@staticmethod
|
||||
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
|
||||
db.session.remove()
|
||||
db.session.commit()
|
||||
if type_relation.constraint == ConstraintEnum.Many2Many:
|
||||
return
|
||||
|
||||
@@ -926,7 +1063,7 @@ class CIRelationManager(object):
|
||||
return abort(400, ErrFormat.relation_constraint.format("1-N"))
|
||||
|
||||
@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)
|
||||
second_ci = CIManager.confirm_ci_existed(second_ci_id)
|
||||
@@ -951,7 +1088,7 @@ class CIRelationManager(object):
|
||||
relation_type_id or abort(404, ErrFormat.relation_not_found.format("{} -> {}".format(
|
||||
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,
|
||||
second_ci.ci_type.name)
|
||||
if not ACLManager().has_permission(
|
||||
@@ -963,7 +1100,7 @@ class CIRelationManager(object):
|
||||
else:
|
||||
type_relation = CITypeRelation.get_by_id(relation_type_id)
|
||||
|
||||
with Lock("ci_relation_add_{}_{}".format(first_ci.type_id, second_ci.type_id), need_lock=True):
|
||||
with redis_lock.Lock(rd.r, "ci_relation_add_{}_{}".format(first_ci.type_id, second_ci.type_id)):
|
||||
|
||||
cls._check_constraint(first_ci_id, first_ci.type_id, second_ci_id, second_ci.type_id, type_relation)
|
||||
|
||||
@@ -999,6 +1136,7 @@ class CIRelationManager(object):
|
||||
his_manager.add(cr, operate_type=OperateType.DELETE)
|
||||
|
||||
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
||||
delete_id_filter.apply_async(args=(cr.second_ci_id,), queue=CMDB_QUEUE)
|
||||
|
||||
return cr_id
|
||||
|
||||
@@ -1010,9 +1148,13 @@ class CIRelationManager(object):
|
||||
to_dict=False,
|
||||
first=True)
|
||||
|
||||
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
|
||||
if cr is not None:
|
||||
cls.delete(cr.id)
|
||||
|
||||
return cr and cls.delete(cr.id)
|
||||
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
|
||||
delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
|
||||
|
||||
return cr
|
||||
|
||||
@classmethod
|
||||
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
|
||||
@@ -1049,11 +1191,62 @@ class CIRelationManager(object):
|
||||
for ci_id in ci_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):
|
||||
@staticmethod
|
||||
def get(type_id):
|
||||
db.session.remove()
|
||||
db.session.commit()
|
||||
return CITypeTrigger.get_by(type_id=type_id, to_dict=True)
|
||||
|
||||
@staticmethod
|
||||
|
@@ -42,10 +42,12 @@ from api.models.cmdb import CITypeAttributeGroup
|
||||
from api.models.cmdb import CITypeAttributeGroupItem
|
||||
from api.models.cmdb import CITypeGroup
|
||||
from api.models.cmdb import CITypeGroupItem
|
||||
from api.models.cmdb import CITypeInheritance
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.models.cmdb import CITypeTrigger
|
||||
from api.models.cmdb import CITypeUniqueConstraint
|
||||
from api.models.cmdb import CustomDashboard
|
||||
from api.models.cmdb import PreferenceCITypeOrder
|
||||
from api.models.cmdb import PreferenceRelationView
|
||||
from api.models.cmdb import PreferenceSearchOption
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
@@ -74,17 +76,26 @@ class CITypeManager(object):
|
||||
|
||||
return CIType.get_by_id(ci_type.id)
|
||||
|
||||
def get_icons(self):
|
||||
return {i.id: i.icon or i.name for i in db.session.query(
|
||||
self.cls.id, self.cls.icon, self.cls.name).filter(self.cls.deleted.is_(False))}
|
||||
|
||||
@staticmethod
|
||||
def get_ci_types(type_name=None):
|
||||
def get_ci_types(type_name=None, like=True):
|
||||
resources = None
|
||||
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
|
||||
resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.CI_TYPE)])
|
||||
|
||||
ci_types = CIType.get_by() if type_name is None else CIType.get_by_like(name=type_name)
|
||||
ci_types = CIType.get_by() if type_name is None else (
|
||||
CIType.get_by_like(name=type_name) if like else CIType.get_by(name=type_name))
|
||||
res = list()
|
||||
for type_dict in ci_types:
|
||||
attr = AttributeCache.get(type_dict["unique_id"])
|
||||
type_dict["unique_key"] = attr and attr.name
|
||||
if type_dict.get('show_id'):
|
||||
attr = AttributeCache.get(type_dict["show_id"])
|
||||
type_dict["show_name"] = attr and attr.name
|
||||
type_dict['parent_ids'] = CITypeInheritanceManager.get_parents(type_dict['id'])
|
||||
if resources is None or type_dict['name'] in resources:
|
||||
res.append(type_dict)
|
||||
|
||||
@@ -131,8 +142,13 @@ class CITypeManager(object):
|
||||
|
||||
kwargs["unique_id"] = unique_key.id
|
||||
kwargs['uid'] = current_user.uid
|
||||
|
||||
parent_ids = kwargs.pop('parent_ids', None)
|
||||
|
||||
ci_type = CIType.create(**kwargs)
|
||||
|
||||
CITypeInheritanceManager.add(parent_ids, ci_type.id)
|
||||
|
||||
CITypeAttributeManager.add(ci_type.id, [unique_key.id], is_required=True)
|
||||
|
||||
CITypeCache.clean(ci_type.name)
|
||||
@@ -179,7 +195,7 @@ class CITypeManager(object):
|
||||
CITypeAttributeManager.update(type_id, [attr])
|
||||
|
||||
ci_type2 = ci_type.to_dict()
|
||||
new = ci_type.update(**kwargs)
|
||||
new = ci_type.update(**kwargs, filter_none=False)
|
||||
|
||||
CITypeCache.clean(type_id)
|
||||
|
||||
@@ -214,21 +230,36 @@ class CITypeManager(object):
|
||||
if item.get('parent_id') == type_id or item.get('child_id') == type_id:
|
||||
return abort(400, ErrFormat.ci_relation_view_exists_and_cannot_delete_type.format(rv.name))
|
||||
|
||||
for item in CITypeRelation.get_by(parent_id=type_id, to_dict=False):
|
||||
item.soft_delete(commit=False)
|
||||
for item in (CITypeRelation.get_by(parent_id=type_id, to_dict=False) +
|
||||
CITypeRelation.get_by(child_id=type_id, to_dict=False)):
|
||||
if current_app.config.get('USE_ACL'):
|
||||
resource_name = CITypeRelationManager.acl_resource_name(item.parent.name, item.child.name)
|
||||
ACLManager().del_resource(resource_name, ResourceTypeEnum.CI_TYPE_RELATION)
|
||||
|
||||
for item in CITypeRelation.get_by(child_id=type_id, to_dict=False):
|
||||
item.soft_delete(commit=False)
|
||||
|
||||
for table in [PreferenceTreeView, PreferenceShowAttributes, PreferenceSearchOption, CustomDashboard,
|
||||
CITypeGroupItem, CITypeAttributeGroup, CITypeAttribute, CITypeUniqueConstraint, CITypeTrigger,
|
||||
AutoDiscoveryCIType, CIFilterPerms]:
|
||||
AutoDiscoveryCIType, CIFilterPerms, PreferenceCITypeOrder]:
|
||||
for item in table.get_by(type_id=type_id, to_dict=False):
|
||||
item.soft_delete(commit=False)
|
||||
|
||||
for item in AutoDiscoveryCI.get_by(type_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
for item in CITypeInheritance.get_by(parent_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
try:
|
||||
from api.models.cmdb import CITypeReconciliation
|
||||
for item in CITypeReconciliation.get_by(type_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
db.session.commit()
|
||||
|
||||
ci_type.soft_delete()
|
||||
@@ -241,6 +272,100 @@ class CITypeManager(object):
|
||||
ACLManager().del_resource(ci_type.name, ResourceTypeEnum.CI)
|
||||
|
||||
|
||||
class CITypeInheritanceManager(object):
|
||||
cls = CITypeInheritance
|
||||
|
||||
@classmethod
|
||||
def get_parents(cls, type_id):
|
||||
return [i.parent_id for i in cls.cls.get_by(child_id=type_id, to_dict=False)]
|
||||
|
||||
@classmethod
|
||||
def recursive_children(cls, type_id):
|
||||
result = []
|
||||
|
||||
def _get_child(_id):
|
||||
children = [i.child_id for i in cls.cls.get_by(parent_id=_id, to_dict=False)]
|
||||
result.extend(children)
|
||||
for child_id in children:
|
||||
_get_child(child_id)
|
||||
|
||||
_get_child(type_id)
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def base(cls, type_id):
|
||||
result = []
|
||||
q = []
|
||||
|
||||
def _get_parents(_type_id):
|
||||
parents = [i.parent_id for i in cls.cls.get_by(child_id=_type_id, to_dict=False)]
|
||||
for i in parents[::-1]:
|
||||
q.append(i)
|
||||
try:
|
||||
out = q.pop(0)
|
||||
except IndexError:
|
||||
return
|
||||
|
||||
result.append(out)
|
||||
|
||||
_get_parents(out)
|
||||
|
||||
_get_parents(type_id)
|
||||
|
||||
return result[::-1]
|
||||
|
||||
@classmethod
|
||||
def add(cls, parent_ids, child_id):
|
||||
|
||||
rels = {}
|
||||
for i in cls.cls.get_by(to_dict=False):
|
||||
rels.setdefault(i.child_id, set()).add(i.parent_id)
|
||||
|
||||
try:
|
||||
toposort_flatten(rels)
|
||||
except toposort.CircularDependencyError as e:
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.circular_dependency_error)
|
||||
|
||||
for parent_id in parent_ids or []:
|
||||
if parent_id == child_id:
|
||||
return abort(400, ErrFormat.circular_dependency_error)
|
||||
|
||||
existed = cls.cls.get_by(parent_id=parent_id, child_id=child_id, first=True, to_dict=False)
|
||||
if existed is None:
|
||||
rels.setdefault(child_id, set()).add(parent_id)
|
||||
try:
|
||||
toposort_flatten(rels)
|
||||
except toposort.CircularDependencyError as e:
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.circular_dependency_error)
|
||||
|
||||
cls.cls.create(parent_id=parent_id, child_id=child_id, commit=False)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
@classmethod
|
||||
def delete(cls, parent_id, child_id):
|
||||
|
||||
existed = cls.cls.get_by(parent_id=parent_id, child_id=child_id, first=True, to_dict=False)
|
||||
|
||||
if existed is not None:
|
||||
children = cls.recursive_children(child_id) + [child_id]
|
||||
for _id in children:
|
||||
if CI.get_by(type_id=_id, to_dict=False, first=True) is not None:
|
||||
return abort(400, ErrFormat.ci_exists_and_cannot_delete_inheritance)
|
||||
|
||||
attr_ids = set([i.id for _, i in CITypeAttributeManager.get_all_attributes(parent_id)])
|
||||
for _id in children:
|
||||
for attr_id in attr_ids:
|
||||
for i in PreferenceShowAttributes.get_by(type_id=_id, attr_id=attr_id, to_dict=False):
|
||||
i.soft_delete(commit=False)
|
||||
db.session.commit()
|
||||
|
||||
existed.soft_delete()
|
||||
|
||||
|
||||
class CITypeGroupManager(object):
|
||||
cls = CITypeGroup
|
||||
|
||||
@@ -261,6 +386,7 @@ class CITypeGroupManager(object):
|
||||
ci_type = CITypeCache.get(t['type_id']).to_dict()
|
||||
if resources is None or (ci_type and ci_type['name'] in resources):
|
||||
ci_type['permissions'] = resources[ci_type['name']] if resources is not None else None
|
||||
ci_type['inherited'] = True if CITypeInheritanceManager.get_parents(ci_type['id']) else False
|
||||
group.setdefault("ci_types", []).append(ci_type)
|
||||
group_types.add(t["type_id"])
|
||||
|
||||
@@ -270,6 +396,7 @@ class CITypeGroupManager(object):
|
||||
for ci_type in ci_types:
|
||||
if ci_type["id"] not in group_types and (resources is None or ci_type['name'] in resources):
|
||||
ci_type['permissions'] = resources.get(ci_type['name']) if resources is not None else None
|
||||
ci_type['inherited'] = True if CITypeInheritanceManager.get_parents(ci_type['id']) else False
|
||||
other_types['ci_types'].append(ci_type)
|
||||
|
||||
groups.append(other_types)
|
||||
@@ -294,9 +421,6 @@ class CITypeGroupManager(object):
|
||||
existed = CITypeGroup.get_by_id(gid) or abort(
|
||||
404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid)))
|
||||
if name is not None and name != existed.name:
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
existed.update(name=name)
|
||||
|
||||
max_order = max([i.order or 0 for i in CITypeGroupItem.get_by(group_id=gid, to_dict=False)] or [0])
|
||||
@@ -361,40 +485,62 @@ class CITypeAttributeManager(object):
|
||||
return attr.name
|
||||
|
||||
@staticmethod
|
||||
def get_attr_names_by_type_id(type_id):
|
||||
return [AttributeCache.get(attr.attr_id).name for attr in CITypeAttributesCache.get(type_id)]
|
||||
def get_all_attributes(type_id):
|
||||
parent_ids = CITypeInheritanceManager.base(type_id)
|
||||
|
||||
result = []
|
||||
for _type_id in parent_ids + [type_id]:
|
||||
result.extend(CITypeAttributesCache.get2(_type_id))
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_attr_names_by_type_id(cls, type_id):
|
||||
return [attr.name for _, attr in cls.get_all_attributes(type_id)]
|
||||
|
||||
@staticmethod
|
||||
def get_attributes_by_type_id(type_id, choice_web_hook_parse=True, choice_other_parse=True):
|
||||
has_config_perm = ACLManager('cmdb').has_permission(
|
||||
CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG)
|
||||
|
||||
attrs = CITypeAttributesCache.get(type_id)
|
||||
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)
|
||||
parent_ids = CITypeInheritanceManager.base(type_id)
|
||||
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
def get_common_attributes(type_ids):
|
||||
@classmethod
|
||||
def get_common_attributes(cls, type_ids):
|
||||
has_config_perm = False
|
||||
for type_id in type_ids:
|
||||
has_config_perm |= ACLManager('cmdb').has_permission(
|
||||
CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG)
|
||||
|
||||
result = CITypeAttribute.get_by(__func_in___key_type_id=list(map(int, type_ids)), to_dict=False)
|
||||
result = {type_id: [i for _, i in cls.get_all_attributes(type_id)] for type_id in type_ids}
|
||||
attr2types = {}
|
||||
for i in result:
|
||||
attr2types.setdefault(i.attr_id, []).append(i.type_id)
|
||||
for type_id in result:
|
||||
for i in result[type_id]:
|
||||
attr2types.setdefault(i.id, []).append(type_id)
|
||||
|
||||
attrs = []
|
||||
for attr_id in attr2types:
|
||||
@@ -511,12 +657,39 @@ class CITypeAttributeManager(object):
|
||||
existed.soft_delete()
|
||||
|
||||
for ci in CI.get_by(type_id=type_id, to_dict=False):
|
||||
AttributeValueManager.delete_attr_value(attr_id, ci.id)
|
||||
AttributeValueManager.delete_attr_value(attr_id, ci.id, commit=False)
|
||||
|
||||
ci_cache.apply_async(args=(ci.id, None, None), queue=CMDB_QUEUE)
|
||||
|
||||
for item in PreferenceShowAttributes.get_by(type_id=type_id, attr_id=attr_id, to_dict=False):
|
||||
item.soft_delete(commit=False)
|
||||
|
||||
child_ids = CITypeInheritanceManager.recursive_children(type_id)
|
||||
for _type_id in [type_id] + child_ids:
|
||||
for item in CITypeUniqueConstraint.get_by(type_id=_type_id, to_dict=False):
|
||||
if attr_id in item.attr_ids:
|
||||
attr_ids = copy.deepcopy(item.attr_ids)
|
||||
attr_ids.remove(attr_id)
|
||||
|
||||
if attr_ids:
|
||||
item.update(attr_ids=attr_ids, commit=False)
|
||||
else:
|
||||
item.soft_delete(commit=False)
|
||||
|
||||
item = CITypeTrigger.get_by(type_id=_type_id, attr_id=attr_id, to_dict=False, first=True)
|
||||
item and item.soft_delete(commit=False)
|
||||
|
||||
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)
|
||||
|
||||
if ci_type.show_id == attr_id:
|
||||
ci_type.update(show_id=None, filter_none=False)
|
||||
|
||||
CITypeHistoryManager.add(CITypeOperateType.DELETE_ATTRIBUTE, type_id, attr_id=attr.id,
|
||||
change=attr and attr.to_dict())
|
||||
|
||||
@@ -528,8 +701,22 @@ class CITypeAttributeManager(object):
|
||||
attr_id = _from.get('attr_id')
|
||||
from_group_id = _from.get('group_id')
|
||||
to_group_id = _to.get('group_id')
|
||||
from_group_name = _from.get('group_name')
|
||||
to_group_name = _to.get('group_name')
|
||||
order = _to.get('order')
|
||||
|
||||
if from_group_name:
|
||||
from_group = CITypeAttributeGroup.get_by(type_id=type_id, name=from_group_name, first=True, to_dict=False)
|
||||
from_group_id = from_group and from_group.id
|
||||
|
||||
if to_group_name:
|
||||
to_group = CITypeAttributeGroup.get_by(type_id=type_id, name=to_group_name, first=True, to_dict=False)
|
||||
to_group_id = to_group and to_group.id
|
||||
|
||||
if 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 is not None:
|
||||
CITypeAttributeGroupManager.delete_item(from_group_id, attr_id)
|
||||
@@ -557,14 +744,19 @@ class CITypeRelationManager(object):
|
||||
@staticmethod
|
||||
def get():
|
||||
res = CITypeRelation.get_by(to_dict=False)
|
||||
type2attributes = dict()
|
||||
for idx, item in enumerate(res):
|
||||
_item = item.to_dict()
|
||||
res[idx] = _item
|
||||
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()
|
||||
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()
|
||||
|
||||
return res
|
||||
return res, type2attributes
|
||||
|
||||
@staticmethod
|
||||
def get_child_type_ids(type_id, level):
|
||||
@@ -596,6 +788,8 @@ class CITypeRelationManager(object):
|
||||
|
||||
ci_type_dict["relation_type"] = relation_inst.relation_type.name
|
||||
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
|
||||
|
||||
@@ -640,7 +834,8 @@ class CITypeRelationManager(object):
|
||||
return "{} -> {}".format(first_name, second_name)
|
||||
|
||||
@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)
|
||||
c = CITypeManager.check_is_existed(child)
|
||||
|
||||
@@ -655,24 +850,21 @@ class CITypeRelationManager(object):
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.circular_dependency_error)
|
||||
|
||||
if constraint == ConstraintEnum.Many2Many:
|
||||
other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
|
||||
to_dict=False, first=True)
|
||||
other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
|
||||
to_dict=False, first=True)
|
||||
if other_c and other_c.child_id != c.id:
|
||||
return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
|
||||
if other_p and other_p.parent_id != p.id:
|
||||
return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
|
||||
|
||||
old_parent_attr_id = None
|
||||
existed = cls._get(p.id, c.id)
|
||||
if existed is not None:
|
||||
existed.update(relation_type_id=relation_type_id,
|
||||
constraint=constraint)
|
||||
old_parent_attr_id = existed.parent_attr_id
|
||||
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:
|
||||
existed = CITypeRelation.create(parent_id=p.id,
|
||||
child_id=c.id,
|
||||
relation_type_id=relation_type_id,
|
||||
parent_attr_id=parent_attr_id,
|
||||
child_attr_id=child_attr_id,
|
||||
constraint=constraint)
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
@@ -686,6 +878,11 @@ class CITypeRelationManager(object):
|
||||
current_user.username,
|
||||
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,
|
||||
change=dict(parent=p.to_dict(), child=c.to_dict(), relation_type_id=relation_type_id))
|
||||
|
||||
@@ -738,25 +935,66 @@ class CITypeAttributeGroupManager(object):
|
||||
|
||||
@staticmethod
|
||||
def get_by_type_id(type_id, need_other=False):
|
||||
groups = CITypeAttributeGroup.get_by(type_id=type_id)
|
||||
groups = sorted(groups, key=lambda x: x["order"] or 0)
|
||||
grouped = list()
|
||||
parent_ids = CITypeInheritanceManager.base(type_id)
|
||||
|
||||
groups = []
|
||||
id2type = {i: CITypeCache.get(i).alias for i in parent_ids}
|
||||
for _type_id in parent_ids + [type_id]:
|
||||
_groups = CITypeAttributeGroup.get_by(type_id=_type_id)
|
||||
_groups = sorted(_groups, key=lambda x: x["order"] or 0)
|
||||
for i in _groups:
|
||||
if type_id != _type_id:
|
||||
i['inherited'] = True
|
||||
i['inherited_from'] = id2type[_type_id]
|
||||
else:
|
||||
i['inherited'] = False
|
||||
|
||||
groups.extend(_groups)
|
||||
|
||||
grouped = set()
|
||||
|
||||
attributes = CITypeAttributeManager.get_attributes_by_type_id(type_id)
|
||||
id2attr = {i.get('id'): i for i in attributes}
|
||||
|
||||
group2pos = dict()
|
||||
attr2pos = dict()
|
||||
result = []
|
||||
for group in groups:
|
||||
items = CITypeAttributeGroupItem.get_by(group_id=group["id"], to_dict=False)
|
||||
items = sorted(items, key=lambda x: x.order or 0)
|
||||
group["attributes"] = [id2attr.get(i.attr_id) for i in items if i.attr_id in id2attr]
|
||||
grouped.extend([i.attr_id for i in items])
|
||||
|
||||
if group['name'] not in group2pos:
|
||||
group_pos = len(result)
|
||||
group['attributes'] = []
|
||||
result.append(group)
|
||||
|
||||
group2pos[group['name']] = group_pos
|
||||
else:
|
||||
group_pos = group2pos[group['name']]
|
||||
|
||||
attr = None
|
||||
for i in items:
|
||||
if i.attr_id in id2attr:
|
||||
attr = id2attr[i.attr_id]
|
||||
attr['inherited'] = group['inherited']
|
||||
attr['inherited_from'] = group.get('inherited_from')
|
||||
result[group_pos]['attributes'].append(attr)
|
||||
|
||||
if i.attr_id in attr2pos:
|
||||
result[attr2pos[i.attr_id][0]]['attributes'].remove(attr2pos[i.attr_id][1])
|
||||
|
||||
attr2pos[i.attr_id] = [group_pos, attr]
|
||||
|
||||
group.pop('inherited_from', None)
|
||||
|
||||
grouped |= set([i.attr_id for i in items])
|
||||
|
||||
if need_other:
|
||||
grouped = set(grouped)
|
||||
other_attributes = [attr for attr in attributes if attr["id"] not in grouped]
|
||||
groups.append(dict(attributes=other_attributes))
|
||||
result.append(dict(attributes=other_attributes))
|
||||
|
||||
return groups
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def create_or_update(type_id, name, attr_order, group_order=0, is_update=False):
|
||||
@@ -890,10 +1128,16 @@ class CITypeAttributeGroupManager(object):
|
||||
@classmethod
|
||||
def transfer(cls, type_id, _from, _to):
|
||||
current_app.logger.info("CIType[{0}] {1} -> {2}".format(type_id, _from, _to))
|
||||
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)))
|
||||
|
||||
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)))
|
||||
|
||||
from_order, to_order = from_group.order, to_group.order
|
||||
@@ -931,6 +1175,8 @@ class CITypeTemplateManager(object):
|
||||
id2obj_dicts[added_id].get('child_id'),
|
||||
id2obj_dicts[added_id].get('relation_type_id'),
|
||||
id2obj_dicts[added_id].get('constraint'),
|
||||
id2obj_dicts[added_id].get('parent_attr_id'),
|
||||
id2obj_dicts[added_id].get('child_attr_id'),
|
||||
)
|
||||
else:
|
||||
obj = cls.create(flush=True, **id2obj_dicts[added_id])
|
||||
@@ -972,6 +1218,8 @@ class CITypeTemplateManager(object):
|
||||
i.pop('choice_web_hook', None)
|
||||
i.pop('choice_other', None)
|
||||
i.pop('order', None)
|
||||
i.pop('inherited', None)
|
||||
i.pop('inherited_from', None)
|
||||
choice_value = i.pop('choice_value', None)
|
||||
if not choice_value:
|
||||
i['is_choice'] = False
|
||||
@@ -989,7 +1237,10 @@ class CITypeTemplateManager(object):
|
||||
def _import_ci_types(self, ci_types, attr_id_map):
|
||||
for i in ci_types:
|
||||
i.pop("unique_key", None)
|
||||
i.pop("show_name", None)
|
||||
i['unique_id'] = attr_id_map.get(i['unique_id'], i['unique_id'])
|
||||
if i.get('show_id'):
|
||||
i['show_id'] = attr_id_map.get(i['show_id'], i['show_id'])
|
||||
i['uid'] = current_user.uid
|
||||
|
||||
return self.__import(CIType, ci_types)
|
||||
@@ -1083,6 +1334,8 @@ class CITypeTemplateManager(object):
|
||||
_group = copy.deepcopy(group)
|
||||
_group.pop('attributes', None)
|
||||
_group.pop('id', None)
|
||||
_group.pop('inherited', None)
|
||||
_group.pop('inherited_from', None)
|
||||
existed = CITypeAttributeGroup.get_by(name=_group['name'],
|
||||
type_id=type_id_map.get(_group['type_id'], _group['type_id']),
|
||||
first=True, to_dict=False)
|
||||
@@ -1138,9 +1391,13 @@ class CITypeTemplateManager(object):
|
||||
rule.pop("id", None)
|
||||
rule.pop("created_at", None)
|
||||
rule.pop("updated_at", None)
|
||||
rule.pop("relation", None)
|
||||
|
||||
rule['uid'] = current_user.uid
|
||||
|
||||
if not rule.get('attributes'):
|
||||
continue
|
||||
|
||||
existed = 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) == (
|
||||
@@ -1213,7 +1470,7 @@ class CITypeTemplateManager(object):
|
||||
ci_types=CITypeManager.get_ci_types(),
|
||||
ci_type_groups=CITypeGroupManager.get(),
|
||||
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(),
|
||||
type2attributes=dict(),
|
||||
type2attribute_group=dict(),
|
||||
@@ -1274,7 +1531,7 @@ class CITypeTemplateManager(object):
|
||||
from api.lib.common_setting.upload_file import CommonFileCRUD
|
||||
|
||||
tpt = dict(
|
||||
ci_types=CITypeManager.get_ci_types(type_name=ci_type.name),
|
||||
ci_types=CITypeManager.get_ci_types(type_name=ci_type.name, like=False),
|
||||
ci_type_auto_discovery_rules=list(),
|
||||
type2attributes=dict(),
|
||||
type2attribute_group=dict(),
|
||||
|
@@ -55,6 +55,9 @@ class CITypeOperateType(BaseEnum):
|
||||
DELETE_UNIQUE_CONSTRAINT = "11" # 删除联合唯一
|
||||
ADD_RELATION = "12" # 新增关系
|
||||
DELETE_RELATION = "13" # 删除关系
|
||||
ADD_RECONCILIATION = "14" # 删除关系
|
||||
UPDATE_RECONCILIATION = "15" # 删除关系
|
||||
DELETE_RECONCILIATION = "16" # 删除关系
|
||||
|
||||
|
||||
class RetKey(BaseEnum):
|
||||
@@ -98,6 +101,12 @@ class AttributeDefaultValueEnum(BaseEnum):
|
||||
AUTO_INC_ID = "$auto_inc_id"
|
||||
|
||||
|
||||
class ExecuteStatusEnum(BaseEnum):
|
||||
COMPLETED = '0'
|
||||
FAILED = '1'
|
||||
RUNNING = '2'
|
||||
|
||||
|
||||
CMDB_QUEUE = "one_cmdb_async"
|
||||
REDIS_PREFIX_CI = "ONE_CMDB"
|
||||
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
||||
|
@@ -26,7 +26,7 @@ from api.models.cmdb import OperationRecord
|
||||
class AttributeHistoryManger(object):
|
||||
@staticmethod
|
||||
def get_records_for_attributes(start, end, username, page, page_size, operate_type, type_id,
|
||||
ci_id=None, attr_id=None):
|
||||
ci_id=None, attr_id=None, ci_ids=None, more=False):
|
||||
|
||||
records = db.session.query(OperationRecord, AttributeHistory).join(
|
||||
AttributeHistory, OperationRecord.id == AttributeHistory.record_id)
|
||||
@@ -48,6 +48,9 @@ class AttributeHistoryManger(object):
|
||||
if ci_id is not None:
|
||||
records = records.filter(AttributeHistory.ci_id == ci_id)
|
||||
|
||||
if ci_ids and isinstance(ci_ids, list):
|
||||
records = records.filter(AttributeHistory.ci_id.in_(ci_ids))
|
||||
|
||||
if attr_id is not None:
|
||||
records = records.filter(AttributeHistory.attr_id == attr_id)
|
||||
|
||||
@@ -62,6 +65,12 @@ class AttributeHistoryManger(object):
|
||||
if attr_hist['attr']:
|
||||
attr_hist['attr_name'] = attr_hist['attr'].name
|
||||
attr_hist['attr_alias'] = attr_hist['attr'].alias
|
||||
if more:
|
||||
attr_hist['is_list'] = attr_hist['attr'].is_list
|
||||
attr_hist['is_computed'] = attr_hist['attr'].is_computed
|
||||
attr_hist['is_password'] = attr_hist['attr'].is_password
|
||||
attr_hist['default'] = attr_hist['attr'].default
|
||||
attr_hist['value_type'] = attr_hist['attr'].value_type
|
||||
attr_hist.pop("attr")
|
||||
|
||||
if record_id not in res:
|
||||
@@ -161,12 +170,14 @@ class AttributeHistoryManger(object):
|
||||
record = i.OperationRecord
|
||||
item = dict(attr_name=attr.name,
|
||||
attr_alias=attr.alias,
|
||||
value_type=attr.value_type,
|
||||
operate_type=hist.operate_type,
|
||||
username=user and user.nickname,
|
||||
old=hist.old,
|
||||
new=hist.new,
|
||||
created_at=record.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
record_id=record.id,
|
||||
ticket_id=record.ticket_id,
|
||||
hid=hist.id
|
||||
)
|
||||
result.append(item)
|
||||
@@ -200,9 +211,9 @@ class AttributeHistoryManger(object):
|
||||
return username, timestamp, attr_dict, rel_dict
|
||||
|
||||
@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:
|
||||
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
|
||||
|
||||
for attr_id, operate_type, old, new in history_list or []:
|
||||
@@ -270,7 +281,7 @@ class CITypeHistoryManager(object):
|
||||
return numfound, result
|
||||
|
||||
@staticmethod
|
||||
def add(operate_type, type_id, attr_id=None, trigger_id=None, unique_constraint_id=None, change=None):
|
||||
def add(operate_type, type_id, attr_id=None, trigger_id=None, unique_constraint_id=None, change=None, rc_id=None):
|
||||
if type_id is None and attr_id is not None:
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
type_ids = [i.type_id for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False)]
|
||||
@@ -283,6 +294,7 @@ class CITypeHistoryManager(object):
|
||||
uid=current_user.uid,
|
||||
attr_id=attr_id,
|
||||
trigger_id=trigger_id,
|
||||
rc_id=rc_id,
|
||||
unique_constraint_id=unique_constraint_id,
|
||||
change=change)
|
||||
|
||||
|
@@ -1,12 +1,15 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import copy
|
||||
import functools
|
||||
|
||||
import redis_lock
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
|
||||
from api.extensions import db
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.mixin import DBMixin
|
||||
@@ -40,6 +43,11 @@ class CIFilterPermsCRUD(DBMixin):
|
||||
result[i['rid']]['ci_filter'] = ""
|
||||
result[i['rid']]['ci_filter'] += (i['ci_filter'] or "")
|
||||
|
||||
if i['id_filter']:
|
||||
if not result[i['rid']]['id_filter']:
|
||||
result[i['rid']]['id_filter'] = {}
|
||||
result[i['rid']]['id_filter'].update(i['id_filter'] or {})
|
||||
|
||||
return result
|
||||
|
||||
def get_by_ids(self, _ids, type_id=None):
|
||||
@@ -70,6 +78,11 @@ class CIFilterPermsCRUD(DBMixin):
|
||||
result[i['type_id']]['ci_filter'] = ""
|
||||
result[i['type_id']]['ci_filter'] += (i['ci_filter'] or "")
|
||||
|
||||
if i['id_filter']:
|
||||
if not result[i['type_id']]['id_filter']:
|
||||
result[i['type_id']]['id_filter'] = {}
|
||||
result[i['type_id']]['id_filter'].update(i['id_filter'] or {})
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
@@ -82,6 +95,54 @@ class CIFilterPermsCRUD(DBMixin):
|
||||
type2filter_perms = cls().get_by_ids(list(map(int, [i['name'] for i in res2])), type_id=type_id)
|
||||
return type2filter_perms.get(type_id, {}).get('attr_filter') or []
|
||||
|
||||
def _revoke_children(self, rid, id_filter, rebuild=True):
|
||||
items = self.cls.get_by(rid=rid, ci_filter=None, attr_filter=None, to_dict=False)
|
||||
for item in items:
|
||||
changed, item_id_filter = False, copy.deepcopy(item.id_filter)
|
||||
for prefix in id_filter:
|
||||
for k, v in copy.deepcopy((item.id_filter or {})).items():
|
||||
if k.startswith(prefix) and k != prefix:
|
||||
item_id_filter.pop(k)
|
||||
changed = True
|
||||
|
||||
if not item_id_filter and current_app.config.get('USE_ACL'):
|
||||
item.soft_delete(commit=False)
|
||||
ACLManager().del_resource(str(item.id), ResourceTypeEnum.CI_FILTER, rebuild=rebuild)
|
||||
elif changed:
|
||||
item.update(id_filter=item_id_filter, commit=False)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
def _revoke_parent(self, rid, parent_path, rebuild=True):
|
||||
parent_path = [i for i in parent_path.split(',') if i] or []
|
||||
revoke_nodes = [','.join(parent_path[:i]) for i in range(len(parent_path), 0, -1)]
|
||||
for node_path in revoke_nodes:
|
||||
delete_item, can_deleted = None, True
|
||||
items = self.cls.get_by(rid=rid, ci_filter=None, attr_filter=None, to_dict=False)
|
||||
for item in items:
|
||||
if node_path in item.id_filter:
|
||||
delete_item = item
|
||||
if any(filter(lambda x: x.startswith(node_path) and x != node_path, item.id_filter.keys())):
|
||||
can_deleted = False
|
||||
break
|
||||
|
||||
if can_deleted and delete_item:
|
||||
id_filter = copy.deepcopy(delete_item.id_filter)
|
||||
id_filter.pop(node_path)
|
||||
delete_item = delete_item.update(id_filter=id_filter, filter_none=False)
|
||||
|
||||
if current_app.config.get('USE_ACL') and not id_filter:
|
||||
ACLManager().del_resource(str(delete_item.id), ResourceTypeEnum.CI_FILTER, rebuild=False)
|
||||
delete_item.soft_delete()
|
||||
items.remove(delete_item)
|
||||
|
||||
if rebuild:
|
||||
from api.tasks.acl import role_rebuild
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
|
||||
role_rebuild.apply_async(args=(rid, AppCache.get('cmdb').id), queue=ACL_QUEUE)
|
||||
|
||||
def _can_add(self, **kwargs):
|
||||
ci_filter = kwargs.get('ci_filter')
|
||||
attr_filter = kwargs.get('attr_filter') or ""
|
||||
@@ -102,34 +163,67 @@ class CIFilterPermsCRUD(DBMixin):
|
||||
|
||||
def add(self, **kwargs):
|
||||
kwargs = self._can_add(**kwargs) or kwargs
|
||||
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid'])):
|
||||
request_id_filter = {}
|
||||
if kwargs.get('id_filter'):
|
||||
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
|
||||
rid=kwargs.get('rid'),
|
||||
ci_filter=None,
|
||||
attr_filter=None,
|
||||
first=True, to_dict=False)
|
||||
|
||||
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
|
||||
rid=kwargs.get('rid'),
|
||||
first=True, to_dict=False)
|
||||
if obj is not None:
|
||||
obj = obj.update(filter_none=False, **kwargs)
|
||||
if not obj.attr_filter and not obj.ci_filter:
|
||||
if current_app.config.get('USE_ACL'):
|
||||
ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER)
|
||||
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']
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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'):
|
||||
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)
|
||||
if request_id_filter:
|
||||
kwargs['id_filter'] = request_id_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):
|
||||
pass
|
||||
@@ -138,19 +232,84 @@ class CIFilterPermsCRUD(DBMixin):
|
||||
pass
|
||||
|
||||
def delete(self, **kwargs):
|
||||
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
|
||||
rid=kwargs.get('rid'),
|
||||
first=True, to_dict=False)
|
||||
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid'])):
|
||||
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
|
||||
rid=kwargs.get('rid'),
|
||||
id_filter=None,
|
||||
first=True, to_dict=False)
|
||||
|
||||
if obj is not None:
|
||||
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
|
||||
if current_app.config.get('USE_ACL'):
|
||||
resource = ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER)
|
||||
if obj is not None:
|
||||
|
||||
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
|
||||
|
||||
def delete_id_filter_by_ci_id(self, ci_id):
|
||||
items = self.cls.get_by(ci_filter=None, attr_filter=None, to_dict=False)
|
||||
|
||||
rebuild_roles = set()
|
||||
for item in items:
|
||||
id_filter = copy.deepcopy(item.id_filter)
|
||||
changed = False
|
||||
for node_path in item.id_filter:
|
||||
if str(ci_id) in node_path:
|
||||
id_filter.pop(node_path)
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
rebuild_roles.add(item.rid)
|
||||
if not id_filter:
|
||||
item.soft_delete(commit=False)
|
||||
else:
|
||||
item.update(id_filter=id_filter, commit=False)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
if rebuild_roles:
|
||||
from api.tasks.acl import role_rebuild
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
for rid in rebuild_roles:
|
||||
role_rebuild.apply_async(args=(rid, AppCache.get('cmdb').id), queue=ACL_QUEUE)
|
||||
|
||||
|
||||
def has_perm_for_ci(arg_name, resource_type, perm, callback=None, app=None):
|
||||
def decorator_has_perm(func):
|
||||
|
@@ -14,6 +14,8 @@ from api.lib.cmdb.attribute import AttributeManager
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.cache import CMDBCounterCache
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
from api.lib.cmdb.const import ConstraintEnum
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
@@ -24,6 +26,7 @@ from api.lib.exception import AbortException
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.models.cmdb import PreferenceCITypeOrder
|
||||
from api.models.cmdb import PreferenceRelationView
|
||||
from api.models.cmdb import PreferenceSearchOption
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
@@ -38,13 +41,22 @@ class PreferenceManager(object):
|
||||
|
||||
@staticmethod
|
||||
def get_types(instance=False, tree=False):
|
||||
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, to_dict=False), key=lambda x: x.order)
|
||||
|
||||
types = db.session.query(PreferenceShowAttributes.type_id).filter(
|
||||
PreferenceShowAttributes.uid == current_user.uid).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
||||
PreferenceShowAttributes.type_id).all() if instance else []
|
||||
types = sorted(types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
|
||||
ci_type_order) if not i.is_tree}.get(x.type_id, 1))
|
||||
|
||||
tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else []
|
||||
type_ids = set([i.type_id for i in types + tree_types])
|
||||
tree_types = sorted(tree_types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
|
||||
ci_type_order) if i.is_tree}.get(x.type_id, 1))
|
||||
|
||||
type_ids = [i.type_id for i in types + tree_types]
|
||||
if types and tree_types:
|
||||
type_ids = set(type_ids)
|
||||
|
||||
return [CITypeCache.get(type_id).to_dict() for type_id in type_ids]
|
||||
|
||||
@@ -59,32 +71,36 @@ class PreferenceManager(object):
|
||||
:param tree:
|
||||
:return:
|
||||
"""
|
||||
result = dict(self=dict(instance=[], tree=[], type_id2subs_time=dict()),
|
||||
type_id2users=dict())
|
||||
result = dict(self=dict(instance=[], tree=[], type_id2subs_time=dict()))
|
||||
|
||||
result.update(CMDBCounterCache.get_sub_counter())
|
||||
|
||||
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, to_dict=False), key=lambda x: x.order)
|
||||
if instance:
|
||||
types = db.session.query(PreferenceShowAttributes.type_id,
|
||||
PreferenceShowAttributes.uid, PreferenceShowAttributes.created_at).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
||||
PreferenceShowAttributes.deleted.is_(False)).filter(
|
||||
PreferenceShowAttributes.uid == current_user.uid).group_by(
|
||||
PreferenceShowAttributes.uid, PreferenceShowAttributes.type_id)
|
||||
for i in types:
|
||||
if i.uid == current_user.uid:
|
||||
result['self']['instance'].append(i.type_id)
|
||||
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
|
||||
result['self']['type_id2subs_time'][i.type_id] = i.created_at
|
||||
result['self']['instance'].append(i.type_id)
|
||||
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
|
||||
result['self']['type_id2subs_time'][i.type_id] = i.created_at
|
||||
|
||||
result['type_id2users'].setdefault(i.type_id, []).append(i.uid)
|
||||
instance_order = [i.type_id for i in ci_type_order if not i.is_tree]
|
||||
if len(instance_order) == len(result['self']['instance']):
|
||||
result['self']['instance'] = instance_order
|
||||
|
||||
if tree:
|
||||
types = PreferenceTreeView.get_by(to_dict=False)
|
||||
types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False)
|
||||
for i in types:
|
||||
if i.uid == current_user.uid:
|
||||
result['self']['tree'].append(i.type_id)
|
||||
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
|
||||
result['self']['type_id2subs_time'][i.type_id] = i.created_at
|
||||
result['self']['tree'].append(i.type_id)
|
||||
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
|
||||
result['self']['type_id2subs_time'][i.type_id] = i.created_at
|
||||
|
||||
result['type_id2users'].setdefault(i.type_id, [])
|
||||
if i.uid not in result['type_id2users'][i.type_id]:
|
||||
result['type_id2users'][i.type_id].append(i.uid)
|
||||
tree_order = [i.type_id for i in ci_type_order if i.is_tree]
|
||||
if len(tree_order) == len(result['self']['tree']):
|
||||
result['self']['tree'] = tree_order
|
||||
|
||||
return result
|
||||
|
||||
@@ -98,8 +114,8 @@ class PreferenceManager(object):
|
||||
CITypeAttribute, CITypeAttribute.attr_id == PreferenceShowAttributes.attr_id).filter(
|
||||
PreferenceShowAttributes.uid == current_user.uid).filter(
|
||||
PreferenceShowAttributes.type_id == type_id).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).filter(
|
||||
CITypeAttribute.type_id == type_id).all()
|
||||
PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).group_by(
|
||||
CITypeAttribute.attr_id).all()
|
||||
|
||||
result = []
|
||||
for i in sorted(attrs, key=lambda x: x.PreferenceShowAttributes.order):
|
||||
@@ -109,17 +125,16 @@ class PreferenceManager(object):
|
||||
|
||||
is_subscribed = True
|
||||
if not attrs:
|
||||
attrs = db.session.query(CITypeAttribute).filter(
|
||||
CITypeAttribute.type_id == type_id).filter(
|
||||
CITypeAttribute.deleted.is_(False)).filter(
|
||||
CITypeAttribute.default_show.is_(True)).order_by(CITypeAttribute.order)
|
||||
result = [i.attr.to_dict() for i in attrs]
|
||||
result = CITypeAttributeManager.get_attributes_by_type_id(type_id,
|
||||
choice_web_hook_parse=False,
|
||||
choice_other_parse=False)
|
||||
result = [i for i in result if i['default_show']]
|
||||
is_subscribed = False
|
||||
|
||||
for i in result:
|
||||
if i["is_choice"]:
|
||||
i.update(dict(choice_value=AttributeManager.get_choice_values(
|
||||
i["id"], i["value_type"], i["choice_web_hook"], i.get("choice_other"))))
|
||||
i["id"], i["value_type"], i.get("choice_web_hook"), i.get("choice_other"))))
|
||||
|
||||
return is_subscribed, result
|
||||
|
||||
@@ -151,9 +166,22 @@ class PreferenceManager(object):
|
||||
if i.attr_id not in attr_dict:
|
||||
i.soft_delete()
|
||||
|
||||
if not existed_all and attr_order:
|
||||
cls.add_ci_type_order_item(type_id, is_tree=False)
|
||||
|
||||
elif not PreferenceShowAttributes.get_by(type_id=type_id, uid=current_user.uid, to_dict=False):
|
||||
cls.delete_ci_type_order_item(type_id, is_tree=False)
|
||||
|
||||
@staticmethod
|
||||
def get_tree_view():
|
||||
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, is_tree=True, to_dict=False),
|
||||
key=lambda x: x.order)
|
||||
|
||||
res = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=True)
|
||||
if ci_type_order:
|
||||
res = sorted(res, key=lambda x: {ii.type_id: idx for idx, ii in enumerate(
|
||||
ci_type_order)}.get(x['type_id'], 1))
|
||||
|
||||
for item in res:
|
||||
if item["levels"]:
|
||||
ci_type = CITypeCache.get(item['type_id']).to_dict()
|
||||
@@ -172,8 +200,8 @@ class PreferenceManager(object):
|
||||
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def create_or_update_tree_view(type_id, levels):
|
||||
@classmethod
|
||||
def create_or_update_tree_view(cls, type_id, levels):
|
||||
attrs = CITypeAttributesCache.get(type_id)
|
||||
for idx, i in enumerate(levels):
|
||||
for attr in attrs:
|
||||
@@ -185,9 +213,12 @@ class PreferenceManager(object):
|
||||
if existed is not None:
|
||||
if not levels:
|
||||
existed.soft_delete()
|
||||
cls.delete_ci_type_order_item(type_id, is_tree=True)
|
||||
return existed
|
||||
return existed.update(levels=levels)
|
||||
elif levels:
|
||||
cls.add_ci_type_order_item(type_id, is_tree=True)
|
||||
|
||||
return PreferenceTreeView.create(levels=levels, type_id=type_id, uid=current_user.uid)
|
||||
|
||||
@staticmethod
|
||||
@@ -207,11 +238,13 @@ class PreferenceManager(object):
|
||||
views = _views
|
||||
|
||||
view2cr_ids = dict()
|
||||
name2view = dict()
|
||||
result = dict()
|
||||
name2id = list()
|
||||
for view in views:
|
||||
view2cr_ids.setdefault(view['name'], []).extend(view['cr_ids'])
|
||||
name2id.append([view['name'], view['id']])
|
||||
name2view[view['name']] = view
|
||||
|
||||
id2type = dict()
|
||||
for view_name in view2cr_ids:
|
||||
@@ -255,6 +288,8 @@ class PreferenceManager(object):
|
||||
topo_flatten=topo_flatten,
|
||||
level2constraint=level2constraint,
|
||||
leaf=leaf,
|
||||
option=name2view[view_name]['option'],
|
||||
is_public=name2view[view_name]['is_public'],
|
||||
leaf2show_types=leaf2show_types,
|
||||
node2show_types=node2show_types,
|
||||
show_types=[CITypeCache.get(j).to_dict()
|
||||
@@ -262,18 +297,26 @@ class PreferenceManager(object):
|
||||
|
||||
for type_id in id2type:
|
||||
id2type[type_id] = CITypeCache.get(type_id).to_dict()
|
||||
id2type[type_id]['unique_name'] = AttributeCache.get(id2type[type_id]['unique_id']).name
|
||||
if id2type[type_id]['show_id']:
|
||||
show_attr = AttributeCache.get(id2type[type_id]['show_id'])
|
||||
id2type[type_id]['show_name'] = show_attr and show_attr.name
|
||||
|
||||
return result, id2type, sorted(name2id, key=lambda x: x[1])
|
||||
|
||||
@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:
|
||||
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)
|
||||
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"):
|
||||
ACLManager().add_resource(name, ResourceTypeEnum.RELATION_VIEW)
|
||||
@@ -281,6 +324,11 @@ class PreferenceManager(object):
|
||||
RoleEnum.CMDB_READ_ALL,
|
||||
ResourceTypeEnum.RELATION_VIEW,
|
||||
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()
|
||||
|
||||
@@ -356,6 +404,9 @@ class PreferenceManager(object):
|
||||
for i in PreferenceTreeView.get_by(type_id=type_id, uid=uid, to_dict=False):
|
||||
i.soft_delete()
|
||||
|
||||
for i in PreferenceCITypeOrder.get_by(type_id=type_id, uid=uid, to_dict=False):
|
||||
i.soft_delete()
|
||||
|
||||
@staticmethod
|
||||
def can_edit_relation(parent_id, child_id):
|
||||
views = PreferenceRelationView.get_by(to_dict=False)
|
||||
@@ -381,3 +432,36 @@ class PreferenceManager(object):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def add_ci_type_order_item(type_id, is_tree=False):
|
||||
max_order = PreferenceCITypeOrder.get_by(
|
||||
uid=current_user.uid, is_tree=is_tree, only_query=True).order_by(PreferenceCITypeOrder.order.desc()).first()
|
||||
order = (max_order and max_order.order + 1) or 1
|
||||
|
||||
PreferenceCITypeOrder.create(type_id=type_id, is_tree=is_tree, uid=current_user.uid, order=order)
|
||||
|
||||
@staticmethod
|
||||
def delete_ci_type_order_item(type_id, is_tree=False):
|
||||
existed = PreferenceCITypeOrder.get_by(uid=current_user.uid, type_id=type_id, is_tree=is_tree,
|
||||
first=True, to_dict=False)
|
||||
|
||||
existed and existed.soft_delete()
|
||||
|
||||
@staticmethod
|
||||
def upsert_ci_type_order(type_ids, is_tree=False):
|
||||
for idx, type_id in enumerate(type_ids):
|
||||
order = idx + 1
|
||||
existed = PreferenceCITypeOrder.get_by(uid=current_user.uid, type_id=type_id, is_tree=is_tree,
|
||||
to_dict=False, first=True)
|
||||
if existed is not None:
|
||||
existed.update(order=order, flush=True)
|
||||
else:
|
||||
PreferenceCITypeOrder.create(uid=current_user.uid, type_id=type_id, is_tree=is_tree, order=order,
|
||||
flush=True)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("upsert citype order failed: {}".format(e))
|
||||
return abort(400, ErrFormat.unknown_error)
|
||||
|
@@ -60,6 +60,8 @@ class ErrFormat(CommonErrFormat):
|
||||
only_owner_can_delete = _l("Only the creator can delete it!") # 只有创建人才能删除它!
|
||||
ci_exists_and_cannot_delete_type = _l(
|
||||
"The model cannot be deleted because the CI already exists") # 因为CI已经存在,不能删除模型
|
||||
ci_exists_and_cannot_delete_inheritance = _l(
|
||||
"The inheritance cannot be deleted because the CI already exists") # 因为CI已经存在,不能删除继承关系
|
||||
|
||||
# 因为关系视图 {} 引用了该模型,不能删除模型
|
||||
ci_relation_view_exists_and_cannot_delete_type = _l(
|
||||
@@ -76,6 +78,8 @@ class ErrFormat(CommonErrFormat):
|
||||
unique_constraint_invalid = _l("Uniquely constrained attributes cannot be JSON and multi-valued")
|
||||
ci_type_trigger_duplicate = _l("Duplicated trigger") # 重复的触发器
|
||||
ci_type_trigger_not_found = _l("Trigger {} does not exist") # 触发器 {} 不存在
|
||||
ci_type_reconciliation_duplicate = _l("Duplicated reconciliation rule") # 重复的校验规则
|
||||
ci_type_reconciliation_not_found = _l("Reconciliation rule {} does not exist") # 规则 {} 不存在
|
||||
|
||||
record_not_found = _l("Operation record {} does not exist") # 操作记录 {} 不存在
|
||||
cannot_delete_unique = _l("Unique identifier cannot be deleted") # 不能删除唯一标识
|
||||
@@ -94,7 +98,7 @@ class ErrFormat(CommonErrFormat):
|
||||
# 属性 {} 的值必须是唯一的, 当前值 {} 已存在
|
||||
attribute_value_unique_required = _l("The value of attribute {} must be unique, {} already exists")
|
||||
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: {}")
|
||||
|
||||
|
@@ -16,10 +16,11 @@ def search(query=None,
|
||||
ret_key=RetKey.NAME,
|
||||
count=1,
|
||||
sort=None,
|
||||
excludes=None):
|
||||
excludes=None,
|
||||
use_id_filter=False):
|
||||
if current_app.config.get("USE_ES"):
|
||||
s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
|
||||
else:
|
||||
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes)
|
||||
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes, use_id_filter=use_id_filter)
|
||||
|
||||
return s
|
||||
|
@@ -62,7 +62,7 @@ QUERY_CI_BY_ATTR_NAME = """
|
||||
QUERY_CI_BY_ID = """
|
||||
SELECT c_cis.id as ci_id
|
||||
FROM c_cis
|
||||
WHERE c_cis.id={}
|
||||
WHERE c_cis.id {}
|
||||
"""
|
||||
|
||||
QUERY_CI_BY_TYPE = """
|
||||
|
@@ -44,7 +44,10 @@ class Search(object):
|
||||
count=1,
|
||||
sort=None,
|
||||
ci_ids=None,
|
||||
excludes=None):
|
||||
excludes=None,
|
||||
parent_node_perm_passed=False,
|
||||
use_id_filter=False,
|
||||
use_ci_filter=True):
|
||||
self.orig_query = query
|
||||
self.fl = fl or []
|
||||
self.excludes = excludes or []
|
||||
@@ -54,12 +57,17 @@ class Search(object):
|
||||
self.count = count
|
||||
self.sort = sort
|
||||
self.ci_ids = ci_ids or []
|
||||
self.raw_ci_ids = copy.deepcopy(self.ci_ids)
|
||||
self.query_sql = ""
|
||||
self.type_id_list = []
|
||||
self.only_type_query = False
|
||||
self.parent_node_perm_passed = parent_node_perm_passed
|
||||
self.use_id_filter = use_id_filter
|
||||
self.use_ci_filter = use_ci_filter
|
||||
|
||||
self.valid_type_names = []
|
||||
self.type2filter_perms = dict()
|
||||
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||
|
||||
@staticmethod
|
||||
def _operator_proc(key):
|
||||
@@ -106,7 +114,7 @@ class Search(object):
|
||||
self.type_id_list.append(str(ci_type.id))
|
||||
if ci_type.id in self.type2filter_perms:
|
||||
ci_filter = self.type2filter_perms[ci_type.id].get('ci_filter')
|
||||
if ci_filter:
|
||||
if ci_filter and self.use_ci_filter and not self.use_id_filter:
|
||||
sub = []
|
||||
ci_filter = Template(ci_filter).render(user=current_user)
|
||||
for i in ci_filter.split(','):
|
||||
@@ -122,6 +130,14 @@ class Search(object):
|
||||
self.fl = set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
||||
else:
|
||||
self.fl = set(self.fl) & set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
||||
|
||||
if self.type2filter_perms[ci_type.id].get('id_filter') and self.use_id_filter:
|
||||
|
||||
if not self.raw_ci_ids:
|
||||
self.ci_ids = list(self.type2filter_perms[ci_type.id]['id_filter'].keys())
|
||||
|
||||
if self.use_id_filter and not self.ci_ids and not self.is_app_admin:
|
||||
self.raw_ci_ids = [0]
|
||||
else:
|
||||
raise SearchError(ErrFormat.no_permission.format(ci_type.alias, PermEnum.READ))
|
||||
else:
|
||||
@@ -138,7 +154,10 @@ class Search(object):
|
||||
|
||||
@staticmethod
|
||||
def _id_query_handler(v):
|
||||
return QUERY_CI_BY_ID.format(v)
|
||||
if ";" in v:
|
||||
return QUERY_CI_BY_ID.format("in {}".format(v.replace(';', ',')))
|
||||
else:
|
||||
return QUERY_CI_BY_ID.format("= {}".format(v))
|
||||
|
||||
@staticmethod
|
||||
def _in_query_handler(attr, v, is_not):
|
||||
@@ -152,6 +171,7 @@ class Search(object):
|
||||
"NOT LIKE" if is_not else "LIKE",
|
||||
_v.replace("*", "%")) for _v in new_v])
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, in_query)
|
||||
|
||||
return _query_sql
|
||||
|
||||
@staticmethod
|
||||
@@ -167,6 +187,7 @@ class Search(object):
|
||||
"NOT BETWEEN" if is_not else "BETWEEN",
|
||||
start.replace("*", "%"), end.replace("*", "%"))
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, range_query)
|
||||
|
||||
return _query_sql
|
||||
|
||||
@staticmethod
|
||||
@@ -183,6 +204,7 @@ class Search(object):
|
||||
|
||||
comparison_query = "{0} '{1}'".format(v[0], v[1:].replace("*", "%"))
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, comparison_query)
|
||||
|
||||
return _query_sql
|
||||
|
||||
@staticmethod
|
||||
@@ -194,6 +216,7 @@ class Search(object):
|
||||
elif field.startswith("-"):
|
||||
field = field[1:]
|
||||
sort_type = "DESC"
|
||||
|
||||
return field, sort_type
|
||||
|
||||
def __sort_by_id(self, sort_type, query_sql):
|
||||
@@ -322,6 +345,11 @@ class Search(object):
|
||||
|
||||
return numfound, res
|
||||
|
||||
def __get_type2filter_perms(self):
|
||||
res2 = ACLManager('cmdb').get_resources(ResourceTypeEnum.CI_FILTER)
|
||||
if res2:
|
||||
self.type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
|
||||
|
||||
def __get_types_has_read(self):
|
||||
"""
|
||||
:return: _type:(type1;type2)
|
||||
@@ -331,14 +359,23 @@ class Search(object):
|
||||
|
||||
self.valid_type_names = {i['name'] for i in res if PermEnum.READ in i['permissions']}
|
||||
|
||||
res2 = acl.get_resources(ResourceTypeEnum.CI_FILTER)
|
||||
if res2:
|
||||
self.type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
|
||||
self.__get_type2filter_perms()
|
||||
|
||||
for type_id in self.type2filter_perms:
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
if ci_type:
|
||||
if self.type2filter_perms[type_id].get('id_filter'):
|
||||
if self.use_id_filter:
|
||||
self.valid_type_names.add(ci_type.name)
|
||||
elif self.type2filter_perms[type_id].get('ci_filter'):
|
||||
if self.use_ci_filter:
|
||||
self.valid_type_names.add(ci_type.name)
|
||||
else:
|
||||
self.valid_type_names.add(ci_type.name)
|
||||
|
||||
return "_type:({})".format(";".join(self.valid_type_names))
|
||||
|
||||
def __confirm_type_first(self, queries):
|
||||
|
||||
has_type = False
|
||||
|
||||
result = []
|
||||
@@ -371,8 +408,10 @@ class Search(object):
|
||||
else:
|
||||
result.append(q)
|
||||
|
||||
_is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||
if result and not has_type and not _is_app_admin:
|
||||
if self.parent_node_perm_passed:
|
||||
self.__get_type2filter_perms()
|
||||
self.valid_type_names = "ALL"
|
||||
elif result and not has_type and not self.is_app_admin:
|
||||
type_q = self.__get_types_has_read()
|
||||
if id_query:
|
||||
ci = CIManager.get_by_id(id_query)
|
||||
@@ -381,13 +420,11 @@ class Search(object):
|
||||
result.insert(0, "_type:{}".format(ci.type_id))
|
||||
else:
|
||||
result.insert(0, type_q)
|
||||
elif _is_app_admin:
|
||||
elif self.is_app_admin:
|
||||
self.valid_type_names = "ALL"
|
||||
else:
|
||||
self.__get_types_has_read()
|
||||
|
||||
current_app.logger.warning(result)
|
||||
|
||||
return result
|
||||
|
||||
def __query_by_attr(self, q, queries, alias):
|
||||
@@ -479,7 +516,7 @@ class Search(object):
|
||||
def _filter_ids(self, query_sql):
|
||||
if self.ci_ids:
|
||||
return "SELECT * FROM ({0}) AS IN_QUERY WHERE IN_QUERY.ci_id IN ({1})".format(
|
||||
query_sql, ",".join(list(map(str, self.ci_ids))))
|
||||
query_sql, ",".join(list(set(map(str, self.ci_ids)))))
|
||||
|
||||
return query_sql
|
||||
|
||||
@@ -511,6 +548,9 @@ class Search(object):
|
||||
s = time.time()
|
||||
if query_sql:
|
||||
query_sql = self._filter_ids(query_sql)
|
||||
if self.raw_ci_ids and not self.ci_ids:
|
||||
return 0, []
|
||||
|
||||
self.query_sql = query_sql
|
||||
# current_app.logger.debug(query_sql)
|
||||
numfound, res = self._execute_sql(query_sql)
|
||||
@@ -569,3 +609,8 @@ class Search(object):
|
||||
total = len(response)
|
||||
|
||||
return response, counter, total, self.page, numfound, facet
|
||||
|
||||
def get_ci_ids(self):
|
||||
_, ci_ids = self._query_build_raw()
|
||||
|
||||
return ci_ids
|
||||
|
@@ -1,21 +1,30 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
import json
|
||||
import sys
|
||||
from collections import Counter
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
from api.lib.cmdb.const import ConstraintEnum
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
|
||||
from api.lib.cmdb.utils import TableMap
|
||||
from api.lib.cmdb.utils import ValueTypeMap
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
|
||||
|
||||
class Search(object):
|
||||
@@ -29,7 +38,9 @@ class Search(object):
|
||||
sort=None,
|
||||
reverse=False,
|
||||
ancestor_ids=None,
|
||||
has_m2m=None):
|
||||
descendant_ids=None,
|
||||
has_m2m=None,
|
||||
root_parent_path=None):
|
||||
self.orig_query = query
|
||||
self.fl = fl
|
||||
self.facet_field = facet_field
|
||||
@@ -46,6 +57,8 @@ class Search(object):
|
||||
level[0] if isinstance(level, list) and level else level)
|
||||
|
||||
self.ancestor_ids = ancestor_ids
|
||||
self.descendant_ids = descendant_ids
|
||||
self.root_parent_path = root_parent_path
|
||||
self.has_m2m = has_m2m or False
|
||||
if not self.has_m2m:
|
||||
if self.ancestor_ids:
|
||||
@@ -56,27 +69,23 @@ class Search(object):
|
||||
if _l < int(level) and c == ConstraintEnum.Many2Many:
|
||||
self.has_m2m = True
|
||||
|
||||
self.type2filter_perms = None
|
||||
|
||||
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||
|
||||
def _get_ids(self, ids):
|
||||
if self.level[-1] == 1 and len(ids) == 1:
|
||||
if self.ancestor_ids is None:
|
||||
return [i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0], to_dict=False)]
|
||||
|
||||
else:
|
||||
seconds = {i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0],
|
||||
ancestor_ids=self.ancestor_ids,
|
||||
to_dict=False)}
|
||||
|
||||
return list(seconds)
|
||||
|
||||
merge_ids = []
|
||||
key = []
|
||||
_tmp = []
|
||||
for level in range(1, sorted(self.level)[-1] + 1):
|
||||
if len(self.descendant_ids) >= level and self.type2filter_perms.get(self.descendant_ids[level - 1]):
|
||||
id_filter_limit, _ = self._get_ci_filter(self.type2filter_perms[self.descendant_ids[level - 1]])
|
||||
else:
|
||||
id_filter_limit = {}
|
||||
|
||||
if not self.has_m2m:
|
||||
_tmp = map(lambda x: json.loads(x).keys(),
|
||||
filter(lambda x: x is not None, rd.get(ids, REDIS_PREFIX_CI_RELATION) or []))
|
||||
ids = [j for i in _tmp for j in i]
|
||||
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
||||
key, prefix = list(map(str, ids)), REDIS_PREFIX_CI_RELATION
|
||||
|
||||
else:
|
||||
if not self.ancestor_ids:
|
||||
@@ -92,12 +101,16 @@ class Search(object):
|
||||
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
|
||||
prefix = REDIS_PREFIX_CI_RELATION2
|
||||
|
||||
_tmp = list(map(lambda x: json.loads(x).keys() if x else [], rd.get(key, prefix) or []))
|
||||
ids = [j for i in _tmp for j in i]
|
||||
|
||||
if not key:
|
||||
if not key or id_filter_limit is None:
|
||||
return []
|
||||
|
||||
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||
_tmp = [[i[0] for i in x if (not id_filter_limit or (
|
||||
key[idx] not in id_filter_limit or int(i[0]) in id_filter_limit[key[idx]]) or
|
||||
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
||||
|
||||
ids = [j for i in _tmp for j in i]
|
||||
|
||||
if level in self.level:
|
||||
merge_ids.extend(ids)
|
||||
|
||||
@@ -120,7 +133,28 @@ class Search(object):
|
||||
|
||||
return merge_ids
|
||||
|
||||
def _has_read_perm_from_parent_nodes(self):
|
||||
self.root_parent_path = list(map(str, self.root_parent_path))
|
||||
if str(self.root_id).isdigit() and str(self.root_id) not in self.root_parent_path:
|
||||
self.root_parent_path.append(str(self.root_id))
|
||||
self.root_parent_path = set(self.root_parent_path)
|
||||
|
||||
if self.is_app_admin:
|
||||
self.type2filter_perms = {}
|
||||
return True
|
||||
|
||||
res = ACLManager().get_resources(ResourceTypeEnum.CI_FILTER) or {}
|
||||
self.type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res]))) or {}
|
||||
for _, filters in self.type2filter_perms.items():
|
||||
if set((filters.get('id_filter') or {}).keys()) & self.root_parent_path:
|
||||
return True
|
||||
|
||||
return True
|
||||
|
||||
def search(self):
|
||||
use_ci_filter = len(self.descendant_ids) == self.level[0] - 1
|
||||
parent_node_perm_passed = self._has_read_perm_from_parent_nodes()
|
||||
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids]
|
||||
|
||||
@@ -161,42 +195,105 @@ class Search(object):
|
||||
page=self.page,
|
||||
count=self.count,
|
||||
sort=self.sort,
|
||||
ci_ids=merge_ids).search()
|
||||
ci_ids=merge_ids,
|
||||
parent_node_perm_passed=parent_node_perm_passed,
|
||||
use_ci_filter=use_ci_filter).search()
|
||||
|
||||
def statistics(self, type_ids):
|
||||
def _get_ci_filter(self, filter_perms, ci_filters=None):
|
||||
ci_filters = ci_filters or []
|
||||
if ci_filters:
|
||||
result = {}
|
||||
for item in ci_filters:
|
||||
res = SearchFromDB('_type:{},{}'.format(item['type_id'], item['ci_filter']),
|
||||
count=sys.maxsize, parent_node_perm_passed=True).get_ci_ids()
|
||||
if res:
|
||||
result[item['type_id']] = set(res)
|
||||
|
||||
return {}, result if result else None
|
||||
|
||||
result = dict()
|
||||
if filter_perms.get('id_filter'):
|
||||
for k in filter_perms['id_filter']:
|
||||
node_path = k.split(',')
|
||||
if len(node_path) == 1:
|
||||
result[int(node_path[0])] = 1
|
||||
elif not self.has_m2m:
|
||||
result.setdefault(node_path[-2], set()).add(int(node_path[-1]))
|
||||
else:
|
||||
result.setdefault(','.join(node_path[:-1]), set()).add(int(node_path[-1]))
|
||||
if result:
|
||||
return result, None
|
||||
else:
|
||||
return None, None
|
||||
|
||||
return {}, None
|
||||
|
||||
def statistics(self, type_ids, need_filter=True):
|
||||
self.level = int(self.level)
|
||||
|
||||
acl = ACLManager('cmdb')
|
||||
|
||||
type2filter_perms = dict()
|
||||
if not self.is_app_admin:
|
||||
res2 = acl.get_resources(ResourceTypeEnum.CI_FILTER)
|
||||
if res2:
|
||||
type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
|
||||
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
_tmp = []
|
||||
_tmp, tmp_res = [], []
|
||||
level2ids = {}
|
||||
for lv in range(1, self.level + 1):
|
||||
level2ids[lv] = []
|
||||
|
||||
if need_filter:
|
||||
id_filter_limit, ci_filter_limit = None, None
|
||||
if len(self.descendant_ids or []) >= lv and type2filter_perms.get(self.descendant_ids[lv - 1]):
|
||||
id_filter_limit, _ = self._get_ci_filter(type2filter_perms[self.descendant_ids[lv - 1]])
|
||||
elif type_ids and self.level == lv:
|
||||
ci_filters = [type2filter_perms[type_id] for type_id in type_ids if type_id in type2filter_perms]
|
||||
if ci_filters:
|
||||
id_filter_limit, ci_filter_limit = self._get_ci_filter({}, ci_filters=ci_filters)
|
||||
else:
|
||||
id_filter_limit = {}
|
||||
else:
|
||||
id_filter_limit = {}
|
||||
else:
|
||||
id_filter_limit, ci_filter_limit = {}, {}
|
||||
|
||||
if lv == 1:
|
||||
if not self.has_m2m:
|
||||
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
||||
key, prefix = [str(i) for i in ids], REDIS_PREFIX_CI_RELATION
|
||||
else:
|
||||
key = ["{},{}".format(self.ancestor_ids, _id) for _id in ids]
|
||||
if not self.ancestor_ids:
|
||||
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
||||
key, prefix = [str(i) for i in ids], REDIS_PREFIX_CI_RELATION
|
||||
else:
|
||||
key = ["{},{}".format(self.ancestor_ids, _id) for _id in ids]
|
||||
prefix = REDIS_PREFIX_CI_RELATION2
|
||||
|
||||
level2ids[lv] = [[i] for i in key]
|
||||
|
||||
if not key:
|
||||
_tmp = []
|
||||
if not key or id_filter_limit is None:
|
||||
_tmp = [[]] * len(ids)
|
||||
continue
|
||||
|
||||
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||
_tmp = []
|
||||
if type_ids and lv == self.level:
|
||||
_tmp = list(map(lambda x: [i for i in x if i[1] in type_ids],
|
||||
(map(lambda x: list(json.loads(x).items()),
|
||||
[i or '{}' for i in rd.get(key, prefix) or []]))))
|
||||
_tmp = [[i for i in x if i[1] in type_ids and
|
||||
(not id_filter_limit or (key[idx] not in id_filter_limit or
|
||||
int(i[0]) in id_filter_limit[key[idx]]) or
|
||||
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
||||
else:
|
||||
_tmp = list(map(lambda x: list(json.loads(x).items()),
|
||||
[i or '{}' for i in rd.get(key, prefix) or []]))
|
||||
_tmp = [[i for i in x if (not id_filter_limit or (key[idx] not in id_filter_limit or
|
||||
int(i[0]) in id_filter_limit[key[idx]]) or
|
||||
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
||||
|
||||
if ci_filter_limit:
|
||||
_tmp = [[j for j in i if j[1] not in ci_filter_limit or int(j[0]) in ci_filter_limit[j[1]]]
|
||||
for i in _tmp]
|
||||
|
||||
else:
|
||||
|
||||
for idx, item in enumerate(_tmp):
|
||||
if item:
|
||||
if not self.has_m2m:
|
||||
@@ -208,19 +305,28 @@ class Search(object):
|
||||
level2ids[lv].append(key)
|
||||
|
||||
if key:
|
||||
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||
if type_ids and lv == self.level:
|
||||
__tmp = map(lambda x: [(_id, type_id) for _id, type_id in json.loads(x).items()
|
||||
if type_id in type_ids],
|
||||
filter(lambda x: x is not None,
|
||||
rd.get(key, prefix) or []))
|
||||
tmp_res = [[i for i in x if i[1] in type_ids and
|
||||
(not id_filter_limit or (
|
||||
key[idx] not in id_filter_limit or
|
||||
int(i[0]) in id_filter_limit[key[idx]]) or
|
||||
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
||||
else:
|
||||
__tmp = map(lambda x: list(json.loads(x).items()),
|
||||
filter(lambda x: x is not None,
|
||||
rd.get(key, prefix) or []))
|
||||
else:
|
||||
__tmp = []
|
||||
tmp_res = [[i for i in x if (not id_filter_limit or (
|
||||
key[idx] not in id_filter_limit or
|
||||
int(i[0]) in id_filter_limit[key[idx]]) or
|
||||
int(i[0]) in id_filter_limit)] for idx, x in
|
||||
enumerate(res)]
|
||||
|
||||
_tmp[idx] = [j for i in __tmp for j in i]
|
||||
if ci_filter_limit:
|
||||
tmp_res = [[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_res]
|
||||
else:
|
||||
tmp_res = []
|
||||
|
||||
if tmp_res:
|
||||
_tmp[idx] = [j for i in tmp_res for j in i]
|
||||
else:
|
||||
_tmp[idx] = []
|
||||
level2ids[lv].append([])
|
||||
@@ -231,3 +337,84 @@ class Search(object):
|
||||
detail={str(_id): dict(Counter([i[1] for i in _tmp[idx]]).items()) for idx, _id in enumerate(ids)})
|
||||
|
||||
return result
|
||||
|
||||
def search_full(self, type_ids):
|
||||
def _get_id2name(_type_id):
|
||||
ci_type = CITypeCache.get(_type_id)
|
||||
|
||||
attr = AttributeCache.get(ci_type.unique_id)
|
||||
value_table = TableMap(attr=attr).table
|
||||
serializer = ValueTypeMap.serialize[attr.value_type]
|
||||
unique_value = {i.ci_id: serializer(i.value) for i in value_table.get_by(attr_id=attr.id, to_dict=False)}
|
||||
|
||||
attr = AttributeCache.get(ci_type.show_id)
|
||||
if attr:
|
||||
value_table = TableMap(attr=attr).table
|
||||
serializer = ValueTypeMap.serialize[attr.value_type]
|
||||
show_value = {i.ci_id: serializer(i.value) for i in value_table.get_by(attr_id=attr.id, to_dict=False)}
|
||||
else:
|
||||
show_value = unique_value
|
||||
|
||||
return show_value, unique_value
|
||||
|
||||
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
|
||||
|
||||
level_ids = [str(i) for i in ids]
|
||||
result = []
|
||||
id2children = {}
|
||||
id2name = _get_id2name(type_ids[0])
|
||||
for i in level_ids:
|
||||
item = dict(id=int(i),
|
||||
type_id=type_ids[0],
|
||||
isLeaf=False,
|
||||
title=id2name[0].get(int(i)),
|
||||
uniqueValue=id2name[1].get(int(i)),
|
||||
children=[])
|
||||
result.append(item)
|
||||
id2children[str(i)] = item['children']
|
||||
|
||||
for lv in range(1, self.level):
|
||||
|
||||
if len(type_ids or []) >= lv and type2filter_perms.get(type_ids[lv]):
|
||||
id_filter_limit, _ = self._get_ci_filter(type2filter_perms[type_ids[lv]])
|
||||
else:
|
||||
id_filter_limit = {}
|
||||
|
||||
if self.has_m2m and lv != 1:
|
||||
key, prefix = [i for i in level_ids], REDIS_PREFIX_CI_RELATION2
|
||||
else:
|
||||
key, prefix = [i.split(',')[-1] for i in level_ids], REDIS_PREFIX_CI_RELATION
|
||||
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||
res = [[i for i in x if (not id_filter_limit or (key[idx] not in id_filter_limit or
|
||||
int(i[0]) in id_filter_limit[key[idx]]) or
|
||||
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
||||
_level_ids = []
|
||||
type_id = type_ids[lv]
|
||||
id2name = _get_id2name(type_id)
|
||||
for idx, node_path in enumerate(level_ids):
|
||||
for child_id, _ in (res[idx] or []):
|
||||
item = dict(id=int(child_id),
|
||||
type_id=type_id,
|
||||
isLeaf=True if lv == self.level - 1 else False,
|
||||
title=id2name[0].get(int(child_id)),
|
||||
uniqueValue=id2name[1].get(int(child_id)),
|
||||
children=[])
|
||||
id2children[node_path].append(item)
|
||||
|
||||
_node_path = "{},{}".format(node_path, child_id)
|
||||
_level_ids.append(_node_path)
|
||||
id2children[_node_path] = item['children']
|
||||
|
||||
level_ids = _level_ids
|
||||
|
||||
return result
|
||||
|
@@ -11,12 +11,21 @@ import six
|
||||
import api.models.cmdb as model
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
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')
|
||||
|
||||
|
||||
class ValueDeserializeError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
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):
|
||||
|
@@ -6,6 +6,7 @@ from __future__ import unicode_literals
|
||||
import copy
|
||||
import imp
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
|
||||
import jinja2
|
||||
@@ -13,6 +14,7 @@ from flask import abort
|
||||
from flask import current_app
|
||||
from jinja2schema import infer
|
||||
from jinja2schema import to_json_schema
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from api.extensions import db
|
||||
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.resp_format import ErrFormat
|
||||
from api.lib.cmdb.utils import TableMap
|
||||
from api.lib.cmdb.utils import ValueDeserializeError
|
||||
from api.lib.cmdb.utils import ValueTypeMap
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.models.cmdb import CI
|
||||
@@ -80,7 +83,7 @@ class AttributeValueManager(object):
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def _deserialize_value(value_type, value):
|
||||
def _deserialize_value(alias, value_type, value):
|
||||
if not value:
|
||||
return value
|
||||
|
||||
@@ -88,6 +91,8 @@ class AttributeValueManager(object):
|
||||
try:
|
||||
v = deserialize(value)
|
||||
return v
|
||||
except ValueDeserializeError as e:
|
||||
return abort(400, ErrFormat.attribute_value_invalid2.format(alias, e))
|
||||
except ValueError:
|
||||
return abort(400, ErrFormat.attribute_value_invalid.format(value))
|
||||
|
||||
@@ -117,9 +122,14 @@ class AttributeValueManager(object):
|
||||
if type_attr and type_attr.is_required and not value and value != 0:
|
||||
return abort(400, ErrFormat.attribute_value_required.format(attr.alias))
|
||||
|
||||
@staticmethod
|
||||
def check_re(expr, value):
|
||||
if not re.compile(expr).match(str(value)):
|
||||
return abort(400, ErrFormat.attribute_value_invalid.format(value))
|
||||
|
||||
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
|
||||
ci = ci or {}
|
||||
v = self._deserialize_value(attr.value_type, value)
|
||||
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_unique and self._check_is_unique(
|
||||
@@ -130,6 +140,9 @@ class AttributeValueManager(object):
|
||||
if v == "" and attr.value_type not in (ValueTypeEnum.TEXT,):
|
||||
v = None
|
||||
|
||||
if attr.re_check and value:
|
||||
self.check_re(attr.re_check, value)
|
||||
|
||||
return v
|
||||
|
||||
@staticmethod
|
||||
@@ -137,9 +150,10 @@ class AttributeValueManager(object):
|
||||
return AttributeHistoryManger.add(record_id, ci_id, [(attr_id, operate_type, old, new)], type_id)
|
||||
|
||||
@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:
|
||||
record_id = AttributeHistoryManger.add(record_id, ci_id, [(attr_id, operate_type, old, new)], type_id,
|
||||
ticket_id=ticket_id,
|
||||
commit=False, flush=False)
|
||||
try:
|
||||
db.session.commit()
|
||||
@@ -232,6 +246,8 @@ class AttributeValueManager(object):
|
||||
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))
|
||||
ci_dict[key] = value
|
||||
except BadRequest as e:
|
||||
raise
|
||||
except Exception as e:
|
||||
current_app.logger.warning(str(e))
|
||||
|
||||
@@ -240,12 +256,13 @@ class AttributeValueManager(object):
|
||||
|
||||
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
|
||||
:param ci: instance object
|
||||
:param ci_dict: attribute dict
|
||||
:param key2attr: attr key to attr
|
||||
:param ticket_id:
|
||||
:return:
|
||||
"""
|
||||
changed = []
|
||||
@@ -258,25 +275,36 @@ class AttributeValueManager(object):
|
||||
if attr.is_list:
|
||||
existed_attrs = value_table.get_by(attr_id=attr.id, ci_id=ci.id, to_dict=False)
|
||||
existed_values = [i.value for i in existed_attrs]
|
||||
added = set(value) - set(existed_values)
|
||||
deleted = set(existed_values) - set(value)
|
||||
for v in added:
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=v, flush=False, commit=False)
|
||||
changed.append((ci.id, attr.id, OperateType.ADD, None, v, ci.type_id))
|
||||
|
||||
# Comparison array starts from which position changes
|
||||
min_len = min(len(value), len(existed_values))
|
||||
index = 0
|
||||
while index < min_len:
|
||||
if value[index] != existed_values[index]:
|
||||
break
|
||||
index += 1
|
||||
added = value[index:]
|
||||
deleted = existed_values[index:]
|
||||
|
||||
# Delete first and then add to ensure id sorting
|
||||
for v in deleted:
|
||||
existed_attr = existed_attrs[existed_values.index(v)]
|
||||
existed_attr.delete(flush=False, commit=False)
|
||||
changed.append((ci.id, attr.id, OperateType.DELETE, v, None, ci.type_id))
|
||||
for v in added:
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=v, flush=False, commit=False)
|
||||
changed.append((ci.id, attr.id, OperateType.ADD, None, v, ci.type_id))
|
||||
else:
|
||||
existed_attr = value_table.get_by(attr_id=attr.id, ci_id=ci.id, first=True, to_dict=False)
|
||||
existed_value = existed_attr and existed_attr.value
|
||||
existed_value = (ValueTypeMap.serialize[attr.value_type](existed_value) if
|
||||
existed_value or existed_value == 0 else existed_value)
|
||||
if existed_value is None and value is not None:
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value, flush=False, commit=False)
|
||||
|
||||
changed.append((ci.id, attr.id, OperateType.ADD, None, value, ci.type_id))
|
||||
else:
|
||||
if existed_value != value:
|
||||
if existed_value != value and existed_attr:
|
||||
if value is None:
|
||||
existed_attr.delete(flush=False, commit=False)
|
||||
else:
|
||||
@@ -291,12 +319,12 @@ class AttributeValueManager(object):
|
||||
current_app.logger.warning(str(e))
|
||||
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
|
||||
def delete_attr_value(attr_id, ci_id):
|
||||
def delete_attr_value(attr_id, ci_id, commit=True):
|
||||
attr = AttributeCache.get(attr_id)
|
||||
if attr is not None:
|
||||
value_table = TableMap(attr=attr).table
|
||||
for item in value_table.get_by(attr_id=attr.id, ci_id=ci_id, to_dict=False):
|
||||
item.delete()
|
||||
item.delete(commit=commit)
|
||||
|
@@ -10,6 +10,11 @@ from api.lib.perm.acl.role import RoleCRUD, RoleRelationCRUD
|
||||
from api.lib.perm.acl.user import UserCRUD
|
||||
|
||||
|
||||
def validate_app(app_id):
|
||||
app = AppCache.get(app_id)
|
||||
return app.id if app else None
|
||||
|
||||
|
||||
class ACLManager(object):
|
||||
def __init__(self, app_name='acl', uid=None):
|
||||
self.log = current_app.logger
|
||||
@@ -133,7 +138,8 @@ class ACLManager(object):
|
||||
numfound, res = ResourceCRUD.search(q, u, self.validate_app().id, rt_id, page, page_size)
|
||||
return res
|
||||
|
||||
def grant_resource(self, rid, resource_id, perms):
|
||||
@staticmethod
|
||||
def grant_resource(rid, resource_id, perms):
|
||||
PermissionCRUD.grant(rid, perms, resource_id=resource_id, group_id=None)
|
||||
|
||||
@staticmethod
|
||||
@@ -141,3 +147,7 @@ class ACLManager(object):
|
||||
rt = AppCRUD.add(**payload)
|
||||
|
||||
return rt.to_dict()
|
||||
|
||||
def role_has_perms(self, rid, resource_name, resource_type_name, perm):
|
||||
app_id = validate_app(self.app_name)
|
||||
return RoleCRUD.has_permission(rid, resource_name, resource_type_name, app_id, perm)
|
||||
|
@@ -35,3 +35,32 @@ AuthCommonConfigAutoRedirect = 'auto_redirect'
|
||||
class TestType(BaseEnum):
|
||||
Connect = 'connect'
|
||||
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',
|
||||
}
|
||||
|
37
cmdb-api/api/lib/common_setting/decorator.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import functools
|
||||
|
||||
from flask import abort, session
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
|
||||
|
||||
def perms_role_required(app_name, resource_type_name, resource_name, perm, role_name=None):
|
||||
def decorator_perms_role_required(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper_required(*args, **kwargs):
|
||||
acl = ACLManager(app_name)
|
||||
has_perms = False
|
||||
try:
|
||||
has_perms = acl.role_has_perms(session["acl"]['rid'], resource_name, resource_type_name, perm)
|
||||
except Exception as e:
|
||||
# resource_type not exist, continue check role
|
||||
if role_name:
|
||||
if role_name not in session.get("acl", {}).get("parentRoles", []):
|
||||
abort(403, ErrFormat.role_required.format(role_name))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
else:
|
||||
abort(403, ErrFormat.resource_no_permission.format(resource_name, perm))
|
||||
|
||||
if not has_perms:
|
||||
if role_name:
|
||||
if role_name not in session.get("acl", {}).get("parentRoles", []):
|
||||
abort(403, ErrFormat.role_required.format(role_name))
|
||||
else:
|
||||
abort(403, ErrFormat.resource_no_permission.format(resource_name, perm))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper_required
|
||||
|
||||
return decorator_perms_role_required
|
@@ -470,8 +470,58 @@ class EditDepartmentInACL(object):
|
||||
|
||||
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
|
||||
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 = []
|
||||
new_department = DepartmentCRUD.get_department_by_id(new_d_id, False)
|
||||
if not new_department:
|
||||
@@ -481,7 +531,11 @@ class EditDepartmentInACL(object):
|
||||
from api.models.acl import Role
|
||||
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
|
||||
acl = ACLManager('acl', str(op_uid))
|
||||
|
||||
if new_d_rid_in_acl == 0:
|
||||
# only remove from old department role
|
||||
cls.remove_from_old_department_role(e_list, acl)
|
||||
return
|
||||
|
||||
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_d_rid_in_acl
|
||||
|
||||
acl = ACLManager('acl', str(op_uid))
|
||||
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')
|
||||
if employee_acl_rid == 0:
|
||||
result.append(f"employee_acl_rid == 0")
|
||||
continue
|
||||
|
||||
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
|
||||
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}")
|
||||
cls.remove_single_employee_from_old_department(acl, employee, result)
|
||||
|
||||
payload = {
|
||||
'app_id': 'acl',
|
||||
'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}")
|
||||
# 在新部门中添加员工
|
||||
cls.add_employee_to_new_department(acl, employee_acl_rid, new_department_acl_rid, result)
|
||||
|
||||
return result
|
||||
|
@@ -16,7 +16,7 @@ from wtforms import validators
|
||||
from api.extensions import db
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.common_setting.const import OperatorType
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.models.common_setting import Employee, Department
|
||||
|
||||
@@ -141,7 +141,7 @@ class EmployeeCRUD(object):
|
||||
def add(**kwargs):
|
||||
try:
|
||||
res = CreateEmployee().create_single(**kwargs)
|
||||
refresh_employee_acl_info.apply_async(args=(), queue=CMDB_QUEUE)
|
||||
refresh_employee_acl_info.apply_async(args=(res.employee_id,), queue=ACL_QUEUE)
|
||||
return res
|
||||
except Exception as e:
|
||||
abort(400, str(e))
|
||||
@@ -171,7 +171,7 @@ class EmployeeCRUD(object):
|
||||
if len(e_list) > 0:
|
||||
edit_employee_department_in_acl.apply_async(
|
||||
args=(e_list, new_department_id, current_user.uid),
|
||||
queue=CMDB_QUEUE
|
||||
queue=ACL_QUEUE
|
||||
)
|
||||
|
||||
return existed
|
||||
@@ -577,7 +577,6 @@ class EmployeeCRUD(object):
|
||||
@staticmethod
|
||||
def import_employee(employee_list):
|
||||
res = CreateEmployee().batch_create(employee_list)
|
||||
refresh_employee_acl_info.apply_async(args=(), queue=CMDB_QUEUE)
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
@@ -788,9 +787,11 @@ class CreateEmployee(object):
|
||||
if existed:
|
||||
return existed
|
||||
|
||||
return Employee.create(
|
||||
res = Employee.create(
|
||||
**kwargs
|
||||
)
|
||||
refresh_employee_acl_info.apply_async(args=(res.employee_id,), queue=ACL_QUEUE)
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def get_department_by_name(d_name):
|
||||
@@ -897,3 +898,75 @@ class EmployeeUpdateByUidForm(Form):
|
||||
avatar = StringField(validators=[])
|
||||
sex = StringField(validators=[])
|
||||
mobile = StringField(validators=[])
|
||||
|
||||
|
||||
class GrantEmployeeACLPerm(object):
|
||||
"""
|
||||
Grant ACL Permission After Create New Employee
|
||||
"""
|
||||
|
||||
def __init__(self, acl=None):
|
||||
self.perms_by_create_resources_type = ['read', 'grant', 'delete', 'update']
|
||||
self.perms_by_common_grant = ['read']
|
||||
self.resource_name_list = ['公司信息', '公司架构', '通知设置']
|
||||
|
||||
self.acl = acl if acl else self.check_app('backend')
|
||||
self.resources_types = self.acl.get_all_resources_types()
|
||||
self.resources_type = self.get_resources_type()
|
||||
self.resource_list = self.acl.get_resource_by_type(None, None, self.resources_type['id'])
|
||||
|
||||
@staticmethod
|
||||
def check_app(app_name):
|
||||
acl = ACLManager(app_name)
|
||||
payload = dict(
|
||||
name=app_name,
|
||||
description=app_name
|
||||
)
|
||||
app = acl.validate_app()
|
||||
if not app:
|
||||
acl.create_app(payload)
|
||||
return acl
|
||||
|
||||
def get_resources_type(self):
|
||||
results = list(filter(lambda t: t['name'] == '操作权限', self.resources_types['groups']))
|
||||
if len(results) == 0:
|
||||
payload = dict(
|
||||
app_id=self.acl.app_name,
|
||||
name='操作权限',
|
||||
description='',
|
||||
perms=self.perms_by_create_resources_type
|
||||
)
|
||||
resource_type = self.acl.create_resources_type(payload)
|
||||
else:
|
||||
resource_type = results[0]
|
||||
resource_type_id = resource_type['id']
|
||||
existed_perms = self.resources_types.get('id2perms', {}).get(resource_type_id, [])
|
||||
existed_perms = [p['name'] for p in existed_perms]
|
||||
new_perms = []
|
||||
for perm in self.perms_by_create_resources_type:
|
||||
if perm not in existed_perms:
|
||||
new_perms.append(perm)
|
||||
if len(new_perms) > 0:
|
||||
resource_type['perms'] = existed_perms + new_perms
|
||||
self.acl.update_resources_type(resource_type_id, resource_type)
|
||||
|
||||
return resource_type
|
||||
|
||||
def grant(self, rid_list):
|
||||
[self.grant_by_rid(rid) for rid in rid_list if rid > 0]
|
||||
|
||||
def grant_by_rid(self, rid, is_admin=False):
|
||||
for name in self.resource_name_list:
|
||||
resource = list(filter(lambda r: r['name'] == name, self.resource_list))
|
||||
if len(resource) == 0:
|
||||
payload = dict(
|
||||
type_id=self.resources_type['id'],
|
||||
app_id=self.acl.app_name,
|
||||
name=name,
|
||||
)
|
||||
resource = self.acl.create_resource(payload)
|
||||
else:
|
||||
resource = resource[0]
|
||||
|
||||
perms = self.perms_by_create_resources_type if is_admin else self.perms_by_common_grant
|
||||
self.acl.grant_resource(rid, resource['id'], perms)
|
||||
|
@@ -80,3 +80,5 @@ class ErrFormat(CommonErrFormat):
|
||||
ldap_test_username_required = _l("LDAP test username required") # LDAP测试用户名必填
|
||||
|
||||
company_wide = _l("Company wide") # 全公司
|
||||
|
||||
resource_no_permission = _l("No permission to access resource {}, perm {} ") # 没有权限访问 {} 资源的 {} 权限"
|
||||
|
59
cmdb-api/api/lib/common_setting/role_perm_base.py
Normal file
@@ -0,0 +1,59 @@
|
||||
class OperationPermission(object):
|
||||
|
||||
def __init__(self, resource_perms):
|
||||
for _r in resource_perms:
|
||||
setattr(self, _r['page'], _r['page'])
|
||||
for _p in _r['perms']:
|
||||
setattr(self, _p, _p)
|
||||
|
||||
|
||||
class BaseApp(object):
|
||||
resource_type_name = 'OperationPermission'
|
||||
all_resource_perms = []
|
||||
|
||||
def __init__(self):
|
||||
self.admin_name = None
|
||||
self.roles = []
|
||||
self.app_name = 'acl'
|
||||
self.require_create_resource_type = self.resource_type_name
|
||||
self.extra_create_resource_type_list = []
|
||||
|
||||
self.op = None
|
||||
|
||||
@staticmethod
|
||||
def format_role(role_name, role_type, acl_rid, resource_perms, description=''):
|
||||
return dict(
|
||||
role_name=role_name,
|
||||
role_type=role_type,
|
||||
acl_rid=acl_rid,
|
||||
description=description,
|
||||
resource_perms=resource_perms,
|
||||
)
|
||||
|
||||
|
||||
class CMDBApp(BaseApp):
|
||||
all_resource_perms = [
|
||||
{"page": "Big_Screen", "page_cn": "大屏", "perms": ["read"]},
|
||||
{"page": "Dashboard", "page_cn": "仪表盘", "perms": ["read"]},
|
||||
{"page": "Resource_Search", "page_cn": "资源搜索", "perms": ["read"]},
|
||||
{"page": "Auto_Discovery_Pool", "page_cn": "自动发现池", "perms": ["read"]},
|
||||
{"page": "My_Subscriptions", "page_cn": "我的订阅", "perms": ["read"]},
|
||||
{"page": "Bulk_Import", "page_cn": "批量导入", "perms": ["read"]},
|
||||
{"page": "Model_Configuration", "page_cn": "模型配置",
|
||||
"perms": ["read", "create_CIType", "create_CIType_group", "update_CIType_group",
|
||||
"delete_CIType_group", "download_CIType"]},
|
||||
{"page": "Backend_Management", "page_cn": "后台管理", "perms": ["read"]},
|
||||
{"page": "Customized_Dashboard", "page_cn": "定制仪表盘", "perms": ["read"]},
|
||||
{"page": "Service_Tree_Definition", "page_cn": "服务树定义", "perms": ["read"]},
|
||||
{"page": "Model_Relationships", "page_cn": "模型关系", "perms": ["read"]},
|
||||
{"page": "Operation_Audit", "page_cn": "操作审计", "perms": ["read"]},
|
||||
{"page": "Relationship_Types", "page_cn": "关系类型", "perms": ["read"]},
|
||||
{"page": "Auto_Discovery", "page_cn": "自动发现", "perms": ["read"]}]
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.admin_name = 'cmdb_admin'
|
||||
self.app_name = 'cmdb'
|
||||
|
||||
self.op = OperationPermission(self.all_resource_perms)
|
@@ -1,5 +1,10 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
from datetime import datetime
|
||||
from flask import current_app
|
||||
from sqlalchemy import inspect, text
|
||||
from sqlalchemy.dialects.mysql import ENUM
|
||||
|
||||
from api.extensions import db
|
||||
|
||||
|
||||
def get_cur_time_str(split_flag='-'):
|
||||
@@ -23,3 +28,115 @@ class BaseEnum(object):
|
||||
if not attr.startswith("_") and not callable(getattr(cls, attr))
|
||||
}
|
||||
return cls._ALL_
|
||||
|
||||
|
||||
class CheckNewColumn(object):
|
||||
|
||||
def __init__(self):
|
||||
self.engine = db.get_engine()
|
||||
self.inspector = inspect(self.engine)
|
||||
self.table_names = self.inspector.get_table_names()
|
||||
|
||||
@staticmethod
|
||||
def get_model_by_table_name(_table_name):
|
||||
registry = getattr(db.Model, 'registry', None)
|
||||
class_registry = getattr(registry, '_class_registry', None)
|
||||
for _model in class_registry.values():
|
||||
if hasattr(_model, '__tablename__') and _model.__tablename__ == _table_name:
|
||||
return _model
|
||||
return None
|
||||
|
||||
def run(self):
|
||||
for table_name in self.table_names:
|
||||
self.check_by_table(table_name)
|
||||
|
||||
def check_by_table(self, table_name):
|
||||
existed_columns = self.inspector.get_columns(table_name)
|
||||
enum_columns = []
|
||||
existed_column_name_list = []
|
||||
for c in existed_columns:
|
||||
if isinstance(c['type'], ENUM):
|
||||
enum_columns.append(c['name'])
|
||||
existed_column_name_list.append(c['name'])
|
||||
|
||||
model = self.get_model_by_table_name(table_name)
|
||||
if model is None:
|
||||
return
|
||||
model_columns = getattr(getattr(getattr(model, '__table__'), 'columns'), '_all_columns')
|
||||
for column in model_columns:
|
||||
if column.name not in existed_column_name_list:
|
||||
add_res = self.add_new_column(table_name, column)
|
||||
if not add_res:
|
||||
continue
|
||||
|
||||
current_app.logger.info(f"add new column [{column.name}] in table [{table_name}] success.")
|
||||
|
||||
if column.name in enum_columns:
|
||||
enum_columns.remove(column.name)
|
||||
|
||||
self.add_new_index(table_name, column)
|
||||
|
||||
if len(enum_columns) > 0:
|
||||
self.check_enum_column(enum_columns, existed_columns, model_columns, table_name)
|
||||
|
||||
def add_new_column(self, target_table_name, new_column):
|
||||
try:
|
||||
column_type = new_column.type.compile(self.engine.dialect)
|
||||
default_value = new_column.default.arg if new_column.default else None
|
||||
|
||||
sql = "ALTER TABLE " + target_table_name + " ADD COLUMN " + f"`{new_column.name}`" + " " + column_type
|
||||
if new_column.comment:
|
||||
sql += f" comment '{new_column.comment}'"
|
||||
|
||||
if column_type == 'JSON':
|
||||
pass
|
||||
elif default_value:
|
||||
if column_type.startswith('VAR') or column_type.startswith('Text'):
|
||||
if default_value is None or len(default_value) == 0:
|
||||
pass
|
||||
else:
|
||||
sql += f" DEFAULT {default_value}"
|
||||
|
||||
sql = text(sql)
|
||||
db.session.execute(sql)
|
||||
return True
|
||||
except Exception as e:
|
||||
err = f"add_new_column [{new_column.name}] to table [{target_table_name}] err: {e}"
|
||||
current_app.logger.error(err)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def add_new_index(target_table_name, new_column):
|
||||
try:
|
||||
if new_column.index:
|
||||
index_name = f"{target_table_name}_{new_column.name}"
|
||||
sql = "CREATE INDEX " + f"{index_name}" + " ON " + target_table_name + " (" + new_column.name + ")"
|
||||
db.session.execute(sql)
|
||||
current_app.logger.info(f"add new index [{index_name}] in table [{target_table_name}] success.")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
err = f"add_new_index [{new_column.name}] to table [{target_table_name}] err: {e}"
|
||||
current_app.logger.error(err)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def check_enum_column(enum_columns, existed_columns, model_columns, table_name):
|
||||
for column_name in enum_columns:
|
||||
try:
|
||||
enum_column = list(filter(lambda x: x['name'] == column_name, existed_columns))[0]
|
||||
old_enum_value = enum_column.get('type', {}).enums
|
||||
target_column = list(filter(lambda x: x.name == column_name, model_columns))[0]
|
||||
new_enum_value = target_column.type.enums
|
||||
|
||||
if set(old_enum_value) == set(new_enum_value):
|
||||
continue
|
||||
|
||||
enum_values_str = ','.join(["'{}'".format(value) for value in new_enum_value])
|
||||
sql = f"ALTER TABLE {table_name} MODIFY COLUMN" + f"`{column_name}`" + f" enum({enum_values_str})"
|
||||
db.session.execute(sql)
|
||||
current_app.logger.info(
|
||||
f"modify column [{column_name}] ENUM: {new_enum_value} in table [{table_name}] success.")
|
||||
except Exception as e:
|
||||
current_app.logger.error(
|
||||
f"modify column ENUM [{column_name}] in table [{table_name}] err: {e}")
|
||||
|
@@ -148,10 +148,10 @@ class ACLManager(object):
|
||||
if group:
|
||||
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)
|
||||
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):
|
||||
if is_app_admin(self.app_id):
|
||||
|
@@ -389,6 +389,7 @@ class AuditCRUD(object):
|
||||
logout_at=logout_at,
|
||||
ip=request.headers.get('X-Real-IP') or request.remote_addr,
|
||||
browser=request.headers.get('User-Agent'),
|
||||
channel=request.values.get('channel', 'web'),
|
||||
)
|
||||
|
||||
if logout_at is None:
|
||||
|
@@ -2,10 +2,11 @@
|
||||
|
||||
|
||||
import msgpack
|
||||
import redis_lock
|
||||
|
||||
from api.extensions import cache
|
||||
from api.extensions import rd
|
||||
from api.lib.decorator import flush_db
|
||||
from api.lib.utils import Lock
|
||||
from api.models.acl import App
|
||||
from api.models.acl import Permission
|
||||
from api.models.acl import Resource
|
||||
@@ -136,14 +137,14 @@ class HasResourceRoleCache(object):
|
||||
|
||||
@classmethod
|
||||
def add(cls, rid, app_id):
|
||||
with Lock('HasResourceRoleCache'):
|
||||
with redis_lock.Lock(rd.r, 'HasResourceRoleCache'):
|
||||
c = cls.get(app_id)
|
||||
c[rid] = 1
|
||||
cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0)
|
||||
|
||||
@classmethod
|
||||
def remove(cls, rid, app_id):
|
||||
with Lock('HasResourceRoleCache'):
|
||||
with redis_lock.Lock(rd.r, 'HasResourceRoleCache'):
|
||||
c = cls.get(app_id)
|
||||
c.pop(rid, None)
|
||||
cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0)
|
||||
@@ -223,16 +224,22 @@ class RoleRelationCache(object):
|
||||
@classmethod
|
||||
@flush_db
|
||||
def rebuild(cls, rid, app_id):
|
||||
cls.clean(rid, app_id)
|
||||
|
||||
cls.get_parent_ids(rid, app_id)
|
||||
cls.get_child_ids(rid, app_id)
|
||||
resources = cls.get_resources(rid, app_id)
|
||||
if resources.get('id2perms') or resources.get('group2perms'):
|
||||
HasResourceRoleCache.add(rid, app_id)
|
||||
if app_id is None:
|
||||
app_ids = [None] + [i.id for i in App.get_by(to_dict=False)]
|
||||
else:
|
||||
HasResourceRoleCache.remove(rid, app_id)
|
||||
cls.get_resources2(rid, app_id)
|
||||
app_ids = [app_id]
|
||||
|
||||
for _app_id in app_ids:
|
||||
cls.clean(rid, _app_id)
|
||||
|
||||
cls.get_parent_ids(rid, _app_id)
|
||||
cls.get_child_ids(rid, _app_id)
|
||||
resources = cls.get_resources(rid, _app_id)
|
||||
if resources.get('id2perms') or resources.get('group2perms'):
|
||||
HasResourceRoleCache.add(rid, _app_id)
|
||||
else:
|
||||
HasResourceRoleCache.remove(rid, _app_id)
|
||||
cls.get_resources2(rid, _app_id)
|
||||
|
||||
@classmethod
|
||||
@flush_db
|
||||
|
@@ -274,12 +274,14 @@ class PermissionCRUD(object):
|
||||
perm2resource.setdefault(_perm, []).append(resource_id)
|
||||
for _perm in perm2resource:
|
||||
perm = PermissionCache.get(_perm, resource_type_id)
|
||||
existeds = RolePermission.get_by(rid=rid,
|
||||
app_id=app_id,
|
||||
perm_id=perm.id,
|
||||
__func_in___key_resource_id=perm2resource[_perm],
|
||||
to_dict=False)
|
||||
for existed in existeds:
|
||||
if perm is None:
|
||||
continue
|
||||
exists = RolePermission.get_by(rid=rid,
|
||||
app_id=app_id,
|
||||
perm_id=perm.id,
|
||||
__func_in___key_resource_id=perm2resource[_perm],
|
||||
to_dict=False)
|
||||
for existed in exists:
|
||||
existed.deleted = True
|
||||
existed.deleted_at = datetime.datetime.now()
|
||||
db.session.add(existed)
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
@@ -127,11 +126,18 @@ class ResourceTypeCRUD(object):
|
||||
existed_ids = [i.id for i in existed]
|
||||
current_ids = []
|
||||
|
||||
rebuild_rids = set()
|
||||
for i in existed:
|
||||
if i.name not in perms:
|
||||
i.soft_delete()
|
||||
i.soft_delete(commit=False)
|
||||
for rp in RolePermission.get_by(perm_id=i.id, to_dict=False):
|
||||
rp.soft_delete(commit=False)
|
||||
rebuild_rids.add((rp.app_id, rp.rid))
|
||||
else:
|
||||
current_ids.append(i.id)
|
||||
db.session.commit()
|
||||
for _app_id, _rid in rebuild_rids:
|
||||
role_rebuild.apply_async(args=(_rid, _app_id), queue=ACL_QUEUE)
|
||||
|
||||
for i in perms:
|
||||
if i not in existed_names:
|
||||
@@ -309,7 +315,7 @@ class ResourceCRUD(object):
|
||||
return resource
|
||||
|
||||
@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)))
|
||||
|
||||
origin = resource.to_dict()
|
||||
@@ -322,8 +328,9 @@ class ResourceCRUD(object):
|
||||
i.soft_delete()
|
||||
rebuilds.append((i.rid, i.app_id))
|
||||
|
||||
for rid, app_id in set(rebuilds):
|
||||
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE)
|
||||
if rebuild:
|
||||
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,
|
||||
AuditScope.resource, resource.id, origin, {}, {})
|
||||
|
@@ -3,12 +3,14 @@
|
||||
|
||||
import time
|
||||
|
||||
import redis_lock
|
||||
import six
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from sqlalchemy import or_
|
||||
|
||||
from api.extensions import db
|
||||
from api.extensions import rd
|
||||
from api.lib.perm.acl.app import AppCRUD
|
||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
@@ -62,7 +64,9 @@ class RoleRelationCRUD(object):
|
||||
|
||||
id2parents = {}
|
||||
for i in res:
|
||||
id2parents.setdefault(rid2uid.get(i.child_id, i.child_id), []).append(RoleCache.get(i.parent_id).to_dict())
|
||||
parent = RoleCache.get(i.parent_id)
|
||||
if parent:
|
||||
id2parents.setdefault(rid2uid.get(i.child_id, i.child_id), []).append(parent.to_dict())
|
||||
|
||||
return id2parents
|
||||
|
||||
@@ -141,24 +145,27 @@ class RoleRelationCRUD(object):
|
||||
|
||||
@classmethod
|
||||
def add(cls, role, parent_id, child_ids, app_id):
|
||||
result = []
|
||||
for child_id in child_ids:
|
||||
existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id, app_id=app_id)
|
||||
if existed:
|
||||
continue
|
||||
with redis_lock.Lock(rd.r, "ROLE_RELATION_ADD"):
|
||||
db.session.commit()
|
||||
|
||||
RoleRelationCache.clean(parent_id, app_id)
|
||||
RoleRelationCache.clean(child_id, app_id)
|
||||
result = []
|
||||
for child_id in child_ids:
|
||||
existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id, app_id=app_id)
|
||||
if existed:
|
||||
continue
|
||||
|
||||
if parent_id in cls.recursive_child_ids(child_id, app_id):
|
||||
return abort(400, ErrFormat.inheritance_dead_loop)
|
||||
RoleRelationCache.clean(parent_id, app_id)
|
||||
RoleRelationCache.clean(child_id, app_id)
|
||||
|
||||
if app_id is None:
|
||||
for app in AppCRUD.get_all():
|
||||
if app.name != "acl":
|
||||
RoleRelationCache.clean(child_id, app.id)
|
||||
if parent_id in cls.recursive_child_ids(child_id, app_id):
|
||||
return abort(400, ErrFormat.inheritance_dead_loop)
|
||||
|
||||
result.append(RoleRelation.create(parent_id=parent_id, child_id=child_id, app_id=app_id).to_dict())
|
||||
if app_id is None:
|
||||
for app in AppCRUD.get_all():
|
||||
if app.name != "acl":
|
||||
RoleRelationCache.clean(child_id, app.id)
|
||||
|
||||
result.append(RoleRelation.create(parent_id=parent_id, child_id=child_id, app_id=app_id).to_dict())
|
||||
|
||||
AuditCRUD.add_role_log(app_id, AuditOperateType.role_relation_add,
|
||||
AuditScope.role_relation, role.id, {}, {},
|
||||
|
@@ -194,7 +194,7 @@ def validate(ticket):
|
||||
|
||||
def _parse_tag(string, tag):
|
||||
"""
|
||||
Used for parsing xml. Search string for the first occurence of
|
||||
Used for parsing xml. Search string for the first occurrence of
|
||||
<tag>.....</tag> and return text (stripped of leading and tailing
|
||||
whitespace) between tags. Return "" if tag not found.
|
||||
"""
|
||||
|
@@ -1,19 +1,15 @@
|
||||
import json
|
||||
import os
|
||||
import secrets
|
||||
import sys
|
||||
from base64 import b64decode, b64encode
|
||||
import threading
|
||||
|
||||
from base64 import b64decode, b64encode
|
||||
from Cryptodome.Protocol.SecretSharing import Shamir
|
||||
from colorama import Back
|
||||
from colorama import Fore
|
||||
from colorama import Style
|
||||
from colorama import init as colorama_init
|
||||
from colorama import Back, Fore, Style, init as colorama_init
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives import padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher
|
||||
from cryptography.hazmat.primitives.ciphers import algorithms
|
||||
from cryptography.hazmat.primitives.ciphers import modes
|
||||
from cryptography.hazmat.primitives import hashes, padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
from flask import current_app
|
||||
@@ -27,11 +23,16 @@ backend_encrypt_key_name = "encrypt_key"
|
||||
backend_root_key_salt_name = "root_key_salt"
|
||||
backend_encrypt_key_salt_name = "encrypt_key_salt"
|
||||
backend_seal_key = "seal_status"
|
||||
|
||||
success = "success"
|
||||
seal_status = True
|
||||
|
||||
secrets_encrypt_key = ""
|
||||
secrets_root_key = ""
|
||||
|
||||
def string_to_bytes(value):
|
||||
if not value:
|
||||
return ""
|
||||
if isinstance(value, bytes):
|
||||
return value
|
||||
if sys.version_info.major == 2:
|
||||
@@ -44,6 +45,8 @@ def string_to_bytes(value):
|
||||
class Backend:
|
||||
def __init__(self, backend=None):
|
||||
self.backend = backend
|
||||
# cache is a redis object
|
||||
self.cache = backend.cache
|
||||
|
||||
def get(self, key):
|
||||
return self.backend.get(key)
|
||||
@@ -54,23 +57,33 @@ class Backend:
|
||||
def update(self, key, value):
|
||||
return self.backend.update(key, value)
|
||||
|
||||
def get_shares(self, key):
|
||||
return self.backend.get_shares(key)
|
||||
|
||||
def set_shares(self, key, value):
|
||||
return self.backend.set_shares(key, value)
|
||||
|
||||
|
||||
class KeyManage:
|
||||
|
||||
def __init__(self, trigger=None, backend=None):
|
||||
self.trigger = trigger
|
||||
self.backend = backend
|
||||
self.share_key = "cmdb::secret::secrets_share"
|
||||
if backend:
|
||||
self.backend = Backend(backend)
|
||||
|
||||
def init_app(self, app, backend=None):
|
||||
if (sys.argv[0].endswith("gunicorn") or
|
||||
(len(sys.argv) > 1 and sys.argv[1] in ("run", "cmdb-password-data-migrate"))):
|
||||
|
||||
self.backend = backend
|
||||
threading.Thread(target=self.watch_root_key, args=(app,)).start()
|
||||
|
||||
self.trigger = app.config.get("INNER_TRIGGER_TOKEN")
|
||||
if not self.trigger:
|
||||
return
|
||||
|
||||
self.backend = backend
|
||||
resp = self.auto_unseal()
|
||||
self.print_response(resp)
|
||||
|
||||
@@ -124,6 +137,8 @@ class KeyManage:
|
||||
return new_shares
|
||||
|
||||
def is_valid_root_key(self, root_key):
|
||||
if not root_key:
|
||||
return False
|
||||
root_key_hash, ok = self.hash_root_key(root_key)
|
||||
if not ok:
|
||||
return root_key_hash, ok
|
||||
@@ -135,35 +150,42 @@ class KeyManage:
|
||||
else:
|
||||
return "", True
|
||||
|
||||
def auth_root_secret(self, root_key):
|
||||
msg, ok = self.is_valid_root_key(root_key)
|
||||
if not ok:
|
||||
return {
|
||||
"message": msg,
|
||||
"status": "failed"
|
||||
}
|
||||
def auth_root_secret(self, root_key, app):
|
||||
with app.app_context():
|
||||
msg, ok = self.is_valid_root_key(root_key)
|
||||
if not ok:
|
||||
return {
|
||||
"message": msg,
|
||||
"status": "failed"
|
||||
}
|
||||
|
||||
encrypt_key_aes = self.backend.get(backend_encrypt_key_name)
|
||||
if not encrypt_key_aes:
|
||||
return {
|
||||
"message": "encrypt key is empty",
|
||||
"status": "failed"
|
||||
}
|
||||
encrypt_key_aes = self.backend.get(backend_encrypt_key_name)
|
||||
if not encrypt_key_aes:
|
||||
return {
|
||||
"message": "encrypt key is empty",
|
||||
"status": "failed"
|
||||
}
|
||||
|
||||
secrets_encrypt_key, ok = InnerCrypt.aes_decrypt(string_to_bytes(root_key), encrypt_key_aes)
|
||||
if ok:
|
||||
msg, ok = self.backend.update(backend_seal_key, "open")
|
||||
secret_encrypt_key, ok = InnerCrypt.aes_decrypt(string_to_bytes(root_key), encrypt_key_aes)
|
||||
if ok:
|
||||
current_app.config["secrets_encrypt_key"] = secrets_encrypt_key
|
||||
current_app.config["secrets_root_key"] = root_key
|
||||
current_app.config["secrets_shares"] = []
|
||||
return {"message": success, "status": success}
|
||||
return {"message": msg, "status": "failed"}
|
||||
else:
|
||||
return {
|
||||
"message": secrets_encrypt_key,
|
||||
"status": "failed"
|
||||
}
|
||||
msg, ok = self.backend.update(backend_seal_key, "open")
|
||||
if ok:
|
||||
global secrets_encrypt_key, secrets_root_key
|
||||
secrets_encrypt_key = secret_encrypt_key
|
||||
secrets_root_key = root_key
|
||||
self.backend.cache.set(self.share_key, json.dumps([]))
|
||||
return {"message": success, "status": success}
|
||||
return {"message": msg, "status": "failed"}
|
||||
else:
|
||||
return {
|
||||
"message": secret_encrypt_key,
|
||||
"status": "failed"
|
||||
}
|
||||
|
||||
def parse_shares(self, shares, app):
|
||||
if len(shares) >= global_key_threshold:
|
||||
recovered_secret = Shamir.combine(shares[:global_key_threshold], False)
|
||||
return self.auth_root_secret(b64encode(recovered_secret), app)
|
||||
|
||||
def unseal(self, key):
|
||||
if not self.is_seal():
|
||||
@@ -175,14 +197,12 @@ class KeyManage:
|
||||
try:
|
||||
t = [i for i in b64decode(key)]
|
||||
v = (int("".join([chr(i) for i in t[-2:]])), bytes(t[:-2]))
|
||||
shares = current_app.config.get("secrets_shares", [])
|
||||
shares = self.backend.get_shares(self.share_key)
|
||||
if v not in shares:
|
||||
shares.append(v)
|
||||
current_app.config["secrets_shares"] = shares
|
||||
|
||||
self.set_shares(shares)
|
||||
if len(shares) >= global_key_threshold:
|
||||
recovered_secret = Shamir.combine(shares[:global_key_threshold], False)
|
||||
return self.auth_root_secret(b64encode(recovered_secret))
|
||||
return self.parse_shares(shares, current_app)
|
||||
else:
|
||||
return {
|
||||
"message": "waiting for inputting other unseal key {0}/{1}".format(len(shares),
|
||||
@@ -242,8 +262,11 @@ class KeyManage:
|
||||
msg, ok = self.backend.add(backend_seal_key, "open")
|
||||
if not ok:
|
||||
return {"message": msg, "status": "failed"}, False
|
||||
current_app.config["secrets_root_key"] = root_key
|
||||
current_app.config["secrets_encrypt_key"] = encrypt_key
|
||||
|
||||
global secrets_encrypt_key, secrets_root_key
|
||||
secrets_encrypt_key = encrypt_key
|
||||
secrets_root_key = root_key
|
||||
|
||||
self.print_token(shares, root_token=root_key)
|
||||
|
||||
return {"message": "OK",
|
||||
@@ -266,7 +289,7 @@ class KeyManage:
|
||||
}
|
||||
# TODO
|
||||
elif len(self.trigger.strip()) == 24:
|
||||
res = self.auth_root_secret(self.trigger.encode())
|
||||
res = self.auth_root_secret(self.trigger.encode(), current_app)
|
||||
if res.get("status") == success:
|
||||
return {
|
||||
"message": success,
|
||||
@@ -298,22 +321,31 @@ class KeyManage:
|
||||
"message": msg,
|
||||
"status": "failed",
|
||||
}
|
||||
current_app.config["secrets_root_key"] = ''
|
||||
current_app.config["secrets_encrypt_key"] = ''
|
||||
self.clear()
|
||||
self.backend.cache.publish(self.share_key, "clear")
|
||||
|
||||
return {
|
||||
"message": success,
|
||||
"status": success
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def clear():
|
||||
global secrets_encrypt_key, secrets_root_key
|
||||
secrets_encrypt_key = ''
|
||||
secrets_root_key = ''
|
||||
|
||||
def is_seal(self):
|
||||
"""
|
||||
If there is no initialization or the root key is inconsistent, it is considered to be in a sealed state.
|
||||
If there is no initialization or the root key is inconsistent, it is considered to be in a sealed state..
|
||||
:return:
|
||||
"""
|
||||
secrets_root_key = current_app.config.get("secrets_root_key")
|
||||
# secrets_root_key = current_app.config.get("secrets_root_key")
|
||||
if not secrets_root_key:
|
||||
return True
|
||||
msg, ok = self.is_valid_root_key(secrets_root_key)
|
||||
if not ok:
|
||||
return true
|
||||
return True
|
||||
status = self.backend.get(backend_seal_key)
|
||||
return status == "block"
|
||||
|
||||
@@ -349,22 +381,53 @@ class KeyManage:
|
||||
}
|
||||
print(status_colors.get(status, Fore.GREEN), message, Style.RESET_ALL)
|
||||
|
||||
def set_shares(self, values):
|
||||
new_value = list()
|
||||
for v in values:
|
||||
new_value.append((v[0], b64encode(v[1]).decode("utf-8")))
|
||||
self.backend.cache.publish(self.share_key, json.dumps(new_value))
|
||||
self.backend.cache.set(self.share_key, json.dumps(new_value))
|
||||
|
||||
def watch_root_key(self, app):
|
||||
pubsub = self.backend.cache.pubsub()
|
||||
pubsub.subscribe(self.share_key)
|
||||
|
||||
new_value = set()
|
||||
for message in pubsub.listen():
|
||||
if message["type"] == "message":
|
||||
if message["data"] == b"clear":
|
||||
self.clear()
|
||||
continue
|
||||
try:
|
||||
value = json.loads(message["data"].decode("utf-8"))
|
||||
for v in value:
|
||||
new_value.add((v[0], b64decode(v[1])))
|
||||
except Exception as e:
|
||||
return []
|
||||
if len(new_value) >= global_key_threshold:
|
||||
self.parse_shares(list(new_value), app)
|
||||
new_value = set()
|
||||
|
||||
|
||||
class InnerCrypt:
|
||||
def __init__(self):
|
||||
secrets_encrypt_key = current_app.config.get("secrets_encrypt_key", "")
|
||||
self.encrypt_key = b64decode(secrets_encrypt_key.encode("utf-8"))
|
||||
self.encrypt_key = b64decode(secrets_encrypt_key)
|
||||
#self.encrypt_key = b64decode(secrets_encrypt_key, "".encode("utf-8"))
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
"""
|
||||
encrypt method contain aes currently
|
||||
"""
|
||||
if not self.encrypt_key:
|
||||
return ValueError("secret is disabled, please seal firstly"), False
|
||||
return self.aes_encrypt(self.encrypt_key, plaintext)
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
"""
|
||||
decrypt method contain aes currently
|
||||
"""
|
||||
if not self.encrypt_key:
|
||||
return ValueError("secret is disabled, please seal firstly"), False
|
||||
return self.aes_decrypt(self.encrypt_key, ciphertext)
|
||||
|
||||
@classmethod
|
||||
@@ -381,6 +444,7 @@ class InnerCrypt:
|
||||
|
||||
return b64encode(iv + ciphertext).decode("utf-8"), True
|
||||
except Exception as e:
|
||||
|
||||
return str(e), False
|
||||
|
||||
@classmethod
|
||||
@@ -426,4 +490,4 @@ if __name__ == "__main__":
|
||||
t_ciphertext, status1 = c.encrypt(t_plaintext)
|
||||
print("Ciphertext:", t_ciphertext)
|
||||
decrypted_plaintext, status2 = c.decrypt(t_ciphertext)
|
||||
print("Decrypted plaintext:", decrypted_plaintext)
|
||||
print("Decrypted plaintext:", decrypted_plaintext)
|
@@ -1,8 +1,13 @@
|
||||
import base64
|
||||
import json
|
||||
|
||||
from api.models.cmdb import InnerKV
|
||||
from api.extensions import rd
|
||||
|
||||
|
||||
class InnerKVManger(object):
|
||||
def __init__(self):
|
||||
self.cache = rd.r
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@@ -33,3 +38,26 @@ class InnerKVManger(object):
|
||||
return "success", True
|
||||
|
||||
return "update failed", True
|
||||
|
||||
@classmethod
|
||||
def get_shares(cls, key):
|
||||
new_value = list()
|
||||
v = rd.get_str(key)
|
||||
if not v:
|
||||
return new_value
|
||||
try:
|
||||
value = json.loads(v.decode("utf-8"))
|
||||
for v in value:
|
||||
new_value.append((v[0], base64.b64decode(v[1])))
|
||||
except Exception as e:
|
||||
return []
|
||||
return new_value
|
||||
|
||||
@classmethod
|
||||
def set_shares(cls, key, value):
|
||||
new_value = list()
|
||||
for v in value:
|
||||
new_value.append((v[0], base64.b64encode(v[1]).decode("utf-8")))
|
||||
rd.set_str(key, json.dumps(new_value))
|
||||
|
||||
|
||||
|
@@ -1,8 +1,6 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import base64
|
||||
import sys
|
||||
import time
|
||||
from typing import Set
|
||||
|
||||
import elasticsearch
|
||||
@@ -119,6 +117,23 @@ class RedisHandler(object):
|
||||
except Exception as e:
|
||||
current_app.logger.error("delete redis key error, {0}".format(str(e)))
|
||||
|
||||
def set_str(self, key, value, expired=None):
|
||||
try:
|
||||
if expired:
|
||||
self.r.setex(key, expired, value)
|
||||
else:
|
||||
self.r.set(key, value)
|
||||
except Exception as e:
|
||||
current_app.logger.error("set redis error, {0}".format(str(e)))
|
||||
|
||||
def get_str(self, key):
|
||||
try:
|
||||
value = self.r.get(key)
|
||||
except Exception as e:
|
||||
current_app.logger.error("get redis error, {0}".format(str(e)))
|
||||
return
|
||||
return value
|
||||
|
||||
|
||||
class ESHandler(object):
|
||||
def __init__(self, flask_app=None):
|
||||
@@ -213,52 +228,6 @@ class ESHandler(object):
|
||||
return 0, [], {}
|
||||
|
||||
|
||||
class Lock(object):
|
||||
def __init__(self, name, timeout=10, app=None, need_lock=True):
|
||||
self.lock_key = name
|
||||
self.need_lock = need_lock
|
||||
self.timeout = timeout
|
||||
if not app:
|
||||
app = current_app
|
||||
self.app = app
|
||||
try:
|
||||
self.redis = redis.Redis(host=self.app.config.get('CACHE_REDIS_HOST'),
|
||||
port=self.app.config.get('CACHE_REDIS_PORT'),
|
||||
password=self.app.config.get('CACHE_REDIS_PASSWORD'))
|
||||
except:
|
||||
self.app.logger.error("cannot connect redis")
|
||||
raise Exception("cannot connect redis")
|
||||
|
||||
def lock(self, timeout=None):
|
||||
if not timeout:
|
||||
timeout = self.timeout
|
||||
retry = 0
|
||||
while retry < 100:
|
||||
timestamp = time.time() + timeout + 1
|
||||
_lock = self.redis.setnx(self.lock_key, timestamp)
|
||||
if _lock == 1 or (
|
||||
time.time() > float(self.redis.get(self.lock_key) or sys.maxsize) and
|
||||
time.time() > float(self.redis.getset(self.lock_key, timestamp) or sys.maxsize)):
|
||||
break
|
||||
else:
|
||||
retry += 1
|
||||
time.sleep(0.6)
|
||||
if retry >= 100:
|
||||
raise Exception("get lock failed...")
|
||||
|
||||
def release(self):
|
||||
if time.time() < float(self.redis.get(self.lock_key)):
|
||||
self.redis.delete(self.lock_key)
|
||||
|
||||
def __enter__(self):
|
||||
if self.need_lock:
|
||||
self.lock()
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.need_lock:
|
||||
self.release()
|
||||
|
||||
|
||||
class AESCrypto(object):
|
||||
BLOCK_SIZE = 16 # Bytes
|
||||
pad = lambda s: s + ((AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE) *
|
||||
|
@@ -356,7 +356,7 @@ class AuditLoginLog(Model2):
|
||||
__tablename__ = "acl_audit_login_logs"
|
||||
|
||||
username = db.Column(db.String(64), index=True)
|
||||
channel = db.Column(db.Enum('web', 'api'), default="web")
|
||||
channel = db.Column(db.Enum('web', 'api', 'ssh'), default="web")
|
||||
ip = db.Column(db.String(15))
|
||||
browser = db.Column(db.String(256))
|
||||
description = db.Column(db.String(128))
|
||||
|
@@ -46,17 +46,31 @@ class CIType(Model):
|
||||
name = db.Column(db.String(32), nullable=False)
|
||||
alias = db.Column(db.String(32), nullable=False)
|
||||
unique_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"), nullable=False)
|
||||
show_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
|
||||
enabled = db.Column(db.Boolean, default=True, nullable=False)
|
||||
is_attached = db.Column(db.Boolean, default=False, nullable=False)
|
||||
icon = db.Column(db.Text)
|
||||
order = db.Column(db.SmallInteger, default=0, nullable=False)
|
||||
default_order_attr = db.Column(db.String(33))
|
||||
|
||||
unique_key = db.relationship("Attribute", backref="c_ci_types.unique_id")
|
||||
unique_key = db.relationship("Attribute", backref="c_ci_types.unique_id",
|
||||
primaryjoin="Attribute.id==CIType.unique_id", foreign_keys=[unique_id])
|
||||
show_key = db.relationship("Attribute", backref="c_ci_types.show_id",
|
||||
primaryjoin="Attribute.id==CIType.show_id", foreign_keys=[show_id])
|
||||
|
||||
uid = db.Column(db.Integer, index=True)
|
||||
|
||||
|
||||
class CITypeInheritance(Model):
|
||||
__tablename__ = "c_ci_type_inheritance"
|
||||
|
||||
parent_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||
child_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||
|
||||
parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeInheritance.parent_id")
|
||||
child = db.relationship("CIType", primaryjoin="CIType.id==CITypeInheritance.child_id")
|
||||
|
||||
|
||||
class CITypeRelation(Model):
|
||||
__tablename__ = "c_ci_type_relations"
|
||||
|
||||
@@ -65,6 +79,9 @@ class CITypeRelation(Model):
|
||||
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)
|
||||
|
||||
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")
|
||||
child = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.child_id")
|
||||
relation_type = db.relationship("RelationType", backref="c_ci_type_relations.relation_type_id")
|
||||
@@ -94,6 +111,8 @@ class Attribute(Model):
|
||||
_choice_web_hook = db.Column('choice_web_hook', db.JSON)
|
||||
choice_other = db.Column(db.JSON)
|
||||
|
||||
re_check = db.Column(db.Text)
|
||||
|
||||
uid = db.Column(db.Integer, index=True)
|
||||
|
||||
option = db.Column(db.JSON)
|
||||
@@ -413,6 +432,7 @@ class CITypeHistory(Model):
|
||||
|
||||
attr_id = db.Column(db.Integer)
|
||||
trigger_id = db.Column(db.Integer)
|
||||
rc_id = db.Column(db.Integer)
|
||||
unique_constraint_id = db.Column(db.Integer)
|
||||
|
||||
uid = db.Column(db.Integer, index=True)
|
||||
@@ -448,6 +468,7 @@ class PreferenceRelationView(Model):
|
||||
name = db.Column(db.String(64), index=True, nullable=False)
|
||||
cr_ids = db.Column(db.JSON) # [{parent_id: x, child_id: y}]
|
||||
is_public = db.Column(db.Boolean, default=False)
|
||||
option = db.Column(db.JSON)
|
||||
|
||||
|
||||
class PreferenceSearchOption(Model):
|
||||
@@ -464,6 +485,15 @@ class PreferenceSearchOption(Model):
|
||||
option = db.Column(db.JSON)
|
||||
|
||||
|
||||
class PreferenceCITypeOrder(Model):
|
||||
__tablename__ = "c_pcto"
|
||||
|
||||
uid = db.Column(db.Integer, index=True, nullable=False)
|
||||
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'))
|
||||
order = db.Column(db.SmallInteger, default=0)
|
||||
is_tree = db.Column(db.Boolean, default=False) # True is tree view, False is resource view
|
||||
|
||||
|
||||
# custom
|
||||
class CustomDashboard(Model):
|
||||
__tablename__ = "c_c_d"
|
||||
@@ -548,6 +578,7 @@ class CIFilterPerms(Model):
|
||||
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'))
|
||||
ci_filter = db.Column(db.Text)
|
||||
attr_filter = db.Column(db.Text)
|
||||
id_filter = db.Column(db.JSON) # {node_path: unique_value}
|
||||
|
||||
rid = db.Column(db.Integer, index=True)
|
||||
|
||||
|
@@ -3,12 +3,13 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
from celery_once import QueueOnce
|
||||
import redis_lock
|
||||
from flask import current_app
|
||||
from werkzeug.exceptions import BadRequest
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from api.extensions import celery
|
||||
from api.extensions import rd
|
||||
from api.lib.decorator import flush_db
|
||||
from api.lib.decorator import reconnect_db
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
@@ -25,14 +26,14 @@ from api.models.acl import Role
|
||||
from api.models.acl import Trigger
|
||||
|
||||
|
||||
@celery.task(name="acl.role_rebuild",
|
||||
queue=ACL_QUEUE,)
|
||||
@celery.task(name="acl.role_rebuild", queue=ACL_QUEUE, )
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def role_rebuild(rids, app_id):
|
||||
rids = rids if isinstance(rids, list) else [rids]
|
||||
for rid in rids:
|
||||
RoleRelationCache.rebuild(rid, app_id)
|
||||
with redis_lock.Lock(rd.r, "ROLE_REBUILD_{}_{}".format(rid, app_id)):
|
||||
RoleRelationCache.rebuild(rid, app_id)
|
||||
|
||||
current_app.logger.info("Role {0} App {1} rebuild..........".format(rids, app_id))
|
||||
|
||||
|
@@ -2,8 +2,8 @@
|
||||
|
||||
|
||||
import json
|
||||
import time
|
||||
|
||||
import redis_lock
|
||||
from flask import current_app
|
||||
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_RELATION
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.decorator import flush_db
|
||||
from api.lib.decorator import reconnect_db
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.utils import Lock
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
@@ -32,8 +32,7 @@ from api.models.cmdb import CITypeAttribute
|
||||
@reconnect_db
|
||||
def ci_cache(ci_id, operate_type, record_id):
|
||||
from api.lib.cmdb.ci import CITriggerManager
|
||||
|
||||
time.sleep(0.01)
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
|
||||
m = api.lib.cmdb.ci.CIManager()
|
||||
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)
|
||||
|
||||
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)
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def batch_ci_cache(ci_ids, ): # only for attribute change index
|
||||
time.sleep(1)
|
||||
|
||||
for ci_id in ci_ids:
|
||||
m = api.lib.cmdb.ci.CIManager()
|
||||
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))
|
||||
|
||||
|
||||
@celery.task(name="cmdb.delete_id_filter", queue=CMDB_QUEUE)
|
||||
@reconnect_db
|
||||
def delete_id_filter(ci_id):
|
||||
CIFilterPermsCRUD().delete_id_filter_by_ci_id(ci_id)
|
||||
|
||||
|
||||
@celery.task(name="cmdb.ci_delete_trigger", queue=CMDB_QUEUE)
|
||||
@reconnect_db
|
||||
def ci_delete_trigger(trigger, operate_type, ci_dict):
|
||||
@@ -99,7 +112,7 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
||||
with Lock("CIRelation_{}".format(parent_id)):
|
||||
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
||||
if ancestor_ids is None:
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
@@ -177,7 +190,7 @@ def ci_relation_add(parent_dict, child_id, uid):
|
||||
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
|
||||
@reconnect_db
|
||||
def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
||||
with Lock("CIRelation_{}".format(parent_id)):
|
||||
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
||||
if ancestor_ids is None:
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
|
@@ -3,14 +3,14 @@ from flask import current_app
|
||||
|
||||
from api.extensions import celery
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.models.common_setting import Department, Employee
|
||||
from api.lib.decorator import flush_db
|
||||
from api.lib.decorator import reconnect_db
|
||||
|
||||
|
||||
@celery.task(name="common_setting.edit_employee_department_in_acl", queue=CMDB_QUEUE)
|
||||
@celery.task(name="common_setting.edit_employee_department_in_acl", queue=ACL_QUEUE)
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
||||
@@ -49,21 +49,20 @@ def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
||||
continue
|
||||
|
||||
old_d_rid_in_acl = role_map.get(old_department.department_name, 0)
|
||||
if old_d_rid_in_acl == 0:
|
||||
return
|
||||
if old_d_rid_in_acl != 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(ErrFormat.acl_remove_user_from_role_failed.format(str(e)))
|
||||
if old_d_rid_in_acl > 0:
|
||||
if old_d_rid_in_acl != old_department.acl_rid:
|
||||
old_department.update(
|
||||
acl_rid=old_d_rid_in_acl
|
||||
)
|
||||
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(ErrFormat.acl_remove_user_from_role_failed.format(str(e)))
|
||||
|
||||
payload = {
|
||||
'app_id': 'acl',
|
||||
@@ -77,10 +76,10 @@ def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
||||
return result
|
||||
|
||||
|
||||
@celery.task(name="common_setting.refresh_employee_acl_info", queue=CMDB_QUEUE)
|
||||
@celery.task(name="common_setting.refresh_employee_acl_info", queue=ACL_QUEUE)
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def refresh_employee_acl_info():
|
||||
def refresh_employee_acl_info(current_employee_id=None):
|
||||
acl = ACLManager('acl')
|
||||
role_map = {role['name']: role for role in acl.get_all_roles()}
|
||||
|
||||
@@ -90,8 +89,12 @@ def refresh_employee_acl_info():
|
||||
query = Employee.query.filter(*criterion).order_by(
|
||||
Employee.created_at.desc()
|
||||
)
|
||||
current_employee_rid = 0
|
||||
|
||||
for em in query.all():
|
||||
if current_employee_id and em.employee_id == current_employee_id:
|
||||
current_employee_rid = em.acl_rid if em.acl_rid else 0
|
||||
|
||||
if em.acl_uid and em.acl_rid:
|
||||
continue
|
||||
role = role_map.get(em.username, None)
|
||||
@@ -105,6 +108,9 @@ def refresh_employee_acl_info():
|
||||
if not em.acl_rid:
|
||||
params['acl_rid'] = role.get('id', 0)
|
||||
|
||||
if current_employee_id and em.employee_id == current_employee_id:
|
||||
current_employee_rid = params['acl_rid'] if params.get('acl_rid', 0) else 0
|
||||
|
||||
try:
|
||||
em.update(**params)
|
||||
current_app.logger.info(
|
||||
@@ -113,3 +119,12 @@ def refresh_employee_acl_info():
|
||||
except Exception as e:
|
||||
current_app.logger.error(str(e))
|
||||
continue
|
||||
|
||||
if current_employee_rid and current_employee_rid > 0:
|
||||
try:
|
||||
from api.lib.common_setting.employee import GrantEmployeeACLPerm
|
||||
|
||||
GrantEmployeeACLPerm().grant_by_rid(current_employee_rid, False)
|
||||
current_app.logger.info(f"GrantEmployeeACLPerm success, current_employee_rid: {current_employee_rid}")
|
||||
except Exception as e:
|
||||
current_app.logger.error(str(e))
|
||||
|
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2024-01-03 11:39+0800\n"
|
||||
"POT-Creation-Date: 2024-03-29 10:42+0800\n"
|
||||
"PO-Revision-Date: 2023-12-25 20:21+0800\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: zh\n"
|
||||
@@ -234,205 +234,213 @@ msgstr "只有创建人才能删除它!"
|
||||
msgid "The model cannot be deleted because the CI already exists"
|
||||
msgstr "因为CI已经存在,不能删除模型"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:65
|
||||
#: api/lib/cmdb/resp_format.py:63
|
||||
msgid "The inheritance cannot be deleted because the CI already exists"
|
||||
msgstr "因为CI已经存在,不能删除继承关系"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:67
|
||||
msgid ""
|
||||
"The model cannot be deleted because the model is referenced by the "
|
||||
"relational view {}"
|
||||
msgstr "因为关系视图 {} 引用了该模型,不能删除模型"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:67
|
||||
#: api/lib/cmdb/resp_format.py:69
|
||||
msgid "Model group {} does not exist"
|
||||
msgstr "模型分组 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:68
|
||||
#: api/lib/cmdb/resp_format.py:70
|
||||
msgid "Model group {} already exists"
|
||||
msgstr "模型分组 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:69
|
||||
#: api/lib/cmdb/resp_format.py:71
|
||||
msgid "Model relationship {} does not exist"
|
||||
msgstr "模型关系 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:70
|
||||
#: api/lib/cmdb/resp_format.py:72
|
||||
msgid "Attribute group {} already exists"
|
||||
msgstr "属性分组 {} 已存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:71
|
||||
#: api/lib/cmdb/resp_format.py:73
|
||||
msgid "Attribute group {} does not exist"
|
||||
msgstr "属性分组 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:73
|
||||
#: api/lib/cmdb/resp_format.py:75
|
||||
msgid "Attribute group <{0}> - attribute <{1}> does not exist"
|
||||
msgstr "属性组<{0}> - 属性<{1}> 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:74
|
||||
#: api/lib/cmdb/resp_format.py:76
|
||||
msgid "The unique constraint already exists!"
|
||||
msgstr "唯一约束已经存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:76
|
||||
#: api/lib/cmdb/resp_format.py:78
|
||||
msgid "Uniquely constrained attributes cannot be JSON and multi-valued"
|
||||
msgstr "唯一约束的属性不能是 JSON 和 多值"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:77
|
||||
#: api/lib/cmdb/resp_format.py:79
|
||||
msgid "Duplicated trigger"
|
||||
msgstr "重复的触发器"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:78
|
||||
#: api/lib/cmdb/resp_format.py:80
|
||||
msgid "Trigger {} does not exist"
|
||||
msgstr "触发器 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:80
|
||||
#: api/lib/cmdb/resp_format.py:82
|
||||
msgid "Operation record {} does not exist"
|
||||
msgstr "操作记录 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:81
|
||||
#: api/lib/cmdb/resp_format.py:83
|
||||
msgid "Unique identifier cannot be deleted"
|
||||
msgstr "不能删除唯一标识"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:82
|
||||
#: api/lib/cmdb/resp_format.py:84
|
||||
msgid "Cannot delete default sorted attributes"
|
||||
msgstr "不能删除默认排序的属性"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:84
|
||||
#: api/lib/cmdb/resp_format.py:86
|
||||
msgid "No node selected"
|
||||
msgstr "没有选择节点"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:85
|
||||
#: api/lib/cmdb/resp_format.py:87
|
||||
msgid "This search option does not exist!"
|
||||
msgstr "该搜索选项不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:86
|
||||
#: api/lib/cmdb/resp_format.py:88
|
||||
msgid "This search option has a duplicate name!"
|
||||
msgstr "该搜索选项命名重复!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:88
|
||||
#: api/lib/cmdb/resp_format.py:90
|
||||
msgid "Relationship type {} already exists"
|
||||
msgstr "关系类型 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:89
|
||||
#: api/lib/cmdb/resp_format.py:91
|
||||
msgid "Relationship type {} does not exist"
|
||||
msgstr "关系类型 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:91
|
||||
#: api/lib/cmdb/resp_format.py:93
|
||||
msgid "Invalid attribute value: {}"
|
||||
msgstr "无效的属性值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:92
|
||||
#: api/lib/cmdb/resp_format.py:94
|
||||
msgid "{} Invalid value: {}"
|
||||
msgstr "无效的值: {}"
|
||||
msgstr "{} 无效的值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:93
|
||||
#: api/lib/cmdb/resp_format.py:95
|
||||
msgid "{} is not in the predefined values"
|
||||
msgstr "{} 不在预定义值里"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:95
|
||||
#: api/lib/cmdb/resp_format.py:97
|
||||
msgid "The value of attribute {} must be unique, {} already exists"
|
||||
msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:96
|
||||
#: api/lib/cmdb/resp_format.py:98
|
||||
msgid "Attribute {} value must exist"
|
||||
msgstr "属性 {} 值必须存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:99
|
||||
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: {}"
|
||||
msgstr "新增或者修改属性值未知错误: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:101
|
||||
#: api/lib/cmdb/resp_format.py:103
|
||||
msgid "Duplicate custom name"
|
||||
msgstr "订制名重复"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:103
|
||||
#: api/lib/cmdb/resp_format.py:105
|
||||
msgid "Number of models exceeds limit: {}"
|
||||
msgstr "模型数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:104
|
||||
#: api/lib/cmdb/resp_format.py:106
|
||||
msgid "The number of CIs exceeds the limit: {}"
|
||||
msgstr "CI数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:106
|
||||
#: api/lib/cmdb/resp_format.py:108
|
||||
msgid "Auto-discovery rule: {} already exists!"
|
||||
msgstr "自动发现规则: {} 已经存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:107
|
||||
#: api/lib/cmdb/resp_format.py:109
|
||||
msgid "Auto-discovery rule: {} does not exist!"
|
||||
msgstr "自动发现规则: {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:109
|
||||
#: api/lib/cmdb/resp_format.py:111
|
||||
msgid "This auto-discovery rule is referenced by the model and cannot be deleted!"
|
||||
msgstr "该自动发现规则被模型引用, 不能删除!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:111
|
||||
#: api/lib/cmdb/resp_format.py:113
|
||||
msgid "The application of auto-discovery rules cannot be defined repeatedly!"
|
||||
msgstr "自动发现规则的应用不能重复定义!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:112
|
||||
#: api/lib/cmdb/resp_format.py:114
|
||||
msgid "The auto-discovery you want to modify: {} does not exist!"
|
||||
msgstr "您要修改的自动发现: {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:113
|
||||
#: api/lib/cmdb/resp_format.py:115
|
||||
msgid "Attribute does not include unique identifier: {}"
|
||||
msgstr "属性字段没有包括唯一标识: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:114
|
||||
#: api/lib/cmdb/resp_format.py:116
|
||||
msgid "The auto-discovery instance does not exist!"
|
||||
msgstr "自动发现的实例不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:115
|
||||
#: api/lib/cmdb/resp_format.py:117
|
||||
msgid "The model is not associated with this auto-discovery!"
|
||||
msgstr "模型并未关联该自动发现!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:116
|
||||
#: api/lib/cmdb/resp_format.py:118
|
||||
msgid "Only the creator can modify the Secret!"
|
||||
msgstr "只有创建人才能修改Secret!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:118
|
||||
#: api/lib/cmdb/resp_format.py:120
|
||||
msgid "This rule already has auto-discovery instances and cannot be deleted!"
|
||||
msgstr "该规则已经有自动发现的实例, 不能被删除!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:120
|
||||
#: api/lib/cmdb/resp_format.py:122
|
||||
msgid "The default auto-discovery rule is already referenced by model {}!"
|
||||
msgstr "该默认的自动发现规则 已经被模型 {} 引用!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:122
|
||||
#: api/lib/cmdb/resp_format.py:124
|
||||
msgid "The unique_key method must return a non-empty string!"
|
||||
msgstr "unique_key方法必须返回非空字符串!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:123
|
||||
#: api/lib/cmdb/resp_format.py:125
|
||||
msgid "The attributes method must return a list"
|
||||
msgstr "attributes方法必须返回的是list"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:125
|
||||
#: api/lib/cmdb/resp_format.py:127
|
||||
msgid "The list returned by the attributes method cannot be empty!"
|
||||
msgstr "attributes方法返回的list不能为空!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:127
|
||||
#: api/lib/cmdb/resp_format.py:129
|
||||
msgid "Only administrators can define execution targets as: all nodes!"
|
||||
msgstr "只有管理员才可以定义执行机器为: 所有节点!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:128
|
||||
#: api/lib/cmdb/resp_format.py:130
|
||||
msgid "Execute targets permission check failed: {}"
|
||||
msgstr "执行机器权限检查不通过: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:130
|
||||
#: api/lib/cmdb/resp_format.py:132
|
||||
msgid "CI filter authorization must be named!"
|
||||
msgstr "CI过滤授权 必须命名!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:131
|
||||
#: api/lib/cmdb/resp_format.py:133
|
||||
msgid "CI filter authorization is currently not supported or query"
|
||||
msgstr "CI过滤授权 暂时不支持 或 查询"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:134
|
||||
#: api/lib/cmdb/resp_format.py:136
|
||||
msgid "You do not have permission to operate attribute {}!"
|
||||
msgstr "您没有属性 {} 的操作权限!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:135
|
||||
#: api/lib/cmdb/resp_format.py:137
|
||||
msgid "You do not have permission to operate this CI!"
|
||||
msgstr "您没有该CI的操作权限!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:137
|
||||
#: api/lib/cmdb/resp_format.py:139
|
||||
msgid "Failed to save password: {}"
|
||||
msgstr "保存密码失败: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:138
|
||||
#: api/lib/cmdb/resp_format.py:140
|
||||
msgid "Failed to get password: {}"
|
||||
msgstr "获取密码失败: {}"
|
||||
|
||||
|
@@ -38,8 +38,9 @@ class LoginView(APIView):
|
||||
username = request.values.get("username") or request.values.get("email")
|
||||
password = request.values.get("password")
|
||||
_role = None
|
||||
auth_with_ldap = request.values.get('auth_with_ldap', True)
|
||||
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
|
||||
user, authenticated = authenticate_with_ldap(username, password)
|
||||
else:
|
||||
|
@@ -11,12 +11,12 @@ from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.const import ExistPolicy
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum, PermEnum
|
||||
from api.lib.cmdb.const import RetKey
|
||||
from api.lib.cmdb.perms import has_perm_for_ci
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.utils import get_page
|
||||
from api.lib.utils import get_page_size
|
||||
@@ -77,6 +77,7 @@ class CIView(APIView):
|
||||
@has_perm_for_ci("ci_type", ResourceTypeEnum.CI, PermEnum.ADD, lambda x: CITypeCache.get(x))
|
||||
def post(self):
|
||||
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)
|
||||
|
||||
exist_policy = request.values.pop('exist_policy', None)
|
||||
@@ -88,6 +89,7 @@ class CIView(APIView):
|
||||
exist_policy=exist_policy or ExistPolicy.REJECT,
|
||||
_no_attribute_policy=_no_attribute_policy,
|
||||
_is_admin=request.values.pop('__is_admin', None) or False,
|
||||
ticket_id=ticket_id,
|
||||
**ci_dict)
|
||||
|
||||
return self.jsonify(ci_id=ci_id)
|
||||
@@ -96,6 +98,7 @@ class CIView(APIView):
|
||||
def put(self, ci_id=None):
|
||||
args = request.values
|
||||
ci_type = args.get("ci_type")
|
||||
ticket_id = request.values.pop("ticket_id", None)
|
||||
_no_attribute_policy = args.get("no_attribute_policy", ExistPolicy.IGNORE)
|
||||
|
||||
ci_dict = self._wrap_ci_dict()
|
||||
@@ -103,6 +106,7 @@ class CIView(APIView):
|
||||
if ci_id is not None:
|
||||
manager.update(ci_id,
|
||||
_is_admin=request.values.pop('__is_admin', None) or False,
|
||||
ticket_id=ticket_id,
|
||||
**ci_dict)
|
||||
else:
|
||||
request.values.pop('exist_policy', None)
|
||||
@@ -110,6 +114,7 @@ class CIView(APIView):
|
||||
exist_policy=ExistPolicy.REPLACE,
|
||||
_no_attribute_policy=_no_attribute_policy,
|
||||
_is_admin=request.values.pop('__is_admin', None) or False,
|
||||
ticket_id=ticket_id,
|
||||
**ci_dict)
|
||||
|
||||
return self.jsonify(ci_id=ci_id)
|
||||
@@ -152,9 +157,10 @@ class CISearchView(APIView):
|
||||
ret_key = RetKey.NAME
|
||||
facet = handle_arg_list(request.values.get("facet", ""))
|
||||
sort = request.values.get("sort")
|
||||
use_id_filter = request.values.get("use_id_filter", False) in current_app.config.get('BOOL_TRUE')
|
||||
|
||||
start = time.time()
|
||||
s = search(query, fl, facet, page, ret_key, count, sort, excludes)
|
||||
s = search(query, fl, facet, page, ret_key, count, sort, excludes, use_id_filter=use_id_filter)
|
||||
try:
|
||||
response, counter, total, page, numfound, facet = s.search()
|
||||
except SearchError as e:
|
||||
@@ -221,7 +227,6 @@ class CIHeartbeatView(APIView):
|
||||
class CIFlushView(APIView):
|
||||
url_prefix = ("/ci/flush", "/ci/<int:ci_id>/flush")
|
||||
|
||||
# @auth_abandoned
|
||||
def get(self, ci_id=None):
|
||||
from api.tasks.cmdb import ci_cache
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
@@ -250,3 +255,23 @@ class CIPasswordView(APIView):
|
||||
|
||||
def post(self, ci_id, attr_id):
|
||||
return self.get(ci_id, attr_id)
|
||||
|
||||
|
||||
class CIBaselineView(APIView):
|
||||
url_prefix = ("/ci/baseline", "/ci/<int:ci_id>/baseline/rollback")
|
||||
|
||||
@args_required("before_date")
|
||||
def get(self):
|
||||
ci_ids = handle_arg_list(request.values.get('ci_ids'))
|
||||
before_date = request.values.get('before_date')
|
||||
|
||||
return self.jsonify(CIManager().baseline(list(map(int, ci_ids)), before_date))
|
||||
|
||||
@args_required("before_date")
|
||||
def post(self, ci_id):
|
||||
if 'rollback' in request.url:
|
||||
before_date = request.values.get('before_date')
|
||||
|
||||
return self.jsonify(**CIManager().rollback(ci_id, before_date))
|
||||
|
||||
return self.get(ci_id)
|
||||
|
@@ -13,7 +13,6 @@ from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci_relation.search import Search
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.auth import auth_abandoned
|
||||
from api.lib.utils import get_page
|
||||
from api.lib.utils import get_page_size
|
||||
from api.lib.utils import handle_arg_list
|
||||
@@ -31,11 +30,14 @@ class CIRelationSearchView(APIView):
|
||||
level: default is 1
|
||||
facet: statistic
|
||||
"""
|
||||
|
||||
page = get_page(request.values.get("page", 1))
|
||||
count = get_page_size(request.values.get("count") or request.values.get("page_size"))
|
||||
|
||||
root_id = request.values.get('root_id')
|
||||
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
|
||||
root_parent_path = handle_arg_list(request.values.get('root_parent_path') or '')
|
||||
descendant_ids = list(map(int, handle_arg_list(request.values.get('descendant_ids', []))))
|
||||
level = list(map(int, handle_arg_list(request.values.get('level', '1'))))
|
||||
|
||||
query = request.values.get('q', "")
|
||||
@@ -47,7 +49,8 @@ class CIRelationSearchView(APIView):
|
||||
|
||||
start = time.time()
|
||||
s = Search(root_id, level, query, fl, facet, page, count, sort, reverse,
|
||||
ancestor_ids=ancestor_ids, has_m2m=has_m2m)
|
||||
ancestor_ids=ancestor_ids, has_m2m=has_m2m, root_parent_path=root_parent_path,
|
||||
descendant_ids=descendant_ids)
|
||||
try:
|
||||
response, counter, total, page, numfound, facet = s.search()
|
||||
except SearchError as e:
|
||||
@@ -65,16 +68,16 @@ class CIRelationSearchView(APIView):
|
||||
class CIRelationStatisticsView(APIView):
|
||||
url_prefix = "/ci_relations/statistics"
|
||||
|
||||
@auth_abandoned
|
||||
def get(self):
|
||||
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
|
||||
level = request.values.get('level', 1)
|
||||
type_ids = set(map(int, handle_arg_list(request.values.get('type_ids', []))))
|
||||
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
|
||||
descendant_ids = list(map(int, handle_arg_list(request.values.get('descendant_ids', []))))
|
||||
has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE')
|
||||
|
||||
start = time.time()
|
||||
s = Search(root_ids, level, ancestor_ids=ancestor_ids, has_m2m=has_m2m)
|
||||
s = Search(root_ids, level, ancestor_ids=ancestor_ids, descendant_ids=descendant_ids, has_m2m=has_m2m)
|
||||
try:
|
||||
result = s.statistics(type_ids)
|
||||
except SearchError as e:
|
||||
@@ -84,6 +87,26 @@ class CIRelationStatisticsView(APIView):
|
||||
return self.jsonify(result)
|
||||
|
||||
|
||||
class CIRelationSearchFullView(APIView):
|
||||
url_prefix = "/ci_relations/search/full"
|
||||
|
||||
def get(self):
|
||||
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
|
||||
level = request.values.get('level', 1)
|
||||
type_ids = list(map(int, handle_arg_list(request.values.get('type_ids', []))))
|
||||
has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE')
|
||||
|
||||
start = time.time()
|
||||
s = Search(root_ids, level, has_m2m=has_m2m)
|
||||
try:
|
||||
result = s.search_full(type_ids)
|
||||
except SearchError as e:
|
||||
return abort(400, str(e))
|
||||
current_app.logger.debug("search time is :{0}".format(time.time() - start))
|
||||
|
||||
return self.jsonify(result)
|
||||
|
||||
|
||||
class GetSecondCIsView(APIView):
|
||||
url_prefix = "/ci_relations/<int:first_ci_id>/second_cis"
|
||||
|
||||
|
@@ -7,21 +7,23 @@ from io import BytesIO
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
from flask import session
|
||||
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeGroupManager
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
from api.lib.cmdb.ci_type import CITypeGroupManager
|
||||
from api.lib.cmdb.ci_type import CITypeInheritanceManager
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.ci_type import CITypeTemplateManager
|
||||
from api.lib.cmdb.ci_type import CITypeTriggerManager
|
||||
from api.lib.cmdb.ci_type import CITypeUniqueConstraintManager
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
@@ -35,17 +37,27 @@ from api.lib.perm.auth import auth_with_app_token
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class CITypeView(APIView):
|
||||
url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>")
|
||||
url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>",
|
||||
"/ci_types/icons")
|
||||
|
||||
def get(self, type_id=None, type_name=None):
|
||||
if request.url.endswith("icons"):
|
||||
return self.jsonify(CITypeManager().get_icons())
|
||||
|
||||
q = request.args.get("type_name")
|
||||
|
||||
if type_id is not None:
|
||||
ci_types = [CITypeCache.get(type_id).to_dict()]
|
||||
ci_type = CITypeCache.get(type_id).to_dict()
|
||||
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(type_id)
|
||||
ci_types = [ci_type]
|
||||
elif type_name is not None:
|
||||
ci_types = [CITypeCache.get(type_name).to_dict()]
|
||||
ci_type = CITypeCache.get(type_name).to_dict()
|
||||
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(ci_type['id'])
|
||||
ci_types = [ci_type]
|
||||
else:
|
||||
ci_types = CITypeManager().get_ci_types(q)
|
||||
count = len(ci_types)
|
||||
@@ -53,7 +65,7 @@ class CITypeView(APIView):
|
||||
return self.jsonify(numfound=count, ci_types=ci_types)
|
||||
|
||||
@args_required("name")
|
||||
@args_validate(CITypeManager.cls)
|
||||
@args_validate(CITypeManager.cls, exclude_args=['parent_ids'])
|
||||
def post(self):
|
||||
params = request.values
|
||||
|
||||
@@ -84,10 +96,29 @@ class CITypeView(APIView):
|
||||
return self.jsonify(type_id=type_id)
|
||||
|
||||
|
||||
class CITypeInheritanceView(APIView):
|
||||
url_prefix = ("/ci_types/inheritance",)
|
||||
|
||||
@args_required("parent_ids")
|
||||
@args_required("child_id")
|
||||
@has_perm_from_args("child_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||
def post(self):
|
||||
CITypeInheritanceManager.add(request.values['parent_ids'], request.values['child_id'])
|
||||
|
||||
return self.jsonify(**request.values)
|
||||
|
||||
@args_required("parent_id")
|
||||
@args_required("child_id")
|
||||
@has_perm_from_args("child_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||
def delete(self):
|
||||
CITypeInheritanceManager.delete(request.values['parent_id'], request.values['child_id'])
|
||||
|
||||
return self.jsonify(**request.values)
|
||||
|
||||
|
||||
class CITypeGroupView(APIView):
|
||||
url_prefix = ("/ci_types/groups",
|
||||
"/ci_types/groups/config",
|
||||
"/ci_types/groups/order",
|
||||
"/ci_types/groups/<int:gid>")
|
||||
|
||||
def get(self):
|
||||
@@ -96,7 +127,8 @@ class CITypeGroupView(APIView):
|
||||
|
||||
return self.jsonify(CITypeGroupManager.get(need_other, config_required))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.create_CIType_group, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_validate(CITypeGroupManager.cls)
|
||||
def post(self):
|
||||
@@ -107,15 +139,6 @@ class CITypeGroupView(APIView):
|
||||
|
||||
@args_validate(CITypeGroupManager.cls)
|
||||
def put(self, gid=None):
|
||||
if "/order" in request.url:
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
group_ids = request.values.get('group_ids')
|
||||
CITypeGroupManager.order(group_ids)
|
||||
|
||||
return self.jsonify(group_ids=group_ids)
|
||||
|
||||
name = request.values.get('name') or abort(400, ErrFormat.argument_value_required.format("name"))
|
||||
type_ids = request.values.get('type_ids')
|
||||
|
||||
@@ -123,7 +146,8 @@ class CITypeGroupView(APIView):
|
||||
|
||||
return self.jsonify(gid=gid)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.delete_CIType_group, app_cli.admin_name)
|
||||
def delete(self, gid):
|
||||
type_ids = request.values.get("type_ids")
|
||||
CITypeGroupManager.delete(gid, type_ids)
|
||||
@@ -131,6 +155,18 @@ class CITypeGroupView(APIView):
|
||||
return self.jsonify(gid=gid)
|
||||
|
||||
|
||||
class CITypeGroupOrderView(APIView):
|
||||
url_prefix = "/ci_types/groups/order"
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.update_CIType_group, app_cli.admin_name)
|
||||
def put(self):
|
||||
group_ids = request.values.get('group_ids')
|
||||
CITypeGroupManager.order(group_ids)
|
||||
|
||||
return self.jsonify(group_ids=group_ids)
|
||||
|
||||
|
||||
class CITypeQueryView(APIView):
|
||||
url_prefix = "/ci_types/query"
|
||||
|
||||
@@ -248,8 +284,8 @@ class CITypeAttributeTransferView(APIView):
|
||||
@args_required('from')
|
||||
@args_required('to')
|
||||
def post(self, type_id):
|
||||
_from = request.values.get('from') # {'attr_id': xx, 'group_id': xx}
|
||||
_to = request.values.get('to') # {'group_id': xx, 'order': xxx}
|
||||
_from = request.values.get('from') # {'attr_id': xx, 'group_id': xx, 'group_name': xx}
|
||||
_to = request.values.get('to') # {'group_id': xx, 'group_name': xx, 'order': xxx}
|
||||
|
||||
CITypeAttributeManager.transfer(type_id, _from, _to)
|
||||
|
||||
@@ -262,8 +298,8 @@ class CITypeAttributeGroupTransferView(APIView):
|
||||
@args_required('from')
|
||||
@args_required('to')
|
||||
def post(self, type_id):
|
||||
_from = request.values.get('from') # group_id
|
||||
_to = request.values.get('to') # group_id
|
||||
_from = request.values.get('from') # group_id or group_name
|
||||
_to = request.values.get('to') # group_id or group_name
|
||||
|
||||
CITypeAttributeGroupManager.transfer(type_id, _from, _to)
|
||||
|
||||
@@ -296,7 +332,7 @@ class CITypeAttributeGroupView(APIView):
|
||||
|
||||
attr_order = list(zip(attrs, orders))
|
||||
group = CITypeAttributeGroupManager.create_or_update(type_id, name, attr_order, order)
|
||||
current_app.logger.warning(group.id)
|
||||
|
||||
return self.jsonify(group_id=group.id)
|
||||
|
||||
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||
@@ -310,25 +346,29 @@ class CITypeAttributeGroupView(APIView):
|
||||
|
||||
attr_order = list(zip(attrs, orders))
|
||||
CITypeAttributeGroupManager.update(group_id, name, attr_order, order)
|
||||
|
||||
return self.jsonify(group_id=group_id)
|
||||
|
||||
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||
def delete(self, group_id):
|
||||
CITypeAttributeGroupManager.delete(group_id)
|
||||
|
||||
return self.jsonify(group_id=group_id)
|
||||
|
||||
|
||||
class CITypeTemplateView(APIView):
|
||||
url_prefix = ("/ci_types/template/import", "/ci_types/template/export", "/ci_types/<int:type_id>/template/export")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def get(self, type_id=None): # export
|
||||
if type_id is not None:
|
||||
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template_by_type(type_id)))
|
||||
|
||||
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template()))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def post(self): # import
|
||||
tpt = request.values.get('ci_type_template') or {}
|
||||
|
||||
@@ -348,7 +388,8 @@ class CITypeCanDefineComputed(APIView):
|
||||
class CITypeTemplateFileView(APIView):
|
||||
url_prefix = ("/ci_types/template/import/file", "/ci_types/template/export/file")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def get(self): # export
|
||||
tpt_json = CITypeTemplateManager.export_template()
|
||||
tpt_json = dict(ci_type_template=tpt_json)
|
||||
@@ -363,7 +404,8 @@ class CITypeTemplateFileView(APIView):
|
||||
mimetype='application/json',
|
||||
max_age=0)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def post(self): # import
|
||||
f = request.files.get('file')
|
||||
|
||||
@@ -463,18 +505,19 @@ class CITypeGrantView(APIView):
|
||||
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||
return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
|
||||
|
||||
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)
|
||||
|
||||
if request.values.get('ci_filter') or request.values.get('attr_filter'):
|
||||
CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values)
|
||||
else:
|
||||
new_resource = None
|
||||
if 'ci_filter' in request.values or 'attr_filter' in request.values or 'id_filter' in request.values:
|
||||
new_resource = CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values)
|
||||
|
||||
if not new_resource:
|
||||
from api.tasks.acl import role_rebuild
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
|
||||
app_id = AppCache.get('cmdb').id
|
||||
current_app.logger.info((rid, app_id))
|
||||
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE)
|
||||
current_app.logger.info('done')
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
@@ -495,10 +538,18 @@ class CITypeRevokeView(APIView):
|
||||
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||
return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
|
||||
|
||||
acl.revoke_resource_from_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
|
||||
|
||||
app_id = AppCache.get('cmdb').id
|
||||
resource = None
|
||||
|
||||
if request.values.get('id_filter'):
|
||||
CIFilterPermsCRUD().delete2(
|
||||
type_id=type_id, rid=rid, id_filter=request.values['id_filter'],
|
||||
parent_path=request.values.get('parent_path'))
|
||||
|
||||
return self.jsonify(type_id=type_id, rid=rid)
|
||||
|
||||
acl.revoke_resource_from_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
|
||||
|
||||
if PermEnum.READ in perms or not perms:
|
||||
resource = CIFilterPermsCRUD().delete(type_id=type_id, rid=rid)
|
||||
|
||||
|
@@ -11,6 +11,8 @@ from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
@@ -18,6 +20,8 @@ from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class GetChildrenView(APIView):
|
||||
url_prefix = ("/ci_type_relations/<int:parent_id>/children",
|
||||
@@ -41,18 +45,22 @@ class GetParentsView(APIView):
|
||||
class CITypeRelationView(APIView):
|
||||
url_prefix = ("/ci_type_relations", "/ci_type_relations/<int:parent_id>/<int:child_id>")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
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)
|
||||
@args_required("relation_type_id")
|
||||
def post(self, parent_id, child_id):
|
||||
relation_type_id = request.values.get("relation_type_id")
|
||||
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)
|
||||
|
||||
@@ -66,7 +74,8 @@ class CITypeRelationView(APIView):
|
||||
class CITypeRelationDelete2View(APIView):
|
||||
url_prefix = "/ci_type_relations/<int:ctr_id>"
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Relationships,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def delete(self, ctr_id):
|
||||
CITypeRelationManager.delete(ctr_id)
|
||||
|
||||
|
@@ -3,14 +3,16 @@
|
||||
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.custom_dashboard import CustomDashboardManager
|
||||
from api.lib.cmdb.custom_dashboard import SystemConfigManager
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class CustomDashboardApiView(APIView):
|
||||
url_prefix = ("/custom_dashboard", "/custom_dashboard/<int:_id>", "/custom_dashboard/batch",
|
||||
@@ -19,7 +21,8 @@ class CustomDashboardApiView(APIView):
|
||||
def get(self):
|
||||
return self.jsonify(CustomDashboardManager.get())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_validate(CustomDashboardManager.cls)
|
||||
def post(self):
|
||||
if request.url.endswith("/preview"):
|
||||
@@ -32,7 +35,8 @@ class CustomDashboardApiView(APIView):
|
||||
|
||||
return self.jsonify(res)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_validate(CustomDashboardManager.cls)
|
||||
def put(self, _id=None):
|
||||
if _id is not None:
|
||||
@@ -47,7 +51,8 @@ class CustomDashboardApiView(APIView):
|
||||
|
||||
return self.jsonify(id2options=request.values.get('id2options'))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def delete(self, _id):
|
||||
CustomDashboardManager.delete(_id)
|
||||
|
||||
@@ -57,12 +62,14 @@ class CustomDashboardApiView(APIView):
|
||||
class SystemConfigApiView(APIView):
|
||||
url_prefix = ("/system_config",)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name", value_required=True)
|
||||
def get(self):
|
||||
return self.jsonify(SystemConfigManager.get(request.values['name']))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_validate(SystemConfigManager.cls)
|
||||
@args_required("name", value_required=True)
|
||||
@args_required("option", value_required=True)
|
||||
@@ -74,7 +81,8 @@ class SystemConfigApiView(APIView):
|
||||
def put(self, _id=None):
|
||||
return self.post()
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
def delete(self):
|
||||
CustomDashboardManager.delete(request.values['name'])
|
||||
|
@@ -5,28 +5,29 @@ import datetime
|
||||
|
||||
from flask import abort
|
||||
from flask import request
|
||||
from flask import session
|
||||
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.history import AttributeHistoryManger
|
||||
from api.lib.cmdb.history import CITriggerHistoryManager
|
||||
from api.lib.cmdb.history import CITypeHistoryManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.lib.utils import get_page
|
||||
from api.lib.utils import get_page_size
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class RecordView(APIView):
|
||||
url_prefix = ("/history/records/attribute", "/history/records/relation")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
page = get_page(request.values.get("page", 1))
|
||||
page_size = get_page_size(request.values.get("page_size"))
|
||||
@@ -80,18 +81,21 @@ class CIHistoryView(APIView):
|
||||
|
||||
|
||||
class CITriggerHistoryView(APIView):
|
||||
url_prefix = ("/history/ci_triggers/<int:ci_id>", "/history/ci_triggers")
|
||||
url_prefix = ("/history/ci_triggers/<int:ci_id>",)
|
||||
|
||||
@has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.READ, CIManager.get_type_name)
|
||||
def get(self, ci_id=None):
|
||||
if ci_id is not None:
|
||||
result = CITriggerHistoryManager.get_by_ci_id(ci_id)
|
||||
def get(self, ci_id):
|
||||
result = CITriggerHistoryManager.get_by_ci_id(ci_id)
|
||||
|
||||
return self.jsonify(result)
|
||||
return self.jsonify(result)
|
||||
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
class CIsTriggerHistoryView(APIView):
|
||||
url_prefix = ("/history/ci_triggers",)
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
type_id = request.values.get("type_id")
|
||||
trigger_id = request.values.get("trigger_id")
|
||||
operate_type = request.values.get("operate_type")
|
||||
@@ -115,7 +119,8 @@ class CITriggerHistoryView(APIView):
|
||||
class CITypeHistoryView(APIView):
|
||||
url_prefix = "/history/ci_types"
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
type_id = request.values.get("type_id")
|
||||
username = request.values.get("username")
|
||||
|
@@ -2,25 +2,28 @@
|
||||
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.lib.perm.acl.acl import validate_permission
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class PreferenceShowCITypesView(APIView):
|
||||
url_prefix = ("/preference/ci_types", "/preference/ci_types2")
|
||||
@@ -96,29 +99,38 @@ class PreferenceTreeApiView(APIView):
|
||||
|
||||
|
||||
class PreferenceRelationApiView(APIView):
|
||||
url_prefix = "/preference/relation/view"
|
||||
url_prefix = ("/preference/relation/view", "/preference/relation/view/<int:_id>")
|
||||
|
||||
def get(self):
|
||||
views, id2type, name2id = PreferenceManager.get_relation_view()
|
||||
|
||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_required("cr_ids")
|
||||
@args_validate(PreferenceManager.pref_rel_cls)
|
||||
def post(self):
|
||||
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")
|
||||
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)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def put(self):
|
||||
return self.post()
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
def put(self, _id):
|
||||
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(_id=_id, **request.values)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
def delete(self):
|
||||
name = request.values.get("name")
|
||||
@@ -187,3 +199,15 @@ class PreferenceRelationRevokeView(APIView):
|
||||
acl.revoke_resource_from_role_by_rid(name, rid, ResourceTypeEnum.RELATION_VIEW, perms)
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
|
||||
class PreferenceCITypeOrderView(APIView):
|
||||
url_prefix = ("/preference/ci_types/order",)
|
||||
|
||||
def post(self):
|
||||
type_ids = request.values.get("type_ids")
|
||||
is_tree = request.values.get("is_tree") in current_app.config.get('BOOL_TRUE')
|
||||
|
||||
PreferenceManager.upsert_ci_type_order(type_ids, is_tree)
|
||||
|
||||
return self.jsonify(type_ids=type_ids, is_tree=is_tree)
|
||||
|
@@ -4,14 +4,16 @@
|
||||
from flask import abort
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.relation_type import RelationTypeManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class RelationTypeView(APIView):
|
||||
url_prefix = ("/relation_types", "/relation_types/<int:rel_id>")
|
||||
@@ -19,7 +21,8 @@ class RelationTypeView(APIView):
|
||||
def get(self):
|
||||
return self.jsonify([i.to_dict() for i in RelationTypeManager.get_all()])
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_validate(RelationTypeManager.cls)
|
||||
def post(self):
|
||||
@@ -28,7 +31,8 @@ class RelationTypeView(APIView):
|
||||
|
||||
return self.jsonify(rel.to_dict())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_validate(RelationTypeManager.cls)
|
||||
def put(self, rel_id):
|
||||
@@ -37,7 +41,8 @@ class RelationTypeView(APIView):
|
||||
|
||||
return self.jsonify(rel.to_dict())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def delete(self, rel_id):
|
||||
RelationTypeManager.delete(rel_id)
|
||||
|
||||
|
@@ -1,10 +1,10 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
import os
|
||||
|
||||
from flask import request, abort, current_app, send_from_directory
|
||||
from flask import request, abort, current_app
|
||||
from werkzeug.utils import secure_filename
|
||||
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.upload_file import allowed_file, generate_new_file_name, CommonFileCRUD
|
||||
from api.resource import APIView
|
||||
@@ -45,32 +45,35 @@ class PostFileView(APIView):
|
||||
|
||||
if not file:
|
||||
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)):
|
||||
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,
|
||||
)
|
||||
m_type = magic.from_buffer(file.read(2048), mime=True)
|
||||
file.seek(0)
|
||||
|
||||
return self.jsonify(file_name=new_filename)
|
||||
except Exception as e:
|
||||
current_app.logger.error(e)
|
||||
abort(400, ErrFormat.upload_failed.format(e))
|
||||
if m_type == 'application/octet-stream':
|
||||
m_type = file.mimetype
|
||||
elif m_type == 'text/plain':
|
||||
# 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))
|
||||
|
@@ -8,7 +8,7 @@ elasticsearch==7.17.9
|
||||
email-validator==1.3.1
|
||||
environs==4.2.0
|
||||
flasgger==0.9.5
|
||||
Flask==2.3.2
|
||||
Flask==2.2.5
|
||||
Flask-Bcrypt==1.0.1
|
||||
flask-babel==4.0.0
|
||||
Flask-Caching==2.0.2
|
||||
@@ -37,6 +37,7 @@ PyMySQL==1.1.0
|
||||
ldap3==2.9.1
|
||||
PyYAML==6.0.1
|
||||
redis==4.6.0
|
||||
python-redis-lock==4.0.0
|
||||
requests==2.31.0
|
||||
requests_oauthlib==1.3.1
|
||||
markdownify==0.11.6
|
||||
@@ -46,9 +47,10 @@ supervisor==4.0.3
|
||||
timeout-decorator==0.5.0
|
||||
toposort==1.10
|
||||
treelib==1.6.1
|
||||
Werkzeug>=2.3.6
|
||||
Werkzeug==2.2.3
|
||||
WTForms==3.0.0
|
||||
shamir~=17.12.0
|
||||
pycryptodomex>=3.19.0
|
||||
colorama>=0.4.6
|
||||
lz4>=4.3.2
|
||||
lz4>=4.3.2
|
||||
python-magic==0.4.27
|
@@ -13,7 +13,7 @@ const getAntdSerials = (color) => {
|
||||
|
||||
const themePluginOption = {
|
||||
fileName: 'css/theme-colors-[contenthash:8].css',
|
||||
matchColors: getAntdSerials('#1890ff'), // 主色系列
|
||||
matchColors: getAntdSerials('#2f54eb'), // 主色系列
|
||||
// 改变样式选择器,解决样式覆盖问题
|
||||
changeSelector (selector) {
|
||||
switch (selector) {
|
||||
|
@@ -42,9 +42,11 @@
|
||||
"relation-graph": "^1.1.0",
|
||||
"snabbdom": "^3.5.1",
|
||||
"sortablejs": "1.9.0",
|
||||
"style-resources-loader": "^1.5.0",
|
||||
"viser-vue": "^2.4.8",
|
||||
"vue": "2.6.11",
|
||||
"vue-clipboard2": "^0.3.3",
|
||||
"vue-cli-plugin-style-resources-loader": "^0.1.5",
|
||||
"vue-codemirror": "^4.0.6",
|
||||
"vue-cropper": "^0.6.2",
|
||||
"vue-grid-layout": "2.3.12",
|
||||
|
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3857903 */
|
||||
src: url('iconfont.woff2?t=1702544951995') format('woff2'),
|
||||
url('iconfont.woff?t=1702544951995') format('woff'),
|
||||
url('iconfont.ttf?t=1702544951995') format('truetype');
|
||||
src: url('iconfont.woff2?t=1713840593232') format('woff2'),
|
||||
url('iconfont.woff?t=1713840593232') format('woff'),
|
||||
url('iconfont.ttf?t=1713840593232') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,270 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.ops-setting-application-selected:before {
|
||||
content: "\e919";
|
||||
}
|
||||
|
||||
.ops-setting-application:before {
|
||||
content: "\e918";
|
||||
}
|
||||
|
||||
.ops-setting-basic:before {
|
||||
content: "\e889";
|
||||
}
|
||||
|
||||
.ops-setting-basic-selected:before {
|
||||
content: "\e917";
|
||||
}
|
||||
|
||||
.ops-setting-security:before {
|
||||
content: "\e915";
|
||||
}
|
||||
|
||||
.ops-setting-theme:before {
|
||||
content: "\e916";
|
||||
}
|
||||
|
||||
.veops-show:before {
|
||||
content: "\e914";
|
||||
}
|
||||
|
||||
.itsm-duration:before {
|
||||
content: "\e913";
|
||||
}
|
||||
|
||||
.itsm-workload:before {
|
||||
content: "\e912";
|
||||
}
|
||||
|
||||
.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 {
|
||||
content: "\e8d8";
|
||||
}
|
||||
@@ -29,11 +293,7 @@
|
||||
content: "\e8d5";
|
||||
}
|
||||
|
||||
.ops-setting-auth-selected:before {
|
||||
content: "\e8d4";
|
||||
}
|
||||
|
||||
.a-itsm-knowledge2:before {
|
||||
.itsm-knowledge2:before {
|
||||
content: "\e8d2";
|
||||
}
|
||||
|
||||
@@ -261,10 +521,6 @@
|
||||
content: "\e89c";
|
||||
}
|
||||
|
||||
.ops-setting-duty-selected:before {
|
||||
content: "\e89b";
|
||||
}
|
||||
|
||||
.datainsight-sequential:before {
|
||||
content: "\e899";
|
||||
}
|
||||
@@ -429,38 +685,6 @@
|
||||
content: "\e870";
|
||||
}
|
||||
|
||||
.ops-itsm-ticketsetting-selected:before {
|
||||
content: "\e860";
|
||||
}
|
||||
|
||||
.ops-itsm-reports-selected:before {
|
||||
content: "\e861";
|
||||
}
|
||||
|
||||
.ops-itsm-servicecatalog-selected:before {
|
||||
content: "\e862";
|
||||
}
|
||||
|
||||
.ops-itsm-ticketmanage-selected:before {
|
||||
content: "\e863";
|
||||
}
|
||||
|
||||
.ops-itsm-knowledge-selected:before {
|
||||
content: "\e864";
|
||||
}
|
||||
|
||||
.ops-itsm-workstation-selected:before {
|
||||
content: "\e865";
|
||||
}
|
||||
|
||||
.ops-itsm-servicedesk-selected:before {
|
||||
content: "\e866";
|
||||
}
|
||||
|
||||
.ops-itsm-planticket-selected:before {
|
||||
content: "\e867";
|
||||
}
|
||||
|
||||
.ops-itsm-servicecatalog:before {
|
||||
content: "\e868";
|
||||
}
|
||||
@@ -833,26 +1057,10 @@
|
||||
content: "\e816";
|
||||
}
|
||||
|
||||
.ops-cmdb-batch-selected:before {
|
||||
content: "\e803";
|
||||
}
|
||||
|
||||
.ops-cmdb-batch:before {
|
||||
content: "\e80a";
|
||||
}
|
||||
|
||||
.ops-cmdb-adc-selected:before {
|
||||
content: "\e7f7";
|
||||
}
|
||||
|
||||
.ops-cmdb-resource-selected:before {
|
||||
content: "\e7f8";
|
||||
}
|
||||
|
||||
.ops-cmdb-preference-selected:before {
|
||||
content: "\e7f9";
|
||||
}
|
||||
|
||||
.ops-cmdb-preference:before {
|
||||
content: "\e7fa";
|
||||
}
|
||||
@@ -861,22 +1069,10 @@
|
||||
content: "\e7fb";
|
||||
}
|
||||
|
||||
.ops-cmdb-tree-selected:before {
|
||||
content: "\e7fc";
|
||||
}
|
||||
|
||||
.ops-cmdb-relation-selected:before {
|
||||
content: "\e7fd";
|
||||
}
|
||||
|
||||
.ops-cmdb-adc:before {
|
||||
content: "\e7fe";
|
||||
}
|
||||
|
||||
.ops-cmdb-search-selected:before {
|
||||
content: "\e7ff";
|
||||
}
|
||||
|
||||
.ops-cmdb-relation:before {
|
||||
content: "\e800";
|
||||
}
|
||||
@@ -885,14 +1081,6 @@
|
||||
content: "\e801";
|
||||
}
|
||||
|
||||
.ops-cmdb-citype-selected:before {
|
||||
content: "\e802";
|
||||
}
|
||||
|
||||
.ops-cmdb-dashboard-selected:before {
|
||||
content: "\e804";
|
||||
}
|
||||
|
||||
.ops-cmdb-citype:before {
|
||||
content: "\e805";
|
||||
}
|
||||
@@ -901,10 +1089,6 @@
|
||||
content: "\e806";
|
||||
}
|
||||
|
||||
.ops-cmdb-screen-selected:before {
|
||||
content: "\e807";
|
||||
}
|
||||
|
||||
.ops-cmdb-resource:before {
|
||||
content: "\e808";
|
||||
}
|
||||
@@ -1253,14 +1437,6 @@
|
||||
content: "\e7a6";
|
||||
}
|
||||
|
||||
.ops-setting-role-selected:before {
|
||||
content: "\e7a0";
|
||||
}
|
||||
|
||||
.ops-setting-group-selected:before {
|
||||
content: "\e7a1";
|
||||
}
|
||||
|
||||
.ops-setting-role:before {
|
||||
content: "\e7a2";
|
||||
}
|
||||
@@ -1701,18 +1877,10 @@
|
||||
content: "\e738";
|
||||
}
|
||||
|
||||
.ops-setting-notice-email-selected-copy:before {
|
||||
content: "\e889";
|
||||
}
|
||||
|
||||
.ops-setting-notice:before {
|
||||
content: "\e72f";
|
||||
}
|
||||
|
||||
.ops-setting-notice-selected:before {
|
||||
content: "\e730";
|
||||
}
|
||||
|
||||
.ops-setting-notice-email-selected:before {
|
||||
content: "\e731";
|
||||
}
|
||||
@@ -1737,10 +1905,6 @@
|
||||
content: "\e736";
|
||||
}
|
||||
|
||||
.ops-setting-companyStructure-selected:before {
|
||||
content: "\e72b";
|
||||
}
|
||||
|
||||
.ops-setting-companyStructure:before {
|
||||
content: "\e72c";
|
||||
}
|
||||
@@ -1749,10 +1913,6 @@
|
||||
content: "\e72d";
|
||||
}
|
||||
|
||||
.ops-setting-companyInfo-selected:before {
|
||||
content: "\e72e";
|
||||
}
|
||||
|
||||
.ops-email:before {
|
||||
content: "\e61a";
|
||||
}
|
||||
@@ -2793,14 +2953,6 @@
|
||||
content: "\e600";
|
||||
}
|
||||
|
||||
.ops-dag-dashboard-selected:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.ops-dag-applet-selected:before {
|
||||
content: "\e602";
|
||||
}
|
||||
|
||||
.ops-dag-applet:before {
|
||||
content: "\e603";
|
||||
}
|
||||
@@ -2809,62 +2961,26 @@
|
||||
content: "\e604";
|
||||
}
|
||||
|
||||
.ops-dag-terminal-selected:before {
|
||||
content: "\e605";
|
||||
}
|
||||
|
||||
.ops-dag-cron:before {
|
||||
content: "\e606";
|
||||
}
|
||||
|
||||
.ops-dag-cron-selected:before {
|
||||
content: "\e608";
|
||||
}
|
||||
|
||||
.ops-dag-history:before {
|
||||
content: "\e609";
|
||||
}
|
||||
|
||||
.ops-dag-history-selected:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
|
||||
.ops-dag-dags-selected:before {
|
||||
content: "\e60c";
|
||||
}
|
||||
|
||||
.ops-dag-dagreview:before {
|
||||
content: "\e60d";
|
||||
}
|
||||
|
||||
.ops-dag-dagreview-selected:before {
|
||||
content: "\e60e";
|
||||
}
|
||||
|
||||
.ops-dag-panel:before {
|
||||
content: "\e60f";
|
||||
}
|
||||
|
||||
.ops-dag-panel-selected:before {
|
||||
content: "\e615";
|
||||
}
|
||||
|
||||
.ops-dag-variables:before {
|
||||
content: "\e616";
|
||||
}
|
||||
|
||||
.ops-dag-variables-selected:before {
|
||||
content: "\e618";
|
||||
}
|
||||
|
||||
.ops-dag-appletadmin:before {
|
||||
content: "\e65c";
|
||||
}
|
||||
|
||||
.ops-dag-appletadmin-selected:before {
|
||||
content: "\e65d";
|
||||
}
|
||||
|
||||
.ops-dag-dags:before {
|
||||
content: "\e60b";
|
||||
}
|
||||
|
@@ -5,6 +5,468 @@
|
||||
"css_prefix_text": "",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "40043662",
|
||||
"name": "ops-setting-application-selected",
|
||||
"font_class": "ops-setting-application-selected",
|
||||
"unicode": "e919",
|
||||
"unicode_decimal": 59673
|
||||
},
|
||||
{
|
||||
"icon_id": "40043685",
|
||||
"name": "ops-setting-application",
|
||||
"font_class": "ops-setting-application",
|
||||
"unicode": "e918",
|
||||
"unicode_decimal": 59672
|
||||
},
|
||||
{
|
||||
"icon_id": "40043049",
|
||||
"name": "ops-setting-basic",
|
||||
"font_class": "ops-setting-basic",
|
||||
"unicode": "e889",
|
||||
"unicode_decimal": 59529
|
||||
},
|
||||
{
|
||||
"icon_id": "40043047",
|
||||
"name": "ops-setting-basic-selected",
|
||||
"font_class": "ops-setting-basic-selected",
|
||||
"unicode": "e917",
|
||||
"unicode_decimal": 59671
|
||||
},
|
||||
{
|
||||
"icon_id": "40038753",
|
||||
"name": "ops-setting-security",
|
||||
"font_class": "ops-setting-security",
|
||||
"unicode": "e915",
|
||||
"unicode_decimal": 59669
|
||||
},
|
||||
{
|
||||
"icon_id": "40038752",
|
||||
"name": "ops-setting-theme",
|
||||
"font_class": "ops-setting-theme",
|
||||
"unicode": "e916",
|
||||
"unicode_decimal": 59670
|
||||
},
|
||||
{
|
||||
"icon_id": "39948814",
|
||||
"name": "veops-show",
|
||||
"font_class": "veops-show",
|
||||
"unicode": "e914",
|
||||
"unicode_decimal": 59668
|
||||
},
|
||||
{
|
||||
"icon_id": "39926816",
|
||||
"name": "itsm-duration",
|
||||
"font_class": "itsm-duration",
|
||||
"unicode": "e913",
|
||||
"unicode_decimal": 59667
|
||||
},
|
||||
{
|
||||
"icon_id": "39926833",
|
||||
"name": "itsm-workload (1)",
|
||||
"font_class": "itsm-workload",
|
||||
"unicode": "e912",
|
||||
"unicode_decimal": 59666
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"name": "OAuth2.0",
|
||||
@@ -33,17 +495,10 @@
|
||||
"unicode": "e8d5",
|
||||
"unicode_decimal": 59605
|
||||
},
|
||||
{
|
||||
"icon_id": "38547389",
|
||||
"name": "setting-authentication-selected",
|
||||
"font_class": "ops-setting-auth-selected",
|
||||
"unicode": "e8d4",
|
||||
"unicode_decimal": 59604
|
||||
},
|
||||
{
|
||||
"icon_id": "38533133",
|
||||
"name": "itsm-knowledge (2)",
|
||||
"font_class": "a-itsm-knowledge2",
|
||||
"font_class": "itsm-knowledge2",
|
||||
"unicode": "e8d2",
|
||||
"unicode_decimal": 59602
|
||||
},
|
||||
@@ -439,13 +894,6 @@
|
||||
"unicode": "e89c",
|
||||
"unicode_decimal": 59548
|
||||
},
|
||||
{
|
||||
"icon_id": "37940033",
|
||||
"name": "ops-setting-duty-selected",
|
||||
"font_class": "ops-setting-duty-selected",
|
||||
"unicode": "e89b",
|
||||
"unicode_decimal": 59547
|
||||
},
|
||||
{
|
||||
"icon_id": "37841524",
|
||||
"name": "datainsight-sequential",
|
||||
@@ -733,62 +1181,6 @@
|
||||
"unicode": "e870",
|
||||
"unicode_decimal": 59504
|
||||
},
|
||||
{
|
||||
"icon_id": "35984161",
|
||||
"name": "ops-itsm-ticketsetting-selected",
|
||||
"font_class": "ops-itsm-ticketsetting-selected",
|
||||
"unicode": "e860",
|
||||
"unicode_decimal": 59488
|
||||
},
|
||||
{
|
||||
"icon_id": "35984162",
|
||||
"name": "ops-itsm-reports-selected",
|
||||
"font_class": "ops-itsm-reports-selected",
|
||||
"unicode": "e861",
|
||||
"unicode_decimal": 59489
|
||||
},
|
||||
{
|
||||
"icon_id": "35984163",
|
||||
"name": "ops-itsm-servicecatalog-selected",
|
||||
"font_class": "ops-itsm-servicecatalog-selected",
|
||||
"unicode": "e862",
|
||||
"unicode_decimal": 59490
|
||||
},
|
||||
{
|
||||
"icon_id": "35984164",
|
||||
"name": "ops-itsm-ticketmanage-selected",
|
||||
"font_class": "ops-itsm-ticketmanage-selected",
|
||||
"unicode": "e863",
|
||||
"unicode_decimal": 59491
|
||||
},
|
||||
{
|
||||
"icon_id": "35984165",
|
||||
"name": "ops-itsm-knowledge-selected",
|
||||
"font_class": "ops-itsm-knowledge-selected",
|
||||
"unicode": "e864",
|
||||
"unicode_decimal": 59492
|
||||
},
|
||||
{
|
||||
"icon_id": "35984166",
|
||||
"name": "ops-itsm-workstation-selected",
|
||||
"font_class": "ops-itsm-workstation-selected",
|
||||
"unicode": "e865",
|
||||
"unicode_decimal": 59493
|
||||
},
|
||||
{
|
||||
"icon_id": "35984167",
|
||||
"name": "ops-itsm-servicedesk-selected",
|
||||
"font_class": "ops-itsm-servicedesk-selected",
|
||||
"unicode": "e866",
|
||||
"unicode_decimal": 59494
|
||||
},
|
||||
{
|
||||
"icon_id": "35984168",
|
||||
"name": "ops-itsm-planticket-selected",
|
||||
"font_class": "ops-itsm-planticket-selected",
|
||||
"unicode": "e867",
|
||||
"unicode_decimal": 59495
|
||||
},
|
||||
{
|
||||
"icon_id": "35984169",
|
||||
"name": "ops-itsm-servicecatalog",
|
||||
@@ -1440,13 +1832,6 @@
|
||||
"unicode": "e816",
|
||||
"unicode_decimal": 59414
|
||||
},
|
||||
{
|
||||
"icon_id": "35400645",
|
||||
"name": "ops-cmdb-batch-selected",
|
||||
"font_class": "ops-cmdb-batch-selected",
|
||||
"unicode": "e803",
|
||||
"unicode_decimal": 59395
|
||||
},
|
||||
{
|
||||
"icon_id": "35400646",
|
||||
"name": "ops-cmdb-batch",
|
||||
@@ -1454,27 +1839,6 @@
|
||||
"unicode": "e80a",
|
||||
"unicode_decimal": 59402
|
||||
},
|
||||
{
|
||||
"icon_id": "35395300",
|
||||
"name": "ops-cmdb-adc-selected",
|
||||
"font_class": "ops-cmdb-adc-selected",
|
||||
"unicode": "e7f7",
|
||||
"unicode_decimal": 59383
|
||||
},
|
||||
{
|
||||
"icon_id": "35395301",
|
||||
"name": "ops-cmdb-resource-selected",
|
||||
"font_class": "ops-cmdb-resource-selected",
|
||||
"unicode": "e7f8",
|
||||
"unicode_decimal": 59384
|
||||
},
|
||||
{
|
||||
"icon_id": "35395302",
|
||||
"name": "ops-cmdb-preference-selected",
|
||||
"font_class": "ops-cmdb-preference-selected",
|
||||
"unicode": "e7f9",
|
||||
"unicode_decimal": 59385
|
||||
},
|
||||
{
|
||||
"icon_id": "35395303",
|
||||
"name": "ops-cmdb-preference",
|
||||
@@ -1489,20 +1853,6 @@
|
||||
"unicode": "e7fb",
|
||||
"unicode_decimal": 59387
|
||||
},
|
||||
{
|
||||
"icon_id": "35395305",
|
||||
"name": "ops-cmdb-tree-selected",
|
||||
"font_class": "ops-cmdb-tree-selected",
|
||||
"unicode": "e7fc",
|
||||
"unicode_decimal": 59388
|
||||
},
|
||||
{
|
||||
"icon_id": "35395306",
|
||||
"name": "ops-cmdb-relation-selected",
|
||||
"font_class": "ops-cmdb-relation-selected",
|
||||
"unicode": "e7fd",
|
||||
"unicode_decimal": 59389
|
||||
},
|
||||
{
|
||||
"icon_id": "35395307",
|
||||
"name": "ops-cmdb-adc",
|
||||
@@ -1510,13 +1860,6 @@
|
||||
"unicode": "e7fe",
|
||||
"unicode_decimal": 59390
|
||||
},
|
||||
{
|
||||
"icon_id": "35395308",
|
||||
"name": "ops-cmdb-search-selected",
|
||||
"font_class": "ops-cmdb-search-selected",
|
||||
"unicode": "e7ff",
|
||||
"unicode_decimal": 59391
|
||||
},
|
||||
{
|
||||
"icon_id": "35395309",
|
||||
"name": "ops-cmdb-relation",
|
||||
@@ -1531,20 +1874,6 @@
|
||||
"unicode": "e801",
|
||||
"unicode_decimal": 59393
|
||||
},
|
||||
{
|
||||
"icon_id": "35395311",
|
||||
"name": "ops-cmdb-citype-selected",
|
||||
"font_class": "ops-cmdb-citype-selected",
|
||||
"unicode": "e802",
|
||||
"unicode_decimal": 59394
|
||||
},
|
||||
{
|
||||
"icon_id": "35395313",
|
||||
"name": "ops-cmdb-dashboard-selected",
|
||||
"font_class": "ops-cmdb-dashboard-selected",
|
||||
"unicode": "e804",
|
||||
"unicode_decimal": 59396
|
||||
},
|
||||
{
|
||||
"icon_id": "35395314",
|
||||
"name": "ops-cmdb-citype",
|
||||
@@ -1559,13 +1888,6 @@
|
||||
"unicode": "e806",
|
||||
"unicode_decimal": 59398
|
||||
},
|
||||
{
|
||||
"icon_id": "35395316",
|
||||
"name": "ops-cmdb-screen-selected",
|
||||
"font_class": "ops-cmdb-screen-selected",
|
||||
"unicode": "e807",
|
||||
"unicode_decimal": 59399
|
||||
},
|
||||
{
|
||||
"icon_id": "35395317",
|
||||
"name": "ops-cmdb-resource",
|
||||
@@ -1841,7 +2163,7 @@
|
||||
},
|
||||
{
|
||||
"icon_id": "35341667",
|
||||
"name": "caise-chajian ",
|
||||
"name": "caise-chajian",
|
||||
"font_class": "caise-chajian",
|
||||
"unicode": "e7d2",
|
||||
"unicode_decimal": 59346
|
||||
@@ -2175,20 +2497,6 @@
|
||||
"unicode": "e7a6",
|
||||
"unicode_decimal": 59302
|
||||
},
|
||||
{
|
||||
"icon_id": "34792792",
|
||||
"name": "ops-setting-role-selected",
|
||||
"font_class": "ops-setting-role-selected",
|
||||
"unicode": "e7a0",
|
||||
"unicode_decimal": 59296
|
||||
},
|
||||
{
|
||||
"icon_id": "34792793",
|
||||
"name": "ops-setting-group-selected",
|
||||
"font_class": "ops-setting-group-selected",
|
||||
"unicode": "e7a1",
|
||||
"unicode_decimal": 59297
|
||||
},
|
||||
{
|
||||
"icon_id": "34792794",
|
||||
"name": "ops-setting-role",
|
||||
@@ -2959,13 +3267,6 @@
|
||||
"unicode": "e738",
|
||||
"unicode_decimal": 59192
|
||||
},
|
||||
{
|
||||
"icon_id": "37575490",
|
||||
"name": "ops-setting-notice-email-selected",
|
||||
"font_class": "ops-setting-notice-email-selected-copy",
|
||||
"unicode": "e889",
|
||||
"unicode_decimal": 59529
|
||||
},
|
||||
{
|
||||
"icon_id": "34108346",
|
||||
"name": "ops-setting-notice",
|
||||
@@ -2973,13 +3274,6 @@
|
||||
"unicode": "e72f",
|
||||
"unicode_decimal": 59183
|
||||
},
|
||||
{
|
||||
"icon_id": "34108348",
|
||||
"name": "ops-setting-notice-selected",
|
||||
"font_class": "ops-setting-notice-selected",
|
||||
"unicode": "e730",
|
||||
"unicode_decimal": 59184
|
||||
},
|
||||
{
|
||||
"icon_id": "34108504",
|
||||
"name": "ops-setting-notice-email-selected",
|
||||
@@ -3022,13 +3316,6 @@
|
||||
"unicode": "e736",
|
||||
"unicode_decimal": 59190
|
||||
},
|
||||
{
|
||||
"icon_id": "34108244",
|
||||
"name": "ops-setting-companyStructure-selected",
|
||||
"font_class": "ops-setting-companyStructure-selected",
|
||||
"unicode": "e72b",
|
||||
"unicode_decimal": 59179
|
||||
},
|
||||
{
|
||||
"icon_id": "34108296",
|
||||
"name": "ops-setting-companyStructure",
|
||||
@@ -3043,13 +3330,6 @@
|
||||
"unicode": "e72d",
|
||||
"unicode_decimal": 59181
|
||||
},
|
||||
{
|
||||
"icon_id": "34108330",
|
||||
"name": "ops-setting-companyInfo-selected",
|
||||
"font_class": "ops-setting-companyInfo-selected",
|
||||
"unicode": "e72e",
|
||||
"unicode_decimal": 59182
|
||||
},
|
||||
{
|
||||
"icon_id": "34099810",
|
||||
"name": "ops-email",
|
||||
@@ -4870,20 +5150,6 @@
|
||||
"unicode": "e600",
|
||||
"unicode_decimal": 58880
|
||||
},
|
||||
{
|
||||
"icon_id": "33053294",
|
||||
"name": "ops-dag-dashboard-selected",
|
||||
"font_class": "ops-dag-dashboard-selected",
|
||||
"unicode": "e601",
|
||||
"unicode_decimal": 58881
|
||||
},
|
||||
{
|
||||
"icon_id": "33053330",
|
||||
"name": "ops-dag-applet-selected",
|
||||
"font_class": "ops-dag-applet-selected",
|
||||
"unicode": "e602",
|
||||
"unicode_decimal": 58882
|
||||
},
|
||||
{
|
||||
"icon_id": "33053531",
|
||||
"name": "ops-dag-applet",
|
||||
@@ -4898,13 +5164,6 @@
|
||||
"unicode": "e604",
|
||||
"unicode_decimal": 58884
|
||||
},
|
||||
{
|
||||
"icon_id": "33053589",
|
||||
"name": "ops-dag-terminal-selected",
|
||||
"font_class": "ops-dag-terminal-selected",
|
||||
"unicode": "e605",
|
||||
"unicode_decimal": 58885
|
||||
},
|
||||
{
|
||||
"icon_id": "33053591",
|
||||
"name": "ops-dag-cron",
|
||||
@@ -4912,13 +5171,6 @@
|
||||
"unicode": "e606",
|
||||
"unicode_decimal": 58886
|
||||
},
|
||||
{
|
||||
"icon_id": "33053609",
|
||||
"name": "ops-dag-cron-selected",
|
||||
"font_class": "ops-dag-cron-selected",
|
||||
"unicode": "e608",
|
||||
"unicode_decimal": 58888
|
||||
},
|
||||
{
|
||||
"icon_id": "33053615",
|
||||
"name": "ops-dag-history",
|
||||
@@ -4926,20 +5178,6 @@
|
||||
"unicode": "e609",
|
||||
"unicode_decimal": 58889
|
||||
},
|
||||
{
|
||||
"icon_id": "33053617",
|
||||
"name": "ops-dag-history-selected",
|
||||
"font_class": "ops-dag-history-selected",
|
||||
"unicode": "e60a",
|
||||
"unicode_decimal": 58890
|
||||
},
|
||||
{
|
||||
"icon_id": "33053681",
|
||||
"name": "ops-dag-dags-selected",
|
||||
"font_class": "ops-dag-dags-selected",
|
||||
"unicode": "e60c",
|
||||
"unicode_decimal": 58892
|
||||
},
|
||||
{
|
||||
"icon_id": "33053682",
|
||||
"name": "ops-dag-dagreview",
|
||||
@@ -4947,13 +5185,6 @@
|
||||
"unicode": "e60d",
|
||||
"unicode_decimal": 58893
|
||||
},
|
||||
{
|
||||
"icon_id": "33053684",
|
||||
"name": "ops-dag-dagreview-selected",
|
||||
"font_class": "ops-dag-dagreview-selected",
|
||||
"unicode": "e60e",
|
||||
"unicode_decimal": 58894
|
||||
},
|
||||
{
|
||||
"icon_id": "33053691",
|
||||
"name": "ops-dag-panel",
|
||||
@@ -4961,13 +5192,6 @@
|
||||
"unicode": "e60f",
|
||||
"unicode_decimal": 58895
|
||||
},
|
||||
{
|
||||
"icon_id": "33053692",
|
||||
"name": "ops-dag-panel-selected",
|
||||
"font_class": "ops-dag-panel-selected",
|
||||
"unicode": "e615",
|
||||
"unicode_decimal": 58901
|
||||
},
|
||||
{
|
||||
"icon_id": "33053707",
|
||||
"name": "ops-dag-variables",
|
||||
@@ -4975,27 +5199,6 @@
|
||||
"unicode": "e616",
|
||||
"unicode_decimal": 58902
|
||||
},
|
||||
{
|
||||
"icon_id": "33053715",
|
||||
"name": "ops-dag-variables-selected",
|
||||
"font_class": "ops-dag-variables-selected",
|
||||
"unicode": "e618",
|
||||
"unicode_decimal": 58904
|
||||
},
|
||||
{
|
||||
"icon_id": "33053718",
|
||||
"name": "ops-dag-appletadmin",
|
||||
"font_class": "ops-dag-appletadmin",
|
||||
"unicode": "e65c",
|
||||
"unicode_decimal": 58972
|
||||
},
|
||||
{
|
||||
"icon_id": "33053720",
|
||||
"name": "ops-dag-appletadmin-selected",
|
||||
"font_class": "ops-dag-appletadmin-selected",
|
||||
"unicode": "e65d",
|
||||
"unicode_decimal": 58973
|
||||
},
|
||||
{
|
||||
"icon_id": "33055163",
|
||||
"name": "ops-dag-dags",
|
||||
|
@@ -30,9 +30,9 @@ export function getAuthDataEnable() {
|
||||
})
|
||||
}
|
||||
|
||||
export function testLDAP(test_type, data) {
|
||||
export function testLDAP(data) {
|
||||
return axios({
|
||||
url: `/common-setting/v1/auth_config/LDAP/test?test_type=${test_type}`,
|
||||
url: `/common-setting/v1/auth_config/LDAP/test`,
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 6.9 KiB |
@@ -1,14 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0H2.5V1.25H1.25V2.5H0V1C0 0.447715 0.447715 0 1 0ZM0 7.5V9C0 9.55229 0.447715 10 1 10H2.5V8.75H1.25V7.5H0ZM8.75 7.5V8.75H7.5V10H9C9.55229 10 10 9.55228 10 9V7.5H8.75ZM10 2.5V1C10 0.447715 9.55228 0 9 0H7.5V1.25H8.75V2.5H10Z" fill="url(#paint0_linear_124_16807)"/>
|
||||
<rect x="2.5" y="3.125" width="5" height="3.75" fill="url(#paint1_linear_124_16807)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16807" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4F84FF"/>
|
||||
<stop offset="1" stop-color="#85CBFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_124_16807" x1="5" y1="3.125" x2="5" y2="6.875" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4F84FF"/>
|
||||
<stop offset="1" stop-color="#85CBFF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 916 B |
@@ -1,14 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.5" y="2.5" width="5" height="5" rx="0.5" fill="url(#paint0_linear_124_16808)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0C0.447715 0 0 0.447715 0 1V9C0 9.55229 0.447715 10 1 10H9C9.55229 10 10 9.55228 10 9V1C10 0.447715 9.55228 0 9 0H1ZM8.75 1.25H1.25V8.75H8.75V1.25Z" fill="url(#paint1_linear_124_16808)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16808" x1="5" y1="2.5" x2="5" y2="7.5" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5187FF"/>
|
||||
<stop offset="1" stop-color="#84C9FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_124_16808" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5187FF"/>
|
||||
<stop offset="1" stop-color="#84C9FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 840 B |
@@ -1,9 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.56845 8.8409C3.06335 8.78963 1.86719 8.05799 2.06279 6.48243C2.1538 5.75105 2.64549 5.3214 3.34457 5.16041C3.67173 5.08909 4.00806 5.06954 4.34128 5.10247C4.40203 5.10811 4.44843 5.11401 4.47689 5.11837L4.51586 5.12631C4.64379 5.15574 4.77263 5.18104 4.90218 5.20219C5.26786 5.2651 5.63914 5.28941 6.0099 5.27474C6.8046 5.23219 7.21015 4.97429 7.23092 4.41672C7.25424 3.79429 6.76332 3.29619 5.86659 2.91832C5.52815 2.77793 5.17843 2.66645 4.82117 2.58506C4.70325 2.55755 4.58482 2.53328 4.46587 2.51226C4.30323 2.94847 3.9867 3.31016 3.57591 3.5292C3.16512 3.74824 2.68841 3.80952 2.23557 3.70149C1.90324 3.61651 1.60053 3.44214 1.36029 3.1973C1.12004 2.95245 0.951447 2.64649 0.872793 2.3126C0.794138 1.97872 0.808429 1.62967 0.914116 1.30333C1.0198 0.976995 1.21285 0.685836 1.4723 0.461451C1.73176 0.237065 2.04771 0.0880244 2.38588 0.0305017C2.72404 -0.0270211 3.07151 0.00917138 3.39056 0.135152C3.70961 0.261132 3.98807 0.472088 4.19571 0.745127C4.40335 1.01817 4.53225 1.34286 4.56841 1.68397C4.6812 1.70269 4.83374 1.73217 5.01524 1.77421C5.42003 1.86601 5.81625 1.99216 6.1996 2.15131C7.38191 2.64966 8.1156 3.39463 8.07638 4.4462C8.03639 5.53187 7.23425 6.04253 6.0563 6.10533C5.62418 6.12373 5.19132 6.09614 4.76503 6.02304C4.61925 5.99997 4.47398 5.9716 4.32923 5.93793C4.30731 5.93532 4.28534 5.9331 4.26335 5.93127C4.02033 5.90687 3.77501 5.92018 3.53606 5.97075C3.15153 6.05893 2.94311 6.24146 2.90056 6.58267C2.78725 7.49504 3.47915 7.94443 5.42694 8.00416C5.44492 7.65558 5.5586 7.3187 5.75548 7.03049C5.95237 6.74229 6.22485 6.51389 6.54303 6.37039C6.8612 6.22689 7.21277 6.17383 7.55912 6.21703C7.90548 6.26023 8.23323 6.39802 8.50641 6.61528C8.77959 6.83254 8.98763 7.12086 9.10769 7.4486C9.22775 7.77634 9.25519 8.13082 9.187 8.47314C9.11881 8.81545 8.95763 9.13235 8.72114 9.38907C8.48465 9.64578 8.18201 9.83237 7.84643 9.92836C7.39921 10.0556 6.92094 10.0153 6.50129 9.81515C6.08164 9.61495 5.74941 9.26855 5.56691 8.8409H5.56845Z" fill="url(#paint0_linear_124_16804)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16804" x1="5.02318" y1="0.00390625" x2="5.02318" y2="10.0013" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#497DFF"/>
|
||||
<stop offset="1" stop-color="#8CD5FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -1,9 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.01211 4.50621L2.7769 5.74077C2.712 5.80565 2.66051 5.88268 2.62538 5.96747C2.59025 6.05225 2.57217 6.14313 2.57217 6.2349C2.57217 6.32668 2.59025 6.41755 2.62538 6.50234C2.66051 6.58712 2.712 6.66416 2.7769 6.72904L3.27085 7.223C3.33573 7.28791 3.41276 7.3394 3.49754 7.37453C3.58232 7.40966 3.67319 7.42774 3.76496 7.42774C3.85674 7.42774 3.94761 7.40966 4.03239 7.37453C4.11717 7.3394 4.1942 7.28791 4.25908 7.223L5.49394 5.98775C5.6237 6.1175 5.72663 6.27155 5.79686 6.44109C5.86708 6.61063 5.90323 6.79234 5.90323 6.97585C5.90323 7.15935 5.86708 7.34106 5.79686 7.5106C5.72663 7.68014 5.6237 7.83419 5.49394 7.96394L3.76479 9.69316C3.56827 9.88963 3.30176 10 3.02387 10C2.74599 10 2.47948 9.88963 2.28296 9.69316L0.306832 7.71696C0.110368 7.52043 0 7.25391 0 6.97602C0 6.69813 0.110368 6.43161 0.306832 6.23508L2.03599 4.50586C2.16574 4.3761 2.31978 4.27317 2.48931 4.20294C2.65884 4.13271 2.84055 4.09657 3.02405 4.09657C3.20755 4.09657 3.38925 4.13271 3.55879 4.20294C3.72832 4.27317 3.88236 4.3761 4.01211 4.50586V4.50621ZM5.98789 5.49414L7.2231 4.25923C7.288 4.19435 7.33949 4.11732 7.37462 4.03253C7.40975 3.94775 7.42783 3.85687 7.42783 3.7651C7.42783 3.67332 7.40975 3.58245 7.37462 3.49766C7.33949 3.41288 7.288 3.33584 7.2231 3.27096L6.72915 2.777C6.66428 2.71209 6.58724 2.6606 6.50246 2.62547C6.41768 2.59034 6.32681 2.57226 6.23504 2.57226C6.14326 2.57226 6.05239 2.59034 5.96761 2.62547C5.88283 2.6606 5.8058 2.71209 5.74092 2.777L4.50606 4.01225C4.3763 3.8825 4.27337 3.72845 4.20314 3.55891C4.13292 3.38937 4.09677 3.20766 4.09677 3.02415C4.09677 2.84065 4.13292 2.65894 4.20314 2.4894C4.27337 2.31986 4.3763 2.16581 4.50606 2.03606L6.23521 0.306843C6.43173 0.110371 6.69824 0 6.97613 0C7.25401 0 7.52052 0.110371 7.71704 0.306843L9.69317 2.28304C9.88963 2.47957 10 2.74609 10 3.02398C10 3.30187 9.88963 3.56839 9.69317 3.76492L7.96401 5.49414C7.83426 5.6239 7.68022 5.72683 7.51069 5.79706C7.34116 5.86729 7.15945 5.90343 6.97595 5.90343C6.79245 5.90343 6.61075 5.86729 6.44121 5.79706C6.27168 5.72683 6.11764 5.6239 5.98789 5.49414ZM3.51817 5.9881L5.98789 3.51829C6.05339 3.45274 6.14225 3.4159 6.23491 3.41586C6.32758 3.41583 6.41646 3.45261 6.48201 3.51812C6.54755 3.58362 6.5844 3.67248 6.58443 3.76515C6.58446 3.85782 6.54768 3.9467 6.48218 4.01225L4.01211 6.48206C3.94661 6.54761 3.85775 6.58445 3.76509 6.58449C3.67242 6.58452 3.58354 6.54774 3.51799 6.48223C3.45245 6.41673 3.4156 6.32787 3.41557 6.2352C3.41554 6.14253 3.45232 6.05365 3.51782 5.9881H3.51817Z" fill="url(#paint0_linear_124_16775)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16775" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5A85FF"/>
|
||||
<stop offset="1" stop-color="#8DD8FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -1,9 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.31822 4.16667H2.54549V2.5C2.54549 1.11458 3.63981 0 5.00003 0C6.36026 0 7.45458 1.11458 7.45458 2.5V4.16667H8.68185C8.90685 4.16667 9.09094 4.35417 9.09094 4.58333V9.58333C9.09094 9.8125 8.90685 10 8.68185 10H1.31822C1.09322 10 0.909124 9.8125 0.909124 9.58333V4.58333C0.909124 4.35417 1.09322 4.16667 1.31822 4.16667ZM5.00003 7.91667C5.45003 7.91667 5.81822 7.54167 5.81822 7.08333C5.81822 6.625 5.45003 6.25 5.00003 6.25C4.55003 6.25 4.18185 6.625 4.18185 7.08333C4.18185 7.54167 4.55003 7.91667 5.00003 7.91667ZM3.36367 4.16667H6.6364V2.5C6.6364 1.58333 5.90003 0.833333 5.00003 0.833333C4.10003 0.833333 3.36367 1.58333 3.36367 2.5V4.16667Z" fill="url(#paint0_linear_124_16805)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16805" x1="5.00003" y1="0" x2="5.00003" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4D82FF"/>
|
||||
<stop offset="1" stop-color="#88CFFF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 1022 B |
@@ -1,9 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.91242 9.46382C3.91242 9.57428 3.82288 9.66382 3.71242 9.66382H2.35075C2.2403 9.66382 2.15075 9.57428 2.15075 9.46382V3.55962C2.15075 3.44916 2.06121 3.35962 1.95075 3.35962H0.539905C0.354312 3.35962 0.268806 3.12879 0.40961 3.00788L3.58212 0.283626C3.71182 0.172253 3.91242 0.264405 3.91242 0.43536V9.46382ZM6.08758 0.567715C6.08758 0.457258 6.17712 0.367716 6.28758 0.367716H7.64925C7.7597 0.367716 7.84925 0.457259 7.84925 0.567716V6.4411C7.84925 6.55156 7.93879 6.6411 8.04925 6.6411H9.46001C9.64561 6.6411 9.73111 6.87195 9.59029 6.99285L6.41786 9.71645C6.28816 9.8278 6.08758 9.73565 6.08758 9.5647V0.567715Z" fill="url(#paint0_linear_124_16806)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16806" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5A85FF"/>
|
||||
<stop offset="1" stop-color="#8DD8FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 979 B |
@@ -1,9 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.51961 6.8937V10H4.48V6.8937H1.76732C1.65823 6.8937 1.56223 6.85372 1.48369 6.77237C1.40522 6.69504 1.3621 6.5915 1.36369 6.48421C1.36369 5.95891 1.52769 5.48566 1.85895 5.06411C2.18986 4.64428 2.56258 4.43334 2.97893 4.43334V1.64277C2.75966 1.64277 2.57167 1.56142 2.41022 1.39873C2.25355 1.24349 2.16738 1.0362 2.17022 0.821384C2.17022 0.598718 2.25022 0.407762 2.41022 0.244037C2.56912 0.0827244 2.7593 0 2.97893 0H7.01959C7.23885 0 7.42685 0.0813456 7.5883 0.244037C7.74721 0.406728 7.82866 0.598718 7.82866 0.821384C7.82866 1.04405 7.74866 1.23501 7.58867 1.39873C7.4283 1.5628 7.23885 1.64277 7.01959 1.64277V4.43196C7.43594 4.43196 7.81012 4.64291 8.13956 5.06273C8.46631 5.47151 8.64098 5.97137 8.63628 6.48421C8.63628 6.59486 8.59701 6.6924 8.51665 6.77237C8.43665 6.85234 8.34211 6.8937 8.23302 6.8937H5.51998H5.51961Z" fill="url(#paint0_linear_124_16803)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16803" x1="5.00001" y1="0" x2="5.00001" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5A85FF"/>
|
||||
<stop offset="1" stop-color="#8DD8FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 35 KiB |
@@ -1,41 +1,41 @@
|
||||
import i18n from '@/lang'
|
||||
|
||||
export const ruleTypeList = () => {
|
||||
return [
|
||||
{ value: 'and', label: i18n.t('cmdbFilterComp.and') },
|
||||
{ value: 'or', label: i18n.t('cmdbFilterComp.or') },
|
||||
// { value: 'not', label: '非' },
|
||||
]
|
||||
}
|
||||
|
||||
export const expList = () => {
|
||||
return [
|
||||
{ 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: '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: '~value', label: i18n.t('cmdbFilterComp.~value') }, // 为空的定义有点绕
|
||||
{ value: 'value', label: i18n.t('cmdbFilterComp.value') },
|
||||
]
|
||||
}
|
||||
|
||||
export const advancedExpList = () => {
|
||||
return [
|
||||
{ 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: 'compare', label: i18n.t('cmdbFilterComp.compare') },
|
||||
]
|
||||
}
|
||||
|
||||
export const compareTypeList = [
|
||||
{ value: '1', label: '>' },
|
||||
{ value: '2', label: '>=' },
|
||||
{ value: '3', label: '<' },
|
||||
{ value: '4', label: '<=' },
|
||||
]
|
||||
import i18n from '@/lang'
|
||||
|
||||
export const ruleTypeList = () => {
|
||||
return [
|
||||
{ value: 'and', label: i18n.t('cmdbFilterComp.and') },
|
||||
{ value: 'or', label: i18n.t('cmdbFilterComp.or') },
|
||||
// { value: 'not', label: '非' },
|
||||
]
|
||||
}
|
||||
|
||||
export const expList = () => {
|
||||
return [
|
||||
{ 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: '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: '~value', label: i18n.t('cmdbFilterComp.~value') }, // 为空的定义有点绕
|
||||
{ value: 'value', label: i18n.t('cmdbFilterComp.value') },
|
||||
]
|
||||
}
|
||||
|
||||
export const advancedExpList = () => {
|
||||
return [
|
||||
{ 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: 'compare', label: i18n.t('cmdbFilterComp.compare') },
|
||||
]
|
||||
}
|
||||
|
||||
export const compareTypeList = [
|
||||
{ value: '1', label: '>' },
|
||||
{ value: '2', label: '>=' },
|
||||
{ value: '3', label: '<' },
|
||||
{ value: '4', label: '<=' },
|
||||
]
|
||||
|
@@ -1,332 +1,346 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id">
|
||||
<div :style="{ width: '70px', height: '24px', position: 'relative' }">
|
||||
<treeselect
|
||||
v-if="index"
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '70px', '--custom-height': '24px', position: 'absolute', top: '-17px', left: 0 }"
|
||||
v-model="item.type"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="ruleTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
</treeselect>
|
||||
</div>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '130px', '--custom-height': '24px' }"
|
||||
v-model="item.property"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="canSearchPreferenceAttrList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.name,
|
||||
label: node.alias || node.name,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" />
|
||||
{{ node.label }}
|
||||
</div>
|
||||
<div
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
slot="value-label"
|
||||
slot-scope="{ node }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" /> {{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '100px', '--custom-height': '24px' }"
|
||||
v-model="item.exp"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="[...getExpListByProperty(item.property), ...advancedExpList]"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
@select="(value) => handleChangeExp(value, item, index)"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '175px', '--custom-height': '24px' }"
|
||||
v-model="item.value"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
v-if="isChoiceByProperty(item.property) && (item.exp === 'is' || item.exp === '~is')"
|
||||
:options="getChoiceValueByProperty(item.property)"
|
||||
:placeholder="$t('placeholder2')"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node[0],
|
||||
label: node[0],
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
{{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<a-input-group
|
||||
size="small"
|
||||
compact
|
||||
v-else-if="item.exp === 'range' || item.exp === '~range'"
|
||||
:style="{ width: '175px' }"
|
||||
>
|
||||
<a-input
|
||||
class="ops-input"
|
||||
size="small"
|
||||
v-model="item.min"
|
||||
:style="{ width: '78px' }"
|
||||
:placeholder="$t('min')"
|
||||
/>
|
||||
~
|
||||
<a-input
|
||||
class="ops-input"
|
||||
size="small"
|
||||
v-model="item.max"
|
||||
:style="{ width: '78px' }"
|
||||
:placeholder="$t('max')"
|
||||
/>
|
||||
</a-input-group>
|
||||
<a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }">
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '60px', '--custom-height': '24px' }"
|
||||
v-model="item.compareType"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="compareTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
>
|
||||
</treeselect>
|
||||
<a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" />
|
||||
</a-input-group>
|
||||
<a-input
|
||||
v-else-if="item.exp !== 'value' && item.exp !== '~value'"
|
||||
size="small"
|
||||
v-model="item.value"
|
||||
:placeholder="item.exp === 'in' || item.exp === '~in' ? $t('cmdbFilterComp.split', { separator: ';' }) : ''"
|
||||
class="ops-input"
|
||||
:style="{ width: '175px' }"
|
||||
></a-input>
|
||||
<div v-else :style="{ width: '175px' }"></div>
|
||||
<a-tooltip :title="$t('copy')">
|
||||
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="$t('delete')">
|
||||
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="$t('cmdbFilterComp.addHere')" v-if="needAddHere">
|
||||
<a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
<div class="table-filter-add">
|
||||
<a @click="handleAddRule">+ {{ $t('new') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants'
|
||||
import ValueTypeMapIcon from '../CMDBValueTypeMapIcon'
|
||||
|
||||
export default {
|
||||
name: 'Expression',
|
||||
components: { ValueTypeMapIcon },
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
needAddHere: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
compareTypeList,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
ruleList: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
ruleTypeList() {
|
||||
return ruleTypeList()
|
||||
},
|
||||
expList() {
|
||||
return expList()
|
||||
},
|
||||
advancedExpList() {
|
||||
return advancedExpList()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getExpListByProperty(property) {
|
||||
if (property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) {
|
||||
return [
|
||||
{ value: 'is', label: this.$t('cmdbFilterComp.is') },
|
||||
{ value: '~is', label: this.$t('cmdbFilterComp.~is') },
|
||||
{ value: '~value', label: this.$t('cmdbFilterComp.~value') }, // 为空的定义有点绕
|
||||
{ value: 'value', label: this.$t('cmdbFilterComp.value') },
|
||||
]
|
||||
}
|
||||
return this.expList
|
||||
}
|
||||
return this.expList
|
||||
},
|
||||
isChoiceByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.is_choice
|
||||
}
|
||||
return false
|
||||
},
|
||||
handleAddRule() {
|
||||
this.ruleList.push({
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0]?.name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
})
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleCopyRule(item) {
|
||||
this.ruleList.push({ ...item, id: uuidv4() })
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleDeleteRule(item) {
|
||||
const idx = this.ruleList.findIndex((r) => r.id === item.id)
|
||||
if (idx > -1) {
|
||||
this.ruleList.splice(idx, 1)
|
||||
}
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleAddRuleAt(item) {
|
||||
const idx = this.ruleList.findIndex((r) => r.id === item.id)
|
||||
if (idx > -1) {
|
||||
this.ruleList.splice(idx, 0, {
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0]?.name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
})
|
||||
}
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
getChoiceValueByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.choice_value
|
||||
}
|
||||
return []
|
||||
},
|
||||
handleChangeExp({ value }, item, index) {
|
||||
const _ruleList = _.cloneDeep(this.ruleList)
|
||||
if (value === 'range') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
min: '',
|
||||
max: '',
|
||||
exp: value,
|
||||
}
|
||||
} else if (value === 'compare') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
compareType: '1',
|
||||
exp: value,
|
||||
}
|
||||
} else {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
exp: value,
|
||||
}
|
||||
}
|
||||
this.ruleList = _ruleList
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<template>
|
||||
<div>
|
||||
<a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id">
|
||||
<div :style="{ width: '70px', height: '24px', position: 'relative' }">
|
||||
<treeselect
|
||||
v-if="index"
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '70px', '--custom-height': '24px', position: 'absolute', top: '-17px', left: 0 }"
|
||||
v-model="item.type"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="ruleTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
:disabled="disabled"
|
||||
>
|
||||
</treeselect>
|
||||
</div>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '130px', '--custom-height': '24px' }"
|
||||
v-model="item.property"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="canSearchPreferenceAttrList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.name,
|
||||
label: node.alias || node.name,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" />
|
||||
{{ node.label }}
|
||||
</div>
|
||||
<div
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
slot="value-label"
|
||||
slot-scope="{ node }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" /> {{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '100px', '--custom-height': '24px' }"
|
||||
v-model="item.exp"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="[...getExpListByProperty(item.property), ...advancedExpList]"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
@select="(value) => handleChangeExp(value, item, index)"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '175px', '--custom-height': '24px' }"
|
||||
v-model="item.value"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
v-if="isChoiceByProperty(item.property) && (item.exp === 'is' || item.exp === '~is')"
|
||||
:options="getChoiceValueByProperty(item.property)"
|
||||
:placeholder="$t('placeholder2')"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node[0],
|
||||
label: node[0],
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
{{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<a-input-group
|
||||
size="small"
|
||||
compact
|
||||
v-else-if="item.exp === 'range' || item.exp === '~range'"
|
||||
:style="{ width: '175px' }"
|
||||
>
|
||||
<a-input
|
||||
class="ops-input"
|
||||
size="small"
|
||||
v-model="item.min"
|
||||
:style="{ width: '78px' }"
|
||||
:placeholder="$t('min')"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
~
|
||||
<a-input
|
||||
class="ops-input"
|
||||
size="small"
|
||||
v-model="item.max"
|
||||
:style="{ width: '78px' }"
|
||||
:placeholder="$t('max')"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</a-input-group>
|
||||
<a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }">
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '60px', '--custom-height': '24px' }"
|
||||
v-model="item.compareType"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="compareTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
>
|
||||
</treeselect>
|
||||
<a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" />
|
||||
</a-input-group>
|
||||
<a-input
|
||||
v-else-if="item.exp !== 'value' && item.exp !== '~value'"
|
||||
size="small"
|
||||
v-model="item.value"
|
||||
:placeholder="item.exp === 'in' || item.exp === '~in' ? $t('cmdbFilterComp.split', { separator: ';' }) : ''"
|
||||
class="ops-input"
|
||||
:style="{ width: '175px' }"
|
||||
:disabled="disabled"
|
||||
></a-input>
|
||||
<div v-else :style="{ width: '175px' }"></div>
|
||||
<template v-if="!disabled">
|
||||
<a-tooltip :title="$t('copy')">
|
||||
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="veops-copy"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="$t('delete')">
|
||||
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="$t('cmdbFilterComp.addHere')" v-if="needAddHere">
|
||||
<a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-space>
|
||||
<div class="table-filter-add" v-if="!disabled">
|
||||
<a @click="handleAddRule">+ {{ $t('new') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants'
|
||||
import ValueTypeMapIcon from '../CMDBValueTypeMapIcon'
|
||||
|
||||
export default {
|
||||
name: 'Expression',
|
||||
components: { ValueTypeMapIcon },
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
needAddHere: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
compareTypeList,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
ruleList: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
ruleTypeList() {
|
||||
return ruleTypeList()
|
||||
},
|
||||
expList() {
|
||||
return expList()
|
||||
},
|
||||
advancedExpList() {
|
||||
return advancedExpList()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getExpListByProperty(property) {
|
||||
if (property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) {
|
||||
return [
|
||||
{ value: 'is', label: this.$t('cmdbFilterComp.is') },
|
||||
{ value: '~is', label: this.$t('cmdbFilterComp.~is') },
|
||||
{ value: '~value', label: this.$t('cmdbFilterComp.~value') }, // 为空的定义有点绕
|
||||
{ value: 'value', label: this.$t('cmdbFilterComp.value') },
|
||||
]
|
||||
}
|
||||
return this.expList
|
||||
}
|
||||
return this.expList
|
||||
},
|
||||
isChoiceByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.is_choice
|
||||
}
|
||||
return false
|
||||
},
|
||||
handleAddRule() {
|
||||
this.ruleList.push({
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0]?.name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
})
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleCopyRule(item) {
|
||||
this.ruleList.push({ ...item, id: uuidv4() })
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleDeleteRule(item) {
|
||||
const idx = this.ruleList.findIndex((r) => r.id === item.id)
|
||||
if (idx > -1) {
|
||||
this.ruleList.splice(idx, 1)
|
||||
}
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleAddRuleAt(item) {
|
||||
const idx = this.ruleList.findIndex((r) => r.id === item.id)
|
||||
if (idx > -1) {
|
||||
this.ruleList.splice(idx, 0, {
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0]?.name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
})
|
||||
}
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
getChoiceValueByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.choice_value
|
||||
}
|
||||
return []
|
||||
},
|
||||
handleChangeExp({ value }, item, index) {
|
||||
const _ruleList = _.cloneDeep(this.ruleList)
|
||||
if (value === 'range') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
min: '',
|
||||
max: '',
|
||||
exp: value,
|
||||
}
|
||||
} else if (value === 'compare') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
compareType: '1',
|
||||
exp: value,
|
||||
}
|
||||
} else {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
exp: value,
|
||||
}
|
||||
}
|
||||
this.ruleList = _ruleList
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@@ -1,296 +1,302 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-popover
|
||||
v-if="isDropdown"
|
||||
v-model="visible"
|
||||
trigger="click"
|
||||
:placement="placement"
|
||||
overlayClassName="table-filter"
|
||||
@visibleChange="visibleChange"
|
||||
>
|
||||
<slot name="popover_item">
|
||||
<a-button type="primary" ghost>{{ $t('cmdbFilterComp.conditionFilter') }}<a-icon type="filter"/></a-button>
|
||||
</slot>
|
||||
<template slot="content">
|
||||
<Expression
|
||||
:needAddHere="needAddHere"
|
||||
v-model="ruleList"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||
/>
|
||||
<a-divider :style="{ margin: '10px 0' }" />
|
||||
<div style="width:554px">
|
||||
<a-space :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
||||
<a-button type="primary" size="small" @click="handleSubmit">{{ $t('confirm') }}</a-button>
|
||||
<a-button size="small" @click="handleClear">{{ $t('clear') }}</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<Expression
|
||||
:needAddHere="needAddHere"
|
||||
v-else
|
||||
v-model="ruleList"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import Expression from './expression.vue'
|
||||
import { advancedExpList, compareTypeList } from './constants'
|
||||
|
||||
export default {
|
||||
name: 'FilterComp',
|
||||
components: { Expression },
|
||||
props: {
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
expression: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
regQ: {
|
||||
type: String,
|
||||
default: '(?<=q=).+(?=&)|(?<=q=).+$',
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottomRight',
|
||||
},
|
||||
isDropdown: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
needAddHere: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
advancedExpList,
|
||||
compareTypeList,
|
||||
visible: false,
|
||||
ruleList: [],
|
||||
filterExp: '',
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
visibleChange(open, isInitOne = true) {
|
||||
// isInitOne 初始化exp为空时,ruleList是否默认给一条
|
||||
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
const exp = this.expression.match(new RegExp(this.regQ, 'g'))
|
||||
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
|
||||
: null
|
||||
if (open && exp) {
|
||||
const expArray = exp.split(',').map((item) => {
|
||||
let has_not = ''
|
||||
const key = item.split(':')[0]
|
||||
const val = item
|
||||
.split(':')
|
||||
.slice(1)
|
||||
.join(':')
|
||||
let type, property, exp, value, min, max, compareType
|
||||
if (key.includes('-')) {
|
||||
type = 'or'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(2)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key.substring(1)
|
||||
}
|
||||
} else {
|
||||
type = 'and'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(1)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key
|
||||
}
|
||||
}
|
||||
|
||||
const in_reg = /(?<=\().+(?=\))/g
|
||||
const range_reg = /(?<=\[).+(?=\])/g
|
||||
const compare_reg = /(?<=>=|<=|>(?!=)|<(?!=)).+/
|
||||
if (val === '*') {
|
||||
exp = has_not + 'value'
|
||||
value = ''
|
||||
} else if (in_reg.test(val)) {
|
||||
exp = has_not + 'in'
|
||||
value = val.match(in_reg)[0]
|
||||
} else if (range_reg.test(val)) {
|
||||
exp = has_not + 'range'
|
||||
value = val.match(range_reg)[0]
|
||||
min = value.split('_TO_')[0]
|
||||
max = value.split('_TO_')[1]
|
||||
} else if (compare_reg.test(val)) {
|
||||
exp = has_not + 'compare'
|
||||
value = val.match(compare_reg)[0]
|
||||
const _compareType = val.substring(0, val.match(compare_reg)['index'])
|
||||
const idx = compareTypeList.findIndex((item) => item.label === _compareType)
|
||||
compareType = compareTypeList[idx].value
|
||||
} else if (!val.includes('*')) {
|
||||
exp = has_not + 'is'
|
||||
value = val
|
||||
} else {
|
||||
const resList = [
|
||||
['contain', /(?<=\*).*(?=\*)/g],
|
||||
['end_with', /(?<=\*).+/g],
|
||||
['start_with', /.+(?=\*)/g],
|
||||
]
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const reg = resList[i]
|
||||
if (reg[1].test(val)) {
|
||||
exp = has_not + reg[0]
|
||||
value = val.match(reg[1])[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: uuidv4(),
|
||||
type,
|
||||
property,
|
||||
exp,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
compareType,
|
||||
}
|
||||
})
|
||||
this.ruleList = [...expArray]
|
||||
} else if (open) {
|
||||
const _canSearchPreferenceAttrList = this.canSearchPreferenceAttrList.filter((attr) => !attr.is_password)
|
||||
this.ruleList = isInitOne
|
||||
? [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property:
|
||||
_canSearchPreferenceAttrList && _canSearchPreferenceAttrList.length
|
||||
? _canSearchPreferenceAttrList[0].name
|
||||
: undefined,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
},
|
||||
handleClear() {
|
||||
this.ruleList = [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0].name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
this.filterExp = ''
|
||||
this.visible = false
|
||||
this.$emit('setExpFromFilter', this.filterExp)
|
||||
},
|
||||
handleSubmit() {
|
||||
if (this.ruleList && this.ruleList.length) {
|
||||
this.ruleList[0].type = 'and' // 增删后,以防万一第一个不是and
|
||||
this.filterExp = ''
|
||||
const expList = this.ruleList.map((rule) => {
|
||||
let singleRuleExp = ''
|
||||
let _exp = rule.exp
|
||||
if (rule.type === 'or') {
|
||||
singleRuleExp += '-'
|
||||
}
|
||||
if (rule.exp.includes('~')) {
|
||||
singleRuleExp += '~'
|
||||
_exp = rule.exp.split('~')[1]
|
||||
}
|
||||
singleRuleExp += `${rule.property}:`
|
||||
if (_exp === 'is') {
|
||||
singleRuleExp += `${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'contain') {
|
||||
singleRuleExp += `*${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'start_with') {
|
||||
singleRuleExp += `${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'end_with') {
|
||||
singleRuleExp += `*${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'value') {
|
||||
singleRuleExp += `*`
|
||||
}
|
||||
if (_exp === 'in') {
|
||||
singleRuleExp += `(${rule.value ?? ''})`
|
||||
}
|
||||
if (_exp === 'range') {
|
||||
singleRuleExp += `[${rule.min}_TO_${rule.max}]`
|
||||
}
|
||||
if (_exp === 'compare') {
|
||||
const idx = compareTypeList.findIndex((item) => item.value === rule.compareType)
|
||||
singleRuleExp += `${compareTypeList[idx].label}${rule.value ?? ''}`
|
||||
}
|
||||
return singleRuleExp
|
||||
})
|
||||
this.filterExp = expList.join(',')
|
||||
this.$emit('setExpFromFilter', this.filterExp)
|
||||
} else {
|
||||
this.$emit('setExpFromFilter', '')
|
||||
}
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table-filter {
|
||||
.table-filter-add {
|
||||
margin-top: 10px;
|
||||
& > a {
|
||||
padding: 2px 8px;
|
||||
&:hover {
|
||||
background-color: #f0faff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.table-filter-extra-icon {
|
||||
padding: 0px 2px;
|
||||
&:hover {
|
||||
display: inline-block;
|
||||
border-radius: 5px;
|
||||
background-color: #f0faff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.table-filter-extra-operation {
|
||||
.ant-popover-inner-content {
|
||||
padding: 3px 4px;
|
||||
.operation {
|
||||
cursor: pointer;
|
||||
width: 90px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 3px 4px;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
background-color: #f0faff;
|
||||
}
|
||||
> .anticon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div>
|
||||
<a-popover
|
||||
v-if="isDropdown"
|
||||
v-model="visible"
|
||||
trigger="click"
|
||||
:placement="placement"
|
||||
overlayClassName="table-filter"
|
||||
@visibleChange="visibleChange"
|
||||
>
|
||||
<slot name="popover_item">
|
||||
<a-button type="primary" ghost>{{ $t('cmdbFilterComp.conditionFilter') }}<a-icon type="filter"/></a-button>
|
||||
</slot>
|
||||
<template slot="content">
|
||||
<Expression
|
||||
:needAddHere="needAddHere"
|
||||
v-model="ruleList"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
<a-divider :style="{ margin: '10px 0' }" />
|
||||
<div style="width:554px">
|
||||
<a-space :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
||||
<a-button type="primary" size="small" @click="handleSubmit">{{ $t('confirm') }}</a-button>
|
||||
<a-button size="small" @click="handleClear">{{ $t('clear') }}</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<Expression
|
||||
:needAddHere="needAddHere"
|
||||
v-else
|
||||
v-model="ruleList"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import Expression from './expression.vue'
|
||||
import { advancedExpList, compareTypeList } from './constants'
|
||||
|
||||
export default {
|
||||
name: 'FilterComp',
|
||||
components: { Expression },
|
||||
props: {
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
expression: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
regQ: {
|
||||
type: String,
|
||||
default: '(?<=q=).+(?=&)|(?<=q=).+$',
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottomRight',
|
||||
},
|
||||
isDropdown: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
needAddHere: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
advancedExpList,
|
||||
compareTypeList,
|
||||
visible: false,
|
||||
ruleList: [],
|
||||
filterExp: '',
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
visibleChange(open, isInitOne = true) {
|
||||
// isInitOne 初始化exp为空时,ruleList是否默认给一条
|
||||
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
const exp = this.expression.match(new RegExp(this.regQ, 'g'))
|
||||
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
|
||||
: null
|
||||
if (open && exp) {
|
||||
const expArray = exp.split(',').map((item) => {
|
||||
let has_not = ''
|
||||
const key = item.split(':')[0]
|
||||
const val = item
|
||||
.split(':')
|
||||
.slice(1)
|
||||
.join(':')
|
||||
let type, property, exp, value, min, max, compareType
|
||||
if (key.includes('-')) {
|
||||
type = 'or'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(2)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key.substring(1)
|
||||
}
|
||||
} else {
|
||||
type = 'and'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(1)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key
|
||||
}
|
||||
}
|
||||
|
||||
const in_reg = /(?<=\().+(?=\))/g
|
||||
const range_reg = /(?<=\[).+(?=\])/g
|
||||
const compare_reg = /(?<=>=|<=|>(?!=)|<(?!=)).+/
|
||||
if (val === '*') {
|
||||
exp = has_not + 'value'
|
||||
value = ''
|
||||
} else if (in_reg.test(val)) {
|
||||
exp = has_not + 'in'
|
||||
value = val.match(in_reg)[0]
|
||||
} else if (range_reg.test(val)) {
|
||||
exp = has_not + 'range'
|
||||
value = val.match(range_reg)[0]
|
||||
min = value.split('_TO_')[0]
|
||||
max = value.split('_TO_')[1]
|
||||
} else if (compare_reg.test(val)) {
|
||||
exp = has_not + 'compare'
|
||||
value = val.match(compare_reg)[0]
|
||||
const _compareType = val.substring(0, val.match(compare_reg)['index'])
|
||||
const idx = compareTypeList.findIndex((item) => item.label === _compareType)
|
||||
compareType = compareTypeList[idx].value
|
||||
} else if (!val.includes('*')) {
|
||||
exp = has_not + 'is'
|
||||
value = val
|
||||
} else {
|
||||
const resList = [
|
||||
['contain', /(?<=\*).*(?=\*)/g],
|
||||
['end_with', /(?<=\*).+/g],
|
||||
['start_with', /.+(?=\*)/g],
|
||||
]
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const reg = resList[i]
|
||||
if (reg[1].test(val)) {
|
||||
exp = has_not + reg[0]
|
||||
value = val.match(reg[1])[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: uuidv4(),
|
||||
type,
|
||||
property,
|
||||
exp,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
compareType,
|
||||
}
|
||||
})
|
||||
this.ruleList = [...expArray]
|
||||
} else if (open) {
|
||||
const _canSearchPreferenceAttrList = this.canSearchPreferenceAttrList.filter((attr) => !attr.is_password)
|
||||
this.ruleList = isInitOne
|
||||
? [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property:
|
||||
_canSearchPreferenceAttrList && _canSearchPreferenceAttrList.length
|
||||
? _canSearchPreferenceAttrList[0].name
|
||||
: undefined,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
},
|
||||
handleClear() {
|
||||
this.ruleList = [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0].name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
this.filterExp = ''
|
||||
this.visible = false
|
||||
this.$emit('setExpFromFilter', this.filterExp)
|
||||
},
|
||||
handleSubmit() {
|
||||
if (this.ruleList && this.ruleList.length) {
|
||||
this.ruleList[0].type = 'and' // 增删后,以防万一第一个不是and
|
||||
this.filterExp = ''
|
||||
const expList = this.ruleList.map((rule) => {
|
||||
let singleRuleExp = ''
|
||||
let _exp = rule.exp
|
||||
if (rule.type === 'or') {
|
||||
singleRuleExp += '-'
|
||||
}
|
||||
if (rule.exp.includes('~')) {
|
||||
singleRuleExp += '~'
|
||||
_exp = rule.exp.split('~')[1]
|
||||
}
|
||||
singleRuleExp += `${rule.property}:`
|
||||
if (_exp === 'is') {
|
||||
singleRuleExp += `${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'contain') {
|
||||
singleRuleExp += `*${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'start_with') {
|
||||
singleRuleExp += `${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'end_with') {
|
||||
singleRuleExp += `*${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'value') {
|
||||
singleRuleExp += `*`
|
||||
}
|
||||
if (_exp === 'in') {
|
||||
singleRuleExp += `(${rule.value ?? ''})`
|
||||
}
|
||||
if (_exp === 'range') {
|
||||
singleRuleExp += `[${rule.min}_TO_${rule.max}]`
|
||||
}
|
||||
if (_exp === 'compare') {
|
||||
const idx = compareTypeList.findIndex((item) => item.value === rule.compareType)
|
||||
singleRuleExp += `${compareTypeList[idx].label}${rule.value ?? ''}`
|
||||
}
|
||||
return singleRuleExp
|
||||
})
|
||||
this.filterExp = expList.join(',')
|
||||
this.$emit('setExpFromFilter', this.filterExp)
|
||||
} else {
|
||||
this.$emit('setExpFromFilter', '')
|
||||
}
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table-filter {
|
||||
.table-filter-add {
|
||||
margin-top: 10px;
|
||||
& > a {
|
||||
padding: 2px 8px;
|
||||
&:hover {
|
||||
background-color: #f0faff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.table-filter-extra-icon {
|
||||
padding: 0px 2px;
|
||||
&:hover {
|
||||
display: inline-block;
|
||||
border-radius: 5px;
|
||||
background-color: #f0faff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.table-filter-extra-operation {
|
||||
.ant-popover-inner-content {
|
||||
padding: 3px 4px;
|
||||
.operation {
|
||||
cursor: pointer;
|
||||
width: 90px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 3px 4px;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
background-color: #f0faff;
|
||||
}
|
||||
> .anticon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -17,25 +17,29 @@ export default {
|
||||
getPropertyIcon(attr) {
|
||||
switch (attr.value_type) {
|
||||
case '0':
|
||||
return 'icon-xianxing-shishu'
|
||||
return 'duose-shishu'
|
||||
case '1':
|
||||
return 'icon-xianxing-fudianshu'
|
||||
return 'duose-fudianshu'
|
||||
case '2':
|
||||
if (attr.is_password) {
|
||||
return 'icon-xianxing-password'
|
||||
return 'duose-password'
|
||||
}
|
||||
if (attr.is_link) {
|
||||
return 'icon-xianxing-link'
|
||||
return 'duose-link'
|
||||
}
|
||||
return 'icon-xianxing-wenben'
|
||||
return 'duose-wenben'
|
||||
case '3':
|
||||
return 'icon-xianxing-datetime'
|
||||
return 'duose-datetime'
|
||||
case '4':
|
||||
return 'icon-xianxing-date'
|
||||
return 'duose-date'
|
||||
case '5':
|
||||
return 'icon-xianxing-time'
|
||||
return 'duose-time'
|
||||
case '6':
|
||||
return 'icon-xianxing-json'
|
||||
return 'duose-json'
|
||||
case '7':
|
||||
return 'duose-password'
|
||||
case '8':
|
||||
return 'duose-link'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@@ -69,12 +69,11 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.custom-drawer-close {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
background: #custom_colors[color_1];
|
||||
background: @primary-color;
|
||||
color: white;
|
||||
text-align: center;
|
||||
transition: all 0.3s;
|
||||
|
@@ -759,6 +759,52 @@ export const multicolorIconList = [
|
||||
value: 'caise-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',
|
||||
label: '操作系统',
|
||||
@@ -976,17 +1022,14 @@ export const multicolorIconList = [
|
||||
value: 'caise-tomcat',
|
||||
label: 'Tomcat'
|
||||
}, {
|
||||
value: 'caise-aliyun',
|
||||
label: '阿里云'
|
||||
value: 'caise-VPC',
|
||||
label: 'VPC'
|
||||
}, {
|
||||
value: 'caise-tengxunyun',
|
||||
label: '腾讯云'
|
||||
value: 'caise-CDN',
|
||||
label: 'CDN'
|
||||
}, {
|
||||
value: 'caise-huaweiyun',
|
||||
label: '华为云'
|
||||
}, {
|
||||
value: 'caise-aws',
|
||||
label: 'AWS'
|
||||
value: 'caise-OOS',
|
||||
label: '对象存储'
|
||||
}]
|
||||
}, {
|
||||
value: 'data',
|
||||
|
@@ -230,7 +230,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
.employee-transfer {
|
||||
width: 100%;
|
||||
.vue-treeselect__multi-value-item-container {
|
||||
@@ -263,7 +262,6 @@ export default {
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.employee-transfer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -300,14 +298,14 @@ export default {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<a-layout-sider
|
||||
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null]"
|
||||
width="200px"
|
||||
width="220px"
|
||||
:collapsible="collapsible"
|
||||
v-model="collapsed"
|
||||
:trigger="null"
|
||||
@@ -15,6 +15,7 @@
|
||||
@select="onSelect"
|
||||
style="padding: 16px 0px;"
|
||||
></s-menu>
|
||||
<!-- <OpsDocs :collapsed="collapsed" /> -->
|
||||
</a-layout-sider>
|
||||
</template>
|
||||
|
||||
@@ -22,10 +23,13 @@
|
||||
import Logo from '@/components/tools/Logo'
|
||||
import SMenu from './index'
|
||||
import { mixin, mixinDevice } from '@/utils/mixin'
|
||||
// import OpsDocs from '@/modules/docs/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'SideMenu',
|
||||
components: { Logo, SMenu },
|
||||
components: { Logo, SMenu,
|
||||
// OpsDocs
|
||||
},
|
||||
mixins: [mixin, mixinDevice],
|
||||
props: {
|
||||
mode: {
|
||||
|
@@ -1,121 +1,121 @@
|
||||
<template>
|
||||
<vxe-table v-bind="$attrs" v-on="new$listeners" ref="xTable">
|
||||
<slot></slot>
|
||||
<template #empty>
|
||||
<slot name="empty">
|
||||
<div :style="{ paddingTop: '10px' }">
|
||||
<img :style="{ width: '100px', height: '90px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<div>{{ $t('noData') }}</div>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
<template #loading>
|
||||
<slot name="loading"></slot>
|
||||
</template>
|
||||
</vxe-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
// 该组件使用方法与vxe-table一致,但调用它的方法时,需先调用getVxetableRef()获取到vxe-table实体
|
||||
export default {
|
||||
name: 'OpsTable',
|
||||
data() {
|
||||
return {
|
||||
// isShifting: false,
|
||||
// lastIndex: -1,
|
||||
lastSelected: [],
|
||||
currentSelected: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
new$listeners() {
|
||||
if (!Object.keys(this.$listeners).length) {
|
||||
return this.$listeners
|
||||
}
|
||||
return Object.assign(this.$listeners, {
|
||||
// 在这里覆盖原有的change事件
|
||||
// 'checkbox-change': this.selectChangeEvent,
|
||||
'checkbox-range-change': this.checkboxRangeChange,
|
||||
'checkbox-range-start': this.checkboxRangeStart,
|
||||
'checkbox-range-end': this.checkboxRangeEnd,
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// window.onkeydown = (e) => {
|
||||
// if (e.key === 'Shift') {
|
||||
// this.isShifting = true
|
||||
// }
|
||||
// }
|
||||
// window.onkeyup = (e) => {
|
||||
// if (e.key === 'Shift') {
|
||||
// this.isShifting = false
|
||||
// this.lastIndex = -1
|
||||
// }
|
||||
// }
|
||||
},
|
||||
beforeDestroy() {
|
||||
// window.onkeydown = ''
|
||||
// window.onkeyup = ''
|
||||
},
|
||||
methods: {
|
||||
getVxetableRef() {
|
||||
return this.$refs.xTable
|
||||
},
|
||||
// selectChangeEvent(e) {
|
||||
// const xTable = this.$refs.xTable
|
||||
// const { lastIndex } = this
|
||||
// const currentIndex = e.rowIndex
|
||||
// const { tableData } = xTable.getTableData()
|
||||
// if (lastIndex > -1 && this.isShifting) {
|
||||
// let start = lastIndex
|
||||
// let end = currentIndex
|
||||
// if (lastIndex > currentIndex) {
|
||||
// start = currentIndex
|
||||
// end = lastIndex
|
||||
// }
|
||||
// const rangeData = tableData.slice(start, end + 1)
|
||||
// xTable.setCheckboxRow(rangeData, true)
|
||||
// }
|
||||
// this.lastIndex = currentIndex
|
||||
// this.$emit('checkbox-change', { ...e, records: xTable.getCheckboxRecords() })
|
||||
// },
|
||||
checkboxRangeStart(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
const lastSelected = xTable.getCheckboxRecords()
|
||||
const selectedReserve = xTable.getCheckboxReserveRecords()
|
||||
this.lastSelected = [...lastSelected, ...selectedReserve]
|
||||
this.$emit('checkbox-range-start', e)
|
||||
},
|
||||
checkboxRangeChange(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
xTable.setCheckboxRow(this.lastSelected, true)
|
||||
this.currentSelected = e.records
|
||||
// this.lastSelected = [...new Set([...this.lastSelected, ...e.records])]
|
||||
this.$emit('checkbox-range-change', {
|
||||
...e,
|
||||
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
|
||||
})
|
||||
},
|
||||
checkboxRangeEnd(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
const isAllSelected = this.currentSelected.every((item) => {
|
||||
const _idx = this.lastSelected.findIndex((ele) => _.isEqual(ele, item))
|
||||
return _idx > -1
|
||||
})
|
||||
if (isAllSelected) {
|
||||
xTable.setCheckboxRow(this.currentSelected, false)
|
||||
}
|
||||
this.currentSelected = []
|
||||
this.lastSelected = []
|
||||
this.$emit('checkbox-range-end', {
|
||||
...e,
|
||||
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
<template>
|
||||
<vxe-table v-bind="$attrs" v-on="new$listeners" ref="xTable">
|
||||
<slot></slot>
|
||||
<template #empty>
|
||||
<slot name="empty">
|
||||
<div :style="{ paddingTop: '10px' }">
|
||||
<img :style="{ width: '140px', height: '90px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<div>{{ $t('noData') }}</div>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
<template #loading>
|
||||
<slot name="loading"></slot>
|
||||
</template>
|
||||
</vxe-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
// 该组件使用方法与vxe-table一致,但调用它的方法时,需先调用getVxetableRef()获取到vxe-table实体
|
||||
export default {
|
||||
name: 'OpsTable',
|
||||
data() {
|
||||
return {
|
||||
// isShifting: false,
|
||||
// lastIndex: -1,
|
||||
lastSelected: [],
|
||||
currentSelected: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
new$listeners() {
|
||||
if (!Object.keys(this.$listeners).length) {
|
||||
return this.$listeners
|
||||
}
|
||||
return Object.assign(this.$listeners, {
|
||||
// 在这里覆盖原有的change事件
|
||||
// 'checkbox-change': this.selectChangeEvent,
|
||||
'checkbox-range-change': this.checkboxRangeChange,
|
||||
'checkbox-range-start': this.checkboxRangeStart,
|
||||
'checkbox-range-end': this.checkboxRangeEnd,
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// window.onkeydown = (e) => {
|
||||
// if (e.key === 'Shift') {
|
||||
// this.isShifting = true
|
||||
// }
|
||||
// }
|
||||
// window.onkeyup = (e) => {
|
||||
// if (e.key === 'Shift') {
|
||||
// this.isShifting = false
|
||||
// this.lastIndex = -1
|
||||
// }
|
||||
// }
|
||||
},
|
||||
beforeDestroy() {
|
||||
// window.onkeydown = ''
|
||||
// window.onkeyup = ''
|
||||
},
|
||||
methods: {
|
||||
getVxetableRef() {
|
||||
return this.$refs.xTable
|
||||
},
|
||||
// selectChangeEvent(e) {
|
||||
// const xTable = this.$refs.xTable
|
||||
// const { lastIndex } = this
|
||||
// const currentIndex = e.rowIndex
|
||||
// const { tableData } = xTable.getTableData()
|
||||
// if (lastIndex > -1 && this.isShifting) {
|
||||
// let start = lastIndex
|
||||
// let end = currentIndex
|
||||
// if (lastIndex > currentIndex) {
|
||||
// start = currentIndex
|
||||
// end = lastIndex
|
||||
// }
|
||||
// const rangeData = tableData.slice(start, end + 1)
|
||||
// xTable.setCheckboxRow(rangeData, true)
|
||||
// }
|
||||
// this.lastIndex = currentIndex
|
||||
// this.$emit('checkbox-change', { ...e, records: xTable.getCheckboxRecords() })
|
||||
// },
|
||||
checkboxRangeStart(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
const lastSelected = xTable.getCheckboxRecords()
|
||||
const selectedReserve = xTable.getCheckboxReserveRecords()
|
||||
this.lastSelected = [...lastSelected, ...selectedReserve]
|
||||
this.$emit('checkbox-range-start', e)
|
||||
},
|
||||
checkboxRangeChange(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
xTable.setCheckboxRow(this.lastSelected, true)
|
||||
this.currentSelected = e.records
|
||||
// this.lastSelected = [...new Set([...this.lastSelected, ...e.records])]
|
||||
this.$emit('checkbox-range-change', {
|
||||
...e,
|
||||
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
|
||||
})
|
||||
},
|
||||
checkboxRangeEnd(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
const isAllSelected = this.currentSelected.every((item) => {
|
||||
const _idx = this.lastSelected.findIndex((ele) => _.isEqual(ele, item))
|
||||
return _idx > -1
|
||||
})
|
||||
if (isAllSelected) {
|
||||
xTable.setCheckboxRow(this.currentSelected, false)
|
||||
}
|
||||
this.currentSelected = []
|
||||
this.lastSelected = []
|
||||
this.$emit('checkbox-range-end', {
|
||||
...e,
|
||||
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
|
2
cmdb-ui/src/components/Pager/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import Pager from './index.vue'
|
||||
export default Pager
|
137
cmdb-ui/src/components/Pager/index.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-row class="row" type="flex" justify="end">
|
||||
<a-col>
|
||||
<a-space align="end">
|
||||
<a-button
|
||||
class="left-button"
|
||||
size="small"
|
||||
:disabled="prevIsDisabled"
|
||||
@click="prevPage"
|
||||
><a-icon
|
||||
type="left"
|
||||
/></a-button>
|
||||
<a-button class="page-button" size="small">{{ currentPage }}</a-button>
|
||||
<a-button
|
||||
class="right-button"
|
||||
size="small"
|
||||
:disabled="nextIsDisabled"
|
||||
@click="nextPage"
|
||||
><a-icon
|
||||
type="right"
|
||||
/></a-button>
|
||||
<a-dropdown
|
||||
class="dropdown"
|
||||
size="small"
|
||||
placement="topCenter"
|
||||
:trigger="['click']"
|
||||
:disabled="dropdownIsDisabled"
|
||||
>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item v-for="(size, index) in pageSizes" :key="index" @click="handleItemClick(size)">
|
||||
{{ `${size}${$t('itemsPerPage')}` }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
<a-button size="small">{{ `${pageSize}${$t('itemsPerPage')}` }}<a-icon type="down" /> </a-button>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Pager',
|
||||
props: {
|
||||
currentPage: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dropdownIsDisabled: false,
|
||||
prevIsDisabled: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
nextIsDisabled() {
|
||||
return this.isLoading || this.total < this.pageSize
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
isLoading: {
|
||||
immediate: true,
|
||||
handler: function(val) {
|
||||
if (val) {
|
||||
this.dropdownIsDisabled = true
|
||||
this.prevIsDisabled = true
|
||||
} else {
|
||||
this.dropdownIsDisabled = false
|
||||
if (this.currentPage === 1) {
|
||||
this.prevIsDisabled = true
|
||||
} else {
|
||||
this.prevIsDisabled = false
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
currentPage: {
|
||||
immediate: true,
|
||||
handler: function(val) {
|
||||
if (val === 1) {
|
||||
this.prevIsDisabled = true
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleItemClick(size) {
|
||||
this.$emit('showSizeChange', size)
|
||||
},
|
||||
nextPage() {
|
||||
const pageNum = this.currentPage + 1
|
||||
this.$emit('change', pageNum)
|
||||
},
|
||||
prevPage() {
|
||||
const pageNum = this.currentPage - 1
|
||||
this.$emit('change', pageNum)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row {
|
||||
margin-top: 5px;
|
||||
.left-button {
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
.right-button {
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
.page-button {
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
19
cmdb-ui/src/components/RegexSelect/constants.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/* eslint-disable no-useless-escape */
|
||||
|
||||
import i18n from '@/lang'
|
||||
export const regList = () => {
|
||||
return [
|
||||
{ id: 'letter', label: i18n.t('regexSelect.letter'), value: '^[A-Za-z]+$', message: '请输入字母' },
|
||||
{ id: 'number', label: i18n.t('regexSelect.number'), value: '^-?(?!0\\d+)\\d+(\\.\\d+)?$', message: '请输入数字' },
|
||||
{ id: 'letterAndNumber', label: i18n.t('regexSelect.letterAndNumber'), value: '^[A-Za-z0-9.]+$', message: '请输入字母和数字' },
|
||||
{ id: 'phone', label: i18n.t('regexSelect.phone'), value: '^1[3-9]\\d{9}$', message: '请输入正确手机号码' },
|
||||
{ id: 'landline', label: i18n.t('regexSelect.landline'), value: '^(?:(?:\\d{3}-)?\\d{8}|^(?:\\d{4}-)?\\d{7,8})(?:-\\d+)?$', message: '请输入正确座机' },
|
||||
{ id: 'zipCode', label: i18n.t('regexSelect.zipCode'), value: '^(0[1-7]|1[0-356]|2[0-7]|3[0-6]|4[0-7]|5[1-7]|6[1-7]|7[0-5]|8[013-6])\\d{4}$', message: '请输入正确邮政编码' },
|
||||
{ id: 'IDCard', label: i18n.t('regexSelect.IDCard'), value: '(^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}$)', message: '请输入正确身份证号' },
|
||||
{ id: 'ip', label: i18n.t('regexSelect.ip'), value: '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$', message: '请输入正确IP地址' },
|
||||
{ id: 'email', label: i18n.t('regexSelect.email'), value: '^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\.\\w+([-.]\\w+)*$', message: '请输入正确邮箱' },
|
||||
{ id: 'link', label: i18n.t('regexSelect.link'), value: '^(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z0-9]{2,}(\.[a-zA-Z0-9]{2,})(\.[a-zA-Z0-9]{2,})?$', message: '请输入链接' },
|
||||
{ id: 'monetaryAmount', label: i18n.t('regexSelect.monetaryAmount'), value: '^-?\\d+(,\\d{3})*(\.\\d{1,2})?$', message: '请输入货币金额' },
|
||||
{ id: 'custom', label: i18n.t('regexSelect.custom'), value: '', message: '' }
|
||||
]
|
||||
}
|
2
cmdb-ui/src/components/RegexSelect/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import RegexSelect from './regexSelect.vue'
|
||||
export default RegexSelect
|
207
cmdb-ui/src/components/RegexSelect/regexSelect.vue
Normal file
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<a-popover
|
||||
trigger="click"
|
||||
placement="bottom"
|
||||
ref="regexSelect"
|
||||
overlayClassName="regex-select-wrapper"
|
||||
:overlayStyle="{ '--overlay-width': `${width}px` }"
|
||||
@visibleChange="visibleChange"
|
||||
>
|
||||
<div class="regex-select" slot="content">
|
||||
<div class="regex-select-left">
|
||||
<div class="regex-select-left-header">{{ $t('regexSelect.limitedFormat') }}</div>
|
||||
<div
|
||||
@click="
|
||||
() => {
|
||||
current = reg
|
||||
testInput = ''
|
||||
showMessage = false
|
||||
}
|
||||
"
|
||||
:class="{
|
||||
'regex-select-left-reg': true,
|
||||
'regex-select-left-reg-selected': current && current.label === reg.label,
|
||||
}"
|
||||
v-for="(reg, index) in regList"
|
||||
:key="reg.label"
|
||||
>
|
||||
<a-divider :style="{ margin: '2px -12px', width: 'calc(100% + 24px)' }" v-if="index === regList.length - 1" />
|
||||
{{ reg.label }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="regex-select-right">
|
||||
<template v-if="current">
|
||||
<div class="regex-select-right-header">{{ $t('regexSelect.regExp') }}</div>
|
||||
<div
|
||||
v-if="current.label !== $t('regexSelect.custom')"
|
||||
:style="{ color: '#000', fontSize: '12px', margin: '12px 0' }"
|
||||
>
|
||||
{{ current.value }}
|
||||
</div>
|
||||
<a-input
|
||||
:style="{ margin: '12px 0' }"
|
||||
size="small"
|
||||
v-else
|
||||
v-model="current.value"
|
||||
@change="
|
||||
() => {
|
||||
this.$emit('change', current)
|
||||
}
|
||||
"
|
||||
/>
|
||||
<template v-if="isShowErrorMsg">
|
||||
<div class="regex-select-right-header">{{ $t('regexSelect.errMsg') }}</div>
|
||||
<a-input :style="{ margin: '12px 0' }" size="small" v-model="current.message" />
|
||||
</template>
|
||||
<div class="regex-select-right-header">{{ $t('regexSelect.test') }}</div>
|
||||
<a-input v-model="testInput" :style="{ margin: '12px 0 4px' }" size="small" @change="validate" />
|
||||
<span :style="{ color: 'red', fontSize: '12px' }" v-if="showMessage">{{
|
||||
locale === 'zh' ? current.message || '错误' : $t('regexSelect.error')
|
||||
}}</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<a-input ref="regInput" :placeholder="$t('regexSelect.placeholder')" :value="current.label" @change="changeLabel">
|
||||
</a-input>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { regList } from './constants'
|
||||
export default {
|
||||
name: 'RegexSelect',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
isShowErrorMsg: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
limitedFormat: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showMessage: false,
|
||||
width: 370,
|
||||
testInput: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['locale']),
|
||||
regList() {
|
||||
if (this.limitedFormat.length) {
|
||||
return regList().filter((item) => this.limitedFormat.includes(item.id))
|
||||
}
|
||||
return regList()
|
||||
},
|
||||
current: {
|
||||
get() {
|
||||
if (this.value?.value && !this.value?.label) {
|
||||
const _find = this.regList.find((reg) => reg.value === this.value?.value)
|
||||
return { ...this.value, label: _find?.label ?? this.$t('regexSelect.custom') }
|
||||
}
|
||||
return this.value ?? {}
|
||||
},
|
||||
set(val) {
|
||||
this.showMessage = false
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
const regInput = this.$refs.regInput.$refs.input
|
||||
this.width = regInput.offsetWidth || 370
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
validate(e) {
|
||||
const reg = RegExp(this.current.value, 'g')
|
||||
this.showMessage = !reg.test(e.target.value)
|
||||
},
|
||||
changeLabel(e) {
|
||||
this.current = {}
|
||||
},
|
||||
visibleChange(visible) {
|
||||
if (visible) {
|
||||
this.$nextTick(() => {
|
||||
this.testInput = ''
|
||||
this.showMessage = false
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.regex-select {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
display: flex;
|
||||
.regex-select-left {
|
||||
width: 40%;
|
||||
height: 100%;
|
||||
border: 1px solid #cacdd9;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
&-reg {
|
||||
padding-left: 2px;
|
||||
cursor: pointer;
|
||||
&-selected,
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-right {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
border: 1px solid #cacdd9;
|
||||
border-radius: 4px;
|
||||
margin-left: 8px;
|
||||
padding: 12px;
|
||||
}
|
||||
&-left,
|
||||
&-right {
|
||||
&-header {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #000000;
|
||||
border-left: 2px solid @primary-color;
|
||||
padding-left: 6px;
|
||||
margin-left: -6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.regex-select-wrapper {
|
||||
.ant-popover-arrow {
|
||||
display: none;
|
||||
}
|
||||
.ant-popover-inner-content {
|
||||
padding: 0;
|
||||
min-width: 370px;
|
||||
width: var(--overlay-width);
|
||||
}
|
||||
}
|
||||
.regex-select-wrapper.ant-popover-placement-bottom .ant-popover-content {
|
||||
margin-top: -8px;
|
||||
}
|
||||
.regex-select-wrapper.ant-popover-placement-top .ant-popover-content {
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
</style>
|
@@ -126,7 +126,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.role-transfer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -186,14 +185,14 @@ export default {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
@@ -60,7 +60,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.sidebar-list-item {
|
||||
.ops_popover_item();
|
||||
margin: 2px 0;
|
||||
@@ -94,7 +93,7 @@ export default {
|
||||
background-color: transparent;
|
||||
}
|
||||
.sidebar-list-item.sidebar-list-item-selected::before {
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
}
|
||||
.sidebar-list-item-dotline {
|
||||
padding-left: 20px;
|
||||
|
@@ -1,179 +1,183 @@
|
||||
<template>
|
||||
<div ref="splitPane" class="split-pane" :class="direction + ' ' + appName" :style="{ flexDirection: direction }">
|
||||
<div class="pane pane-one" ref="one" :style="lengthType + ':' + paneLengthValue1">
|
||||
<slot name="one"></slot>
|
||||
</div>
|
||||
|
||||
<div class="spliter-wrap">
|
||||
<a-button
|
||||
v-show="collapsable"
|
||||
:icon="isExpanded ? 'left' : 'right'"
|
||||
class="collapse-btn"
|
||||
@click="handleExpand"
|
||||
></a-button>
|
||||
<div
|
||||
class="pane-trigger"
|
||||
@mousedown="handleMouseDown"
|
||||
:style="{ backgroundColor: triggerColor, width: `${triggerLength}px` }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="pane pane-two" ref="two" :style="lengthType + ':' + paneLengthValue2">
|
||||
<slot name="two"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SplitPane',
|
||||
props: {
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'row',
|
||||
},
|
||||
|
||||
min: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
|
||||
max: {
|
||||
type: Number,
|
||||
default: 90,
|
||||
},
|
||||
|
||||
paneLengthPixel: {
|
||||
type: Number,
|
||||
default: 220,
|
||||
},
|
||||
|
||||
triggerLength: {
|
||||
type: Number,
|
||||
default: 8,
|
||||
},
|
||||
|
||||
appName: {
|
||||
type: String,
|
||||
default: 'viewer',
|
||||
},
|
||||
|
||||
collapsable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
triggerColor: {
|
||||
type: String,
|
||||
default: '#f0f2f5',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
triggerLeftOffset: 0, // 鼠标距滑动器左(顶)侧偏移量
|
||||
isExpanded: localStorage.getItem(`${this.appName}-isExpanded`)
|
||||
? JSON.parse(localStorage.getItem(`${this.appName}-isExpanded`))
|
||||
: false,
|
||||
parentContainer: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lengthType() {
|
||||
return this.direction === 'row' ? 'width' : 'height'
|
||||
},
|
||||
|
||||
minLengthType() {
|
||||
return this.direction === 'row' ? 'minWidth' : 'minHeight'
|
||||
},
|
||||
|
||||
paneLengthValue1() {
|
||||
return `calc(${this.paneLengthPercent}% - ${this.triggerLength / 2 + 'px'})`
|
||||
},
|
||||
|
||||
paneLengthValue2() {
|
||||
const rest = 100 - this.paneLengthPercent
|
||||
return `calc(${rest}% - ${this.triggerLength / 2 + 'px'})`
|
||||
},
|
||||
|
||||
paneLengthPercent() {
|
||||
const clientRectWidth = this.parentContainer
|
||||
? this.parentContainer.clientWidth
|
||||
: document.documentElement.getBoundingClientRect().width
|
||||
return (this.paneLengthPixel / clientRectWidth) * 100
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
isExpanded(newValue) {
|
||||
if (newValue) {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
||||
} else {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = ''
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.parentContainer = document.querySelector(`.${this.appName}`)
|
||||
if (this.isExpanded) {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
||||
} else {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = ''
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 按下滑动器
|
||||
handleMouseDown(e) {
|
||||
document.addEventListener('mousemove', this.handleMouseMove)
|
||||
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
|
||||
|
||||
if (this.direction === 'row') {
|
||||
const offset = e.pageX - clientRect.left - this.triggerLeftOffset + this.triggerLength / 2
|
||||
paneLengthPixel = offset
|
||||
} else {
|
||||
const offset = e.pageY - clientRect.top - this.triggerLeftOffset + this.triggerLength / 2
|
||||
paneLengthPixel = offset
|
||||
}
|
||||
|
||||
if (paneLengthPixel < this.min) {
|
||||
paneLengthPixel = this.min
|
||||
}
|
||||
if (paneLengthPixel > this.max) {
|
||||
paneLengthPixel = this.max
|
||||
}
|
||||
|
||||
this.$emit('update:paneLengthPixel', paneLengthPixel)
|
||||
|
||||
localStorage.setItem(`${this.appName}-paneLengthPixel`, paneLengthPixel)
|
||||
},
|
||||
|
||||
// 松开滑动器
|
||||
handleMouseUp() {
|
||||
document.removeEventListener('mousemove', this.handleMouseMove)
|
||||
},
|
||||
|
||||
handleExpand() {
|
||||
this.isExpanded = !this.isExpanded
|
||||
this.$emit('expand', this.isExpanded)
|
||||
localStorage.setItem(`${this.appName}-isExpanded`, this.isExpanded)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import './index.less';
|
||||
</style>
|
||||
<template>
|
||||
<div ref="splitPane" class="split-pane" :class="direction + ' ' + appName" :style="{ flexDirection: direction }">
|
||||
<div class="pane pane-one" ref="one" :style="lengthType + ':' + paneLengthValue1">
|
||||
<slot name="one"></slot>
|
||||
</div>
|
||||
|
||||
<div class="spliter-wrap">
|
||||
<a-button
|
||||
v-show="collapsable"
|
||||
:icon="isExpanded ? 'left' : 'right'"
|
||||
class="collapse-btn"
|
||||
@click="handleExpand"
|
||||
></a-button>
|
||||
<div
|
||||
class="pane-trigger"
|
||||
@mousedown="handleMouseDown"
|
||||
:style="{ backgroundColor: triggerColor, width: `${triggerLength}px` }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="pane pane-two" ref="two" :style="lengthType + ':' + paneLengthValue2">
|
||||
<slot name="two"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SplitPane',
|
||||
props: {
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'row',
|
||||
},
|
||||
|
||||
min: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
|
||||
max: {
|
||||
type: Number,
|
||||
default: 90,
|
||||
},
|
||||
|
||||
paneLengthPixel: {
|
||||
type: Number,
|
||||
default: 220,
|
||||
},
|
||||
|
||||
triggerLength: {
|
||||
type: Number,
|
||||
default: 8,
|
||||
},
|
||||
|
||||
appName: {
|
||||
type: String,
|
||||
default: 'viewer',
|
||||
},
|
||||
|
||||
collapsable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
triggerColor: {
|
||||
type: String,
|
||||
default: '#f7f8fa',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
triggerLeftOffset: 0, // 鼠标距滑动器左(顶)侧偏移量
|
||||
isExpanded: localStorage.getItem(`${this.appName}-isExpanded`)
|
||||
? JSON.parse(localStorage.getItem(`${this.appName}-isExpanded`))
|
||||
: false,
|
||||
parentContainer: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lengthType() {
|
||||
return this.direction === 'row' ? 'width' : 'height'
|
||||
},
|
||||
|
||||
minLengthType() {
|
||||
return this.direction === 'row' ? 'minWidth' : 'minHeight'
|
||||
},
|
||||
|
||||
paneLengthValue1() {
|
||||
return `calc(${this.paneLengthPercent}% - ${this.triggerLength / 2 + 'px'})`
|
||||
},
|
||||
|
||||
paneLengthValue2() {
|
||||
const rest = 100 - this.paneLengthPercent
|
||||
return `calc(${rest}% - ${this.triggerLength / 2 + 'px'})`
|
||||
},
|
||||
|
||||
paneLengthPercent() {
|
||||
const clientRectWidth = this.parentContainer
|
||||
? this.parentContainer.clientWidth
|
||||
: document.documentElement.getBoundingClientRect().width
|
||||
return (this.paneLengthPixel / clientRectWidth) * 100
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
isExpanded(newValue) {
|
||||
if (newValue) {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
||||
} else {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = ''
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const paneLengthPixel = localStorage.getItem(`${this.appName}-paneLengthPixel`)
|
||||
if (paneLengthPixel) {
|
||||
this.$emit('update:paneLengthPixel', Number(paneLengthPixel))
|
||||
}
|
||||
this.parentContainer = document.querySelector(`.${this.appName}`)
|
||||
if (this.isExpanded) {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
||||
} else {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = ''
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 按下滑动器
|
||||
handleMouseDown(e) {
|
||||
document.addEventListener('mousemove', this.handleMouseMove)
|
||||
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
|
||||
|
||||
if (this.direction === 'row') {
|
||||
const offset = e.pageX - clientRect.left - this.triggerLeftOffset + this.triggerLength / 2
|
||||
paneLengthPixel = offset
|
||||
} else {
|
||||
const offset = e.pageY - clientRect.top - this.triggerLeftOffset + this.triggerLength / 2
|
||||
paneLengthPixel = offset
|
||||
}
|
||||
|
||||
if (paneLengthPixel < this.min) {
|
||||
paneLengthPixel = this.min
|
||||
}
|
||||
if (paneLengthPixel > this.max) {
|
||||
paneLengthPixel = this.max
|
||||
}
|
||||
|
||||
this.$emit('update:paneLengthPixel', paneLengthPixel)
|
||||
|
||||
localStorage.setItem(`${this.appName}-paneLengthPixel`, paneLengthPixel)
|
||||
},
|
||||
|
||||
// 松开滑动器
|
||||
handleMouseUp() {
|
||||
document.removeEventListener('mousemove', this.handleMouseMove)
|
||||
},
|
||||
|
||||
handleExpand() {
|
||||
this.isExpanded = !this.isExpanded
|
||||
this.$emit('expand', this.isExpanded)
|
||||
localStorage.setItem(`${this.appName}-isExpanded`, this.isExpanded)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import './index.less';
|
||||
</style>
|
||||
|
@@ -1,2 +1,2 @@
|
||||
import SplitPane from './SplitPane'
|
||||
export default SplitPane
|
||||
import SplitPane from './SplitPane'
|
||||
export default SplitPane
|
||||
|
@@ -1,48 +1,48 @@
|
||||
.split-pane {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.split-pane .pane-two {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.split-pane .pane-trigger {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.split-pane.row .pane-one {
|
||||
width: 20%;
|
||||
height: 100%;
|
||||
// overflow-y: auto;
|
||||
}
|
||||
|
||||
.split-pane.column .pane {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.split-pane.row .pane-trigger {
|
||||
width: 8px;
|
||||
height: 100%;
|
||||
cursor: e-resize;
|
||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAPCAYAAADDNm69AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAeSURBVBhXY/4PBMzMzA379u1rANFMDGhgGAswMAAAn6EH6K9ktYAAAAAASUVORK5CYII=')
|
||||
1px 50% no-repeat #f0f2f5;
|
||||
}
|
||||
|
||||
.split-pane .collapse-btn {
|
||||
width: 25px;
|
||||
height: 70px;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: calc(50% - 35px);
|
||||
background-color: #f0f2f5;
|
||||
border-color: transparent;
|
||||
border-radius: 8px 0px 0px 8px;
|
||||
.anticon {
|
||||
color: #7cb0fe;
|
||||
}
|
||||
}
|
||||
|
||||
.split-pane .spliter-wrap {
|
||||
position: relative;
|
||||
}
|
||||
.split-pane {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.split-pane .pane-two {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.split-pane .pane-trigger {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.split-pane.row .pane-one {
|
||||
width: 20%;
|
||||
height: 100%;
|
||||
// overflow-y: auto;
|
||||
}
|
||||
|
||||
.split-pane.column .pane {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.split-pane.row .pane-trigger {
|
||||
width: 8px;
|
||||
height: 100%;
|
||||
cursor: e-resize;
|
||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAPCAYAAADDNm69AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAeSURBVBhXY/4PBMzMzA379u1rANFMDGhgGAswMAAAn6EH6K9ktYAAAAAASUVORK5CYII=')
|
||||
1px 50% no-repeat #f0f2f5;
|
||||
}
|
||||
|
||||
.split-pane .collapse-btn {
|
||||
width: 25px;
|
||||
height: 70px;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: calc(50% - 35px);
|
||||
background-color: #f0f2f5;
|
||||
border-color: transparent;
|
||||
border-radius: 8px 0px 0px 8px;
|
||||
.anticon {
|
||||
color: #7cb0fe;
|
||||
}
|
||||
}
|
||||
|
||||
.split-pane .spliter-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ export default {
|
||||
},
|
||||
triggerColor: {
|
||||
type: String,
|
||||
default: '#F0F5FF',
|
||||
default: '#f7f8fa',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
@@ -52,22 +52,20 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.two-column-layout {
|
||||
margin-bottom: -24px;
|
||||
width: 100%;
|
||||
.two-column-layout-sidebar {
|
||||
height: 100%;
|
||||
padding: 15px 7px;
|
||||
border-radius: 15px;
|
||||
overflow-y: auto;
|
||||
background-color: #fff;
|
||||
}
|
||||
.two-column-layout-main {
|
||||
height: 100%;
|
||||
padding: 12px;
|
||||
background-color: #fff;
|
||||
overflow-y: auto;
|
||||
border-radius: 15px;
|
||||
border-radius: @border-radius-box;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -4,34 +4,22 @@
|
||||
@click="jumpTo"
|
||||
v-if="showTitle && !collapsed"
|
||||
style="width: 100%; height: 100%; cursor: pointer"
|
||||
:src="file_name ? `/api/common-setting/v1/file/${file_name}` : require('@/assets/logo_VECMDB.png')"
|
||||
:src="require('@/assets/logo_VECMDB.png')"
|
||||
/>
|
||||
<img
|
||||
@click="jumpTo"
|
||||
v-else
|
||||
style="width: 32px; height: 32px; margin-left: 24px; cursor: pointer"
|
||||
:src="small_file_name ? `/api/common-setting/v1/file/${small_file_name}` : require('@/assets/logo.png')"
|
||||
:src="require('@/assets/logo.png')"
|
||||
/>
|
||||
<!-- <logo-svg/> -->
|
||||
<!-- <img v-if="showTitle" style="width:92px;height: 32px" src="@/assets/OneOps.png" /> -->
|
||||
<!-- <h1 v-if="showTitle">{{ title }}</h1> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import LogoSvg from '@/assets/logo.svg?inline'
|
||||
import { mapState } from 'vuex'
|
||||
export default {
|
||||
name: 'Logo',
|
||||
components: {
|
||||
// LogoSvg,
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
file_name: (state) => state.logo.file_name,
|
||||
small_file_name: (state) => state.logo.small_file_name,
|
||||
}),
|
||||
},
|
||||
components: {},
|
||||
computed: {},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
|
@@ -1,15 +1,11 @@
|
||||
<template>
|
||||
<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
|
||||
:class="current === route.name ? 'top-menu-selected' : ''"
|
||||
v-for="route in defaultShowRoutes"
|
||||
:key="route.name"
|
||||
@click="() => handleClick(route)"
|
||||
:title="$t(route.meta.title)"
|
||||
>
|
||||
{{ route.meta.title }}
|
||||
</span>
|
||||
@@ -43,6 +39,7 @@
|
||||
<script>
|
||||
import store from '@/store'
|
||||
import { gridSvg, top_agent, top_acl } from '@/core/icons'
|
||||
import { getPreference } from '@/modules/cmdb/api/preference'
|
||||
export default {
|
||||
name: 'TopMenu',
|
||||
components: { gridSvg, top_agent, top_acl },
|
||||
@@ -78,10 +75,20 @@ export default {
|
||||
this.current = this.$route.matched[0].name
|
||||
},
|
||||
methods: {
|
||||
handleClick(route) {
|
||||
async handleClick(route) {
|
||||
this.visible = false
|
||||
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
|
||||
}
|
||||
},
|
||||
@@ -91,15 +98,6 @@ export default {
|
||||
|
||||
<style lang="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 {
|
||||
display: inline-flex;
|
||||
@@ -110,32 +108,29 @@ export default {
|
||||
line-height: @layout-header-icon-height;
|
||||
border-radius: 4px !important;
|
||||
display: inline-flex;
|
||||
align-items: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
> span {
|
||||
cursor: pointer;
|
||||
padding: 4px 10px;
|
||||
margin: 0 5px;
|
||||
border-radius: 4px;
|
||||
color: @layout-header-font-color;
|
||||
height: @layout-header-height;
|
||||
line-height: @layout-header-height;
|
||||
&: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;
|
||||
}
|
||||
line-height: @layout-header-line-height;
|
||||
display: inline-block;
|
||||
}
|
||||
> span:hover,
|
||||
.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;
|
||||
border-radius: 3px 3px 0px 0px;
|
||||
border-bottom: 3px solid @layout-header-font-selected-color;
|
||||
&: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::before {
|
||||
display: block;
|
||||
content: attr(title);
|
||||
font-weight: bold;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -71,8 +71,6 @@ export default {
|
||||
title: this.$t('tip'),
|
||||
content: this.$t('topMenu.confirmLogout'),
|
||||
onOk() {
|
||||
// localStorage.removeItem('ops_cityps_currentId')
|
||||
localStorage.clear()
|
||||
return that.Logout()
|
||||
},
|
||||
onCancel() {},
|
||||
@@ -98,10 +96,9 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
.color {
|
||||
color: #custom_colors[color_1];
|
||||
background-color: #custom_colors[color_2];
|
||||
color: @primary-color;
|
||||
background-color: @primary-color_5;
|
||||
}
|
||||
.custom-user {
|
||||
.custom-user-item {
|
||||
@@ -120,7 +117,7 @@ export default {
|
||||
.locale {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
primaryColor: '#1890ff', // primary color of ant design
|
||||
primaryColor: '#2f54eb', // primary color of ant design
|
||||
navTheme: 'dark', // theme for nav menu
|
||||
layout: 'sidemenu', // nav menu position: sidemenu or topmenu
|
||||
contentWidth: 'Fixed', // layout of content: Fluid or Fixed, only works when layout is topmenu
|
||||
|