mirror of
https://github.com/veops/cmdb.git
synced 2025-09-06 05:17:03 +08:00
Compare commits
65 Commits
dependabot
...
2.3.9
Author | SHA1 | Date | |
---|---|---|---|
|
100a889cb8 | ||
|
b093569453 | ||
|
3919dfdfbb | ||
|
ef85ba2542 | ||
|
3d5c2ec5bc | ||
|
c143d6ae5b | ||
|
10b273ee81 | ||
|
855cb91b31 | ||
|
ffae57642c | ||
|
9ed1108c20 | ||
|
72b2f8b6de | ||
|
20f3e917fe | ||
|
c430515377 | ||
|
a6e2aca281 | ||
|
18313b7bd1 | ||
|
dbc44a8ad6 | ||
|
f143e30cf5 | ||
|
4beece5a6e | ||
|
920295d955 | ||
|
6eb8ae1dac | ||
|
f1f86ce25a | ||
|
090007487d | ||
|
aa98a304c1 | ||
|
fe22e363b4 | ||
|
2d2fb6e1d6 | ||
|
9894c77300 | ||
|
4ea947f741 | ||
|
e5ab2c2573 | ||
|
5581fa8d0f | ||
|
76c939fe5c | ||
|
6e9ce08e2c | ||
|
092a8b9b92 | ||
|
b45fd0cbbb | ||
|
5320ecfd62 | ||
|
1d253d7ad3 | ||
|
73d53f0440 | ||
|
d4a37af183 | ||
|
e03849b054 | ||
|
faed3fe6a2 | ||
|
21c9d9accd | ||
|
a06599ce33 | ||
|
2b69217136 | ||
|
03a3b8b169 | ||
|
cd319421d5 | ||
|
c918d54ea5 | ||
|
cf0ad7bad6 | ||
|
e0c8263542 | ||
|
275e8b15f3 | ||
|
6ff942c107 | ||
|
d3c87ee500 | ||
|
a4f65e7fc6 | ||
|
10527bf9b8 | ||
|
0414121c27 | ||
|
edde467c87 | ||
|
d525e1ec54 | ||
|
91c49b690f | ||
|
05453becf9 | ||
|
5fe27f8678 | ||
|
0924b8846f | ||
|
62a669159a | ||
|
2933bf1efa | ||
|
981f8b0145 | ||
|
213bda671c | ||
|
837aabfe77 | ||
|
cc599d414a |
26
README.md
26
README.md
@@ -73,20 +73,34 @@
|
||||
## 安装
|
||||
|
||||
### Docker 一键快速构建
|
||||
- 进入主目录(先安装 docker 环境, 注意要clone整个项目)
|
||||
|
||||
> 方法一
|
||||
- 第一步: 先安装 docker 环境, 以及docker-compose
|
||||
- 第二步: 拷贝项目
|
||||
```shell
|
||||
git clone https://github.com/veops/cmdb.git
|
||||
```
|
||||
- 第三步:进入主目录,执行:
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
- 浏览器打开: [http://127.0.0.1:8000](http://127.0.0.1:8000)
|
||||
- username: demo 或者 admin
|
||||
- password: 123456
|
||||
> 方法二, 该方法适用于linux系统
|
||||
- 第一步: 先安装 docker 环境, 以及docker-compose
|
||||
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
|
||||
```shell
|
||||
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/master/install.sh
|
||||
sh install.sh install
|
||||
```
|
||||
|
||||
### [本地开发环境搭建](docs/local.md)
|
||||
|
||||
### [Makefile 安装](docs/makefile.md)
|
||||
|
||||
## 验证
|
||||
- 浏览器打开: [http://127.0.0.1:8000](http://127.0.0.1:8000)
|
||||
- username: demo 或者 admin
|
||||
- password: 123456
|
||||
|
||||
|
||||
---
|
||||
|
||||
_**欢迎关注公众号(维易科技OneOps),关注后可加入微信群,进行产品和技术交流。**_
|
||||
|
@@ -62,6 +62,7 @@ alembic = "==1.7.7"
|
||||
hvac = "==2.0.0"
|
||||
colorama = ">=0.4.6"
|
||||
pycryptodomex = ">=3.19.0"
|
||||
lz4 = ">=4.3.2"
|
||||
|
||||
[dev-packages]
|
||||
# Testing
|
||||
|
@@ -19,7 +19,8 @@ from flask.json.provider import DefaultJSONProvider
|
||||
import api.views.entry
|
||||
from api.extensions import (bcrypt, cache, celery, cors, db, es, login_manager, migrate, rd)
|
||||
from api.extensions import inner_secrets
|
||||
from api.flask_cas import CAS
|
||||
from api.lib.perm.authentication.cas import CAS
|
||||
from api.lib.perm.authentication.oauth2 import OAuth2
|
||||
from api.lib.secrets.secrets import InnerKVManger
|
||||
from api.models.acl import User
|
||||
|
||||
@@ -96,6 +97,7 @@ def create_app(config_object="settings"):
|
||||
register_shell_context(app)
|
||||
register_commands(app)
|
||||
CAS(app)
|
||||
OAuth2(app)
|
||||
app.wsgi_app = ReverseProxy(app.wsgi_app)
|
||||
configure_upload_dir(app)
|
||||
|
||||
@@ -192,10 +194,11 @@ def configure_logger(app):
|
||||
app.logger.addHandler(handler)
|
||||
|
||||
log_file = app.config['LOG_PATH']
|
||||
file_handler = RotatingFileHandler(log_file,
|
||||
maxBytes=2 ** 30,
|
||||
backupCount=7)
|
||||
file_handler.setLevel(getattr(logging, app.config['LOG_LEVEL']))
|
||||
file_handler.setFormatter(formatter)
|
||||
app.logger.addHandler(file_handler)
|
||||
if log_file and log_file != "/dev/stdout":
|
||||
file_handler = RotatingFileHandler(log_file,
|
||||
maxBytes=2 ** 30,
|
||||
backupCount=7)
|
||||
file_handler.setLevel(getattr(logging, app.config['LOG_LEVEL']))
|
||||
file_handler.setFormatter(formatter)
|
||||
app.logger.addHandler(file_handler)
|
||||
app.logger.setLevel(getattr(logging, app.config['LOG_LEVEL']))
|
||||
|
@@ -19,6 +19,7 @@ from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
@@ -49,12 +50,17 @@ def cmdb_init_cache():
|
||||
|
||||
ci_relations = CIRelation.get_by(to_dict=False)
|
||||
relations = dict()
|
||||
relations2 = dict()
|
||||
for cr in ci_relations:
|
||||
relations.setdefault(cr.first_ci_id, {}).update({cr.second_ci_id: cr.second_ci.type_id})
|
||||
if cr.ancestor_ids:
|
||||
relations2.setdefault(cr.ancestor_ids, {}).update({cr.second_ci_id: cr.second_ci.type_id})
|
||||
for i in relations:
|
||||
relations[i] = json.dumps(relations[i])
|
||||
if relations:
|
||||
rd.create_or_update(relations, REDIS_PREFIX_CI_RELATION)
|
||||
if relations2:
|
||||
rd.create_or_update(relations2, REDIS_PREFIX_CI_RELATION2)
|
||||
|
||||
es = None
|
||||
if current_app.config.get("USE_ES"):
|
||||
@@ -111,7 +117,15 @@ def cmdb_init_acl():
|
||||
# 1. add resource type
|
||||
for resource_type in ResourceTypeEnum.all():
|
||||
try:
|
||||
ResourceTypeCRUD.add(app_id, resource_type, '', PermEnum.all())
|
||||
perms = PermEnum.all()
|
||||
if resource_type in (ResourceTypeEnum.CI_FILTER, ResourceTypeEnum.PAGE):
|
||||
perms = [PermEnum.READ]
|
||||
elif resource_type == ResourceTypeEnum.CI_TYPE_RELATION:
|
||||
perms = [PermEnum.ADD, PermEnum.DELETE, PermEnum.GRANT]
|
||||
elif resource_type == ResourceTypeEnum.RELATION_VIEW:
|
||||
perms = [PermEnum.READ, PermEnum.UPDATE, PermEnum.DELETE, PermEnum.GRANT]
|
||||
|
||||
ResourceTypeCRUD.add(app_id, resource_type, '', perms)
|
||||
except AbortException:
|
||||
pass
|
||||
|
||||
|
@@ -299,3 +299,20 @@ def common_check_new_columns():
|
||||
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()
|
||||
@with_appcontext
|
||||
def common_sync_file_to_db():
|
||||
from api.lib.common_setting.upload_file import CommonFileCRUD
|
||||
CommonFileCRUD.sync_file_to_db()
|
||||
|
||||
|
||||
@click.command()
|
||||
@with_appcontext
|
||||
@click.option('--value', type=click.INT, default=-1)
|
||||
def set_auth_auto_redirect_enable(value):
|
||||
if value < 0:
|
||||
return
|
||||
from api.lib.common_setting.common_data import CommonDataCRUD
|
||||
CommonDataCRUD.set_auth_auto_redirect_enable(value)
|
||||
|
@@ -189,7 +189,8 @@ class AttributeManager(object):
|
||||
return attr
|
||||
|
||||
def get_attribute(self, key, choice_web_hook_parse=True, choice_other_parse=True):
|
||||
attr = AttributeCache.get(key).to_dict()
|
||||
attr = AttributeCache.get(key) or dict()
|
||||
attr = attr and attr.to_dict()
|
||||
if attr.get("is_choice"):
|
||||
attr["choice_value"] = self.get_choice_values(
|
||||
attr["id"],
|
||||
|
@@ -3,11 +3,6 @@ import datetime
|
||||
import json
|
||||
import os
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
from sqlalchemy import func
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.auto_discovery.const import ClOUD_MAP
|
||||
from api.lib.cmdb.cache import CITypeAttributeCache
|
||||
@@ -28,6 +23,10 @@ from api.lib.utils import AESCrypto
|
||||
from api.models.cmdb import AutoDiscoveryCI
|
||||
from api.models.cmdb import AutoDiscoveryCIType
|
||||
from api.models.cmdb import AutoDiscoveryRule
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
from sqlalchemy import func
|
||||
|
||||
PWD = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
@@ -251,20 +250,17 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
current_app.logger.warning(e)
|
||||
return abort(400, str(e))
|
||||
|
||||
def _can_add(self, **kwargs):
|
||||
self.cls.get_by(type_id=kwargs['type_id'], adr_id=kwargs.get('adr_id') or None) and abort(
|
||||
400, ErrFormat.ad_duplicate)
|
||||
|
||||
# self.__valid_exec_target(kwargs.get('agent_id'), kwargs.get('query_expr'))
|
||||
@staticmethod
|
||||
def _can_add(**kwargs):
|
||||
|
||||
if kwargs.get('adr_id'):
|
||||
adr = AutoDiscoveryRule.get_by_id(kwargs['adr_id']) or abort(
|
||||
AutoDiscoveryRule.get_by_id(kwargs['adr_id']) or abort(
|
||||
404, ErrFormat.adr_not_found.format("id={}".format(kwargs['adr_id'])))
|
||||
if not adr.is_plugin:
|
||||
other = self.cls.get_by(adr_id=adr.id, first=True, to_dict=False)
|
||||
if other:
|
||||
ci_type = CITypeCache.get(other.type_id)
|
||||
return abort(400, ErrFormat.adr_default_ref_once.format(ci_type.alias))
|
||||
# if not adr.is_plugin:
|
||||
# other = self.cls.get_by(adr_id=adr.id, first=True, to_dict=False)
|
||||
# if other:
|
||||
# ci_type = CITypeCache.get(other.type_id)
|
||||
# return abort(400, ErrFormat.adr_default_ref_once.format(ci_type.alias))
|
||||
|
||||
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
|
||||
kwargs = check_plugin_script(**kwargs)
|
||||
|
@@ -182,6 +182,9 @@ class CIManager(object):
|
||||
need_children and res.update(CIRelationManager.get_children(ci_id, ret_key=ret_key)) # one floor
|
||||
|
||||
ci_type = CITypeCache.get(ci.type_id)
|
||||
if not ci_type:
|
||||
return res
|
||||
|
||||
res["ci_type"] = ci_type.name
|
||||
|
||||
fields = CITypeAttributeManager.get_attr_names_by_type_id(ci.type_id) if not fields else fields
|
||||
@@ -392,8 +395,9 @@ class CIManager(object):
|
||||
k not in ci_type_attrs_alias and _no_attribute_policy == ExistPolicy.REJECT):
|
||||
return abort(400, ErrFormat.attribute_not_found.format(k))
|
||||
|
||||
if limit_attrs and ci_type_attrs_name.get(k) not in limit_attrs and (
|
||||
ci_type_attrs_alias.get(k) not in limit_attrs):
|
||||
_attr_name = ((ci_type_attrs_name.get(k) and ci_type_attrs_name[k].name) or
|
||||
(ci_type_attrs_alias.get(k) and ci_type_attrs_alias[k].name))
|
||||
if limit_attrs and _attr_name not in limit_attrs:
|
||||
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
|
||||
|
||||
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name or k in ci_type_attrs_alias}
|
||||
@@ -511,18 +515,20 @@ class CIManager(object):
|
||||
ci_delete_trigger.apply_async(args=(trigger, OperateType.DELETE, ci_dict), queue=CMDB_QUEUE)
|
||||
|
||||
attrs = CITypeAttribute.get_by(type_id=ci.type_id, to_dict=False)
|
||||
attr_names = set([AttributeCache.get(attr.attr_id).name for attr in attrs])
|
||||
for attr_name in attr_names:
|
||||
value_table = TableMap(attr_name=attr_name).table
|
||||
attrs = [AttributeCache.get(attr.attr_id) for attr in attrs]
|
||||
for attr in attrs:
|
||||
value_table = TableMap(attr=attr).table
|
||||
for item in value_table.get_by(ci_id=ci_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
for item in CIRelation.get_by(first_ci_id=ci_id, to_dict=False):
|
||||
ci_relation_delete.apply_async(args=(item.first_ci_id, item.second_ci_id), queue=CMDB_QUEUE)
|
||||
ci_relation_delete.apply_async(
|
||||
args=(item.first_ci_id, item.second_ci_id, item.ancestor_ids), queue=CMDB_QUEUE)
|
||||
item.delete(commit=False)
|
||||
|
||||
for item in CIRelation.get_by(second_ci_id=ci_id, to_dict=False):
|
||||
ci_relation_delete.apply_async(args=(item.first_ci_id, item.second_ci_id), queue=CMDB_QUEUE)
|
||||
ci_relation_delete.apply_async(
|
||||
args=(item.first_ci_id, item.second_ci_id, item.ancestor_ids), queue=CMDB_QUEUE)
|
||||
item.delete(commit=False)
|
||||
|
||||
ad_ci = AutoDiscoveryCI.get_by(ci_id=ci_id, to_dict=False, first=True)
|
||||
@@ -886,12 +892,14 @@ class CIRelationManager(object):
|
||||
|
||||
@classmethod
|
||||
def get_ancestor_ids(cls, ci_ids, level=1):
|
||||
for _ in range(level):
|
||||
cis = db.session.query(CIRelation.first_ci_id).filter(
|
||||
level2ids = dict()
|
||||
for _level in range(1, level + 1):
|
||||
cis = db.session.query(CIRelation.first_ci_id, CIRelation.ancestor_ids).filter(
|
||||
CIRelation.second_ci_id.in_(ci_ids)).filter(CIRelation.deleted.is_(False))
|
||||
ci_ids = [i.first_ci_id for i in cis]
|
||||
level2ids[_level + 1] = {int(i.ancestor_ids.split(',')[-1]) for i in cis if i.ancestor_ids}
|
||||
|
||||
return ci_ids
|
||||
return ci_ids, level2ids
|
||||
|
||||
@staticmethod
|
||||
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
|
||||
@@ -918,13 +926,14 @@ class CIRelationManager(object):
|
||||
return abort(400, ErrFormat.relation_constraint.format("1-N"))
|
||||
|
||||
@classmethod
|
||||
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None):
|
||||
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, ancestor_ids=None):
|
||||
|
||||
first_ci = CIManager.confirm_ci_existed(first_ci_id)
|
||||
second_ci = CIManager.confirm_ci_existed(second_ci_id)
|
||||
|
||||
existed = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
ancestor_ids=ancestor_ids,
|
||||
to_dict=False,
|
||||
first=True)
|
||||
if existed is not None:
|
||||
@@ -960,11 +969,12 @@ class CIRelationManager(object):
|
||||
|
||||
existed = CIRelation.create(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
relation_type_id=relation_type_id)
|
||||
relation_type_id=relation_type_id,
|
||||
ancestor_ids=ancestor_ids)
|
||||
|
||||
CIRelationHistoryManager().add(existed, OperateType.ADD)
|
||||
|
||||
ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id), queue=CMDB_QUEUE)
|
||||
ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
|
||||
|
||||
if more is not None:
|
||||
existed.upadte(more=more)
|
||||
@@ -988,53 +998,56 @@ class CIRelationManager(object):
|
||||
his_manager = CIRelationHistoryManager()
|
||||
his_manager.add(cr, operate_type=OperateType.DELETE)
|
||||
|
||||
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id), queue=CMDB_QUEUE)
|
||||
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
||||
|
||||
return cr_id
|
||||
|
||||
@classmethod
|
||||
def delete_2(cls, first_ci_id, second_ci_id):
|
||||
def delete_2(cls, first_ci_id, second_ci_id, ancestor_ids=None):
|
||||
cr = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
ancestor_ids=ancestor_ids,
|
||||
to_dict=False,
|
||||
first=True)
|
||||
|
||||
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id), queue=CMDB_QUEUE)
|
||||
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
|
||||
|
||||
return cls.delete(cr.id)
|
||||
return cr and cls.delete(cr.id)
|
||||
|
||||
@classmethod
|
||||
def batch_update(cls, ci_ids, parents, children):
|
||||
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
|
||||
"""
|
||||
only for many to one
|
||||
:param ci_ids:
|
||||
:param parents:
|
||||
:param children:
|
||||
:param ancestor_ids:
|
||||
:return:
|
||||
"""
|
||||
if isinstance(parents, list):
|
||||
for parent_id in parents:
|
||||
for ci_id in ci_ids:
|
||||
cls.add(parent_id, ci_id)
|
||||
cls.add(parent_id, ci_id, ancestor_ids=ancestor_ids)
|
||||
|
||||
if isinstance(children, list):
|
||||
for child_id in children:
|
||||
for ci_id in ci_ids:
|
||||
cls.add(ci_id, child_id)
|
||||
cls.add(ci_id, child_id, ancestor_ids=ancestor_ids)
|
||||
|
||||
@classmethod
|
||||
def batch_delete(cls, ci_ids, parents):
|
||||
def batch_delete(cls, ci_ids, parents, ancestor_ids=None):
|
||||
"""
|
||||
only for many to one
|
||||
:param ci_ids:
|
||||
:param parents:
|
||||
:param ancestor_ids:
|
||||
:return:
|
||||
"""
|
||||
|
||||
if isinstance(parents, list):
|
||||
for parent_id in parents:
|
||||
for ci_id in ci_ids:
|
||||
cls.delete_2(parent_id, ci_id)
|
||||
cls.delete_2(parent_id, ci_id, ancestor_ids=ancestor_ids)
|
||||
|
||||
|
||||
class CITriggerManager(object):
|
||||
|
@@ -5,8 +5,10 @@ import copy
|
||||
import toposort
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import session
|
||||
from flask_login import current_user
|
||||
from toposort import toposort_flatten
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.attribute import AttributeManager
|
||||
@@ -22,6 +24,7 @@ from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
from api.lib.cmdb.history import CITypeHistoryManager
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.relation_type import RelationTypeManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.value import AttributeValueManager
|
||||
@@ -75,12 +78,13 @@ class CITypeManager(object):
|
||||
def get_ci_types(type_name=None):
|
||||
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("CIType")])
|
||||
resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.CI_TYPE)])
|
||||
|
||||
ci_types = CIType.get_by() if type_name is None else CIType.get_by_like(name=type_name)
|
||||
res = list()
|
||||
for type_dict in ci_types:
|
||||
type_dict["unique_key"] = AttributeCache.get(type_dict["unique_id"]).name
|
||||
attr = AttributeCache.get(type_dict["unique_id"])
|
||||
type_dict["unique_key"] = attr and attr.name
|
||||
if resources is None or type_dict['name'] in resources:
|
||||
res.append(type_dict)
|
||||
|
||||
@@ -113,6 +117,9 @@ class CITypeManager(object):
|
||||
@classmethod
|
||||
@kwargs_required("name")
|
||||
def add(cls, **kwargs):
|
||||
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)}:
|
||||
return abort(403, ErrFormat.no_permission2)
|
||||
|
||||
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)
|
||||
@@ -131,7 +138,11 @@ class CITypeManager(object):
|
||||
CITypeCache.clean(ci_type.name)
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
ACLManager().add_resource(ci_type.name, ResourceTypeEnum.CI)
|
||||
try:
|
||||
ACLManager().add_resource(ci_type.name, ResourceTypeEnum.CI)
|
||||
except BadRequest:
|
||||
pass
|
||||
|
||||
ACLManager().grant_resource_to_role(ci_type.name,
|
||||
RoleEnum.CMDB_READ_ALL,
|
||||
ResourceTypeEnum.CI,
|
||||
@@ -243,7 +254,6 @@ class CITypeGroupManager(object):
|
||||
else:
|
||||
resources = {i['name']: i['permissions'] for i in resources if PermEnum.READ in i.get("permissions")}
|
||||
|
||||
current_app.logger.info(resources)
|
||||
groups = sorted(CITypeGroup.get_by(), key=lambda x: x['order'] or 0)
|
||||
group_types = set()
|
||||
for group in groups:
|
||||
@@ -283,7 +293,10 @@ class CITypeGroupManager(object):
|
||||
"""
|
||||
existed = CITypeGroup.get_by_id(gid) or abort(
|
||||
404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid)))
|
||||
if name is not None:
|
||||
if name is not None and name != existed.name:
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
existed.update(name=name)
|
||||
|
||||
max_order = max([i.order or 0 for i in CITypeGroupItem.get_by(group_id=gid, to_dict=False)] or [0])
|
||||
@@ -576,6 +589,11 @@ class CITypeRelationManager(object):
|
||||
ci_type_dict = CITypeCache.get(type_id).to_dict()
|
||||
ci_type_dict["ctr_id"] = relation_inst.id
|
||||
ci_type_dict["attributes"] = CITypeAttributeManager.get_attributes_by_type_id(ci_type_dict["id"])
|
||||
attr_filter = CIFilterPermsCRUD.get_attr_filter(type_id)
|
||||
if attr_filter:
|
||||
ci_type_dict["attributes"] = [attr for attr in (ci_type_dict["attributes"] or [])
|
||||
if attr['name'] in attr_filter]
|
||||
|
||||
ci_type_dict["relation_type"] = relation_inst.relation_type.name
|
||||
ci_type_dict["constraint"] = relation_inst.constraint
|
||||
|
||||
@@ -637,6 +655,16 @@ class CITypeRelationManager(object):
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.circular_dependency_error)
|
||||
|
||||
if constraint == ConstraintEnum.Many2Many:
|
||||
other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
|
||||
to_dict=False, first=True)
|
||||
other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
|
||||
to_dict=False, first=True)
|
||||
if other_c and other_c.child_id != c.id:
|
||||
return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
|
||||
if other_p and other_p.parent_id != p.id:
|
||||
return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
|
||||
|
||||
existed = cls._get(p.id, c.id)
|
||||
if existed is not None:
|
||||
existed.update(relation_type_id=relation_type_id,
|
||||
@@ -686,6 +714,24 @@ class CITypeRelationManager(object):
|
||||
|
||||
cls.delete(ctr.id)
|
||||
|
||||
@staticmethod
|
||||
def get_level2constraint(root_id, level):
|
||||
level = level + 1 if level == 1 else level
|
||||
ci = CI.get_by_id(root_id)
|
||||
if ci is None:
|
||||
return dict()
|
||||
|
||||
root_id = ci.type_id
|
||||
level2constraint = dict()
|
||||
for lv in range(1, int(level) + 1):
|
||||
for i in CITypeRelation.get_by(parent_id=root_id, to_dict=False):
|
||||
if i.constraint == ConstraintEnum.Many2Many:
|
||||
root_id = i.child_id
|
||||
level2constraint[lv] = ConstraintEnum.Many2Many
|
||||
break
|
||||
|
||||
return level2constraint
|
||||
|
||||
|
||||
class CITypeAttributeGroupManager(object):
|
||||
cls = CITypeAttributeGroup
|
||||
@@ -697,7 +743,7 @@ class CITypeAttributeGroupManager(object):
|
||||
grouped = list()
|
||||
|
||||
attributes = CITypeAttributeManager.get_attributes_by_type_id(type_id)
|
||||
id2attr = {i['id']: i for i in attributes}
|
||||
id2attr = {i.get('id'): i for i in attributes}
|
||||
|
||||
for group in groups:
|
||||
items = CITypeAttributeGroupItem.get_by(group_id=group["id"], to_dict=False)
|
||||
@@ -863,97 +909,58 @@ class CITypeAttributeGroupManager(object):
|
||||
|
||||
class CITypeTemplateManager(object):
|
||||
@staticmethod
|
||||
def __import(cls, data):
|
||||
id2obj_dicts = {i['id']: i for i in data}
|
||||
existed = cls.get_by(deleted=None, to_dict=False)
|
||||
id2existed = {i.id: i for i in existed}
|
||||
existed_ids = [i.id for i in existed]
|
||||
existed_no_delete_ids = [i.id for i in existed if not i.deleted]
|
||||
def __import(cls, data, unique_key='name'):
|
||||
id2obj_dicts = {i[unique_key]: i for i in data}
|
||||
existed = cls.get_by(to_dict=False)
|
||||
id2existed = {getattr(i, unique_key): i for i in existed}
|
||||
existed_ids = [getattr(i, unique_key) for i in existed]
|
||||
|
||||
id_map = dict()
|
||||
# add
|
||||
for added_id in set(id2obj_dicts.keys()) - set(existed_ids):
|
||||
_id = id2obj_dicts[added_id].pop('id', None)
|
||||
id2obj_dicts[added_id].pop('created_at', None)
|
||||
id2obj_dicts[added_id].pop('updated_at', None)
|
||||
id2obj_dicts[added_id].pop('uid', None)
|
||||
|
||||
if cls == CIType:
|
||||
CITypeManager.add(**id2obj_dicts[added_id])
|
||||
__id = CITypeManager.add(**id2obj_dicts[added_id])
|
||||
CITypeCache.clean(__id)
|
||||
elif cls == CITypeRelation:
|
||||
CITypeRelationManager.add(id2obj_dicts[added_id].get('parent_id'),
|
||||
id2obj_dicts[added_id].get('child_id'),
|
||||
id2obj_dicts[added_id].get('relation_type_id'),
|
||||
id2obj_dicts[added_id].get('constraint'),
|
||||
)
|
||||
__id = CITypeRelationManager.add(id2obj_dicts[added_id].get('parent_id'),
|
||||
id2obj_dicts[added_id].get('child_id'),
|
||||
id2obj_dicts[added_id].get('relation_type_id'),
|
||||
id2obj_dicts[added_id].get('constraint'),
|
||||
)
|
||||
else:
|
||||
cls.create(flush=True, **id2obj_dicts[added_id])
|
||||
obj = cls.create(flush=True, **id2obj_dicts[added_id])
|
||||
if cls == Attribute:
|
||||
AttributeCache.clean(obj)
|
||||
__id = obj.id
|
||||
|
||||
id_map[_id] = __id
|
||||
|
||||
# update
|
||||
for updated_id in set(id2obj_dicts.keys()) & set(existed_ids):
|
||||
_id = id2obj_dicts[updated_id].pop('id', None)
|
||||
|
||||
id2existed[updated_id].update(flush=True, **id2obj_dicts[updated_id])
|
||||
|
||||
id_map[_id] = id2existed[updated_id].id
|
||||
|
||||
if cls == Attribute:
|
||||
AttributeCache.clean(id2existed[updated_id])
|
||||
|
||||
if cls == CIType:
|
||||
deleted = id2existed[updated_id].deleted
|
||||
CITypeManager.update(updated_id, **id2obj_dicts[updated_id])
|
||||
if deleted and current_app.config.get("USE_ACL"):
|
||||
type_name = id2obj_dicts[updated_id]['name']
|
||||
ACLManager().add_resource(type_name, ResourceTypeEnum.CI)
|
||||
ACLManager().grant_resource_to_role(type_name,
|
||||
RoleEnum.CMDB_READ_ALL,
|
||||
ResourceTypeEnum.CI,
|
||||
permissions=[PermEnum.READ])
|
||||
ACLManager().grant_resource_to_role(type_name,
|
||||
current_user.username,
|
||||
ResourceTypeEnum.CI)
|
||||
CITypeCache.clean(id2existed[updated_id].id)
|
||||
|
||||
else:
|
||||
id2existed[updated_id].update(flush=True, **id2obj_dicts[updated_id])
|
||||
|
||||
# delete
|
||||
for deleted_id in set(existed_no_delete_ids) - set(id2obj_dicts.keys()):
|
||||
if cls == CIType:
|
||||
id2existed[deleted_id].soft_delete(flush=True)
|
||||
|
||||
CITypeCache.clean(deleted_id)
|
||||
|
||||
CITypeHistoryManager.add(CITypeOperateType.DELETE, deleted_id, change=id2existed[deleted_id].to_dict())
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
ACLManager().del_resource(id2existed[deleted_id].name, ResourceTypeEnum.CI)
|
||||
else:
|
||||
id2existed[deleted_id].soft_delete(flush=True)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
raise Exception(str(e))
|
||||
|
||||
def _import_ci_types(self, ci_types):
|
||||
for i in ci_types:
|
||||
i.pop("unique_key", None)
|
||||
|
||||
self.__import(CIType, ci_types)
|
||||
|
||||
def _import_ci_type_groups(self, ci_type_groups):
|
||||
_ci_type_groups = copy.deepcopy(ci_type_groups)
|
||||
for i in _ci_type_groups:
|
||||
i.pop('ci_types', None)
|
||||
|
||||
self.__import(CITypeGroup, _ci_type_groups)
|
||||
|
||||
# import group type items
|
||||
for group in ci_type_groups:
|
||||
existed = CITypeGroupItem.get_by(group_id=group['id'], to_dict=False)
|
||||
for i in existed:
|
||||
i.soft_delete()
|
||||
|
||||
for order, ci_type in enumerate(group.get('ci_types') or []):
|
||||
payload = dict(group_id=group['id'], type_id=ci_type['id'], order=order)
|
||||
CITypeGroupItem.create(**payload)
|
||||
|
||||
def _import_relation_types(self, relation_types):
|
||||
self.__import(RelationType, relation_types)
|
||||
|
||||
def _import_ci_type_relations(self, ci_type_relations):
|
||||
for i in ci_type_relations:
|
||||
i.pop('parent', None)
|
||||
i.pop('child', None)
|
||||
i.pop('relation_type', None)
|
||||
|
||||
self.__import(CITypeRelation, ci_type_relations)
|
||||
return id_map
|
||||
|
||||
def _import_attributes(self, type2attributes):
|
||||
attributes = [attr for type_id in type2attributes for attr in type2attributes[type_id]]
|
||||
@@ -962,122 +969,262 @@ class CITypeTemplateManager(object):
|
||||
i.pop('default_show', None)
|
||||
i.pop('is_required', None)
|
||||
i.pop('order', None)
|
||||
i.pop('choice_web_hook', None)
|
||||
i.pop('choice_other', None)
|
||||
i.pop('order', None)
|
||||
choice_value = i.pop('choice_value', None)
|
||||
if not choice_value:
|
||||
i['is_choice'] = False
|
||||
|
||||
attrs.append((i, choice_value))
|
||||
|
||||
self.__import(Attribute, [i[0] for i in attrs])
|
||||
attr_id_map = self.__import(Attribute, [i[0] for i in copy.deepcopy(attrs)])
|
||||
|
||||
for i, choice_value in attrs:
|
||||
if choice_value:
|
||||
AttributeManager.add_choice_values(i['id'], i['value_type'], choice_value)
|
||||
if choice_value and not i.get('choice_web_hook') and not i.get('choice_other'):
|
||||
AttributeManager.add_choice_values(attr_id_map.get(i['id'], i['id']), i['value_type'], choice_value)
|
||||
|
||||
return attr_id_map
|
||||
|
||||
def _import_ci_types(self, ci_types, attr_id_map):
|
||||
for i in ci_types:
|
||||
i.pop("unique_key", None)
|
||||
i['unique_id'] = attr_id_map.get(i['unique_id'], i['unique_id'])
|
||||
i['uid'] = current_user.uid
|
||||
|
||||
return self.__import(CIType, ci_types)
|
||||
|
||||
def _import_ci_type_groups(self, ci_type_groups, type_id_map):
|
||||
_ci_type_groups = copy.deepcopy(ci_type_groups)
|
||||
for i in _ci_type_groups:
|
||||
i.pop('ci_types', None)
|
||||
|
||||
group_id_map = self.__import(CITypeGroup, _ci_type_groups)
|
||||
|
||||
# import group type items
|
||||
for group in ci_type_groups:
|
||||
for order, ci_type in enumerate(group.get('ci_types') or []):
|
||||
payload = dict(group_id=group_id_map.get(group['id'], group['id']),
|
||||
type_id=type_id_map.get(ci_type['id'], ci_type['id']),
|
||||
order=order)
|
||||
existed = CITypeGroupItem.get_by(group_id=payload['group_id'], type_id=payload['type_id'],
|
||||
first=True, to_dict=False)
|
||||
if existed is None:
|
||||
CITypeGroupItem.create(flush=True, **payload)
|
||||
else:
|
||||
existed.update(flush=True, **payload)
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
raise Exception(str(e))
|
||||
|
||||
def _import_relation_types(self, relation_types):
|
||||
return self.__import(RelationType, relation_types)
|
||||
|
||||
@staticmethod
|
||||
def _import_type_attributes(type2attributes):
|
||||
# add type attribute
|
||||
def _import_ci_type_relations(ci_type_relations, type_id_map, relation_type_id_map):
|
||||
for i in ci_type_relations:
|
||||
i.pop('parent', None)
|
||||
i.pop('child', None)
|
||||
i.pop('relation_type', None)
|
||||
|
||||
i['parent_id'] = type_id_map.get(i['parent_id'], i['parent_id'])
|
||||
i['child_id'] = type_id_map.get(i['child_id'], i['child_id'])
|
||||
i['relation_type_id'] = relation_type_id_map.get(i['relation_type_id'], i['relation_type_id'])
|
||||
|
||||
try:
|
||||
CITypeRelationManager.add(i.get('parent_id'),
|
||||
i.get('child_id'),
|
||||
i.get('relation_type_id'),
|
||||
i.get('constraint'),
|
||||
)
|
||||
except BadRequest:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _import_type_attributes(type2attributes, type_id_map, attr_id_map):
|
||||
for type_id in type2attributes:
|
||||
CITypeAttributesCache.clean(type_id_map.get(int(type_id), type_id))
|
||||
|
||||
for type_id in type2attributes:
|
||||
existed = CITypeAttribute.get_by(type_id=type_id, to_dict=False)
|
||||
existed_attr_ids = {i.attr_id: i for i in existed}
|
||||
new_attr_ids = {i['id']: i for i in type2attributes[type_id]}
|
||||
existed = CITypeAttributesCache.get2(type_id_map.get(int(type_id), type_id))
|
||||
existed_attr_names = {attr.name: ta for ta, attr in existed}
|
||||
|
||||
handled = set()
|
||||
for attr in type2attributes[type_id]:
|
||||
payload = dict(type_id=type_id,
|
||||
attr_id=attr['id'],
|
||||
payload = dict(type_id=type_id_map.get(int(type_id), type_id),
|
||||
attr_id=attr_id_map.get(attr['id'], attr['id']),
|
||||
default_show=attr['default_show'],
|
||||
is_required=attr['is_required'],
|
||||
order=attr['order'])
|
||||
if attr['id'] not in existed_attr_ids: # new
|
||||
CITypeAttribute.create(flush=True, **payload)
|
||||
else: # update
|
||||
existed_attr_ids[attr['id']].update(**payload)
|
||||
if attr['name'] not in handled:
|
||||
if attr['name'] not in existed_attr_names: # new
|
||||
CITypeAttribute.create(flush=True, **payload)
|
||||
else: # update
|
||||
existed_attr_names[attr['name']].update(flush=True, **payload)
|
||||
|
||||
# delete
|
||||
for i in existed:
|
||||
if i.attr_id not in new_attr_ids:
|
||||
i.soft_delete()
|
||||
handled.add(attr['name'])
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
raise Exception(str(e))
|
||||
|
||||
for type_id in type2attributes:
|
||||
CITypeAttributesCache.clean(type_id_map.get(int(type_id), type_id))
|
||||
|
||||
@staticmethod
|
||||
def _import_attribute_group(type2attribute_group):
|
||||
def _import_attribute_group(type2attribute_group, type_id_map, attr_id_map):
|
||||
for type_id in type2attribute_group:
|
||||
existed = CITypeAttributeGroup.get_by(type_id=type_id, to_dict=False)
|
||||
for i in existed:
|
||||
i.soft_delete()
|
||||
|
||||
for group in type2attribute_group[type_id] or []:
|
||||
_group = copy.deepcopy(group)
|
||||
_group.pop('attributes', None)
|
||||
_group.pop('id', None)
|
||||
new = CITypeAttributeGroup.create(**_group)
|
||||
existed = CITypeAttributeGroup.get_by(name=_group['name'],
|
||||
type_id=type_id_map.get(_group['type_id'], _group['type_id']),
|
||||
first=True, to_dict=False)
|
||||
if existed is None:
|
||||
_group['type_id'] = type_id_map.get(_group['type_id'], _group['type_id'])
|
||||
|
||||
existed = CITypeAttributeGroupItem.get_by(group_id=new.id, to_dict=False)
|
||||
for i in existed:
|
||||
i.soft_delete()
|
||||
existed = CITypeAttributeGroup.create(flush=True, **_group)
|
||||
|
||||
for order, attr in enumerate(group['attributes'] or []):
|
||||
CITypeAttributeGroupItem.create(group_id=new.id, attr_id=attr['id'], order=order)
|
||||
item_existed = CITypeAttributeGroupItem.get_by(group_id=existed.id,
|
||||
attr_id=attr_id_map.get(attr['id'], attr['id']),
|
||||
first=True, to_dict=False)
|
||||
if item_existed is None:
|
||||
CITypeAttributeGroupItem.create(group_id=existed.id,
|
||||
attr_id=attr_id_map.get(attr['id'], attr['id']),
|
||||
order=order)
|
||||
else:
|
||||
item_existed.update(flush=True, order=order)
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
raise Exception(str(e))
|
||||
|
||||
@staticmethod
|
||||
def _import_auto_discovery_rules(rules):
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleCRUD
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD
|
||||
|
||||
for rule in rules:
|
||||
ci_type = CITypeCache.get(rule.pop('type_name', None))
|
||||
adr = rule.pop('adr', {}) or {}
|
||||
|
||||
if ci_type:
|
||||
rule['type_id'] = ci_type.id
|
||||
if rule.get('adr_name'):
|
||||
ad_rule = AutoDiscoveryRuleCRUD.get_by_name(rule.pop("adr_name"))
|
||||
adr.pop('created_at', None)
|
||||
adr.pop('updated_at', None)
|
||||
adr.pop('id', None)
|
||||
|
||||
if ad_rule:
|
||||
rule['adr_id'] = ad_rule.id
|
||||
ad_rule.update(**adr)
|
||||
|
||||
elif adr:
|
||||
ad_rule = AutoDiscoveryRuleCRUD().add(**adr)
|
||||
rule['adr_id'] = ad_rule.id
|
||||
else:
|
||||
continue
|
||||
|
||||
rule.pop("id", None)
|
||||
rule.pop("created_at", None)
|
||||
rule.pop("updated_at", None)
|
||||
|
||||
rule['uid'] = current_user.uid
|
||||
try:
|
||||
AutoDiscoveryCITypeCRUD.add(**rule)
|
||||
except Exception as e:
|
||||
current_app.logger.warning("import auto discovery rules failed: {}".format(e))
|
||||
|
||||
existed = False
|
||||
for i in AutoDiscoveryCIType.get_by(type_id=ci_type.id, adr_id=rule['adr_id'], to_dict=False):
|
||||
if ((i.extra_option or {}).get('alias') or None) == (
|
||||
(rule.get('extra_option') or {}).get('alias') or None):
|
||||
existed = True
|
||||
AutoDiscoveryCITypeCRUD().update(i.id, **rule)
|
||||
break
|
||||
|
||||
if not existed:
|
||||
try:
|
||||
AutoDiscoveryCITypeCRUD().add(**rule)
|
||||
except Exception as e:
|
||||
current_app.logger.warning("import auto discovery rules failed: {}".format(e))
|
||||
|
||||
@staticmethod
|
||||
def _import_icons(icons):
|
||||
from api.lib.common_setting.upload_file import CommonFileCRUD
|
||||
for icon_name in icons:
|
||||
if icons[icon_name]:
|
||||
try:
|
||||
CommonFileCRUD().save_str_to_file(icon_name, icons[icon_name])
|
||||
except Exception as e:
|
||||
current_app.logger.warning("save icon failed: {}".format(e))
|
||||
|
||||
def import_template(self, tpt):
|
||||
import time
|
||||
s = time.time()
|
||||
self._import_attributes(tpt.get('type2attributes') or {})
|
||||
attr_id_map = self._import_attributes(tpt.get('type2attributes') or {})
|
||||
current_app.logger.info('import attributes cost: {}'.format(time.time() - s))
|
||||
|
||||
s = time.time()
|
||||
self._import_ci_types(tpt.get('ci_types') or [])
|
||||
ci_type_id_map = self._import_ci_types(tpt.get('ci_types') or [], attr_id_map)
|
||||
current_app.logger.info('import ci_types cost: {}'.format(time.time() - s))
|
||||
|
||||
s = time.time()
|
||||
self._import_ci_type_groups(tpt.get('ci_type_groups') or [])
|
||||
self._import_ci_type_groups(tpt.get('ci_type_groups') or [], ci_type_id_map)
|
||||
current_app.logger.info('import ci_type_groups cost: {}'.format(time.time() - s))
|
||||
|
||||
s = time.time()
|
||||
self._import_relation_types(tpt.get('relation_types') or [])
|
||||
relation_type_id_map = self._import_relation_types(tpt.get('relation_types') or [])
|
||||
current_app.logger.info('import relation_types cost: {}'.format(time.time() - s))
|
||||
|
||||
s = time.time()
|
||||
self._import_ci_type_relations(tpt.get('ci_type_relations') or [])
|
||||
self._import_ci_type_relations(tpt.get('ci_type_relations') or [], ci_type_id_map, relation_type_id_map)
|
||||
current_app.logger.info('import ci_type_relations cost: {}'.format(time.time() - s))
|
||||
|
||||
s = time.time()
|
||||
self._import_type_attributes(tpt.get('type2attributes') or {})
|
||||
self._import_type_attributes(tpt.get('type2attributes') or {}, ci_type_id_map, attr_id_map)
|
||||
current_app.logger.info('import type2attributes cost: {}'.format(time.time() - s))
|
||||
|
||||
s = time.time()
|
||||
self._import_attribute_group(tpt.get('type2attribute_group') or {})
|
||||
self._import_attribute_group(tpt.get('type2attribute_group') or {}, ci_type_id_map, attr_id_map)
|
||||
current_app.logger.info('import type2attribute_group cost: {}'.format(time.time() - s))
|
||||
|
||||
s = time.time()
|
||||
self._import_auto_discovery_rules(tpt.get('ci_type_auto_discovery_rules') or [])
|
||||
current_app.logger.info('import ci_type_auto_discovery_rules cost: {}'.format(time.time() - s))
|
||||
|
||||
s = time.time()
|
||||
self._import_icons(tpt.get('icons') or {})
|
||||
current_app.logger.info('import icons cost: {}'.format(time.time() - s))
|
||||
|
||||
@staticmethod
|
||||
def export_template():
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleCRUD
|
||||
from api.lib.common_setting.upload_file import CommonFileCRUD
|
||||
|
||||
tpt = dict(
|
||||
ci_types=CITypeManager.get_ci_types(),
|
||||
ci_type_groups=CITypeGroupManager.get(),
|
||||
relation_types=[i.to_dict() for i in RelationTypeManager.get_all()],
|
||||
ci_type_relations=CITypeRelationManager.get(),
|
||||
ci_type_auto_discovery_rules=list(),
|
||||
type2attributes=dict(),
|
||||
type2attribute_group=dict(),
|
||||
icons=dict()
|
||||
)
|
||||
|
||||
def get_icon_value(icon):
|
||||
try:
|
||||
return CommonFileCRUD().get_file_binary_str(icon)
|
||||
except:
|
||||
return ""
|
||||
|
||||
ad_rules = AutoDiscoveryCITypeCRUD.get_all()
|
||||
rules = []
|
||||
@@ -1088,23 +1235,91 @@ class CITypeTemplateManager(object):
|
||||
if r.get('adr_id'):
|
||||
adr = AutoDiscoveryRuleCRUD.get_by_id(r.pop('adr_id'))
|
||||
r['adr_name'] = adr and adr.name
|
||||
r['adr'] = adr and adr.to_dict() or {}
|
||||
|
||||
icon_url = r['adr'].get('option', {}).get('icon', {}).get('url')
|
||||
if icon_url and icon_url not in tpt['icons']:
|
||||
tpt['icons'][icon_url] = get_icon_value(icon_url)
|
||||
|
||||
rules.append(r)
|
||||
|
||||
tpt = dict(
|
||||
ci_types=CITypeManager.get_ci_types(),
|
||||
ci_type_groups=CITypeGroupManager.get(),
|
||||
relation_types=[i.to_dict() for i in RelationTypeManager.get_all()],
|
||||
ci_type_relations=CITypeRelationManager.get(),
|
||||
ci_type_auto_discovery_rules=rules,
|
||||
type2attributes=dict(),
|
||||
type2attribute_group=dict()
|
||||
)
|
||||
tpt['ci_type_auto_discovery_rules'] = rules
|
||||
|
||||
for ci_type in tpt['ci_types']:
|
||||
if ci_type['icon'] and len(ci_type['icon'].split('$$')) > 3:
|
||||
icon_url = ci_type['icon'].split('$$')[3]
|
||||
if icon_url not in tpt['icons']:
|
||||
tpt['icons'][icon_url] = get_icon_value(icon_url)
|
||||
|
||||
tpt['type2attributes'][ci_type['id']] = CITypeAttributeManager.get_attributes_by_type_id(
|
||||
ci_type['id'], choice_web_hook_parse=False, choice_other_parse=False)
|
||||
|
||||
for attr in tpt['type2attributes'][ci_type['id']]:
|
||||
for i in (attr.get('choice_value') or []):
|
||||
if (i[1] or {}).get('icon', {}).get('url') and len(i[1]['icon']['url'].split('$$')) > 3:
|
||||
icon_url = i[1]['icon']['url'].split('$$')[3]
|
||||
if icon_url not in tpt['icons']:
|
||||
tpt['icons'][icon_url] = get_icon_value(icon_url)
|
||||
|
||||
tpt['type2attribute_group'][ci_type['id']] = CITypeAttributeGroupManager.get_by_type_id(ci_type['id'])
|
||||
|
||||
return tpt
|
||||
|
||||
@staticmethod
|
||||
def export_template_by_type(type_id):
|
||||
ci_type = CITypeCache.get(type_id) or abort(404, ErrFormat.ci_type_not_found2.format("id={}".format(type_id)))
|
||||
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleCRUD
|
||||
from api.lib.common_setting.upload_file import CommonFileCRUD
|
||||
|
||||
tpt = dict(
|
||||
ci_types=CITypeManager.get_ci_types(type_name=ci_type.name),
|
||||
ci_type_auto_discovery_rules=list(),
|
||||
type2attributes=dict(),
|
||||
type2attribute_group=dict(),
|
||||
icons=dict()
|
||||
)
|
||||
|
||||
def get_icon_value(icon):
|
||||
try:
|
||||
return CommonFileCRUD().get_file_binary_str(icon)
|
||||
except:
|
||||
return ""
|
||||
|
||||
ad_rules = AutoDiscoveryCITypeCRUD.get_by_type_id(ci_type.id)
|
||||
rules = []
|
||||
for r in ad_rules:
|
||||
r = r.to_dict()
|
||||
r['type_name'] = ci_type and ci_type.name
|
||||
if r.get('adr_id'):
|
||||
adr = AutoDiscoveryRuleCRUD.get_by_id(r.pop('adr_id'))
|
||||
r['adr_name'] = adr and adr.name
|
||||
r['adr'] = adr and adr.to_dict() or {}
|
||||
|
||||
icon_url = r['adr'].get('option', {}).get('icon', {}).get('url')
|
||||
if icon_url and icon_url not in tpt['icons']:
|
||||
tpt['icons'][icon_url] = get_icon_value(icon_url)
|
||||
|
||||
rules.append(r)
|
||||
tpt['ci_type_auto_discovery_rules'] = rules
|
||||
|
||||
for ci_type in tpt['ci_types']:
|
||||
if ci_type['icon'] and len(ci_type['icon'].split('$$')) > 3:
|
||||
icon_url = ci_type['icon'].split('$$')[3]
|
||||
if icon_url not in tpt['icons']:
|
||||
tpt['icons'][icon_url] = get_icon_value(icon_url)
|
||||
|
||||
tpt['type2attributes'][ci_type['id']] = CITypeAttributeManager.get_attributes_by_type_id(
|
||||
ci_type['id'], choice_web_hook_parse=False, choice_other_parse=False)
|
||||
|
||||
for attr in tpt['type2attributes'][ci_type['id']]:
|
||||
for i in (attr.get('choice_value') or []):
|
||||
if (i[1] or {}).get('icon', {}).get('url') and len(i[1]['icon']['url'].split('$$')) > 3:
|
||||
icon_url = i[1]['icon']['url'].split('$$')[3]
|
||||
if icon_url not in tpt['icons']:
|
||||
tpt['icons'][icon_url] = get_icon_value(icon_url)
|
||||
|
||||
tpt['type2attribute_group'][ci_type['id']] = CITypeAttributeGroupManager.get_by_type_id(ci_type['id'])
|
||||
|
||||
return tpt
|
||||
|
@@ -69,6 +69,7 @@ class ResourceTypeEnum(BaseEnum):
|
||||
CI_TYPE_RELATION = "CITypeRelation" # create/delete/grant
|
||||
RELATION_VIEW = "RelationView" # read/update/delete/grant
|
||||
CI_FILTER = "CIFilter" # read
|
||||
PAGE = "page" # read
|
||||
|
||||
|
||||
class PermEnum(BaseEnum):
|
||||
@@ -100,6 +101,7 @@ class AttributeDefaultValueEnum(BaseEnum):
|
||||
CMDB_QUEUE = "one_cmdb_async"
|
||||
REDIS_PREFIX_CI = "ONE_CMDB"
|
||||
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
||||
REDIS_PREFIX_CI_RELATION2 = "CMDB_CI_RELATION2"
|
||||
|
||||
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type'}
|
||||
|
||||
|
@@ -135,7 +135,7 @@ class AttributeHistoryManger(object):
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
cis = CIManager().get_cis_by_ids(list(ci_ids),
|
||||
unique_required=True)
|
||||
cis = {i['_id']: i for i in cis}
|
||||
cis = {i['_id']: i for i in cis if i}
|
||||
|
||||
return total, res, cis
|
||||
|
||||
|
@@ -143,11 +143,14 @@ class CIFilterPermsCRUD(DBMixin):
|
||||
first=True, to_dict=False)
|
||||
|
||||
if obj is not None:
|
||||
resource = None
|
||||
if current_app.config.get('USE_ACL'):
|
||||
ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER)
|
||||
resource = ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER)
|
||||
|
||||
obj.soft_delete()
|
||||
|
||||
return resource
|
||||
|
||||
|
||||
def has_perm_for_ci(arg_name, resource_type, perm, callback=None, app=None):
|
||||
def decorator_has_perm(func):
|
||||
|
@@ -14,7 +14,10 @@ from api.lib.cmdb.attribute import AttributeManager
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum
|
||||
from api.lib.cmdb.const import ConstraintEnum
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.exception import AbortException
|
||||
@@ -229,14 +232,28 @@ class PreferenceManager(object):
|
||||
if not parents:
|
||||
return
|
||||
|
||||
for l in leaf:
|
||||
_find_parent(l)
|
||||
for _l in leaf:
|
||||
_find_parent(_l)
|
||||
|
||||
for node_id in node2show_types:
|
||||
node2show_types[node_id] = [CITypeCache.get(i).to_dict() for i in set(node2show_types[node_id])]
|
||||
|
||||
topo_flatten = list(toposort.toposort_flatten(topo))
|
||||
level2constraint = {}
|
||||
for i, _ in enumerate(topo_flatten[1:]):
|
||||
ctr = CITypeRelation.get_by(
|
||||
parent_id=topo_flatten[i], child_id=topo_flatten[i + 1], first=True, to_dict=False)
|
||||
level2constraint[i + 1] = ctr and ctr.constraint
|
||||
|
||||
if leaf2show_types.get(topo_flatten[-1]):
|
||||
ctr = CITypeRelation.get_by(
|
||||
parent_id=topo_flatten[-1],
|
||||
child_id=leaf2show_types[topo_flatten[-1]][0], first=True, to_dict=False)
|
||||
level2constraint[len(topo_flatten)] = ctr and ctr.constraint
|
||||
|
||||
result[view_name] = dict(topo=list(map(list, toposort.toposort(topo))),
|
||||
topo_flatten=list(toposort.toposort_flatten(topo)),
|
||||
topo_flatten=topo_flatten,
|
||||
level2constraint=level2constraint,
|
||||
leaf=leaf,
|
||||
leaf2show_types=leaf2show_types,
|
||||
node2show_types=node2show_types,
|
||||
@@ -338,3 +355,29 @@ class PreferenceManager(object):
|
||||
|
||||
for i in PreferenceTreeView.get_by(type_id=type_id, uid=uid, to_dict=False):
|
||||
i.soft_delete()
|
||||
|
||||
@staticmethod
|
||||
def can_edit_relation(parent_id, child_id):
|
||||
views = PreferenceRelationView.get_by(to_dict=False)
|
||||
for view in views:
|
||||
has_m2m = False
|
||||
last_node_id = None
|
||||
for cr in view.cr_ids:
|
||||
_rel = CITypeRelation.get_by(parent_id=cr['parent_id'], child_id=cr['child_id'],
|
||||
first=True, to_dict=False)
|
||||
if _rel and _rel.constraint == ConstraintEnum.Many2Many:
|
||||
has_m2m = True
|
||||
|
||||
if parent_id == _rel.parent_id and child_id == _rel.child_id:
|
||||
return False
|
||||
|
||||
if _rel:
|
||||
last_node_id = _rel.child_id
|
||||
|
||||
if parent_id == last_node_id:
|
||||
rels = CITypeRelation.get_by(parent_id=last_node_id, to_dict=False)
|
||||
for rel in rels:
|
||||
if rel.child_id == child_id and has_m2m:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
@@ -4,6 +4,8 @@ from api.lib.resp_format import CommonErrFormat
|
||||
|
||||
|
||||
class ErrFormat(CommonErrFormat):
|
||||
ci_type_config = "模型配置"
|
||||
|
||||
invalid_relation_type = "无效的关系类型: {}"
|
||||
ci_type_not_found = "模型不存在!"
|
||||
argument_attributes_must_be_list = "参数 attributes 类型必须是列表"
|
||||
@@ -31,6 +33,7 @@ class ErrFormat(CommonErrFormat):
|
||||
unique_key_required = "主键字段 {} 缺失"
|
||||
ci_is_already_existed = "CI 已经存在!"
|
||||
relation_constraint = "关系约束: {}, 校验失败 "
|
||||
m2m_relation_constraint = "多对多关系 限制: 模型 {} <-> {} 已经存在多对多关系!"
|
||||
relation_not_found = "CI关系: {} 不存在"
|
||||
ci_search_Parentheses_invalid = "搜索表达式里小括号前不支持: 或、非"
|
||||
|
||||
|
@@ -1,6 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import json
|
||||
from collections import Counter
|
||||
|
||||
@@ -10,11 +8,14 @@ from flask import current_app
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
from api.lib.cmdb.const import ConstraintEnum
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
|
||||
|
||||
class Search(object):
|
||||
@@ -26,7 +27,9 @@ class Search(object):
|
||||
page=1,
|
||||
count=None,
|
||||
sort=None,
|
||||
reverse=False):
|
||||
reverse=False,
|
||||
ancestor_ids=None,
|
||||
has_m2m=None):
|
||||
self.orig_query = query
|
||||
self.fl = fl
|
||||
self.facet_field = facet_field
|
||||
@@ -38,25 +41,82 @@ class Search(object):
|
||||
self.level = level or 0
|
||||
self.reverse = reverse
|
||||
|
||||
def _get_ids(self):
|
||||
self.level2constraint = CITypeRelationManager.get_level2constraint(
|
||||
root_id[0] if root_id and isinstance(root_id, list) else root_id,
|
||||
level[0] if isinstance(level, list) and level else level)
|
||||
|
||||
self.ancestor_ids = ancestor_ids
|
||||
self.has_m2m = has_m2m or False
|
||||
if not self.has_m2m:
|
||||
if self.ancestor_ids:
|
||||
self.has_m2m = True
|
||||
else:
|
||||
level = level[0] if isinstance(level, list) and level else level
|
||||
for _l, c in self.level2constraint.items():
|
||||
if _l < int(level) and c == ConstraintEnum.Many2Many:
|
||||
self.has_m2m = True
|
||||
|
||||
def _get_ids(self, ids):
|
||||
if self.level[-1] == 1 and len(ids) == 1:
|
||||
if self.ancestor_ids is None:
|
||||
return [i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0], to_dict=False)]
|
||||
|
||||
else:
|
||||
seconds = {i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0],
|
||||
ancestor_ids=self.ancestor_ids,
|
||||
to_dict=False)}
|
||||
|
||||
return list(seconds)
|
||||
|
||||
merge_ids = []
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
key = []
|
||||
_tmp = []
|
||||
for level in range(1, sorted(self.level)[-1] + 1):
|
||||
_tmp = list(map(lambda x: list(json.loads(x).keys()),
|
||||
filter(lambda x: x is not None, rd.get(ids, REDIS_PREFIX_CI_RELATION) or [])))
|
||||
ids = [j for i in _tmp for j in i]
|
||||
if not self.has_m2m:
|
||||
_tmp = map(lambda x: json.loads(x).keys(),
|
||||
filter(lambda x: x is not None, rd.get(ids, REDIS_PREFIX_CI_RELATION) or []))
|
||||
ids = [j for i in _tmp for j in i]
|
||||
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
||||
|
||||
else:
|
||||
if not self.ancestor_ids:
|
||||
if level == 1:
|
||||
key, prefix = list(map(str, ids)), REDIS_PREFIX_CI_RELATION
|
||||
else:
|
||||
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
|
||||
prefix = REDIS_PREFIX_CI_RELATION2
|
||||
else:
|
||||
if level == 1:
|
||||
key, prefix = ["{},{}".format(self.ancestor_ids, i) for i in ids], REDIS_PREFIX_CI_RELATION2
|
||||
else:
|
||||
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
|
||||
prefix = REDIS_PREFIX_CI_RELATION2
|
||||
|
||||
_tmp = list(map(lambda x: json.loads(x).keys() if x else [], rd.get(key, prefix) or []))
|
||||
ids = [j for i in _tmp for j in i]
|
||||
|
||||
if not key:
|
||||
return []
|
||||
|
||||
if level in self.level:
|
||||
merge_ids.extend(ids)
|
||||
|
||||
return merge_ids
|
||||
|
||||
def _get_reverse_ids(self):
|
||||
def _get_reverse_ids(self, ids):
|
||||
merge_ids = []
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
level2ids = {}
|
||||
for level in range(1, sorted(self.level)[-1] + 1):
|
||||
ids = CIRelationManager.get_ancestor_ids(ids, 1)
|
||||
ids, _level2ids = CIRelationManager.get_ancestor_ids(ids, 1)
|
||||
|
||||
if _level2ids.get(2):
|
||||
level2ids[level + 1] = _level2ids[2]
|
||||
|
||||
if level in self.level:
|
||||
merge_ids.extend(ids)
|
||||
if level in level2ids and level2ids[level]:
|
||||
merge_ids.extend(set(ids) & set(level2ids[level]))
|
||||
else:
|
||||
merge_ids.extend(ids)
|
||||
|
||||
return merge_ids
|
||||
|
||||
@@ -64,7 +124,7 @@ class Search(object):
|
||||
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]
|
||||
|
||||
merge_ids = self._get_ids() if not self.reverse else self._get_reverse_ids()
|
||||
merge_ids = self._get_ids(ids) if not self.reverse else self._get_reverse_ids(ids)
|
||||
|
||||
if not self.orig_query or ("_type:" not in self.orig_query
|
||||
and "type_id:" not in self.orig_query
|
||||
@@ -76,11 +136,11 @@ class Search(object):
|
||||
type_ids.extend(CITypeRelationManager.get_child_type_ids(ci.type_id, level))
|
||||
else:
|
||||
type_ids.extend(CITypeRelationManager.get_parent_type_ids(ci.type_id, level))
|
||||
type_ids = list(set(type_ids))
|
||||
type_ids = set(type_ids)
|
||||
if self.orig_query:
|
||||
self.orig_query = "_type:({0}),{1}".format(";".join(list(map(str, type_ids))), self.orig_query)
|
||||
self.orig_query = "_type:({0}),{1}".format(";".join(map(str, type_ids)), self.orig_query)
|
||||
else:
|
||||
self.orig_query = "_type:({0})".format(";".join(list(map(str, type_ids))))
|
||||
self.orig_query = "_type:({0})".format(";".join(map(str, type_ids)))
|
||||
|
||||
if not merge_ids:
|
||||
# cis, counter, total, self.page, numfound, facet_
|
||||
@@ -105,35 +165,65 @@ class Search(object):
|
||||
|
||||
def statistics(self, type_ids):
|
||||
self.level = int(self.level)
|
||||
_tmp = []
|
||||
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
for lv in range(0, self.level):
|
||||
if not lv:
|
||||
if type_ids and lv == self.level - 1:
|
||||
_tmp = []
|
||||
level2ids = {}
|
||||
for lv in range(1, self.level + 1):
|
||||
level2ids[lv] = []
|
||||
|
||||
if lv == 1:
|
||||
if not self.has_m2m:
|
||||
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
||||
else:
|
||||
if not self.ancestor_ids:
|
||||
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
||||
else:
|
||||
key = ["{},{}".format(self.ancestor_ids, _id) for _id in ids]
|
||||
prefix = REDIS_PREFIX_CI_RELATION2
|
||||
|
||||
level2ids[lv] = [[i] for i in key]
|
||||
|
||||
if not key:
|
||||
_tmp = []
|
||||
continue
|
||||
|
||||
if type_ids and lv == self.level:
|
||||
_tmp = list(map(lambda x: [i for i in x if i[1] in type_ids],
|
||||
(map(lambda x: list(json.loads(x).items()),
|
||||
[i or '{}' for i in rd.get(ids, REDIS_PREFIX_CI_RELATION) or []]))))
|
||||
[i or '{}' for i in rd.get(key, prefix) or []]))))
|
||||
else:
|
||||
_tmp = list(map(lambda x: list(json.loads(x).items()),
|
||||
[i or '{}' for i in rd.get(ids, REDIS_PREFIX_CI_RELATION) or []]))
|
||||
[i or '{}' for i in rd.get(key, prefix) or []]))
|
||||
|
||||
else:
|
||||
for idx, item in enumerate(_tmp):
|
||||
if item:
|
||||
if type_ids and lv == self.level - 1:
|
||||
__tmp = list(
|
||||
map(lambda x: [(_id, type_id) for _id, type_id in json.loads(x).items()
|
||||
if type_id in type_ids],
|
||||
filter(lambda x: x is not None,
|
||||
rd.get([i[0] for i in item], REDIS_PREFIX_CI_RELATION) or [])))
|
||||
if not self.has_m2m:
|
||||
key, prefix = [i[0] for i in item], REDIS_PREFIX_CI_RELATION
|
||||
else:
|
||||
key = list(set(['{},{}'.format(j, i[0]) for i in item for j in level2ids[lv - 1][idx]]))
|
||||
prefix = REDIS_PREFIX_CI_RELATION2
|
||||
|
||||
__tmp = list(map(lambda x: list(json.loads(x).items()),
|
||||
filter(lambda x: x is not None,
|
||||
rd.get([i[0] for i in item], REDIS_PREFIX_CI_RELATION) or [])))
|
||||
level2ids[lv].append(key)
|
||||
|
||||
if key:
|
||||
if type_ids and lv == self.level:
|
||||
__tmp = map(lambda x: [(_id, type_id) for _id, type_id in json.loads(x).items()
|
||||
if type_id in type_ids],
|
||||
filter(lambda x: x is not None,
|
||||
rd.get(key, prefix) or []))
|
||||
else:
|
||||
__tmp = map(lambda x: list(json.loads(x).items()),
|
||||
filter(lambda x: x is not None,
|
||||
rd.get(key, prefix) or []))
|
||||
else:
|
||||
__tmp = []
|
||||
|
||||
_tmp[idx] = [j for i in __tmp for j in i]
|
||||
else:
|
||||
_tmp[idx] = []
|
||||
level2ids[lv].append([])
|
||||
|
||||
result = {str(_id): len(_tmp[idx]) for idx, _id in enumerate(ids)}
|
||||
|
||||
|
@@ -12,7 +12,7 @@ import api.models.cmdb as model
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
|
||||
TIME_RE = re.compile(r"^20|21|22|23|[0-1]\d:[0-5]\d:[0-5]\d$")
|
||||
TIME_RE = re.compile(r'(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d')
|
||||
|
||||
|
||||
def string2int(x):
|
||||
|
@@ -1,14 +1,24 @@
|
||||
from flask import abort
|
||||
import copy
|
||||
import json
|
||||
|
||||
from flask import abort, current_app
|
||||
from ldap3 import Connection
|
||||
from ldap3 import Server
|
||||
from ldap3.core.exceptions import LDAPBindError, LDAPSocketOpenError
|
||||
from ldap3 import AUTO_BIND_NO_TLS
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.models.common_setting import CommonData
|
||||
from api.lib.utils import AESCrypto
|
||||
from api.lib.common_setting.const import AuthCommonConfig, AuthenticateType, AuthCommonConfigAutoRedirect, TestType
|
||||
|
||||
|
||||
class CommonDataCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def get_data_by_type(data_type):
|
||||
CommonDataCRUD.check_auth_type(data_type)
|
||||
return CommonData.get_by(data_type=data_type)
|
||||
|
||||
@staticmethod
|
||||
@@ -18,6 +28,8 @@ class CommonDataCRUD(object):
|
||||
@staticmethod
|
||||
def create_new_data(data_type, **kwargs):
|
||||
try:
|
||||
CommonDataCRUD.check_auth_type(data_type)
|
||||
|
||||
return CommonData.create(data_type=data_type, **kwargs)
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
@@ -29,6 +41,7 @@ class CommonDataCRUD(object):
|
||||
if not existed:
|
||||
abort(404, ErrFormat.common_data_not_found.format(_id))
|
||||
try:
|
||||
CommonDataCRUD.check_auth_type(existed.data_type)
|
||||
return existed.update(**kwargs)
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
@@ -40,7 +53,230 @@ class CommonDataCRUD(object):
|
||||
if not existed:
|
||||
abort(404, ErrFormat.common_data_not_found.format(_id))
|
||||
try:
|
||||
CommonDataCRUD.check_auth_type(existed.data_type)
|
||||
existed.soft_delete()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
abort(400, str(e))
|
||||
|
||||
@staticmethod
|
||||
def check_auth_type(data_type):
|
||||
if data_type in list(AuthenticateType.all()) + [AuthCommonConfig]:
|
||||
abort(400, ErrFormat.common_data_not_support_auth_type.format(data_type))
|
||||
|
||||
@staticmethod
|
||||
def set_auth_auto_redirect_enable(_value: int):
|
||||
existed = CommonData.get_by(first=True, data_type=AuthCommonConfig, to_dict=False)
|
||||
if not existed:
|
||||
CommonDataCRUD.create_new_data(AuthCommonConfig, data={AuthCommonConfigAutoRedirect: _value})
|
||||
else:
|
||||
data = existed.data
|
||||
data = copy.deepcopy(existed.data) if data else {}
|
||||
data[AuthCommonConfigAutoRedirect] = _value
|
||||
CommonDataCRUD.update_data(existed.id, data=data)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def get_auth_auto_redirect_enable():
|
||||
existed = CommonData.get_by(first=True, data_type=AuthCommonConfig)
|
||||
if not existed:
|
||||
return 0
|
||||
data = existed.get('data', {})
|
||||
if not data:
|
||||
return 0
|
||||
return data.get(AuthCommonConfigAutoRedirect, 0)
|
||||
|
||||
|
||||
class AuthenticateDataCRUD(object):
|
||||
common_type_list = [AuthCommonConfig]
|
||||
|
||||
def __init__(self, _type):
|
||||
self._type = _type
|
||||
self.record = None
|
||||
self.decrypt_data = {}
|
||||
|
||||
def get_support_type_list(self):
|
||||
return list(AuthenticateType.all()) + self.common_type_list
|
||||
|
||||
def get(self):
|
||||
if not self.decrypt_data:
|
||||
self.decrypt_data = self.get_decrypt_data()
|
||||
|
||||
return self.decrypt_data
|
||||
|
||||
def get_by_key(self, _key):
|
||||
if not self.decrypt_data:
|
||||
self.decrypt_data = self.get_decrypt_data()
|
||||
|
||||
return self.decrypt_data.get(_key, None)
|
||||
|
||||
def get_record(self, to_dict=False) -> CommonData:
|
||||
return CommonData.get_by(first=True, data_type=self._type, to_dict=to_dict)
|
||||
|
||||
def get_record_with_decrypt(self) -> dict:
|
||||
record = CommonData.get_by(first=True, data_type=self._type, to_dict=True)
|
||||
if not record:
|
||||
return {}
|
||||
data = self.get_decrypt_dict(record.get('data', ''))
|
||||
record['data'] = data
|
||||
return record
|
||||
|
||||
def get_decrypt_dict(self, data):
|
||||
decrypt_str = self.decrypt(data)
|
||||
try:
|
||||
return json.loads(decrypt_str)
|
||||
except Exception as e:
|
||||
abort(400, str(e))
|
||||
|
||||
def get_decrypt_data(self) -> dict:
|
||||
self.record = self.get_record()
|
||||
if not self.record:
|
||||
return self.get_from_config()
|
||||
return self.get_decrypt_dict(self.record.data)
|
||||
|
||||
def get_from_config(self):
|
||||
return current_app.config.get(self._type, {})
|
||||
|
||||
def check_by_type(self) -> None:
|
||||
existed = self.get_record()
|
||||
if existed:
|
||||
abort(400, ErrFormat.common_data_already_existed.format(self._type))
|
||||
|
||||
def create(self, data) -> CommonData:
|
||||
self.check_by_type()
|
||||
encrypt = data.pop('encrypt', None)
|
||||
if encrypt is False:
|
||||
return CommonData.create(data_type=self._type, data=data)
|
||||
encrypted_data = self.encrypt(data)
|
||||
try:
|
||||
return CommonData.create(data_type=self._type, data=encrypted_data)
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
abort(400, str(e))
|
||||
|
||||
def update_by_record(self, record, data) -> CommonData:
|
||||
encrypt = data.pop('encrypt', None)
|
||||
if encrypt is False:
|
||||
return record.update(data=data)
|
||||
encrypted_data = self.encrypt(data)
|
||||
try:
|
||||
return record.update(data=encrypted_data)
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
abort(400, str(e))
|
||||
|
||||
def update(self, _id, data) -> CommonData:
|
||||
existed = CommonData.get_by(first=True, to_dict=False, id=_id)
|
||||
if not existed:
|
||||
abort(404, ErrFormat.common_data_not_found.format(_id))
|
||||
|
||||
return self.update_by_record(existed, data)
|
||||
|
||||
@staticmethod
|
||||
def delete(_id) -> None:
|
||||
existed = CommonData.get_by(first=True, to_dict=False, id=_id)
|
||||
if not existed:
|
||||
abort(404, ErrFormat.common_data_not_found.format(_id))
|
||||
try:
|
||||
existed.soft_delete()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
abort(400, str(e))
|
||||
|
||||
@staticmethod
|
||||
def encrypt(data) -> str:
|
||||
if type(data) is dict:
|
||||
try:
|
||||
data = json.dumps(data)
|
||||
except Exception as e:
|
||||
abort(400, str(e))
|
||||
return AESCrypto().encrypt(data)
|
||||
|
||||
@staticmethod
|
||||
def decrypt(data) -> str:
|
||||
return AESCrypto().decrypt(data)
|
||||
|
||||
@staticmethod
|
||||
def get_enable_list():
|
||||
all_records = CommonData.query.filter(
|
||||
CommonData.data_type.in_(AuthenticateType.all()),
|
||||
CommonData.deleted == 0
|
||||
).all()
|
||||
enable_list = []
|
||||
for auth_type in AuthenticateType.all():
|
||||
record = list(filter(lambda x: x.data_type == auth_type, all_records))
|
||||
if not record:
|
||||
config = current_app.config.get(auth_type, None)
|
||||
if not config:
|
||||
continue
|
||||
|
||||
if config.get('enable', False):
|
||||
enable_list.append(dict(
|
||||
auth_type=auth_type,
|
||||
))
|
||||
|
||||
continue
|
||||
|
||||
try:
|
||||
decrypt_data = json.loads(AuthenticateDataCRUD.decrypt(record[0].data))
|
||||
except Exception as e:
|
||||
current_app.logger.error(e)
|
||||
continue
|
||||
|
||||
if decrypt_data.get('enable', 0) == 1:
|
||||
enable_list.append(dict(
|
||||
auth_type=auth_type,
|
||||
))
|
||||
|
||||
auth_auto_redirect = CommonDataCRUD.get_auth_auto_redirect_enable()
|
||||
|
||||
return dict(
|
||||
enable_list=enable_list,
|
||||
auth_auto_redirect=auth_auto_redirect,
|
||||
)
|
||||
|
||||
def test(self, test_type, data):
|
||||
type_lower = self._type.lower()
|
||||
func_name = f'test_{type_lower}'
|
||||
if hasattr(self, func_name):
|
||||
try:
|
||||
return getattr(self, f'test_{type_lower}')(test_type, data)
|
||||
except Exception as e:
|
||||
abort(400, str(e))
|
||||
abort(400, ErrFormat.not_support_test.format(self._type))
|
||||
|
||||
@staticmethod
|
||||
def test_ldap(test_type, data):
|
||||
ldap_server = data.get('ldap_server')
|
||||
ldap_user_dn = data.get('ldap_user_dn', '{}')
|
||||
|
||||
server = Server(ldap_server, connect_timeout=2)
|
||||
if not server.check_availability():
|
||||
raise Exception(ErrFormat.ldap_server_connect_not_available)
|
||||
else:
|
||||
if test_type == TestType.Connect:
|
||||
return True
|
||||
|
||||
username = data.get('username', None)
|
||||
if not username:
|
||||
raise Exception(ErrFormat.ldap_test_username_required)
|
||||
user = ldap_user_dn.format(username)
|
||||
password = data.get('password', None)
|
||||
|
||||
try:
|
||||
Connection(server, user=user, password=password, auto_bind=AUTO_BIND_NO_TLS)
|
||||
except LDAPBindError:
|
||||
ldap_domain = data.get('ldap_domain')
|
||||
user_with_domain = f"{username}@{ldap_domain}"
|
||||
try:
|
||||
Connection(server, user=user_with_domain, password=password, auto_bind=AUTO_BIND_NO_TLS)
|
||||
except Exception as e:
|
||||
raise Exception(ErrFormat.ldap_test_unknown_error.format(str(e)))
|
||||
|
||||
except LDAPSocketOpenError:
|
||||
raise Exception(ErrFormat.ldap_server_connect_timeout)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(ErrFormat.ldap_test_unknown_error.format(str(e)))
|
||||
|
||||
return True
|
||||
|
@@ -1,4 +1,6 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from api.extensions import cache
|
||||
from api.models.common_setting import CompanyInfo
|
||||
|
||||
@@ -11,6 +13,7 @@ class CompanyInfoCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def create(**kwargs):
|
||||
CompanyInfoCRUD.check_data(**kwargs)
|
||||
res = CompanyInfo.create(**kwargs)
|
||||
CompanyInfoCache.refresh(res.info)
|
||||
return res
|
||||
@@ -22,10 +25,26 @@ class CompanyInfoCRUD(object):
|
||||
if not existed:
|
||||
existed = CompanyInfoCRUD.create(**kwargs)
|
||||
else:
|
||||
CompanyInfoCRUD.check_data(**kwargs)
|
||||
existed = existed.update(**kwargs)
|
||||
CompanyInfoCache.refresh(existed.info)
|
||||
return existed
|
||||
|
||||
@staticmethod
|
||||
def check_data(**kwargs):
|
||||
info = kwargs.get('info', {})
|
||||
info['messenger'] = CompanyInfoCRUD.check_messenger(info.get('messenger', None))
|
||||
|
||||
kwargs['info'] = info
|
||||
|
||||
@staticmethod
|
||||
def check_messenger(messenger):
|
||||
if not messenger:
|
||||
return messenger
|
||||
|
||||
parsed_url = urlparse(messenger)
|
||||
return f"{parsed_url.scheme}://{parsed_url.netloc}"
|
||||
|
||||
|
||||
class CompanyInfoCache(object):
|
||||
key = 'CompanyInfoCache::'
|
||||
@@ -41,4 +60,4 @@ class CompanyInfoCache(object):
|
||||
|
||||
@classmethod
|
||||
def refresh(cls, info):
|
||||
cache.set(cls.key, info)
|
||||
cache.set(cls.key, info)
|
||||
|
@@ -19,3 +19,19 @@ BotNameMap = {
|
||||
'feishuApp': 'feishuBot',
|
||||
'dingdingApp': 'dingdingBot',
|
||||
}
|
||||
|
||||
|
||||
class AuthenticateType(BaseEnum):
|
||||
CAS = 'CAS'
|
||||
OAUTH2 = 'OAUTH2'
|
||||
OIDC = 'OIDC'
|
||||
LDAP = 'LDAP'
|
||||
|
||||
|
||||
AuthCommonConfig = 'AuthCommonConfig'
|
||||
AuthCommonConfigAutoRedirect = 'auto_redirect'
|
||||
|
||||
|
||||
class TestType(BaseEnum):
|
||||
Connect = 'connect'
|
||||
Login = 'login'
|
||||
|
@@ -15,10 +15,13 @@ from wtforms import validators
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.common_setting.const import COMMON_SETTING_QUEUE, OperatorType
|
||||
from api.lib.common_setting.const import OperatorType
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.models.common_setting import Employee, Department
|
||||
|
||||
from api.tasks.common_setting import refresh_employee_acl_info, edit_employee_department_in_acl
|
||||
|
||||
acl_user_columns = [
|
||||
'email',
|
||||
'mobile',
|
||||
@@ -137,7 +140,9 @@ class EmployeeCRUD(object):
|
||||
@staticmethod
|
||||
def add(**kwargs):
|
||||
try:
|
||||
return CreateEmployee().create_single(**kwargs)
|
||||
res = CreateEmployee().create_single(**kwargs)
|
||||
refresh_employee_acl_info.apply_async(args=(), queue=CMDB_QUEUE)
|
||||
return res
|
||||
except Exception as e:
|
||||
abort(400, str(e))
|
||||
|
||||
@@ -164,10 +169,9 @@ class EmployeeCRUD(object):
|
||||
existed.update(**kwargs)
|
||||
|
||||
if len(e_list) > 0:
|
||||
from api.tasks.common_setting import edit_employee_department_in_acl
|
||||
edit_employee_department_in_acl.apply_async(
|
||||
args=(e_list, new_department_id, current_user.uid),
|
||||
queue=COMMON_SETTING_QUEUE
|
||||
queue=CMDB_QUEUE
|
||||
)
|
||||
|
||||
return existed
|
||||
@@ -291,7 +295,7 @@ class EmployeeCRUD(object):
|
||||
employees = []
|
||||
for r in pagination.items:
|
||||
d = r.Employee.to_dict()
|
||||
d['department_name'] = r.Department.department_name
|
||||
d['department_name'] = r.Department.department_name if r.Department else ''
|
||||
employees.append(d)
|
||||
|
||||
return {
|
||||
@@ -437,7 +441,7 @@ class EmployeeCRUD(object):
|
||||
employees = []
|
||||
for r in pagination.items:
|
||||
d = r.Employee.to_dict()
|
||||
d['department_name'] = r.Department.department_name
|
||||
d['department_name'] = r.Department.department_name if r.Department else ''
|
||||
employees.append(d)
|
||||
|
||||
return {
|
||||
@@ -563,6 +567,7 @@ class EmployeeCRUD(object):
|
||||
for column in direct_columns:
|
||||
tmp[column] = d.get(column, '')
|
||||
notice_info = d.get('notice_info', {})
|
||||
notice_info = copy.deepcopy(notice_info) if notice_info else {}
|
||||
tmp.update(**notice_info)
|
||||
results.append(tmp)
|
||||
return results
|
||||
@@ -570,6 +575,7 @@ class EmployeeCRUD(object):
|
||||
@staticmethod
|
||||
def import_employee(employee_list):
|
||||
res = CreateEmployee().batch_create(employee_list)
|
||||
refresh_employee_acl_info.apply_async(args=(), queue=CMDB_QUEUE)
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
@@ -686,6 +692,27 @@ class EmployeeCRUD(object):
|
||||
else:
|
||||
abort(400, ErrFormat.column_name_not_support)
|
||||
|
||||
@staticmethod
|
||||
def update_last_login_by_uid(uid, last_login=None):
|
||||
employee = Employee.get_by(acl_uid=uid, first=True, to_dict=False)
|
||||
if not employee:
|
||||
return
|
||||
if last_login:
|
||||
try:
|
||||
last_login = datetime.strptime(last_login, '%Y-%m-%d %H:%M:%S')
|
||||
except Exception as e:
|
||||
last_login = datetime.now()
|
||||
else:
|
||||
last_login = datetime.now()
|
||||
|
||||
try:
|
||||
employee.update(
|
||||
last_login=last_login
|
||||
)
|
||||
return last_login
|
||||
except Exception as e:
|
||||
return
|
||||
|
||||
|
||||
def get_user_map(key='uid', acl=None):
|
||||
"""
|
||||
@@ -726,6 +753,7 @@ class CreateEmployee(object):
|
||||
try:
|
||||
existed = self.check_acl_user(user_data)
|
||||
if not existed:
|
||||
user_data['add_from'] = 'common'
|
||||
return self.acl.create_user(user_data)
|
||||
return existed
|
||||
except Exception as e:
|
||||
|
@@ -8,6 +8,9 @@ class ErrFormat(CommonErrFormat):
|
||||
|
||||
no_file_part = "没有文件部分"
|
||||
file_is_required = "文件是必须的"
|
||||
file_not_found = "文件不存在"
|
||||
file_type_not_allowed = "文件类型不允许"
|
||||
upload_failed = "上传失败: {}"
|
||||
|
||||
direct_supervisor_is_not_self = "直属上级不能是自己"
|
||||
parent_department_is_not_self = "上级部门不能是自己"
|
||||
@@ -56,6 +59,7 @@ class ErrFormat(CommonErrFormat):
|
||||
email_send_timeout = "邮件发送超时"
|
||||
|
||||
common_data_not_found = "ID {} 找不到记录"
|
||||
common_data_already_existed = "{} 已存在"
|
||||
notice_platform_existed = "{} 已存在"
|
||||
notice_not_existed = "{} 配置项不存在"
|
||||
notice_please_config_messenger_first = "请先配置 messenger"
|
||||
@@ -63,3 +67,11 @@ class ErrFormat(CommonErrFormat):
|
||||
notice_bind_failed = "绑定失败: {}"
|
||||
notice_bind_success = "绑定成功"
|
||||
notice_remove_bind_success = "解绑成功"
|
||||
|
||||
not_support_test = "不支持的测试类型: {}"
|
||||
not_support_auth_type = "不支持的认证类型: {}"
|
||||
ldap_server_connect_timeout = "LDAP服务器连接超时"
|
||||
ldap_server_connect_not_available = "LDAP服务器连接不可用"
|
||||
ldap_test_unknown_error = "LDAP测试未知错误: {}"
|
||||
common_data_not_support_auth_type = "通用数据不支持auth类型: {}"
|
||||
ldap_test_username_required = "LDAP测试用户名必填"
|
||||
|
@@ -1,6 +1,14 @@
|
||||
import base64
|
||||
import uuid
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
from flask import abort, current_app
|
||||
import lz4.frame
|
||||
|
||||
from api.lib.common_setting.utils import get_cur_time_str
|
||||
from api.models.common_setting import CommonFile
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
|
||||
|
||||
def allowed_file(filename, allowed_extensions):
|
||||
@@ -14,3 +22,73 @@ def generate_new_file_name(name):
|
||||
cur_str = get_cur_time_str('_')
|
||||
|
||||
return f"{prev_name}_{cur_str}_{uid}.{ext}"
|
||||
|
||||
|
||||
class CommonFileCRUD:
|
||||
@staticmethod
|
||||
def add_file(**kwargs):
|
||||
return CommonFile.create(**kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_file(file_name, to_str=False):
|
||||
existed = CommonFile.get_by(file_name=file_name, first=True, to_dict=False)
|
||||
if not existed:
|
||||
abort(400, ErrFormat.file_not_found)
|
||||
|
||||
uncompressed_data = lz4.frame.decompress(existed.binary)
|
||||
|
||||
return base64.b64encode(uncompressed_data).decode('utf-8') if to_str else BytesIO(uncompressed_data)
|
||||
|
||||
@staticmethod
|
||||
def sync_file_to_db():
|
||||
for p in ['UPLOAD_DIRECTORY_FULL']:
|
||||
upload_path = current_app.config.get(p, None)
|
||||
if not upload_path:
|
||||
continue
|
||||
for root, dirs, files in os.walk(upload_path):
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
if not os.path.isfile(file_path):
|
||||
continue
|
||||
|
||||
existed = CommonFile.get_by(file_name=file, first=True, to_dict=False)
|
||||
if existed:
|
||||
continue
|
||||
with open(file_path, 'rb') as f:
|
||||
data = f.read()
|
||||
compressed_data = lz4.frame.compress(data)
|
||||
try:
|
||||
CommonFileCRUD.add_file(
|
||||
origin_name=file,
|
||||
file_name=file,
|
||||
binary=compressed_data
|
||||
)
|
||||
|
||||
current_app.logger.info(f'sync file {file} to db')
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'sync file {file} to db error: {e}')
|
||||
|
||||
def get_file_binary_str(self, file_name):
|
||||
return self.get_file(file_name, True)
|
||||
|
||||
def save_str_to_file(self, file_name, str_data):
|
||||
try:
|
||||
self.get_file(file_name)
|
||||
current_app.logger.info(f'file {file_name} already exists')
|
||||
return
|
||||
except Exception as e:
|
||||
# file not found
|
||||
pass
|
||||
|
||||
bytes_data = base64.b64decode(str_data)
|
||||
compressed_data = lz4.frame.compress(bytes_data)
|
||||
|
||||
try:
|
||||
self.add_file(
|
||||
origin_name=file_name,
|
||||
file_name=file_name,
|
||||
binary=compressed_data
|
||||
)
|
||||
current_app.logger.info(f'save_str_to_file {file_name} success')
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"save_str_to_file error: {e}")
|
||||
|
@@ -94,7 +94,7 @@ class CRUDMixin(FormatMixin):
|
||||
if any((isinstance(_id, six.string_types) and _id.isdigit(),
|
||||
isinstance(_id, (six.integer_types, float))), ):
|
||||
obj = getattr(cls, "query").get(int(_id))
|
||||
if obj and not obj.deleted:
|
||||
if obj and not getattr(obj, 'deleted', False):
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
|
@@ -117,15 +117,15 @@ class ACLManager(object):
|
||||
if group:
|
||||
PermissionCRUD.grant(role.id, permissions, group_id=group.id)
|
||||
|
||||
def grant_resource_to_role_by_rid(self, name, rid, resource_type_name=None, permissions=None):
|
||||
def grant_resource_to_role_by_rid(self, name, rid, resource_type_name=None, permissions=None, rebuild=True):
|
||||
resource = self._get_resource(name, resource_type_name)
|
||||
|
||||
if resource:
|
||||
PermissionCRUD.grant(rid, permissions, resource_id=resource.id)
|
||||
PermissionCRUD.grant(rid, permissions, resource_id=resource.id, rebuild=rebuild)
|
||||
else:
|
||||
group = self._get_resource_group(name)
|
||||
if group:
|
||||
PermissionCRUD.grant(rid, permissions, group_id=group.id)
|
||||
PermissionCRUD.grant(rid, permissions, group_id=group.id, rebuild=rebuild)
|
||||
|
||||
def revoke_resource_from_role(self, name, role, resource_type_name=None, permissions=None):
|
||||
resource = self._get_resource(name, resource_type_name)
|
||||
@@ -138,20 +138,20 @@ class ACLManager(object):
|
||||
if group:
|
||||
PermissionCRUD.revoke(role.id, permissions, group_id=group.id)
|
||||
|
||||
def revoke_resource_from_role_by_rid(self, name, rid, resource_type_name=None, permissions=None):
|
||||
def revoke_resource_from_role_by_rid(self, name, rid, resource_type_name=None, permissions=None, rebuild=True):
|
||||
resource = self._get_resource(name, resource_type_name)
|
||||
|
||||
if resource:
|
||||
PermissionCRUD.revoke(rid, permissions, resource_id=resource.id)
|
||||
PermissionCRUD.revoke(rid, permissions, resource_id=resource.id, rebuild=rebuild)
|
||||
else:
|
||||
group = self._get_resource_group(name)
|
||||
if group:
|
||||
PermissionCRUD.revoke(rid, permissions, group_id=group.id)
|
||||
PermissionCRUD.revoke(rid, permissions, group_id=group.id, rebuild=rebuild)
|
||||
|
||||
def del_resource(self, name, resource_type_name=None):
|
||||
resource = self._get_resource(name, resource_type_name)
|
||||
if resource:
|
||||
ResourceCRUD.delete(resource.id)
|
||||
return ResourceCRUD.delete(resource.id)
|
||||
|
||||
def has_permission(self, resource_name, resource_type, perm, resource_id=None):
|
||||
if is_app_admin(self.app_id):
|
||||
|
@@ -1,14 +1,19 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import datetime
|
||||
import itertools
|
||||
import json
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
|
||||
from flask import has_request_context, request
|
||||
from flask import has_request_context
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
from sqlalchemy import func
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.perm.acl import AppCache
|
||||
from api.models.acl import AuditLoginLog
|
||||
from api.models.acl import AuditPermissionLog
|
||||
from api.models.acl import AuditResourceLog
|
||||
from api.models.acl import AuditRoleLog
|
||||
@@ -283,6 +288,27 @@ class AuditCRUD(object):
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def search_login(_, q=None, page=1, page_size=10, start=None, end=None):
|
||||
query = db.session.query(AuditLoginLog)
|
||||
|
||||
if start:
|
||||
query = query.filter(AuditLoginLog.login_at >= start)
|
||||
if end:
|
||||
query = query.filter(AuditLoginLog.login_at <= end)
|
||||
|
||||
if q:
|
||||
query = query.filter(AuditLoginLog.username == q)
|
||||
|
||||
records = query.order_by(
|
||||
AuditLoginLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||
|
||||
data = {
|
||||
'data': [r.to_dict() for r in records],
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def add_role_log(cls, app_id, operate_type: AuditOperateType,
|
||||
scope: AuditScope, link_id: int, origin: dict, current: dict, extra: dict,
|
||||
@@ -348,3 +374,30 @@ class AuditCRUD(object):
|
||||
AuditTriggerLog.create(app_id=app_id, trigger_id=trigger_id, operate_uid=user_id,
|
||||
operate_type=operate_type.value,
|
||||
origin=origin, current=current, extra=extra, source=source.value)
|
||||
|
||||
@classmethod
|
||||
def add_login_log(cls, username, is_ok, description, _id=None, logout_at=None):
|
||||
if _id is not None:
|
||||
existed = AuditLoginLog.get_by_id(_id)
|
||||
if existed is not None:
|
||||
existed.update(logout_at=logout_at)
|
||||
return
|
||||
|
||||
payload = dict(username=username,
|
||||
is_ok=is_ok,
|
||||
description=description,
|
||||
logout_at=logout_at,
|
||||
ip=request.headers.get('X-Real-IP') or request.remote_addr,
|
||||
browser=request.headers.get('User-Agent'),
|
||||
)
|
||||
|
||||
if logout_at is None:
|
||||
payload['login_at'] = datetime.datetime.now()
|
||||
|
||||
try:
|
||||
from api.lib.common_setting.employee import EmployeeCRUD
|
||||
EmployeeCRUD.update_last_login_by_uid(current_user.uid)
|
||||
except:
|
||||
pass
|
||||
|
||||
return AuditLoginLog.create(**payload).id
|
||||
|
@@ -328,6 +328,8 @@ class ResourceCRUD(object):
|
||||
AuditCRUD.add_resource_log(resource.app_id, AuditOperateType.delete,
|
||||
AuditScope.resource, resource.id, origin, {}, {})
|
||||
|
||||
return rebuilds
|
||||
|
||||
@classmethod
|
||||
def delete_by_name(cls, name, type_id, app_id):
|
||||
resource = Resource.get_by(name=name, resource_type_id=type_id, app_id=app_id) or abort(
|
||||
|
@@ -4,6 +4,9 @@ from api.lib.resp_format import CommonErrFormat
|
||||
|
||||
|
||||
class ErrFormat(CommonErrFormat):
|
||||
login_succeed = "登录成功"
|
||||
ldap_connection_failed = "连接LDAP服务失败"
|
||||
invalid_password = "密码验证失败"
|
||||
auth_only_with_app_token_failed = "应用 Token验证失败"
|
||||
session_invalid = "您不是应用管理员 或者 session失效(尝试一下退出重新登录)"
|
||||
|
||||
@@ -17,11 +20,11 @@ class ErrFormat(CommonErrFormat):
|
||||
role_exists = "角色 {} 已经存在!"
|
||||
global_role_not_found = "全局角色 {} 不存在!"
|
||||
global_role_exists = "全局角色 {} 已经存在!"
|
||||
user_role_delete_invalid = "删除用户角色, 请在 用户管理 页面操作!"
|
||||
|
||||
resource_no_permission = "您没有资源: {} 的 {} 权限"
|
||||
admin_required = "需要管理员权限"
|
||||
role_required = "需要角色: {}"
|
||||
user_role_delete_invalid = "删除用户角色, 请在 用户管理 页面操作!"
|
||||
|
||||
app_is_ready_existed = "应用 {} 已经存在"
|
||||
app_not_found = "应用 {} 不存在!"
|
||||
|
@@ -41,6 +41,7 @@ class UserCRUD(object):
|
||||
|
||||
@classmethod
|
||||
def add(cls, **kwargs):
|
||||
add_from = kwargs.pop('add_from', None)
|
||||
existed = User.get_by(username=kwargs['username'])
|
||||
existed and abort(400, ErrFormat.user_exists.format(kwargs['username']))
|
||||
|
||||
@@ -62,10 +63,12 @@ class UserCRUD(object):
|
||||
AuditCRUD.add_role_log(None, AuditOperateType.create,
|
||||
AuditScope.user, user.uid, {}, user.to_dict(), {}, {}
|
||||
)
|
||||
from api.lib.common_setting.employee import EmployeeCRUD
|
||||
payload = {column: getattr(user, column) for column in ['uid', 'username', 'nickname', 'email', 'block']}
|
||||
payload['rid'] = role.id
|
||||
EmployeeCRUD.add_employee_from_acl_created(**payload)
|
||||
|
||||
if add_from != 'common':
|
||||
from api.lib.common_setting.employee import EmployeeCRUD
|
||||
payload = {column: getattr(user, column) for column in ['uid', 'username', 'nickname', 'email', 'block']}
|
||||
payload['rid'] = role.id
|
||||
EmployeeCRUD.add_employee_from_acl_created(**payload)
|
||||
|
||||
return user
|
||||
|
||||
|
@@ -93,6 +93,9 @@ def _auth_with_token():
|
||||
|
||||
|
||||
def _auth_with_ip_white_list():
|
||||
if request.url.endswith("acl/users/info"):
|
||||
return False
|
||||
|
||||
ip = request.headers.get('X-Real-IP') or request.remote_addr
|
||||
key = request.values.get('_key')
|
||||
secret = request.values.get('_secret')
|
||||
|
1
cmdb-api/api/lib/perm/authentication/__init__.py
Normal file
1
cmdb-api/api/lib/perm/authentication/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# -*- coding:utf-8 -*-
|
@@ -15,7 +15,7 @@ try:
|
||||
except ImportError:
|
||||
from flask import _request_ctx_stack as stack
|
||||
|
||||
from api.flask_cas import routing
|
||||
from . import routing
|
||||
|
||||
|
||||
class CAS(object):
|
@@ -119,4 +119,4 @@ def create_cas_validate_url(cas_url, cas_route, service, ticket,
|
||||
('service', service),
|
||||
('ticket', ticket),
|
||||
('renew', renew),
|
||||
)
|
||||
)
|
@@ -1,14 +1,24 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import json
|
||||
import datetime
|
||||
import uuid
|
||||
|
||||
import bs4
|
||||
from flask import Blueprint
|
||||
from flask import current_app, session, request, url_for, redirect
|
||||
from flask_login import login_user, logout_user
|
||||
from flask import current_app
|
||||
from flask import redirect
|
||||
from flask import request
|
||||
from flask import session
|
||||
from flask import url_for
|
||||
from flask_login import login_user
|
||||
from flask_login import logout_user
|
||||
from six.moves.urllib.parse import urlparse
|
||||
from six.moves.urllib_request import urlopen
|
||||
|
||||
from api.lib.common_setting.common_data import AuthenticateDataCRUD
|
||||
from api.lib.common_setting.const import AuthenticateType
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.perm.acl.resp_format import ErrFormat
|
||||
from .cas_urls import create_cas_login_url
|
||||
from .cas_urls import create_cas_logout_url
|
||||
from .cas_urls import create_cas_validate_url
|
||||
@@ -16,6 +26,7 @@ from .cas_urls import create_cas_validate_url
|
||||
blueprint = Blueprint('cas', __name__)
|
||||
|
||||
|
||||
@blueprint.route('/api/cas/login')
|
||||
@blueprint.route('/api/sso/login')
|
||||
def login():
|
||||
"""
|
||||
@@ -29,16 +40,20 @@ def login():
|
||||
If validation was successful the logged in username is saved in
|
||||
the user's session under the key `CAS_USERNAME_SESSION_KEY`.
|
||||
"""
|
||||
config = AuthenticateDataCRUD(AuthenticateType.CAS).get()
|
||||
|
||||
cas_token_session_key = current_app.config['CAS_TOKEN_SESSION_KEY']
|
||||
if request.values.get("next"):
|
||||
session["next"] = request.values.get("next")
|
||||
|
||||
_service = url_for('cas.login', _external=True, next=session["next"]) \
|
||||
if session.get("next") else url_for('cas.login', _external=True)
|
||||
# _service = url_for('cas.login', _external=True)
|
||||
_service = "{}://{}{}".format(urlparse(request.referrer).scheme,
|
||||
urlparse(request.referrer).netloc,
|
||||
url_for('cas.login'))
|
||||
|
||||
redirect_url = create_cas_login_url(
|
||||
current_app.config['CAS_SERVER'],
|
||||
current_app.config['CAS_LOGIN_ROUTE'],
|
||||
config['cas_server'],
|
||||
config['cas_login_route'],
|
||||
_service)
|
||||
|
||||
if 'ticket' in request.args:
|
||||
@@ -47,30 +62,38 @@ def login():
|
||||
if request.args.get('ticket'):
|
||||
|
||||
if validate(request.args['ticket']):
|
||||
redirect_url = session.get("next") or \
|
||||
current_app.config.get("CAS_AFTER_LOGIN")
|
||||
redirect_url = session.get("next") or config.get("cas_after_login") or "/"
|
||||
username = session.get("CAS_USERNAME")
|
||||
user = UserCache.get(username)
|
||||
login_user(user)
|
||||
|
||||
session.permanent = True
|
||||
|
||||
_id = AuditCRUD.add_login_log(username, True, ErrFormat.login_succeed)
|
||||
session['LOGIN_ID'] = _id
|
||||
|
||||
else:
|
||||
del session[cas_token_session_key]
|
||||
redirect_url = create_cas_login_url(
|
||||
current_app.config['CAS_SERVER'],
|
||||
current_app.config['CAS_LOGIN_ROUTE'],
|
||||
config['cas_server'],
|
||||
config['cas_login_route'],
|
||||
url_for('cas.login', _external=True),
|
||||
renew=True)
|
||||
|
||||
AuditCRUD.add_login_log(session.get("CAS_USERNAME"), False, ErrFormat.invalid_password)
|
||||
|
||||
current_app.logger.info("redirect to: {0}".format(redirect_url))
|
||||
return redirect(redirect_url)
|
||||
|
||||
|
||||
@blueprint.route('/api/cas/logout')
|
||||
@blueprint.route('/api/sso/logout')
|
||||
def logout():
|
||||
"""
|
||||
When the user accesses this route they are logged out.
|
||||
"""
|
||||
config = AuthenticateDataCRUD(AuthenticateType.CAS).get()
|
||||
current_app.logger.info(config)
|
||||
|
||||
cas_username_session_key = current_app.config['CAS_USERNAME_SESSION_KEY']
|
||||
cas_token_session_key = current_app.config['CAS_TOKEN_SESSION_KEY']
|
||||
@@ -82,12 +105,14 @@ def logout():
|
||||
"next" in session and session.pop("next")
|
||||
|
||||
redirect_url = create_cas_logout_url(
|
||||
current_app.config['CAS_SERVER'],
|
||||
current_app.config['CAS_LOGOUT_ROUTE'],
|
||||
config['cas_server'],
|
||||
config['cas_logout_route'],
|
||||
url_for('cas.login', _external=True, next=request.referrer))
|
||||
|
||||
logout_user()
|
||||
|
||||
AuditCRUD.add_login_log(None, None, None, _id=session.get('LOGIN_ID'), logout_at=datetime.datetime.now())
|
||||
|
||||
current_app.logger.debug('Redirecting to: {0}'.format(redirect_url))
|
||||
|
||||
return redirect(redirect_url)
|
||||
@@ -100,14 +125,15 @@ def validate(ticket):
|
||||
and the validated username is saved in the session under the
|
||||
key `CAS_USERNAME_SESSION_KEY`.
|
||||
"""
|
||||
config = AuthenticateDataCRUD(AuthenticateType.CAS).get()
|
||||
|
||||
cas_username_session_key = current_app.config['CAS_USERNAME_SESSION_KEY']
|
||||
|
||||
current_app.logger.debug("validating token {0}".format(ticket))
|
||||
|
||||
cas_validate_url = create_cas_validate_url(
|
||||
current_app.config['CAS_VALIDATE_SERVER'],
|
||||
current_app.config['CAS_VALIDATE_ROUTE'],
|
||||
config['cas_validate_server'],
|
||||
config['cas_validate_route'],
|
||||
url_for('cas.login', _external=True),
|
||||
ticket)
|
||||
|
||||
@@ -115,23 +141,35 @@ def validate(ticket):
|
||||
|
||||
try:
|
||||
response = urlopen(cas_validate_url).read()
|
||||
ticketid = _parse_tag(response, "cas:user")
|
||||
strs = [s.strip() for s in ticketid.split('|') if s.strip()]
|
||||
ticket_id = _parse_tag(response, "cas:user")
|
||||
strs = [s.strip() for s in ticket_id.split('|') if s.strip()]
|
||||
username, is_valid = None, False
|
||||
if len(strs) == 1:
|
||||
username = strs[0]
|
||||
is_valid = True
|
||||
user_info = json.loads(_parse_tag(response, "cas:other"))
|
||||
current_app.logger.info(user_info)
|
||||
except ValueError:
|
||||
current_app.logger.error("CAS returned unexpected result")
|
||||
is_valid = False
|
||||
return is_valid
|
||||
|
||||
if is_valid:
|
||||
current_app.logger.debug("valid")
|
||||
current_app.logger.debug("{}: {}".format(cas_username_session_key, username))
|
||||
session[cas_username_session_key] = username
|
||||
user = UserCache.get(username)
|
||||
if user is None:
|
||||
current_app.logger.info("create user: {}".format(username))
|
||||
from api.lib.perm.acl.user import UserCRUD
|
||||
soup = bs4.BeautifulSoup(response)
|
||||
cas_user_map = config.get('cas_user_map')
|
||||
user_dict = dict()
|
||||
for k in cas_user_map:
|
||||
v = soup.find(cas_user_map[k]['tag'], cas_user_map[k].get('attrs', {}))
|
||||
user_dict[k] = v and v.text or None
|
||||
user_dict['password'] = uuid.uuid4().hex
|
||||
if "email" not in user_dict:
|
||||
user_dict['email'] = username
|
||||
|
||||
UserCRUD.add(**user_dict)
|
||||
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
user_info = ACLManager.get_user_info(username)
|
||||
@@ -164,4 +202,5 @@ def _parse_tag(string, tag):
|
||||
|
||||
if soup.find(tag) is None:
|
||||
return ''
|
||||
|
||||
return soup.find(tag).string.strip()
|
67
cmdb-api/api/lib/perm/authentication/ldap.py
Normal file
67
cmdb-api/api/lib/perm/authentication/ldap.py
Normal file
@@ -0,0 +1,67 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import uuid
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import session
|
||||
from ldap3 import ALL
|
||||
from ldap3 import AUTO_BIND_NO_TLS
|
||||
from ldap3 import Connection
|
||||
from ldap3 import Server
|
||||
from ldap3.core.exceptions import LDAPBindError
|
||||
from ldap3.core.exceptions import LDAPCertificateError
|
||||
from ldap3.core.exceptions import LDAPSocketOpenError
|
||||
|
||||
from api.lib.common_setting.common_data import AuthenticateDataCRUD
|
||||
from api.lib.common_setting.const import AuthenticateType
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
from api.lib.perm.acl.resp_format import ErrFormat
|
||||
from api.models.acl import User
|
||||
|
||||
|
||||
def authenticate_with_ldap(username, password):
|
||||
config = AuthenticateDataCRUD(AuthenticateType.LDAP).get()
|
||||
|
||||
server = Server(config.get('ldap_server'), get_info=ALL, connect_timeout=3)
|
||||
if '@' in username:
|
||||
email = username
|
||||
who = config.get('ldap_user_dn').format(username.split('@')[0])
|
||||
else:
|
||||
who = config.get('ldap_user_dn').format(username)
|
||||
email = "{}@{}".format(who, config.get('ldap_domain'))
|
||||
|
||||
username = username.split('@')[0]
|
||||
user = User.query.get_by_username(username)
|
||||
try:
|
||||
if not password:
|
||||
raise LDAPCertificateError
|
||||
|
||||
try:
|
||||
conn = Connection(server, user=who, password=password, auto_bind=AUTO_BIND_NO_TLS)
|
||||
except LDAPBindError:
|
||||
conn = Connection(server,
|
||||
user=f"{username}@{config.get('ldap_domain')}",
|
||||
password=password,
|
||||
auto_bind=AUTO_BIND_NO_TLS)
|
||||
|
||||
if conn.result['result'] != 0:
|
||||
AuditCRUD.add_login_log(username, False, ErrFormat.invalid_password)
|
||||
raise LDAPBindError
|
||||
else:
|
||||
_id = AuditCRUD.add_login_log(username, True, ErrFormat.login_succeed)
|
||||
session['LOGIN_ID'] = _id
|
||||
|
||||
if not user:
|
||||
from api.lib.perm.acl.user import UserCRUD
|
||||
user = UserCRUD.add(username=username, email=email, password=uuid.uuid4().hex)
|
||||
|
||||
return user, True
|
||||
|
||||
except LDAPBindError as e:
|
||||
current_app.logger.info(e)
|
||||
return user, False
|
||||
|
||||
except LDAPSocketOpenError as e:
|
||||
current_app.logger.info(e)
|
||||
return abort(403, ErrFormat.ldap_connection_failed)
|
30
cmdb-api/api/lib/perm/authentication/oauth2/__init__.py
Normal file
30
cmdb-api/api/lib/perm/authentication/oauth2/__init__.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from . import routing
|
||||
|
||||
|
||||
class OAuth2(object):
|
||||
def __init__(self, app=None, url_prefix=None):
|
||||
self._app = app
|
||||
if app is not None:
|
||||
self.init_app(app, url_prefix)
|
||||
|
||||
@staticmethod
|
||||
def init_app(app, url_prefix=None):
|
||||
# Configuration defaults
|
||||
app.config.setdefault('OAUTH2_GRANT_TYPE', 'authorization_code')
|
||||
app.config.setdefault('OAUTH2_RESPONSE_TYPE', 'code')
|
||||
app.config.setdefault('OAUTH2_AFTER_LOGIN', '/')
|
||||
|
||||
app.config.setdefault('OIDC_GRANT_TYPE', 'authorization_code')
|
||||
app.config.setdefault('OIDC_RESPONSE_TYPE', 'code')
|
||||
app.config.setdefault('OIDC_AFTER_LOGIN', '/')
|
||||
|
||||
# Register Blueprint
|
||||
app.register_blueprint(routing.blueprint, url_prefix=url_prefix)
|
||||
|
||||
@property
|
||||
def app(self):
|
||||
return self._app or current_app
|
139
cmdb-api/api/lib/perm/authentication/oauth2/routing.py
Normal file
139
cmdb-api/api/lib/perm/authentication/oauth2/routing.py
Normal file
@@ -0,0 +1,139 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import datetime
|
||||
import secrets
|
||||
import uuid
|
||||
|
||||
import requests
|
||||
from flask import Blueprint
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import redirect
|
||||
from flask import request
|
||||
from flask import session
|
||||
from flask import url_for
|
||||
from flask_login import login_user
|
||||
from flask_login import logout_user
|
||||
from six.moves.urllib.parse import urlencode
|
||||
from six.moves.urllib.parse import urlparse
|
||||
|
||||
from api.lib.common_setting.common_data import AuthenticateDataCRUD
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.perm.acl.resp_format import ErrFormat
|
||||
|
||||
blueprint = Blueprint('oauth2', __name__)
|
||||
|
||||
|
||||
@blueprint.route('/api/<string:auth_type>/login')
|
||||
def login(auth_type):
|
||||
config = AuthenticateDataCRUD(auth_type.upper()).get()
|
||||
|
||||
if request.values.get("next"):
|
||||
session["next"] = request.values.get("next")
|
||||
|
||||
session[f'{auth_type}_state'] = secrets.token_urlsafe(16)
|
||||
|
||||
auth_type = auth_type.upper()
|
||||
|
||||
redirect_uri = "{}://{}{}".format(urlparse(request.referrer).scheme,
|
||||
urlparse(request.referrer).netloc,
|
||||
url_for('oauth2.callback', auth_type=auth_type.lower()))
|
||||
qs = urlencode({
|
||||
'client_id': config['client_id'],
|
||||
'redirect_uri': redirect_uri,
|
||||
'response_type': current_app.config[f'{auth_type}_RESPONSE_TYPE'],
|
||||
'scope': ' '.join(config['scopes'] or []),
|
||||
'state': session[f'{auth_type.lower()}_state'],
|
||||
})
|
||||
|
||||
return redirect("{}?{}".format(config['authorize_url'].split('?')[0], qs))
|
||||
|
||||
|
||||
@blueprint.route('/api/<string:auth_type>/callback')
|
||||
def callback(auth_type):
|
||||
auth_type = auth_type.upper()
|
||||
config = AuthenticateDataCRUD(auth_type).get()
|
||||
|
||||
redirect_url = session.get("next") or config.get('after_login') or '/'
|
||||
|
||||
if request.values['state'] != session.get(f'{auth_type.lower()}_state'):
|
||||
return abort(401, "state is invalid")
|
||||
|
||||
if 'code' not in request.values:
|
||||
return abort(401, 'code is invalid')
|
||||
|
||||
response = requests.post(config['token_url'], data={
|
||||
'client_id': config['client_id'],
|
||||
'client_secret': config['client_secret'],
|
||||
'code': request.values['code'],
|
||||
'grant_type': current_app.config[f'{auth_type}_GRANT_TYPE'],
|
||||
'redirect_uri': url_for('oauth2.callback', auth_type=auth_type.lower(), _external=True),
|
||||
}, headers={'Accept': 'application/json'})
|
||||
if response.status_code != 200:
|
||||
current_app.logger.error(response.text)
|
||||
return abort(401)
|
||||
access_token = response.json().get('access_token')
|
||||
if not access_token:
|
||||
return abort(401)
|
||||
|
||||
response = requests.get(config['user_info']['url'], headers={
|
||||
'Authorization': 'Bearer {}'.format(access_token),
|
||||
'Accept': 'application/json',
|
||||
})
|
||||
if response.status_code != 200:
|
||||
return abort(401)
|
||||
|
||||
res = response.json()
|
||||
email = res.get(config['user_info']['email'])
|
||||
username = res.get(config['user_info']['username'])
|
||||
avatar = res.get(config['user_info'].get('avatar'))
|
||||
user = UserCache.get(username)
|
||||
if user is None:
|
||||
current_app.logger.info("create user: {}".format(username))
|
||||
from api.lib.perm.acl.user import UserCRUD
|
||||
|
||||
user_dict = dict(username=username, email=email, avatar=avatar)
|
||||
user_dict['password'] = uuid.uuid4().hex
|
||||
|
||||
user = UserCRUD.add(**user_dict)
|
||||
|
||||
# log the user in
|
||||
login_user(user)
|
||||
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
user_info = ACLManager.get_user_info(username)
|
||||
|
||||
session["acl"] = dict(uid=user_info.get("uid"),
|
||||
avatar=user.avatar if user else user_info.get("avatar"),
|
||||
userId=user_info.get("uid"),
|
||||
rid=user_info.get("rid"),
|
||||
userName=user_info.get("username"),
|
||||
nickName=user_info.get("nickname") or user_info.get("username"),
|
||||
parentRoles=user_info.get("parents"),
|
||||
childRoles=user_info.get("children"),
|
||||
roleName=user_info.get("role"))
|
||||
session["uid"] = user_info.get("uid")
|
||||
|
||||
_id = AuditCRUD.add_login_log(username, True, ErrFormat.login_succeed)
|
||||
session['LOGIN_ID'] = _id
|
||||
|
||||
return redirect(redirect_url)
|
||||
|
||||
|
||||
@blueprint.route('/api/<string:auth_type>/logout')
|
||||
def logout(auth_type):
|
||||
"acl" in session and session.pop("acl")
|
||||
"uid" in session and session.pop("uid")
|
||||
f'{auth_type}_state' in session and session.pop(f'{auth_type}_state')
|
||||
"next" in session and session.pop("next")
|
||||
|
||||
redirect_url = url_for('oauth2.login', auth_type=auth_type, _external=True, next=request.referrer)
|
||||
|
||||
logout_user()
|
||||
|
||||
current_app.logger.debug('Redirecting to: {0}'.format(redirect_url))
|
||||
|
||||
AuditCRUD.add_login_log(None, None, None, _id=session.get('LOGIN_ID'), logout_at=datetime.datetime.now())
|
||||
|
||||
return redirect(redirect_url)
|
@@ -38,7 +38,6 @@ def string_to_bytes(value):
|
||||
byte_string = value
|
||||
else:
|
||||
byte_string = value.encode("utf-8")
|
||||
|
||||
return byte_string
|
||||
|
||||
|
||||
@@ -314,7 +313,7 @@ class KeyManage:
|
||||
secrets_root_key = current_app.config.get("secrets_root_key")
|
||||
msg, ok = self.is_valid_root_key(secrets_root_key)
|
||||
if not ok:
|
||||
return {"message": msg, "status": "failed"}
|
||||
return true
|
||||
status = self.backend.get(backend_seal_key)
|
||||
return status == "block"
|
||||
|
||||
|
@@ -5,17 +5,18 @@ import copy
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
|
||||
from ldap3 import Server, Connection, ALL
|
||||
from ldap3.core.exceptions import LDAPBindError, LDAPCertificateError
|
||||
from flask import current_app
|
||||
from flask import session
|
||||
from flask_sqlalchemy import BaseQuery
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.database import CRUDModel
|
||||
from api.lib.database import Model
|
||||
from api.lib.database import Model2
|
||||
from api.lib.database import SoftDeleteMixin
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
from api.lib.perm.acl.const import OperateType
|
||||
from api.lib.perm.acl.resp_format import ErrFormat
|
||||
|
||||
|
||||
class App(Model):
|
||||
@@ -28,21 +29,26 @@ class App(Model):
|
||||
|
||||
|
||||
class UserQuery(BaseQuery):
|
||||
def _join(self, *args, **kwargs):
|
||||
super(UserQuery, self)._join(*args, **kwargs)
|
||||
|
||||
def authenticate(self, login, password):
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
|
||||
user = self.filter(db.or_(User.username == login,
|
||||
User.email == login)).filter(User.deleted.is_(False)).filter(User.block == 0).first()
|
||||
if user:
|
||||
current_app.logger.info(user)
|
||||
authenticated = user.check_password(password)
|
||||
if authenticated:
|
||||
from api.tasks.acl import op_record
|
||||
op_record.apply_async(args=(None, login, OperateType.LOGIN, ["ACL"]), queue=ACL_QUEUE)
|
||||
_id = AuditCRUD.add_login_log(login, True, ErrFormat.login_succeed)
|
||||
session['LOGIN_ID'] = _id
|
||||
else:
|
||||
AuditCRUD.add_login_log(login, False, ErrFormat.invalid_password)
|
||||
else:
|
||||
authenticated = False
|
||||
|
||||
AuditCRUD.add_login_log(login, False, ErrFormat.user_not_found.format(login))
|
||||
|
||||
current_app.logger.info(("login", login, user, authenticated))
|
||||
|
||||
return user, authenticated
|
||||
|
||||
def authenticate_with_key(self, key, secret, args, path):
|
||||
@@ -57,38 +63,6 @@ class UserQuery(BaseQuery):
|
||||
|
||||
return user, authenticated
|
||||
|
||||
def authenticate_with_ldap(self, username, password):
|
||||
server = Server(current_app.config.get('LDAP_SERVER'), get_info=ALL)
|
||||
if '@' in username:
|
||||
email = username
|
||||
who = current_app.config.get('LDAP_USER_DN').format(username.split('@')[0])
|
||||
else:
|
||||
who = current_app.config.get('LDAP_USER_DN').format(username)
|
||||
email = "{}@{}".format(who, current_app.config.get('LDAP_DOMAIN'))
|
||||
|
||||
username = username.split('@')[0]
|
||||
user = self.get_by_username(username)
|
||||
try:
|
||||
if not password:
|
||||
raise LDAPCertificateError
|
||||
|
||||
conn = Connection(server, user=who, password=password)
|
||||
conn.bind()
|
||||
if conn.result['result'] != 0:
|
||||
raise LDAPBindError
|
||||
conn.unbind()
|
||||
|
||||
if not user:
|
||||
from api.lib.perm.acl.user import UserCRUD
|
||||
user = UserCRUD.add(username=username, email=email)
|
||||
|
||||
from api.tasks.acl import op_record
|
||||
op_record.apply_async(args=(None, username, OperateType.LOGIN, ["ACL"]), queue=ACL_QUEUE)
|
||||
|
||||
return user, True
|
||||
except LDAPBindError:
|
||||
return user, False
|
||||
|
||||
def search(self, key):
|
||||
query = self.filter(db.or_(User.email == key,
|
||||
User.nickname.ilike('%' + key + '%'),
|
||||
@@ -138,6 +112,7 @@ class User(CRUDModel, SoftDeleteMixin):
|
||||
wx_id = db.Column(db.String(32))
|
||||
employee_id = db.Column(db.String(16), index=True)
|
||||
avatar = db.Column(db.String(128))
|
||||
|
||||
# apps = db.Column(db.JSON)
|
||||
|
||||
def __str__(self):
|
||||
@@ -168,11 +143,9 @@ class User(CRUDModel, SoftDeleteMixin):
|
||||
|
||||
|
||||
class RoleQuery(BaseQuery):
|
||||
def _join(self, *args, **kwargs):
|
||||
super(RoleQuery, self)._join(*args, **kwargs)
|
||||
|
||||
def authenticate(self, login, password):
|
||||
role = self.filter(Role.name == login).first()
|
||||
role = self.filter(Role.name == login).filter(Role.deleted.is_(False)).first()
|
||||
if role:
|
||||
authenticated = role.check_password(password)
|
||||
|
||||
@@ -377,3 +350,16 @@ class AuditTriggerLog(Model):
|
||||
current = db.Column(db.JSON, default=dict(), comment='当前数据')
|
||||
extra = db.Column(db.JSON, default=dict(), comment='权限名')
|
||||
source = db.Column(db.String(16), default='', comment='来源')
|
||||
|
||||
|
||||
class AuditLoginLog(Model2):
|
||||
__tablename__ = "acl_audit_login_logs"
|
||||
|
||||
username = db.Column(db.String(64), index=True)
|
||||
channel = db.Column(db.Enum('web', 'api'), default="web")
|
||||
ip = db.Column(db.String(15))
|
||||
browser = db.Column(db.String(256))
|
||||
description = db.Column(db.String(128))
|
||||
is_ok = db.Column(db.Boolean)
|
||||
login_at = db.Column(db.DateTime)
|
||||
logout_at = db.Column(db.DateTime)
|
||||
|
@@ -218,6 +218,8 @@ class CIRelation(Model):
|
||||
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
|
||||
more = db.Column(db.Integer, db.ForeignKey("c_cis.id"))
|
||||
|
||||
ancestor_ids = db.Column(db.String(128), index=True)
|
||||
|
||||
first_ci = db.relationship("CI", primaryjoin="CI.id==CIRelation.first_ci_id")
|
||||
second_ci = db.relationship("CI", primaryjoin="CI.id==CIRelation.second_ci_id")
|
||||
relation_type = db.relationship("RelationType", backref="c_ci_relations.relation_type_id")
|
||||
|
@@ -96,3 +96,11 @@ class NoticeConfig(Model):
|
||||
|
||||
platform = db.Column(db.VARCHAR(255), nullable=False)
|
||||
info = db.Column(db.JSON)
|
||||
|
||||
|
||||
class CommonFile(Model):
|
||||
__tablename__ = 'common_file'
|
||||
|
||||
file_name = db.Column(db.VARCHAR(512), nullable=False, index=True)
|
||||
origin_name = db.Column(db.VARCHAR(512), nullable=False)
|
||||
binary = db.Column(db.LargeBinary(16777216), nullable=False)
|
||||
|
@@ -25,10 +25,9 @@ from api.models.acl import Role
|
||||
from api.models.acl import Trigger
|
||||
|
||||
|
||||
@celery.task(base=QueueOnce,
|
||||
name="acl.role_rebuild",
|
||||
queue=ACL_QUEUE,
|
||||
once={"graceful": True, "unlock_before_run": True})
|
||||
@celery.task(name="acl.role_rebuild",
|
||||
queue=ACL_QUEUE,)
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def role_rebuild(rids, app_id):
|
||||
rids = rids if isinstance(rids, list) else [rids]
|
||||
@@ -190,18 +189,18 @@ def cancel_trigger(_id, resource_id=None, operator_uid=None):
|
||||
|
||||
@celery.task(name="acl.op_record", queue=ACL_QUEUE)
|
||||
@reconnect_db
|
||||
def op_record(app, rolename, operate_type, obj):
|
||||
def op_record(app, role_name, operate_type, obj):
|
||||
if isinstance(app, int):
|
||||
app = AppCache.get(app)
|
||||
app = app and app.name
|
||||
|
||||
if isinstance(rolename, int):
|
||||
u = UserCache.get(rolename)
|
||||
if isinstance(role_name, int):
|
||||
u = UserCache.get(role_name)
|
||||
if u:
|
||||
rolename = u.username
|
||||
role_name = u.username
|
||||
if not u:
|
||||
r = RoleCache.get(rolename)
|
||||
r = RoleCache.get(role_name)
|
||||
if r:
|
||||
rolename = r.name
|
||||
role_name = r.name
|
||||
|
||||
OperateRecordCRUD.add(app, rolename, operate_type, obj)
|
||||
OperateRecordCRUD.add(app, role_name, operate_type, obj)
|
||||
|
@@ -16,6 +16,7 @@ from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||
from api.lib.decorator import flush_db
|
||||
from api.lib.decorator import reconnect_db
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
@@ -97,16 +98,30 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
|
||||
@celery.task(name="cmdb.ci_relation_cache", queue=CMDB_QUEUE)
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def ci_relation_cache(parent_id, child_id):
|
||||
def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
||||
with Lock("CIRelation_{}".format(parent_id)):
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
if ancestor_ids is None:
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
|
||||
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, first=True, to_dict=False)
|
||||
if str(child_id) not in children:
|
||||
children[str(child_id)] = cr.second_ci.type_id
|
||||
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
|
||||
first=True, to_dict=False)
|
||||
if str(child_id) not in children:
|
||||
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:
|
||||
key = "{},{}".format(ancestor_ids, parent_id)
|
||||
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
||||
grandson = json.loads(grandson) if grandson is not None else {}
|
||||
|
||||
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
|
||||
first=True, to_dict=False)
|
||||
if cr and str(cr.second_ci_id) not in grandson:
|
||||
grandson[str(cr.second_ci_id)] = cr.second_ci.type_id
|
||||
|
||||
rd.create_or_update({key: json.dumps(grandson)}, REDIS_PREFIX_CI_RELATION2)
|
||||
|
||||
current_app.logger.info("ADD ci relation cache: {0} -> {1}".format(parent_id, child_id))
|
||||
|
||||
@@ -156,20 +171,31 @@ def ci_relation_add(parent_dict, child_id, uid):
|
||||
try:
|
||||
db.session.commit()
|
||||
except:
|
||||
pass
|
||||
db.session.rollback()
|
||||
|
||||
|
||||
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
|
||||
@reconnect_db
|
||||
def ci_relation_delete(parent_id, child_id):
|
||||
def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
||||
with Lock("CIRelation_{}".format(parent_id)):
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
if ancestor_ids is None:
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
|
||||
if str(child_id) in children:
|
||||
children.pop(str(child_id))
|
||||
if str(child_id) in children:
|
||||
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:
|
||||
key = "{},{}".format(ancestor_ids, parent_id)
|
||||
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
||||
grandson = json.loads(grandson) if grandson is not None else {}
|
||||
|
||||
if str(child_id) in grandson:
|
||||
grandson.pop(str(child_id))
|
||||
|
||||
rd.create_or_update({key: json.dumps(grandson)}, REDIS_PREFIX_CI_RELATION2)
|
||||
|
||||
current_app.logger.info("DELETE ci relation cache: {0} -> {1}".format(parent_id, child_id))
|
||||
|
||||
|
@@ -1,24 +1,24 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
import requests
|
||||
from flask import current_app
|
||||
|
||||
from api.extensions import celery
|
||||
from api.extensions import db
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.common_setting.const import COMMON_SETTING_QUEUE
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.models.common_setting import Department
|
||||
from api.models.common_setting import Department, Employee
|
||||
from api.lib.decorator import flush_db
|
||||
from api.lib.decorator import reconnect_db
|
||||
|
||||
|
||||
@celery.task(name="common_setting.edit_employee_department_in_acl", queue=COMMON_SETTING_QUEUE)
|
||||
@celery.task(name="common_setting.edit_employee_department_in_acl", queue=CMDB_QUEUE)
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
||||
"""
|
||||
:param e_list:{acl_rid: 11, department_id: 22}
|
||||
:param new_d_id
|
||||
:param op_uid
|
||||
"""
|
||||
db.session.remove()
|
||||
|
||||
result = []
|
||||
new_department = Department.get_by(
|
||||
first=True, department_id=new_d_id, to_dict=False)
|
||||
@@ -75,3 +75,41 @@ def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
||||
result.append(ErrFormat.acl_add_user_to_role_failed.format(str(e)))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@celery.task(name="common_setting.refresh_employee_acl_info", queue=CMDB_QUEUE)
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def refresh_employee_acl_info():
|
||||
acl = ACLManager('acl')
|
||||
role_map = {role['name']: role for role in acl.get_all_roles()}
|
||||
|
||||
criterion = [
|
||||
Employee.deleted == 0
|
||||
]
|
||||
query = Employee.query.filter(*criterion).order_by(
|
||||
Employee.created_at.desc()
|
||||
)
|
||||
|
||||
for em in query.all():
|
||||
if em.acl_uid and em.acl_rid:
|
||||
continue
|
||||
role = role_map.get(em.username, None)
|
||||
if not role:
|
||||
continue
|
||||
|
||||
params = dict()
|
||||
if not em.acl_uid:
|
||||
params['acl_uid'] = role.get('uid', 0)
|
||||
|
||||
if not em.acl_rid:
|
||||
params['acl_rid'] = role.get('id', 0)
|
||||
|
||||
try:
|
||||
em.update(**params)
|
||||
current_app.logger.info(
|
||||
f"refresh_employee_acl_info success, employee_id: {em.employee_id}, uid: {em.acl_uid}, "
|
||||
f"rid: {em.acl_rid}")
|
||||
except Exception as e:
|
||||
current_app.logger.error(str(e))
|
||||
continue
|
||||
|
@@ -24,6 +24,7 @@ class AuditLogView(APIView):
|
||||
'role': AuditCRUD.search_role,
|
||||
'trigger': AuditCRUD.search_trigger,
|
||||
'resource': AuditCRUD.search_resource,
|
||||
'login': AuditCRUD.search_login,
|
||||
}
|
||||
if name not in func_map:
|
||||
abort(400, f'wrong {name}, please use {func_map.keys()}')
|
||||
|
@@ -8,11 +8,15 @@ from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
from flask import session
|
||||
from flask_login import login_user, logout_user
|
||||
from flask_login import login_user
|
||||
from flask_login import logout_user
|
||||
|
||||
from api.lib.common_setting.common_data import AuthenticateDataCRUD
|
||||
from api.lib.common_setting.const import AuthenticateType
|
||||
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.audit import AuditCRUD
|
||||
from api.lib.perm.acl.cache import RoleCache
|
||||
from api.lib.perm.acl.cache import User
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
@@ -34,8 +38,10 @@ class LoginView(APIView):
|
||||
username = request.values.get("username") or request.values.get("email")
|
||||
password = request.values.get("password")
|
||||
_role = None
|
||||
if current_app.config.get('AUTH_WITH_LDAP'):
|
||||
user, authenticated = User.query.authenticate_with_ldap(username, password)
|
||||
config = AuthenticateDataCRUD(AuthenticateType.LDAP).get()
|
||||
if config.get('enabled') or config.get('enable'):
|
||||
from api.lib.perm.authentication.ldap import authenticate_with_ldap
|
||||
user, authenticated = authenticate_with_ldap(username, password)
|
||||
else:
|
||||
user, authenticated = User.query.authenticate(username, password)
|
||||
if not user:
|
||||
@@ -176,4 +182,7 @@ class LogoutView(APIView):
|
||||
@auth_abandoned
|
||||
def post(self):
|
||||
logout_user()
|
||||
|
||||
AuditCRUD.add_login_log(None, None, None, _id=session.get('LOGIN_ID'), logout_at=datetime.datetime.now())
|
||||
|
||||
self.jsonify(code=200)
|
||||
|
@@ -35,6 +35,7 @@ class CIRelationSearchView(APIView):
|
||||
count = get_page_size(request.values.get("count") or request.values.get("page_size"))
|
||||
|
||||
root_id = request.values.get('root_id')
|
||||
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
|
||||
level = list(map(int, handle_arg_list(request.values.get('level', '1'))))
|
||||
|
||||
query = request.values.get('q', "")
|
||||
@@ -42,9 +43,11 @@ class CIRelationSearchView(APIView):
|
||||
facet = handle_arg_list(request.values.get("facet", ""))
|
||||
sort = request.values.get("sort")
|
||||
reverse = request.values.get("reverse") in current_app.config.get('BOOL_TRUE')
|
||||
has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE')
|
||||
|
||||
start = time.time()
|
||||
s = Search(root_id, level, query, fl, facet, page, count, sort, reverse)
|
||||
s = Search(root_id, level, query, fl, facet, page, count, sort, reverse,
|
||||
ancestor_ids=ancestor_ids, has_m2m=has_m2m)
|
||||
try:
|
||||
response, counter, total, page, numfound, facet = s.search()
|
||||
except SearchError as e:
|
||||
@@ -67,9 +70,11 @@ class CIRelationStatisticsView(APIView):
|
||||
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
|
||||
level = request.values.get('level', 1)
|
||||
type_ids = set(map(int, handle_arg_list(request.values.get('type_ids', []))))
|
||||
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
|
||||
has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE')
|
||||
|
||||
start = time.time()
|
||||
s = Search(root_ids, level)
|
||||
s = Search(root_ids, level, ancestor_ids=ancestor_ids, has_m2m=has_m2m)
|
||||
try:
|
||||
result = s.statistics(type_ids)
|
||||
except SearchError as e:
|
||||
@@ -121,14 +126,18 @@ class CIRelationView(APIView):
|
||||
url_prefix = "/ci_relations/<int:first_ci_id>/<int:second_ci_id>"
|
||||
|
||||
def post(self, first_ci_id, second_ci_id):
|
||||
ancestor_ids = request.values.get('ancestor_ids') or None
|
||||
|
||||
manager = CIRelationManager()
|
||||
res = manager.add(first_ci_id, second_ci_id)
|
||||
res = manager.add(first_ci_id, second_ci_id, ancestor_ids=ancestor_ids)
|
||||
|
||||
return self.jsonify(cr_id=res)
|
||||
|
||||
def delete(self, first_ci_id, second_ci_id):
|
||||
ancestor_ids = request.values.get('ancestor_ids') or None
|
||||
|
||||
manager = CIRelationManager()
|
||||
manager.delete_2(first_ci_id, second_ci_id)
|
||||
manager.delete_2(first_ci_id, second_ci_id, ancestor_ids=ancestor_ids)
|
||||
|
||||
return self.jsonify(message="CIType Relation is deleted")
|
||||
|
||||
@@ -151,8 +160,9 @@ class BatchCreateOrUpdateCIRelationView(APIView):
|
||||
ci_ids = list(map(int, request.values.get('ci_ids')))
|
||||
parents = list(map(int, request.values.get('parents', [])))
|
||||
children = list(map(int, request.values.get('children', [])))
|
||||
ancestor_ids = request.values.get('ancestor_ids') or None
|
||||
|
||||
CIRelationManager.batch_update(ci_ids, parents, children)
|
||||
CIRelationManager.batch_update(ci_ids, parents, children, ancestor_ids=ancestor_ids)
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
@@ -166,7 +176,8 @@ class BatchCreateOrUpdateCIRelationView(APIView):
|
||||
def delete(self):
|
||||
ci_ids = list(map(int, request.values.get('ci_ids')))
|
||||
parents = list(map(int, request.values.get('parents', [])))
|
||||
ancestor_ids = request.values.get('ancestor_ids') or None
|
||||
|
||||
CIRelationManager.batch_delete(ci_ids, parents)
|
||||
CIRelationManager.batch_delete(ci_ids, parents, ancestor_ids=ancestor_ids)
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
@@ -166,7 +166,8 @@ class CITypeAttributeView(APIView):
|
||||
t = CITypeCache.get(type_id) or CITypeCache.get(type_name) or abort(404, ErrFormat.ci_type_not_found)
|
||||
type_id = t.id
|
||||
unique_id = t.unique_id
|
||||
unique = AttributeCache.get(unique_id).name
|
||||
unique = AttributeCache.get(unique_id)
|
||||
unique = unique and unique.name
|
||||
|
||||
attr_filter = CIFilterPermsCRUD.get_attr_filter(type_id)
|
||||
attributes = CITypeAttributeManager.get_attributes_by_type_id(type_id)
|
||||
@@ -318,12 +319,14 @@ class CITypeAttributeGroupView(APIView):
|
||||
|
||||
|
||||
class CITypeTemplateView(APIView):
|
||||
url_prefix = ("/ci_types/template/import", "/ci_types/template/export")
|
||||
url_prefix = ("/ci_types/template/import", "/ci_types/template/export", "/ci_types/<int:type_id>/template/export")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def get(self): # export
|
||||
return self.jsonify(
|
||||
dict(ci_type_template=CITypeTemplateManager.export_template()))
|
||||
def get(self, type_id=None): # export
|
||||
if type_id is not None:
|
||||
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template_by_type(type_id)))
|
||||
|
||||
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template()))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def post(self): # import
|
||||
@@ -457,13 +460,21 @@ class CITypeGrantView(APIView):
|
||||
_type = CITypeCache.get(type_id)
|
||||
type_name = _type and _type.name or abort(404, ErrFormat.ci_type_not_found)
|
||||
acl = ACLManager('cmdb')
|
||||
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and \
|
||||
not is_app_admin('cmdb'):
|
||||
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||
return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
|
||||
|
||||
acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms)
|
||||
acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
|
||||
|
||||
CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values)
|
||||
if request.values.get('ci_filter') or request.values.get('attr_filter'):
|
||||
CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values)
|
||||
else:
|
||||
from api.tasks.acl import role_rebuild
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
|
||||
app_id = AppCache.get('cmdb').id
|
||||
current_app.logger.info((rid, app_id))
|
||||
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE)
|
||||
current_app.logger.info('done')
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
@@ -481,21 +492,27 @@ class CITypeRevokeView(APIView):
|
||||
_type = CITypeCache.get(type_id)
|
||||
type_name = _type and _type.name or abort(404, ErrFormat.ci_type_not_found)
|
||||
acl = ACLManager('cmdb')
|
||||
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and \
|
||||
not is_app_admin('cmdb'):
|
||||
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||
return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
|
||||
|
||||
acl.revoke_resource_from_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms)
|
||||
acl.revoke_resource_from_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
|
||||
|
||||
if PermEnum.READ in perms:
|
||||
CIFilterPermsCRUD().delete(type_id=type_id, rid=rid)
|
||||
app_id = AppCache.get('cmdb').id
|
||||
resource = None
|
||||
if PermEnum.READ in perms or not perms:
|
||||
resource = CIFilterPermsCRUD().delete(type_id=type_id, rid=rid)
|
||||
|
||||
app_id = AppCache.get('cmdb').id
|
||||
users = RoleRelationCRUD.get_users_by_rid(rid, app_id)
|
||||
for i in (users or []):
|
||||
if i.get('role', {}).get('id') and not RoleCRUD.has_permission(
|
||||
i.get('role').get('id'), type_name, ResourceTypeEnum.CI_TYPE, app_id, PermEnum.READ):
|
||||
PreferenceManager.delete_by_type_id(type_id, i.get('uid'))
|
||||
if not resource:
|
||||
from api.tasks.acl import role_rebuild
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
|
||||
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE)
|
||||
|
||||
users = RoleRelationCRUD.get_users_by_rid(rid, app_id)
|
||||
for i in (users or []):
|
||||
if i.get('role', {}).get('id') and not RoleCRUD.has_permission(
|
||||
i.get('role').get('id'), type_name, ResourceTypeEnum.CI_TYPE, app_id, PermEnum.READ):
|
||||
PreferenceManager.delete_by_type_id(type_id, i.get('uid'))
|
||||
|
||||
return self.jsonify(type_id=type_id, rid=rid)
|
||||
|
||||
|
@@ -9,6 +9,7 @@ from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
@@ -109,3 +110,10 @@ class CITypeRelationRevokeView(APIView):
|
||||
acl.revoke_resource_from_role_by_rid(resource_name, rid, ResourceTypeEnum.CI_TYPE_RELATION, perms)
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
|
||||
class CITypeRelationCanEditView(APIView):
|
||||
url_prefix = "/ci_type_relations/<int:parent_id>/<int:child_id>/can_edit"
|
||||
|
||||
def get(self, parent_id, child_id):
|
||||
return self.jsonify(result=PreferenceManager.can_edit_relation(parent_id, child_id))
|
||||
|
88
cmdb-api/api/views/common_setting/auth_config.py
Normal file
88
cmdb-api/api/views/common_setting/auth_config.py
Normal file
@@ -0,0 +1,88 @@
|
||||
from flask import abort, request
|
||||
|
||||
from api.lib.common_setting.common_data import AuthenticateDataCRUD
|
||||
from api.lib.common_setting.const import TestType
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
prefix = '/auth_config'
|
||||
|
||||
|
||||
class AuthConfigView(APIView):
|
||||
url_prefix = (f'{prefix}/<string:auth_type>',)
|
||||
|
||||
@role_required("acl_admin")
|
||||
def get(self, auth_type):
|
||||
cli = AuthenticateDataCRUD(auth_type)
|
||||
|
||||
if auth_type not in cli.get_support_type_list():
|
||||
abort(400, ErrFormat.not_support_auth_type.format(auth_type))
|
||||
|
||||
if auth_type in cli.common_type_list:
|
||||
data = cli.get_record(True)
|
||||
else:
|
||||
data = cli.get_record_with_decrypt()
|
||||
return self.jsonify(data)
|
||||
|
||||
@role_required("acl_admin")
|
||||
def post(self, auth_type):
|
||||
cli = AuthenticateDataCRUD(auth_type)
|
||||
|
||||
if auth_type not in cli.get_support_type_list():
|
||||
abort(400, ErrFormat.not_support_auth_type.format(auth_type))
|
||||
|
||||
params = request.json
|
||||
data = params.get('data', {})
|
||||
if auth_type in cli.common_type_list:
|
||||
data['encrypt'] = False
|
||||
cli.create(data)
|
||||
|
||||
return self.jsonify(params)
|
||||
|
||||
|
||||
class AuthConfigViewWithId(APIView):
|
||||
url_prefix = (f'{prefix}/<string:auth_type>/<int:_id>',)
|
||||
|
||||
@role_required("acl_admin")
|
||||
def put(self, auth_type, _id):
|
||||
cli = AuthenticateDataCRUD(auth_type)
|
||||
|
||||
if auth_type not in cli.get_support_type_list():
|
||||
abort(400, ErrFormat.not_support_auth_type.format(auth_type))
|
||||
|
||||
params = request.json
|
||||
data = params.get('data', {})
|
||||
if auth_type in cli.common_type_list:
|
||||
data['encrypt'] = False
|
||||
|
||||
res = cli.update(_id, data)
|
||||
|
||||
return self.jsonify(res.to_dict())
|
||||
|
||||
@role_required("acl_admin")
|
||||
def delete(self, auth_type, _id):
|
||||
cli = AuthenticateDataCRUD(auth_type)
|
||||
|
||||
if auth_type not in cli.get_support_type_list():
|
||||
abort(400, ErrFormat.not_support_auth_type.format(auth_type))
|
||||
cli.delete(_id)
|
||||
return self.jsonify({})
|
||||
|
||||
|
||||
class AuthEnableListView(APIView):
|
||||
url_prefix = (f'{prefix}/enable_list',)
|
||||
|
||||
method_decorators = []
|
||||
|
||||
def get(self):
|
||||
return self.jsonify(AuthenticateDataCRUD.get_enable_list())
|
||||
|
||||
|
||||
class AuthConfigTestView(APIView):
|
||||
url_prefix = (f'{prefix}/<string:auth_type>/test',)
|
||||
|
||||
def post(self, auth_type):
|
||||
test_type = request.values.get('test_type', TestType.Connect)
|
||||
params = request.json
|
||||
return self.jsonify(AuthenticateDataCRUD(auth_type).test(test_type, params.get('data')))
|
@@ -24,12 +24,12 @@ class DataView(APIView):
|
||||
class DataViewWithId(APIView):
|
||||
url_prefix = (f'{prefix}/<string:data_type>/<int:_id>',)
|
||||
|
||||
def put(self, _id):
|
||||
def put(self, data_type, _id):
|
||||
params = request.json
|
||||
res = CommonDataCRUD.update_data(_id, **params)
|
||||
|
||||
return self.jsonify(res.to_dict())
|
||||
|
||||
def delete(self, _id):
|
||||
def delete(self, data_type, _id):
|
||||
CommonDataCRUD.delete(_id)
|
||||
return self.jsonify({})
|
||||
|
@@ -3,9 +3,10 @@ import os
|
||||
|
||||
from flask import request, abort, current_app, send_from_directory
|
||||
from werkzeug.utils import secure_filename
|
||||
import lz4.frame
|
||||
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.lib.common_setting.upload_file import allowed_file, generate_new_file_name
|
||||
from api.lib.common_setting.upload_file import allowed_file, generate_new_file_name, CommonFileCRUD
|
||||
from api.resource import APIView
|
||||
|
||||
prefix = '/file'
|
||||
@@ -28,7 +29,8 @@ class GetFileView(APIView):
|
||||
url_prefix = (f'{prefix}/<string:_filename>',)
|
||||
|
||||
def get(self, _filename):
|
||||
return send_from_directory(current_app.config['UPLOAD_DIRECTORY_FULL'], _filename, as_attachment=True)
|
||||
file_stream = CommonFileCRUD.get_file(_filename)
|
||||
return self.send_file(file_stream, as_attachment=True, download_name=_filename)
|
||||
|
||||
|
||||
class PostFileView(APIView):
|
||||
@@ -44,6 +46,8 @@ class PostFileView(APIView):
|
||||
if not file:
|
||||
abort(400, ErrFormat.file_is_required)
|
||||
extension = file.mimetype.split('/')[-1]
|
||||
if '+' in extension:
|
||||
extension = file.filename.split('.')[-1]
|
||||
if file.filename == '':
|
||||
filename = f'.{extension}'
|
||||
else:
|
||||
@@ -53,11 +57,20 @@ class PostFileView(APIView):
|
||||
filename = file.filename
|
||||
|
||||
if allowed_file(filename, current_app.config.get('ALLOWED_EXTENSIONS', ALLOWED_EXTENSIONS)):
|
||||
filename = generate_new_file_name(filename)
|
||||
filename = secure_filename(filename)
|
||||
file.save(os.path.join(
|
||||
current_app.config['UPLOAD_DIRECTORY_FULL'], filename))
|
||||
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=filename)
|
||||
return self.jsonify(file_name=new_filename)
|
||||
except Exception as e:
|
||||
current_app.logger.error(e)
|
||||
abort(400, ErrFormat.upload_failed.format(e))
|
||||
|
||||
abort(400, 'Extension not allow')
|
||||
abort(400, ErrFormat.file_type_not_allowed.format(filename))
|
||||
|
@@ -34,7 +34,7 @@ cryptography>=41.0.2
|
||||
PyJWT==2.4.0
|
||||
PyMySQL==1.1.0
|
||||
ldap3==2.9.1
|
||||
PyYAML==6.0
|
||||
PyYAML==6.0.1
|
||||
redis==4.6.0
|
||||
requests==2.31.0
|
||||
requests_oauthlib==1.3.1
|
||||
@@ -48,6 +48,6 @@ treelib==1.6.1
|
||||
Werkzeug>=2.3.6
|
||||
WTForms==3.0.0
|
||||
shamir~=17.12.0
|
||||
hvac~=2.0.0
|
||||
pycryptodomex>=3.19.0
|
||||
colorama>=0.4.6
|
||||
lz4>=4.3.2
|
@@ -11,10 +11,10 @@ from environs import Env
|
||||
env = Env()
|
||||
env.read_env()
|
||||
|
||||
ENV = env.str("FLASK_ENV", default="production")
|
||||
DEBUG = ENV == "development"
|
||||
SECRET_KEY = env.str("SECRET_KEY")
|
||||
BCRYPT_LOG_ROUNDS = env.int("BCRYPT_LOG_ROUNDS", default=13)
|
||||
ENV = env.str('FLASK_ENV', default='production')
|
||||
DEBUG = ENV == 'development'
|
||||
SECRET_KEY = env.str('SECRET_KEY')
|
||||
BCRYPT_LOG_ROUNDS = env.int('BCRYPT_LOG_ROUNDS', default=13)
|
||||
DEBUG_TB_ENABLED = DEBUG
|
||||
DEBUG_TB_INTERCEPT_REDIRECTS = False
|
||||
|
||||
@@ -23,7 +23,7 @@ ERROR_CODES = [400, 401, 403, 404, 405, 500, 502]
|
||||
# # database
|
||||
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
|
||||
SQLALCHEMY_BINDS = {
|
||||
"user": 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
|
||||
'user': 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
|
||||
}
|
||||
SQLALCHEMY_ECHO = False
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
@@ -32,11 +32,11 @@ SQLALCHEMY_ENGINE_OPTIONS = {
|
||||
}
|
||||
|
||||
# # cache
|
||||
CACHE_TYPE = "redis"
|
||||
CACHE_REDIS_HOST = "127.0.0.1"
|
||||
CACHE_TYPE = 'redis'
|
||||
CACHE_REDIS_HOST = '127.0.0.1'
|
||||
CACHE_REDIS_PORT = 6379
|
||||
CACHE_REDIS_PASSWORD = ""
|
||||
CACHE_KEY_PREFIX = "CMDB::"
|
||||
CACHE_REDIS_PASSWORD = ''
|
||||
CACHE_KEY_PREFIX = 'CMDB::'
|
||||
CACHE_DEFAULT_TIMEOUT = 3000
|
||||
|
||||
# # log
|
||||
@@ -55,10 +55,10 @@ DEFAULT_MAIL_SENDER = ''
|
||||
|
||||
# # queue
|
||||
CELERY = {
|
||||
"broker_url": 'redis://127.0.0.1:6379/2',
|
||||
"result_backend": "redis://127.0.0.1:6379/2",
|
||||
"broker_vhost": "/",
|
||||
"broker_connection_retry_on_startup": True
|
||||
'broker_url': 'redis://127.0.0.1:6379/2',
|
||||
'result_backend': 'redis://127.0.0.1:6379/2',
|
||||
'broker_vhost': '/',
|
||||
'broker_connection_retry_on_startup': True
|
||||
}
|
||||
ONCE = {
|
||||
'backend': 'celery_once.backends.Redis',
|
||||
@@ -67,33 +67,81 @@ ONCE = {
|
||||
}
|
||||
}
|
||||
|
||||
# # SSO
|
||||
CAS_SERVER = "http://sso.xxx.com"
|
||||
CAS_VALIDATE_SERVER = "http://sso.xxx.com"
|
||||
CAS_LOGIN_ROUTE = "/cas/login"
|
||||
CAS_LOGOUT_ROUTE = "/cas/logout"
|
||||
CAS_VALIDATE_ROUTE = "/cas/serviceValidate"
|
||||
CAS_AFTER_LOGIN = "/"
|
||||
DEFAULT_SERVICE = "http://127.0.0.1:8000"
|
||||
# =============================== Authentication ===========================================================
|
||||
|
||||
# # ldap
|
||||
AUTH_WITH_LDAP = False
|
||||
LDAP_SERVER = ''
|
||||
LDAP_DOMAIN = ''
|
||||
LDAP_USER_DN = 'cn={},ou=users,dc=xxx,dc=com'
|
||||
# # CAS
|
||||
CAS = dict(
|
||||
enabled=False,
|
||||
cas_server='https://{your-CASServer-hostname}',
|
||||
cas_validate_server='https://{your-CASServer-hostname}',
|
||||
cas_login_route='/cas/built-in/cas/login',
|
||||
cas_logout_route='/cas/built-in/cas/logout',
|
||||
cas_validate_route='/cas/built-in/cas/serviceValidate',
|
||||
cas_after_login='/',
|
||||
cas_user_map={
|
||||
'username': {'tag': 'cas:user'},
|
||||
'nickname': {'tag': 'cas:attribute', 'attrs': {'name': 'displayName'}},
|
||||
'email': {'tag': 'cas:attribute', 'attrs': {'name': 'email'}},
|
||||
'mobile': {'tag': 'cas:attribute', 'attrs': {'name': 'phone'}},
|
||||
'avatar': {'tag': 'cas:attribute', 'attrs': {'name': 'avatar'}},
|
||||
}
|
||||
)
|
||||
|
||||
# # OAuth2.0
|
||||
OAUTH2 = dict(
|
||||
enabled=False,
|
||||
client_id='',
|
||||
client_secret='',
|
||||
authorize_url='https://{your-OAuth2Server-hostname}/login/oauth/authorize',
|
||||
token_url='https://{your-OAuth2Server-hostname}/api/login/oauth/access_token',
|
||||
scopes=['profile', 'email'],
|
||||
user_info={
|
||||
'url': 'https://{your-OAuth2Server-hostname}/api/userinfo',
|
||||
'email': 'email',
|
||||
'username': 'name',
|
||||
'avatar': 'picture'
|
||||
},
|
||||
after_login='/'
|
||||
)
|
||||
|
||||
# # OIDC
|
||||
OIDC = dict(
|
||||
enabled=False,
|
||||
client_id='',
|
||||
client_secret='',
|
||||
authorize_url='https://{your-OIDCServer-hostname}/login/oauth/authorize',
|
||||
token_url='https://{your-OIDCServer-hostname}/api/login/oauth/access_token',
|
||||
scopes=['openid', 'profile', 'email'],
|
||||
user_info={
|
||||
'url': 'https://{your-OIDCServer-hostname}/api/userinfo',
|
||||
'email': 'email',
|
||||
'username': 'name',
|
||||
'avatar': 'picture'
|
||||
},
|
||||
after_login='/'
|
||||
)
|
||||
|
||||
# # LDAP
|
||||
LDAP = dict(
|
||||
enabled=False,
|
||||
ldap_server='',
|
||||
ldap_domain='',
|
||||
ldap_user_dn='cn={},ou=users,dc=xxx,dc=com'
|
||||
)
|
||||
# ==========================================================================================================
|
||||
|
||||
# # pagination
|
||||
DEFAULT_PAGE_COUNT = 50
|
||||
|
||||
# # permission
|
||||
WHITE_LIST = ["127.0.0.1"]
|
||||
WHITE_LIST = ['127.0.0.1']
|
||||
USE_ACL = True
|
||||
|
||||
# # elastic search
|
||||
ES_HOST = '127.0.0.1'
|
||||
USE_ES = False
|
||||
|
||||
BOOL_TRUE = ['true', 'TRUE', 'True', True, '1', 1, "Yes", "YES", "yes", 'Y', 'y']
|
||||
BOOL_TRUE = ['true', 'TRUE', 'True', True, '1', 1, 'Yes', 'YES', 'yes', 'Y', 'y']
|
||||
|
||||
# # messenger
|
||||
USE_MESSENGER = True
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3857903 */
|
||||
src: url('iconfont.woff2?t=1698273699449') format('woff2'),
|
||||
url('iconfont.woff?t=1698273699449') format('woff'),
|
||||
url('iconfont.ttf?t=1698273699449') format('truetype');
|
||||
src: url('iconfont.woff2?t=1702544951995') format('woff2'),
|
||||
url('iconfont.woff?t=1702544951995') format('woff'),
|
||||
url('iconfont.ttf?t=1702544951995') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,274 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.OAUTH2:before {
|
||||
content: "\e8d8";
|
||||
}
|
||||
|
||||
.OIDC:before {
|
||||
content: "\e8d6";
|
||||
}
|
||||
|
||||
.CAS:before {
|
||||
content: "\e8d7";
|
||||
}
|
||||
|
||||
.ops-setting-auth:before {
|
||||
content: "\e8d5";
|
||||
}
|
||||
|
||||
.ops-setting-auth-selected:before {
|
||||
content: "\e8d4";
|
||||
}
|
||||
|
||||
.a-itsm-knowledge2:before {
|
||||
content: "\e8d2";
|
||||
}
|
||||
|
||||
.itsm-qrdownload:before {
|
||||
content: "\e8d3";
|
||||
}
|
||||
|
||||
.oneterm-playback:before {
|
||||
content: "\e8d1";
|
||||
}
|
||||
|
||||
.oneterm-disconnect:before {
|
||||
content: "\e8d0";
|
||||
}
|
||||
|
||||
.ops-oneterm-publickey-selected:before {
|
||||
content: "\e8cf";
|
||||
}
|
||||
|
||||
.ops-oneterm-publickey:before {
|
||||
content: "\e8ce";
|
||||
}
|
||||
|
||||
.ops-oneterm-gateway:before {
|
||||
content: "\e8b9";
|
||||
}
|
||||
|
||||
.ops-oneterm-gateway-selected:before {
|
||||
content: "\e8bf";
|
||||
}
|
||||
|
||||
.ops-oneterm-account:before {
|
||||
content: "\e8c0";
|
||||
}
|
||||
|
||||
.ops-oneterm-account-selected:before {
|
||||
content: "\e8c1";
|
||||
}
|
||||
|
||||
.ops-oneterm-command:before {
|
||||
content: "\e8c2";
|
||||
}
|
||||
|
||||
.ops-oneterm-command-selected:before {
|
||||
content: "\e8c3";
|
||||
}
|
||||
|
||||
.ops-oneterm-assetlist:before {
|
||||
content: "\e8c4";
|
||||
}
|
||||
|
||||
.ops-oneterm-assetlist-selected:before {
|
||||
content: "\e8c5";
|
||||
}
|
||||
|
||||
.ops-oneterm-sessiononline:before {
|
||||
content: "\e8c6";
|
||||
}
|
||||
|
||||
.ops-oneterm-sessiononline-selected:before {
|
||||
content: "\e8c7";
|
||||
}
|
||||
|
||||
.ops-oneterm-sessionhistory-selected:before {
|
||||
content: "\e8c8";
|
||||
}
|
||||
|
||||
.ops-oneterm-sessionhistory:before {
|
||||
content: "\e8c9";
|
||||
}
|
||||
|
||||
.ops-oneterm-login:before {
|
||||
content: "\e8ca";
|
||||
}
|
||||
|
||||
.ops-oneterm-login-selected:before {
|
||||
content: "\e8cb";
|
||||
}
|
||||
|
||||
.ops-oneterm-operation:before {
|
||||
content: "\e8cc";
|
||||
}
|
||||
|
||||
.ops-oneterm-operation-selected:before {
|
||||
content: "\e8cd";
|
||||
}
|
||||
|
||||
.ops-oneterm-workstation-selected:before {
|
||||
content: "\e8b7";
|
||||
}
|
||||
|
||||
.ops-oneterm-workstation:before {
|
||||
content: "\e8b8";
|
||||
}
|
||||
|
||||
.oneterm-file-selected:before {
|
||||
content: "\e8be";
|
||||
}
|
||||
|
||||
.oneterm-file:before {
|
||||
content: "\e8bc";
|
||||
}
|
||||
|
||||
.oneterm-time:before {
|
||||
content: "\e8bd";
|
||||
}
|
||||
|
||||
.oneterm-download:before {
|
||||
content: "\e8bb";
|
||||
}
|
||||
|
||||
.oneterm-commandrecord:before {
|
||||
content: "\e8ba";
|
||||
}
|
||||
|
||||
.oneterm-asset:before {
|
||||
content: "\e8b6";
|
||||
}
|
||||
|
||||
.oneterm-total_asset:before {
|
||||
content: "\e8b5";
|
||||
}
|
||||
|
||||
.oneterm-switch:before {
|
||||
content: "\e8b4";
|
||||
}
|
||||
|
||||
.oneterm-session:before {
|
||||
content: "\e8b3";
|
||||
}
|
||||
|
||||
.oneterm-connect:before {
|
||||
content: "\e8b2";
|
||||
}
|
||||
|
||||
.oneterm-login:before {
|
||||
content: "\e8b1";
|
||||
}
|
||||
|
||||
.ops-oneterm-dashboard:before {
|
||||
content: "\e8af";
|
||||
}
|
||||
|
||||
.ops-oneterm-dashboard-selected:before {
|
||||
content: "\e8b0";
|
||||
}
|
||||
|
||||
.oneterm-recentsession:before {
|
||||
content: "\e8ae";
|
||||
}
|
||||
|
||||
.oneterm-myassets:before {
|
||||
content: "\e8ad";
|
||||
}
|
||||
|
||||
.ops-oneterm-log:before {
|
||||
content: "\e8aa";
|
||||
}
|
||||
|
||||
.ops-oneterm-session-selected:before {
|
||||
content: "\e8ab";
|
||||
}
|
||||
|
||||
.ops-oneterm-session:before {
|
||||
content: "\e8ac";
|
||||
}
|
||||
|
||||
.ops-oneterm-log-selected:before {
|
||||
content: "\e8a9";
|
||||
}
|
||||
|
||||
.ops-oneterm-assets:before {
|
||||
content: "\e8a7";
|
||||
}
|
||||
|
||||
.ops-oneterm-assets-selected:before {
|
||||
content: "\e8a8";
|
||||
}
|
||||
|
||||
.itsm-down:before {
|
||||
content: "\e8a5";
|
||||
}
|
||||
|
||||
.itsm-up:before {
|
||||
content: "\e8a6";
|
||||
}
|
||||
|
||||
.itsm-download:before {
|
||||
content: "\e8a4";
|
||||
}
|
||||
|
||||
.itsm-print:before {
|
||||
content: "\e8a3";
|
||||
}
|
||||
|
||||
.itsm-view:before {
|
||||
content: "\e8a2";
|
||||
}
|
||||
|
||||
.itsm-word:before {
|
||||
content: "\e8a1";
|
||||
}
|
||||
|
||||
.datainsight-custom:before {
|
||||
content: "\e89e";
|
||||
}
|
||||
|
||||
.datainsight-prometheus:before {
|
||||
content: "\e89f";
|
||||
}
|
||||
|
||||
.datainsight-zabbix:before {
|
||||
content: "\e8a0";
|
||||
}
|
||||
|
||||
.setting-mainpeople:before {
|
||||
content: "\e89a";
|
||||
}
|
||||
|
||||
.setting-deputypeople:before {
|
||||
content: "\e89d";
|
||||
}
|
||||
|
||||
.ops-setting-duty:before {
|
||||
content: "\e89c";
|
||||
}
|
||||
|
||||
.ops-setting-duty-selected:before {
|
||||
content: "\e89b";
|
||||
}
|
||||
|
||||
.datainsight-sequential:before {
|
||||
content: "\e899";
|
||||
}
|
||||
|
||||
.datainsight-close:before {
|
||||
content: "\e898";
|
||||
}
|
||||
|
||||
.datainsight-handle:before {
|
||||
content: "\e897";
|
||||
}
|
||||
|
||||
.datainsight-table:before {
|
||||
content: "\e896";
|
||||
}
|
||||
|
||||
.icon-xianxing-password:before {
|
||||
content: "\e894";
|
||||
}
|
||||
@@ -21,11 +289,11 @@
|
||||
content: "\e895";
|
||||
}
|
||||
|
||||
.a-itsm-oneclickdownload:before {
|
||||
.itsm-download-all:before {
|
||||
content: "\e892";
|
||||
}
|
||||
|
||||
.a-itsm-packagedownload:before {
|
||||
.itsm-download-package:before {
|
||||
content: "\e893";
|
||||
}
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@@ -5,6 +5,475 @@
|
||||
"css_prefix_text": "",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "38566548",
|
||||
"name": "OAuth2.0",
|
||||
"font_class": "OAUTH2",
|
||||
"unicode": "e8d8",
|
||||
"unicode_decimal": 59608
|
||||
},
|
||||
{
|
||||
"icon_id": "38566584",
|
||||
"name": "OIDC",
|
||||
"font_class": "OIDC",
|
||||
"unicode": "e8d6",
|
||||
"unicode_decimal": 59606
|
||||
},
|
||||
{
|
||||
"icon_id": "38566578",
|
||||
"name": "cas",
|
||||
"font_class": "CAS",
|
||||
"unicode": "e8d7",
|
||||
"unicode_decimal": 59607
|
||||
},
|
||||
{
|
||||
"icon_id": "38547395",
|
||||
"name": "setting-authentication",
|
||||
"font_class": "ops-setting-auth",
|
||||
"unicode": "e8d5",
|
||||
"unicode_decimal": 59605
|
||||
},
|
||||
{
|
||||
"icon_id": "38547389",
|
||||
"name": "setting-authentication-selected",
|
||||
"font_class": "ops-setting-auth-selected",
|
||||
"unicode": "e8d4",
|
||||
"unicode_decimal": 59604
|
||||
},
|
||||
{
|
||||
"icon_id": "38533133",
|
||||
"name": "itsm-knowledge (2)",
|
||||
"font_class": "a-itsm-knowledge2",
|
||||
"unicode": "e8d2",
|
||||
"unicode_decimal": 59602
|
||||
},
|
||||
{
|
||||
"icon_id": "38531868",
|
||||
"name": "itsm-QRcode",
|
||||
"font_class": "itsm-qrdownload",
|
||||
"unicode": "e8d3",
|
||||
"unicode_decimal": 59603
|
||||
},
|
||||
{
|
||||
"icon_id": "38413515",
|
||||
"name": "oneterm-playback",
|
||||
"font_class": "oneterm-playback",
|
||||
"unicode": "e8d1",
|
||||
"unicode_decimal": 59601
|
||||
},
|
||||
{
|
||||
"icon_id": "38413481",
|
||||
"name": "oneterm-disconnect",
|
||||
"font_class": "oneterm-disconnect",
|
||||
"unicode": "e8d0",
|
||||
"unicode_decimal": 59600
|
||||
},
|
||||
{
|
||||
"icon_id": "38407867",
|
||||
"name": "oneterm-key-selected",
|
||||
"font_class": "ops-oneterm-publickey-selected",
|
||||
"unicode": "e8cf",
|
||||
"unicode_decimal": 59599
|
||||
},
|
||||
{
|
||||
"icon_id": "38407915",
|
||||
"name": "oneterm-key",
|
||||
"font_class": "ops-oneterm-publickey",
|
||||
"unicode": "e8ce",
|
||||
"unicode_decimal": 59598
|
||||
},
|
||||
{
|
||||
"icon_id": "38311855",
|
||||
"name": "oneterm-gateway",
|
||||
"font_class": "ops-oneterm-gateway",
|
||||
"unicode": "e8b9",
|
||||
"unicode_decimal": 59577
|
||||
},
|
||||
{
|
||||
"icon_id": "38311938",
|
||||
"name": "oneterm-gateway-selected",
|
||||
"font_class": "ops-oneterm-gateway-selected",
|
||||
"unicode": "e8bf",
|
||||
"unicode_decimal": 59583
|
||||
},
|
||||
{
|
||||
"icon_id": "38311957",
|
||||
"name": "oneterm-account",
|
||||
"font_class": "ops-oneterm-account",
|
||||
"unicode": "e8c0",
|
||||
"unicode_decimal": 59584
|
||||
},
|
||||
{
|
||||
"icon_id": "38311961",
|
||||
"name": "oneterm-account-selected",
|
||||
"font_class": "ops-oneterm-account-selected",
|
||||
"unicode": "e8c1",
|
||||
"unicode_decimal": 59585
|
||||
},
|
||||
{
|
||||
"icon_id": "38311974",
|
||||
"name": "oneterm-command",
|
||||
"font_class": "ops-oneterm-command",
|
||||
"unicode": "e8c2",
|
||||
"unicode_decimal": 59586
|
||||
},
|
||||
{
|
||||
"icon_id": "38311976",
|
||||
"name": "oneterm-command-selected",
|
||||
"font_class": "ops-oneterm-command-selected",
|
||||
"unicode": "e8c3",
|
||||
"unicode_decimal": 59587
|
||||
},
|
||||
{
|
||||
"icon_id": "38311979",
|
||||
"name": "oneterm-asset_list",
|
||||
"font_class": "ops-oneterm-assetlist",
|
||||
"unicode": "e8c4",
|
||||
"unicode_decimal": 59588
|
||||
},
|
||||
{
|
||||
"icon_id": "38311985",
|
||||
"name": "oneterm-asset_list-selected",
|
||||
"font_class": "ops-oneterm-assetlist-selected",
|
||||
"unicode": "e8c5",
|
||||
"unicode_decimal": 59589
|
||||
},
|
||||
{
|
||||
"icon_id": "38312030",
|
||||
"name": "oneterm-online",
|
||||
"font_class": "ops-oneterm-sessiononline",
|
||||
"unicode": "e8c6",
|
||||
"unicode_decimal": 59590
|
||||
},
|
||||
{
|
||||
"icon_id": "38312152",
|
||||
"name": "oneterm-online-selected",
|
||||
"font_class": "ops-oneterm-sessiononline-selected",
|
||||
"unicode": "e8c7",
|
||||
"unicode_decimal": 59591
|
||||
},
|
||||
{
|
||||
"icon_id": "38312154",
|
||||
"name": "oneterm-history-selected",
|
||||
"font_class": "ops-oneterm-sessionhistory-selected",
|
||||
"unicode": "e8c8",
|
||||
"unicode_decimal": 59592
|
||||
},
|
||||
{
|
||||
"icon_id": "38312155",
|
||||
"name": "oneterm-history",
|
||||
"font_class": "ops-oneterm-sessionhistory",
|
||||
"unicode": "e8c9",
|
||||
"unicode_decimal": 59593
|
||||
},
|
||||
{
|
||||
"icon_id": "38312404",
|
||||
"name": "oneterm-entry_log",
|
||||
"font_class": "ops-oneterm-login",
|
||||
"unicode": "e8ca",
|
||||
"unicode_decimal": 59594
|
||||
},
|
||||
{
|
||||
"icon_id": "38312423",
|
||||
"name": "oneterm-entry_log-selected",
|
||||
"font_class": "ops-oneterm-login-selected",
|
||||
"unicode": "e8cb",
|
||||
"unicode_decimal": 59595
|
||||
},
|
||||
{
|
||||
"icon_id": "38312426",
|
||||
"name": "oneterm-operation_log",
|
||||
"font_class": "ops-oneterm-operation",
|
||||
"unicode": "e8cc",
|
||||
"unicode_decimal": 59596
|
||||
},
|
||||
{
|
||||
"icon_id": "38312445",
|
||||
"name": "oneterm-operation_log-selected",
|
||||
"font_class": "ops-oneterm-operation-selected",
|
||||
"unicode": "e8cd",
|
||||
"unicode_decimal": 59597
|
||||
},
|
||||
{
|
||||
"icon_id": "38307876",
|
||||
"name": "oneterm-workstation-selected",
|
||||
"font_class": "ops-oneterm-workstation-selected",
|
||||
"unicode": "e8b7",
|
||||
"unicode_decimal": 59575
|
||||
},
|
||||
{
|
||||
"icon_id": "38307871",
|
||||
"name": "oneterm-workstation",
|
||||
"font_class": "ops-oneterm-workstation",
|
||||
"unicode": "e8b8",
|
||||
"unicode_decimal": 59576
|
||||
},
|
||||
{
|
||||
"icon_id": "38302246",
|
||||
"name": "oneterm-file-selected",
|
||||
"font_class": "oneterm-file-selected",
|
||||
"unicode": "e8be",
|
||||
"unicode_decimal": 59582
|
||||
},
|
||||
{
|
||||
"icon_id": "38302255",
|
||||
"name": "oneterm-file",
|
||||
"font_class": "oneterm-file",
|
||||
"unicode": "e8bc",
|
||||
"unicode_decimal": 59580
|
||||
},
|
||||
{
|
||||
"icon_id": "38203528",
|
||||
"name": "oneterm-time",
|
||||
"font_class": "oneterm-time",
|
||||
"unicode": "e8bd",
|
||||
"unicode_decimal": 59581
|
||||
},
|
||||
{
|
||||
"icon_id": "38203331",
|
||||
"name": "oneterm-download",
|
||||
"font_class": "oneterm-download",
|
||||
"unicode": "e8bb",
|
||||
"unicode_decimal": 59579
|
||||
},
|
||||
{
|
||||
"icon_id": "38201351",
|
||||
"name": "oneterm-command record",
|
||||
"font_class": "oneterm-commandrecord",
|
||||
"unicode": "e8ba",
|
||||
"unicode_decimal": 59578
|
||||
},
|
||||
{
|
||||
"icon_id": "38199341",
|
||||
"name": "oneterm-connected assets",
|
||||
"font_class": "oneterm-asset",
|
||||
"unicode": "e8b6",
|
||||
"unicode_decimal": 59574
|
||||
},
|
||||
{
|
||||
"icon_id": "38199350",
|
||||
"name": "oneterm-total assets",
|
||||
"font_class": "oneterm-total_asset",
|
||||
"unicode": "e8b5",
|
||||
"unicode_decimal": 59573
|
||||
},
|
||||
{
|
||||
"icon_id": "38199303",
|
||||
"name": "oneterm-switch (3)",
|
||||
"font_class": "oneterm-switch",
|
||||
"unicode": "e8b4",
|
||||
"unicode_decimal": 59572
|
||||
},
|
||||
{
|
||||
"icon_id": "38199317",
|
||||
"name": "oneterm-session",
|
||||
"font_class": "oneterm-session",
|
||||
"unicode": "e8b3",
|
||||
"unicode_decimal": 59571
|
||||
},
|
||||
{
|
||||
"icon_id": "38199339",
|
||||
"name": "oneterm-connection",
|
||||
"font_class": "oneterm-connect",
|
||||
"unicode": "e8b2",
|
||||
"unicode_decimal": 59570
|
||||
},
|
||||
{
|
||||
"icon_id": "38198321",
|
||||
"name": "oneterm-log in",
|
||||
"font_class": "oneterm-login",
|
||||
"unicode": "e8b1",
|
||||
"unicode_decimal": 59569
|
||||
},
|
||||
{
|
||||
"icon_id": "38194554",
|
||||
"name": "oneterm-dashboard",
|
||||
"font_class": "ops-oneterm-dashboard",
|
||||
"unicode": "e8af",
|
||||
"unicode_decimal": 59567
|
||||
},
|
||||
{
|
||||
"icon_id": "38194525",
|
||||
"name": "oneterm-dashboard-selected",
|
||||
"font_class": "ops-oneterm-dashboard-selected",
|
||||
"unicode": "e8b0",
|
||||
"unicode_decimal": 59568
|
||||
},
|
||||
{
|
||||
"icon_id": "38194352",
|
||||
"name": "oneterm-recent session",
|
||||
"font_class": "oneterm-recentsession",
|
||||
"unicode": "e8ae",
|
||||
"unicode_decimal": 59566
|
||||
},
|
||||
{
|
||||
"icon_id": "38194383",
|
||||
"name": "oneterm-my assets",
|
||||
"font_class": "oneterm-myassets",
|
||||
"unicode": "e8ad",
|
||||
"unicode_decimal": 59565
|
||||
},
|
||||
{
|
||||
"icon_id": "38194089",
|
||||
"name": "oneterm-log",
|
||||
"font_class": "ops-oneterm-log",
|
||||
"unicode": "e8aa",
|
||||
"unicode_decimal": 59562
|
||||
},
|
||||
{
|
||||
"icon_id": "38194088",
|
||||
"name": "oneterm-conversation-selected",
|
||||
"font_class": "ops-oneterm-session-selected",
|
||||
"unicode": "e8ab",
|
||||
"unicode_decimal": 59563
|
||||
},
|
||||
{
|
||||
"icon_id": "38194065",
|
||||
"name": "oneterm-conversation",
|
||||
"font_class": "ops-oneterm-session",
|
||||
"unicode": "e8ac",
|
||||
"unicode_decimal": 59564
|
||||
},
|
||||
{
|
||||
"icon_id": "38194105",
|
||||
"name": "oneterm-log-selected",
|
||||
"font_class": "ops-oneterm-log-selected",
|
||||
"unicode": "e8a9",
|
||||
"unicode_decimal": 59561
|
||||
},
|
||||
{
|
||||
"icon_id": "38194054",
|
||||
"name": "oneterm-assets",
|
||||
"font_class": "ops-oneterm-assets",
|
||||
"unicode": "e8a7",
|
||||
"unicode_decimal": 59559
|
||||
},
|
||||
{
|
||||
"icon_id": "38194055",
|
||||
"name": "oneterm-assets-selected",
|
||||
"font_class": "ops-oneterm-assets-selected",
|
||||
"unicode": "e8a8",
|
||||
"unicode_decimal": 59560
|
||||
},
|
||||
{
|
||||
"icon_id": "38123087",
|
||||
"name": "itsm-down",
|
||||
"font_class": "itsm-down",
|
||||
"unicode": "e8a5",
|
||||
"unicode_decimal": 59557
|
||||
},
|
||||
{
|
||||
"icon_id": "38123084",
|
||||
"name": "itsm-up",
|
||||
"font_class": "itsm-up",
|
||||
"unicode": "e8a6",
|
||||
"unicode_decimal": 59558
|
||||
},
|
||||
{
|
||||
"icon_id": "38105374",
|
||||
"name": "itsm-download",
|
||||
"font_class": "itsm-download",
|
||||
"unicode": "e8a4",
|
||||
"unicode_decimal": 59556
|
||||
},
|
||||
{
|
||||
"icon_id": "38105235",
|
||||
"name": "itsm-print",
|
||||
"font_class": "itsm-print",
|
||||
"unicode": "e8a3",
|
||||
"unicode_decimal": 59555
|
||||
},
|
||||
{
|
||||
"icon_id": "38104997",
|
||||
"name": "itsm-view",
|
||||
"font_class": "itsm-view",
|
||||
"unicode": "e8a2",
|
||||
"unicode_decimal": 59554
|
||||
},
|
||||
{
|
||||
"icon_id": "38105129",
|
||||
"name": "itsm-word",
|
||||
"font_class": "itsm-word",
|
||||
"unicode": "e8a1",
|
||||
"unicode_decimal": 59553
|
||||
},
|
||||
{
|
||||
"icon_id": "38095730",
|
||||
"name": "datainsight-custom",
|
||||
"font_class": "datainsight-custom",
|
||||
"unicode": "e89e",
|
||||
"unicode_decimal": 59550
|
||||
},
|
||||
{
|
||||
"icon_id": "38095729",
|
||||
"name": "datainsight-prometheus",
|
||||
"font_class": "datainsight-prometheus",
|
||||
"unicode": "e89f",
|
||||
"unicode_decimal": 59551
|
||||
},
|
||||
{
|
||||
"icon_id": "38095728",
|
||||
"name": "datainsight-zabbix",
|
||||
"font_class": "datainsight-zabbix",
|
||||
"unicode": "e8a0",
|
||||
"unicode_decimal": 59552
|
||||
},
|
||||
{
|
||||
"icon_id": "37944507",
|
||||
"name": "setting-main people",
|
||||
"font_class": "setting-mainpeople",
|
||||
"unicode": "e89a",
|
||||
"unicode_decimal": 59546
|
||||
},
|
||||
{
|
||||
"icon_id": "37944503",
|
||||
"name": "setting-deputy people",
|
||||
"font_class": "setting-deputypeople",
|
||||
"unicode": "e89d",
|
||||
"unicode_decimal": 59549
|
||||
},
|
||||
{
|
||||
"icon_id": "37940080",
|
||||
"name": "ops-setting-duty",
|
||||
"font_class": "ops-setting-duty",
|
||||
"unicode": "e89c",
|
||||
"unicode_decimal": 59548
|
||||
},
|
||||
{
|
||||
"icon_id": "37940033",
|
||||
"name": "ops-setting-duty-selected",
|
||||
"font_class": "ops-setting-duty-selected",
|
||||
"unicode": "e89b",
|
||||
"unicode_decimal": 59547
|
||||
},
|
||||
{
|
||||
"icon_id": "37841524",
|
||||
"name": "datainsight-sequential",
|
||||
"font_class": "datainsight-sequential",
|
||||
"unicode": "e899",
|
||||
"unicode_decimal": 59545
|
||||
},
|
||||
{
|
||||
"icon_id": "37841535",
|
||||
"name": "datainsight-close",
|
||||
"font_class": "datainsight-close",
|
||||
"unicode": "e898",
|
||||
"unicode_decimal": 59544
|
||||
},
|
||||
{
|
||||
"icon_id": "37841537",
|
||||
"name": "datainsight-handle",
|
||||
"font_class": "datainsight-handle",
|
||||
"unicode": "e897",
|
||||
"unicode_decimal": 59543
|
||||
},
|
||||
{
|
||||
"icon_id": "37841515",
|
||||
"name": "datainsight-table",
|
||||
"font_class": "datainsight-table",
|
||||
"unicode": "e896",
|
||||
"unicode_decimal": 59542
|
||||
},
|
||||
{
|
||||
"icon_id": "37830610",
|
||||
"name": "icon-xianxing-password",
|
||||
@@ -22,14 +491,14 @@
|
||||
{
|
||||
"icon_id": "37822199",
|
||||
"name": "itsm-oneclick download",
|
||||
"font_class": "a-itsm-oneclickdownload",
|
||||
"font_class": "itsm-download-all",
|
||||
"unicode": "e892",
|
||||
"unicode_decimal": 59538
|
||||
},
|
||||
{
|
||||
"icon_id": "37822198",
|
||||
"name": "itsm-package download",
|
||||
"font_class": "a-itsm-packagedownload",
|
||||
"font_class": "itsm-download-package",
|
||||
"unicode": "e893",
|
||||
"unicode_decimal": 59539
|
||||
},
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
39
cmdb-ui/src/api/auth.js
Normal file
39
cmdb-ui/src/api/auth.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
export function getAuthData(data_type) {
|
||||
return axios({
|
||||
url: `/common-setting/v1/auth_config/${data_type}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function postAuthData(data_type, data) {
|
||||
return axios({
|
||||
url: `/common-setting/v1/auth_config/${data_type}`,
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function putAuthData(data_type, id, data) {
|
||||
return axios({
|
||||
url: `/common-setting/v1/auth_config/${data_type}/${id}`,
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function getAuthDataEnable() {
|
||||
return axios({
|
||||
url: `/common-setting/v1/auth_config/enable_list`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function testLDAP(test_type, data) {
|
||||
return axios({
|
||||
url: `/common-setting/v1/auth_config/LDAP/test?test_type=${test_type}`,
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
@@ -1,8 +1,6 @@
|
||||
import config from '@/config/setting'
|
||||
|
||||
const api = {
|
||||
Login: config.useSSO ? '/api/sso/login' : '/v1/acl/login',
|
||||
Logout: config.useSSO ? '/api/sso/logout' : '/v1/acl/logout',
|
||||
Login: '/v1/acl/login',
|
||||
Logout: '/v1/acl/logout',
|
||||
ForgePassword: '/auth/forge-password',
|
||||
Register: '/auth/register',
|
||||
twoStepCode: '/auth/2step-code',
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import api from './index'
|
||||
import { axios } from '@/utils/request'
|
||||
import config from '@/config/setting'
|
||||
/**
|
||||
* login func
|
||||
* parameter: {
|
||||
@@ -12,9 +11,10 @@ import config from '@/config/setting'
|
||||
* @param parameter
|
||||
* @returns {*}
|
||||
*/
|
||||
export function login(data) {
|
||||
if (config.useSSO) {
|
||||
window.location.href = config.ssoLoginUrl
|
||||
export function login(data, auth_type) {
|
||||
if (auth_type) {
|
||||
localStorage.setItem('ops_auth_type', auth_type)
|
||||
window.location.href = `/api/${auth_type.toLowerCase()}/login`
|
||||
} else {
|
||||
return axios({
|
||||
url: api.Login,
|
||||
@@ -43,17 +43,15 @@ export function getInfo() {
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
if (config.useSSO) {
|
||||
window.location.replace(api.Logout)
|
||||
} else {
|
||||
return axios({
|
||||
url: api.Logout,
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
}
|
||||
})
|
||||
}
|
||||
const auth_type = localStorage.getItem('ops_auth_type')
|
||||
localStorage.clear()
|
||||
return axios({
|
||||
url: auth_type ? `/${auth_type.toLowerCase()}/logout` : api.Logout,
|
||||
method: auth_type ? 'get' : 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
BIN
cmdb-ui/src/assets/ops_logout.png
Normal file
BIN
cmdb-ui/src/assets/ops_logout.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 78 KiB |
@@ -95,6 +95,10 @@ export default {
|
||||
const unsubTree = subscribeTreeView(citypeId, '')
|
||||
Promise.all([unsubCIType, unsubTree]).then(() => {
|
||||
that.$message.success('取消订阅成功')
|
||||
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
|
||||
if (Number(citypeId) === Number(lastTypeId)) {
|
||||
localStorage.setItem('ops_ci_typeid', '')
|
||||
}
|
||||
// 删除路由
|
||||
const href = window.location.href
|
||||
const hrefSplit = href.split('/')
|
||||
|
@@ -66,10 +66,8 @@ export default {
|
||||
|
||||
this.$confirm({
|
||||
title: '提示',
|
||||
content: '真的要注销登录吗 ?',
|
||||
content: '确认注销登录 ?',
|
||||
onOk() {
|
||||
// localStorage.removeItem('ops_cityps_currentId')
|
||||
localStorage.clear()
|
||||
return that.Logout()
|
||||
},
|
||||
onCancel() {},
|
||||
|
@@ -2,7 +2,6 @@ const appConfig = {
|
||||
buildModules: ['cmdb', 'acl'], // 需要编译的模块
|
||||
redirectTo: '/cmdb', // 首页的重定向路径
|
||||
buildAclToModules: true, // 是否在各个应用下 内联权限管理
|
||||
ssoLogoutURL: '/api/sso/logout',
|
||||
showDocs: false,
|
||||
useEncryption: false,
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
/**
|
||||
* 项目默认配置项
|
||||
* useSSO - 是否启用单点登录, 默认为否, 可以根据需要接入到公司的单点登录系统
|
||||
* primaryColor - 默认主题色, 如果修改颜色不生效,请清理 localStorage
|
||||
* navTheme - sidebar theme ['dark', 'light'] 两种主题
|
||||
* colorWeak - 色盲模式
|
||||
@@ -15,8 +14,6 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
useSSO: false,
|
||||
ssoLoginUrl: '/api/sso/login',
|
||||
primaryColor: '#1890ff', // primary color of ant design
|
||||
navTheme: 'dark', // theme for nav menu
|
||||
layout: 'sidemenu', // nav menu position: sidemenu or topmenu
|
||||
|
@@ -6,7 +6,6 @@ import store from './store'
|
||||
import NProgress from 'nprogress'
|
||||
import 'nprogress/nprogress.css'
|
||||
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
|
||||
import config from '@/config/setting'
|
||||
import { ACCESS_TOKEN } from './store/global/mutation-types'
|
||||
|
||||
NProgress.configure({ showSpinner: false })
|
||||
@@ -16,16 +15,16 @@ const whitePath = ['/user/login', '/user/logout', '/user/register', '/api/sso/lo
|
||||
|
||||
// 此处不处理登录, 只处理 是否有用户信息的认证 前端permission的处理 axios处理401 -> 登录
|
||||
// 登录页面处理处理 是否使用单点登录
|
||||
router.beforeEach((to, from, next) => {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
NProgress.start() // start progress bar
|
||||
to.meta && (!!to.meta.title && setDocumentTitle(`${to.meta.title} - ${domTitle}`))
|
||||
|
||||
const authed = store.state.authed
|
||||
|
||||
|
||||
const auth_type = localStorage.getItem('ops_auth_type')
|
||||
if (whitePath.includes(to.path)) {
|
||||
next()
|
||||
} else if ((config.useSSO || (!config.useSSO && Vue.ls.get(ACCESS_TOKEN))) && store.getters.roles.length === 0) {
|
||||
} else if ((auth_type || (!auth_type && Vue.ls.get(ACCESS_TOKEN))) && store.getters.roles.length === 0) {
|
||||
store.dispatch('GetAuthDataEnable')
|
||||
store.dispatch('GetInfo').then(res => {
|
||||
const roles = res.result && res.result.role
|
||||
store.dispatch("loadAllUsers")
|
||||
@@ -46,10 +45,17 @@ router.beforeEach((to, from, next) => {
|
||||
}).catch((e) => {
|
||||
setTimeout(() => { store.dispatch('Logout') }, 3000)
|
||||
})
|
||||
} else if (to.path === '/user/login' && !config.useSSO && store.getters.roles.length !== 0) {
|
||||
} else if (to.path === '/user/login' && !auth_type && store.getters.roles.length !== 0) {
|
||||
next({ path: '/' })
|
||||
} else if (!config.useSSO && !Vue.ls.get(ACCESS_TOKEN) && to.path !== '/user/login') {
|
||||
next({ path: '/user/login', query: { redirect: to.fullPath } })
|
||||
} else if (!auth_type && !Vue.ls.get(ACCESS_TOKEN) && to.path !== '/user/login') {
|
||||
await store.dispatch('GetAuthDataEnable')
|
||||
const { enable_list = [] } = store?.state?.user?.auth_enable ?? {}
|
||||
const _enable_list = enable_list.filter(en => en.auth_type !== 'LDAP')
|
||||
if (_enable_list.length === 1) {
|
||||
next({ path: '/user/logout', query: { redirect: to.fullPath } })
|
||||
} else {
|
||||
next({ path: '/user/login', query: { redirect: to.fullPath } })
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
|
@@ -65,8 +65,7 @@ const genAclRoutes = async () => {
|
||||
path: `/acl/operate_history`,
|
||||
name: 'acl_operate_history',
|
||||
component: () => import('../views/operation_history/index.vue'),
|
||||
// meta: { title: '操作审计', icon: 'search', permission: ['acl_admin'] },
|
||||
meta: { title: '操作审计', icon: 'search' }
|
||||
meta: { title: '操作审计', icon: 'search', permission: ['acl_admin'] },
|
||||
},
|
||||
{
|
||||
path: `/acl/user`,
|
||||
|
@@ -68,6 +68,7 @@
|
||||
ref="xTable"
|
||||
row-id="id"
|
||||
show-overflow
|
||||
resizable
|
||||
>
|
||||
<!-- 1 -->
|
||||
<vxe-table-column type="checkbox" fixed="left" :width="45"></vxe-table-column>
|
||||
|
@@ -133,8 +133,8 @@ export default {
|
||||
if (newVal) {
|
||||
this.tableData = this.allUsers.filter(
|
||||
(item) =>
|
||||
item.username.toLowerCase().includes(newVal.toLowerCase()) ||
|
||||
item.nickname.toLowerCase().includes(newVal.toLowerCase())
|
||||
(item.username && item.username.toLowerCase().includes(newVal.toLowerCase())) ||
|
||||
(item.nickname && item.nickname.toLowerCase().includes(newVal.toLowerCase()))
|
||||
)
|
||||
} else {
|
||||
this.tableData = this.allUsers
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
export function getFirstCIs(ciId) {
|
||||
export function getFirstCIsByCiId(ciId) {
|
||||
return axios({
|
||||
url: '/v0.1/ci_relations/' + ciId + '/first_cis',
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function getSecondCIs(ciId) {
|
||||
export function getSecondCIsByCiId(ciId) {
|
||||
return axios({
|
||||
url: '/v0.1/ci_relations/' + ciId + '/second_cis',
|
||||
method: 'GET'
|
||||
@@ -30,11 +30,11 @@ export function statisticsCIRelation(params) {
|
||||
}
|
||||
|
||||
// 批量添加子节点
|
||||
export function batchUpdateCIRelationChildren(ciIds, parents) {
|
||||
export function batchUpdateCIRelationChildren(ciIds, parents, ancestor_ids = undefined) {
|
||||
return axios({
|
||||
url: '/v0.1/ci_relations/batch',
|
||||
method: 'POST',
|
||||
data: { ci_ids: ciIds, parents: parents }
|
||||
data: { ci_ids: ciIds, parents, ancestor_ids }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -48,26 +48,28 @@ export function batchUpdateCIRelationParents(ciIds, children) {
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
export function batchDeleteCIRelation(ciIds, parents) {
|
||||
export function batchDeleteCIRelation(ciIds, parents, ancestor_ids = undefined) {
|
||||
return axios({
|
||||
url: '/v0.1/ci_relations/batch',
|
||||
method: 'DELETE',
|
||||
data: { ci_ids: ciIds, parents: parents }
|
||||
data: { ci_ids: ciIds, parents, ancestor_ids }
|
||||
})
|
||||
}
|
||||
|
||||
// 单个添加
|
||||
export function addCIRelationView(firstCiId, secondCiId) {
|
||||
export function addCIRelationView(firstCiId, secondCiId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_relations/${firstCiId}/${secondCiId}`,
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 单个删除
|
||||
export function deleteCIRelationView(firstCiId, secondCiId) {
|
||||
export function deleteCIRelationView(firstCiId, secondCiId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_relations/${firstCiId}/${secondCiId}`,
|
||||
method: 'DELETE',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
@@ -68,3 +68,10 @@ export function getRecursive_level2children(type_id) {
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function getCanEditByParentIdChildId(parent_id, child_id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_type_relations/${parent_id}/${child_id}/can_edit`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
@@ -16,12 +16,14 @@ export function processFile(fileObj) {
|
||||
}
|
||||
|
||||
export function uploadData(ciId, data) {
|
||||
data.ci_type = ciId
|
||||
data.exist_policy = 'replace'
|
||||
return axios({
|
||||
url: '/v0.1/ci',
|
||||
method: 'POST',
|
||||
data,
|
||||
data: {
|
||||
...data,
|
||||
ci_type: ciId,
|
||||
exist_policy: 'replace'
|
||||
},
|
||||
isShowMessage: false
|
||||
})
|
||||
}
|
||||
|
@@ -111,12 +111,14 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('cmdbStore', ['SET_IS_TABLE_LOADING']),
|
||||
open({ preferenceAttrList }) {
|
||||
open({ preferenceAttrList, ciTypeName = undefined }) {
|
||||
this.preferenceAttrList = preferenceAttrList
|
||||
this.visible = true
|
||||
this.$nextTick((res) => {
|
||||
this.form.setFieldsValue({
|
||||
filename: `cmdb-${moment().format('YYYYMMDDHHmmss')}`,
|
||||
filename: ciTypeName
|
||||
? `cmdb-${ciTypeName}-${moment().format('YYYYMMDDHHmmss')}`
|
||||
: `cmdb-${moment().format('YYYYMMDDHHmmss')}`,
|
||||
})
|
||||
if (this.treeType === 'tree') {
|
||||
const _check = ['ci_type_alias']
|
||||
|
@@ -77,7 +77,7 @@ const genCmdbRoutes = async () => {
|
||||
path: '/cmdb/ci_types',
|
||||
name: 'ci_type',
|
||||
component: () => import('../views/ci_types/index'),
|
||||
meta: { title: '模型配置', icon: 'ops-cmdb-citype', selectedIcon: 'ops-cmdb-citype-selected', keepAlive: false }
|
||||
meta: { title: '模型配置', icon: 'ops-cmdb-citype', selectedIcon: 'ops-cmdb-citype-selected', keepAlive: false, permission: ['cmdb_admin', 'admin'] }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/disabled3',
|
||||
|
@@ -1,11 +1,16 @@
|
||||
<template>
|
||||
<div class="cmdb-batch-upload" :style="{ height: `${windowHeight - 64}px` }">
|
||||
<div id="title">
|
||||
<ci-type-choice @getCiTypeAttr="showCiType" />
|
||||
<ci-type-choice ref="ciTypeChoice" @getCiTypeAttr="showCiType" />
|
||||
</div>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<upload-file-form :ciType="ciType" ref="uploadFileForm" @uploadDone="uploadDone"></upload-file-form>
|
||||
<upload-file-form
|
||||
:isUploading="isUploading"
|
||||
:ciType="ciType"
|
||||
ref="uploadFileForm"
|
||||
@uploadDone="uploadDone"
|
||||
></upload-file-form>
|
||||
</a-col>
|
||||
<a-col :span="24" v-if="ciType && uploadData.length">
|
||||
<CiUploadTable :ciTypeAttrs="ciTypeAttrs" ref="ciUploadTable" :uploadData="uploadData"></CiUploadTable>
|
||||
@@ -13,15 +18,19 @@
|
||||
<a-space size="large">
|
||||
<a-button type="primary" ghost @click="handleCancel">取消</a-button>
|
||||
<a-button @click="handleUpload" type="primary">上传</a-button>
|
||||
<a-button v-if="hasError && !isUploading" @click="downloadError" type="primary">失败下载</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-col :span="24" v-if="ciType">
|
||||
<upload-result
|
||||
ref="uploadResult"
|
||||
:upLoadData="uploadData"
|
||||
:ciType="ciType"
|
||||
:unique-field="uniqueField"
|
||||
:isUploading="isUploading"
|
||||
@uploadResultDone="uploadResultDone"
|
||||
@uploadResultError="uploadResultError"
|
||||
></upload-result>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@@ -29,6 +38,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
import { mapState } from 'vuex'
|
||||
import CiTypeChoice from './modules/CiTypeChoice'
|
||||
import CiUploadTable from './modules/CiUploadTable'
|
||||
@@ -51,7 +61,8 @@ export default {
|
||||
ciType: 0,
|
||||
uniqueField: '',
|
||||
uniqueId: 0,
|
||||
displayUpload: true,
|
||||
isUploading: false,
|
||||
hasError: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -59,13 +70,12 @@ export default {
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
}),
|
||||
},
|
||||
inject: ['reload'],
|
||||
methods: {
|
||||
showCiType(message) {
|
||||
this.ciTypeAttrs = message
|
||||
this.ciType = message.type_id
|
||||
this.uniqueField = message.unique
|
||||
this.uniqueId = message.unique_id
|
||||
this.ciTypeAttrs = message ?? {}
|
||||
this.ciType = message?.type_id ?? 0
|
||||
this.uniqueField = message?.unique ?? ''
|
||||
this.uniqueId = message?.unique_id ?? 0
|
||||
},
|
||||
uploadDone(dataList) {
|
||||
const _uploadData = filterNull(dataList).map((item, i) => {
|
||||
@@ -73,7 +83,20 @@ export default {
|
||||
const _ele = {}
|
||||
item.forEach((ele, j) => {
|
||||
if (ele !== undefined && ele !== null) {
|
||||
_ele[dataList[0][j]] = ele
|
||||
const _find = this.ciTypeAttrs.attributes.find(
|
||||
(attr) => attr.alias === dataList[0][j] || attr.name === dataList[0][j]
|
||||
)
|
||||
if (_find?.value_type === '4' && typeof ele === 'number') {
|
||||
_ele[dataList[0][j]] = moment(Math.round((ele - 25569) * 86400 * 1000 - 28800000)).format('YYYY-MM-DD')
|
||||
} else if (_find?.value_type === '3' && typeof ele === 'number') {
|
||||
_ele[dataList[0][j]] = moment(Math.round((ele - 25569) * 86400 * 1000 - 28800000)).format(
|
||||
'YYYY-MM-DD HH:mm:ss'
|
||||
)
|
||||
} else if (_find?.value_type === '5' && typeof ele === 'number') {
|
||||
_ele[dataList[0][j]] = moment(Math.round(ele * 86400 * 1000 - 28800000)).format('HH:mm:ss')
|
||||
} else {
|
||||
_ele[dataList[0][j]] = ele
|
||||
}
|
||||
}
|
||||
})
|
||||
return _ele
|
||||
@@ -81,6 +104,8 @@ export default {
|
||||
return item
|
||||
})
|
||||
this.uploadData = _uploadData.slice(1)
|
||||
this.hasError = false
|
||||
this.isUploading = false
|
||||
},
|
||||
handleUpload() {
|
||||
if (!this.ciType) {
|
||||
@@ -88,6 +113,7 @@ export default {
|
||||
return
|
||||
}
|
||||
if (this.uploadData && this.uploadData.length > 0) {
|
||||
this.isUploading = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.uploadResult.upload2Server()
|
||||
})
|
||||
@@ -96,7 +122,24 @@ export default {
|
||||
}
|
||||
},
|
||||
handleCancel() {
|
||||
this.reload()
|
||||
if (!this.isUploading) {
|
||||
this.showCiType(null)
|
||||
this.$refs.ciTypeChoice.selectNum = null
|
||||
this.hasError = false
|
||||
} else {
|
||||
this.$message.warning('批量上传已取消')
|
||||
this.isUploading = false
|
||||
}
|
||||
},
|
||||
uploadResultDone() {
|
||||
this.isUploading = false
|
||||
},
|
||||
uploadResultError(index) {
|
||||
this.hasError = true
|
||||
this.$refs.ciUploadTable.uploadResultError(index)
|
||||
},
|
||||
downloadError() {
|
||||
this.$refs.ciUploadTable.downloadError()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@
|
||||
:style="{ width: '300px' }"
|
||||
class="ops-select"
|
||||
:filter-option="filterOption"
|
||||
v-model="selectNum"
|
||||
>
|
||||
<a-select-option v-for="ciType in ciTypeList" :key="ciType.name" :value="ciType.id">{{
|
||||
ciType.alias
|
||||
@@ -40,7 +41,7 @@
|
||||
全选
|
||||
</a-checkbox>
|
||||
<br />
|
||||
<a-checkbox-group v-model="checkedAttrs">
|
||||
<a-checkbox-group style="width:100%" v-model="checkedAttrs">
|
||||
<a-row>
|
||||
<a-col :span="6" v-for="item in selectCiTypeAttrList.attributes" :key="item.alias || item.name">
|
||||
<a-checkbox :disabled="item.name === selectCiTypeAttrList.unique" :value="item.alias || item.name">
|
||||
@@ -87,10 +88,13 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { mapState } from 'vuex'
|
||||
import { downloadExcel } from '../../../utils/helper'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { getCITypeParent } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { searchPermResourceByRoleId } from '@/modules/acl/api/permission'
|
||||
|
||||
export default {
|
||||
name: 'CiTypeChoice',
|
||||
@@ -98,7 +102,7 @@ export default {
|
||||
return {
|
||||
ciTypeList: [],
|
||||
ciTypeName: '',
|
||||
selectNum: 0,
|
||||
selectNum: null,
|
||||
selectCiTypeAttrList: [],
|
||||
visible: false,
|
||||
checkedAttrs: [],
|
||||
@@ -107,11 +111,24 @@ export default {
|
||||
parentsType: [],
|
||||
parentsForm: {},
|
||||
checkedParents: [],
|
||||
canEdit: {},
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
computed: {
|
||||
...mapState({
|
||||
rid: (state) => state.user.rid,
|
||||
}),
|
||||
},
|
||||
async created() {
|
||||
const { resources } = await searchPermResourceByRoleId(this.rid, {
|
||||
resource_type_id: 'CIType',
|
||||
app_id: 'cmdb',
|
||||
})
|
||||
getCITypes().then((res) => {
|
||||
this.ciTypeList = res.ci_types
|
||||
this.ciTypeList = res.ci_types.filter((type) => {
|
||||
const _findRe = resources.find((re) => re.name === type.name)
|
||||
return _findRe?.permissions.includes('create') ?? false
|
||||
})
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
@@ -129,7 +146,6 @@ export default {
|
||||
methods: {
|
||||
selectCiType(el) {
|
||||
// 当选择好模板类型时的回调函数
|
||||
this.selectNum = el
|
||||
getCITypeAttributesById(el).then((res) => {
|
||||
this.$emit('getCiTypeAttr', res)
|
||||
this.selectCiTypeAttrList = res
|
||||
@@ -143,8 +159,16 @@ export default {
|
||||
},
|
||||
|
||||
openModal() {
|
||||
getCITypeParent(this.selectNum).then((res) => {
|
||||
this.parentsType = res.parents
|
||||
getCITypeParent(this.selectNum).then(async (res) => {
|
||||
for (let i = 0; i < res.parents.length; i++) {
|
||||
await getCanEditByParentIdChildId(res.parents[i].id, this.selectNum).then((p_res) => {
|
||||
this.canEdit = {
|
||||
..._.cloneDeep(this.canEdit),
|
||||
[res.parents[i].id]: p_res.result,
|
||||
}
|
||||
})
|
||||
}
|
||||
this.parentsType = res.parents.filter((parent) => this.canEdit[parent.id])
|
||||
const _parentsForm = {}
|
||||
res.parents.forEach((item) => {
|
||||
const _find = item.attributes.find((attr) => attr.id === item.unique_id)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="cmdb-batch-upload-table">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
stripe
|
||||
show-header-overflow
|
||||
show-overflow=""
|
||||
@@ -8,6 +9,8 @@
|
||||
class="ops-stripe-table"
|
||||
:max-height="200"
|
||||
:data="dataSource"
|
||||
resizable
|
||||
:row-style="rowStyle"
|
||||
>
|
||||
<vxe-column type="seq" width="40" />
|
||||
<vxe-column
|
||||
@@ -36,7 +39,9 @@ export default {
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
return {
|
||||
errorIndexList: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns() {
|
||||
@@ -64,7 +69,33 @@ export default {
|
||||
return _.cloneDeep(this.uploadData)
|
||||
},
|
||||
},
|
||||
methods: {},
|
||||
watch: {
|
||||
uploadData() {
|
||||
this.errorIndexList = []
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
uploadResultError(index) {
|
||||
const _errorIndexList = _.cloneDeep(this.errorIndexList)
|
||||
_errorIndexList.push(index)
|
||||
this.errorIndexList = _errorIndexList
|
||||
},
|
||||
rowStyle({ rowIndex }) {
|
||||
if (this.errorIndexList.includes(rowIndex)) {
|
||||
return 'color:red;'
|
||||
}
|
||||
},
|
||||
downloadError() {
|
||||
const data = this.uploadData.filter((item, index) => this.errorIndexList.includes(index))
|
||||
this.$refs.xTable.exportData({
|
||||
data,
|
||||
type: 'xlsx',
|
||||
columnFilterMethod({ column }) {
|
||||
return column.property
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
accept=".xls,.xlsx"
|
||||
:showUploadList="false"
|
||||
:fileList="fileList"
|
||||
:disabled="!ciType"
|
||||
:disabled="!ciType || isUploading"
|
||||
>
|
||||
<img :style="{ width: '80px', height: '80px' }" src="@/assets/file_upload.png" />
|
||||
<p class="ant-upload-text">点击或拖拽文件至此上传!</p>
|
||||
@@ -29,7 +29,11 @@ export default {
|
||||
ciType: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
}
|
||||
},
|
||||
isUploading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -40,7 +44,20 @@ export default {
|
||||
percent: 0,
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
ciType: {
|
||||
handler(newValue) {
|
||||
if (!newValue) {
|
||||
this.ciItemNum = 0
|
||||
this.fileList = []
|
||||
this.dataList = []
|
||||
this.progressStatus = 'active'
|
||||
this.percent = 0
|
||||
this.$emit('uploadDone', this.dataList)
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
customRequest(data) {
|
||||
this.fileList = [data.file]
|
||||
|
@@ -34,6 +34,10 @@ export default {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
isUploading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
@@ -51,33 +55,38 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async sleep(n) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve()
|
||||
}, n || 5)
|
||||
})
|
||||
},
|
||||
async upload2Server() {
|
||||
this.visible = true
|
||||
this.success = 0
|
||||
this.errorNum = 0
|
||||
this.errorItems = []
|
||||
for (let i = 0; i < this.total; i++) {
|
||||
// await this.sleep(20)
|
||||
const item = this.upLoadData[i]
|
||||
await uploadData(this.ciType, item)
|
||||
.then((res) => {
|
||||
console.log(res)
|
||||
this.success += 1
|
||||
})
|
||||
.catch((err) => {
|
||||
this.errorNum += 1
|
||||
this.errorItems.push(((err.response || {}).data || {}).message || '请求出现错误,请稍后再试')
|
||||
})
|
||||
.finally(() => {
|
||||
this.complete += 1
|
||||
})
|
||||
const floor = Math.ceil(this.total / 6)
|
||||
for (let i = 0; i < floor; i++) {
|
||||
if (this.isUploading) {
|
||||
const itemList = this.upLoadData.slice(6 * i, 6 * i + 6)
|
||||
const promises = itemList.map((x) => uploadData(this.ciType, x))
|
||||
await Promise.allSettled(promises)
|
||||
.then((res) => {
|
||||
res.forEach((r, j) => {
|
||||
if (r.status === 'fulfilled') {
|
||||
this.success += 1
|
||||
} else {
|
||||
this.errorItems.push(r?.reason?.response?.data.message ?? '请求出现错误,请稍后再试')
|
||||
this.errorNum += 1
|
||||
this.$emit('uploadResultError', 6 * i + j)
|
||||
}
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.complete += 6
|
||||
})
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (this.isUploading) {
|
||||
this.$emit('uploadResultDone')
|
||||
this.$message.success('批量上传已完成')
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@@ -108,7 +108,7 @@
|
||||
<span>{{ col.title }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="col.is_choice || col.is_password" #edit="{ row }">
|
||||
<template v-if="col.is_choice || col.is_password || col.is_list" #edit="{ row }">
|
||||
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
|
||||
<a-select
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
@@ -145,6 +145,18 @@
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
:style="{ width: '100%', height: '32px' }"
|
||||
v-model="row[col.field]"
|
||||
placeholder="请选择"
|
||||
v-else-if="col.is_list"
|
||||
:showArrow="false"
|
||||
mode="tags"
|
||||
class="ci-table-edit-select"
|
||||
allowClear
|
||||
>
|
||||
</a-select>
|
||||
</template>
|
||||
<template
|
||||
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
|
||||
@@ -564,7 +576,10 @@ export default {
|
||||
},
|
||||
|
||||
async openBatchDownload() {
|
||||
this.$refs.batchDownload.open({ preferenceAttrList: this.preferenceAttrList })
|
||||
this.$refs.batchDownload.open({
|
||||
preferenceAttrList: this.preferenceAttrList,
|
||||
ciTypeName: this.$route.meta.title || this.$route.meta.name,
|
||||
})
|
||||
},
|
||||
batchDownload({ filename, type, checkedKeys }) {
|
||||
const jsonAttrList = []
|
||||
@@ -654,13 +669,19 @@ export default {
|
||||
let errorNum = 0
|
||||
this.loading = true
|
||||
this.loadTip = `正在删除...`
|
||||
for (let i = 0; i < this.selectedRowKeys.length; i++) {
|
||||
await deleteCI(this.selectedRowKeys[i], false)
|
||||
.then(() => {
|
||||
successNum += 1
|
||||
})
|
||||
.catch(() => {
|
||||
errorNum += 1
|
||||
const floor = Math.ceil(this.selectedRowKeys.length / 6)
|
||||
for (let i = 0; i < floor; i++) {
|
||||
const itemList = this.selectedRowKeys.slice(6 * i, 6 * i + 6)
|
||||
const promises = itemList.map((x) => deleteCI(x, false))
|
||||
await Promise.allSettled(promises)
|
||||
.then((res) => {
|
||||
res.forEach((r) => {
|
||||
if (r.status === 'fulfilled') {
|
||||
successNum += 1
|
||||
} else {
|
||||
errorNum += 1
|
||||
}
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.loadTip = `正在删除,共${this.selectedRowKeys.length}个,成功${successNum}个,失败${errorNum}个`
|
||||
@@ -873,6 +894,10 @@ export default {
|
||||
unsubscribe(ciType, type = 'all') {
|
||||
const promises = [subscribeCIType(this.typeId, ''), subscribeTreeView(this.typeId, '')]
|
||||
Promise.all(promises).then(() => {
|
||||
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
|
||||
if (Number(ciType) === Number(lastTypeId)) {
|
||||
localStorage.setItem('ops_ci_typeid', '')
|
||||
}
|
||||
this.$message.success('取消订阅成功')
|
||||
this.resetRoute()
|
||||
this.$router.push('/cmdb/preference')
|
||||
|
@@ -276,15 +276,16 @@ export default {
|
||||
},
|
||||
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
|
||||
const fields = ['created_at', 'username']
|
||||
const cellValue = row[column.property]
|
||||
if (cellValue && fields.includes(column.property)) {
|
||||
const cellValue1 = row['created_at']
|
||||
const cellValue2 = row['username']
|
||||
if (cellValue1 && cellValue2 && fields.includes(column.property)) {
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
if (prevRow && prevRow[column.property] === cellValue) {
|
||||
if (prevRow && prevRow['created_at'] === cellValue1 && prevRow['username'] === cellValue2) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow[column.property] === cellValue) {
|
||||
while (nextRow && nextRow['created_at'] === cellValue1 && nextRow['username'] === cellValue2) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
|
@@ -122,12 +122,12 @@
|
||||
<a-button type="primary" ghost icon="plus" @click="handleAdd">新增修改字段</a-button>
|
||||
</a-form>
|
||||
</template>
|
||||
<!-- </a-form> -->
|
||||
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import moment from 'moment'
|
||||
import { Select, Option } from 'element-ui'
|
||||
import { getCIType, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
|
||||
@@ -135,7 +135,7 @@ import { addCI } from '@/modules/cmdb/api/ci'
|
||||
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
|
||||
import { valueTypeMap } from '../../../utils/const'
|
||||
import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue'
|
||||
import { getCITypeParent } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
|
||||
|
||||
export default {
|
||||
name: 'CreateInstanceForm',
|
||||
@@ -166,6 +166,7 @@ export default {
|
||||
attributesByGroup: [],
|
||||
parentsType: [],
|
||||
parentsForm: {},
|
||||
canEdit: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -300,8 +301,16 @@ export default {
|
||||
this.batchUpdateLists = [{ name: this.attributeList[0].name }]
|
||||
})
|
||||
if (action === 'create') {
|
||||
getCITypeParent(this.typeId).then((res) => {
|
||||
this.parentsType = res.parents
|
||||
getCITypeParent(this.typeId).then(async (res) => {
|
||||
for (let i = 0; i < res.parents.length; i++) {
|
||||
await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => {
|
||||
this.canEdit = {
|
||||
..._.cloneDeep(this.canEdit),
|
||||
[res.parents[i].id]: p_res.result,
|
||||
}
|
||||
})
|
||||
}
|
||||
this.parentsType = res.parents.filter((parent) => this.canEdit[parent.id])
|
||||
const _parentsForm = {}
|
||||
res.parents.forEach((item) => {
|
||||
const _find = item.attributes.find((attr) => attr.id === item.unique_id)
|
||||
|
@@ -59,6 +59,9 @@
|
||||
{{ ci[attr.name] }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="attr.is_list">
|
||||
<span> {{ ci[attr.name].join(',') }}</span>
|
||||
</template>
|
||||
<template v-else>{{ getName(ci[attr.name]) }}</template>
|
||||
</span>
|
||||
<template v-else>
|
||||
@@ -75,7 +78,6 @@
|
||||
placeholder="请选择"
|
||||
v-if="attr.is_choice"
|
||||
:mode="attr.is_list ? 'multiple' : 'default'"
|
||||
:multiple="attr.is_list"
|
||||
showSearch
|
||||
allowClear
|
||||
size="small"
|
||||
@@ -103,6 +105,23 @@
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
:style="{ width: '100%' }"
|
||||
v-decorator="[
|
||||
attr.name,
|
||||
{
|
||||
rules: [{ required: attr.is_required }],
|
||||
},
|
||||
]"
|
||||
placeholder="请选择"
|
||||
v-else-if="attr.is_list"
|
||||
mode="tags"
|
||||
showSearch
|
||||
allowClear
|
||||
size="small"
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
>
|
||||
</a-select>
|
||||
<a-input-number
|
||||
size="small"
|
||||
v-decorator="[
|
||||
@@ -222,7 +241,7 @@ export default {
|
||||
this.$nextTick(async () => {
|
||||
if (this.attr.is_list && !this.attr.is_choice) {
|
||||
this.form.setFieldsValue({
|
||||
[`${this.attr.name}`]: this.ci[this.attr.name].join(',') || null,
|
||||
[`${this.attr.name}`]: this.ci[this.attr.name] || null,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@
|
||||
<div class="ci-detail-relation-table-title">
|
||||
{{ parent.alias || parent.name }}
|
||||
<a
|
||||
:disabled="!canEdit[parent.id]"
|
||||
@click="
|
||||
() => {
|
||||
$refs.addTableModal.openModal({ [`${ci.unique}`]: ci[ci.unique] }, ci._id, parent.id, 'parents')
|
||||
@@ -23,6 +24,7 @@
|
||||
><a-icon
|
||||
type="plus-square"
|
||||
/></a>
|
||||
<span v-if="!canEdit[parent.id]">(当前模型关系为多对多,请前往关系视图进行增删操作)</span>
|
||||
</div>
|
||||
<vxe-grid
|
||||
v-if="firstCIs[parent.name]"
|
||||
@@ -38,7 +40,14 @@
|
||||
>
|
||||
<template #operation_default="{ row }">
|
||||
<a-popconfirm arrowPointAtCenter title="确认删除关系?" @confirm="deleteRelation(row._id, ciId)">
|
||||
<a :style="{ color: 'red' }"><a-icon type="delete"/></a>
|
||||
<a
|
||||
:disabled="!canEdit[parent.id]"
|
||||
:style="{
|
||||
color: !canEdit[parent.id] ? 'rgba(0, 0, 0, 0.25)' : 'red',
|
||||
}"
|
||||
><a-icon
|
||||
type="delete"
|
||||
/></a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
@@ -50,6 +59,7 @@
|
||||
<div class="ci-detail-relation-table-title">
|
||||
{{ child.alias || child.name }}
|
||||
<a
|
||||
:disabled="!canEdit[child.id]"
|
||||
@click="
|
||||
() => {
|
||||
$refs.addTableModal.openModal({ [`${ci.unique}`]: ci[ci.unique] }, ci._id, child.id, 'children')
|
||||
@@ -58,6 +68,7 @@
|
||||
><a-icon
|
||||
type="plus-square"
|
||||
/></a>
|
||||
<span v-if="!canEdit[child.id]">(当前模型关系为多对多,请前往关系视图进行增删操作)</span>
|
||||
</div>
|
||||
<vxe-grid
|
||||
v-if="secondCIs[child.name]"
|
||||
@@ -72,7 +83,14 @@
|
||||
>
|
||||
<template #operation_default="{ row }">
|
||||
<a-popconfirm arrowPointAtCenter title="确认删除关系?" @confirm="deleteRelation(ciId, row._id)">
|
||||
<a :style="{ color: 'red' }"><a-icon type="delete"/></a>
|
||||
<a
|
||||
:disabled="!canEdit[child.id]"
|
||||
:style="{
|
||||
color: !canEdit[child.id] ? 'rgba(0, 0, 0, 0.25)' : 'red',
|
||||
}"
|
||||
><a-icon
|
||||
type="delete"
|
||||
/></a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
@@ -85,7 +103,7 @@
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { getCITypeChildren, getCITypeParent } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getCITypeChildren, getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { searchCIRelation, deleteCIRelationView } from '@/modules/cmdb/api/CIRelation'
|
||||
import CiDetailRelationTopo from './ciDetailRelationTopo/index.vue'
|
||||
import Node from './ciDetailRelationTopo/node.js'
|
||||
@@ -118,6 +136,7 @@ export default {
|
||||
secondCIColumns: {},
|
||||
firstCIJsonAttr: {},
|
||||
secondCIJsonAttr: {},
|
||||
canEdit: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -293,76 +312,85 @@ export default {
|
||||
.catch((e) => {})
|
||||
},
|
||||
async getParentCITypes() {
|
||||
await getCITypeParent(this.typeId)
|
||||
.then((res) => {
|
||||
this.parentCITypes = res.parents
|
||||
|
||||
const firstCIColumns = {}
|
||||
const firstCIJsonAttr = {}
|
||||
res.parents.forEach((item) => {
|
||||
const columns = []
|
||||
const jsonAttr = []
|
||||
item.attributes.forEach((attr) => {
|
||||
columns.push({ key: 'p_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
|
||||
if (attr.value_type === '6') {
|
||||
jsonAttr.push(attr.name)
|
||||
}
|
||||
})
|
||||
firstCIJsonAttr[item.id] = jsonAttr
|
||||
firstCIColumns[item.id] = columns
|
||||
firstCIColumns[item.id].push({
|
||||
key: 'p_operation',
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
width: '60px',
|
||||
fixed: 'right',
|
||||
slots: {
|
||||
default: 'operation_default',
|
||||
},
|
||||
align: 'center',
|
||||
})
|
||||
})
|
||||
|
||||
this.firstCIColumns = firstCIColumns
|
||||
this.firstCIJsonAttr = firstCIJsonAttr
|
||||
const res = await getCITypeParent(this.typeId)
|
||||
this.parentCITypes = res.parents
|
||||
for (let i = 0; i < res.parents.length; i++) {
|
||||
await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => {
|
||||
this.canEdit = {
|
||||
..._.cloneDeep(this.canEdit),
|
||||
[res.parents[i].id]: p_res.result,
|
||||
}
|
||||
})
|
||||
.catch((e) => {})
|
||||
}
|
||||
const firstCIColumns = {}
|
||||
const firstCIJsonAttr = {}
|
||||
res.parents.forEach((item) => {
|
||||
const columns = []
|
||||
const jsonAttr = []
|
||||
item.attributes.forEach((attr) => {
|
||||
columns.push({ key: 'p_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
|
||||
if (attr.value_type === '6') {
|
||||
jsonAttr.push(attr.name)
|
||||
}
|
||||
})
|
||||
firstCIJsonAttr[item.id] = jsonAttr
|
||||
firstCIColumns[item.id] = columns
|
||||
firstCIColumns[item.id].push({
|
||||
key: 'p_operation',
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
width: '60px',
|
||||
fixed: 'right',
|
||||
slots: {
|
||||
default: 'operation_default',
|
||||
},
|
||||
align: 'center',
|
||||
})
|
||||
})
|
||||
|
||||
this.firstCIColumns = firstCIColumns
|
||||
this.firstCIJsonAttr = firstCIJsonAttr
|
||||
},
|
||||
async getChildCITypes() {
|
||||
await getCITypeChildren(this.typeId)
|
||||
.then((res) => {
|
||||
this.childCITypes = res.children
|
||||
const res = await getCITypeChildren(this.typeId)
|
||||
|
||||
const secondCIColumns = {}
|
||||
const secondCIJsonAttr = {}
|
||||
res.children.forEach((item) => {
|
||||
const columns = []
|
||||
const jsonAttr = []
|
||||
item.attributes.forEach((attr) => {
|
||||
columns.push({ key: 'c_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
|
||||
if (attr.value_type === '6') {
|
||||
jsonAttr.push(attr.name)
|
||||
}
|
||||
})
|
||||
secondCIJsonAttr[item.id] = jsonAttr
|
||||
secondCIColumns[item.id] = columns
|
||||
secondCIColumns[item.id].push({
|
||||
key: 'c_operation',
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
width: '60px',
|
||||
fixed: 'right',
|
||||
slots: {
|
||||
default: 'operation_default',
|
||||
},
|
||||
align: 'center',
|
||||
})
|
||||
})
|
||||
|
||||
this.secondCIColumns = secondCIColumns
|
||||
this.secondCIJsonAttr = secondCIJsonAttr
|
||||
this.childCITypes = res.children
|
||||
for (let i = 0; i < res.children.length; i++) {
|
||||
await getCanEditByParentIdChildId(this.typeId, res.children[i].id).then((c_res) => {
|
||||
this.canEdit = {
|
||||
..._.cloneDeep(this.canEdit),
|
||||
[res.children[i].id]: c_res.result,
|
||||
}
|
||||
})
|
||||
.catch((e) => {})
|
||||
}
|
||||
const secondCIColumns = {}
|
||||
const secondCIJsonAttr = {}
|
||||
res.children.forEach((item) => {
|
||||
const columns = []
|
||||
const jsonAttr = []
|
||||
item.attributes.forEach((attr) => {
|
||||
columns.push({ key: 'c_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
|
||||
if (attr.value_type === '6') {
|
||||
jsonAttr.push(attr.name)
|
||||
}
|
||||
})
|
||||
secondCIJsonAttr[item.id] = jsonAttr
|
||||
secondCIColumns[item.id] = columns
|
||||
secondCIColumns[item.id].push({
|
||||
key: 'c_operation',
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
width: '60px',
|
||||
fixed: 'right',
|
||||
slots: {
|
||||
default: 'operation_default',
|
||||
},
|
||||
align: 'center',
|
||||
})
|
||||
})
|
||||
|
||||
this.secondCIColumns = secondCIColumns
|
||||
this.secondCIJsonAttr = secondCIJsonAttr
|
||||
},
|
||||
reload() {
|
||||
this.init()
|
||||
|
@@ -28,7 +28,6 @@
|
||||
placeholder="请选择"
|
||||
v-if="attr.is_choice"
|
||||
:mode="attr.is_list ? 'multiple' : 'default'"
|
||||
:multiple="attr.is_list"
|
||||
showSearch
|
||||
allowClear
|
||||
>
|
||||
|
@@ -53,8 +53,8 @@ export default {
|
||||
return postCITypeDiscovery(this.CITypeId, { adr_id: id, interval: type === 'agent' ? 300 : 3600 })
|
||||
})
|
||||
await Promise.all(promises)
|
||||
.then(() => {
|
||||
this.getCITypeDiscovery(this.selectedIds[0].id)
|
||||
.then((res) => {
|
||||
this.getCITypeDiscovery(res[0].id)
|
||||
this.$message.success('添加成功')
|
||||
})
|
||||
.catch(() => {
|
||||
|
@@ -2,20 +2,22 @@
|
||||
<div class="attr-ad" :style="{ height: `${windowHeight - 104}px` }">
|
||||
<div v-if="adCITypeList && adCITypeList.length">
|
||||
<a-tabs size="small" v-model="currentTab">
|
||||
<a-tab-pane v-for="item in adCITypeList" :key="item.adr_id">
|
||||
<a-tab-pane v-for="item in adCITypeList" :key="item.id">
|
||||
<a-space slot="tab">
|
||||
<span>{{ getADCITypeParam(item.adr_id) }}</span>
|
||||
<span v-if="item.extra_option && item.extra_option.alias">{{ item.extra_option.alias }}</span>
|
||||
<span v-else>{{ getADCITypeParam(item.adr_id) }}</span>
|
||||
<a-icon type="close-circle" @click="(e) => deleteADT(e, item)" />
|
||||
</a-space>
|
||||
<AttrADTabpane
|
||||
:ref="`attrAdTabpane_${item.adr_id}`"
|
||||
:currentTab="item.adr_id"
|
||||
:ref="`attrAdTabpane_${item.id}`"
|
||||
:adr_id="item.adr_id"
|
||||
:adrList="adrList"
|
||||
:adCITypeList="adCITypeList"
|
||||
:currentAdt="item"
|
||||
:ciTypeAttributes="ciTypeAttributes"
|
||||
:currentAdr="getADCITypeParam(item.adr_id, undefined, true)"
|
||||
@openEditDrawer="(data, type, adType) => openEditDrawer(data, type, adType)"
|
||||
@handleSave="getCITypeDiscovery"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-space
|
||||
@@ -134,8 +136,8 @@ export default {
|
||||
async getCITypeDiscovery(currentTab) {
|
||||
await getCITypeDiscovery(this.CITypeId).then((res) => {
|
||||
this.adCITypeList = res.filter((item) => item.adr_id)
|
||||
if (res && res.length && !this.currentTab) {
|
||||
this.currentTab = res[0].adr_id
|
||||
if (this.adCITypeList && this.adCITypeList.length && !this.currentTab) {
|
||||
this.currentTab = this.adCITypeList[0].id
|
||||
}
|
||||
if (currentTab) {
|
||||
this.currentTab = currentTab
|
||||
@@ -156,7 +158,7 @@ export default {
|
||||
e.stopPropagation()
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: `确认删除 【${this.getADCITypeParam(item.adr_id)}】`,
|
||||
title: `确认删除 【${item?.extra_option?.alias || this.getADCITypeParam(item.adr_id)}】`,
|
||||
content: (h) => (
|
||||
<div>
|
||||
<a-checkbox v-model={that.deletePlugin}>删除插件</a-checkbox>
|
||||
@@ -164,18 +166,22 @@ export default {
|
||||
),
|
||||
onOk() {
|
||||
deleteCITypeDiscovery(item.id).then(async () => {
|
||||
if (that.currentTab === item.adr_id) {
|
||||
if (that.currentTab === item.id) {
|
||||
that.currentTab = ''
|
||||
}
|
||||
that.deletePlugin = false
|
||||
that.$message.success('删除成功!')
|
||||
that.getCITypeDiscovery()
|
||||
if (that.deletePlugin) {
|
||||
await deleteDiscovery(item.adr_id)
|
||||
await deleteDiscovery(item.adr_id).finally(() => {
|
||||
that.deletePlugin = false
|
||||
})
|
||||
}
|
||||
that.deletePlugin = false
|
||||
})
|
||||
},
|
||||
onCancel() {},
|
||||
onCancel() {
|
||||
that.deletePlugin = false
|
||||
},
|
||||
})
|
||||
},
|
||||
openEditDrawer(data, type, adType) {
|
||||
@@ -183,12 +189,12 @@ export default {
|
||||
},
|
||||
async updateNotInner(adr) {
|
||||
const _idx = this.adCITypeList.findIndex((item) => item.adr_id === adr.id)
|
||||
let res
|
||||
if (_idx < 0) {
|
||||
await postCITypeDiscovery(this.CITypeId, { adr_id: adr.id, interval: 300 })
|
||||
res = await postCITypeDiscovery(this.CITypeId, { adr_id: adr.id, interval: 300 })
|
||||
}
|
||||
await this.getDiscovery()
|
||||
await this.getCITypeDiscovery()
|
||||
this.currentTab = adr.id
|
||||
await this.getCITypeDiscovery(res?.id ?? undefined)
|
||||
this.$nextTick(() => {
|
||||
this.$refs[`attrAdTabpane_${this.currentTab}`][0].init()
|
||||
})
|
||||
|
@@ -14,6 +14,7 @@
|
||||
<span>编辑</span>
|
||||
</a-space>
|
||||
</a>
|
||||
<div>别名:<a-input v-model="alias" style="width:200px;" /></div>
|
||||
<div class="attr-ad-header">字段映射</div>
|
||||
<vxe-table
|
||||
v-if="adrType === 'agent'"
|
||||
@@ -56,7 +57,7 @@
|
||||
:ruleName="adrName"
|
||||
:ciTypeAttributes="ciTypeAttributes"
|
||||
:adCITypeList="adCITypeList"
|
||||
:currentTab="currentTab"
|
||||
:currentTab="adr_id"
|
||||
:style="{ marginBottom: '20px' }"
|
||||
/>
|
||||
<a-form-model
|
||||
@@ -133,7 +134,7 @@ export default {
|
||||
name: 'AttrADTabpane',
|
||||
components: { Vcrontab, HttpSnmpAD, CMDBExprDrawer, MonitorNodeSetting },
|
||||
props: {
|
||||
currentTab: {
|
||||
adr_id: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
@@ -187,6 +188,7 @@ export default {
|
||||
},
|
||||
],
|
||||
form3: this.$form.createForm(this, { name: 'snmp_form' }),
|
||||
alias: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -205,7 +207,7 @@ export default {
|
||||
},
|
||||
agentTypeRadioList() {
|
||||
const { permissions = [] } = this.userRoles
|
||||
if (permissions.includes('cmdb_admin') || permissions.includes('admin')) {
|
||||
if ((permissions.includes('cmdb_admin') || permissions.includes('admin')) && this.adrType !== 'http') {
|
||||
return [
|
||||
{ value: 'all', label: '所有节点' },
|
||||
{ value: 'agent_id', label: '指定节点' },
|
||||
@@ -221,8 +223,9 @@ export default {
|
||||
mounted() {},
|
||||
methods: {
|
||||
init() {
|
||||
const _find = this.adrList.find((item) => Number(item.id) === Number(this.currentTab))
|
||||
const _findADT = this.adCITypeList.find((item) => Number(item.adr_id) === Number(this.currentTab))
|
||||
const _find = this.adrList.find((item) => Number(item.id) === Number(this.adr_id))
|
||||
const _findADT = this.adCITypeList.find((item) => Number(item.id) === Number(this.currentAdt.id))
|
||||
this.alias = _findADT?.extra_option?.alias ?? ''
|
||||
if (this.adrType === 'http') {
|
||||
const { category = undefined, key = '', secret = '' } = _findADT?.extra_option ?? {}
|
||||
this.form2 = {
|
||||
@@ -294,7 +297,7 @@ export default {
|
||||
this.cron = cron
|
||||
},
|
||||
handleSave() {
|
||||
const { currentAdt } = this
|
||||
const { currentAdt, alias } = this
|
||||
let params
|
||||
if (this.adrType === 'http') {
|
||||
params = {
|
||||
@@ -360,9 +363,15 @@ export default {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (params.extra_option) {
|
||||
params.extra_option.alias = alias
|
||||
} else {
|
||||
params.extra_option = {}
|
||||
params.extra_option.alias = alias
|
||||
}
|
||||
putCITypeDiscovery(currentAdt.id, params).then((res) => {
|
||||
this.$message.success('保存成功')
|
||||
this.$emit('handleSave')
|
||||
})
|
||||
},
|
||||
handleOpenCmdb() {
|
||||
|
@@ -104,6 +104,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
attributes: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const propertyList = [
|
||||
@@ -160,7 +164,7 @@ export default {
|
||||
})
|
||||
},
|
||||
openTrigger() {
|
||||
this.$refs.triggerForm.open(this.property)
|
||||
this.$refs.triggerForm.open(this.property, this.attributes)
|
||||
},
|
||||
handleCalcComputed() {
|
||||
const that = this
|
||||
|
@@ -98,6 +98,7 @@
|
||||
:property="item"
|
||||
@ok="handleOk"
|
||||
:CITypeId="CITypeId"
|
||||
:attributes="attributes"
|
||||
/>
|
||||
<i></i> <i></i> <i></i> <i></i> <i></i>
|
||||
</draggable>
|
||||
@@ -137,6 +138,7 @@
|
||||
:property="item"
|
||||
@ok="handleOk"
|
||||
:CITypeId="CITypeId"
|
||||
:attributes="attributes"
|
||||
/>
|
||||
<i></i> <i></i> <i></i> <i></i> <i></i>
|
||||
</draggable>
|
||||
|
@@ -40,20 +40,18 @@
|
||||
<a-menu-item key="0">
|
||||
<a-upload
|
||||
name="file"
|
||||
accept="json"
|
||||
accept=".json"
|
||||
:showUploadList="false"
|
||||
style="display: inline-block"
|
||||
action="/api/v0.1/ci_types/template/import/file "
|
||||
action="/api/v0.1/ci_types/template/import/file"
|
||||
@change="changeUploadFile"
|
||||
>
|
||||
<a-space
|
||||
><a><a-icon type="upload"/></a><a>导入</a></a-space
|
||||
>
|
||||
<a><a-icon type="upload"/></a><a> 导入</a>
|
||||
</a-upload>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="1">
|
||||
<a-space>
|
||||
<a><a-icon type="download"/></a>
|
||||
<a href="/api/v0.1/ci_types/template/export/file">导出</a>
|
||||
<a href="/api/v0.1/ci_types/template/export/file"><a-icon type="download" /> 导出</a>
|
||||
</a-space>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
@@ -134,6 +132,12 @@
|
||||
<a-space class="ci-types-left-detail-action">
|
||||
<a><a-icon type="user-add" @click="(e) => handlePerm(e, ci)"/></a>
|
||||
<a><a-icon type="edit" @click="(e) => handleEdit(e, ci)"/></a>
|
||||
<a
|
||||
v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"
|
||||
@click="(e) => handleDownloadCiType(e, ci)"
|
||||
><a-icon
|
||||
type="download"
|
||||
/></a>
|
||||
<a style="color: red" @click="(e) => handleDelete(e, ci)"><a-icon type="delete"/></a>
|
||||
</a-space>
|
||||
</div>
|
||||
@@ -293,6 +297,7 @@ import SplitPane from '@/components/SplitPane'
|
||||
import CMDBGrant from '../../components/cmdbGrant'
|
||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||
import AttributeStore from './attributeStore.vue'
|
||||
import { getAllDepAndEmployee } from '@/api/company'
|
||||
|
||||
export default {
|
||||
name: 'CITypes',
|
||||
@@ -308,6 +313,7 @@ export default {
|
||||
OpsMoveIcon,
|
||||
AttributeStore,
|
||||
},
|
||||
inject: ['reload'],
|
||||
data() {
|
||||
return {
|
||||
emptyImage,
|
||||
@@ -342,6 +348,8 @@ export default {
|
||||
|
||||
orderSelectionOptions: [],
|
||||
default_order_asc: '1',
|
||||
|
||||
allTreeDepAndEmp: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -405,9 +413,13 @@ export default {
|
||||
resource_type: () => {
|
||||
return this.resource_type
|
||||
},
|
||||
provide_allTreeDepAndEmp: () => {
|
||||
return this.allTreeDepAndEmp
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getAllDepAndEmployee()
|
||||
const _currentId = localStorage.getItem('ops_cityps_currentId')
|
||||
if (_currentId) {
|
||||
this.currentId = _currentId
|
||||
@@ -419,6 +431,11 @@ export default {
|
||||
this.getAttributes()
|
||||
},
|
||||
methods: {
|
||||
getAllDepAndEmployee() {
|
||||
getAllDepAndEmployee({ block: 0 }).then((res) => {
|
||||
this.allTreeDepAndEmp = res
|
||||
})
|
||||
},
|
||||
async loadCITypes(isResetCurrentId = false) {
|
||||
const groups = await getCITypeGroupsConfig({ need_other: true })
|
||||
let alreadyReset = false
|
||||
@@ -704,6 +721,21 @@ export default {
|
||||
},
|
||||
})
|
||||
},
|
||||
handleDownloadCiType(e, ci) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const x = new XMLHttpRequest()
|
||||
x.open('GET', `/api/v0.1/ci_types/${ci.id}/template/export`, true)
|
||||
x.responseType = 'blob'
|
||||
x.onload = function(e) {
|
||||
const url = window.URL.createObjectURL(x.response)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `${ci.alias || ci.name}.json`
|
||||
a.click()
|
||||
}
|
||||
x.send()
|
||||
},
|
||||
resetRoute() {
|
||||
resetRouter()
|
||||
const roles = store.getters.roles
|
||||
@@ -768,6 +800,19 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
changeUploadFile({ file, fileList, event }) {
|
||||
const key = 'upload'
|
||||
if (file.status === 'uploading') {
|
||||
this.$message.loading({ content: '正在导入中', key, duration: 0 })
|
||||
}
|
||||
if (file.status === 'done') {
|
||||
this.$message.success({ content: '导入成功', key, duration: 2 })
|
||||
this.reload()
|
||||
}
|
||||
if (file.status === 'error') {
|
||||
this.$message.error({ content: '导入失败,请稍后重试', key, duration: 2 })
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@@ -372,7 +372,7 @@ export default {
|
||||
},
|
||||
async open(property, attrList) {
|
||||
this.visible = true
|
||||
this.getNoticeConfigAppBot()
|
||||
await this.getNoticeConfigAppBot()
|
||||
this.attrList = attrList
|
||||
if (property.has_trigger) {
|
||||
this.triggerId = property.trigger.id
|
||||
|
@@ -71,7 +71,6 @@ import _ from 'lodash'
|
||||
import { getTriggerList, deleteTrigger, updateTrigger } from '../../api/CIType'
|
||||
import { getCITypeAttributesById } from '../../api/CITypeAttr'
|
||||
import TriggerForm from './triggerForm.vue'
|
||||
import { getAllDepAndEmployee } from '@/api/company'
|
||||
|
||||
export default {
|
||||
name: 'TriggerTable',
|
||||
@@ -86,7 +85,6 @@ export default {
|
||||
return {
|
||||
tableData: [],
|
||||
attrList: [],
|
||||
allTreeDepAndEmp: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -97,20 +95,9 @@ export default {
|
||||
provide() {
|
||||
return {
|
||||
refresh: this.getTableData,
|
||||
provide_allTreeDepAndEmp: () => {
|
||||
return this.allTreeDepAndEmp
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getAllDepAndEmployee()
|
||||
},
|
||||
methods: {
|
||||
getAllDepAndEmployee() {
|
||||
getAllDepAndEmployee({ block: 0 }).then((res) => {
|
||||
this.allTreeDepAndEmp = res
|
||||
})
|
||||
},
|
||||
async getTableData() {
|
||||
const [triggerList, attrList] = await Promise.all([
|
||||
getTriggerList(this.CITypeId),
|
||||
|
@@ -372,7 +372,7 @@ export default {
|
||||
width: 3,
|
||||
fontColor: '#ffffff',
|
||||
bgColor: ['#6ABFFE', '#5375EB'],
|
||||
chartColor: '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD', // 图表颜色
|
||||
chartColor: '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF', // 图表颜色
|
||||
isShowPreview: false,
|
||||
filterExp: undefined,
|
||||
previewData: null,
|
||||
@@ -410,7 +410,7 @@ export default {
|
||||
this.width = width
|
||||
this.chartType = chartType
|
||||
this.filterExp = item?.options?.filter ?? ''
|
||||
this.chartColor = item?.options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD'
|
||||
this.chartColor = item?.options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF'
|
||||
this.isShadow = item?.options?.isShadow ?? false
|
||||
|
||||
if (chartType === 'count') {
|
||||
|
@@ -24,7 +24,7 @@ export const category_1_bar_options = (data, options) => {
|
||||
})
|
||||
return {
|
||||
|
||||
color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(','),
|
||||
color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(','),
|
||||
grid: {
|
||||
top: 15,
|
||||
left: 'left',
|
||||
@@ -83,7 +83,7 @@ export const category_1_bar_options = (data, options) => {
|
||||
export const category_1_line_options = (data, options) => {
|
||||
const xData = Object.keys(data)
|
||||
return {
|
||||
color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(','),
|
||||
color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(','),
|
||||
grid: {
|
||||
top: 15,
|
||||
left: 'left',
|
||||
@@ -117,7 +117,7 @@ export const category_1_line_options = (data, options) => {
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [{
|
||||
offset: 0, color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(',')[0] // 0% 处的颜色
|
||||
offset: 0, color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(',')[0] // 0% 处的颜色
|
||||
}, {
|
||||
offset: 1, color: '#ffffff' // 100% 处的颜色
|
||||
}],
|
||||
@@ -131,7 +131,7 @@ export const category_1_line_options = (data, options) => {
|
||||
|
||||
export const category_1_pie_options = (data, options) => {
|
||||
return {
|
||||
color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(','),
|
||||
color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(','),
|
||||
grid: {
|
||||
top: 10,
|
||||
left: 'left',
|
||||
@@ -181,7 +181,7 @@ export const category_2_bar_options = (data, options, chartType) => {
|
||||
})
|
||||
const legend = [...new Set(_legend)]
|
||||
return {
|
||||
color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(','),
|
||||
color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(','),
|
||||
grid: {
|
||||
top: 15,
|
||||
left: 'left',
|
||||
@@ -249,7 +249,7 @@ export const category_2_bar_options = (data, options, chartType) => {
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [{
|
||||
offset: 0, color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(',')[index % 8] // 0% 处的颜色
|
||||
offset: 0, color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(',')[index % 8] // 0% 处的颜色
|
||||
}, {
|
||||
offset: 1, color: '#ffffff' // 100% 处的颜色
|
||||
}],
|
||||
@@ -269,7 +269,7 @@ export const category_2_pie_options = (data, options) => {
|
||||
})
|
||||
})
|
||||
return {
|
||||
color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(','),
|
||||
color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(','),
|
||||
grid: {
|
||||
top: 15,
|
||||
left: 'left',
|
||||
|
@@ -24,10 +24,8 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
list: [
|
||||
'#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD',
|
||||
'#C1A9DC,#E2B5CD,#EE8EBC,#8483C3,#4D66BD,#213764,#D9B6E9,#DD88EB',
|
||||
'#6FC4DF,#9FE8CE,#16B4BE,#86E6FB,#1871A3,#E1BF8D,#ED8D8D,#DD88EB',
|
||||
'#F8B751,#FC9054,#FFE380,#DF963F,#AB5200,#EA9387,#FFBB7C,#D27467',
|
||||
'#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF',
|
||||
'#9BA1F9,#0F2BA8,#A2EBFE,#4982F6,#FEB09C,#6C78E8,#FFDDAB,#4D66BD',
|
||||
],
|
||||
}
|
||||
},
|
||||
|
@@ -61,7 +61,7 @@
|
||||
</vxe-column>
|
||||
<vxe-column field="type_id" title="模型" width="150px">
|
||||
<template #default="{ row }">
|
||||
{{ row.operate_type === '删除模型' ? row.change.alias : row.type_id}}
|
||||
{{ row.operate_type === '删除模型' ? row.change.alias : row.type_id }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="changeDescription" title="描述">
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user