Compare commits
93 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
65ef58dea9 | ||
|
0a2e7aa99f | ||
|
8875e75883 | ||
|
2f03639c57 | ||
|
49bc5d94a9 | ||
|
39354e1293 | ||
|
d3714f3ecf | ||
|
729a616282 | ||
|
2d3a290aa3 | ||
|
9e885a5b12 | ||
|
f5822d7cba | ||
|
21ea553e74 | ||
|
e63038d1b6 | ||
|
d56806f511 | ||
|
7ac7fdc08e | ||
|
ba11707146 | ||
|
d49dc8a067 | ||
|
6bfb34fe2a | ||
|
2c7ed8c32d | ||
|
5b275af54e | ||
|
dde7ec6246 | ||
|
9181817e96 | ||
|
46b54bb7f2 | ||
|
fe63310c4e | ||
|
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 |
6
.env
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
MYSQL_ROOT_PASSWORD='123456'
|
||||||
|
MYSQL_HOST='mysql'
|
||||||
|
MYSQL_PORT=3306
|
||||||
|
MYSQL_USER='cmdb'
|
||||||
|
MYSQL_DATABASE='cmdb'
|
||||||
|
MYSQL_PASSWORD='123456'
|
@@ -74,20 +74,20 @@
|
|||||||
|
|
||||||
### Docker 一键快速构建
|
### Docker 一键快速构建
|
||||||
> 方法一
|
> 方法一
|
||||||
- 第一步: 先安装 docker 环境, 以及docker-compose
|
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
|
||||||
- 第二步: 拷贝项目
|
- 第二步: 拷贝项目
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/veops/cmdb.git
|
git clone https://github.com/veops/cmdb.git
|
||||||
```
|
```
|
||||||
- 第三步:进入主目录,执行:
|
- 第三步:进入主目录,执行:
|
||||||
```
|
```
|
||||||
docker-compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
> 方法二, 该方法适用于linux系统
|
> 方法二, 该方法适用于linux系统
|
||||||
- 第一步: 先安装 docker 环境, 以及docker-compose
|
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
|
||||||
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
|
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
|
||||||
```shell
|
```shell
|
||||||
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/master/install.sh
|
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/deploy_on_kylin_docker/install.sh
|
||||||
sh install.sh install
|
sh install.sh install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@@ -36,7 +36,7 @@ Flask-Caching = ">=1.0.0"
|
|||||||
environs = "==4.2.0"
|
environs = "==4.2.0"
|
||||||
marshmallow = "==2.20.2"
|
marshmallow = "==2.20.2"
|
||||||
# async tasks
|
# async tasks
|
||||||
celery = ">=5.3.1"
|
celery = "==5.3.1"
|
||||||
celery_once = "==3.0.1"
|
celery_once = "==3.0.1"
|
||||||
more-itertools = "==5.0.0"
|
more-itertools = "==5.0.0"
|
||||||
kombu = ">=5.3.1"
|
kombu = ">=5.3.1"
|
||||||
@@ -66,6 +66,7 @@ hvac = "==2.0.0"
|
|||||||
colorama = ">=0.4.6"
|
colorama = ">=0.4.6"
|
||||||
pycryptodomex = ">=3.19.0"
|
pycryptodomex = ">=3.19.0"
|
||||||
lz4 = ">=4.3.2"
|
lz4 = ">=4.3.2"
|
||||||
|
python-magic = "==0.4.27"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
# Testing
|
# Testing
|
||||||
|
@@ -55,9 +55,12 @@ def cmdb_init_cache():
|
|||||||
for cr in ci_relations:
|
for cr in ci_relations:
|
||||||
relations.setdefault(cr.first_ci_id, {}).update({cr.second_ci_id: cr.second_ci.type_id})
|
relations.setdefault(cr.first_ci_id, {}).update({cr.second_ci_id: cr.second_ci.type_id})
|
||||||
if cr.ancestor_ids:
|
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:
|
for i in relations:
|
||||||
relations[i] = json.dumps(relations[i])
|
relations[i] = json.dumps(relations[i])
|
||||||
|
for i in relations2:
|
||||||
|
relations2[i] = json.dumps(relations2[i])
|
||||||
if relations:
|
if relations:
|
||||||
rd.create_or_update(relations, REDIS_PREFIX_CI_RELATION)
|
rd.create_or_update(relations, REDIS_PREFIX_CI_RELATION)
|
||||||
if relations2:
|
if relations2:
|
||||||
@@ -125,7 +128,7 @@ def cmdb_init_acl():
|
|||||||
perms = [PermEnum.READ]
|
perms = [PermEnum.READ]
|
||||||
elif resource_type == ResourceTypeEnum.CI_TYPE_RELATION:
|
elif resource_type == ResourceTypeEnum.CI_TYPE_RELATION:
|
||||||
perms = [PermEnum.ADD, PermEnum.DELETE, PermEnum.GRANT]
|
perms = [PermEnum.ADD, PermEnum.DELETE, PermEnum.GRANT]
|
||||||
elif resource_type == ResourceTypeEnum.RELATION_VIEW:
|
elif resource_type in (ResourceTypeEnum.RELATION_VIEW, ResourceTypeEnum.TOPOLOGY_VIEW):
|
||||||
perms = [PermEnum.READ, PermEnum.UPDATE, PermEnum.DELETE, PermEnum.GRANT]
|
perms = [PermEnum.READ, PermEnum.UPDATE, PermEnum.DELETE, PermEnum.GRANT]
|
||||||
|
|
||||||
ResourceTypeCRUD.add(app_id, resource_type, '', perms)
|
ResourceTypeCRUD.add(app_id, resource_type, '', perms)
|
||||||
@@ -323,7 +326,7 @@ def cmdb_inner_secrets_init(address):
|
|||||||
"""
|
"""
|
||||||
init inner secrets for password feature
|
init inner secrets for password feature
|
||||||
"""
|
"""
|
||||||
res, ok = KeyManage(backend=InnerKVManger).init()
|
res, ok = KeyManage(backend=InnerKVManger()).init()
|
||||||
if not ok:
|
if not ok:
|
||||||
if res.get("status") == "failed":
|
if res.get("status") == "failed":
|
||||||
KeyManage.print_response(res)
|
KeyManage.print_response(res)
|
||||||
@@ -357,13 +360,13 @@ def cmdb_inner_secrets_unseal(address):
|
|||||||
"""
|
"""
|
||||||
unseal the secrets feature
|
unseal the secrets feature
|
||||||
"""
|
"""
|
||||||
if not valid_address(address):
|
# if not valid_address(address):
|
||||||
return
|
# return
|
||||||
address = "{}/api/v0.1/secrets/unseal".format(address.strip("/"))
|
address = "{}/api/v0.1/secrets/unseal".format(address.strip("/"))
|
||||||
for i in range(global_key_threshold):
|
for i in range(global_key_threshold):
|
||||||
token = click.prompt(f'Enter unseal token {i + 1}', hide_input=True, confirmation_prompt=False)
|
token = click.prompt(f'Enter unseal token {i + 1}', hide_input=True, confirmation_prompt=False)
|
||||||
assert token is not None
|
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:
|
if resp.status_code == 200:
|
||||||
KeyManage.print_response(resp.json())
|
KeyManage.print_response(resp.json())
|
||||||
if resp.json().get("status") in ["success", "skip"]:
|
if resp.json().get("status") in ["success", "skip"]:
|
||||||
|
@@ -6,6 +6,7 @@ from werkzeug.datastructures import MultiDict
|
|||||||
from api.lib.common_setting.acl import ACLManager
|
from api.lib.common_setting.acl import ACLManager
|
||||||
from api.lib.common_setting.employee import EmployeeAddForm, GrantEmployeeACLPerm
|
from api.lib.common_setting.employee import EmployeeAddForm, GrantEmployeeACLPerm
|
||||||
from api.lib.common_setting.resp_format import ErrFormat
|
from api.lib.common_setting.resp_format import ErrFormat
|
||||||
|
from api.lib.common_setting.utils import CheckNewColumn
|
||||||
from api.models.common_setting import Employee, Department
|
from api.models.common_setting import Employee, Department
|
||||||
|
|
||||||
|
|
||||||
@@ -209,57 +210,7 @@ def common_check_new_columns():
|
|||||||
"""
|
"""
|
||||||
add new columns to tables
|
add new columns to tables
|
||||||
"""
|
"""
|
||||||
from api.extensions import db
|
CheckNewColumn().run()
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
|
@@ -331,14 +331,14 @@ class AutoDiscoveryCICRUD(DBMixin):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_attributes_by_type_id(type_id):
|
def get_attributes_by_type_id(type_id):
|
||||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||||
attributes = [i[1] for i in CITypeAttributeManager.get_all_attributes(type_id) or []]
|
attributes = [i for i in CITypeAttributeManager.get_attributes_by_type_id(type_id) or []]
|
||||||
|
|
||||||
attr_names = set()
|
attr_names = set()
|
||||||
adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id)
|
adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id)
|
||||||
for adt in adts:
|
for adt in adts:
|
||||||
attr_names |= set((adt.attributes or {}).values())
|
attr_names |= set((adt.attributes or {}).values())
|
||||||
|
|
||||||
return [attr.to_dict() for attr in attributes if attr.name in attr_names]
|
return [attr for attr in attributes if attr['name'] in attr_names]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def search(cls, page, page_size, fl=None, **kwargs):
|
def search(cls, page, page_size, fl=None, **kwargs):
|
||||||
|
@@ -264,9 +264,11 @@ class CIManager(object):
|
|||||||
for attr_id in constraint.attr_ids:
|
for attr_id in constraint.attr_ids:
|
||||||
value_table = TableMap(attr_name=id2name[attr_id]).table
|
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,
|
values = value_table.get_by(attr_id=attr_id,
|
||||||
to_dict=False,
|
value=ci_dict.get(id2name[attr_id]) or None,
|
||||||
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:
|
if ci_ids is None:
|
||||||
ci_ids = _ci_ids
|
ci_ids = _ci_ids
|
||||||
else:
|
else:
|
||||||
@@ -295,6 +297,7 @@ class CIManager(object):
|
|||||||
_no_attribute_policy=ExistPolicy.IGNORE,
|
_no_attribute_policy=ExistPolicy.IGNORE,
|
||||||
is_auto_discovery=False,
|
is_auto_discovery=False,
|
||||||
_is_admin=False,
|
_is_admin=False,
|
||||||
|
ticket_id=None,
|
||||||
**ci_dict):
|
**ci_dict):
|
||||||
"""
|
"""
|
||||||
add ci
|
add ci
|
||||||
@@ -303,6 +306,7 @@ class CIManager(object):
|
|||||||
:param _no_attribute_policy: ignore or reject
|
:param _no_attribute_policy: ignore or reject
|
||||||
:param is_auto_discovery: default is False
|
:param is_auto_discovery: default is False
|
||||||
:param _is_admin: default is False
|
:param _is_admin: default is False
|
||||||
|
:param ticket_id:
|
||||||
:param ci_dict:
|
:param ci_dict:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
@@ -417,7 +421,7 @@ class CIManager(object):
|
|||||||
operate_type = OperateType.UPDATE if ci is not None else OperateType.ADD
|
operate_type = OperateType.UPDATE if ci is not None else OperateType.ADD
|
||||||
try:
|
try:
|
||||||
ci = ci or CI.create(type_id=ci_type.id, is_auto_discovery=is_auto_discovery)
|
ci = ci or CI.create(type_id=ci_type.id, is_auto_discovery=is_auto_discovery)
|
||||||
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr)
|
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr, ticket_id=ticket_id)
|
||||||
except BadRequest as e:
|
except BadRequest as e:
|
||||||
if existed is None:
|
if existed is None:
|
||||||
cls.delete(ci.id)
|
cls.delete(ci.id)
|
||||||
@@ -435,14 +439,18 @@ class CIManager(object):
|
|||||||
|
|
||||||
return ci.id
|
return ci.id
|
||||||
|
|
||||||
def update(self, ci_id, _is_admin=False, **ci_dict):
|
def update(self, ci_id, _is_admin=False, ticket_id=None, __sync=False, **ci_dict):
|
||||||
|
current_app.logger.info((ci_id, ci_dict, __sync))
|
||||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
ci = self.confirm_ci_existed(ci_id)
|
ci = self.confirm_ci_existed(ci_id)
|
||||||
|
|
||||||
raw_dict = copy.deepcopy(ci_dict)
|
|
||||||
|
|
||||||
attrs = CITypeAttributeManager.get_all_attributes(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_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}
|
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
||||||
for _, attr in attrs:
|
for _, attr in attrs:
|
||||||
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||||
@@ -487,7 +495,7 @@ class CIManager(object):
|
|||||||
ci_dict.pop(k)
|
ci_dict.pop(k)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr)
|
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr, ticket_id=ticket_id)
|
||||||
except BadRequest as e:
|
except BadRequest as e:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
@@ -496,11 +504,17 @@ class CIManager(object):
|
|||||||
record_id = self.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci.type_id)
|
record_id = self.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci.type_id)
|
||||||
|
|
||||||
if record_id: # has change
|
if record_id: # has change
|
||||||
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
|
if not __sync:
|
||||||
|
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
|
||||||
|
else:
|
||||||
|
ci_cache((ci_id, OperateType.UPDATE, record_id))
|
||||||
|
|
||||||
ref_ci_dict = {k: v for k, v in ci_dict.items() if k.startswith("$") and "." in k}
|
ref_ci_dict = {k: v for k, v in ci_dict.items() if k.startswith("$") and "." in k}
|
||||||
if ref_ci_dict:
|
if ref_ci_dict:
|
||||||
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id), queue=CMDB_QUEUE)
|
if not __sync:
|
||||||
|
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id), queue=CMDB_QUEUE)
|
||||||
|
else:
|
||||||
|
ci_relation_add((ref_ci_dict, ci.id))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_unique_value(ci_id, unique_name, unique_value):
|
def update_unique_value(ci_id, unique_name, unique_value):
|
||||||
@@ -823,6 +837,149 @@ class CIManager(object):
|
|||||||
|
|
||||||
return data.get('v')
|
return data.get('v')
|
||||||
|
|
||||||
|
def baseline(self, ci_ids, before_date):
|
||||||
|
"""
|
||||||
|
return CI changes
|
||||||
|
:param ci_ids:
|
||||||
|
:param before_date:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
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 baseline_cis(self, ci_ids, before_date, fl=None):
|
||||||
|
"""
|
||||||
|
return CI changes
|
||||||
|
:param ci_ids:
|
||||||
|
:param before_date:
|
||||||
|
:param fl:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
ci_list = self.get_cis_by_ids(ci_ids, fields=fl)
|
||||||
|
if not ci_list:
|
||||||
|
return []
|
||||||
|
|
||||||
|
id2ci = {ci['_id']: ci for ci in ci_list}
|
||||||
|
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
|
||||||
|
|
||||||
|
if change['is_list']:
|
||||||
|
old, new, value_type, operate_type, ci_id, attr_name = (
|
||||||
|
change['old'], change['new'], change['value_type'], change['operate_type'],
|
||||||
|
change['ci_id'], change['attr_name'])
|
||||||
|
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 and new in (id2ci[ci_id][attr_name] or []):
|
||||||
|
id2ci[ci_id][attr_name].remove(new)
|
||||||
|
elif operate_type == OperateType.DELETE and old not in id2ci[ci_id][attr_name]:
|
||||||
|
id2ci[ci_id][attr_name].append(old)
|
||||||
|
else:
|
||||||
|
id2ci[change['ci_id']][change['attr_name']] = change['old']
|
||||||
|
|
||||||
|
return list(id2ci.values())
|
||||||
|
|
||||||
|
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):
|
class CIRelationManager(object):
|
||||||
"""
|
"""
|
||||||
@@ -933,6 +1090,18 @@ class CIRelationManager(object):
|
|||||||
|
|
||||||
return ci_ids, level2ids
|
return ci_ids, level2ids
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_parent_ids(cls, ci_ids):
|
||||||
|
cis = db.session.query(CIRelation.first_ci_id, CIRelation.second_ci_id, CI.type_id).join(
|
||||||
|
CI, CI.id == CIRelation.first_ci_id).filter(
|
||||||
|
CIRelation.second_ci_id.in_(ci_ids)).filter(CIRelation.deleted.is_(False))
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
for ci in cis:
|
||||||
|
result.setdefault(ci.second_ci_id, []).append((ci.first_ci_id, ci.type_id))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
|
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@@ -958,7 +1127,7 @@ class CIRelationManager(object):
|
|||||||
return abort(400, ErrFormat.relation_constraint.format("1-N"))
|
return abort(400, ErrFormat.relation_constraint.format("1-N"))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, ancestor_ids=None):
|
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, ancestor_ids=None, valid=True):
|
||||||
|
|
||||||
first_ci = CIManager.confirm_ci_existed(first_ci_id)
|
first_ci = CIManager.confirm_ci_existed(first_ci_id)
|
||||||
second_ci = CIManager.confirm_ci_existed(second_ci_id)
|
second_ci = CIManager.confirm_ci_existed(second_ci_id)
|
||||||
@@ -983,7 +1152,7 @@ class CIRelationManager(object):
|
|||||||
relation_type_id or abort(404, ErrFormat.relation_not_found.format("{} -> {}".format(
|
relation_type_id or abort(404, ErrFormat.relation_not_found.format("{} -> {}".format(
|
||||||
first_ci.ci_type.name, second_ci.ci_type.name)))
|
first_ci.ci_type.name, second_ci.ci_type.name)))
|
||||||
|
|
||||||
if current_app.config.get('USE_ACL'):
|
if current_app.config.get('USE_ACL') and valid and current_user.username != 'worker':
|
||||||
resource_name = CITypeRelationManager.acl_resource_name(first_ci.ci_type.name,
|
resource_name = CITypeRelationManager.acl_resource_name(first_ci.ci_type.name,
|
||||||
second_ci.ci_type.name)
|
second_ci.ci_type.name)
|
||||||
if not ACLManager().has_permission(
|
if not ACLManager().has_permission(
|
||||||
@@ -1017,7 +1186,7 @@ class CIRelationManager(object):
|
|||||||
def delete(cr_id):
|
def delete(cr_id):
|
||||||
cr = CIRelation.get_by_id(cr_id) or abort(404, ErrFormat.relation_not_found.format("id={}".format(cr_id)))
|
cr = CIRelation.get_by_id(cr_id) or abort(404, ErrFormat.relation_not_found.format("id={}".format(cr_id)))
|
||||||
|
|
||||||
if current_app.config.get('USE_ACL'):
|
if current_app.config.get('USE_ACL') and current_user.username != 'worker':
|
||||||
resource_name = CITypeRelationManager.acl_resource_name(cr.first_ci.ci_type.name, cr.second_ci.ci_type.name)
|
resource_name = CITypeRelationManager.acl_resource_name(cr.first_ci.ci_type.name, cr.second_ci.ci_type.name)
|
||||||
if not ACLManager().has_permission(
|
if not ACLManager().has_permission(
|
||||||
resource_name,
|
resource_name,
|
||||||
@@ -1051,6 +1220,21 @@ class CIRelationManager(object):
|
|||||||
|
|
||||||
return cr
|
return cr
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete_3(cls, first_ci_id, second_ci_id):
|
||||||
|
cr = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||||
|
second_ci_id=second_ci_id,
|
||||||
|
to_dict=False,
|
||||||
|
first=True)
|
||||||
|
|
||||||
|
if cr is not None:
|
||||||
|
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
||||||
|
delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
|
cls.delete(cr.id)
|
||||||
|
|
||||||
|
return cr
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
|
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
|
||||||
"""
|
"""
|
||||||
@@ -1086,6 +1270,57 @@ class CIRelationManager(object):
|
|||||||
for ci_id in ci_ids:
|
for ci_id in ci_ids:
|
||||||
cls.delete_2(parent_id, ci_id, ancestor_ids=ancestor_ids)
|
cls.delete_2(parent_id, ci_id, ancestor_ids=ancestor_ids)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def build_by_attribute(cls, ci_dict):
|
||||||
|
type_id = ci_dict['_type']
|
||||||
|
child_items = CITypeRelation.get_by(parent_id=type_id, only_query=True).filter(
|
||||||
|
CITypeRelation.parent_attr_id.isnot(None))
|
||||||
|
for item in child_items:
|
||||||
|
parent_attr = AttributeCache.get(item.parent_attr_id)
|
||||||
|
child_attr = AttributeCache.get(item.child_attr_id)
|
||||||
|
attr_value = ci_dict.get(parent_attr.name)
|
||||||
|
value_table = TableMap(attr=child_attr).table
|
||||||
|
for child in value_table.get_by(attr_id=child_attr.id, value=attr_value, only_query=True).join(
|
||||||
|
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.child_id):
|
||||||
|
CIRelationManager.add(ci_dict['_id'], child.ci_id, valid=False)
|
||||||
|
|
||||||
|
parent_items = CITypeRelation.get_by(child_id=type_id, only_query=True).filter(
|
||||||
|
CITypeRelation.child_attr_id.isnot(None))
|
||||||
|
for item in parent_items:
|
||||||
|
parent_attr = AttributeCache.get(item.parent_attr_id)
|
||||||
|
child_attr = AttributeCache.get(item.child_attr_id)
|
||||||
|
attr_value = ci_dict.get(child_attr.name)
|
||||||
|
value_table = TableMap(attr=parent_attr).table
|
||||||
|
for parent in value_table.get_by(attr_id=parent_attr.id, value=attr_value, only_query=True).join(
|
||||||
|
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.parent_id):
|
||||||
|
CIRelationManager.add(parent.ci_id, ci_dict['_id'], valid=False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def rebuild_all_by_attribute(cls, ci_type_relation):
|
||||||
|
parent_attr = AttributeCache.get(ci_type_relation['parent_attr_id'])
|
||||||
|
child_attr = AttributeCache.get(ci_type_relation['child_attr_id'])
|
||||||
|
if not parent_attr or not child_attr:
|
||||||
|
return
|
||||||
|
|
||||||
|
parent_value_table = TableMap(attr=parent_attr).table
|
||||||
|
child_value_table = TableMap(attr=child_attr).table
|
||||||
|
|
||||||
|
parent_values = parent_value_table.get_by(attr_id=parent_attr.id, only_query=True).join(
|
||||||
|
CI, CI.id == parent_value_table.ci_id).filter(CI.type_id == ci_type_relation['parent_id'])
|
||||||
|
child_values = child_value_table.get_by(attr_id=child_attr.id, only_query=True).join(
|
||||||
|
CI, CI.id == child_value_table.ci_id).filter(CI.type_id == ci_type_relation['child_id'])
|
||||||
|
|
||||||
|
child_value2ci_ids = {}
|
||||||
|
for child in child_values:
|
||||||
|
child_value2ci_ids.setdefault(child.value, []).append(child.ci_id)
|
||||||
|
|
||||||
|
for parent in parent_values:
|
||||||
|
for child_ci_id in child_value2ci_ids.get(parent.value, []):
|
||||||
|
try:
|
||||||
|
cls.add(parent.ci_id, child_ci_id, valid=False)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CITriggerManager(object):
|
class CITriggerManager(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@@ -5,7 +5,6 @@ import copy
|
|||||||
import toposort
|
import toposort
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask import session
|
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from toposort import toposort_flatten
|
from toposort import toposort_flatten
|
||||||
from werkzeug.exceptions import BadRequest
|
from werkzeug.exceptions import BadRequest
|
||||||
@@ -28,9 +27,11 @@ from api.lib.cmdb.perms import CIFilterPermsCRUD
|
|||||||
from api.lib.cmdb.relation_type import RelationTypeManager
|
from api.lib.cmdb.relation_type import RelationTypeManager
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.cmdb.value import AttributeValueManager
|
from api.lib.cmdb.value import AttributeValueManager
|
||||||
|
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||||
from api.lib.decorator import kwargs_required
|
from api.lib.decorator import kwargs_required
|
||||||
from api.lib.perm.acl.acl import ACLManager
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
from api.lib.perm.acl.acl import is_app_admin
|
from api.lib.perm.acl.acl import is_app_admin
|
||||||
|
from api.lib.perm.acl.acl import validate_permission
|
||||||
from api.models.cmdb import Attribute
|
from api.models.cmdb import Attribute
|
||||||
from api.models.cmdb import AutoDiscoveryCI
|
from api.models.cmdb import AutoDiscoveryCI
|
||||||
from api.models.cmdb import AutoDiscoveryCIType
|
from api.models.cmdb import AutoDiscoveryCIType
|
||||||
@@ -48,6 +49,7 @@ from api.models.cmdb import CITypeTrigger
|
|||||||
from api.models.cmdb import CITypeUniqueConstraint
|
from api.models.cmdb import CITypeUniqueConstraint
|
||||||
from api.models.cmdb import CustomDashboard
|
from api.models.cmdb import CustomDashboard
|
||||||
from api.models.cmdb import PreferenceCITypeOrder
|
from api.models.cmdb import PreferenceCITypeOrder
|
||||||
|
from api.models.cmdb import TopologyView
|
||||||
from api.models.cmdb import PreferenceRelationView
|
from api.models.cmdb import PreferenceRelationView
|
||||||
from api.models.cmdb import PreferenceSearchOption
|
from api.models.cmdb import PreferenceSearchOption
|
||||||
from api.models.cmdb import PreferenceShowAttributes
|
from api.models.cmdb import PreferenceShowAttributes
|
||||||
@@ -92,6 +94,9 @@ class CITypeManager(object):
|
|||||||
for type_dict in ci_types:
|
for type_dict in ci_types:
|
||||||
attr = AttributeCache.get(type_dict["unique_id"])
|
attr = AttributeCache.get(type_dict["unique_id"])
|
||||||
type_dict["unique_key"] = attr and attr.name
|
type_dict["unique_key"] = attr and attr.name
|
||||||
|
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'])
|
type_dict['parent_ids'] = CITypeInheritanceManager.get_parents(type_dict['id'])
|
||||||
if resources is None or type_dict['name'] in resources:
|
if resources is None or type_dict['name'] in resources:
|
||||||
res.append(type_dict)
|
res.append(type_dict)
|
||||||
@@ -127,7 +132,9 @@ class CITypeManager(object):
|
|||||||
def add(cls, **kwargs):
|
def add(cls, **kwargs):
|
||||||
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
|
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
|
||||||
if ErrFormat.ci_type_config not in {i['name'] for i in ACLManager().get_resources(ResourceTypeEnum.PAGE)}:
|
if ErrFormat.ci_type_config not in {i['name'] for i in ACLManager().get_resources(ResourceTypeEnum.PAGE)}:
|
||||||
return abort(403, ErrFormat.no_permission2)
|
app_cli = CMDBApp()
|
||||||
|
validate_permission(app_cli.op.Model_Configuration, app_cli.resource_type_name,
|
||||||
|
app_cli.op.create_CIType, app_cli.app_name)
|
||||||
|
|
||||||
unique_key = kwargs.pop("unique_key", None) or kwargs.pop("unique_id", None)
|
unique_key = kwargs.pop("unique_key", None) or kwargs.pop("unique_id", None)
|
||||||
unique_key = AttributeCache.get(unique_key) or abort(404, ErrFormat.unique_key_not_define)
|
unique_key = AttributeCache.get(unique_key) or abort(404, ErrFormat.unique_key_not_define)
|
||||||
@@ -192,7 +199,7 @@ class CITypeManager(object):
|
|||||||
CITypeAttributeManager.update(type_id, [attr])
|
CITypeAttributeManager.update(type_id, [attr])
|
||||||
|
|
||||||
ci_type2 = ci_type.to_dict()
|
ci_type2 = ci_type.to_dict()
|
||||||
new = ci_type.update(**kwargs)
|
new = ci_type.update(**kwargs, filter_none=False)
|
||||||
|
|
||||||
CITypeCache.clean(type_id)
|
CITypeCache.clean(type_id)
|
||||||
|
|
||||||
@@ -250,6 +257,16 @@ class CITypeManager(object):
|
|||||||
for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False):
|
for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False):
|
||||||
item.delete(commit=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
|
||||||
|
|
||||||
|
for item in TopologyView.get_by(central_node_type=type_id, to_dict=False):
|
||||||
|
item.delete(commit=False)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
ci_type.soft_delete()
|
ci_type.soft_delete()
|
||||||
@@ -411,9 +428,6 @@ class CITypeGroupManager(object):
|
|||||||
existed = CITypeGroup.get_by_id(gid) or abort(
|
existed = CITypeGroup.get_by_id(gid) or abort(
|
||||||
404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid)))
|
404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid)))
|
||||||
if name is not None and name != existed.name:
|
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)
|
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])
|
max_order = max([i.order or 0 for i in CITypeGroupItem.get_by(group_id=gid, to_dict=False)] or [0])
|
||||||
@@ -672,10 +686,17 @@ class CITypeAttributeManager(object):
|
|||||||
item = CITypeTrigger.get_by(type_id=_type_id, attr_id=attr_id, to_dict=False, first=True)
|
item = CITypeTrigger.get_by(type_id=_type_id, attr_id=attr_id, to_dict=False, first=True)
|
||||||
item and item.soft_delete(commit=False)
|
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()
|
db.session.commit()
|
||||||
|
|
||||||
CITypeAttributeCache.clean(type_id, attr_id)
|
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,
|
CITypeHistoryManager.add(CITypeOperateType.DELETE_ATTRIBUTE, type_id, attr_id=attr.id,
|
||||||
change=attr and attr.to_dict())
|
change=attr and attr.to_dict())
|
||||||
|
|
||||||
@@ -699,6 +720,10 @@ class CITypeAttributeManager(object):
|
|||||||
to_group = CITypeAttributeGroup.get_by(type_id=type_id, name=to_group_name, first=True, to_dict=False)
|
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
|
to_group_id = to_group and to_group.id
|
||||||
|
|
||||||
|
if not to_group_id and CITypeInheritance.get_by(child_id=type_id, to_dict=False):
|
||||||
|
to_group = CITypeAttributeGroup.create(type_id=type_id, name=to_group_name)
|
||||||
|
to_group_id = to_group and to_group.id
|
||||||
|
|
||||||
if from_group_id != to_group_id:
|
if from_group_id != to_group_id:
|
||||||
if from_group_id is not None:
|
if from_group_id is not None:
|
||||||
CITypeAttributeGroupManager.delete_item(from_group_id, attr_id)
|
CITypeAttributeGroupManager.delete_item(from_group_id, attr_id)
|
||||||
@@ -726,14 +751,19 @@ class CITypeRelationManager(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get():
|
def get():
|
||||||
res = CITypeRelation.get_by(to_dict=False)
|
res = CITypeRelation.get_by(to_dict=False)
|
||||||
|
type2attributes = dict()
|
||||||
for idx, item in enumerate(res):
|
for idx, item in enumerate(res):
|
||||||
_item = item.to_dict()
|
_item = item.to_dict()
|
||||||
res[idx] = _item
|
res[idx] = _item
|
||||||
res[idx]['parent'] = item.parent.to_dict()
|
res[idx]['parent'] = item.parent.to_dict()
|
||||||
|
if item.parent_id not in type2attributes:
|
||||||
|
type2attributes[item.parent_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.parent_id)]
|
||||||
res[idx]['child'] = item.child.to_dict()
|
res[idx]['child'] = item.child.to_dict()
|
||||||
|
if item.child_id not in type2attributes:
|
||||||
|
type2attributes[item.child_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.child_id)]
|
||||||
res[idx]['relation_type'] = item.relation_type.to_dict()
|
res[idx]['relation_type'] = item.relation_type.to_dict()
|
||||||
|
|
||||||
return res
|
return res, type2attributes
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_child_type_ids(type_id, level):
|
def get_child_type_ids(type_id, level):
|
||||||
@@ -765,6 +795,8 @@ class CITypeRelationManager(object):
|
|||||||
|
|
||||||
ci_type_dict["relation_type"] = relation_inst.relation_type.name
|
ci_type_dict["relation_type"] = relation_inst.relation_type.name
|
||||||
ci_type_dict["constraint"] = relation_inst.constraint
|
ci_type_dict["constraint"] = relation_inst.constraint
|
||||||
|
ci_type_dict["parent_attr_id"] = relation_inst.parent_attr_id
|
||||||
|
ci_type_dict["child_attr_id"] = relation_inst.child_attr_id
|
||||||
|
|
||||||
return ci_type_dict
|
return ci_type_dict
|
||||||
|
|
||||||
@@ -797,6 +829,70 @@ class CITypeRelationManager(object):
|
|||||||
|
|
||||||
return [cls._wrap_relation_type_dict(parent.parent_id, parent) for parent in parents]
|
return [cls._wrap_relation_type_dict(parent.parent_id, parent) for parent in parents]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_relations_by_type_id(type_id):
|
||||||
|
nodes, edges = [], []
|
||||||
|
node_ids, edge_tuples = set(), set()
|
||||||
|
ci_type = CITypeCache.get(type_id)
|
||||||
|
if ci_type is None:
|
||||||
|
return nodes, edges
|
||||||
|
|
||||||
|
nodes.append(ci_type.to_dict())
|
||||||
|
node_ids.add(ci_type.id)
|
||||||
|
|
||||||
|
def _find(_id, lv):
|
||||||
|
lv += 1
|
||||||
|
for i in CITypeRelation.get_by(parent_id=_id, to_dict=False):
|
||||||
|
if i.child_id not in node_ids:
|
||||||
|
node_ids.add(i.child_id)
|
||||||
|
node = i.child.to_dict()
|
||||||
|
node.setdefault('level', []).append(lv)
|
||||||
|
nodes.append(node)
|
||||||
|
|
||||||
|
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=False))
|
||||||
|
edge_tuples.add((i.parent_id, i.child_id))
|
||||||
|
|
||||||
|
_find(i.child_id, lv)
|
||||||
|
continue
|
||||||
|
elif (i.parent_id, i.child_id) not in edge_tuples:
|
||||||
|
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=False))
|
||||||
|
edge_tuples.add((i.parent_id, i.child_id))
|
||||||
|
_find(i.child_id, lv)
|
||||||
|
|
||||||
|
for _node in nodes:
|
||||||
|
if _node['id'] == i.child_id and lv not in _node['level']:
|
||||||
|
_node['level'].append(lv)
|
||||||
|
|
||||||
|
def _reverse_find(_id, lv):
|
||||||
|
lv -= 1
|
||||||
|
for i in CITypeRelation.get_by(child_id=_id, to_dict=False):
|
||||||
|
if i.parent_id not in node_ids:
|
||||||
|
node_ids.add(i.parent_id)
|
||||||
|
node = i.parent.to_dict()
|
||||||
|
node.setdefault('level', []).append(lv)
|
||||||
|
nodes.append(node)
|
||||||
|
|
||||||
|
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=True))
|
||||||
|
edge_tuples.add((i.parent_id, i.child_id))
|
||||||
|
|
||||||
|
_reverse_find(i.parent_id, lv)
|
||||||
|
continue
|
||||||
|
elif (i.parent_id, i.child_id) not in edge_tuples:
|
||||||
|
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=True))
|
||||||
|
edge_tuples.add((i.parent_id, i.child_id))
|
||||||
|
_reverse_find(i.parent_id, lv)
|
||||||
|
|
||||||
|
for _node in nodes:
|
||||||
|
if _node['id'] == i.child_id and lv not in _node['level']:
|
||||||
|
_node['level'].append(lv)
|
||||||
|
|
||||||
|
level = 0
|
||||||
|
_reverse_find(ci_type.id, level)
|
||||||
|
level = 0
|
||||||
|
_find(ci_type.id, level)
|
||||||
|
|
||||||
|
return nodes, edges
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get(parent_id, child_id):
|
def _get(parent_id, child_id):
|
||||||
return CITypeRelation.get_by(parent_id=parent_id,
|
return CITypeRelation.get_by(parent_id=parent_id,
|
||||||
@@ -809,7 +905,8 @@ class CITypeRelationManager(object):
|
|||||||
return "{} -> {}".format(first_name, second_name)
|
return "{} -> {}".format(first_name, second_name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add(cls, parent, child, relation_type_id, constraint=ConstraintEnum.One2Many):
|
def add(cls, parent, child, relation_type_id, constraint=ConstraintEnum.One2Many,
|
||||||
|
parent_attr_id=None, child_attr_id=None):
|
||||||
p = CITypeManager.check_is_existed(parent)
|
p = CITypeManager.check_is_existed(parent)
|
||||||
c = CITypeManager.check_is_existed(child)
|
c = CITypeManager.check_is_existed(child)
|
||||||
|
|
||||||
@@ -824,24 +921,21 @@ class CITypeRelationManager(object):
|
|||||||
current_app.logger.warning(str(e))
|
current_app.logger.warning(str(e))
|
||||||
return abort(400, ErrFormat.circular_dependency_error)
|
return abort(400, ErrFormat.circular_dependency_error)
|
||||||
|
|
||||||
# if constraint == ConstraintEnum.Many2Many:
|
old_parent_attr_id = None
|
||||||
# other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
|
|
||||||
# to_dict=False, first=True)
|
|
||||||
# other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
|
|
||||||
# to_dict=False, first=True)
|
|
||||||
# if other_c and other_c.child_id != c.id:
|
|
||||||
# return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
|
|
||||||
# if other_p and other_p.parent_id != p.id:
|
|
||||||
# return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
|
|
||||||
|
|
||||||
existed = cls._get(p.id, c.id)
|
existed = cls._get(p.id, c.id)
|
||||||
if existed is not None:
|
if existed is not None:
|
||||||
existed.update(relation_type_id=relation_type_id,
|
old_parent_attr_id = existed.parent_attr_id
|
||||||
constraint=constraint)
|
existed = existed.update(relation_type_id=relation_type_id,
|
||||||
|
constraint=constraint,
|
||||||
|
parent_attr_id=parent_attr_id,
|
||||||
|
child_attr_id=child_attr_id,
|
||||||
|
filter_none=False)
|
||||||
else:
|
else:
|
||||||
existed = CITypeRelation.create(parent_id=p.id,
|
existed = CITypeRelation.create(parent_id=p.id,
|
||||||
child_id=c.id,
|
child_id=c.id,
|
||||||
relation_type_id=relation_type_id,
|
relation_type_id=relation_type_id,
|
||||||
|
parent_attr_id=parent_attr_id,
|
||||||
|
child_attr_id=child_attr_id,
|
||||||
constraint=constraint)
|
constraint=constraint)
|
||||||
|
|
||||||
if current_app.config.get("USE_ACL"):
|
if current_app.config.get("USE_ACL"):
|
||||||
@@ -855,6 +949,11 @@ class CITypeRelationManager(object):
|
|||||||
current_user.username,
|
current_user.username,
|
||||||
ResourceTypeEnum.CI_TYPE_RELATION)
|
ResourceTypeEnum.CI_TYPE_RELATION)
|
||||||
|
|
||||||
|
if parent_attr_id and parent_attr_id != old_parent_attr_id:
|
||||||
|
if parent_attr_id and parent_attr_id != existed.parent_attr_id:
|
||||||
|
from api.tasks.cmdb import rebuild_relation_for_attribute_changed
|
||||||
|
rebuild_relation_for_attribute_changed.apply_async(args=(existed.to_dict()))
|
||||||
|
|
||||||
CITypeHistoryManager.add(CITypeOperateType.ADD_RELATION, p.id,
|
CITypeHistoryManager.add(CITypeOperateType.ADD_RELATION, p.id,
|
||||||
change=dict(parent=p.to_dict(), child=c.to_dict(), relation_type_id=relation_type_id))
|
change=dict(parent=p.to_dict(), child=c.to_dict(), relation_type_id=relation_type_id))
|
||||||
|
|
||||||
@@ -1147,6 +1246,8 @@ class CITypeTemplateManager(object):
|
|||||||
id2obj_dicts[added_id].get('child_id'),
|
id2obj_dicts[added_id].get('child_id'),
|
||||||
id2obj_dicts[added_id].get('relation_type_id'),
|
id2obj_dicts[added_id].get('relation_type_id'),
|
||||||
id2obj_dicts[added_id].get('constraint'),
|
id2obj_dicts[added_id].get('constraint'),
|
||||||
|
id2obj_dicts[added_id].get('parent_attr_id'),
|
||||||
|
id2obj_dicts[added_id].get('child_attr_id'),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
obj = cls.create(flush=True, **id2obj_dicts[added_id])
|
obj = cls.create(flush=True, **id2obj_dicts[added_id])
|
||||||
@@ -1188,6 +1289,8 @@ class CITypeTemplateManager(object):
|
|||||||
i.pop('choice_web_hook', None)
|
i.pop('choice_web_hook', None)
|
||||||
i.pop('choice_other', None)
|
i.pop('choice_other', None)
|
||||||
i.pop('order', None)
|
i.pop('order', None)
|
||||||
|
i.pop('inherited', None)
|
||||||
|
i.pop('inherited_from', None)
|
||||||
choice_value = i.pop('choice_value', None)
|
choice_value = i.pop('choice_value', None)
|
||||||
if not choice_value:
|
if not choice_value:
|
||||||
i['is_choice'] = False
|
i['is_choice'] = False
|
||||||
@@ -1205,7 +1308,10 @@ class CITypeTemplateManager(object):
|
|||||||
def _import_ci_types(self, ci_types, attr_id_map):
|
def _import_ci_types(self, ci_types, attr_id_map):
|
||||||
for i in ci_types:
|
for i in ci_types:
|
||||||
i.pop("unique_key", None)
|
i.pop("unique_key", None)
|
||||||
|
i.pop("show_name", None)
|
||||||
i['unique_id'] = attr_id_map.get(i['unique_id'], i['unique_id'])
|
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
|
i['uid'] = current_user.uid
|
||||||
|
|
||||||
return self.__import(CIType, ci_types)
|
return self.__import(CIType, ci_types)
|
||||||
@@ -1299,6 +1405,8 @@ class CITypeTemplateManager(object):
|
|||||||
_group = copy.deepcopy(group)
|
_group = copy.deepcopy(group)
|
||||||
_group.pop('attributes', None)
|
_group.pop('attributes', None)
|
||||||
_group.pop('id', None)
|
_group.pop('id', None)
|
||||||
|
_group.pop('inherited', None)
|
||||||
|
_group.pop('inherited_from', None)
|
||||||
existed = CITypeAttributeGroup.get_by(name=_group['name'],
|
existed = CITypeAttributeGroup.get_by(name=_group['name'],
|
||||||
type_id=type_id_map.get(_group['type_id'], _group['type_id']),
|
type_id=type_id_map.get(_group['type_id'], _group['type_id']),
|
||||||
first=True, to_dict=False)
|
first=True, to_dict=False)
|
||||||
@@ -1354,9 +1462,13 @@ class CITypeTemplateManager(object):
|
|||||||
rule.pop("id", None)
|
rule.pop("id", None)
|
||||||
rule.pop("created_at", None)
|
rule.pop("created_at", None)
|
||||||
rule.pop("updated_at", None)
|
rule.pop("updated_at", None)
|
||||||
|
rule.pop("relation", None)
|
||||||
|
|
||||||
rule['uid'] = current_user.uid
|
rule['uid'] = current_user.uid
|
||||||
|
|
||||||
|
if not rule.get('attributes'):
|
||||||
|
continue
|
||||||
|
|
||||||
existed = False
|
existed = False
|
||||||
for i in AutoDiscoveryCIType.get_by(type_id=ci_type.id, adr_id=rule['adr_id'], to_dict=False):
|
for i in AutoDiscoveryCIType.get_by(type_id=ci_type.id, adr_id=rule['adr_id'], to_dict=False):
|
||||||
if ((i.extra_option or {}).get('alias') or None) == (
|
if ((i.extra_option or {}).get('alias') or None) == (
|
||||||
@@ -1429,7 +1541,7 @@ class CITypeTemplateManager(object):
|
|||||||
ci_types=CITypeManager.get_ci_types(),
|
ci_types=CITypeManager.get_ci_types(),
|
||||||
ci_type_groups=CITypeGroupManager.get(),
|
ci_type_groups=CITypeGroupManager.get(),
|
||||||
relation_types=[i.to_dict() for i in RelationTypeManager.get_all()],
|
relation_types=[i.to_dict() for i in RelationTypeManager.get_all()],
|
||||||
ci_type_relations=CITypeRelationManager.get(),
|
ci_type_relations=CITypeRelationManager.get()[0],
|
||||||
ci_type_auto_discovery_rules=list(),
|
ci_type_auto_discovery_rules=list(),
|
||||||
type2attributes=dict(),
|
type2attributes=dict(),
|
||||||
type2attribute_group=dict(),
|
type2attribute_group=dict(),
|
||||||
|
@@ -55,6 +55,9 @@ class CITypeOperateType(BaseEnum):
|
|||||||
DELETE_UNIQUE_CONSTRAINT = "11" # 删除联合唯一
|
DELETE_UNIQUE_CONSTRAINT = "11" # 删除联合唯一
|
||||||
ADD_RELATION = "12" # 新增关系
|
ADD_RELATION = "12" # 新增关系
|
||||||
DELETE_RELATION = "13" # 删除关系
|
DELETE_RELATION = "13" # 删除关系
|
||||||
|
ADD_RECONCILIATION = "14" # 删除关系
|
||||||
|
UPDATE_RECONCILIATION = "15" # 删除关系
|
||||||
|
DELETE_RECONCILIATION = "16" # 删除关系
|
||||||
|
|
||||||
|
|
||||||
class RetKey(BaseEnum):
|
class RetKey(BaseEnum):
|
||||||
@@ -70,6 +73,7 @@ class ResourceTypeEnum(BaseEnum):
|
|||||||
RELATION_VIEW = "RelationView" # read/update/delete/grant
|
RELATION_VIEW = "RelationView" # read/update/delete/grant
|
||||||
CI_FILTER = "CIFilter" # read
|
CI_FILTER = "CIFilter" # read
|
||||||
PAGE = "page" # read
|
PAGE = "page" # read
|
||||||
|
TOPOLOGY_VIEW = "TopologyView" # read/update/delete/grant
|
||||||
|
|
||||||
|
|
||||||
class PermEnum(BaseEnum):
|
class PermEnum(BaseEnum):
|
||||||
@@ -98,6 +102,12 @@ class AttributeDefaultValueEnum(BaseEnum):
|
|||||||
AUTO_INC_ID = "$auto_inc_id"
|
AUTO_INC_ID = "$auto_inc_id"
|
||||||
|
|
||||||
|
|
||||||
|
class ExecuteStatusEnum(BaseEnum):
|
||||||
|
COMPLETED = '0'
|
||||||
|
FAILED = '1'
|
||||||
|
RUNNING = '2'
|
||||||
|
|
||||||
|
|
||||||
CMDB_QUEUE = "one_cmdb_async"
|
CMDB_QUEUE = "one_cmdb_async"
|
||||||
REDIS_PREFIX_CI = "ONE_CMDB"
|
REDIS_PREFIX_CI = "ONE_CMDB"
|
||||||
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
||||||
|
@@ -26,7 +26,7 @@ from api.models.cmdb import OperationRecord
|
|||||||
class AttributeHistoryManger(object):
|
class AttributeHistoryManger(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_records_for_attributes(start, end, username, page, page_size, operate_type, type_id,
|
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(
|
records = db.session.query(OperationRecord, AttributeHistory).join(
|
||||||
AttributeHistory, OperationRecord.id == AttributeHistory.record_id)
|
AttributeHistory, OperationRecord.id == AttributeHistory.record_id)
|
||||||
@@ -48,6 +48,9 @@ class AttributeHistoryManger(object):
|
|||||||
if ci_id is not None:
|
if ci_id is not None:
|
||||||
records = records.filter(AttributeHistory.ci_id == ci_id)
|
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:
|
if attr_id is not None:
|
||||||
records = records.filter(AttributeHistory.attr_id == attr_id)
|
records = records.filter(AttributeHistory.attr_id == attr_id)
|
||||||
|
|
||||||
@@ -62,6 +65,12 @@ class AttributeHistoryManger(object):
|
|||||||
if attr_hist['attr']:
|
if attr_hist['attr']:
|
||||||
attr_hist['attr_name'] = attr_hist['attr'].name
|
attr_hist['attr_name'] = attr_hist['attr'].name
|
||||||
attr_hist['attr_alias'] = attr_hist['attr'].alias
|
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")
|
attr_hist.pop("attr")
|
||||||
|
|
||||||
if record_id not in res:
|
if record_id not in res:
|
||||||
@@ -161,12 +170,14 @@ class AttributeHistoryManger(object):
|
|||||||
record = i.OperationRecord
|
record = i.OperationRecord
|
||||||
item = dict(attr_name=attr.name,
|
item = dict(attr_name=attr.name,
|
||||||
attr_alias=attr.alias,
|
attr_alias=attr.alias,
|
||||||
|
value_type=attr.value_type,
|
||||||
operate_type=hist.operate_type,
|
operate_type=hist.operate_type,
|
||||||
username=user and user.nickname,
|
username=user and user.nickname,
|
||||||
old=hist.old,
|
old=hist.old,
|
||||||
new=hist.new,
|
new=hist.new,
|
||||||
created_at=record.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
created_at=record.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
record_id=record.id,
|
record_id=record.id,
|
||||||
|
ticket_id=record.ticket_id,
|
||||||
hid=hist.id
|
hid=hist.id
|
||||||
)
|
)
|
||||||
result.append(item)
|
result.append(item)
|
||||||
@@ -200,9 +211,9 @@ class AttributeHistoryManger(object):
|
|||||||
return username, timestamp, attr_dict, rel_dict
|
return username, timestamp, attr_dict, rel_dict
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add(record_id, ci_id, history_list, type_id=None, flush=False, commit=True):
|
def add(record_id, ci_id, history_list, type_id=None, ticket_id=None, flush=False, commit=True):
|
||||||
if record_id is None:
|
if record_id is None:
|
||||||
record = OperationRecord.create(uid=current_user.uid, type_id=type_id)
|
record = OperationRecord.create(uid=current_user.uid, type_id=type_id, ticket_id=ticket_id)
|
||||||
record_id = record.id
|
record_id = record.id
|
||||||
|
|
||||||
for attr_id, operate_type, old, new in history_list or []:
|
for attr_id, operate_type, old, new in history_list or []:
|
||||||
@@ -270,7 +281,7 @@ class CITypeHistoryManager(object):
|
|||||||
return numfound, result
|
return numfound, result
|
||||||
|
|
||||||
@staticmethod
|
@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:
|
if type_id is None and attr_id is not None:
|
||||||
from api.models.cmdb import CITypeAttribute
|
from api.models.cmdb import CITypeAttribute
|
||||||
type_ids = [i.type_id for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False)]
|
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,
|
uid=current_user.uid,
|
||||||
attr_id=attr_id,
|
attr_id=attr_id,
|
||||||
trigger_id=trigger_id,
|
trigger_id=trigger_id,
|
||||||
|
rc_id=rc_id,
|
||||||
unique_constraint_id=unique_constraint_id,
|
unique_constraint_id=unique_constraint_id,
|
||||||
change=change)
|
change=change)
|
||||||
|
|
||||||
|
@@ -258,7 +258,7 @@ class CIFilterPermsCRUD(DBMixin):
|
|||||||
|
|
||||||
request_id_filter = {}
|
request_id_filter = {}
|
||||||
for _id, v in (kwargs.get('id_filter') or {}).items():
|
for _id, v in (kwargs.get('id_filter') or {}).items():
|
||||||
key = ",".join([v['parent_path']] if v.get('parent_path') else [] + [str(_id)])
|
key = ",".join(([v['parent_path']] if v.get('parent_path') else []) + [str(_id)])
|
||||||
request_id_filter[key] = v['name']
|
request_id_filter[key] = v['name']
|
||||||
|
|
||||||
resource = None
|
resource = None
|
||||||
|
@@ -25,6 +25,8 @@ from api.lib.cmdb.resp_format import ErrFormat
|
|||||||
from api.lib.exception import AbortException
|
from api.lib.exception import AbortException
|
||||||
from api.lib.perm.acl.acl import ACLManager
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
from api.models.cmdb import CITypeAttribute
|
from api.models.cmdb import CITypeAttribute
|
||||||
|
from api.models.cmdb import CITypeGroup
|
||||||
|
from api.models.cmdb import CITypeGroupItem
|
||||||
from api.models.cmdb import CITypeRelation
|
from api.models.cmdb import CITypeRelation
|
||||||
from api.models.cmdb import PreferenceCITypeOrder
|
from api.models.cmdb import PreferenceCITypeOrder
|
||||||
from api.models.cmdb import PreferenceRelationView
|
from api.models.cmdb import PreferenceRelationView
|
||||||
@@ -43,22 +45,46 @@ class PreferenceManager(object):
|
|||||||
def get_types(instance=False, tree=False):
|
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)
|
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, to_dict=False), key=lambda x: x.order)
|
||||||
|
|
||||||
|
type2group = {}
|
||||||
|
for i in db.session.query(CITypeGroupItem, CITypeGroup).join(
|
||||||
|
CITypeGroup, CITypeGroup.id == CITypeGroupItem.group_id).filter(
|
||||||
|
CITypeGroup.deleted.is_(False)).filter(CITypeGroupItem.deleted.is_(False)):
|
||||||
|
type2group[i.CITypeGroupItem.type_id] = i.CITypeGroup.to_dict()
|
||||||
|
|
||||||
types = db.session.query(PreferenceShowAttributes.type_id).filter(
|
types = db.session.query(PreferenceShowAttributes.type_id).filter(
|
||||||
PreferenceShowAttributes.uid == current_user.uid).filter(
|
PreferenceShowAttributes.uid == current_user.uid).filter(
|
||||||
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
||||||
PreferenceShowAttributes.type_id).all() if instance else []
|
PreferenceShowAttributes.type_id).all() if instance else []
|
||||||
types = sorted(types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
|
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))
|
ci_type_order) if not i.is_tree}.get(x.type_id, 1))
|
||||||
|
group_types = []
|
||||||
|
other_types = []
|
||||||
|
group2idx = {}
|
||||||
|
type_ids = set()
|
||||||
|
for ci_type in types:
|
||||||
|
type_id = ci_type.type_id
|
||||||
|
type_ids.add(type_id)
|
||||||
|
type_dict = CITypeCache.get(type_id).to_dict()
|
||||||
|
if type_id not in type2group:
|
||||||
|
other_types.append(type_dict)
|
||||||
|
else:
|
||||||
|
group = type2group[type_id]
|
||||||
|
if group['id'] not in group2idx:
|
||||||
|
group_types.append(type2group[type_id])
|
||||||
|
group2idx[group['id']] = len(group_types) - 1
|
||||||
|
group_types[group2idx[group['id']]].setdefault('ci_types', []).append(type_dict)
|
||||||
|
if other_types:
|
||||||
|
group_types.append(dict(ci_types=other_types))
|
||||||
|
|
||||||
tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else []
|
tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else []
|
||||||
tree_types = sorted(tree_types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
|
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))
|
ci_type_order) if i.is_tree}.get(x.type_id, 1))
|
||||||
|
|
||||||
type_ids = [i.type_id for i in types + tree_types]
|
tree_types = [CITypeCache.get(_type.type_id).to_dict() for _type in tree_types]
|
||||||
if types and tree_types:
|
for _type in tree_types:
|
||||||
type_ids = set(type_ids)
|
type_ids.add(_type['id'])
|
||||||
|
|
||||||
return [CITypeCache.get(type_id).to_dict() for type_id in type_ids]
|
return dict(group_types=group_types, tree_types=tree_types, type_ids=list(type_ids))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_types2(instance=False, tree=False):
|
def get_types2(instance=False, tree=False):
|
||||||
@@ -238,11 +264,13 @@ class PreferenceManager(object):
|
|||||||
views = _views
|
views = _views
|
||||||
|
|
||||||
view2cr_ids = dict()
|
view2cr_ids = dict()
|
||||||
|
name2view = dict()
|
||||||
result = dict()
|
result = dict()
|
||||||
name2id = list()
|
name2id = list()
|
||||||
for view in views:
|
for view in views:
|
||||||
view2cr_ids.setdefault(view['name'], []).extend(view['cr_ids'])
|
view2cr_ids.setdefault(view['name'], []).extend(view['cr_ids'])
|
||||||
name2id.append([view['name'], view['id']])
|
name2id.append([view['name'], view['id']])
|
||||||
|
name2view[view['name']] = view
|
||||||
|
|
||||||
id2type = dict()
|
id2type = dict()
|
||||||
for view_name in view2cr_ids:
|
for view_name in view2cr_ids:
|
||||||
@@ -286,6 +314,8 @@ class PreferenceManager(object):
|
|||||||
topo_flatten=topo_flatten,
|
topo_flatten=topo_flatten,
|
||||||
level2constraint=level2constraint,
|
level2constraint=level2constraint,
|
||||||
leaf=leaf,
|
leaf=leaf,
|
||||||
|
option=name2view[view_name]['option'],
|
||||||
|
is_public=name2view[view_name]['is_public'],
|
||||||
leaf2show_types=leaf2show_types,
|
leaf2show_types=leaf2show_types,
|
||||||
node2show_types=node2show_types,
|
node2show_types=node2show_types,
|
||||||
show_types=[CITypeCache.get(j).to_dict()
|
show_types=[CITypeCache.get(j).to_dict()
|
||||||
@@ -293,18 +323,26 @@ class PreferenceManager(object):
|
|||||||
|
|
||||||
for type_id in id2type:
|
for type_id in id2type:
|
||||||
id2type[type_id] = CITypeCache.get(type_id).to_dict()
|
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])
|
return result, id2type, sorted(name2id, key=lambda x: x[1])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_or_update_relation_view(cls, name, cr_ids, is_public=False):
|
def create_or_update_relation_view(cls, name=None, cr_ids=None, _id=None, is_public=False, option=None):
|
||||||
if not cr_ids:
|
if not cr_ids:
|
||||||
return abort(400, ErrFormat.preference_relation_view_node_required)
|
return abort(400, ErrFormat.preference_relation_view_node_required)
|
||||||
|
|
||||||
existed = PreferenceRelationView.get_by(name=name, to_dict=False, first=True)
|
if _id is None:
|
||||||
|
existed = PreferenceRelationView.get_by(name=name, to_dict=False, first=True)
|
||||||
|
else:
|
||||||
|
existed = PreferenceRelationView.get_by_id(_id)
|
||||||
current_app.logger.debug(existed)
|
current_app.logger.debug(existed)
|
||||||
if existed is None:
|
if existed is None:
|
||||||
PreferenceRelationView.create(name=name, cr_ids=cr_ids, uid=current_user.uid, is_public=is_public)
|
PreferenceRelationView.create(name=name, cr_ids=cr_ids, uid=current_user.uid,
|
||||||
|
is_public=is_public, option=option)
|
||||||
|
|
||||||
if current_app.config.get("USE_ACL"):
|
if current_app.config.get("USE_ACL"):
|
||||||
ACLManager().add_resource(name, ResourceTypeEnum.RELATION_VIEW)
|
ACLManager().add_resource(name, ResourceTypeEnum.RELATION_VIEW)
|
||||||
@@ -312,6 +350,11 @@ class PreferenceManager(object):
|
|||||||
RoleEnum.CMDB_READ_ALL,
|
RoleEnum.CMDB_READ_ALL,
|
||||||
ResourceTypeEnum.RELATION_VIEW,
|
ResourceTypeEnum.RELATION_VIEW,
|
||||||
permissions=[PermEnum.READ])
|
permissions=[PermEnum.READ])
|
||||||
|
else:
|
||||||
|
if existed.name != name and current_app.config.get("USE_ACL"):
|
||||||
|
ACLManager().update_resource(existed.name, name, ResourceTypeEnum.RELATION_VIEW)
|
||||||
|
|
||||||
|
existed.update(name=name, cr_ids=cr_ids, is_public=is_public, option=option)
|
||||||
|
|
||||||
return cls.get_relation_view()
|
return cls.get_relation_view()
|
||||||
|
|
||||||
|
@@ -78,6 +78,8 @@ class ErrFormat(CommonErrFormat):
|
|||||||
unique_constraint_invalid = _l("Uniquely constrained attributes cannot be JSON and multi-valued")
|
unique_constraint_invalid = _l("Uniquely constrained attributes cannot be JSON and multi-valued")
|
||||||
ci_type_trigger_duplicate = _l("Duplicated trigger") # 重复的触发器
|
ci_type_trigger_duplicate = _l("Duplicated trigger") # 重复的触发器
|
||||||
ci_type_trigger_not_found = _l("Trigger {} does not exist") # 触发器 {} 不存在
|
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") # 操作记录 {} 不存在
|
record_not_found = _l("Operation record {} does not exist") # 操作记录 {} 不存在
|
||||||
cannot_delete_unique = _l("Unique identifier cannot be deleted") # 不能删除唯一标识
|
cannot_delete_unique = _l("Unique identifier cannot be deleted") # 不能删除唯一标识
|
||||||
@@ -96,7 +98,7 @@ class ErrFormat(CommonErrFormat):
|
|||||||
# 属性 {} 的值必须是唯一的, 当前值 {} 已存在
|
# 属性 {} 的值必须是唯一的, 当前值 {} 已存在
|
||||||
attribute_value_unique_required = _l("The value of attribute {} must be unique, {} already exists")
|
attribute_value_unique_required = _l("The value of attribute {} must be unique, {} already exists")
|
||||||
attribute_value_required = _l("Attribute {} value must exist") # 属性 {} 值必须存在
|
attribute_value_required = _l("Attribute {} value must exist") # 属性 {} 值必须存在
|
||||||
|
attribute_value_out_of_range = _l("Out of range value, the maximum value is 2147483647")
|
||||||
# 新增或者修改属性值未知错误: {}
|
# 新增或者修改属性值未知错误: {}
|
||||||
attribute_value_unknown_error = _l("Unknown error when adding or modifying attribute value: {}")
|
attribute_value_unknown_error = _l("Unknown error when adding or modifying attribute value: {}")
|
||||||
|
|
||||||
@@ -138,3 +140,12 @@ class ErrFormat(CommonErrFormat):
|
|||||||
|
|
||||||
password_save_failed = _l("Failed to save password: {}") # 保存密码失败: {}
|
password_save_failed = _l("Failed to save password: {}") # 保存密码失败: {}
|
||||||
password_load_failed = _l("Failed to get password: {}") # 获取密码失败: {}
|
password_load_failed = _l("Failed to get password: {}") # 获取密码失败: {}
|
||||||
|
|
||||||
|
cron_time_format_invalid = _l("Scheduling time format error") # 调度时间格式错误
|
||||||
|
reconciliation_title = _l("CMDB data reconciliation results") # CMDB数据合规检查结果
|
||||||
|
reconciliation_body = _l("Number of {} illegal: {}") # "{} 不合规数: {}"
|
||||||
|
|
||||||
|
topology_exists = _l("Topology view {} already exists") # 拓扑视图 {} 已经存在
|
||||||
|
topology_group_exists = _l("Topology group {} already exists") # 拓扑视图分组 {} 已经存在
|
||||||
|
# 因为该分组下定义了拓扑视图,不能删除
|
||||||
|
topo_view_exists_cannot_delete_group = _l("The group cannot be deleted because the topology view already exists")
|
||||||
|
@@ -17,10 +17,12 @@ def search(query=None,
|
|||||||
count=1,
|
count=1,
|
||||||
sort=None,
|
sort=None,
|
||||||
excludes=None,
|
excludes=None,
|
||||||
use_id_filter=True):
|
use_id_filter=False,
|
||||||
|
use_ci_filter=True):
|
||||||
if current_app.config.get("USE_ES"):
|
if current_app.config.get("USE_ES"):
|
||||||
s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
|
s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
|
||||||
else:
|
else:
|
||||||
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes, use_id_filter=use_id_filter)
|
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes,
|
||||||
|
use_id_filter=use_id_filter, use_ci_filter=use_ci_filter)
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
@@ -47,7 +47,8 @@ class Search(object):
|
|||||||
excludes=None,
|
excludes=None,
|
||||||
parent_node_perm_passed=False,
|
parent_node_perm_passed=False,
|
||||||
use_id_filter=False,
|
use_id_filter=False,
|
||||||
use_ci_filter=True):
|
use_ci_filter=True,
|
||||||
|
only_ids=False):
|
||||||
self.orig_query = query
|
self.orig_query = query
|
||||||
self.fl = fl or []
|
self.fl = fl or []
|
||||||
self.excludes = excludes or []
|
self.excludes = excludes or []
|
||||||
@@ -64,10 +65,12 @@ class Search(object):
|
|||||||
self.parent_node_perm_passed = parent_node_perm_passed
|
self.parent_node_perm_passed = parent_node_perm_passed
|
||||||
self.use_id_filter = use_id_filter
|
self.use_id_filter = use_id_filter
|
||||||
self.use_ci_filter = use_ci_filter
|
self.use_ci_filter = use_ci_filter
|
||||||
|
self.only_ids = only_ids
|
||||||
|
|
||||||
self.valid_type_names = []
|
self.valid_type_names = []
|
||||||
self.type2filter_perms = dict()
|
self.type2filter_perms = dict()
|
||||||
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||||
|
self.is_app_admin = self.is_app_admin or (not self.use_ci_filter and not self.use_id_filter)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _operator_proc(key):
|
def _operator_proc(key):
|
||||||
@@ -590,6 +593,8 @@ class Search(object):
|
|||||||
def search(self):
|
def search(self):
|
||||||
numfound, ci_ids = self._query_build_raw()
|
numfound, ci_ids = self._query_build_raw()
|
||||||
ci_ids = list(map(str, ci_ids))
|
ci_ids = list(map(str, ci_ids))
|
||||||
|
if self.only_ids:
|
||||||
|
return ci_ids
|
||||||
|
|
||||||
_fl = self._fl_build()
|
_fl = self._fl_build()
|
||||||
|
|
||||||
|
@@ -8,6 +8,8 @@ from flask import current_app
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from api.extensions import rd
|
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 import CIRelationManager
|
||||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||||
from api.lib.cmdb.const import ConstraintEnum
|
from api.lib.cmdb.const import ConstraintEnum
|
||||||
@@ -18,6 +20,8 @@ from api.lib.cmdb.perms import CIFilterPermsCRUD
|
|||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||||
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
|
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
|
||||||
|
from api.lib.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 ACLManager
|
||||||
from api.lib.perm.acl.acl import is_app_admin
|
from api.lib.perm.acl.acl import is_app_admin
|
||||||
from api.models.cmdb import CI
|
from api.models.cmdb import CI
|
||||||
@@ -65,8 +69,8 @@ class Search(object):
|
|||||||
if _l < int(level) and c == ConstraintEnum.Many2Many:
|
if _l < int(level) and c == ConstraintEnum.Many2Many:
|
||||||
self.has_m2m = True
|
self.has_m2m = True
|
||||||
|
|
||||||
self.type2filter_perms = None
|
self.type2filter_perms = {}
|
||||||
|
|
||||||
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||||
|
|
||||||
def _get_ids(self, ids):
|
def _get_ids(self, ids):
|
||||||
@@ -75,7 +79,7 @@ class Search(object):
|
|||||||
key = []
|
key = []
|
||||||
_tmp = []
|
_tmp = []
|
||||||
for level in range(1, sorted(self.level)[-1] + 1):
|
for level in range(1, sorted(self.level)[-1] + 1):
|
||||||
if len(self.descendant_ids) >= level and self.type2filter_perms.get(self.descendant_ids[level - 1]):
|
if len(self.descendant_ids or []) >= 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]])
|
id_filter_limit, _ = self._get_ci_filter(self.type2filter_perms[self.descendant_ids[level - 1]])
|
||||||
else:
|
else:
|
||||||
id_filter_limit = {}
|
id_filter_limit = {}
|
||||||
@@ -147,9 +151,9 @@ class Search(object):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def search(self):
|
def search(self, only_ids=False):
|
||||||
use_ci_filter = len(self.descendant_ids) == self.level[0] - 1
|
use_ci_filter = len(self.descendant_ids or []) == self.level[0] - 1
|
||||||
parent_node_perm_passed = self._has_read_perm_from_parent_nodes()
|
parent_node_perm_passed = not self.is_app_admin and self._has_read_perm_from_parent_nodes()
|
||||||
|
|
||||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||||
cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids]
|
cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids]
|
||||||
@@ -193,7 +197,8 @@ class Search(object):
|
|||||||
sort=self.sort,
|
sort=self.sort,
|
||||||
ci_ids=merge_ids,
|
ci_ids=merge_ids,
|
||||||
parent_node_perm_passed=parent_node_perm_passed,
|
parent_node_perm_passed=parent_node_perm_passed,
|
||||||
use_ci_filter=use_ci_filter).search()
|
use_ci_filter=use_ci_filter,
|
||||||
|
only_ids=only_ids).search()
|
||||||
|
|
||||||
def _get_ci_filter(self, filter_perms, ci_filters=None):
|
def _get_ci_filter(self, filter_perms, ci_filters=None):
|
||||||
ci_filters = ci_filters or []
|
ci_filters = ci_filters or []
|
||||||
@@ -236,7 +241,7 @@ class Search(object):
|
|||||||
type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
|
type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
|
||||||
|
|
||||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||||
_tmp = []
|
_tmp, tmp_res = [], []
|
||||||
level2ids = {}
|
level2ids = {}
|
||||||
for lv in range(1, self.level + 1):
|
for lv in range(1, self.level + 1):
|
||||||
level2ids[lv] = []
|
level2ids[lv] = []
|
||||||
@@ -303,24 +308,26 @@ class Search(object):
|
|||||||
if key:
|
if key:
|
||||||
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||||
if type_ids and lv == self.level:
|
if type_ids and lv == self.level:
|
||||||
__tmp = [[i for i in x if i[1] in type_ids and
|
tmp_res = [[i for i in x if i[1] in type_ids and
|
||||||
(not id_filter_limit or (
|
(not id_filter_limit or (
|
||||||
key[idx] not in 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[key[idx]]) or
|
||||||
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
||||||
else:
|
else:
|
||||||
__tmp = [[i for i in x if (not id_filter_limit or (
|
tmp_res = [[i for i in x if (not id_filter_limit or (
|
||||||
key[idx] not in 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[key[idx]]) or
|
||||||
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
int(i[0]) in id_filter_limit)] for idx, x in
|
||||||
|
enumerate(res)]
|
||||||
|
|
||||||
if ci_filter_limit:
|
if ci_filter_limit:
|
||||||
__tmp = [[j for j in i if j[1] not in ci_filter_limit or
|
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]
|
int(j[0]) in ci_filter_limit[j[1]]] for i in tmp_res]
|
||||||
else:
|
else:
|
||||||
__tmp = []
|
tmp_res = []
|
||||||
|
|
||||||
_tmp[idx] = [j for i in __tmp for j in i]
|
if tmp_res:
|
||||||
|
_tmp[idx] = [j for i in tmp_res for j in i]
|
||||||
else:
|
else:
|
||||||
_tmp[idx] = []
|
_tmp[idx] = []
|
||||||
level2ids[lv].append([])
|
level2ids[lv].append([])
|
||||||
@@ -331,3 +338,84 @@ class Search(object):
|
|||||||
detail={str(_id): dict(Counter([i[1] for i in _tmp[idx]]).items()) for idx, _id in enumerate(ids)})
|
detail={str(_id): dict(Counter([i[1] for i in _tmp[idx]]).items()) for idx, _id in enumerate(ids)})
|
||||||
|
|
||||||
return result
|
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
|
||||||
|
251
cmdb-api/api/lib/cmdb/topology.py
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from flask import abort
|
||||||
|
from flask import current_app
|
||||||
|
from flask_login import current_user
|
||||||
|
from werkzeug.exceptions import BadRequest
|
||||||
|
|
||||||
|
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 REDIS_PREFIX_CI_RELATION
|
||||||
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
|
from api.lib.cmdb.search import SearchError
|
||||||
|
from api.lib.cmdb.search.ci import search
|
||||||
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
|
from api.lib.perm.acl.acl import is_app_admin
|
||||||
|
from api.models.cmdb import TopologyView
|
||||||
|
from api.models.cmdb import TopologyViewGroup
|
||||||
|
|
||||||
|
|
||||||
|
class TopologyViewManager(object):
|
||||||
|
group_cls = TopologyViewGroup
|
||||||
|
cls = TopologyView
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_name_by_id(cls, _id):
|
||||||
|
res = cls.cls.get_by_id(_id)
|
||||||
|
return res and res.name
|
||||||
|
|
||||||
|
def get_view_by_id(self, _id):
|
||||||
|
res = self.cls.get_by_id(_id)
|
||||||
|
|
||||||
|
return res and res.to_dict() or {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_group(cls, name, order):
|
||||||
|
if order is None:
|
||||||
|
cur_max_order = cls.group_cls.get_by(only_query=True).order_by(cls.group_cls.order.desc()).first()
|
||||||
|
cur_max_order = cur_max_order and cur_max_order.order or 0
|
||||||
|
order = cur_max_order + 1
|
||||||
|
|
||||||
|
cls.group_cls.get_by(name=name, first=True, to_dict=False) and abort(
|
||||||
|
400, ErrFormat.topology_group_exists.format(name))
|
||||||
|
|
||||||
|
return cls.group_cls.create(name=name, order=order)
|
||||||
|
|
||||||
|
def update_group(self, group_id, name, view_ids):
|
||||||
|
existed = self.group_cls.get_by_id(group_id) or abort(404, ErrFormat.not_found)
|
||||||
|
if name is not None and name != existed.name:
|
||||||
|
existed.update(name=name)
|
||||||
|
|
||||||
|
for idx, view_id in enumerate(view_ids):
|
||||||
|
view = self.cls.get_by_id(view_id)
|
||||||
|
if view is not None:
|
||||||
|
view.update(group_id=group_id, order=idx)
|
||||||
|
|
||||||
|
return existed.to_dict()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete_group(cls, _id):
|
||||||
|
existed = cls.group_cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
|
||||||
|
|
||||||
|
if cls.cls.get_by(group_id=_id, first=True):
|
||||||
|
return abort(400, ErrFormat.topo_view_exists_cannot_delete_group)
|
||||||
|
|
||||||
|
existed.soft_delete()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def group_order(cls, group_ids):
|
||||||
|
for idx, group_id in enumerate(group_ids):
|
||||||
|
group = cls.group_cls.get_by_id(group_id)
|
||||||
|
group.update(order=idx + 1)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add(cls, name, group_id, option, order=None, **kwargs):
|
||||||
|
cls.cls.get_by(name=name, first=True) and abort(400, ErrFormat.topology_exists.format(name))
|
||||||
|
if order is None:
|
||||||
|
cur_max_order = cls.cls.get_by(group_id=group_id, only_query=True).order_by(
|
||||||
|
cls.cls.order.desc()).first()
|
||||||
|
cur_max_order = cur_max_order and cur_max_order.order or 0
|
||||||
|
order = cur_max_order + 1
|
||||||
|
|
||||||
|
inst = cls.cls.create(name=name, group_id=group_id, option=option, order=order, **kwargs).to_dict()
|
||||||
|
if current_app.config.get('USE_ACL'):
|
||||||
|
try:
|
||||||
|
ACLManager().add_resource(name, ResourceTypeEnum.TOPOLOGY_VIEW)
|
||||||
|
except BadRequest:
|
||||||
|
pass
|
||||||
|
|
||||||
|
ACLManager().grant_resource_to_role(name,
|
||||||
|
current_user.username,
|
||||||
|
ResourceTypeEnum.TOPOLOGY_VIEW)
|
||||||
|
|
||||||
|
return inst
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update(cls, _id, **kwargs):
|
||||||
|
existed = cls.cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
|
||||||
|
existed_name = existed.name
|
||||||
|
|
||||||
|
inst = existed.update(filter_none=False, **kwargs).to_dict()
|
||||||
|
if current_app.config.get('USE_ACL') and existed_name != kwargs.get('name') and kwargs.get('name'):
|
||||||
|
try:
|
||||||
|
ACLManager().update_resource(existed_name, kwargs['name'], ResourceTypeEnum.TOPOLOGY_VIEW)
|
||||||
|
except BadRequest:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return inst
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete(cls, _id):
|
||||||
|
existed = cls.cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
|
||||||
|
|
||||||
|
existed.soft_delete()
|
||||||
|
if current_app.config.get("USE_ACL"):
|
||||||
|
ACLManager().del_resource(existed.name, ResourceTypeEnum.TOPOLOGY_VIEW)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def group_inner_order(cls, _ids):
|
||||||
|
for idx, _id in enumerate(_ids):
|
||||||
|
topology = cls.cls.get_by_id(_id)
|
||||||
|
topology.update(order=idx + 1)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_all(cls):
|
||||||
|
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.TOPOLOGY_VIEW)])
|
||||||
|
|
||||||
|
groups = cls.group_cls.get_by(to_dict=True)
|
||||||
|
groups = sorted(groups, key=lambda x: x['order'])
|
||||||
|
group2pos = {group['id']: idx for idx, group in enumerate(groups)}
|
||||||
|
|
||||||
|
topo_views = sorted(cls.cls.get_by(to_dict=True), key=lambda x: x['order'])
|
||||||
|
other_group = dict(views=[])
|
||||||
|
for view in topo_views:
|
||||||
|
if resources is not None and view['name'] not in resources:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if view['group_id']:
|
||||||
|
groups[group2pos[view['group_id']]].setdefault('views', []).append(view)
|
||||||
|
else:
|
||||||
|
other_group['views'].append(view)
|
||||||
|
|
||||||
|
if other_group['views']:
|
||||||
|
groups.append(other_group)
|
||||||
|
|
||||||
|
return groups
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def relation_from_ci_type(type_id):
|
||||||
|
nodes, edges = CITypeRelationManager.get_relations_by_type_id(type_id)
|
||||||
|
|
||||||
|
return dict(nodes=nodes, edges=edges)
|
||||||
|
|
||||||
|
def topology_view(self, view_id=None, preview=None):
|
||||||
|
if view_id is not None:
|
||||||
|
view = self.cls.get_by_id(view_id) or abort(404, ErrFormat.not_found)
|
||||||
|
central_node_type, central_node_instances, path = (view.central_node_type,
|
||||||
|
view.central_node_instances, view.path)
|
||||||
|
else:
|
||||||
|
central_node_type = preview.get('central_node_type')
|
||||||
|
central_node_instances = preview.get('central_node_instances')
|
||||||
|
path = preview.get('path')
|
||||||
|
|
||||||
|
nodes, links = [], []
|
||||||
|
_type = CITypeCache.get(central_node_type)
|
||||||
|
if not _type:
|
||||||
|
return dict(nodes=nodes, links=links)
|
||||||
|
root_ids = []
|
||||||
|
show_key = AttributeCache.get(_type.show_id or _type.unique_id)
|
||||||
|
|
||||||
|
q = (central_node_instances[2:] if central_node_instances.startswith('q=') else
|
||||||
|
central_node_instances)
|
||||||
|
s = search(q, fl=['_id', show_key.name], use_id_filter=False, use_ci_filter=False, count=1000000)
|
||||||
|
try:
|
||||||
|
response, _, _, _, _, _ = s.search()
|
||||||
|
except SearchError as e:
|
||||||
|
current_app.logger.info(e)
|
||||||
|
return dict(nodes=nodes, links=links)
|
||||||
|
for i in response:
|
||||||
|
root_ids.append(i['_id'])
|
||||||
|
nodes.append(dict(id=str(i['_id']), name=i[show_key.name], type_id=central_node_type))
|
||||||
|
if not root_ids:
|
||||||
|
return dict(nodes=nodes, links=links)
|
||||||
|
|
||||||
|
prefix = REDIS_PREFIX_CI_RELATION
|
||||||
|
key = list(map(str, root_ids))
|
||||||
|
id2node = {}
|
||||||
|
type2meta = {}
|
||||||
|
for level in sorted([i for i in path.keys() if int(i) > 0]):
|
||||||
|
type_ids = {int(i) for i in path[level]}
|
||||||
|
|
||||||
|
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||||
|
new_key = []
|
||||||
|
for idx, from_id in enumerate(key):
|
||||||
|
for to_id, type_id in res[idx]:
|
||||||
|
if type_id in type_ids:
|
||||||
|
links.append({'from': from_id, 'to': to_id})
|
||||||
|
id2node[to_id] = {'id': to_id, 'type_id': type_id}
|
||||||
|
new_key.append(to_id)
|
||||||
|
if type_id not in type2meta:
|
||||||
|
type2meta[type_id] = CITypeCache.get(type_id).icon
|
||||||
|
|
||||||
|
key = new_key
|
||||||
|
|
||||||
|
ci_ids = list(map(int, root_ids))
|
||||||
|
for level in sorted([i for i in path.keys() if int(i) < 0]):
|
||||||
|
type_ids = {int(i) for i in path[level]}
|
||||||
|
res = CIRelationManager.get_parent_ids(ci_ids)
|
||||||
|
_ci_ids = []
|
||||||
|
for to_id in res:
|
||||||
|
for from_id, type_id in res[to_id]:
|
||||||
|
if type_id in type_ids:
|
||||||
|
from_id, to_id = str(from_id), str(to_id)
|
||||||
|
links.append({'from': from_id, 'to': to_id})
|
||||||
|
id2node[from_id] = {'id': str(from_id), 'type_id': type_id}
|
||||||
|
_ci_ids.append(from_id)
|
||||||
|
if type_id not in type2meta:
|
||||||
|
type2meta[type_id] = CITypeCache.get(type_id).icon
|
||||||
|
|
||||||
|
ci_ids = _ci_ids
|
||||||
|
|
||||||
|
fl = set()
|
||||||
|
type_ids = {t for lv in path if lv != '0' for t in path[lv]}
|
||||||
|
type2show = {}
|
||||||
|
for type_id in type_ids:
|
||||||
|
ci_type = CITypeCache.get(type_id)
|
||||||
|
if ci_type:
|
||||||
|
attr = AttributeCache.get(ci_type.show_id or ci_type.unique_id)
|
||||||
|
if attr:
|
||||||
|
fl.add(attr.name)
|
||||||
|
type2show[type_id] = attr.name
|
||||||
|
|
||||||
|
if id2node:
|
||||||
|
s = search("_id:({})".format(';'.join(id2node.keys())), fl=list(fl),
|
||||||
|
use_id_filter=False, use_ci_filter=False, count=1000000)
|
||||||
|
try:
|
||||||
|
response, _, _, _, _, _ = s.search()
|
||||||
|
except SearchError:
|
||||||
|
return dict(nodes=nodes, links=links)
|
||||||
|
for i in response:
|
||||||
|
id2node[str(i['_id'])]['name'] = i[type2show[str(i['_type'])]]
|
||||||
|
nodes.extend(id2node.values())
|
||||||
|
|
||||||
|
return dict(nodes=nodes, links=links, type2meta=type2meta)
|
@@ -11,12 +11,21 @@ import six
|
|||||||
import api.models.cmdb as model
|
import api.models.cmdb as model
|
||||||
from api.lib.cmdb.cache import AttributeCache
|
from api.lib.cmdb.cache import AttributeCache
|
||||||
from api.lib.cmdb.const import ValueTypeEnum
|
from api.lib.cmdb.const import ValueTypeEnum
|
||||||
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
|
|
||||||
TIME_RE = re.compile(r'(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d')
|
TIME_RE = re.compile(r'(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d')
|
||||||
|
|
||||||
|
|
||||||
|
class ValueDeserializeError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def string2int(x):
|
def string2int(x):
|
||||||
return int(float(x))
|
v = int(float(x))
|
||||||
|
if v > 2147483647:
|
||||||
|
raise ValueDeserializeError(ErrFormat.attribute_value_out_of_range)
|
||||||
|
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
def str2datetime(x):
|
def str2datetime(x):
|
||||||
|
@@ -5,14 +5,16 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
import imp
|
import imp
|
||||||
import jinja2
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
import jinja2
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from jinja2schema import infer
|
from jinja2schema import infer
|
||||||
from jinja2schema import to_json_schema
|
from jinja2schema import to_json_schema
|
||||||
|
from werkzeug.exceptions import BadRequest
|
||||||
|
|
||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
from api.lib.cmdb.attribute import AttributeManager
|
from api.lib.cmdb.attribute import AttributeManager
|
||||||
@@ -23,6 +25,7 @@ from api.lib.cmdb.const import ValueTypeEnum
|
|||||||
from api.lib.cmdb.history import AttributeHistoryManger
|
from api.lib.cmdb.history import AttributeHistoryManger
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.cmdb.utils import TableMap
|
from api.lib.cmdb.utils import TableMap
|
||||||
|
from api.lib.cmdb.utils import ValueDeserializeError
|
||||||
from api.lib.cmdb.utils import ValueTypeMap
|
from api.lib.cmdb.utils import ValueTypeMap
|
||||||
from api.lib.utils import handle_arg_list
|
from api.lib.utils import handle_arg_list
|
||||||
from api.models.cmdb import CI
|
from api.models.cmdb import CI
|
||||||
@@ -80,7 +83,7 @@ class AttributeValueManager(object):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _deserialize_value(value_type, value):
|
def _deserialize_value(alias, value_type, value):
|
||||||
if not value:
|
if not value:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -88,6 +91,8 @@ class AttributeValueManager(object):
|
|||||||
try:
|
try:
|
||||||
v = deserialize(value)
|
v = deserialize(value)
|
||||||
return v
|
return v
|
||||||
|
except ValueDeserializeError as e:
|
||||||
|
return abort(400, ErrFormat.attribute_value_invalid2.format(alias, e))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return abort(400, ErrFormat.attribute_value_invalid.format(value))
|
return abort(400, ErrFormat.attribute_value_invalid.format(value))
|
||||||
|
|
||||||
@@ -124,7 +129,7 @@ class AttributeValueManager(object):
|
|||||||
|
|
||||||
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
|
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
|
||||||
ci = ci or {}
|
ci = ci or {}
|
||||||
v = self._deserialize_value(attr.value_type, value)
|
v = self._deserialize_value(attr.alias, attr.value_type, value)
|
||||||
|
|
||||||
attr.is_choice and value and self._check_is_choice(attr, attr.value_type, v)
|
attr.is_choice and value and self._check_is_choice(attr, attr.value_type, v)
|
||||||
attr.is_unique and self._check_is_unique(
|
attr.is_unique and self._check_is_unique(
|
||||||
@@ -145,9 +150,10 @@ class AttributeValueManager(object):
|
|||||||
return AttributeHistoryManger.add(record_id, ci_id, [(attr_id, operate_type, old, new)], type_id)
|
return AttributeHistoryManger.add(record_id, ci_id, [(attr_id, operate_type, old, new)], type_id)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def write_change2(changed, record_id=None):
|
def write_change2(changed, record_id=None, ticket_id=None):
|
||||||
for ci_id, attr_id, operate_type, old, new, type_id in changed:
|
for ci_id, attr_id, operate_type, old, new, type_id in changed:
|
||||||
record_id = AttributeHistoryManger.add(record_id, ci_id, [(attr_id, operate_type, old, new)], type_id,
|
record_id = AttributeHistoryManger.add(record_id, ci_id, [(attr_id, operate_type, old, new)], type_id,
|
||||||
|
ticket_id=ticket_id,
|
||||||
commit=False, flush=False)
|
commit=False, flush=False)
|
||||||
try:
|
try:
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@@ -240,6 +246,8 @@ class AttributeValueManager(object):
|
|||||||
value = self._validate(attr, value, value_table, ci=None, type_id=type_id, ci_id=ci_id,
|
value = self._validate(attr, value, value_table, ci=None, type_id=type_id, ci_id=ci_id,
|
||||||
type_attr=ci_attr2type_attr.get(attr.id))
|
type_attr=ci_attr2type_attr.get(attr.id))
|
||||||
ci_dict[key] = value
|
ci_dict[key] = value
|
||||||
|
except BadRequest as e:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.warning(str(e))
|
current_app.logger.warning(str(e))
|
||||||
|
|
||||||
@@ -248,12 +256,13 @@ class AttributeValueManager(object):
|
|||||||
|
|
||||||
return key2attr
|
return key2attr
|
||||||
|
|
||||||
def create_or_update_attr_value(self, ci, ci_dict, key2attr):
|
def create_or_update_attr_value(self, ci, ci_dict, key2attr, ticket_id=None):
|
||||||
"""
|
"""
|
||||||
add or update attribute value, then write history
|
add or update attribute value, then write history
|
||||||
:param ci: instance object
|
:param ci: instance object
|
||||||
:param ci_dict: attribute dict
|
:param ci_dict: attribute dict
|
||||||
:param key2attr: attr key to attr
|
:param key2attr: attr key to attr
|
||||||
|
:param ticket_id:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
changed = []
|
changed = []
|
||||||
@@ -265,26 +274,36 @@ class AttributeValueManager(object):
|
|||||||
|
|
||||||
if attr.is_list:
|
if attr.is_list:
|
||||||
existed_attrs = value_table.get_by(attr_id=attr.id, ci_id=ci.id, to_dict=False)
|
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]
|
existed_values = [(ValueTypeMap.serialize[attr.value_type](i.value) if
|
||||||
added = set(value) - set(existed_values)
|
i.value or i.value == 0 else i.value) for i in existed_attrs]
|
||||||
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))
|
|
||||||
|
|
||||||
for v in deleted:
|
# Comparison array starts from which position changes
|
||||||
existed_attr = existed_attrs[existed_values.index(v)]
|
min_len = min(len(value), len(existed_values))
|
||||||
|
index = 0
|
||||||
|
while index < min_len:
|
||||||
|
if value[index] != existed_values[index]:
|
||||||
|
break
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
# Delete first and then add to ensure id sorting
|
||||||
|
for idx in range(index, len(existed_attrs)):
|
||||||
|
existed_attr = existed_attrs[idx]
|
||||||
existed_attr.delete(flush=False, commit=False)
|
existed_attr.delete(flush=False, commit=False)
|
||||||
changed.append((ci.id, attr.id, OperateType.DELETE, v, None, ci.type_id))
|
changed.append((ci.id, attr.id, OperateType.DELETE, existed_values[idx], None, ci.type_id))
|
||||||
|
for idx in range(index, len(value)):
|
||||||
|
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value[idx], flush=False, commit=False)
|
||||||
|
changed.append((ci.id, attr.id, OperateType.ADD, None, value[idx], ci.type_id))
|
||||||
else:
|
else:
|
||||||
existed_attr = value_table.get_by(attr_id=attr.id, ci_id=ci.id, first=True, to_dict=False)
|
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 = 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:
|
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)
|
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))
|
changed.append((ci.id, attr.id, OperateType.ADD, None, value, ci.type_id))
|
||||||
else:
|
else:
|
||||||
if existed_value != value:
|
if existed_value != value and existed_attr:
|
||||||
if value is None:
|
if value is None:
|
||||||
existed_attr.delete(flush=False, commit=False)
|
existed_attr.delete(flush=False, commit=False)
|
||||||
else:
|
else:
|
||||||
@@ -299,7 +318,7 @@ class AttributeValueManager(object):
|
|||||||
current_app.logger.warning(str(e))
|
current_app.logger.warning(str(e))
|
||||||
return abort(400, ErrFormat.attribute_value_unknown_error.format(e.args[0]))
|
return abort(400, ErrFormat.attribute_value_unknown_error.format(e.args[0]))
|
||||||
|
|
||||||
return self.write_change2(changed)
|
return self.write_change2(changed, ticket_id=ticket_id)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_attr_value(attr_id, ci_id, commit=True):
|
def delete_attr_value(attr_id, ci_id, commit=True):
|
||||||
|
@@ -10,6 +10,11 @@ from api.lib.perm.acl.role import RoleCRUD, RoleRelationCRUD
|
|||||||
from api.lib.perm.acl.user import UserCRUD
|
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):
|
class ACLManager(object):
|
||||||
def __init__(self, app_name='acl', uid=None):
|
def __init__(self, app_name='acl', uid=None):
|
||||||
self.log = current_app.logger
|
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)
|
numfound, res = ResourceCRUD.search(q, u, self.validate_app().id, rt_id, page, page_size)
|
||||||
return res
|
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)
|
PermissionCRUD.grant(rid, perms, resource_id=resource_id, group_id=None)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -141,3 +147,7 @@ class ACLManager(object):
|
|||||||
rt = AppCRUD.add(**payload)
|
rt = AppCRUD.add(**payload)
|
||||||
|
|
||||||
return rt.to_dict()
|
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):
|
class TestType(BaseEnum):
|
||||||
Connect = 'connect'
|
Connect = 'connect'
|
||||||
Login = 'login'
|
Login = 'login'
|
||||||
|
|
||||||
|
|
||||||
|
MIMEExtMap = {
|
||||||
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx',
|
||||||
|
'application/msword': '.doc',
|
||||||
|
'application/vnd.ms-word.document.macroEnabled.12': '.docm',
|
||||||
|
'application/vnd.ms-excel': '.xls',
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
|
||||||
|
'application/vnd.ms-excel.sheet.macroEnabled.12': '.xlsm',
|
||||||
|
'application/vnd.ms-powerpoint': '.ppt',
|
||||||
|
'application/vnd.openxmlformats-officedocument.presentationml.presentation': '.pptx',
|
||||||
|
'application/vnd.ms-powerpoint.presentation.macroEnabled.12': '.pptm',
|
||||||
|
'application/zip': '.zip',
|
||||||
|
'application/x-7z-compressed': '.7z',
|
||||||
|
'application/json': '.json',
|
||||||
|
'application/pdf': '.pdf',
|
||||||
|
'image/png': '.png',
|
||||||
|
'image/bmp': '.bmp',
|
||||||
|
'image/prs.btif': '.btif',
|
||||||
|
'image/gif': '.gif',
|
||||||
|
'image/jpeg': '.jpg',
|
||||||
|
'image/tiff': '.tif',
|
||||||
|
'image/vnd.microsoft.icon': '.ico',
|
||||||
|
'image/webp': '.webp',
|
||||||
|
'image/svg+xml': '.svg',
|
||||||
|
'image/vnd.adobe.photoshop': '.psd',
|
||||||
|
'text/plain': '.txt',
|
||||||
|
'text/csv': '.csv',
|
||||||
|
}
|
||||||
|
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"
|
return f"edit_department_name_in_acl, rid: {d_rid}, success"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def remove_from_old_department_role(cls, e_list, acl):
|
||||||
|
result = []
|
||||||
|
for employee in e_list:
|
||||||
|
employee_acl_rid = employee.get('e_acl_rid')
|
||||||
|
if employee_acl_rid == 0:
|
||||||
|
result.append(f"employee_acl_rid == 0")
|
||||||
|
continue
|
||||||
|
cls.remove_single_employee_from_old_department(acl, employee, result)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def edit_employee_department_in_acl(e_list: list, new_d_id: int, op_uid: int):
|
def remove_single_employee_from_old_department(acl, employee, result):
|
||||||
|
from api.models.acl import Role
|
||||||
|
old_department = DepartmentCRUD.get_department_by_id(employee.get('department_id'), False)
|
||||||
|
if not old_department:
|
||||||
|
return False
|
||||||
|
|
||||||
|
old_role = Role.get_by(first=True, name=old_department.department_name, app_id=None)
|
||||||
|
old_d_rid_in_acl = old_role.get('id') if old_role else 0
|
||||||
|
if old_d_rid_in_acl == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
d_acl_rid = old_department.acl_rid if old_d_rid_in_acl == old_department.acl_rid else old_d_rid_in_acl
|
||||||
|
payload = {
|
||||||
|
'app_id': 'acl',
|
||||||
|
'parent_id': d_acl_rid,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
acl.remove_user_from_role(employee.get('e_acl_rid'), payload)
|
||||||
|
current_app.logger.info(f"remove {employee.get('e_acl_rid')} from {d_acl_rid}")
|
||||||
|
except Exception as e:
|
||||||
|
result.append(
|
||||||
|
f"remove_user_from_role employee_acl_rid: {employee.get('e_acl_rid')}, parent_id: {d_acl_rid}, err: {e}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_employee_to_new_department(acl, employee_acl_rid, new_department_acl_rid, result):
|
||||||
|
payload = {
|
||||||
|
'app_id': 'acl',
|
||||||
|
'child_ids': [employee_acl_rid],
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
acl.add_user_to_role(new_department_acl_rid, payload)
|
||||||
|
current_app.logger.info(f"add {employee_acl_rid} to {new_department_acl_rid}")
|
||||||
|
except Exception as e:
|
||||||
|
result.append(
|
||||||
|
f"add_user_to_role employee_acl_rid: {employee_acl_rid}, parent_id: {new_department_acl_rid}, \
|
||||||
|
err: {e}")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def edit_employee_department_in_acl(cls, e_list: list, new_d_id: int, op_uid: int):
|
||||||
result = []
|
result = []
|
||||||
new_department = DepartmentCRUD.get_department_by_id(new_d_id, False)
|
new_department = DepartmentCRUD.get_department_by_id(new_d_id, False)
|
||||||
if not new_department:
|
if not new_department:
|
||||||
@@ -481,7 +531,11 @@ class EditDepartmentInACL(object):
|
|||||||
from api.models.acl import Role
|
from api.models.acl import Role
|
||||||
new_role = Role.get_by(first=True, name=new_department.department_name, app_id=None)
|
new_role = Role.get_by(first=True, name=new_department.department_name, app_id=None)
|
||||||
new_d_rid_in_acl = new_role.get('id') if new_role else 0
|
new_d_rid_in_acl = new_role.get('id') if new_role else 0
|
||||||
|
acl = ACLManager('acl', str(op_uid))
|
||||||
|
|
||||||
if new_d_rid_in_acl == 0:
|
if new_d_rid_in_acl == 0:
|
||||||
|
# only remove from old department role
|
||||||
|
cls.remove_from_old_department_role(e_list, acl)
|
||||||
return
|
return
|
||||||
|
|
||||||
if new_d_rid_in_acl != new_department.acl_rid:
|
if new_d_rid_in_acl != new_department.acl_rid:
|
||||||
@@ -491,43 +545,15 @@ class EditDepartmentInACL(object):
|
|||||||
new_department_acl_rid = new_department.acl_rid if new_d_rid_in_acl == new_department.acl_rid else \
|
new_department_acl_rid = new_department.acl_rid if new_d_rid_in_acl == new_department.acl_rid else \
|
||||||
new_d_rid_in_acl
|
new_d_rid_in_acl
|
||||||
|
|
||||||
acl = ACLManager('acl', str(op_uid))
|
|
||||||
for employee in e_list:
|
for employee in e_list:
|
||||||
old_department = DepartmentCRUD.get_department_by_id(employee.get('department_id'), False)
|
|
||||||
if not old_department:
|
|
||||||
continue
|
|
||||||
employee_acl_rid = employee.get('e_acl_rid')
|
employee_acl_rid = employee.get('e_acl_rid')
|
||||||
if employee_acl_rid == 0:
|
if employee_acl_rid == 0:
|
||||||
result.append(f"employee_acl_rid == 0")
|
result.append(f"employee_acl_rid == 0")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
old_role = Role.get_by(first=True, name=old_department.department_name, app_id=None)
|
cls.remove_single_employee_from_old_department(acl, employee, result)
|
||||||
old_d_rid_in_acl = old_role.get('id') if old_role else 0
|
|
||||||
if old_d_rid_in_acl == 0:
|
|
||||||
return
|
|
||||||
if old_d_rid_in_acl != old_department.acl_rid:
|
|
||||||
old_department.update(
|
|
||||||
acl_rid=old_d_rid_in_acl
|
|
||||||
)
|
|
||||||
d_acl_rid = old_department.acl_rid if old_d_rid_in_acl == old_department.acl_rid else old_d_rid_in_acl
|
|
||||||
payload = {
|
|
||||||
'app_id': 'acl',
|
|
||||||
'parent_id': d_acl_rid,
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
acl.remove_user_from_role(employee_acl_rid, payload)
|
|
||||||
except Exception as e:
|
|
||||||
result.append(
|
|
||||||
f"remove_user_from_role employee_acl_rid: {employee_acl_rid}, parent_id: {d_acl_rid}, err: {e}")
|
|
||||||
|
|
||||||
payload = {
|
# 在新部门中添加员工
|
||||||
'app_id': 'acl',
|
cls.add_employee_to_new_department(acl, employee_acl_rid, new_department_acl_rid, result)
|
||||||
'child_ids': [employee_acl_rid],
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
acl.add_user_to_role(new_department_acl_rid, payload)
|
|
||||||
except Exception as e:
|
|
||||||
result.append(
|
|
||||||
f"add_user_to_role employee_acl_rid: {employee_acl_rid}, parent_id: {d_acl_rid}, err: {e}")
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
@@ -80,3 +80,5 @@ class ErrFormat(CommonErrFormat):
|
|||||||
ldap_test_username_required = _l("LDAP test username required") # LDAP测试用户名必填
|
ldap_test_username_required = _l("LDAP test username required") # LDAP测试用户名必填
|
||||||
|
|
||||||
company_wide = _l("Company wide") # 全公司
|
company_wide = _l("Company wide") # 全公司
|
||||||
|
|
||||||
|
resource_no_permission = _l("No permission to access resource {}, perm {} ") # 没有权限访问 {} 资源的 {} 权限"
|
||||||
|
64
cmdb-api/api/lib/common_setting/role_perm_base.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
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"]},
|
||||||
|
{"page": "TopologyView", "page_cn": "拓扑视图",
|
||||||
|
"perms": ["read", "create_topology_group", "update_topology_group", "delete_topology_group",
|
||||||
|
"create_topology_view"],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
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 -*-
|
# -*- coding:utf-8 -*-
|
||||||
from datetime import datetime
|
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='-'):
|
def get_cur_time_str(split_flag='-'):
|
||||||
@@ -23,3 +28,115 @@ class BaseEnum(object):
|
|||||||
if not attr.startswith("_") and not callable(getattr(cls, attr))
|
if not attr.startswith("_") and not callable(getattr(cls, attr))
|
||||||
}
|
}
|
||||||
return cls._ALL_
|
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,16 +148,16 @@ class ACLManager(object):
|
|||||||
if group:
|
if group:
|
||||||
PermissionCRUD.revoke(rid, permissions, group_id=group.id, rebuild=rebuild)
|
PermissionCRUD.revoke(rid, permissions, group_id=group.id, rebuild=rebuild)
|
||||||
|
|
||||||
def del_resource(self, name, resource_type_name=None):
|
def del_resource(self, name, resource_type_name=None, rebuild=True):
|
||||||
resource = self._get_resource(name, resource_type_name)
|
resource = self._get_resource(name, resource_type_name)
|
||||||
if resource:
|
if resource:
|
||||||
return ResourceCRUD.delete(resource.id)
|
return ResourceCRUD.delete(resource.id, rebuild=rebuild)
|
||||||
|
|
||||||
def has_permission(self, resource_name, resource_type, perm, resource_id=None):
|
def has_permission(self, resource_name, resource_type, perm, resource_id=None, rid=None):
|
||||||
if is_app_admin(self.app_id):
|
if is_app_admin(self.app_id):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
role = self._get_role(current_user.username)
|
role = self._get_role(current_user.username) if rid is None else RoleCache.get(rid)
|
||||||
|
|
||||||
role or abort(404, ErrFormat.role_not_found.format(current_user.username))
|
role or abort(404, ErrFormat.role_not_found.format(current_user.username))
|
||||||
|
|
||||||
|
@@ -5,6 +5,7 @@ import msgpack
|
|||||||
import redis_lock
|
import redis_lock
|
||||||
|
|
||||||
from api.extensions import cache
|
from api.extensions import cache
|
||||||
|
from api.extensions import db
|
||||||
from api.extensions import rd
|
from api.extensions import rd
|
||||||
from api.lib.decorator import flush_db
|
from api.lib.decorator import flush_db
|
||||||
from api.models.acl import App
|
from api.models.acl import App
|
||||||
@@ -157,9 +158,10 @@ class RoleRelationCache(object):
|
|||||||
PREFIX_RESOURCES2 = "RoleRelationResources2::id::{0}::AppId::{1}"
|
PREFIX_RESOURCES2 = "RoleRelationResources2::id::{0}::AppId::{1}"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_parent_ids(cls, rid, app_id):
|
def get_parent_ids(cls, rid, app_id, force=False):
|
||||||
parent_ids = cache.get(cls.PREFIX_PARENT.format(rid, app_id))
|
parent_ids = cache.get(cls.PREFIX_PARENT.format(rid, app_id))
|
||||||
if not parent_ids:
|
if not parent_ids or force:
|
||||||
|
db.session.commit()
|
||||||
from api.lib.perm.acl.role import RoleRelationCRUD
|
from api.lib.perm.acl.role import RoleRelationCRUD
|
||||||
parent_ids = RoleRelationCRUD.get_parent_ids(rid, app_id)
|
parent_ids = RoleRelationCRUD.get_parent_ids(rid, app_id)
|
||||||
cache.set(cls.PREFIX_PARENT.format(rid, app_id), parent_ids, timeout=0)
|
cache.set(cls.PREFIX_PARENT.format(rid, app_id), parent_ids, timeout=0)
|
||||||
@@ -167,9 +169,10 @@ class RoleRelationCache(object):
|
|||||||
return parent_ids
|
return parent_ids
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_child_ids(cls, rid, app_id):
|
def get_child_ids(cls, rid, app_id, force=False):
|
||||||
child_ids = cache.get(cls.PREFIX_CHILDREN.format(rid, app_id))
|
child_ids = cache.get(cls.PREFIX_CHILDREN.format(rid, app_id))
|
||||||
if not child_ids:
|
if not child_ids or force:
|
||||||
|
db.session.commit()
|
||||||
from api.lib.perm.acl.role import RoleRelationCRUD
|
from api.lib.perm.acl.role import RoleRelationCRUD
|
||||||
child_ids = RoleRelationCRUD.get_child_ids(rid, app_id)
|
child_ids = RoleRelationCRUD.get_child_ids(rid, app_id)
|
||||||
cache.set(cls.PREFIX_CHILDREN.format(rid, app_id), child_ids, timeout=0)
|
cache.set(cls.PREFIX_CHILDREN.format(rid, app_id), child_ids, timeout=0)
|
||||||
@@ -177,14 +180,16 @@ class RoleRelationCache(object):
|
|||||||
return child_ids
|
return child_ids
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_resources(cls, rid, app_id):
|
def get_resources(cls, rid, app_id, force=False):
|
||||||
"""
|
"""
|
||||||
:param rid:
|
:param rid:
|
||||||
:param app_id:
|
:param app_id:
|
||||||
|
:param force:
|
||||||
:return: {id2perms: {resource_id: [perm,]}, group2perms: {group_id: [perm, ]}}
|
:return: {id2perms: {resource_id: [perm,]}, group2perms: {group_id: [perm, ]}}
|
||||||
"""
|
"""
|
||||||
resources = cache.get(cls.PREFIX_RESOURCES.format(rid, app_id))
|
resources = cache.get(cls.PREFIX_RESOURCES.format(rid, app_id))
|
||||||
if not resources:
|
if not resources or force:
|
||||||
|
db.session.commit()
|
||||||
from api.lib.perm.acl.role import RoleCRUD
|
from api.lib.perm.acl.role import RoleCRUD
|
||||||
resources = RoleCRUD.get_resources(rid, app_id)
|
resources = RoleCRUD.get_resources(rid, app_id)
|
||||||
if resources['id2perms'] or resources['group2perms']:
|
if resources['id2perms'] or resources['group2perms']:
|
||||||
@@ -193,9 +198,10 @@ class RoleRelationCache(object):
|
|||||||
return resources or {}
|
return resources or {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_resources2(cls, rid, app_id):
|
def get_resources2(cls, rid, app_id, force=False):
|
||||||
r_g = cache.get(cls.PREFIX_RESOURCES2.format(rid, app_id))
|
r_g = cache.get(cls.PREFIX_RESOURCES2.format(rid, app_id))
|
||||||
if not r_g:
|
if not r_g or force:
|
||||||
|
db.session.commit()
|
||||||
res = cls.get_resources(rid, app_id)
|
res = cls.get_resources(rid, app_id)
|
||||||
id2perms = res['id2perms']
|
id2perms = res['id2perms']
|
||||||
group2perms = res['group2perms']
|
group2perms = res['group2perms']
|
||||||
@@ -224,22 +230,28 @@ class RoleRelationCache(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
@flush_db
|
@flush_db
|
||||||
def rebuild(cls, rid, app_id):
|
def rebuild(cls, rid, app_id):
|
||||||
cls.clean(rid, app_id)
|
if app_id is None:
|
||||||
|
app_ids = [None] + [i.id for i in App.get_by(to_dict=False)]
|
||||||
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:
|
else:
|
||||||
HasResourceRoleCache.remove(rid, app_id)
|
app_ids = [app_id]
|
||||||
cls.get_resources2(rid, app_id)
|
|
||||||
|
for _app_id in app_ids:
|
||||||
|
cls.clean(rid, _app_id)
|
||||||
|
|
||||||
|
cls.get_parent_ids(rid, _app_id, force=True)
|
||||||
|
cls.get_child_ids(rid, _app_id, force=True)
|
||||||
|
resources = cls.get_resources(rid, _app_id, force=True)
|
||||||
|
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, force=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@flush_db
|
@flush_db
|
||||||
def rebuild2(cls, rid, app_id):
|
def rebuild2(cls, rid, app_id):
|
||||||
cache.delete(cls.PREFIX_RESOURCES2.format(rid, app_id))
|
cache.delete(cls.PREFIX_RESOURCES2.format(rid, app_id))
|
||||||
cls.get_resources2(rid, app_id)
|
cls.get_resources2(rid, app_id, force=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def clean(cls, rid, app_id):
|
def clean(cls, rid, app_id):
|
||||||
|
@@ -274,12 +274,14 @@ class PermissionCRUD(object):
|
|||||||
perm2resource.setdefault(_perm, []).append(resource_id)
|
perm2resource.setdefault(_perm, []).append(resource_id)
|
||||||
for _perm in perm2resource:
|
for _perm in perm2resource:
|
||||||
perm = PermissionCache.get(_perm, resource_type_id)
|
perm = PermissionCache.get(_perm, resource_type_id)
|
||||||
existeds = RolePermission.get_by(rid=rid,
|
if perm is None:
|
||||||
app_id=app_id,
|
continue
|
||||||
perm_id=perm.id,
|
exists = RolePermission.get_by(rid=rid,
|
||||||
__func_in___key_resource_id=perm2resource[_perm],
|
app_id=app_id,
|
||||||
to_dict=False)
|
perm_id=perm.id,
|
||||||
for existed in existeds:
|
__func_in___key_resource_id=perm2resource[_perm],
|
||||||
|
to_dict=False)
|
||||||
|
for existed in exists:
|
||||||
existed.deleted = True
|
existed.deleted = True
|
||||||
existed.deleted_at = datetime.datetime.now()
|
existed.deleted_at = datetime.datetime.now()
|
||||||
db.session.add(existed)
|
db.session.add(existed)
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
|
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
|
||||||
|
|
||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
from api.lib.perm.acl.audit import AuditCRUD
|
from api.lib.perm.acl.audit import AuditCRUD
|
||||||
@@ -127,11 +126,18 @@ class ResourceTypeCRUD(object):
|
|||||||
existed_ids = [i.id for i in existed]
|
existed_ids = [i.id for i in existed]
|
||||||
current_ids = []
|
current_ids = []
|
||||||
|
|
||||||
|
rebuild_rids = set()
|
||||||
for i in existed:
|
for i in existed:
|
||||||
if i.name not in perms:
|
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:
|
else:
|
||||||
current_ids.append(i.id)
|
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:
|
for i in perms:
|
||||||
if i not in existed_names:
|
if i not in existed_names:
|
||||||
@@ -309,9 +315,12 @@ class ResourceCRUD(object):
|
|||||||
return resource
|
return resource
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete(_id):
|
def delete(_id, rebuild=True, app_id=None):
|
||||||
resource = Resource.get_by_id(_id) or abort(404, ErrFormat.resource_not_found.format("id={}".format(_id)))
|
resource = Resource.get_by_id(_id) or abort(404, ErrFormat.resource_not_found.format("id={}".format(_id)))
|
||||||
|
|
||||||
|
if app_id is not None and resource.app_id != app_id:
|
||||||
|
return abort(404, ErrFormat.resource_not_found.format("id={}".format(_id)))
|
||||||
|
|
||||||
origin = resource.to_dict()
|
origin = resource.to_dict()
|
||||||
resource.soft_delete()
|
resource.soft_delete()
|
||||||
|
|
||||||
@@ -322,8 +331,9 @@ class ResourceCRUD(object):
|
|||||||
i.soft_delete()
|
i.soft_delete()
|
||||||
rebuilds.append((i.rid, i.app_id))
|
rebuilds.append((i.rid, i.app_id))
|
||||||
|
|
||||||
for rid, app_id in set(rebuilds):
|
if rebuild:
|
||||||
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE)
|
for rid, app_id in set(rebuilds):
|
||||||
|
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE)
|
||||||
|
|
||||||
AuditCRUD.add_resource_log(resource.app_id, AuditOperateType.delete,
|
AuditCRUD.add_resource_log(resource.app_id, AuditOperateType.delete,
|
||||||
AuditScope.resource, resource.id, origin, {}, {})
|
AuditScope.resource, resource.id, origin, {}, {})
|
||||||
|
@@ -3,12 +3,14 @@
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import redis_lock
|
||||||
import six
|
import six
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_
|
||||||
|
|
||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
|
from api.extensions import rd
|
||||||
from api.lib.perm.acl.app import AppCRUD
|
from api.lib.perm.acl.app import AppCRUD
|
||||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
|
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
|
||||||
from api.lib.perm.acl.cache import AppCache
|
from api.lib.perm.acl.cache import AppCache
|
||||||
@@ -62,7 +64,9 @@ class RoleRelationCRUD(object):
|
|||||||
|
|
||||||
id2parents = {}
|
id2parents = {}
|
||||||
for i in res:
|
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
|
return id2parents
|
||||||
|
|
||||||
@@ -141,24 +145,27 @@ class RoleRelationCRUD(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add(cls, role, parent_id, child_ids, app_id):
|
def add(cls, role, parent_id, child_ids, app_id):
|
||||||
result = []
|
with redis_lock.Lock(rd.r, "ROLE_RELATION_ADD"):
|
||||||
for child_id in child_ids:
|
db.session.commit()
|
||||||
existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id, app_id=app_id)
|
|
||||||
if existed:
|
|
||||||
continue
|
|
||||||
|
|
||||||
RoleRelationCache.clean(parent_id, app_id)
|
result = []
|
||||||
RoleRelationCache.clean(child_id, app_id)
|
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):
|
if parent_id in cls.recursive_child_ids(child_id, app_id):
|
||||||
return abort(400, ErrFormat.inheritance_dead_loop)
|
return abort(400, ErrFormat.inheritance_dead_loop)
|
||||||
|
|
||||||
if app_id is None:
|
result.append(RoleRelation.create(parent_id=parent_id, child_id=child_id, app_id=app_id).to_dict())
|
||||||
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())
|
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)
|
||||||
|
|
||||||
AuditCRUD.add_role_log(app_id, AuditOperateType.role_relation_add,
|
AuditCRUD.add_role_log(app_id, AuditOperateType.role_relation_add,
|
||||||
AuditScope.role_relation, role.id, {}, {},
|
AuditScope.role_relation, role.id, {}, {},
|
||||||
@@ -372,16 +379,16 @@ class RoleCRUD(object):
|
|||||||
resource_type_id = resource_type and resource_type.id
|
resource_type_id = resource_type and resource_type.id
|
||||||
|
|
||||||
result = dict(resources=dict(), groups=dict())
|
result = dict(resources=dict(), groups=dict())
|
||||||
s = time.time()
|
# s = time.time()
|
||||||
parent_ids = RoleRelationCRUD.recursive_parent_ids(rid, app_id)
|
parent_ids = RoleRelationCRUD.recursive_parent_ids(rid, app_id)
|
||||||
current_app.logger.info('parent ids {0}: {1}'.format(parent_ids, time.time() - s))
|
# current_app.logger.info('parent ids {0}: {1}'.format(parent_ids, time.time() - s))
|
||||||
for parent_id in parent_ids:
|
for parent_id in parent_ids:
|
||||||
|
|
||||||
_resources, _groups = cls._extend_resources(parent_id, resource_type_id, app_id)
|
_resources, _groups = cls._extend_resources(parent_id, resource_type_id, app_id)
|
||||||
current_app.logger.info('middle1: {0}'.format(time.time() - s))
|
# current_app.logger.info('middle1: {0}'.format(time.time() - s))
|
||||||
_merge(result['resources'], _resources)
|
_merge(result['resources'], _resources)
|
||||||
current_app.logger.info('middle2: {0}'.format(time.time() - s))
|
# current_app.logger.info('middle2: {0}'.format(time.time() - s))
|
||||||
current_app.logger.info(len(_groups))
|
# current_app.logger.info(len(_groups))
|
||||||
if not group_flat:
|
if not group_flat:
|
||||||
_merge(result['groups'], _groups)
|
_merge(result['groups'], _groups)
|
||||||
else:
|
else:
|
||||||
@@ -392,7 +399,7 @@ class RoleCRUD(object):
|
|||||||
item.setdefault('permissions', [])
|
item.setdefault('permissions', [])
|
||||||
item['permissions'] = list(set(item['permissions'] + _groups[rg_id]['permissions']))
|
item['permissions'] = list(set(item['permissions'] + _groups[rg_id]['permissions']))
|
||||||
result['resources'][item['id']] = item
|
result['resources'][item['id']] = item
|
||||||
current_app.logger.info('End: {0}'.format(time.time() - s))
|
# current_app.logger.info('End: {0}'.format(time.time() - s))
|
||||||
|
|
||||||
result['resources'] = list(result['resources'].values())
|
result['resources'] = list(result['resources'].values())
|
||||||
result['groups'] = list(result['groups'].values())
|
result['groups'] = list(result['groups'].values())
|
||||||
|
@@ -1,19 +1,15 @@
|
|||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
from base64 import b64decode, b64encode
|
from base64 import b64decode, b64encode
|
||||||
|
|
||||||
from Cryptodome.Protocol.SecretSharing import Shamir
|
from Cryptodome.Protocol.SecretSharing import Shamir
|
||||||
from colorama import Back
|
from colorama import Back, Fore, Style, init as colorama_init
|
||||||
from colorama import Fore
|
|
||||||
from colorama import Style
|
|
||||||
from colorama import init as colorama_init
|
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
from cryptography.hazmat.primitives import hashes
|
from cryptography.hazmat.primitives import hashes, padding
|
||||||
from cryptography.hazmat.primitives import padding
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
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.ciphers.aead import AESGCM
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
@@ -27,11 +23,17 @@ backend_encrypt_key_name = "encrypt_key"
|
|||||||
backend_root_key_salt_name = "root_key_salt"
|
backend_root_key_salt_name = "root_key_salt"
|
||||||
backend_encrypt_key_salt_name = "encrypt_key_salt"
|
backend_encrypt_key_salt_name = "encrypt_key_salt"
|
||||||
backend_seal_key = "seal_status"
|
backend_seal_key = "seal_status"
|
||||||
|
|
||||||
success = "success"
|
success = "success"
|
||||||
seal_status = True
|
seal_status = True
|
||||||
|
|
||||||
|
secrets_encrypt_key = ""
|
||||||
|
secrets_root_key = ""
|
||||||
|
|
||||||
|
|
||||||
def string_to_bytes(value):
|
def string_to_bytes(value):
|
||||||
|
if not value:
|
||||||
|
return ""
|
||||||
if isinstance(value, bytes):
|
if isinstance(value, bytes):
|
||||||
return value
|
return value
|
||||||
if sys.version_info.major == 2:
|
if sys.version_info.major == 2:
|
||||||
@@ -44,6 +46,8 @@ def string_to_bytes(value):
|
|||||||
class Backend:
|
class Backend:
|
||||||
def __init__(self, backend=None):
|
def __init__(self, backend=None):
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
|
# cache is a redis object
|
||||||
|
self.cache = backend.cache
|
||||||
|
|
||||||
def get(self, key):
|
def get(self, key):
|
||||||
return self.backend.get(key)
|
return self.backend.get(key)
|
||||||
@@ -54,23 +58,33 @@ class Backend:
|
|||||||
def update(self, key, value):
|
def update(self, key, value):
|
||||||
return self.backend.update(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:
|
class KeyManage:
|
||||||
|
|
||||||
def __init__(self, trigger=None, backend=None):
|
def __init__(self, trigger=None, backend=None):
|
||||||
self.trigger = trigger
|
self.trigger = trigger
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
|
self.share_key = "cmdb::secret::secrets_share"
|
||||||
if backend:
|
if backend:
|
||||||
self.backend = Backend(backend)
|
self.backend = Backend(backend)
|
||||||
|
|
||||||
def init_app(self, app, backend=None):
|
def init_app(self, app, backend=None):
|
||||||
if (sys.argv[0].endswith("gunicorn") or
|
if (sys.argv[0].endswith("gunicorn") or
|
||||||
(len(sys.argv) > 1 and sys.argv[1] in ("run", "cmdb-password-data-migrate"))):
|
(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,), daemon=True).start()
|
||||||
|
|
||||||
self.trigger = app.config.get("INNER_TRIGGER_TOKEN")
|
self.trigger = app.config.get("INNER_TRIGGER_TOKEN")
|
||||||
if not self.trigger:
|
if not self.trigger:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.backend = backend
|
|
||||||
resp = self.auto_unseal()
|
resp = self.auto_unseal()
|
||||||
self.print_response(resp)
|
self.print_response(resp)
|
||||||
|
|
||||||
@@ -124,6 +138,8 @@ class KeyManage:
|
|||||||
return new_shares
|
return new_shares
|
||||||
|
|
||||||
def is_valid_root_key(self, root_key):
|
def is_valid_root_key(self, root_key):
|
||||||
|
if not root_key:
|
||||||
|
return False
|
||||||
root_key_hash, ok = self.hash_root_key(root_key)
|
root_key_hash, ok = self.hash_root_key(root_key)
|
||||||
if not ok:
|
if not ok:
|
||||||
return root_key_hash, ok
|
return root_key_hash, ok
|
||||||
@@ -135,35 +151,42 @@ class KeyManage:
|
|||||||
else:
|
else:
|
||||||
return "", True
|
return "", True
|
||||||
|
|
||||||
def auth_root_secret(self, root_key):
|
def auth_root_secret(self, root_key, app):
|
||||||
msg, ok = self.is_valid_root_key(root_key)
|
with app.app_context():
|
||||||
if not ok:
|
msg, ok = self.is_valid_root_key(root_key)
|
||||||
return {
|
if not ok:
|
||||||
"message": msg,
|
return {
|
||||||
"status": "failed"
|
"message": msg,
|
||||||
}
|
"status": "failed"
|
||||||
|
}
|
||||||
|
|
||||||
encrypt_key_aes = self.backend.get(backend_encrypt_key_name)
|
encrypt_key_aes = self.backend.get(backend_encrypt_key_name)
|
||||||
if not encrypt_key_aes:
|
if not encrypt_key_aes:
|
||||||
return {
|
return {
|
||||||
"message": "encrypt key is empty",
|
"message": "encrypt key is empty",
|
||||||
"status": "failed"
|
"status": "failed"
|
||||||
}
|
}
|
||||||
|
|
||||||
secrets_encrypt_key, ok = InnerCrypt.aes_decrypt(string_to_bytes(root_key), encrypt_key_aes)
|
secret_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")
|
|
||||||
if ok:
|
if ok:
|
||||||
current_app.config["secrets_encrypt_key"] = secrets_encrypt_key
|
msg, ok = self.backend.update(backend_seal_key, "open")
|
||||||
current_app.config["secrets_root_key"] = root_key
|
if ok:
|
||||||
current_app.config["secrets_shares"] = []
|
global secrets_encrypt_key, secrets_root_key
|
||||||
return {"message": success, "status": success}
|
secrets_encrypt_key = secret_encrypt_key
|
||||||
return {"message": msg, "status": "failed"}
|
secrets_root_key = root_key
|
||||||
else:
|
self.backend.cache.set(self.share_key, json.dumps([]))
|
||||||
return {
|
return {"message": success, "status": success}
|
||||||
"message": secrets_encrypt_key,
|
return {"message": msg, "status": "failed"}
|
||||||
"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):
|
def unseal(self, key):
|
||||||
if not self.is_seal():
|
if not self.is_seal():
|
||||||
@@ -175,14 +198,12 @@ class KeyManage:
|
|||||||
try:
|
try:
|
||||||
t = [i for i in b64decode(key)]
|
t = [i for i in b64decode(key)]
|
||||||
v = (int("".join([chr(i) for i in t[-2:]])), bytes(t[:-2]))
|
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:
|
if v not in shares:
|
||||||
shares.append(v)
|
shares.append(v)
|
||||||
current_app.config["secrets_shares"] = shares
|
self.set_shares(shares)
|
||||||
|
|
||||||
if len(shares) >= global_key_threshold:
|
if len(shares) >= global_key_threshold:
|
||||||
recovered_secret = Shamir.combine(shares[:global_key_threshold], False)
|
return self.parse_shares(shares, current_app)
|
||||||
return self.auth_root_secret(b64encode(recovered_secret))
|
|
||||||
else:
|
else:
|
||||||
return {
|
return {
|
||||||
"message": "waiting for inputting other unseal key {0}/{1}".format(len(shares),
|
"message": "waiting for inputting other unseal key {0}/{1}".format(len(shares),
|
||||||
@@ -242,8 +263,11 @@ class KeyManage:
|
|||||||
msg, ok = self.backend.add(backend_seal_key, "open")
|
msg, ok = self.backend.add(backend_seal_key, "open")
|
||||||
if not ok:
|
if not ok:
|
||||||
return {"message": msg, "status": "failed"}, False
|
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)
|
self.print_token(shares, root_token=root_key)
|
||||||
|
|
||||||
return {"message": "OK",
|
return {"message": "OK",
|
||||||
@@ -266,7 +290,7 @@ class KeyManage:
|
|||||||
}
|
}
|
||||||
# TODO
|
# TODO
|
||||||
elif len(self.trigger.strip()) == 24:
|
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:
|
if res.get("status") == success:
|
||||||
return {
|
return {
|
||||||
"message": success,
|
"message": success,
|
||||||
@@ -298,22 +322,31 @@ class KeyManage:
|
|||||||
"message": msg,
|
"message": msg,
|
||||||
"status": "failed",
|
"status": "failed",
|
||||||
}
|
}
|
||||||
current_app.config["secrets_root_key"] = ''
|
self.clear()
|
||||||
current_app.config["secrets_encrypt_key"] = ''
|
self.backend.cache.publish(self.share_key, "clear")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"message": success,
|
"message": success,
|
||||||
"status": success
|
"status": success
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def clear():
|
||||||
|
global secrets_encrypt_key, secrets_root_key
|
||||||
|
secrets_encrypt_key = ''
|
||||||
|
secrets_root_key = ''
|
||||||
|
|
||||||
def is_seal(self):
|
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:
|
: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)
|
msg, ok = self.is_valid_root_key(secrets_root_key)
|
||||||
if not ok:
|
if not ok:
|
||||||
return true
|
return True
|
||||||
status = self.backend.get(backend_seal_key)
|
status = self.backend.get(backend_seal_key)
|
||||||
return status == "block"
|
return status == "block"
|
||||||
|
|
||||||
@@ -349,22 +382,53 @@ class KeyManage:
|
|||||||
}
|
}
|
||||||
print(status_colors.get(status, Fore.GREEN), message, Style.RESET_ALL)
|
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:
|
class InnerCrypt:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
secrets_encrypt_key = current_app.config.get("secrets_encrypt_key", "")
|
self.encrypt_key = b64decode(secrets_encrypt_key)
|
||||||
self.encrypt_key = b64decode(secrets_encrypt_key.encode("utf-8"))
|
# self.encrypt_key = b64decode(secrets_encrypt_key, "".encode("utf-8"))
|
||||||
|
|
||||||
def encrypt(self, plaintext):
|
def encrypt(self, plaintext):
|
||||||
"""
|
"""
|
||||||
encrypt method contain aes currently
|
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)
|
return self.aes_encrypt(self.encrypt_key, plaintext)
|
||||||
|
|
||||||
def decrypt(self, ciphertext):
|
def decrypt(self, ciphertext):
|
||||||
"""
|
"""
|
||||||
decrypt method contain aes currently
|
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)
|
return self.aes_decrypt(self.encrypt_key, ciphertext)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -381,6 +445,7 @@ class InnerCrypt:
|
|||||||
|
|
||||||
return b64encode(iv + ciphertext).decode("utf-8"), True
|
return b64encode(iv + ciphertext).decode("utf-8"), True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
||||||
return str(e), False
|
return str(e), False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@@ -1,8 +1,13 @@
|
|||||||
|
import base64
|
||||||
|
import json
|
||||||
|
|
||||||
from api.models.cmdb import InnerKV
|
from api.models.cmdb import InnerKV
|
||||||
|
from api.extensions import rd
|
||||||
|
|
||||||
|
|
||||||
class InnerKVManger(object):
|
class InnerKVManger(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.cache = rd.r
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -33,3 +38,26 @@ class InnerKVManger(object):
|
|||||||
return "success", True
|
return "success", True
|
||||||
|
|
||||||
return "update failed", 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))
|
||||||
|
|
||||||
|
|
||||||
|
@@ -117,6 +117,23 @@ class RedisHandler(object):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error("delete redis key error, {0}".format(str(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):
|
class ESHandler(object):
|
||||||
def __init__(self, flask_app=None):
|
def __init__(self, flask_app=None):
|
||||||
|
@@ -88,11 +88,11 @@ def webhook_request(webhook, payload):
|
|||||||
|
|
||||||
params = webhook.get('parameters') or None
|
params = webhook.get('parameters') or None
|
||||||
if isinstance(params, dict):
|
if isinstance(params, dict):
|
||||||
params = json.loads(Template(json.dumps(params)).render(payload))
|
params = json.loads(Template(json.dumps(params)).render(payload).encode('utf-8'))
|
||||||
|
|
||||||
headers = json.loads(Template(json.dumps(webhook.get('headers') or {})).render(payload))
|
headers = json.loads(Template(json.dumps(webhook.get('headers') or {})).render(payload))
|
||||||
|
|
||||||
data = Template(json.dumps(webhook.get('body', ''))).render(payload)
|
data = Template(json.dumps(webhook.get('body', ''))).render(payload).encode('utf-8')
|
||||||
auth = _wrap_auth(**webhook.get('authorization', {}))
|
auth = _wrap_auth(**webhook.get('authorization', {}))
|
||||||
|
|
||||||
if (webhook.get('authorization', {}).get("type") or '').lower() == 'oauth2.0':
|
if (webhook.get('authorization', {}).get("type") or '').lower() == 'oauth2.0':
|
||||||
|
@@ -46,13 +46,17 @@ class CIType(Model):
|
|||||||
name = db.Column(db.String(32), nullable=False)
|
name = db.Column(db.String(32), nullable=False)
|
||||||
alias = 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)
|
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)
|
enabled = db.Column(db.Boolean, default=True, nullable=False)
|
||||||
is_attached = db.Column(db.Boolean, default=False, nullable=False)
|
is_attached = db.Column(db.Boolean, default=False, nullable=False)
|
||||||
icon = db.Column(db.Text)
|
icon = db.Column(db.Text)
|
||||||
order = db.Column(db.SmallInteger, default=0, nullable=False)
|
order = db.Column(db.SmallInteger, default=0, nullable=False)
|
||||||
default_order_attr = db.Column(db.String(33))
|
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)
|
uid = db.Column(db.Integer, index=True)
|
||||||
|
|
||||||
@@ -75,6 +79,9 @@ class CITypeRelation(Model):
|
|||||||
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
|
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
|
||||||
constraint = db.Column(db.Enum(*ConstraintEnum.all()), default=ConstraintEnum.One2Many)
|
constraint = db.Column(db.Enum(*ConstraintEnum.all()), default=ConstraintEnum.One2Many)
|
||||||
|
|
||||||
|
parent_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
|
||||||
|
child_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
|
||||||
|
|
||||||
parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.parent_id")
|
parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.parent_id")
|
||||||
child = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.child_id")
|
child = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.child_id")
|
||||||
relation_type = db.relationship("RelationType", backref="c_ci_type_relations.relation_type_id")
|
relation_type = db.relationship("RelationType", backref="c_ci_type_relations.relation_type_id")
|
||||||
@@ -202,6 +209,26 @@ class CITriggerHistory(Model):
|
|||||||
webhook = db.Column(db.Text)
|
webhook = db.Column(db.Text)
|
||||||
|
|
||||||
|
|
||||||
|
class TopologyViewGroup(Model):
|
||||||
|
__tablename__ = 'c_topology_view_groups'
|
||||||
|
|
||||||
|
name = db.Column(db.String(64), index=True)
|
||||||
|
order = db.Column(db.Integer, default=0)
|
||||||
|
|
||||||
|
|
||||||
|
class TopologyView(Model):
|
||||||
|
__tablename__ = 'c_topology_views'
|
||||||
|
|
||||||
|
name = db.Column(db.String(64), index=True)
|
||||||
|
group_id = db.Column(db.Integer, db.ForeignKey('c_topology_view_groups.id'))
|
||||||
|
category = db.Column(db.String(32))
|
||||||
|
central_node_type = db.Column(db.Integer)
|
||||||
|
central_node_instances = db.Column(db.Text)
|
||||||
|
path = db.Column(db.JSON)
|
||||||
|
order = db.Column(db.Integer, default=0)
|
||||||
|
option = db.Column(db.JSON)
|
||||||
|
|
||||||
|
|
||||||
class CITypeUniqueConstraint(Model):
|
class CITypeUniqueConstraint(Model):
|
||||||
__tablename__ = "c_c_t_u_c"
|
__tablename__ = "c_c_t_u_c"
|
||||||
|
|
||||||
@@ -425,6 +452,7 @@ class CITypeHistory(Model):
|
|||||||
|
|
||||||
attr_id = db.Column(db.Integer)
|
attr_id = db.Column(db.Integer)
|
||||||
trigger_id = db.Column(db.Integer)
|
trigger_id = db.Column(db.Integer)
|
||||||
|
rc_id = db.Column(db.Integer)
|
||||||
unique_constraint_id = db.Column(db.Integer)
|
unique_constraint_id = db.Column(db.Integer)
|
||||||
|
|
||||||
uid = db.Column(db.Integer, index=True)
|
uid = db.Column(db.Integer, index=True)
|
||||||
@@ -460,6 +488,7 @@ class PreferenceRelationView(Model):
|
|||||||
name = db.Column(db.String(64), index=True, nullable=False)
|
name = db.Column(db.String(64), index=True, nullable=False)
|
||||||
cr_ids = db.Column(db.JSON) # [{parent_id: x, child_id: y}]
|
cr_ids = db.Column(db.JSON) # [{parent_id: x, child_id: y}]
|
||||||
is_public = db.Column(db.Boolean, default=False)
|
is_public = db.Column(db.Boolean, default=False)
|
||||||
|
option = db.Column(db.JSON)
|
||||||
|
|
||||||
|
|
||||||
class PreferenceSearchOption(Model):
|
class PreferenceSearchOption(Model):
|
||||||
|
@@ -3,12 +3,13 @@
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from celery_once import QueueOnce
|
import redis_lock
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from werkzeug.exceptions import BadRequest
|
from werkzeug.exceptions import BadRequest
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from api.extensions import celery
|
from api.extensions import celery
|
||||||
|
from api.extensions import rd
|
||||||
from api.lib.decorator import flush_db
|
from api.lib.decorator import flush_db
|
||||||
from api.lib.decorator import reconnect_db
|
from api.lib.decorator import reconnect_db
|
||||||
from api.lib.perm.acl.audit import AuditCRUD
|
from api.lib.perm.acl.audit import AuditCRUD
|
||||||
@@ -25,14 +26,14 @@ from api.models.acl import Role
|
|||||||
from api.models.acl import Trigger
|
from api.models.acl import Trigger
|
||||||
|
|
||||||
|
|
||||||
@celery.task(name="acl.role_rebuild",
|
@celery.task(name="acl.role_rebuild", queue=ACL_QUEUE, )
|
||||||
queue=ACL_QUEUE,)
|
|
||||||
@flush_db
|
@flush_db
|
||||||
@reconnect_db
|
@reconnect_db
|
||||||
def role_rebuild(rids, app_id):
|
def role_rebuild(rids, app_id):
|
||||||
rids = rids if isinstance(rids, list) else [rids]
|
rids = rids if isinstance(rids, list) else [rids]
|
||||||
for rid in 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))
|
current_app.logger.info("Role {0} App {1} rebuild..........".format(rids, app_id))
|
||||||
|
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import time
|
|
||||||
|
|
||||||
import redis_lock
|
import redis_lock
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
@@ -33,8 +32,7 @@ from api.models.cmdb import CITypeAttribute
|
|||||||
@reconnect_db
|
@reconnect_db
|
||||||
def ci_cache(ci_id, operate_type, record_id):
|
def ci_cache(ci_id, operate_type, record_id):
|
||||||
from api.lib.cmdb.ci import CITriggerManager
|
from api.lib.cmdb.ci import CITriggerManager
|
||||||
|
from api.lib.cmdb.ci import CIRelationManager
|
||||||
time.sleep(0.01)
|
|
||||||
|
|
||||||
m = api.lib.cmdb.ci.CIManager()
|
m = api.lib.cmdb.ci.CIManager()
|
||||||
ci_dict = m.get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
|
ci_dict = m.get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
|
||||||
@@ -52,13 +50,21 @@ def ci_cache(ci_id, operate_type, record_id):
|
|||||||
|
|
||||||
CITriggerManager.fire(operate_type, ci_dict, record_id)
|
CITriggerManager.fire(operate_type, ci_dict, record_id)
|
||||||
|
|
||||||
|
ci_dict and CIRelationManager.build_by_attribute(ci_dict)
|
||||||
|
|
||||||
|
|
||||||
|
@celery.task(name="cmdb.rebuild_relation_for_attribute_changed", queue=CMDB_QUEUE)
|
||||||
|
@reconnect_db
|
||||||
|
def rebuild_relation_for_attribute_changed(ci_type_relation):
|
||||||
|
from api.lib.cmdb.ci import CIRelationManager
|
||||||
|
|
||||||
|
CIRelationManager.rebuild_all_by_attribute(ci_type_relation)
|
||||||
|
|
||||||
|
|
||||||
@celery.task(name="cmdb.batch_ci_cache", queue=CMDB_QUEUE)
|
@celery.task(name="cmdb.batch_ci_cache", queue=CMDB_QUEUE)
|
||||||
@flush_db
|
@flush_db
|
||||||
@reconnect_db
|
@reconnect_db
|
||||||
def batch_ci_cache(ci_ids, ): # only for attribute change index
|
def batch_ci_cache(ci_ids, ): # only for attribute change index
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
for ci_id in ci_ids:
|
for ci_id in ci_ids:
|
||||||
m = api.lib.cmdb.ci.CIManager()
|
m = api.lib.cmdb.ci.CIManager()
|
||||||
ci_dict = m.get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
|
ci_dict = m.get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
|
||||||
@@ -87,7 +93,6 @@ def ci_delete(ci_id):
|
|||||||
@celery.task(name="cmdb.delete_id_filter", queue=CMDB_QUEUE)
|
@celery.task(name="cmdb.delete_id_filter", queue=CMDB_QUEUE)
|
||||||
@reconnect_db
|
@reconnect_db
|
||||||
def delete_id_filter(ci_id):
|
def delete_id_filter(ci_id):
|
||||||
|
|
||||||
CIFilterPermsCRUD().delete_id_filter_by_ci_id(ci_id)
|
CIFilterPermsCRUD().delete_id_filter_by_ci_id(ci_id)
|
||||||
|
|
||||||
|
|
||||||
@@ -108,18 +113,17 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
|
|||||||
@reconnect_db
|
@reconnect_db
|
||||||
def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
||||||
with redis_lock.Lock(rd.r, "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 = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
children = json.loads(children) if children is not None else {}
|
||||||
children = json.loads(children) if children is not None else {}
|
|
||||||
|
|
||||||
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
|
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
|
||||||
first=True, to_dict=False)
|
first=True, to_dict=False)
|
||||||
if str(child_id) not in children:
|
if str(child_id) not in children:
|
||||||
children[str(child_id)] = cr.second_ci.type_id
|
children[str(child_id)] = cr.second_ci.type_id
|
||||||
|
|
||||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||||
|
|
||||||
else:
|
if ancestor_ids is not None:
|
||||||
key = "{},{}".format(ancestor_ids, parent_id)
|
key = "{},{}".format(ancestor_ids, parent_id)
|
||||||
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
||||||
grandson = json.loads(grandson) if grandson is not None else {}
|
grandson = json.loads(grandson) if grandson is not None else {}
|
||||||
@@ -186,16 +190,15 @@ def ci_relation_add(parent_dict, child_id, uid):
|
|||||||
@reconnect_db
|
@reconnect_db
|
||||||
def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
||||||
with redis_lock.Lock(rd.r, "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 = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
children = json.loads(children) if children is not None else {}
|
||||||
children = json.loads(children) if children is not None else {}
|
|
||||||
|
|
||||||
if str(child_id) in children:
|
if str(child_id) in children:
|
||||||
children.pop(str(child_id))
|
children.pop(str(child_id))
|
||||||
|
|
||||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||||
|
|
||||||
else:
|
if ancestor_ids is not None:
|
||||||
key = "{},{}".format(ancestor_ids, parent_id)
|
key = "{},{}".format(ancestor_ids, parent_id)
|
||||||
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
||||||
grandson = json.loads(grandson) if grandson is not None else {}
|
grandson = json.loads(grandson) if grandson is not None else {}
|
||||||
|
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-03-01 13:49+0800\n"
|
"POT-Creation-Date: 2024-05-28 18:05+0800\n"
|
||||||
"PO-Revision-Date: 2023-12-25 20:21+0800\n"
|
"PO-Revision-Date: 2023-12-25 20:21+0800\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language: zh\n"
|
"Language: zh\n"
|
||||||
@@ -284,162 +284,198 @@ msgstr "重复的触发器"
|
|||||||
msgid "Trigger {} does not exist"
|
msgid "Trigger {} does not exist"
|
||||||
msgstr "触发器 {} 不存在"
|
msgstr "触发器 {} 不存在"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:81
|
||||||
|
msgid "Duplicated reconciliation rule"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:82
|
#: api/lib/cmdb/resp_format.py:82
|
||||||
|
msgid "Reconciliation rule {} does not exist"
|
||||||
|
msgstr "关系类型 {} 不存在"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:84
|
||||||
msgid "Operation record {} does not exist"
|
msgid "Operation record {} does not exist"
|
||||||
msgstr "操作记录 {} 不存在"
|
msgstr "操作记录 {} 不存在"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:83
|
#: api/lib/cmdb/resp_format.py:85
|
||||||
msgid "Unique identifier cannot be deleted"
|
msgid "Unique identifier cannot be deleted"
|
||||||
msgstr "不能删除唯一标识"
|
msgstr "不能删除唯一标识"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:84
|
#: api/lib/cmdb/resp_format.py:86
|
||||||
msgid "Cannot delete default sorted attributes"
|
msgid "Cannot delete default sorted attributes"
|
||||||
msgstr "不能删除默认排序的属性"
|
msgstr "不能删除默认排序的属性"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:86
|
#: api/lib/cmdb/resp_format.py:88
|
||||||
msgid "No node selected"
|
msgid "No node selected"
|
||||||
msgstr "没有选择节点"
|
msgstr "没有选择节点"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:87
|
#: api/lib/cmdb/resp_format.py:89
|
||||||
msgid "This search option does not exist!"
|
msgid "This search option does not exist!"
|
||||||
msgstr "该搜索选项不存在!"
|
msgstr "该搜索选项不存在!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:88
|
#: api/lib/cmdb/resp_format.py:90
|
||||||
msgid "This search option has a duplicate name!"
|
msgid "This search option has a duplicate name!"
|
||||||
msgstr "该搜索选项命名重复!"
|
msgstr "该搜索选项命名重复!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:90
|
#: api/lib/cmdb/resp_format.py:92
|
||||||
msgid "Relationship type {} already exists"
|
msgid "Relationship type {} already exists"
|
||||||
msgstr "关系类型 {} 已经存在"
|
msgstr "关系类型 {} 已经存在"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:91
|
#: api/lib/cmdb/resp_format.py:93
|
||||||
msgid "Relationship type {} does not exist"
|
msgid "Relationship type {} does not exist"
|
||||||
msgstr "关系类型 {} 不存在"
|
msgstr "关系类型 {} 不存在"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:93
|
#: api/lib/cmdb/resp_format.py:95
|
||||||
msgid "Invalid attribute value: {}"
|
msgid "Invalid attribute value: {}"
|
||||||
msgstr "无效的属性值: {}"
|
msgstr "无效的属性值: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:94
|
#: api/lib/cmdb/resp_format.py:96
|
||||||
msgid "{} Invalid value: {}"
|
msgid "{} Invalid value: {}"
|
||||||
msgstr "无效的值: {}"
|
msgstr "{} 无效的值: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:95
|
#: api/lib/cmdb/resp_format.py:97
|
||||||
msgid "{} is not in the predefined values"
|
msgid "{} is not in the predefined values"
|
||||||
msgstr "{} 不在预定义值里"
|
msgstr "{} 不在预定义值里"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:97
|
#: api/lib/cmdb/resp_format.py:99
|
||||||
msgid "The value of attribute {} must be unique, {} already exists"
|
msgid "The value of attribute {} must be unique, {} already exists"
|
||||||
msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
|
msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:98
|
#: api/lib/cmdb/resp_format.py:100
|
||||||
msgid "Attribute {} value must exist"
|
msgid "Attribute {} value must exist"
|
||||||
msgstr "属性 {} 值必须存在"
|
msgstr "属性 {} 值必须存在"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:101
|
#: api/lib/cmdb/resp_format.py:101
|
||||||
|
msgid "Out of range value, the maximum value is 2147483647"
|
||||||
|
msgstr "超过最大值限制, 最大值是2147483647"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:103
|
||||||
msgid "Unknown error when adding or modifying attribute value: {}"
|
msgid "Unknown error when adding or modifying attribute value: {}"
|
||||||
msgstr "新增或者修改属性值未知错误: {}"
|
msgstr "新增或者修改属性值未知错误: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:103
|
#: api/lib/cmdb/resp_format.py:105
|
||||||
msgid "Duplicate custom name"
|
msgid "Duplicate custom name"
|
||||||
msgstr "订制名重复"
|
msgstr "订制名重复"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:105
|
#: api/lib/cmdb/resp_format.py:107
|
||||||
msgid "Number of models exceeds limit: {}"
|
msgid "Number of models exceeds limit: {}"
|
||||||
msgstr "模型数超过限制: {}"
|
msgstr "模型数超过限制: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:106
|
#: api/lib/cmdb/resp_format.py:108
|
||||||
msgid "The number of CIs exceeds the limit: {}"
|
msgid "The number of CIs exceeds the limit: {}"
|
||||||
msgstr "CI数超过限制: {}"
|
msgstr "CI数超过限制: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:108
|
#: api/lib/cmdb/resp_format.py:110
|
||||||
msgid "Auto-discovery rule: {} already exists!"
|
msgid "Auto-discovery rule: {} already exists!"
|
||||||
msgstr "自动发现规则: {} 已经存在!"
|
msgstr "自动发现规则: {} 已经存在!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:109
|
#: api/lib/cmdb/resp_format.py:111
|
||||||
msgid "Auto-discovery rule: {} does not exist!"
|
msgid "Auto-discovery rule: {} does not exist!"
|
||||||
msgstr "自动发现规则: {} 不存在!"
|
msgstr "自动发现规则: {} 不存在!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:111
|
#: api/lib/cmdb/resp_format.py:113
|
||||||
msgid "This auto-discovery rule is referenced by the model and cannot be deleted!"
|
msgid "This auto-discovery rule is referenced by the model and cannot be deleted!"
|
||||||
msgstr "该自动发现规则被模型引用, 不能删除!"
|
msgstr "该自动发现规则被模型引用, 不能删除!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:113
|
#: api/lib/cmdb/resp_format.py:115
|
||||||
msgid "The application of auto-discovery rules cannot be defined repeatedly!"
|
msgid "The application of auto-discovery rules cannot be defined repeatedly!"
|
||||||
msgstr "自动发现规则的应用不能重复定义!"
|
msgstr "自动发现规则的应用不能重复定义!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:114
|
#: api/lib/cmdb/resp_format.py:116
|
||||||
msgid "The auto-discovery you want to modify: {} does not exist!"
|
msgid "The auto-discovery you want to modify: {} does not exist!"
|
||||||
msgstr "您要修改的自动发现: {} 不存在!"
|
msgstr "您要修改的自动发现: {} 不存在!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:115
|
#: api/lib/cmdb/resp_format.py:117
|
||||||
msgid "Attribute does not include unique identifier: {}"
|
msgid "Attribute does not include unique identifier: {}"
|
||||||
msgstr "属性字段没有包括唯一标识: {}"
|
msgstr "属性字段没有包括唯一标识: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:116
|
#: api/lib/cmdb/resp_format.py:118
|
||||||
msgid "The auto-discovery instance does not exist!"
|
msgid "The auto-discovery instance does not exist!"
|
||||||
msgstr "自动发现的实例不存在!"
|
msgstr "自动发现的实例不存在!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:117
|
#: api/lib/cmdb/resp_format.py:119
|
||||||
msgid "The model is not associated with this auto-discovery!"
|
msgid "The model is not associated with this auto-discovery!"
|
||||||
msgstr "模型并未关联该自动发现!"
|
msgstr "模型并未关联该自动发现!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:118
|
#: api/lib/cmdb/resp_format.py:120
|
||||||
msgid "Only the creator can modify the Secret!"
|
msgid "Only the creator can modify the Secret!"
|
||||||
msgstr "只有创建人才能修改Secret!"
|
msgstr "只有创建人才能修改Secret!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:120
|
#: api/lib/cmdb/resp_format.py:122
|
||||||
msgid "This rule already has auto-discovery instances and cannot be deleted!"
|
msgid "This rule already has auto-discovery instances and cannot be deleted!"
|
||||||
msgstr "该规则已经有自动发现的实例, 不能被删除!"
|
msgstr "该规则已经有自动发现的实例, 不能被删除!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:122
|
#: api/lib/cmdb/resp_format.py:124
|
||||||
msgid "The default auto-discovery rule is already referenced by model {}!"
|
msgid "The default auto-discovery rule is already referenced by model {}!"
|
||||||
msgstr "该默认的自动发现规则 已经被模型 {} 引用!"
|
msgstr "该默认的自动发现规则 已经被模型 {} 引用!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:124
|
#: api/lib/cmdb/resp_format.py:126
|
||||||
msgid "The unique_key method must return a non-empty string!"
|
msgid "The unique_key method must return a non-empty string!"
|
||||||
msgstr "unique_key方法必须返回非空字符串!"
|
msgstr "unique_key方法必须返回非空字符串!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:125
|
#: api/lib/cmdb/resp_format.py:127
|
||||||
msgid "The attributes method must return a list"
|
msgid "The attributes method must return a list"
|
||||||
msgstr "attributes方法必须返回的是list"
|
msgstr "attributes方法必须返回的是list"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:127
|
#: api/lib/cmdb/resp_format.py:129
|
||||||
msgid "The list returned by the attributes method cannot be empty!"
|
msgid "The list returned by the attributes method cannot be empty!"
|
||||||
msgstr "attributes方法返回的list不能为空!"
|
msgstr "attributes方法返回的list不能为空!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:129
|
#: api/lib/cmdb/resp_format.py:131
|
||||||
msgid "Only administrators can define execution targets as: all nodes!"
|
msgid "Only administrators can define execution targets as: all nodes!"
|
||||||
msgstr "只有管理员才可以定义执行机器为: 所有节点!"
|
msgstr "只有管理员才可以定义执行机器为: 所有节点!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:130
|
#: api/lib/cmdb/resp_format.py:132
|
||||||
msgid "Execute targets permission check failed: {}"
|
msgid "Execute targets permission check failed: {}"
|
||||||
msgstr "执行机器权限检查不通过: {}"
|
msgstr "执行机器权限检查不通过: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:132
|
#: api/lib/cmdb/resp_format.py:134
|
||||||
msgid "CI filter authorization must be named!"
|
msgid "CI filter authorization must be named!"
|
||||||
msgstr "CI过滤授权 必须命名!"
|
msgstr "CI过滤授权 必须命名!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:133
|
#: api/lib/cmdb/resp_format.py:135
|
||||||
msgid "CI filter authorization is currently not supported or query"
|
msgid "CI filter authorization is currently not supported or query"
|
||||||
msgstr "CI过滤授权 暂时不支持 或 查询"
|
msgstr "CI过滤授权 暂时不支持 或 查询"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:136
|
#: api/lib/cmdb/resp_format.py:138
|
||||||
msgid "You do not have permission to operate attribute {}!"
|
msgid "You do not have permission to operate attribute {}!"
|
||||||
msgstr "您没有属性 {} 的操作权限!"
|
msgstr "您没有属性 {} 的操作权限!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:137
|
#: api/lib/cmdb/resp_format.py:139
|
||||||
msgid "You do not have permission to operate this CI!"
|
msgid "You do not have permission to operate this CI!"
|
||||||
msgstr "您没有该CI的操作权限!"
|
msgstr "您没有该CI的操作权限!"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:139
|
#: api/lib/cmdb/resp_format.py:141
|
||||||
msgid "Failed to save password: {}"
|
msgid "Failed to save password: {}"
|
||||||
msgstr "保存密码失败: {}"
|
msgstr "保存密码失败: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:140
|
#: api/lib/cmdb/resp_format.py:142
|
||||||
msgid "Failed to get password: {}"
|
msgid "Failed to get password: {}"
|
||||||
msgstr "获取密码失败: {}"
|
msgstr "获取密码失败: {}"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:144
|
||||||
|
msgid "Scheduling time format error"
|
||||||
|
msgstr "{}格式错误,应该为:%Y-%m-%d %H:%M:%S"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:145
|
||||||
|
msgid "CMDB data reconciliation results"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:146
|
||||||
|
msgid "Number of {} illegal: {}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:148
|
||||||
|
msgid "Topology view {} already exists"
|
||||||
|
msgstr "拓扑视图 {} 已经存在"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:149
|
||||||
|
msgid "Topology group {} already exists"
|
||||||
|
msgstr "拓扑视图分组 {} 已经存在"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:151
|
||||||
|
msgid "The group cannot be deleted because the topology view already exists"
|
||||||
|
msgstr "因为该分组下定义了拓扑视图,不能删除"
|
||||||
|
|
||||||
#: api/lib/common_setting/resp_format.py:8
|
#: api/lib/common_setting/resp_format.py:8
|
||||||
msgid "Company info already existed"
|
msgid "Company info already existed"
|
||||||
msgstr "公司信息已存在,无法创建!"
|
msgstr "公司信息已存在,无法创建!"
|
||||||
@@ -696,6 +732,10 @@ msgstr "LDAP测试用户名必填"
|
|||||||
msgid "Company wide"
|
msgid "Company wide"
|
||||||
msgstr "全公司"
|
msgstr "全公司"
|
||||||
|
|
||||||
|
#: api/lib/common_setting/resp_format.py:84
|
||||||
|
msgid "No permission to access resource {}, perm {} "
|
||||||
|
msgstr "您没有资源: {} 的 {} 权限"
|
||||||
|
|
||||||
#: api/lib/perm/acl/resp_format.py:9
|
#: api/lib/perm/acl/resp_format.py:9
|
||||||
msgid "login successful"
|
msgid "login successful"
|
||||||
msgstr "登录成功"
|
msgstr "登录成功"
|
||||||
|
@@ -38,8 +38,9 @@ class LoginView(APIView):
|
|||||||
username = request.values.get("username") or request.values.get("email")
|
username = request.values.get("username") or request.values.get("email")
|
||||||
password = request.values.get("password")
|
password = request.values.get("password")
|
||||||
_role = None
|
_role = None
|
||||||
|
auth_with_ldap = request.values.get('auth_with_ldap', True)
|
||||||
config = AuthenticateDataCRUD(AuthenticateType.LDAP).get()
|
config = AuthenticateDataCRUD(AuthenticateType.LDAP).get()
|
||||||
if config.get('enabled') or config.get('enable'):
|
if (config.get('enabled') or config.get('enable')) and auth_with_ldap:
|
||||||
from api.lib.perm.authentication.ldap import authenticate_with_ldap
|
from api.lib.perm.authentication.ldap import authenticate_with_ldap
|
||||||
user, authenticated = authenticate_with_ldap(username, password)
|
user, authenticated = authenticate_with_ldap(username, password)
|
||||||
else:
|
else:
|
||||||
|
@@ -16,6 +16,7 @@ from api.lib.cmdb.const import RetKey
|
|||||||
from api.lib.cmdb.perms import has_perm_for_ci
|
from api.lib.cmdb.perms import has_perm_for_ci
|
||||||
from api.lib.cmdb.search import SearchError
|
from api.lib.cmdb.search import SearchError
|
||||||
from api.lib.cmdb.search.ci import search
|
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.perm.acl.acl import has_perm_from_args
|
||||||
from api.lib.utils import get_page
|
from api.lib.utils import get_page
|
||||||
from api.lib.utils import get_page_size
|
from api.lib.utils import get_page_size
|
||||||
@@ -76,6 +77,7 @@ class CIView(APIView):
|
|||||||
@has_perm_for_ci("ci_type", ResourceTypeEnum.CI, PermEnum.ADD, lambda x: CITypeCache.get(x))
|
@has_perm_for_ci("ci_type", ResourceTypeEnum.CI, PermEnum.ADD, lambda x: CITypeCache.get(x))
|
||||||
def post(self):
|
def post(self):
|
||||||
ci_type = request.values.get("ci_type")
|
ci_type = request.values.get("ci_type")
|
||||||
|
ticket_id = request.values.pop("ticket_id", None)
|
||||||
_no_attribute_policy = request.values.get("no_attribute_policy", ExistPolicy.IGNORE)
|
_no_attribute_policy = request.values.get("no_attribute_policy", ExistPolicy.IGNORE)
|
||||||
|
|
||||||
exist_policy = request.values.pop('exist_policy', None)
|
exist_policy = request.values.pop('exist_policy', None)
|
||||||
@@ -87,6 +89,7 @@ class CIView(APIView):
|
|||||||
exist_policy=exist_policy or ExistPolicy.REJECT,
|
exist_policy=exist_policy or ExistPolicy.REJECT,
|
||||||
_no_attribute_policy=_no_attribute_policy,
|
_no_attribute_policy=_no_attribute_policy,
|
||||||
_is_admin=request.values.pop('__is_admin', None) or False,
|
_is_admin=request.values.pop('__is_admin', None) or False,
|
||||||
|
ticket_id=ticket_id,
|
||||||
**ci_dict)
|
**ci_dict)
|
||||||
|
|
||||||
return self.jsonify(ci_id=ci_id)
|
return self.jsonify(ci_id=ci_id)
|
||||||
@@ -95,6 +98,7 @@ class CIView(APIView):
|
|||||||
def put(self, ci_id=None):
|
def put(self, ci_id=None):
|
||||||
args = request.values
|
args = request.values
|
||||||
ci_type = args.get("ci_type")
|
ci_type = args.get("ci_type")
|
||||||
|
ticket_id = request.values.pop("ticket_id", None)
|
||||||
_no_attribute_policy = args.get("no_attribute_policy", ExistPolicy.IGNORE)
|
_no_attribute_policy = args.get("no_attribute_policy", ExistPolicy.IGNORE)
|
||||||
|
|
||||||
ci_dict = self._wrap_ci_dict()
|
ci_dict = self._wrap_ci_dict()
|
||||||
@@ -102,6 +106,7 @@ class CIView(APIView):
|
|||||||
if ci_id is not None:
|
if ci_id is not None:
|
||||||
manager.update(ci_id,
|
manager.update(ci_id,
|
||||||
_is_admin=request.values.pop('__is_admin', None) or False,
|
_is_admin=request.values.pop('__is_admin', None) or False,
|
||||||
|
ticket_id=ticket_id,
|
||||||
**ci_dict)
|
**ci_dict)
|
||||||
else:
|
else:
|
||||||
request.values.pop('exist_policy', None)
|
request.values.pop('exist_policy', None)
|
||||||
@@ -109,6 +114,7 @@ class CIView(APIView):
|
|||||||
exist_policy=ExistPolicy.REPLACE,
|
exist_policy=ExistPolicy.REPLACE,
|
||||||
_no_attribute_policy=_no_attribute_policy,
|
_no_attribute_policy=_no_attribute_policy,
|
||||||
_is_admin=request.values.pop('__is_admin', None) or False,
|
_is_admin=request.values.pop('__is_admin', None) or False,
|
||||||
|
ticket_id=ticket_id,
|
||||||
**ci_dict)
|
**ci_dict)
|
||||||
|
|
||||||
return self.jsonify(ci_id=ci_id)
|
return self.jsonify(ci_id=ci_id)
|
||||||
@@ -221,7 +227,6 @@ class CIHeartbeatView(APIView):
|
|||||||
class CIFlushView(APIView):
|
class CIFlushView(APIView):
|
||||||
url_prefix = ("/ci/flush", "/ci/<int:ci_id>/flush")
|
url_prefix = ("/ci/flush", "/ci/<int:ci_id>/flush")
|
||||||
|
|
||||||
# @auth_abandoned
|
|
||||||
def get(self, ci_id=None):
|
def get(self, ci_id=None):
|
||||||
from api.tasks.cmdb import ci_cache
|
from api.tasks.cmdb import ci_cache
|
||||||
from api.lib.cmdb.const import CMDB_QUEUE
|
from api.lib.cmdb.const import CMDB_QUEUE
|
||||||
@@ -250,3 +255,23 @@ class CIPasswordView(APIView):
|
|||||||
|
|
||||||
def post(self, ci_id, attr_id):
|
def post(self, ci_id, attr_id):
|
||||||
return self.get(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)
|
||||||
|
@@ -30,6 +30,7 @@ class CIRelationSearchView(APIView):
|
|||||||
level: default is 1
|
level: default is 1
|
||||||
facet: statistic
|
facet: statistic
|
||||||
"""
|
"""
|
||||||
|
|
||||||
page = get_page(request.values.get("page", 1))
|
page = get_page(request.values.get("page", 1))
|
||||||
count = get_page_size(request.values.get("count") or request.values.get("page_size"))
|
count = get_page_size(request.values.get("count") or request.values.get("page_size"))
|
||||||
|
|
||||||
@@ -86,6 +87,26 @@ class CIRelationStatisticsView(APIView):
|
|||||||
return self.jsonify(result)
|
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):
|
class GetSecondCIsView(APIView):
|
||||||
url_prefix = "/ci_relations/<int:first_ci_id>/second_cis"
|
url_prefix = "/ci_relations/<int:first_ci_id>/second_cis"
|
||||||
|
|
||||||
|
@@ -7,7 +7,6 @@ from io import BytesIO
|
|||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask import session
|
|
||||||
|
|
||||||
from api.lib.cmdb.cache import AttributeCache
|
from api.lib.cmdb.cache import AttributeCache
|
||||||
from api.lib.cmdb.cache import CITypeCache
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
@@ -19,10 +18,12 @@ from api.lib.cmdb.ci_type import CITypeManager
|
|||||||
from api.lib.cmdb.ci_type import CITypeTemplateManager
|
from api.lib.cmdb.ci_type import CITypeTemplateManager
|
||||||
from api.lib.cmdb.ci_type import CITypeTriggerManager
|
from api.lib.cmdb.ci_type import CITypeTriggerManager
|
||||||
from api.lib.cmdb.ci_type import CITypeUniqueConstraintManager
|
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.perms import CIFilterPermsCRUD
|
||||||
from api.lib.cmdb.preference import PreferenceManager
|
from api.lib.cmdb.preference import PreferenceManager
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
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_required
|
||||||
from api.lib.decorator import args_validate
|
from api.lib.decorator import args_validate
|
||||||
from api.lib.perm.acl.acl import ACLManager
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
@@ -36,6 +37,8 @@ from api.lib.perm.auth import auth_with_app_token
|
|||||||
from api.lib.utils import handle_arg_list
|
from api.lib.utils import handle_arg_list
|
||||||
from api.resource import APIView
|
from api.resource import APIView
|
||||||
|
|
||||||
|
app_cli = CMDBApp()
|
||||||
|
|
||||||
|
|
||||||
class CITypeView(APIView):
|
class CITypeView(APIView):
|
||||||
url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>",
|
url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>",
|
||||||
@@ -116,7 +119,6 @@ class CITypeInheritanceView(APIView):
|
|||||||
class CITypeGroupView(APIView):
|
class CITypeGroupView(APIView):
|
||||||
url_prefix = ("/ci_types/groups",
|
url_prefix = ("/ci_types/groups",
|
||||||
"/ci_types/groups/config",
|
"/ci_types/groups/config",
|
||||||
"/ci_types/groups/order",
|
|
||||||
"/ci_types/groups/<int:gid>")
|
"/ci_types/groups/<int:gid>")
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
@@ -125,7 +127,8 @@ class CITypeGroupView(APIView):
|
|||||||
|
|
||||||
return self.jsonify(CITypeGroupManager.get(need_other, config_required))
|
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_required("name")
|
||||||
@args_validate(CITypeGroupManager.cls)
|
@args_validate(CITypeGroupManager.cls)
|
||||||
def post(self):
|
def post(self):
|
||||||
@@ -136,15 +139,6 @@ class CITypeGroupView(APIView):
|
|||||||
|
|
||||||
@args_validate(CITypeGroupManager.cls)
|
@args_validate(CITypeGroupManager.cls)
|
||||||
def put(self, gid=None):
|
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"))
|
name = request.values.get('name') or abort(400, ErrFormat.argument_value_required.format("name"))
|
||||||
type_ids = request.values.get('type_ids')
|
type_ids = request.values.get('type_ids')
|
||||||
|
|
||||||
@@ -152,7 +146,8 @@ class CITypeGroupView(APIView):
|
|||||||
|
|
||||||
return self.jsonify(gid=gid)
|
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):
|
def delete(self, gid):
|
||||||
type_ids = request.values.get("type_ids")
|
type_ids = request.values.get("type_ids")
|
||||||
CITypeGroupManager.delete(gid, type_ids)
|
CITypeGroupManager.delete(gid, type_ids)
|
||||||
@@ -160,6 +155,18 @@ class CITypeGroupView(APIView):
|
|||||||
return self.jsonify(gid=gid)
|
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):
|
class CITypeQueryView(APIView):
|
||||||
url_prefix = "/ci_types/query"
|
url_prefix = "/ci_types/query"
|
||||||
|
|
||||||
@@ -352,14 +359,16 @@ class CITypeAttributeGroupView(APIView):
|
|||||||
class CITypeTemplateView(APIView):
|
class CITypeTemplateView(APIView):
|
||||||
url_prefix = ("/ci_types/template/import", "/ci_types/template/export", "/ci_types/<int:type_id>/template/export")
|
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
|
def get(self, type_id=None): # export
|
||||||
if type_id is not None:
|
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_by_type(type_id)))
|
||||||
|
|
||||||
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template()))
|
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
|
def post(self): # import
|
||||||
tpt = request.values.get('ci_type_template') or {}
|
tpt = request.values.get('ci_type_template') or {}
|
||||||
|
|
||||||
@@ -379,7 +388,8 @@ class CITypeCanDefineComputed(APIView):
|
|||||||
class CITypeTemplateFileView(APIView):
|
class CITypeTemplateFileView(APIView):
|
||||||
url_prefix = ("/ci_types/template/import/file", "/ci_types/template/export/file")
|
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
|
def get(self): # export
|
||||||
tpt_json = CITypeTemplateManager.export_template()
|
tpt_json = CITypeTemplateManager.export_template()
|
||||||
tpt_json = dict(ci_type_template=tpt_json)
|
tpt_json = dict(ci_type_template=tpt_json)
|
||||||
@@ -394,7 +404,8 @@ class CITypeTemplateFileView(APIView):
|
|||||||
mimetype='application/json',
|
mimetype='application/json',
|
||||||
max_age=0)
|
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
|
def post(self): # import
|
||||||
f = request.files.get('file')
|
f = request.files.get('file')
|
||||||
|
|
||||||
|
@@ -11,6 +11,8 @@ from api.lib.cmdb.const import ResourceTypeEnum
|
|||||||
from api.lib.cmdb.const import RoleEnum
|
from api.lib.cmdb.const import RoleEnum
|
||||||
from api.lib.cmdb.preference import PreferenceManager
|
from api.lib.cmdb.preference import PreferenceManager
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
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_required
|
||||||
from api.lib.perm.acl.acl import ACLManager
|
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 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.lib.perm.acl.acl import role_required
|
||||||
from api.resource import APIView
|
from api.resource import APIView
|
||||||
|
|
||||||
|
app_cli = CMDBApp()
|
||||||
|
|
||||||
|
|
||||||
class GetChildrenView(APIView):
|
class GetChildrenView(APIView):
|
||||||
url_prefix = ("/ci_type_relations/<int:parent_id>/children",
|
url_prefix = ("/ci_type_relations/<int:parent_id>/children",
|
||||||
@@ -41,18 +45,22 @@ class GetParentsView(APIView):
|
|||||||
class CITypeRelationView(APIView):
|
class CITypeRelationView(APIView):
|
||||||
url_prefix = ("/ci_type_relations", "/ci_type_relations/<int:parent_id>/<int:child_id>")
|
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):
|
def get(self):
|
||||||
res = CITypeRelationManager.get()
|
res, type2attributes = CITypeRelationManager.get()
|
||||||
|
|
||||||
return self.jsonify(res)
|
return self.jsonify(relations=res, type2attributes=type2attributes)
|
||||||
|
|
||||||
@has_perm_from_args("parent_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
@has_perm_from_args("parent_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||||
@args_required("relation_type_id")
|
@args_required("relation_type_id")
|
||||||
def post(self, parent_id, child_id):
|
def post(self, parent_id, child_id):
|
||||||
relation_type_id = request.values.get("relation_type_id")
|
relation_type_id = request.values.get("relation_type_id")
|
||||||
constraint = request.values.get("constraint")
|
constraint = request.values.get("constraint")
|
||||||
ctr_id = CITypeRelationManager.add(parent_id, child_id, relation_type_id, constraint)
|
parent_attr_id = request.values.get("parent_attr_id")
|
||||||
|
child_attr_id = request.values.get("child_attr_id")
|
||||||
|
ctr_id = CITypeRelationManager.add(parent_id, child_id, relation_type_id, constraint,
|
||||||
|
parent_attr_id, child_attr_id)
|
||||||
|
|
||||||
return self.jsonify(ctr_id=ctr_id)
|
return self.jsonify(ctr_id=ctr_id)
|
||||||
|
|
||||||
@@ -66,7 +74,8 @@ class CITypeRelationView(APIView):
|
|||||||
class CITypeRelationDelete2View(APIView):
|
class CITypeRelationDelete2View(APIView):
|
||||||
url_prefix = "/ci_type_relations/<int:ctr_id>"
|
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):
|
def delete(self, ctr_id):
|
||||||
CITypeRelationManager.delete(ctr_id)
|
CITypeRelationManager.delete(ctr_id)
|
||||||
|
|
||||||
|
@@ -3,14 +3,16 @@
|
|||||||
|
|
||||||
from flask import request
|
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 CustomDashboardManager
|
||||||
from api.lib.cmdb.custom_dashboard import SystemConfigManager
|
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_required
|
||||||
from api.lib.decorator import args_validate
|
from api.lib.decorator import args_validate
|
||||||
from api.lib.perm.acl.acl import role_required
|
|
||||||
from api.resource import APIView
|
from api.resource import APIView
|
||||||
|
|
||||||
|
app_cli = CMDBApp()
|
||||||
|
|
||||||
|
|
||||||
class CustomDashboardApiView(APIView):
|
class CustomDashboardApiView(APIView):
|
||||||
url_prefix = ("/custom_dashboard", "/custom_dashboard/<int:_id>", "/custom_dashboard/batch",
|
url_prefix = ("/custom_dashboard", "/custom_dashboard/<int:_id>", "/custom_dashboard/batch",
|
||||||
@@ -19,7 +21,8 @@ class CustomDashboardApiView(APIView):
|
|||||||
def get(self):
|
def get(self):
|
||||||
return self.jsonify(CustomDashboardManager.get())
|
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)
|
@args_validate(CustomDashboardManager.cls)
|
||||||
def post(self):
|
def post(self):
|
||||||
if request.url.endswith("/preview"):
|
if request.url.endswith("/preview"):
|
||||||
@@ -32,7 +35,8 @@ class CustomDashboardApiView(APIView):
|
|||||||
|
|
||||||
return self.jsonify(res)
|
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)
|
@args_validate(CustomDashboardManager.cls)
|
||||||
def put(self, _id=None):
|
def put(self, _id=None):
|
||||||
if _id is not None:
|
if _id is not None:
|
||||||
@@ -47,7 +51,8 @@ class CustomDashboardApiView(APIView):
|
|||||||
|
|
||||||
return self.jsonify(id2options=request.values.get('id2options'))
|
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):
|
def delete(self, _id):
|
||||||
CustomDashboardManager.delete(_id)
|
CustomDashboardManager.delete(_id)
|
||||||
|
|
||||||
@@ -57,12 +62,14 @@ class CustomDashboardApiView(APIView):
|
|||||||
class SystemConfigApiView(APIView):
|
class SystemConfigApiView(APIView):
|
||||||
url_prefix = ("/system_config",)
|
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)
|
@args_required("name", value_required=True)
|
||||||
def get(self):
|
def get(self):
|
||||||
return self.jsonify(SystemConfigManager.get(request.values['name']))
|
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_validate(SystemConfigManager.cls)
|
||||||
@args_required("name", value_required=True)
|
@args_required("name", value_required=True)
|
||||||
@args_required("option", value_required=True)
|
@args_required("option", value_required=True)
|
||||||
@@ -74,7 +81,8 @@ class SystemConfigApiView(APIView):
|
|||||||
def put(self, _id=None):
|
def put(self, _id=None):
|
||||||
return self.post()
|
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")
|
@args_required("name")
|
||||||
def delete(self):
|
def delete(self):
|
||||||
CustomDashboardManager.delete(request.values['name'])
|
CustomDashboardManager.delete(request.values['name'])
|
||||||
|
@@ -5,28 +5,29 @@ import datetime
|
|||||||
|
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask import session
|
|
||||||
|
|
||||||
from api.lib.cmdb.ci import CIManager
|
from api.lib.cmdb.ci import CIManager
|
||||||
from api.lib.cmdb.const import PermEnum
|
from api.lib.cmdb.const import PermEnum
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
from api.lib.cmdb.const import RoleEnum
|
|
||||||
from api.lib.cmdb.history import AttributeHistoryManger
|
from api.lib.cmdb.history import AttributeHistoryManger
|
||||||
from api.lib.cmdb.history import CITriggerHistoryManager
|
from api.lib.cmdb.history import CITriggerHistoryManager
|
||||||
from api.lib.cmdb.history import CITypeHistoryManager
|
from api.lib.cmdb.history import CITypeHistoryManager
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
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 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
|
||||||
from api.lib.utils import get_page_size
|
from api.lib.utils import get_page_size
|
||||||
from api.resource import APIView
|
from api.resource import APIView
|
||||||
|
|
||||||
|
app_cli = CMDBApp()
|
||||||
|
|
||||||
|
|
||||||
class RecordView(APIView):
|
class RecordView(APIView):
|
||||||
url_prefix = ("/history/records/attribute", "/history/records/relation")
|
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):
|
def get(self):
|
||||||
page = get_page(request.values.get("page", 1))
|
page = get_page(request.values.get("page", 1))
|
||||||
page_size = get_page_size(request.values.get("page_size"))
|
page_size = get_page_size(request.values.get("page_size"))
|
||||||
@@ -80,18 +81,21 @@ class CIHistoryView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class CITriggerHistoryView(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)
|
@has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.READ, CIManager.get_type_name)
|
||||||
def get(self, ci_id=None):
|
def get(self, ci_id):
|
||||||
if ci_id is not None:
|
result = CITriggerHistoryManager.get_by_ci_id(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")
|
type_id = request.values.get("type_id")
|
||||||
trigger_id = request.values.get("trigger_id")
|
trigger_id = request.values.get("trigger_id")
|
||||||
operate_type = request.values.get("operate_type")
|
operate_type = request.values.get("operate_type")
|
||||||
@@ -115,7 +119,8 @@ class CITriggerHistoryView(APIView):
|
|||||||
class CITypeHistoryView(APIView):
|
class CITypeHistoryView(APIView):
|
||||||
url_prefix = "/history/ci_types"
|
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):
|
def get(self):
|
||||||
type_id = request.values.get("type_id")
|
type_id = request.values.get("type_id")
|
||||||
username = request.values.get("username")
|
username = request.values.get("username")
|
||||||
|
@@ -8,20 +8,22 @@ from flask import request
|
|||||||
from api.lib.cmdb.ci_type import CITypeManager
|
from api.lib.cmdb.ci_type import CITypeManager
|
||||||
from api.lib.cmdb.const import PermEnum
|
from api.lib.cmdb.const import PermEnum
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
from api.lib.cmdb.const import RoleEnum
|
|
||||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||||
from api.lib.cmdb.preference import PreferenceManager
|
from api.lib.cmdb.preference import PreferenceManager
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
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_required
|
||||||
from api.lib.decorator import args_validate
|
from api.lib.decorator import args_validate
|
||||||
from api.lib.perm.acl.acl import ACLManager
|
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 has_perm_from_args
|
||||||
from api.lib.perm.acl.acl import is_app_admin
|
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.perm.acl.acl import validate_permission
|
||||||
from api.lib.utils import handle_arg_list
|
from api.lib.utils import handle_arg_list
|
||||||
from api.resource import APIView
|
from api.resource import APIView
|
||||||
|
|
||||||
|
app_cli = CMDBApp()
|
||||||
|
|
||||||
|
|
||||||
class PreferenceShowCITypesView(APIView):
|
class PreferenceShowCITypesView(APIView):
|
||||||
url_prefix = ("/preference/ci_types", "/preference/ci_types2")
|
url_prefix = ("/preference/ci_types", "/preference/ci_types2")
|
||||||
@@ -97,29 +99,38 @@ class PreferenceTreeApiView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class PreferenceRelationApiView(APIView):
|
class PreferenceRelationApiView(APIView):
|
||||||
url_prefix = "/preference/relation/view"
|
url_prefix = ("/preference/relation/view", "/preference/relation/view/<int:_id>")
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
views, id2type, name2id = PreferenceManager.get_relation_view()
|
views, id2type, name2id = PreferenceManager.get_relation_view()
|
||||||
|
|
||||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
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("name")
|
||||||
@args_required("cr_ids")
|
@args_required("cr_ids")
|
||||||
@args_validate(PreferenceManager.pref_rel_cls)
|
@args_validate(PreferenceManager.pref_rel_cls)
|
||||||
def post(self):
|
def post(self):
|
||||||
name = request.values.get("name")
|
name = request.values.get("name")
|
||||||
|
is_public = request.values.get("is_public") in current_app.config.get('BOOL_TRUE')
|
||||||
cr_ids = request.values.get("cr_ids")
|
cr_ids = request.values.get("cr_ids")
|
||||||
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(name, cr_ids)
|
option = request.values.get("option") or None
|
||||||
|
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(name, cr_ids, is_public=is_public,
|
||||||
|
option=option)
|
||||||
|
|
||||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||||
|
|
||||||
@role_required(RoleEnum.CONFIG)
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||||
def put(self):
|
app_cli.op.read, app_cli.admin_name)
|
||||||
return self.post()
|
@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")
|
@args_required("name")
|
||||||
def delete(self):
|
def delete(self):
|
||||||
name = request.values.get("name")
|
name = request.values.get("name")
|
||||||
|
@@ -4,14 +4,16 @@
|
|||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
from api.lib.cmdb.const import RoleEnum
|
|
||||||
from api.lib.cmdb.relation_type import RelationTypeManager
|
from api.lib.cmdb.relation_type import RelationTypeManager
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
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_required
|
||||||
from api.lib.decorator import args_validate
|
from api.lib.decorator import args_validate
|
||||||
from api.lib.perm.acl.acl import role_required
|
|
||||||
from api.resource import APIView
|
from api.resource import APIView
|
||||||
|
|
||||||
|
app_cli = CMDBApp()
|
||||||
|
|
||||||
|
|
||||||
class RelationTypeView(APIView):
|
class RelationTypeView(APIView):
|
||||||
url_prefix = ("/relation_types", "/relation_types/<int:rel_id>")
|
url_prefix = ("/relation_types", "/relation_types/<int:rel_id>")
|
||||||
@@ -19,7 +21,8 @@ class RelationTypeView(APIView):
|
|||||||
def get(self):
|
def get(self):
|
||||||
return self.jsonify([i.to_dict() for i in RelationTypeManager.get_all()])
|
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_required("name")
|
||||||
@args_validate(RelationTypeManager.cls)
|
@args_validate(RelationTypeManager.cls)
|
||||||
def post(self):
|
def post(self):
|
||||||
@@ -28,7 +31,8 @@ class RelationTypeView(APIView):
|
|||||||
|
|
||||||
return self.jsonify(rel.to_dict())
|
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_required("name")
|
||||||
@args_validate(RelationTypeManager.cls)
|
@args_validate(RelationTypeManager.cls)
|
||||||
def put(self, rel_id):
|
def put(self, rel_id):
|
||||||
@@ -37,7 +41,8 @@ class RelationTypeView(APIView):
|
|||||||
|
|
||||||
return self.jsonify(rel.to_dict())
|
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):
|
def delete(self, rel_id):
|
||||||
RelationTypeManager.delete(rel_id)
|
RelationTypeManager.delete(rel_id)
|
||||||
|
|
||||||
|
178
cmdb-api/api/views/cmdb/topology.py
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
from flask import abort
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum
|
||||||
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
|
from api.lib.cmdb.topology import TopologyViewManager
|
||||||
|
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.resource import APIView
|
||||||
|
|
||||||
|
app_cli = CMDBApp()
|
||||||
|
|
||||||
|
|
||||||
|
class TopologyGroupView(APIView):
|
||||||
|
url_prefix = ('/topology_views/groups', '/topology_views/groups/<int:group_id>')
|
||||||
|
|
||||||
|
@args_required('name')
|
||||||
|
@args_validate(TopologyViewManager.group_cls)
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||||
|
app_cli.op.create_topology_group, app_cli.admin_name)
|
||||||
|
def post(self):
|
||||||
|
name = request.values.get('name')
|
||||||
|
order = request.values.get('order')
|
||||||
|
|
||||||
|
group = TopologyViewManager.add_group(name, order)
|
||||||
|
|
||||||
|
return self.jsonify(group.to_dict())
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||||
|
app_cli.op.update_topology_group, app_cli.admin_name)
|
||||||
|
def put(self, group_id):
|
||||||
|
name = request.values.get('name')
|
||||||
|
view_ids = request.values.get('view_ids')
|
||||||
|
group = TopologyViewManager().update_group(group_id, name, view_ids)
|
||||||
|
|
||||||
|
return self.jsonify(**group)
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||||
|
app_cli.op.delete_topology_group, app_cli.admin_name)
|
||||||
|
def delete(self, group_id):
|
||||||
|
TopologyViewManager.delete_group(group_id)
|
||||||
|
|
||||||
|
return self.jsonify(group_id=group_id)
|
||||||
|
|
||||||
|
|
||||||
|
class TopologyGroupOrderView(APIView):
|
||||||
|
url_prefix = ('/topology_views/groups/order',)
|
||||||
|
|
||||||
|
@args_required('group_ids')
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||||
|
app_cli.op.update_topology_group, app_cli.admin_name)
|
||||||
|
def post(self):
|
||||||
|
group_ids = request.values.get('group_ids')
|
||||||
|
|
||||||
|
TopologyViewManager.group_order(group_ids)
|
||||||
|
|
||||||
|
return self.jsonify(group_ids=group_ids)
|
||||||
|
|
||||||
|
def put(self):
|
||||||
|
return self.post()
|
||||||
|
|
||||||
|
|
||||||
|
class TopologyView(APIView):
|
||||||
|
url_prefix = ('/topology_views', '/topology_views/relations/ci_types/<int:type_id>', '/topology_views/<int:_id>')
|
||||||
|
|
||||||
|
def get(self, type_id=None, _id=None):
|
||||||
|
if type_id is not None:
|
||||||
|
return self.jsonify(TopologyViewManager.relation_from_ci_type(type_id))
|
||||||
|
|
||||||
|
if _id is not None:
|
||||||
|
return self.jsonify(TopologyViewManager().get_view_by_id(_id))
|
||||||
|
|
||||||
|
return self.jsonify(TopologyViewManager.get_all())
|
||||||
|
|
||||||
|
@args_required('name', 'central_node_type', 'central_node_instances', 'path', 'group_id')
|
||||||
|
@args_validate(TopologyViewManager.cls)
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||||
|
app_cli.op.create_topology_view, app_cli.admin_name)
|
||||||
|
def post(self):
|
||||||
|
name = request.values.pop('name')
|
||||||
|
group_id = request.values.pop('group_id', None)
|
||||||
|
option = request.values.pop('option', None)
|
||||||
|
order = request.values.pop('order', None)
|
||||||
|
|
||||||
|
topo_view = TopologyViewManager.add(name, group_id, option, order, **request.values)
|
||||||
|
|
||||||
|
return self.jsonify(topo_view)
|
||||||
|
|
||||||
|
@args_validate(TopologyViewManager.cls)
|
||||||
|
@has_perm_from_args("_id", ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.UPDATE, TopologyViewManager.get_name_by_id)
|
||||||
|
def put(self, _id):
|
||||||
|
topo_view = TopologyViewManager.update(_id, **request.values)
|
||||||
|
|
||||||
|
return self.jsonify(topo_view)
|
||||||
|
|
||||||
|
@has_perm_from_args("_id", ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.DELETE, TopologyViewManager.get_name_by_id)
|
||||||
|
def delete(self, _id):
|
||||||
|
TopologyViewManager.delete(_id)
|
||||||
|
|
||||||
|
return self.jsonify(code=200)
|
||||||
|
|
||||||
|
|
||||||
|
class TopologyOrderView(APIView):
|
||||||
|
url_prefix = ('/topology_views/order',)
|
||||||
|
|
||||||
|
@args_required('view_ids')
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||||
|
app_cli.op.create_topology_view, app_cli.admin_name)
|
||||||
|
def post(self):
|
||||||
|
view_ids = request.values.get('view_ids')
|
||||||
|
|
||||||
|
TopologyViewManager.group_inner_order(view_ids)
|
||||||
|
|
||||||
|
return self.jsonify(view_ids=view_ids)
|
||||||
|
|
||||||
|
def put(self):
|
||||||
|
return self.post()
|
||||||
|
|
||||||
|
|
||||||
|
class TopologyViewPreview(APIView):
|
||||||
|
url_prefix = ('/topology_views/preview', '/topology_views/<int:_id>/view')
|
||||||
|
|
||||||
|
def get(self, _id=None):
|
||||||
|
if _id is not None:
|
||||||
|
acl = ACLManager('cmdb')
|
||||||
|
resource_name = TopologyViewManager.get_name_by_id(_id)
|
||||||
|
if (not acl.has_permission(resource_name, ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.READ) and
|
||||||
|
not is_app_admin('cmdb')):
|
||||||
|
return abort(403, ErrFormat.no_permission.format(resource_name, PermEnum.READ))
|
||||||
|
|
||||||
|
return self.jsonify(TopologyViewManager().topology_view(view_id=_id))
|
||||||
|
else:
|
||||||
|
return self.jsonify(TopologyViewManager().topology_view(preview=request.values))
|
||||||
|
|
||||||
|
def post(self, _id=None):
|
||||||
|
return self.get(_id)
|
||||||
|
|
||||||
|
|
||||||
|
class TopologyViewGrantView(APIView):
|
||||||
|
url_prefix = "/topology_views/<int:view_id>/roles/<int:rid>/grant"
|
||||||
|
|
||||||
|
def post(self, view_id, rid):
|
||||||
|
perms = request.values.pop('perms', None)
|
||||||
|
|
||||||
|
view_name = TopologyViewManager.get_name_by_id(view_id) or abort(404, ErrFormat.not_found)
|
||||||
|
acl = ACLManager('cmdb')
|
||||||
|
if not acl.has_permission(view_name, ResourceTypeEnum.TOPOLOGY_VIEW,
|
||||||
|
PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||||
|
return abort(403, ErrFormat.no_permission.format(view_name, PermEnum.GRANT))
|
||||||
|
|
||||||
|
acl.grant_resource_to_role_by_rid(view_name, rid, ResourceTypeEnum.TOPOLOGY_VIEW, perms, rebuild=True)
|
||||||
|
|
||||||
|
return self.jsonify(code=200)
|
||||||
|
|
||||||
|
|
||||||
|
class TopologyViewRevokeView(APIView):
|
||||||
|
url_prefix = "/topology_views/<int:view_id>/roles/<int:rid>/revoke"
|
||||||
|
|
||||||
|
@args_required('perms')
|
||||||
|
def post(self, view_id, rid):
|
||||||
|
perms = request.values.pop('perms', None)
|
||||||
|
|
||||||
|
view_name = TopologyViewManager.get_name_by_id(view_id) or abort(404, ErrFormat.not_found)
|
||||||
|
acl = ACLManager('cmdb')
|
||||||
|
if not acl.has_permission(view_name, ResourceTypeEnum.TOPOLOGY_VIEW,
|
||||||
|
PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||||
|
return abort(403, ErrFormat.no_permission.format(view_name, PermEnum.GRANT))
|
||||||
|
|
||||||
|
acl.revoke_resource_from_role_by_rid(view_name, rid, ResourceTypeEnum.TOPOLOGY_VIEW, perms, rebuild=True)
|
||||||
|
|
||||||
|
return self.jsonify(code=200)
|
@@ -1,10 +1,10 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
import os
|
from flask import request, abort, current_app
|
||||||
|
|
||||||
from flask import request, abort, current_app, send_from_directory
|
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
import lz4.frame
|
import lz4.frame
|
||||||
|
import magic
|
||||||
|
|
||||||
|
from api.lib.common_setting.const import MIMEExtMap
|
||||||
from api.lib.common_setting.resp_format import ErrFormat
|
from api.lib.common_setting.resp_format import ErrFormat
|
||||||
from api.lib.common_setting.upload_file import allowed_file, generate_new_file_name, CommonFileCRUD
|
from api.lib.common_setting.upload_file import allowed_file, generate_new_file_name, CommonFileCRUD
|
||||||
from api.resource import APIView
|
from api.resource import APIView
|
||||||
@@ -45,32 +45,35 @@ class PostFileView(APIView):
|
|||||||
|
|
||||||
if not file:
|
if not file:
|
||||||
abort(400, ErrFormat.file_is_required)
|
abort(400, ErrFormat.file_is_required)
|
||||||
extension = file.mimetype.split('/')[-1]
|
|
||||||
if '+' in extension:
|
|
||||||
extension = file.filename.split('.')[-1]
|
|
||||||
if file.filename == '':
|
|
||||||
filename = f'.{extension}'
|
|
||||||
else:
|
|
||||||
if extension not in file.filename:
|
|
||||||
filename = file.filename + f".{extension}"
|
|
||||||
else:
|
|
||||||
filename = file.filename
|
|
||||||
|
|
||||||
if allowed_file(filename, current_app.config.get('ALLOWED_EXTENSIONS', ALLOWED_EXTENSIONS)):
|
m_type = magic.from_buffer(file.read(2048), mime=True)
|
||||||
new_filename = generate_new_file_name(filename)
|
file.seek(0)
|
||||||
new_filename = secure_filename(new_filename)
|
|
||||||
file_content = file.read()
|
|
||||||
compressed_data = lz4.frame.compress(file_content)
|
|
||||||
try:
|
|
||||||
CommonFileCRUD.add_file(
|
|
||||||
origin_name=filename,
|
|
||||||
file_name=new_filename,
|
|
||||||
binary=compressed_data,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.jsonify(file_name=new_filename)
|
if m_type == 'application/octet-stream':
|
||||||
except Exception as e:
|
m_type = file.mimetype
|
||||||
current_app.logger.error(e)
|
elif m_type == 'text/plain':
|
||||||
abort(400, ErrFormat.upload_failed.format(e))
|
# https://github.com/ahupp/python-magic/issues/193
|
||||||
|
m_type = m_type if file.mimetype == m_type else file.mimetype
|
||||||
|
|
||||||
abort(400, ErrFormat.file_type_not_allowed.format(filename))
|
extension = MIMEExtMap.get(m_type, None)
|
||||||
|
|
||||||
|
if extension is None:
|
||||||
|
abort(400, f"不支持的文件类型: {m_type}")
|
||||||
|
|
||||||
|
filename = file.filename if file.filename and file.filename.endswith(extension) else file.filename + extension
|
||||||
|
|
||||||
|
new_filename = generate_new_file_name(filename)
|
||||||
|
new_filename = secure_filename(new_filename)
|
||||||
|
file_content = file.read()
|
||||||
|
compressed_data = lz4.frame.compress(file_content)
|
||||||
|
try:
|
||||||
|
CommonFileCRUD.add_file(
|
||||||
|
origin_name=filename,
|
||||||
|
file_name=new_filename,
|
||||||
|
binary=compressed_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.jsonify(file_name=new_filename)
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(e)
|
||||||
|
abort(400, ErrFormat.upload_failed.format(e))
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
-i https://mirrors.aliyun.com/pypi/simple
|
-i https://mirrors.aliyun.com/pypi/simple
|
||||||
alembic==1.7.7
|
alembic==1.7.7
|
||||||
bs4==0.0.1
|
bs4==0.0.1
|
||||||
celery>=5.3.1
|
celery==5.3.1
|
||||||
celery-once==3.0.1
|
celery-once==3.0.1
|
||||||
click==8.1.3
|
click==8.1.3
|
||||||
elasticsearch==7.17.9
|
elasticsearch==7.17.9
|
||||||
@@ -52,4 +52,5 @@ WTForms==3.0.0
|
|||||||
shamir~=17.12.0
|
shamir~=17.12.0
|
||||||
pycryptodomex>=3.19.0
|
pycryptodomex>=3.19.0
|
||||||
colorama>=0.4.6
|
colorama>=0.4.6
|
||||||
lz4>=4.3.2
|
lz4>=4.3.2
|
||||||
|
python-magic==0.4.27
|
||||||
|
@@ -20,10 +20,16 @@ DEBUG_TB_INTERCEPT_REDIRECTS = False
|
|||||||
|
|
||||||
ERROR_CODES = [400, 401, 403, 404, 405, 500, 502]
|
ERROR_CODES = [400, 401, 403, 404, 405, 500, 502]
|
||||||
|
|
||||||
|
MYSQL_USER = env.str('MYSQL_USER', default='cmdb')
|
||||||
|
MYSQL_PASSWORD = env.str('MYSQL_PASSWORD', default='123456')
|
||||||
|
MYSQL_HOST = env.str('MYSQL_HOST', default='127.0.0.1')
|
||||||
|
MYSQL_PORT = env.int('MYSQL_PORT', default=3306)
|
||||||
|
MYSQL_DATABASE = env.str('MYSQL_DATABASE', default='cmdb')
|
||||||
# # database
|
# # database
|
||||||
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
|
SQLALCHEMY_DATABASE_URI = f'mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@' \
|
||||||
|
f'{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}?charset=utf8'
|
||||||
SQLALCHEMY_BINDS = {
|
SQLALCHEMY_BINDS = {
|
||||||
'user': 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
|
'user': SQLALCHEMY_DATABASE_URI
|
||||||
}
|
}
|
||||||
SQLALCHEMY_ECHO = False
|
SQLALCHEMY_ECHO = False
|
||||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
|
@@ -39,12 +39,14 @@
|
|||||||
"md5": "^2.2.1",
|
"md5": "^2.2.1",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"relation-graph": "^1.1.0",
|
"relation-graph": "^2.1.42",
|
||||||
"snabbdom": "^3.5.1",
|
"snabbdom": "^3.5.1",
|
||||||
"sortablejs": "1.9.0",
|
"sortablejs": "1.9.0",
|
||||||
|
"style-resources-loader": "^1.5.0",
|
||||||
"viser-vue": "^2.4.8",
|
"viser-vue": "^2.4.8",
|
||||||
"vue": "2.6.11",
|
"vue": "2.6.11",
|
||||||
"vue-clipboard2": "^0.3.3",
|
"vue-clipboard2": "^0.3.3",
|
||||||
|
"vue-cli-plugin-style-resources-loader": "^0.1.5",
|
||||||
"vue-codemirror": "^4.0.6",
|
"vue-codemirror": "^4.0.6",
|
||||||
"vue-cropper": "^0.6.2",
|
"vue-cropper": "^0.6.2",
|
||||||
"vue-grid-layout": "2.3.12",
|
"vue-grid-layout": "2.3.12",
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 3857903 */
|
font-family: "iconfont"; /* Project id 3857903 */
|
||||||
src: url('iconfont.woff2?t=1702544951995') format('woff2'),
|
src: url('iconfont.woff2?t=1716896994700') format('woff2'),
|
||||||
url('iconfont.woff?t=1702544951995') format('woff'),
|
url('iconfont.woff?t=1716896994700') format('woff'),
|
||||||
url('iconfont.ttf?t=1702544951995') format('truetype');
|
url('iconfont.ttf?t=1716896994700') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@@ -13,6 +13,434 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ops-topology_view:before {
|
||||||
|
content: "\e92b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-host_analysis:before {
|
||||||
|
content: "\e92a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-Group427319324:before {
|
||||||
|
content: "\e929";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-native:before {
|
||||||
|
content: "\e928";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-filter2:before {
|
||||||
|
content: "\e927";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-cmdb-data_companies-selected:before {
|
||||||
|
content: "\e601";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-cmdb-data_companies:before {
|
||||||
|
content: "\e926";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-threshold_value:before {
|
||||||
|
content: "\e921";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-disposition:before {
|
||||||
|
content: "\e922";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-automatic_discovery:before {
|
||||||
|
content: "\e923";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-grouping_list:before {
|
||||||
|
content: "\e924";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-node_list:before {
|
||||||
|
content: "\e925";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-general_view:before {
|
||||||
|
content: "\e920";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-network_topology:before {
|
||||||
|
content: "\e91b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-node_management:before {
|
||||||
|
content: "\e91c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-alarm_policy:before {
|
||||||
|
content: "\e91d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-alarm:before {
|
||||||
|
content: "\e91e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-healing:before {
|
||||||
|
content: "\e91f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-data_acquisition:before {
|
||||||
|
content: "\e8d4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-analysis:before {
|
||||||
|
content: "\e91a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-index:before {
|
||||||
|
content: "\e89b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-user_defined:before {
|
||||||
|
content: "\e867";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-database:before {
|
||||||
|
content: "\e861";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-common:before {
|
||||||
|
content: "\e865";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-edit:before {
|
||||||
|
content: "\e866";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-empower:before {
|
||||||
|
content: "\e863";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-share:before {
|
||||||
|
content: "\e864";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-export:before {
|
||||||
|
content: "\e862";
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-veops-import1:before {
|
||||||
|
content: "\e860";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-ip:before {
|
||||||
|
content: "\e807";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-director:before {
|
||||||
|
content: "\e803";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-host:before {
|
||||||
|
content: "\e804";
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-cmdb-log1:before {
|
||||||
|
content: "\e802";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-add:before {
|
||||||
|
content: "\e7ff";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-down:before {
|
||||||
|
content: "\e7fc";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-up:before {
|
||||||
|
content: "\e7fd";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-unfold:before {
|
||||||
|
content: "\e7f9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-stretch:before {
|
||||||
|
content: "\e7f8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-data_comaparison2:before {
|
||||||
|
content: "\e7a1";
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-data_comaparison1:before {
|
||||||
|
content: "\e7f7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-monitor-online1:before {
|
||||||
|
content: "\e7a0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
.OAUTH2:before {
|
||||||
content: "\e8d8";
|
content: "\e8d8";
|
||||||
}
|
}
|
||||||
@@ -29,11 +457,7 @@
|
|||||||
content: "\e8d5";
|
content: "\e8d5";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-setting-auth-selected:before {
|
.itsm-knowledge2:before {
|
||||||
content: "\e8d4";
|
|
||||||
}
|
|
||||||
|
|
||||||
.a-itsm-knowledge2:before {
|
|
||||||
content: "\e8d2";
|
content: "\e8d2";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,10 +685,6 @@
|
|||||||
content: "\e89c";
|
content: "\e89c";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-setting-duty-selected:before {
|
|
||||||
content: "\e89b";
|
|
||||||
}
|
|
||||||
|
|
||||||
.datainsight-sequential:before {
|
.datainsight-sequential:before {
|
||||||
content: "\e899";
|
content: "\e899";
|
||||||
}
|
}
|
||||||
@@ -429,38 +849,6 @@
|
|||||||
content: "\e870";
|
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 {
|
.ops-itsm-servicecatalog:before {
|
||||||
content: "\e868";
|
content: "\e868";
|
||||||
}
|
}
|
||||||
@@ -833,26 +1221,10 @@
|
|||||||
content: "\e816";
|
content: "\e816";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-cmdb-batch-selected:before {
|
|
||||||
content: "\e803";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-cmdb-batch:before {
|
.ops-cmdb-batch:before {
|
||||||
content: "\e80a";
|
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 {
|
.ops-cmdb-preference:before {
|
||||||
content: "\e7fa";
|
content: "\e7fa";
|
||||||
}
|
}
|
||||||
@@ -861,22 +1233,10 @@
|
|||||||
content: "\e7fb";
|
content: "\e7fb";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-cmdb-tree-selected:before {
|
|
||||||
content: "\e7fc";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-cmdb-relation-selected:before {
|
|
||||||
content: "\e7fd";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-cmdb-adc:before {
|
.ops-cmdb-adc:before {
|
||||||
content: "\e7fe";
|
content: "\e7fe";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-cmdb-search-selected:before {
|
|
||||||
content: "\e7ff";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-cmdb-relation:before {
|
.ops-cmdb-relation:before {
|
||||||
content: "\e800";
|
content: "\e800";
|
||||||
}
|
}
|
||||||
@@ -885,14 +1245,6 @@
|
|||||||
content: "\e801";
|
content: "\e801";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-cmdb-citype-selected:before {
|
|
||||||
content: "\e802";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-cmdb-dashboard-selected:before {
|
|
||||||
content: "\e804";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-cmdb-citype:before {
|
.ops-cmdb-citype:before {
|
||||||
content: "\e805";
|
content: "\e805";
|
||||||
}
|
}
|
||||||
@@ -901,10 +1253,6 @@
|
|||||||
content: "\e806";
|
content: "\e806";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-cmdb-screen-selected:before {
|
|
||||||
content: "\e807";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-cmdb-resource:before {
|
.ops-cmdb-resource:before {
|
||||||
content: "\e808";
|
content: "\e808";
|
||||||
}
|
}
|
||||||
@@ -1253,14 +1601,6 @@
|
|||||||
content: "\e7a6";
|
content: "\e7a6";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-setting-role-selected:before {
|
|
||||||
content: "\e7a0";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-setting-group-selected:before {
|
|
||||||
content: "\e7a1";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-setting-role:before {
|
.ops-setting-role:before {
|
||||||
content: "\e7a2";
|
content: "\e7a2";
|
||||||
}
|
}
|
||||||
@@ -1701,18 +2041,10 @@
|
|||||||
content: "\e738";
|
content: "\e738";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-setting-notice-email-selected-copy:before {
|
|
||||||
content: "\e889";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-setting-notice:before {
|
.ops-setting-notice:before {
|
||||||
content: "\e72f";
|
content: "\e72f";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-setting-notice-selected:before {
|
|
||||||
content: "\e730";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-setting-notice-email-selected:before {
|
.ops-setting-notice-email-selected:before {
|
||||||
content: "\e731";
|
content: "\e731";
|
||||||
}
|
}
|
||||||
@@ -1737,10 +2069,6 @@
|
|||||||
content: "\e736";
|
content: "\e736";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-setting-companyStructure-selected:before {
|
|
||||||
content: "\e72b";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-setting-companyStructure:before {
|
.ops-setting-companyStructure:before {
|
||||||
content: "\e72c";
|
content: "\e72c";
|
||||||
}
|
}
|
||||||
@@ -1749,10 +2077,6 @@
|
|||||||
content: "\e72d";
|
content: "\e72d";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-setting-companyInfo-selected:before {
|
|
||||||
content: "\e72e";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-email:before {
|
.ops-email:before {
|
||||||
content: "\e61a";
|
content: "\e61a";
|
||||||
}
|
}
|
||||||
@@ -2793,14 +3117,6 @@
|
|||||||
content: "\e600";
|
content: "\e600";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-dag-dashboard-selected:before {
|
|
||||||
content: "\e601";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-dag-applet-selected:before {
|
|
||||||
content: "\e602";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-dag-applet:before {
|
.ops-dag-applet:before {
|
||||||
content: "\e603";
|
content: "\e603";
|
||||||
}
|
}
|
||||||
@@ -2809,62 +3125,26 @@
|
|||||||
content: "\e604";
|
content: "\e604";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-dag-terminal-selected:before {
|
|
||||||
content: "\e605";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-dag-cron:before {
|
.ops-dag-cron:before {
|
||||||
content: "\e606";
|
content: "\e606";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-dag-cron-selected:before {
|
|
||||||
content: "\e608";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-dag-history:before {
|
.ops-dag-history:before {
|
||||||
content: "\e609";
|
content: "\e609";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-dag-history-selected:before {
|
|
||||||
content: "\e60a";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-dag-dags-selected:before {
|
|
||||||
content: "\e60c";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-dag-dagreview:before {
|
.ops-dag-dagreview:before {
|
||||||
content: "\e60d";
|
content: "\e60d";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-dag-dagreview-selected:before {
|
|
||||||
content: "\e60e";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-dag-panel:before {
|
.ops-dag-panel:before {
|
||||||
content: "\e60f";
|
content: "\e60f";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-dag-panel-selected:before {
|
|
||||||
content: "\e615";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ops-dag-variables:before {
|
.ops-dag-variables:before {
|
||||||
content: "\e616";
|
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 {
|
.ops-dag-dags:before {
|
||||||
content: "\e60b";
|
content: "\e60b";
|
||||||
}
|
}
|
||||||
|
@@ -30,9 +30,9 @@ export function getAuthDataEnable() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function testLDAP(test_type, data) {
|
export function testLDAP(data) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/common-setting/v1/auth_config/LDAP/test?test_type=${test_type}`,
|
url: `/common-setting/v1/auth_config/LDAP/test`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data,
|
data,
|
||||||
})
|
})
|
||||||
|
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 |
@@ -9,7 +9,7 @@
|
|||||||
:bodyStyle="{ padding: '24px 12px' }"
|
:bodyStyle="{ padding: '24px 12px' }"
|
||||||
:placement="placement"
|
:placement="placement"
|
||||||
>
|
>
|
||||||
<ResourceSearch :fromCronJob="true" @copySuccess="copySuccess" />
|
<ResourceSearch ref="resourceSearch" :fromCronJob="true" :type="type" :typeId="typeId" @copySuccess="copySuccess" />
|
||||||
</CustomDrawer>
|
</CustomDrawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -23,6 +23,14 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: 'right',
|
default: 'right',
|
||||||
},
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'resourceSearch'
|
||||||
|
},
|
||||||
|
typeId: {
|
||||||
|
type: Number,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@@ -178,7 +178,7 @@
|
|||||||
<div v-else :style="{ width: '175px' }"></div>
|
<div v-else :style="{ width: '175px' }"></div>
|
||||||
<template v-if="!disabled">
|
<template v-if="!disabled">
|
||||||
<a-tooltip :title="$t('copy')">
|
<a-tooltip :title="$t('copy')">
|
||||||
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a>
|
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="veops-copy"/></a>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip :title="$t('delete')">
|
<a-tooltip :title="$t('delete')">
|
||||||
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a>
|
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a>
|
||||||
|
@@ -17,25 +17,29 @@ export default {
|
|||||||
getPropertyIcon(attr) {
|
getPropertyIcon(attr) {
|
||||||
switch (attr.value_type) {
|
switch (attr.value_type) {
|
||||||
case '0':
|
case '0':
|
||||||
return 'icon-xianxing-shishu'
|
return 'duose-shishu'
|
||||||
case '1':
|
case '1':
|
||||||
return 'icon-xianxing-fudianshu'
|
return 'duose-fudianshu'
|
||||||
case '2':
|
case '2':
|
||||||
if (attr.is_password) {
|
if (attr.is_password) {
|
||||||
return 'icon-xianxing-password'
|
return 'duose-password'
|
||||||
}
|
}
|
||||||
if (attr.is_link) {
|
if (attr.is_link) {
|
||||||
return 'icon-xianxing-link'
|
return 'duose-link'
|
||||||
}
|
}
|
||||||
return 'icon-xianxing-wenben'
|
return 'duose-wenben'
|
||||||
case '3':
|
case '3':
|
||||||
return 'icon-xianxing-datetime'
|
return 'duose-datetime'
|
||||||
case '4':
|
case '4':
|
||||||
return 'icon-xianxing-date'
|
return 'duose-date'
|
||||||
case '5':
|
case '5':
|
||||||
return 'icon-xianxing-time'
|
return 'duose-time'
|
||||||
case '6':
|
case '6':
|
||||||
return 'icon-xianxing-json'
|
return 'duose-json'
|
||||||
|
case '7':
|
||||||
|
return 'duose-password'
|
||||||
|
case '8':
|
||||||
|
return 'duose-link'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@@ -69,12 +69,11 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
@import '~@/style/static.less';
|
|
||||||
|
|
||||||
.custom-drawer-close {
|
.custom-drawer-close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: #custom_colors[color_1];
|
background: @primary-color;
|
||||||
color: white;
|
color: white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
@@ -759,6 +759,52 @@ export const multicolorIconList = [
|
|||||||
value: 'caise-redis',
|
value: 'caise-redis',
|
||||||
label: 'redis'
|
label: 'redis'
|
||||||
}]
|
}]
|
||||||
|
}, {
|
||||||
|
value: 'cloud',
|
||||||
|
label: '云',
|
||||||
|
list: [{
|
||||||
|
value: 'AWS',
|
||||||
|
label: 'AWS'
|
||||||
|
}, {
|
||||||
|
value: 'Azure',
|
||||||
|
label: 'Azure'
|
||||||
|
}, {
|
||||||
|
value: 'Google_Cloud_Platform',
|
||||||
|
label: 'Google Cloud Platform'
|
||||||
|
}, {
|
||||||
|
value: 'Alibaba_Cloud',
|
||||||
|
label: '阿里云'
|
||||||
|
}, {
|
||||||
|
value: 'Huawei_Cloud',
|
||||||
|
label: '华为云'
|
||||||
|
}, {
|
||||||
|
value: 'Tencent_Cloud',
|
||||||
|
label: '腾讯云'
|
||||||
|
}, {
|
||||||
|
value: 'UCloud',
|
||||||
|
label: 'UCloud'
|
||||||
|
}, {
|
||||||
|
value: 'Ctyun',
|
||||||
|
label: '天翼云'
|
||||||
|
}, {
|
||||||
|
value: 'ECloud',
|
||||||
|
label: '移动云'
|
||||||
|
}, {
|
||||||
|
value: 'JDCloud',
|
||||||
|
label: '京东云'
|
||||||
|
}, {
|
||||||
|
value: 'Bytecloud',
|
||||||
|
label: '字节云'
|
||||||
|
}, {
|
||||||
|
value: 'OpenStack',
|
||||||
|
label: 'OpenStack'
|
||||||
|
}, {
|
||||||
|
value: 'ZStack',
|
||||||
|
label: 'ZStack'
|
||||||
|
}, {
|
||||||
|
value: 'Nutanix',
|
||||||
|
label: 'Nutanix'
|
||||||
|
}]
|
||||||
}, {
|
}, {
|
||||||
value: 'system',
|
value: 'system',
|
||||||
label: '操作系统',
|
label: '操作系统',
|
||||||
@@ -976,17 +1022,14 @@ export const multicolorIconList = [
|
|||||||
value: 'caise-tomcat',
|
value: 'caise-tomcat',
|
||||||
label: 'Tomcat'
|
label: 'Tomcat'
|
||||||
}, {
|
}, {
|
||||||
value: 'caise-aliyun',
|
value: 'caise-VPC',
|
||||||
label: '阿里云'
|
label: 'VPC'
|
||||||
}, {
|
}, {
|
||||||
value: 'caise-tengxunyun',
|
value: 'caise-CDN',
|
||||||
label: '腾讯云'
|
label: 'CDN'
|
||||||
}, {
|
}, {
|
||||||
value: 'caise-huaweiyun',
|
value: 'caise-OOS',
|
||||||
label: '华为云'
|
label: '对象存储'
|
||||||
}, {
|
|
||||||
value: 'caise-aws',
|
|
||||||
label: 'AWS'
|
|
||||||
}]
|
}]
|
||||||
}, {
|
}, {
|
||||||
value: 'data',
|
value: 'data',
|
||||||
|
@@ -230,7 +230,6 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
@import '~@/style/static.less';
|
|
||||||
.employee-transfer {
|
.employee-transfer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
.vue-treeselect__multi-value-item-container {
|
.vue-treeselect__multi-value-item-container {
|
||||||
@@ -263,7 +262,6 @@ export default {
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@import '~@/style/static.less';
|
|
||||||
.employee-transfer {
|
.employee-transfer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -300,14 +298,14 @@ export default {
|
|||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background-color: #custom_colors[color_2];
|
background-color: @primary-color_5;
|
||||||
color: #custom_colors[color_1];
|
color: @primary-color;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #custom_colors[color_1];
|
background-color: @primary-color;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-layout-sider
|
<a-layout-sider
|
||||||
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null]"
|
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null]"
|
||||||
width="200px"
|
width="220px"
|
||||||
:collapsible="collapsible"
|
:collapsible="collapsible"
|
||||||
v-model="collapsed"
|
v-model="collapsed"
|
||||||
:trigger="null"
|
:trigger="null"
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
@select="onSelect"
|
@select="onSelect"
|
||||||
style="padding: 16px 0px;"
|
style="padding: 16px 0px;"
|
||||||
></s-menu>
|
></s-menu>
|
||||||
|
<!-- <OpsDocs :collapsed="collapsed" /> -->
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -22,10 +23,13 @@
|
|||||||
import Logo from '@/components/tools/Logo'
|
import Logo from '@/components/tools/Logo'
|
||||||
import SMenu from './index'
|
import SMenu from './index'
|
||||||
import { mixin, mixinDevice } from '@/utils/mixin'
|
import { mixin, mixinDevice } from '@/utils/mixin'
|
||||||
|
// import OpsDocs from '@/modules/docs/index.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SideMenu',
|
name: 'SideMenu',
|
||||||
components: { Logo, SMenu },
|
components: { Logo, SMenu,
|
||||||
|
// OpsDocs
|
||||||
|
},
|
||||||
mixins: [mixin, mixinDevice],
|
mixins: [mixin, mixinDevice],
|
||||||
props: {
|
props: {
|
||||||
mode: {
|
mode: {
|
||||||
|
@@ -182,8 +182,8 @@ export default {
|
|||||||
<tag {...{ props, attrs }}>
|
<tag {...{ props, attrs }}>
|
||||||
{this.renderIcon({ icon: menu.meta.icon, customIcon: menu.meta.customIcon, name: menu.meta.name, typeId: menu.meta.typeId, routeName: menu.name, selectedIcon: menu.meta.selectedIcon, })}
|
{this.renderIcon({ icon: menu.meta.icon, customIcon: menu.meta.customIcon, name: menu.meta.name, typeId: menu.meta.typeId, routeName: menu.name, selectedIcon: menu.meta.selectedIcon, })}
|
||||||
<span>
|
<span>
|
||||||
<span class={this.renderI18n(menu.meta.title).length > 10 ? 'scroll' : ''}>{this.renderI18n(menu.meta.title)}</span>
|
<span style={menu.meta.style} class={this.renderI18n(menu.meta.title).length > 10 ? 'scroll' : ''}>{this.renderI18n(menu.meta.title)}</span>
|
||||||
{isShowDot &&
|
{isShowDot && !menu.meta.disabled &&
|
||||||
<a-popover
|
<a-popover
|
||||||
overlayClassName="custom-menu-extra-submenu"
|
overlayClassName="custom-menu-extra-submenu"
|
||||||
placement="rightTop"
|
placement="rightTop"
|
||||||
|
@@ -1,121 +1,121 @@
|
|||||||
<template>
|
<template>
|
||||||
<vxe-table v-bind="$attrs" v-on="new$listeners" ref="xTable">
|
<vxe-table v-bind="$attrs" v-on="new$listeners" ref="xTable">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<slot name="empty">
|
<slot name="empty">
|
||||||
<div :style="{ paddingTop: '10px' }">
|
<div :style="{ paddingTop: '10px' }">
|
||||||
<img :style="{ width: '100px', height: '90px' }" :src="require('@/assets/data_empty.png')" />
|
<img :style="{ width: '140px', height: '90px' }" :src="require('@/assets/data_empty.png')" />
|
||||||
<div>{{ $t('noData') }}</div>
|
<div>{{ $t('noData') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</slot>
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
<template #loading>
|
<template #loading>
|
||||||
<slot name="loading"></slot>
|
<slot name="loading"></slot>
|
||||||
</template>
|
</template>
|
||||||
</vxe-table>
|
</vxe-table>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
// 该组件使用方法与vxe-table一致,但调用它的方法时,需先调用getVxetableRef()获取到vxe-table实体
|
// 该组件使用方法与vxe-table一致,但调用它的方法时,需先调用getVxetableRef()获取到vxe-table实体
|
||||||
export default {
|
export default {
|
||||||
name: 'OpsTable',
|
name: 'OpsTable',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// isShifting: false,
|
// isShifting: false,
|
||||||
// lastIndex: -1,
|
// lastIndex: -1,
|
||||||
lastSelected: [],
|
lastSelected: [],
|
||||||
currentSelected: [],
|
currentSelected: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
new$listeners() {
|
new$listeners() {
|
||||||
if (!Object.keys(this.$listeners).length) {
|
if (!Object.keys(this.$listeners).length) {
|
||||||
return this.$listeners
|
return this.$listeners
|
||||||
}
|
}
|
||||||
return Object.assign(this.$listeners, {
|
return Object.assign(this.$listeners, {
|
||||||
// 在这里覆盖原有的change事件
|
// 在这里覆盖原有的change事件
|
||||||
// 'checkbox-change': this.selectChangeEvent,
|
// 'checkbox-change': this.selectChangeEvent,
|
||||||
'checkbox-range-change': this.checkboxRangeChange,
|
'checkbox-range-change': this.checkboxRangeChange,
|
||||||
'checkbox-range-start': this.checkboxRangeStart,
|
'checkbox-range-start': this.checkboxRangeStart,
|
||||||
'checkbox-range-end': this.checkboxRangeEnd,
|
'checkbox-range-end': this.checkboxRangeEnd,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// window.onkeydown = (e) => {
|
// window.onkeydown = (e) => {
|
||||||
// if (e.key === 'Shift') {
|
// if (e.key === 'Shift') {
|
||||||
// this.isShifting = true
|
// this.isShifting = true
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// window.onkeyup = (e) => {
|
// window.onkeyup = (e) => {
|
||||||
// if (e.key === 'Shift') {
|
// if (e.key === 'Shift') {
|
||||||
// this.isShifting = false
|
// this.isShifting = false
|
||||||
// this.lastIndex = -1
|
// this.lastIndex = -1
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
// window.onkeydown = ''
|
// window.onkeydown = ''
|
||||||
// window.onkeyup = ''
|
// window.onkeyup = ''
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getVxetableRef() {
|
getVxetableRef() {
|
||||||
return this.$refs.xTable
|
return this.$refs.xTable
|
||||||
},
|
},
|
||||||
// selectChangeEvent(e) {
|
// selectChangeEvent(e) {
|
||||||
// const xTable = this.$refs.xTable
|
// const xTable = this.$refs.xTable
|
||||||
// const { lastIndex } = this
|
// const { lastIndex } = this
|
||||||
// const currentIndex = e.rowIndex
|
// const currentIndex = e.rowIndex
|
||||||
// const { tableData } = xTable.getTableData()
|
// const { tableData } = xTable.getTableData()
|
||||||
// if (lastIndex > -1 && this.isShifting) {
|
// if (lastIndex > -1 && this.isShifting) {
|
||||||
// let start = lastIndex
|
// let start = lastIndex
|
||||||
// let end = currentIndex
|
// let end = currentIndex
|
||||||
// if (lastIndex > currentIndex) {
|
// if (lastIndex > currentIndex) {
|
||||||
// start = currentIndex
|
// start = currentIndex
|
||||||
// end = lastIndex
|
// end = lastIndex
|
||||||
// }
|
// }
|
||||||
// const rangeData = tableData.slice(start, end + 1)
|
// const rangeData = tableData.slice(start, end + 1)
|
||||||
// xTable.setCheckboxRow(rangeData, true)
|
// xTable.setCheckboxRow(rangeData, true)
|
||||||
// }
|
// }
|
||||||
// this.lastIndex = currentIndex
|
// this.lastIndex = currentIndex
|
||||||
// this.$emit('checkbox-change', { ...e, records: xTable.getCheckboxRecords() })
|
// this.$emit('checkbox-change', { ...e, records: xTable.getCheckboxRecords() })
|
||||||
// },
|
// },
|
||||||
checkboxRangeStart(e) {
|
checkboxRangeStart(e) {
|
||||||
const xTable = this.$refs.xTable
|
const xTable = this.$refs.xTable
|
||||||
const lastSelected = xTable.getCheckboxRecords()
|
const lastSelected = xTable.getCheckboxRecords()
|
||||||
const selectedReserve = xTable.getCheckboxReserveRecords()
|
const selectedReserve = xTable.getCheckboxReserveRecords()
|
||||||
this.lastSelected = [...lastSelected, ...selectedReserve]
|
this.lastSelected = [...lastSelected, ...selectedReserve]
|
||||||
this.$emit('checkbox-range-start', e)
|
this.$emit('checkbox-range-start', e)
|
||||||
},
|
},
|
||||||
checkboxRangeChange(e) {
|
checkboxRangeChange(e) {
|
||||||
const xTable = this.$refs.xTable
|
const xTable = this.$refs.xTable
|
||||||
xTable.setCheckboxRow(this.lastSelected, true)
|
xTable.setCheckboxRow(this.lastSelected, true)
|
||||||
this.currentSelected = e.records
|
this.currentSelected = e.records
|
||||||
// this.lastSelected = [...new Set([...this.lastSelected, ...e.records])]
|
// this.lastSelected = [...new Set([...this.lastSelected, ...e.records])]
|
||||||
this.$emit('checkbox-range-change', {
|
this.$emit('checkbox-range-change', {
|
||||||
...e,
|
...e,
|
||||||
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
|
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
checkboxRangeEnd(e) {
|
checkboxRangeEnd(e) {
|
||||||
const xTable = this.$refs.xTable
|
const xTable = this.$refs.xTable
|
||||||
const isAllSelected = this.currentSelected.every((item) => {
|
const isAllSelected = this.currentSelected.every((item) => {
|
||||||
const _idx = this.lastSelected.findIndex((ele) => _.isEqual(ele, item))
|
const _idx = this.lastSelected.findIndex((ele) => _.isEqual(ele, item))
|
||||||
return _idx > -1
|
return _idx > -1
|
||||||
})
|
})
|
||||||
if (isAllSelected) {
|
if (isAllSelected) {
|
||||||
xTable.setCheckboxRow(this.currentSelected, false)
|
xTable.setCheckboxRow(this.currentSelected, false)
|
||||||
}
|
}
|
||||||
this.currentSelected = []
|
this.currentSelected = []
|
||||||
this.lastSelected = []
|
this.lastSelected = []
|
||||||
this.$emit('checkbox-range-end', {
|
this.$emit('checkbox-range-end', {
|
||||||
...e,
|
...e,
|
||||||
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
|
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less"></style>
|
<style lang="less"></style>
|
||||||
|
@@ -146,7 +146,6 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@import '~@/style/static.less';
|
|
||||||
.regex-select {
|
.regex-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
@@ -162,7 +161,7 @@ export default {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&-selected,
|
&-selected,
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #custom_colors[color_1];
|
color: @primary-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,7 +179,7 @@ export default {
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
border-left: 2px solid #custom_colors[color_1];
|
border-left: 2px solid @primary-color;
|
||||||
padding-left: 6px;
|
padding-left: 6px;
|
||||||
margin-left: -6px;
|
margin-left: -6px;
|
||||||
}
|
}
|
||||||
|
@@ -126,7 +126,6 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@import '~@/style/static.less';
|
|
||||||
.role-transfer {
|
.role-transfer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -186,14 +185,14 @@ export default {
|
|||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background-color: #custom_colors[color_2];
|
background-color: @primary-color_5;
|
||||||
color: #custom_colors[color_1];
|
color: @primary-color;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #custom_colors[color_1];
|
background-color: @primary-color;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -60,7 +60,6 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@import '~@/style/static.less';
|
|
||||||
.sidebar-list-item {
|
.sidebar-list-item {
|
||||||
.ops_popover_item();
|
.ops_popover_item();
|
||||||
margin: 2px 0;
|
margin: 2px 0;
|
||||||
@@ -94,7 +93,7 @@ export default {
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
.sidebar-list-item.sidebar-list-item-selected::before {
|
.sidebar-list-item.sidebar-list-item-selected::before {
|
||||||
background-color: #custom_colors[color_1];
|
background-color: @primary-color;
|
||||||
}
|
}
|
||||||
.sidebar-list-item-dotline {
|
.sidebar-list-item-dotline {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
|
@@ -64,7 +64,7 @@ export default {
|
|||||||
},
|
},
|
||||||
triggerColor: {
|
triggerColor: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '#f0f2f5',
|
default: '#f7f8fa',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@@ -35,7 +35,7 @@ export default {
|
|||||||
},
|
},
|
||||||
triggerColor: {
|
triggerColor: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '#F0F5FF',
|
default: '#f7f8fa',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -52,22 +52,20 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
||||||
.two-column-layout {
|
.two-column-layout {
|
||||||
margin-bottom: -24px;
|
margin-bottom: -24px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
.two-column-layout-sidebar {
|
.two-column-layout-sidebar {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 15px 7px;
|
|
||||||
border-radius: 15px;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background-color: #fff;
|
|
||||||
}
|
}
|
||||||
.two-column-layout-main {
|
.two-column-layout-main {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border-radius: 15px;
|
border-radius: @border-radius-box;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -4,34 +4,22 @@
|
|||||||
@click="jumpTo"
|
@click="jumpTo"
|
||||||
v-if="showTitle && !collapsed"
|
v-if="showTitle && !collapsed"
|
||||||
style="width: 100%; height: 100%; cursor: pointer"
|
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
|
<img
|
||||||
@click="jumpTo"
|
@click="jumpTo"
|
||||||
v-else
|
v-else
|
||||||
style="width: 32px; height: 32px; margin-left: 24px; cursor: pointer"
|
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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// import LogoSvg from '@/assets/logo.svg?inline'
|
|
||||||
import { mapState } from 'vuex'
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Logo',
|
name: 'Logo',
|
||||||
components: {
|
components: {},
|
||||||
// LogoSvg,
|
computed: {},
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState({
|
|
||||||
file_name: (state) => state.logo.file_name,
|
|
||||||
small_file_name: (state) => state.logo.small_file_name,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@@ -1,15 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="top-menu" v-if="routes.length > 2">
|
<div class="top-menu" v-if="routes.length > 2">
|
||||||
<!-- <a-menu v-model="current" mode="horizontal">
|
|
||||||
<a-menu-item :key="route.name" v-for="route in routes.slice(0, routes.length - 1)">
|
|
||||||
<router-link :to="{ name: route.name }">{{ route.meta.title }}</router-link>
|
|
||||||
</a-menu-item>
|
|
||||||
</a-menu>-->
|
|
||||||
<span
|
<span
|
||||||
:class="current === route.name ? 'top-menu-selected' : ''"
|
:class="current === route.name ? 'top-menu-selected' : ''"
|
||||||
v-for="route in defaultShowRoutes"
|
v-for="route in defaultShowRoutes"
|
||||||
:key="route.name"
|
:key="route.name"
|
||||||
@click="() => handleClick(route)"
|
@click="() => handleClick(route)"
|
||||||
|
:title="$t(route.meta.title)"
|
||||||
>
|
>
|
||||||
{{ route.meta.title }}
|
{{ route.meta.title }}
|
||||||
</span>
|
</span>
|
||||||
@@ -43,6 +39,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
import { gridSvg, top_agent, top_acl } from '@/core/icons'
|
import { gridSvg, top_agent, top_acl } from '@/core/icons'
|
||||||
|
import { getPreference } from '@/modules/cmdb/api/preference'
|
||||||
export default {
|
export default {
|
||||||
name: 'TopMenu',
|
name: 'TopMenu',
|
||||||
components: { gridSvg, top_agent, top_acl },
|
components: { gridSvg, top_agent, top_acl },
|
||||||
@@ -78,10 +75,20 @@ export default {
|
|||||||
this.current = this.$route.matched[0].name
|
this.current = this.$route.matched[0].name
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleClick(route) {
|
async handleClick(route) {
|
||||||
this.visible = false
|
this.visible = false
|
||||||
if (route.name !== this.current) {
|
if (route.name !== this.current) {
|
||||||
this.$router.push(route.redirect)
|
if (route.name === 'cmdb') {
|
||||||
|
const preference = await getPreference()
|
||||||
|
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
|
||||||
|
if (lastTypeId && preference.type_ids.some((item) => item === Number(lastTypeId))) {
|
||||||
|
this.$router.push(`/cmdb/instances/types/${lastTypeId}`)
|
||||||
|
} else {
|
||||||
|
this.$router.push('/cmdb/dashboard')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.$router.push(route.redirect)
|
||||||
|
}
|
||||||
// this.current = route.name
|
// this.current = route.name
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -91,15 +98,6 @@ export default {
|
|||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
@import '../../style/static.less';
|
@import '../../style/static.less';
|
||||||
// .top-menu {
|
|
||||||
// display: inline-block;
|
|
||||||
// }
|
|
||||||
// .ant-menu-horizontal {
|
|
||||||
// border-bottom: 0 !important;
|
|
||||||
// }
|
|
||||||
// .ant-menu-horizontal > .ant-menu-item {
|
|
||||||
// border-bottom: 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
.top-menu {
|
.top-menu {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -110,33 +108,29 @@ export default {
|
|||||||
line-height: @layout-header-icon-height;
|
line-height: @layout-header-icon-height;
|
||||||
border-radius: 4px !important;
|
border-radius: 4px !important;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: flex-end;
|
align-items: center;
|
||||||
}
|
}
|
||||||
> span {
|
> span {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
margin: 0 5px;
|
margin: 0 5px;
|
||||||
border-radius: 4px;
|
|
||||||
color: @layout-header-font-color;
|
color: @layout-header-font-color;
|
||||||
height: @layout-header-height;
|
height: @layout-header-height;
|
||||||
display: inline-flex;
|
line-height: @layout-header-line-height;
|
||||||
align-items: center;
|
display: inline-block;
|
||||||
&:hover {
|
|
||||||
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
|
|
||||||
color: @layout-header-font-selected-color;
|
|
||||||
border-radius: 3px 3px 0px 0px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
> span:hover,
|
||||||
.top-menu-selected {
|
.top-menu-selected {
|
||||||
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
|
font-weight: bold;
|
||||||
color: @layout-header-font-selected-color;
|
color: @layout-header-font-selected-color;
|
||||||
border-radius: 3px 3px 0px 0px;
|
}
|
||||||
border-bottom: 3px solid @layout-header-font-selected-color;
|
> span::before {
|
||||||
&:hover {
|
display: block;
|
||||||
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
|
content: attr(title);
|
||||||
color: @layout-header-font-selected-color;
|
font-weight: bold;
|
||||||
border-radius: 3px 3px 0px 0px;
|
height: 0;
|
||||||
}
|
overflow: hidden;
|
||||||
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -96,10 +96,9 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
@import '~@/style/static.less';
|
|
||||||
.color {
|
.color {
|
||||||
color: #custom_colors[color_1];
|
color: @primary-color;
|
||||||
background-color: #custom_colors[color_2];
|
background-color: @primary-color_5;
|
||||||
}
|
}
|
||||||
.custom-user {
|
.custom-user {
|
||||||
.custom-user-item {
|
.custom-user-item {
|
||||||
@@ -118,7 +117,7 @@ export default {
|
|||||||
.locale {
|
.locale {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #custom_colors[color_1];
|
color: @primary-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -9,25 +9,11 @@
|
|||||||
import gridSvg from '@/assets/icons/grid.svg?inline'
|
import gridSvg from '@/assets/icons/grid.svg?inline'
|
||||||
import top_agent from '@/assets/icons/top_agent.svg?inline'
|
import top_agent from '@/assets/icons/top_agent.svg?inline'
|
||||||
import top_acl from '@/assets/icons/top_acl.svg?inline'
|
import top_acl from '@/assets/icons/top_acl.svg?inline'
|
||||||
import ops_default_show from '@/assets/icons/ops-default_show.svg?inline'
|
|
||||||
import ops_is_choice from '@/assets/icons/ops-is_choice.svg?inline'
|
|
||||||
import ops_is_index from '@/assets/icons/ops-is_index.svg?inline'
|
|
||||||
import ops_is_link from '@/assets/icons/ops-is_link.svg?inline'
|
|
||||||
import ops_is_password from '@/assets/icons/ops-is_password.svg?inline'
|
|
||||||
import ops_is_sortable from '@/assets/icons/ops-is_sortable.svg?inline'
|
|
||||||
import ops_is_unique from '@/assets/icons/ops-is_unique.svg?inline'
|
|
||||||
import ops_move_icon from '@/assets/icons/ops-move-icon.svg?inline'
|
import ops_move_icon from '@/assets/icons/ops-move-icon.svg?inline'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
gridSvg,
|
gridSvg,
|
||||||
top_agent,
|
top_agent,
|
||||||
top_acl,
|
top_acl,
|
||||||
ops_default_show,
|
|
||||||
ops_is_choice,
|
|
||||||
ops_is_index,
|
|
||||||
ops_is_link,
|
|
||||||
ops_is_password,
|
|
||||||
ops_is_sortable,
|
|
||||||
ops_is_unique,
|
|
||||||
ops_move_icon
|
ops_move_icon
|
||||||
}
|
}
|
||||||
|
14
cmdb-ui/src/directive/highlight/highlight.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import './highlight.less'
|
||||||
|
|
||||||
|
const highlight = (el, binding) => {
|
||||||
|
if (binding.value.value) {
|
||||||
|
let testValue = `${binding.value.value}`
|
||||||
|
if (['(', ')', '$'].includes(testValue)) {
|
||||||
|
testValue = `\\${testValue}`
|
||||||
|
}
|
||||||
|
const regex = new RegExp(`(${testValue})`, 'gi')
|
||||||
|
el.innerHTML = el.innerText.replace(regex, `<span class='${binding.value.class ?? 'ops-text-highlight'}'>$1</span>`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default highlight
|
5
cmdb-ui/src/directive/highlight/highlight.less
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
|
.ops-text-highlight {
|
||||||
|
background-color: @primary-color_3;
|
||||||
|
}
|
12
cmdb-ui/src/directive/highlight/index.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import hightlight from './highlight'
|
||||||
|
|
||||||
|
const install = function (Vue) {
|
||||||
|
Vue.directive('hightlight', hightlight)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.Vue) {
|
||||||
|
window.hightlight = hightlight
|
||||||
|
Vue.use(install); // eslint-disable-line
|
||||||
|
}
|
||||||
|
hightlight.install = install
|
||||||
|
export default hightlight
|
13
cmdb-ui/src/directive/waves/index.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import waves from './waves'
|
||||||
|
|
||||||
|
const install = function (Vue) {
|
||||||
|
Vue.directive('waves', waves)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.Vue) {
|
||||||
|
window.waves = waves
|
||||||
|
Vue.use(install); // eslint-disable-line
|
||||||
|
}
|
||||||
|
|
||||||
|
waves.install = install
|
||||||
|
export default waves
|
26
cmdb-ui/src/directive/waves/waves.css
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
.waves-ripple {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.15);
|
||||||
|
background-clip: padding-box;
|
||||||
|
pointer-events: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-transform: scale(0);
|
||||||
|
-ms-transform: scale(0);
|
||||||
|
transform: scale(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waves-ripple.z-active {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transform: scale(2);
|
||||||
|
-ms-transform: scale(2);
|
||||||
|
transform: scale(2);
|
||||||
|
-webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
|
||||||
|
transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
|
||||||
|
transition: opacity 1.2s ease-out, transform 0.6s ease-out;
|
||||||
|
transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
|
||||||
|
}
|
72
cmdb-ui/src/directive/waves/waves.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import './waves.css'
|
||||||
|
|
||||||
|
const context = '@@wavesContext'
|
||||||
|
|
||||||
|
function handleClick(el, binding) {
|
||||||
|
function handle(e) {
|
||||||
|
const customOpts = Object.assign({}, binding.value)
|
||||||
|
const opts = Object.assign({
|
||||||
|
ele: el, // 波纹作用元素
|
||||||
|
type: 'hit', // hit 点击位置扩散 center中心点扩展
|
||||||
|
color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
|
||||||
|
},
|
||||||
|
customOpts
|
||||||
|
)
|
||||||
|
const target = opts.ele
|
||||||
|
if (target) {
|
||||||
|
target.style.position = 'relative'
|
||||||
|
target.style.overflow = 'hidden'
|
||||||
|
const rect = target.getBoundingClientRect()
|
||||||
|
let ripple = target.querySelector('.waves-ripple')
|
||||||
|
if (!ripple) {
|
||||||
|
ripple = document.createElement('span')
|
||||||
|
ripple.className = 'waves-ripple'
|
||||||
|
ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
|
||||||
|
target.appendChild(ripple)
|
||||||
|
} else {
|
||||||
|
ripple.className = 'waves-ripple'
|
||||||
|
}
|
||||||
|
switch (opts.type) {
|
||||||
|
case 'center':
|
||||||
|
ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
|
||||||
|
ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
ripple.style.top =
|
||||||
|
(e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop ||
|
||||||
|
document.body.scrollTop) + 'px'
|
||||||
|
ripple.style.left =
|
||||||
|
(e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft ||
|
||||||
|
document.body.scrollLeft) + 'px'
|
||||||
|
}
|
||||||
|
ripple.style.backgroundColor = opts.color
|
||||||
|
ripple.className = 'waves-ripple z-active'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!el[context]) {
|
||||||
|
el[context] = {
|
||||||
|
removeHandle: handle
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
el[context].removeHandle = handle
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
bind(el, binding) {
|
||||||
|
el.addEventListener('click', handleClick(el, binding), false)
|
||||||
|
},
|
||||||
|
update(el, binding) {
|
||||||
|
el.removeEventListener('click', el[context].removeHandle, false)
|
||||||
|
el.addEventListener('click', handleClick(el, binding), false)
|
||||||
|
},
|
||||||
|
unbind(el) {
|
||||||
|
el.removeEventListener('click', el[context].removeHandle, false)
|
||||||
|
el[context] = null
|
||||||
|
delete el[context]
|
||||||
|
}
|
||||||
|
}
|
@@ -31,7 +31,6 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
store.dispatch("loadAllUsers")
|
store.dispatch("loadAllUsers")
|
||||||
store.dispatch("loadAllEmployees")
|
store.dispatch("loadAllEmployees")
|
||||||
store.dispatch("loadAllDepartments")
|
store.dispatch("loadAllDepartments")
|
||||||
store.dispatch("getCompanyInfo")
|
|
||||||
store.dispatch('GenerateRoutes', { roles }).then(() => {
|
store.dispatch('GenerateRoutes', { roles }).then(() => {
|
||||||
router.addRoutes(store.getters.appRoutes)
|
router.addRoutes(store.getters.appRoutes)
|
||||||
const redirect = decodeURIComponent(from.query.redirect || to.path)
|
const redirect = decodeURIComponent(from.query.redirect || to.path)
|
||||||
|
@@ -87,21 +87,21 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
// 动态主路由
|
// 动态主路由
|
||||||
mainMenu: state => state.routes.appRoutes,
|
mainMenu: (state) => state.routes.appRoutes,
|
||||||
}),
|
}),
|
||||||
contentPaddingLeft() {
|
contentPaddingLeft() {
|
||||||
if (!this.fixSidebar || this.isMobile()) {
|
if (!this.fixSidebar || this.isMobile()) {
|
||||||
return '0'
|
return '0'
|
||||||
}
|
}
|
||||||
if (this.sidebarOpened) {
|
if (this.sidebarOpened) {
|
||||||
return '200px'
|
return '220px'
|
||||||
}
|
}
|
||||||
return '80px'
|
return '80px'
|
||||||
},
|
},
|
||||||
sideBarMenu() {
|
sideBarMenu() {
|
||||||
const sideMenus = this.mainMenu.filter(item => this.$route.path.startsWith(item.path))
|
const sideMenus = this.mainMenu.filter((item) => this.$route.path.startsWith(item.path))
|
||||||
if (sideMenus.length > 1) {
|
if (sideMenus.length > 1) {
|
||||||
return sideMenus.find(item => item.path !== '/').children
|
return sideMenus.find((item) => item.path !== '/').children
|
||||||
} else {
|
} else {
|
||||||
return sideMenus[0].children
|
return sideMenus[0].children
|
||||||
}
|
}
|
||||||
@@ -110,6 +110,9 @@ export default {
|
|||||||
provide() {
|
provide() {
|
||||||
return {
|
return {
|
||||||
reloadBoard: this.reload,
|
reloadBoard: this.reload,
|
||||||
|
collapsed: () => {
|
||||||
|
return this.collapsed
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -146,15 +149,6 @@ export default {
|
|||||||
this.alive = true
|
this.alive = true
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
paddingCalc() {
|
|
||||||
let left = ''
|
|
||||||
if (this.sidebarOpened) {
|
|
||||||
left = this.isDesktop() ? '200px' : '80px'
|
|
||||||
} else {
|
|
||||||
left = (this.isMobile() && '0') || (this.fixSidebar && '80px') || '0'
|
|
||||||
}
|
|
||||||
return left
|
|
||||||
},
|
|
||||||
menuSelect() {
|
menuSelect() {
|
||||||
if (!this.isDesktop()) {
|
if (!this.isDesktop()) {
|
||||||
this.collapsed = false
|
this.collapsed = false
|
||||||
|
@@ -233,8 +233,9 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
||||||
.acl-history {
|
.acl-history {
|
||||||
border-radius: 15px;
|
border-radius: @border-radius-box;
|
||||||
height: calc(100vh - 64px);
|
height: calc(100vh - 64px);
|
||||||
margin-bottom: -24px;
|
margin-bottom: -24px;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
|
@@ -290,7 +290,6 @@ export default {
|
|||||||
const str = ` 【 ${key} : -> ${newVal} 】 `
|
const str = ` 【 ${key} : -> ${newVal} 】 `
|
||||||
item.description += str
|
item.description += str
|
||||||
} else {
|
} else {
|
||||||
const str = ` 【 ${key} : ${oldVal} -> ${newVal} 】 `
|
|
||||||
item.description += ` 【 ${key} : ${oldVal} -> ${newVal} 】 `
|
item.description += ` 【 ${key} : ${oldVal} -> ${newVal} 】 `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -241,7 +241,6 @@ export default {
|
|||||||
const str = ` 【 ${key} : -> ${newVal} 】 `
|
const str = ` 【 ${key} : -> ${newVal} 】 `
|
||||||
item.changeDescription += str
|
item.changeDescription += str
|
||||||
} else {
|
} else {
|
||||||
const str = ` 【 ${key} : ${oldVal} -> ${newVal} 】 `
|
|
||||||
item.changeDescription += ` 【 ${key} : ${oldVal} -> ${newVal} 】 `
|
item.changeDescription += ` 【 ${key} : ${oldVal} -> ${newVal} 】 `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|